From 135ca3fa1b676c172c1bb2ec0e92487d0af2f4a5 Mon Sep 17 00:00:00 2001 From: Undefine Date: Sun, 18 Sep 2022 18:21:10 +0200 Subject: [PATCH] Kernel: Add support for the FAT32 filesystem This commit adds read-only support for the FAT32 filesystem. It also includes support for long file names. --- Kernel/CMakeLists.txt | 1 + Kernel/Debug.h.in | 4 + Kernel/FileSystem/FATFileSystem.cpp | 348 ++++++++++++++++++++++++++ Kernel/FileSystem/FATFileSystem.h | 196 +++++++++++++++ Kernel/Syscalls/mount.cpp | 2 + Meta/CMake/all_the_debug_macros.cmake | 1 + 6 files changed, 552 insertions(+) create mode 100644 Kernel/FileSystem/FATFileSystem.cpp create mode 100644 Kernel/FileSystem/FATFileSystem.h diff --git a/Kernel/CMakeLists.txt b/Kernel/CMakeLists.txt index 02ed2d5c03f..1b575db4a58 100644 --- a/Kernel/CMakeLists.txt +++ b/Kernel/CMakeLists.txt @@ -111,6 +111,7 @@ set(KERNEL_SOURCES FileSystem/DevPtsFS.cpp FileSystem/DevTmpFS.cpp FileSystem/Ext2FileSystem.cpp + FileSystem/FATFileSystem.cpp FileSystem/FIFO.cpp FileSystem/File.cpp FileSystem/FileBackedFileSystem.cpp diff --git a/Kernel/Debug.h.in b/Kernel/Debug.h.in index b12b5f03a91..a0c94e52d56 100644 --- a/Kernel/Debug.h.in +++ b/Kernel/Debug.h.in @@ -91,6 +91,10 @@ #cmakedefine01 EXT2_VERY_DEBUG #endif +#ifndef FAT_DEBUG +#cmakedefine01 FAT_DEBUG +#endif + #ifndef FRAMEBUFFER_DEVICE_DEBUG #cmakedefine01 FRAMEBUFFER_DEVICE_DEBUG #endif diff --git a/Kernel/FileSystem/FATFileSystem.cpp b/Kernel/FileSystem/FATFileSystem.cpp new file mode 100644 index 00000000000..ee06488b869 --- /dev/null +++ b/Kernel/FileSystem/FATFileSystem.cpp @@ -0,0 +1,348 @@ +/* + * Copyright (c) 2022, Undefine + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include + +namespace Kernel { + +ErrorOr> FATFS::try_create(OpenFileDescription& file_description) +{ + return TRY(adopt_nonnull_lock_ref_or_enomem(new (nothrow) FATFS(file_description))); +} + +FATFS::FATFS(OpenFileDescription& file_description) + : BlockBasedFileSystem(file_description) +{ +} + +ErrorOr FATFS::initialize() +{ + MutexLocker locker(m_lock); + + m_boot_record = TRY(KBuffer::try_create_with_size("FATFS: Boot Record"sv, m_logical_block_size)); + auto boot_record_buffer = UserOrKernelBuffer::for_kernel_buffer(m_boot_record->data()); + TRY(raw_read(0, boot_record_buffer)); + + if constexpr (FAT_DEBUG) { + dbgln("FATFS: oem_identifier: {}", boot_record()->oem_identifier); + dbgln("FATFS: bytes_per_sector: {}", boot_record()->bytes_per_sector); + dbgln("FATFS: sectors_per_cluster: {}", boot_record()->sectors_per_cluster); + dbgln("FATFS: reserved_sector_count: {}", boot_record()->reserved_sector_count); + dbgln("FATFS: fat_count: {}", boot_record()->fat_count); + dbgln("FATFS: root_directory_entry_count: {}", boot_record()->root_directory_entry_count); + dbgln("FATFS: media_descriptor_type: {}", boot_record()->media_descriptor_type); + dbgln("FATFS: sectors_per_track: {}", boot_record()->sectors_per_track); + dbgln("FATFS: head_count: {}", boot_record()->head_count); + dbgln("FATFS: hidden_sector_count: {}", boot_record()->hidden_sector_count); + dbgln("FATFS: sector_count: {}", boot_record()->sector_count); + dbgln("FATFS: sectors_per_fat: {}", boot_record()->sectors_per_fat); + dbgln("FATFS: flags: {}", boot_record()->flags); + dbgln("FATFS: fat_version: {}", boot_record()->fat_version); + dbgln("FATFS: root_directory_cluster: {}", boot_record()->root_directory_cluster); + dbgln("FATFS: fs_info_sector: {}", boot_record()->fs_info_sector); + dbgln("FATFS: backup_boot_sector: {}", boot_record()->backup_boot_sector); + dbgln("FATFS: drive_number: {}", boot_record()->drive_number); + dbgln("FATFS: volume_id: {}", boot_record()->volume_id); + } + + if (boot_record()->signature != signature_1 && boot_record()->signature != signature_2) { + dbgln("FATFS: Invalid signature"); + return EINVAL; + } + + m_logical_block_size = boot_record()->bytes_per_sector; + set_block_size(m_logical_block_size); + + u32 root_directory_sectors = ((boot_record()->root_directory_entry_count * sizeof(FATEntry)) + (m_logical_block_size - 1)) / m_logical_block_size; + m_first_data_sector = boot_record()->reserved_sector_count + (boot_record()->fat_count * boot_record()->sectors_per_fat) + root_directory_sectors; + + TRY(BlockBasedFileSystem::initialize()); + + FATEntry root_entry {}; + + root_entry.first_cluster_low = boot_record()->root_directory_cluster & 0xFFFF; + root_entry.first_cluster_high = boot_record()->root_directory_cluster >> 16; + + root_entry.attributes = FATAttributes::Directory; + m_root_inode = TRY(FATInode::create(*this, root_entry)); + + return {}; +} + +Inode& FATFS::root_inode() +{ + return *m_root_inode; +} + +BlockBasedFileSystem::BlockIndex FATFS::first_block_of_cluster(u32 cluster) const +{ + return ((cluster - first_data_cluster) * boot_record()->sectors_per_cluster) + m_first_data_sector; +} + +ErrorOr> FATInode::create(FATFS& fs, FATEntry entry, Vector const& lfn_entries) +{ + auto filename = TRY(compute_filename(entry, lfn_entries)); + return adopt_nonnull_lock_ref_or_enomem(new (nothrow) FATInode(fs, entry, move(filename))); +} + +FATInode::FATInode(FATFS& fs, FATEntry entry, NonnullOwnPtr filename) + : Inode(fs, first_cluster()) + , m_entry(entry) + , m_filename(move(filename)) +{ + dbgln_if(FAT_DEBUG, "FATFS: Creating inode {} with filename \"{}\"", index(), m_filename); + + m_metadata = { + .inode = identifier(), + .size = m_entry.file_size, + .mode = static_cast((has_flag(m_entry.attributes, FATAttributes::Directory) ? S_IFDIR : S_IFREG) | 0777), + .uid = 0, + .gid = 0, + .link_count = 0, + .atime = fat_date_time(m_entry.last_accessed_date, { 0 }), + .ctime = fat_date_time(m_entry.creation_date, m_entry.creation_time), + .mtime = fat_date_time(m_entry.modification_date, m_entry.modification_time), + .dtime = 0, + .block_count = 0, + .block_size = 0, + .major_device = 0, + .minor_device = 0, + }; +} + +ErrorOr> FATInode::compute_block_list() +{ + VERIFY(m_inode_lock.is_locked()); + + dbgln_if(FAT_DEBUG, "FATFS: computing block list for inode {}", index()); + + u32 cluster = first_cluster(); + + Vector block_list; + + auto fat_sector = TRY(KBuffer::try_create_with_size("FATFS: FAT read buffer"sv, fs().m_logical_block_size)); + auto fat_sector_buffer = UserOrKernelBuffer::for_kernel_buffer(fat_sector->data()); + + while (cluster < no_more_clusters) { + dbgln_if(FAT_DEBUG, "FATFS: Appending cluster {} to inode {}'s cluster chain", cluster, index()); + + BlockBasedFileSystem::BlockIndex first_block = fs().first_block_of_cluster(cluster); + for (u8 i = 0; i < fs().boot_record()->sectors_per_cluster; i++) + block_list.append(BlockBasedFileSystem::BlockIndex { first_block.value() + i }); + + u32 fat_offset = cluster * sizeof(u32); + u32 fat_sector_index = fs().boot_record()->reserved_sector_count + (fat_offset / fs().m_logical_block_size); + u32 entry_offset = fat_offset % fs().m_logical_block_size; + + TRY(fs().raw_read(fat_sector_index, fat_sector_buffer)); + + cluster = *reinterpret_cast(&fat_sector->data()[entry_offset]); + cluster &= cluster_number_mask; + } + + return block_list; +} + +ErrorOr> FATInode::read_block_list() +{ + VERIFY(m_inode_lock.is_locked()); + + dbgln_if(FAT_DEBUG, "FATFS: reading block list for inode {} ({} blocks)", index(), m_block_list.size()); + + if (m_block_list.is_empty()) + m_block_list = TRY(compute_block_list()); + + auto builder = TRY(KBufferBuilder::try_create()); + + u8 buffer[512]; + VERIFY(fs().m_logical_block_size <= sizeof(buffer)); + auto buf = UserOrKernelBuffer::for_kernel_buffer(buffer); + + for (BlockBasedFileSystem::BlockIndex block : m_block_list) { + dbgln_if(FAT_DEBUG, "FATFS: reading block: {}", block); + TRY(fs().raw_read(block, buf)); + TRY(builder.append((char const*)buffer, fs().m_logical_block_size)); + } + + auto blocks = builder.build(); + if (!blocks) + return ENOMEM; + return blocks.release_nonnull(); +} + +ErrorOr> FATInode::traverse(Function(LockRefPtr)> callback) +{ + VERIFY(has_flag(m_entry.attributes, FATAttributes::Directory)); + + Vector lfn_entries; + auto blocks = TRY(read_block_list()); + + for (u32 i = 0; i < blocks->size() / sizeof(FATEntry); i++) { + auto* entry = reinterpret_cast(blocks->data() + i * sizeof(FATEntry)); + if (entry->filename[0] == end_entry_byte) { + dbgln_if(FAT_DEBUG, "FATFS: Found end entry"); + return nullptr; + } else if (static_cast(entry->filename[0]) == unused_entry_byte) { + dbgln_if(FAT_DEBUG, "FATFS: Found unused entry"); + lfn_entries.clear(); + } else if (entry->attributes == FATAttributes::LongFileName) { + dbgln_if(FAT_DEBUG, "FATFS: Found LFN entry"); + TRY(lfn_entries.try_append(*reinterpret_cast(entry))); + } else { + dbgln_if(FAT_DEBUG, "FATFS: Found 8.3 entry"); + lfn_entries.reverse(); + auto inode = TRY(FATInode::create(fs(), *entry, lfn_entries)); + if (TRY(callback(inode))) + return inode; + lfn_entries.clear(); + } + } + + return EINVAL; +} + +ErrorOr> FATInode::compute_filename(FATEntry& entry, Vector const& lfn_entries) +{ + if (lfn_entries.is_empty()) { + StringBuilder filename; + filename.append(byte_terminated_string(StringView(entry.filename, normal_filename_length), ' ')); + if (entry.extension[0] != ' ') { + filename.append('.'); + filename.append(byte_terminated_string(StringView(entry.extension, normal_extension_length), ' ')); + } + return TRY(KString::try_create(filename.string_view())); + } else { + StringBuilder filename; + for (auto& lfn_entry : lfn_entries) { + filename.append(lfn_entry.characters1[0]); + filename.append(lfn_entry.characters1[1]); + filename.append(lfn_entry.characters1[2]); + filename.append(lfn_entry.characters1[3]); + filename.append(lfn_entry.characters1[4]); + filename.append(lfn_entry.characters2[0]); + filename.append(lfn_entry.characters2[1]); + filename.append(lfn_entry.characters2[2]); + filename.append(lfn_entry.characters2[3]); + filename.append(lfn_entry.characters2[4]); + filename.append(lfn_entry.characters2[5]); + filename.append(lfn_entry.characters3[0]); + filename.append(lfn_entry.characters3[1]); + } + + return TRY(KString::try_create(byte_terminated_string(filename.string_view(), lfn_entry_text_termination))); + } + + VERIFY_NOT_REACHED(); +} + +time_t FATInode::fat_date_time(FATPackedDate date, FATPackedTime time) +{ + if (date.value == 0) + return 0; + + return Time::from_timestamp(first_fat_year + date.year, date.month, date.day, time.hour, time.minute, time.second * 2, 0).to_seconds(); +} + +StringView FATInode::byte_terminated_string(StringView string, u8 fill_byte) +{ + if (auto index = string.find_last_not(fill_byte); index.has_value()) + return string.substring_view(0, index.value()); + return string; +} + +u32 FATInode::first_cluster() const +{ + return (((u32)m_entry.first_cluster_high) << 16) | m_entry.first_cluster_low; +} + +ErrorOr FATInode::read_bytes_locked(off_t offset, size_t size, UserOrKernelBuffer& buffer, OpenFileDescription*) const +{ + dbgln_if(FAT_DEBUG, "FATFS: Reading inode {}: size: {} offset: {}", identifier().index(), size, offset); + + // FIXME: Read only the needed blocks instead of the whole file + auto blocks = TRY(const_cast(*this).read_block_list()); + TRY(buffer.write(blocks->data() + offset, min(size, m_block_list.size() * fs().m_logical_block_size - offset))); + + return min(size, m_block_list.size() * fs().m_logical_block_size - offset); +} + +InodeMetadata FATInode::metadata() const +{ + return m_metadata; +} + +ErrorOr FATInode::traverse_as_directory(Function(FileSystem::DirectoryEntryView const&)> callback) const +{ + MutexLocker locker(m_inode_lock); + + VERIFY(has_flag(m_entry.attributes, FATAttributes::Directory)); + + [[maybe_unused]] auto inode = TRY(const_cast(*this).traverse([&callback](auto inode) -> ErrorOr { + if (inode->m_filename->view() == "" || inode->m_filename->view() == "." || inode->m_filename->view() == "..") + return false; + TRY(callback({ inode->m_filename->view(), inode->identifier(), static_cast(inode->m_entry.attributes) })); + return false; + })); + + return {}; +} + +ErrorOr> FATInode::lookup(StringView name) +{ + MutexLocker locker(m_inode_lock); + + VERIFY(has_flag(m_entry.attributes, FATAttributes::Directory)); + + auto inode = TRY(traverse([name](auto child) -> ErrorOr { + return child->m_filename->view() == name; + })); + + if (inode.is_null()) + return ENOENT; + else + return inode.release_nonnull(); +} + +ErrorOr FATInode::write_bytes_locked(off_t, size_t, UserOrKernelBuffer const&, OpenFileDescription*) +{ + return EROFS; +} + +ErrorOr> FATInode::create_child(StringView, mode_t, dev_t, UserID, GroupID) +{ + return EROFS; +} + +ErrorOr FATInode::add_child(Inode&, StringView, mode_t) +{ + return EROFS; +} + +ErrorOr FATInode::remove_child(StringView) +{ + return EROFS; +} + +ErrorOr FATInode::chmod(mode_t) +{ + return EROFS; +} + +ErrorOr FATInode::chown(UserID, GroupID) +{ + return EROFS; +} + +ErrorOr FATInode::flush_metadata() +{ + return EROFS; +} + +} diff --git a/Kernel/FileSystem/FATFileSystem.h b/Kernel/FileSystem/FATFileSystem.h new file mode 100644 index 00000000000..807191aaa3d --- /dev/null +++ b/Kernel/FileSystem/FATFileSystem.h @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2022, Undefine + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace Kernel { + +struct [[gnu::packed]] FAT32BootRecord { + u8 boot_jump[3]; + char oem_identifier[8]; + u16 bytes_per_sector; + u8 sectors_per_cluster; + u16 reserved_sector_count; + u8 fat_count; + u16 root_directory_entry_count; + u16 unused1; + u8 media_descriptor_type; + u16 unused2; + u16 sectors_per_track; + u16 head_count; + u32 hidden_sector_count; + u32 sector_count; + u32 sectors_per_fat; + u16 flags; + u16 fat_version; + u32 root_directory_cluster; + u16 fs_info_sector; + u16 backup_boot_sector; + u8 unused3[12]; + u8 drive_number; + u8 unused4; + u8 signature; + u32 volume_id; + char volume_label_string[11]; + char system_identifier_string[8]; +}; +static_assert(sizeof(FAT32BootRecord) == 90); + +enum class FATAttributes : u8 { + ReadOnly = 0x01, + Hidden = 0x02, + System = 0x04, + VolumeID = 0x08, + Directory = 0x10, + Archive = 0x20, + LongFileName = 0x0F +}; + +AK_ENUM_BITWISE_OPERATORS(FATAttributes); + +union FATPackedTime { + u16 value; + struct { + u16 second : 5; + u16 minute : 6; + u16 hour : 5; + }; +}; +static_assert(sizeof(FATPackedTime) == 2); + +union FATPackedDate { + u16 value; + struct { + u16 day : 5; + u16 month : 4; + u16 year : 7; + }; +}; +static_assert(sizeof(FATPackedDate) == 2); + +struct [[gnu::packed]] FATEntry { + char filename[8]; + char extension[3]; + FATAttributes attributes; + u8 unused1; + u8 creation_time_seconds; + FATPackedTime creation_time; + FATPackedDate creation_date; + FATPackedDate last_accessed_date; + u16 first_cluster_high; + FATPackedTime modification_time; + FATPackedDate modification_date; + u16 first_cluster_low; + u32 file_size; +}; +static_assert(sizeof(FATEntry) == 32); + +struct [[gnu::packed]] FATLongFileNameEntry { + u8 entry_index; + u16 characters1[5]; + FATAttributes attributes; + u8 entry_type; + u8 checksum; + u16 characters2[6]; + u16 zero; + u16 characters3[2]; +}; +static_assert(sizeof(FATLongFileNameEntry) == 32); + +class FATInode; + +class FATFS final : public BlockBasedFileSystem { + friend FATInode; + +public: + static ErrorOr> try_create(OpenFileDescription&); + + virtual ~FATFS() override = default; + virtual ErrorOr initialize() override; + virtual StringView class_name() const override { return "FATFS"sv; } + virtual Inode& root_inode() override; + +private: + FATFS(OpenFileDescription&); + + static constexpr u8 signature_1 = 0x28; + static constexpr u8 signature_2 = 0x29; + + static constexpr u32 first_data_cluster = 2; + + FAT32BootRecord const* boot_record() const { return reinterpret_cast(m_boot_record->data()); }; + + BlockBasedFileSystem::BlockIndex first_block_of_cluster(u32 cluster) const; + + OwnPtr m_boot_record; + LockRefPtr m_root_inode; + u32 m_first_data_sector; +}; + +class FATInode final : public Inode { + friend FATFS; + +public: + virtual ~FATInode() override = default; + + static ErrorOr> create(FATFS&, FATEntry, Vector const& = {}); + + FATFS& fs() { return static_cast(Inode::fs()); } + FATFS const& fs() const { return static_cast(Inode::fs()); } + +private: + FATInode(FATFS&, FATEntry, NonnullOwnPtr filename); + + static constexpr u32 no_more_clusters = 0x0FFFFFF8; + static constexpr u32 cluster_number_mask = 0x0FFFFFFF; + + static constexpr u8 end_entry_byte = 0x00; + static constexpr u8 unused_entry_byte = 0xE5; + + static constexpr u8 lfn_entry_text_termination = 0xFF; + + static constexpr u16 first_fat_year = 1980; + + static constexpr u8 normal_filename_length = 8; + static constexpr u8 normal_extension_length = 3; + + static ErrorOr> compute_filename(FATEntry&, Vector const& = {}); + static StringView byte_terminated_string(StringView, u8); + static time_t fat_date_time(FATPackedDate date, FATPackedTime time); + + ErrorOr> compute_block_list(); + ErrorOr> read_block_list(); + ErrorOr> traverse(Function(LockRefPtr)> callback); + u32 first_cluster() const; + + // ^Inode + virtual ErrorOr write_bytes_locked(off_t, size_t, UserOrKernelBuffer const& data, OpenFileDescription*) override; + virtual ErrorOr read_bytes_locked(off_t, size_t, UserOrKernelBuffer& buffer, OpenFileDescription*) const override; + + virtual InodeMetadata metadata() const override; + virtual ErrorOr traverse_as_directory(Function(FileSystem::DirectoryEntryView const&)>) const override; + virtual ErrorOr> lookup(StringView name) override; + virtual ErrorOr> create_child(StringView name, mode_t, dev_t, UserID, GroupID) override; + virtual ErrorOr add_child(Inode&, StringView name, mode_t) override; + virtual ErrorOr remove_child(StringView name) override; + virtual ErrorOr chmod(mode_t) override; + virtual ErrorOr chown(UserID, GroupID) override; + virtual ErrorOr flush_metadata() override; + + Vector m_block_list; + FATEntry m_entry; + NonnullOwnPtr m_filename; + InodeMetadata m_metadata; +}; + +} diff --git a/Kernel/Syscalls/mount.cpp b/Kernel/Syscalls/mount.cpp index e82110a7f14..27b1eeda73c 100644 --- a/Kernel/Syscalls/mount.cpp +++ b/Kernel/Syscalls/mount.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -37,6 +38,7 @@ static constexpr FileSystemInitializer s_initializers[] = { { "ext2"sv, "Ext2FS"sv, true, true, true, Ext2FS::try_create, {} }, { "9p"sv, "Plan9FS"sv, true, true, true, Plan9FS::try_create, {} }, { "iso9660"sv, "ISO9660FS"sv, true, true, true, ISO9660FS::try_create, {} }, + { "fat"sv, "FATFS"sv, true, true, true, FATFS::try_create, {} }, }; static ErrorOr> create_filesystem_instance(StringView fs_type, OpenFileDescription* possible_description) diff --git a/Meta/CMake/all_the_debug_macros.cmake b/Meta/CMake/all_the_debug_macros.cmake index 7b05ed522fa..c7e3fe43379 100644 --- a/Meta/CMake/all_the_debug_macros.cmake +++ b/Meta/CMake/all_the_debug_macros.cmake @@ -53,6 +53,7 @@ set(EXEC_DEBUG ON) set(EXT2_BLOCKLIST_DEBUG ON) set(EXT2_DEBUG ON) set(EXT2_VERY_DEBUG ON) +set(FAT_DEBUG ON) set(FILE_CONTENT_DEBUG ON) set(FILEDESCRIPTION_DEBUG ON) set(FILE_WATCHER_DEBUG ON)