Newer
Older
minerva / Kernel / FileSystem / FATFS / SFNUtilities.cpp
@minerva minerva on 13 Jul 4 KB Initial commit
/*
 * Copyright (c) 2024, the SerenityOS developers.
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <AK/CharacterTypes.h>
#include <Kernel/FileSystem/FATFS/SFNUtilities.h>

#ifdef KERNEL
#    include <Kernel/Library/KString.h>
#else
#    include <AK/ByteString.h>
#endif

namespace Kernel::SFNUtils {

ErrorOr<NonnullRefPtr<SFN>> 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<ByteBuffer> 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<ByteBuffer> 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<char>({ '$', '%', '\'', '-', '_', '@', ' ', '~', '`', '!', '(', ')' });

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<NonnullRefPtr<SFN>> 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);
    }
}

}