/* * Copyright (c) 2024, the SerenityOS developers. * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #ifdef KERNEL # include #else # include #endif namespace Kernel::SFNUtils { ErrorOr> SFN::try_create(ByteBuffer name, ByteBuffer extension, size_t unique) { VERIFY(name.size() > 0); auto new_name = TRY(name.slice(0, min(name.size(), 6))); auto new_extension = TRY(extension.slice(0, min(extension.size(), 3))); return adopt_nonnull_ref_or_enomem(new (nothrow) SFN(move(new_name), move(new_extension), unique)); } SFN::SFN(ByteBuffer name, ByteBuffer extension, size_t unique) : m_name(move(name)) , m_extension(move(extension)) , m_unique(unique) { } size_t SFN::digits() const { size_t digits = 0; size_t w = m_unique; while (w /= 10) ++digits; return digits; } ErrorOr SFN::serialize_name() const { auto name = TRY(ByteBuffer::copy(m_name.data(), m_name.size() - digits())); TRY(name.try_ensure_capacity(8)); #ifdef KERNEL auto suffix = TRY(KString::formatted("~{}", m_unique)); name.append(suffix->bytes()); #else auto suffix = ByteString::formatted("~{}", m_unique); name.append(suffix.bytes()); #endif while (name.size() < 8) name.append(' '); return name; } ErrorOr SFN::serialize_extension() const { auto extension = TRY(ByteBuffer::copy(m_extension)); TRY(extension.try_ensure_capacity(3)); while (extension.size() < 3) extension.append(' '); return extension; } static constexpr auto valid_misc_sfn_chars = to_array({ '$', '%', '\'', '-', '_', '@', ' ', '~', '`', '!', '(', ')' }); static bool is_valid_sfn_char(char c) { if (c >= 'A' && c <= 'Z') return true; if (c >= '0' && c <= '9') return true; return valid_misc_sfn_chars.contains_slow(c); } bool is_valid_sfn(StringView sfn) { StringView name = {}; StringView extension = {}; auto dot = sfn.find('.'); if (!dot.has_value()) { name = sfn; } else { if (*dot + 1 >= sfn.length()) return false; name = sfn.substring_view(0, *dot); extension = sfn.substring_view(*dot + 1, sfn.length() - *dot - 1); } if (name.length() > 8 || extension.length() > 3) return false; for (char c : name) { if (!is_valid_sfn_char(c)) return false; } for (char c : extension) { if (!is_valid_sfn_char(c)) return false; } if (name.length() == 0 || name[0] == ' ') return false; return true; } // http://www.osdever.net/documents/LongFileName.pdf ErrorOr> create_sfn_from_lfn(StringView lfn) { ByteBuffer out; // 1. Remove all spaces. // 2. Initial periods, trailing periods, and extra periods prior to the last embedded period are removed. lfn = lfn.trim("."sv); auto last_dot_index = lfn.find_last('.'); for (size_t i = 0; i < lfn.length(); ++i) { if (lfn[i] == ' ') continue; if (lfn[i] == '.' && i != last_dot_index.value()) continue; TRY(out.try_append(to_ascii_uppercase(lfn[i]))); } // 3. Translate all illegal 8.3 characters into "_". for (size_t i = 0; i < out.size(); ++i) { if (!is_valid_sfn_char(out[i]) && out[i] != '.') out[i] = '_'; } // 4. If the name does not contain an extension then truncate it to 6 characters. // If the names does contain an extension, then truncate the first part to 6 characters and the extension to 3 characters. auto last_period = StringView(out).find_last('.'); if (!last_period.has_value()) { auto name = TRY(out.slice(0, min(out.size(), 6))); return SFNUtils::SFN::try_create(move(name), {}, 1); } else { auto name = TRY(out.slice(0, min(last_period.value(), 6))); size_t extension_length = min(out.size() - last_period.value() - 1, 3); auto extension = TRY(out.slice(last_period.value() + 1, extension_length)); return SFNUtils::SFN::try_create(move(name), move(extension), 1); } } }