From a62265b504c5fee9b433389d988acf2417346344 Mon Sep 17 00:00:00 2001 From: apio Date: Thu, 22 Jun 2023 19:41:35 +0200 Subject: [PATCH] kernel/ext2: Implement directory traversal --- kernel/debug.cmake | 1 + kernel/src/fs/ext2/FileSystem.cpp | 45 +++++++++++--- kernel/src/fs/ext2/FileSystem.h | 52 +++++++++++++--- kernel/src/fs/ext2/Inode.cpp | 96 +++++++++++++++++++++++++++++ kernel/src/fs/ext2/Inode.h | 59 ++++++++++++++++-- libluna/include/luna/StaticString.h | 10 +++ 6 files changed, 242 insertions(+), 21 deletions(-) diff --git a/kernel/debug.cmake b/kernel/debug.cmake index 0dfb5512..0cd0778f 100644 --- a/kernel/debug.cmake +++ b/kernel/debug.cmake @@ -7,6 +7,7 @@ target_compile_definitions(moon PRIVATE EXEC_DEBUG) target_compile_definitions(moon PRIVATE OPEN_DEBUG) target_compile_definitions(moon PRIVATE REAP_DEBUG) target_compile_definitions(moon PRIVATE PCI_DEBUG) +target_compile_definitions(moon PRIVATE EXT2_DEBUG) target_compile_definitions(moon PRIVATE DEVICE_REGISTRY_DEBUG) target_compile_definitions(moon PRIVATE FORK_DEBUG) target_compile_options(moon PRIVATE -fsanitize=undefined) diff --git a/kernel/src/fs/ext2/FileSystem.cpp b/kernel/src/fs/ext2/FileSystem.cpp index 36695b00..1be95315 100644 --- a/kernel/src/fs/ext2/FileSystem.cpp +++ b/kernel/src/fs/ext2/FileSystem.cpp @@ -25,7 +25,7 @@ namespace Ext2 { } - Result> FileSystem::find_inode_by_number(ino_t inum) + Result> FileSystem::find_inode_by_number(ino_t inum, bool initialize_dir_now) { check(inum < m_superblock.nr_inodes); @@ -37,23 +37,29 @@ namespace Ext2 const auto* block_group_descriptor = TRY(find_block_group_descriptor(block_group)); check(block_group_descriptor); - // FIXME: This is determined by a field in the Superblock if the Ext2 revision >= 1.0. + // FIXME: Even if the inode size is bigger (Ext2::FileSystem::m_inode_size), we only read this amount. Enlarge + // the Inode structure to fit this case. static constexpr usize INODE_SIZE = 128; const u64 index = (inum - 1) % m_superblock.inodes_per_block_group; - const u64 inode_address = (block_group_descriptor->inode_table_start * m_block_size) + (index * INODE_SIZE); + const u64 inode_address = (block_group_descriptor->inode_table_start * m_block_size) + (index * m_inode_size); auto inode = TRY(adopt_shared_if_nonnull(new (std::nothrow) Ext2::Inode({}, this))); TRY(m_host_device->read((u8*)&inode->m_raw_inode, inode_address, INODE_SIZE)); inode->m_type = vfs_type_from_ext2_type(inode->m_raw_inode.mode); inode->m_inum = inum; +#ifdef EXT2_DEBUG kdbgln("ext2: Read inode %lu with mode %#x (%#x + %#o), size %lu", inum, inode->m_raw_inode.mode, inode->m_raw_inode.mode & 0xf000, inode->mode(), inode->size()); +#endif - // TODO: Locate the inode's block group descriptor and find it in the block group's inode table. - return err(ENOENT); + m_inode_cache.try_set(inum, inode); + + if (initialize_dir_now && inode->m_type == VFS::InodeType::Directory) TRY(inode->lazy_initialize_dir()); + + return (SharedPtr)inode; } Result FileSystem::find_block_group_descriptor(u32 index) @@ -80,20 +86,43 @@ namespace Ext2 if (nread != 1024) return err(EINVAL); // Source had an invalid superblock. if (fs->m_superblock.signature != EXT2_MAGIC) return err(EINVAL); // Source had an invalid superblock. + if (fs->m_superblock.major_version >= 1) + { + auto required = fs->m_superblock.ext_superblock.required_features; + if (required & EXT2_REQUIRED_COMPAT_D_TYPE) fs->m_dirs_have_type_field = true; + + required &= ~EXT2_REQUIRED_COMPAT_D_TYPE; + + if (required > 0) + { + kwarnln("ext2: File system has required features not supported by the implementation, cannot mount"); + return err(EINVAL); + } + + fs->m_uses_extended_size = true; + fs->m_inode_size = fs->m_superblock.ext_superblock.inode_size; + } + fs->m_host_device = host_device; fs->m_block_size = 1024 << fs->m_superblock.log_block_size; fs->m_block_groups = get_blocks_from_size(fs->m_superblock.nr_blocks, fs->m_superblock.blocks_per_block_group); +#ifdef EXT2_DEBUG kdbgln("ext2: Mounting new Ext2 file system, block size=%lu, blocks=%u, inodes=%u, block group=(%u blocks, %u " "inodes), %lu block groups", fs->m_block_size, fs->m_superblock.nr_blocks, fs->m_superblock.nr_inodes, fs->m_superblock.blocks_per_block_group, fs->m_superblock.inodes_per_block_group, fs->m_block_groups); +#endif // Lookup the root inode. - fs->find_inode_by_number(2); + fs->m_root_inode = TRY(fs->find_inode_by_number(2, true)); - // TODO: Implement basic Ext2 reading, enough to be able to mount a volume. - return err(ENOTSUP); + return (SharedPtr)fs; + } + + Result FileSystem::set_mount_dir(SharedPtr inode) + { + return m_root_inode->replace_entry(inode, ".."); } } diff --git a/kernel/src/fs/ext2/FileSystem.h b/kernel/src/fs/ext2/FileSystem.h index 9bc06762..812dd079 100644 --- a/kernel/src/fs/ext2/FileSystem.h +++ b/kernel/src/fs/ext2/FileSystem.h @@ -5,6 +5,8 @@ #define EXT2_MAGIC 0xef53 +#define EXT2_REQUIRED_COMPAT_D_TYPE 0x0002 + namespace Ext2 { struct [[gnu::packed]] Superblock @@ -37,8 +39,27 @@ namespace Ext2 u16 reserved_block_uid; u16 reserved_block_gid; - // TODO: Add extended superblock fields. - u8 padding[1024 - 84]; + struct + { + u32 first_non_reserved_inode; + u16 inode_size; + u16 this_block_group; + u32 optional_features; + u32 required_features; + u32 ro_features; + u8 fsid[16]; + u8 name[16]; + u8 last_mountpoint[64]; + u32 compression_algs; + u8 nr_preallocated_blocks_for_files; + u8 nr_preallocated_blocks_for_dirs; + u16 unused; + u8 journal_id[16]; + u32 journal_inode; + u32 journal_device; + u32 orphan_inode_head; + } ext_superblock; + u8 padding[1024 - 236]; }; struct [[gnu::packed]] BlockGroupDescriptor @@ -77,6 +98,21 @@ namespace Ext2 u32 os_specific_2[3]; }; + struct [[gnu::packed]] RawDirectoryEntry + { + u32 inum; + u16 size; + union { + u16 name_length; + struct + { + u8 name_length_low; + u8 type; + }; + }; + char name[4]; // Names should be padded to a multiple of 4 bytes. + }; + static_assert(sizeof(Superblock) == 1024); static_assert(sizeof(BlockGroupDescriptor) == 32); static_assert(sizeof(RawInode) == 128); @@ -111,10 +147,7 @@ namespace Ext2 return err(EROFS); } - Result set_mount_dir(SharedPtr) override - { - return {}; - } + Result set_mount_dir(SharedPtr) override; bool is_readonly() const override { @@ -128,7 +161,7 @@ namespace Ext2 return m_host_device_id; } - Result> find_inode_by_number(ino_t inode); + Result> find_inode_by_number(ino_t inode, bool initialize_dir_now = false); Result find_block_group_descriptor(u32 index); virtual ~FileSystem() = default; @@ -146,6 +179,11 @@ namespace Ext2 u64 m_block_size; u64 m_block_groups; + u32 m_inode_size { 128 }; + + bool m_dirs_have_type_field { false }; + bool m_uses_extended_size { false }; + // FIXME: This inode cache will keep all inodes in it alive despite having no other references to it, but we're // not worrying about that as for now the filesystem implementation is read-only. HashMap> m_inode_cache; diff --git a/kernel/src/fs/ext2/Inode.cpp b/kernel/src/fs/ext2/Inode.cpp index 96a9d29d..da6bd70b 100644 --- a/kernel/src/fs/ext2/Inode.cpp +++ b/kernel/src/fs/ext2/Inode.cpp @@ -1,4 +1,5 @@ #include "fs/ext2/Inode.h" +#include namespace Ext2 { @@ -6,6 +7,13 @@ namespace Ext2 { } + usize Inode::size() const + { + return (m_fs->m_uses_extended_size && (m_type == VFS::InodeType::RegularFile)) + ? ((u64)m_raw_inode.size_high << 32) | (u64)m_raw_inode.size_low + : m_raw_inode.size_low; + } + Result Inode::read(u8* buf, usize offset, usize length) const { if (length == 0) return 0; @@ -22,6 +30,7 @@ namespace Ext2 usize block_offset = (offset % block_size); usize block = find_block(offset / block_size); usize size_to_read = block_size - block_offset; + if (size_to_read > to_read) size_to_read = to_read; usize host_offset = (block * block_size) + block_offset; @@ -66,4 +75,91 @@ namespace Ext2 return m_raw_inode.direct_pointers[index]; } + + Result Inode::lazy_initialize_dir() const + { + check(m_type == VFS::InodeType::Directory); + + const usize inode_size = size(); + const usize block_size = m_fs->m_block_size; + + u8* const buf = TRY(make_array(block_size)); + auto guard = make_scope_guard([buf] { delete[] buf; }); + + m_entries.clear(); + + for (usize offset = 0; offset < inode_size; offset += block_size) + { + TRY(read(buf, offset, block_size)); + + usize dir_offset = 0; + while (dir_offset < block_size) + { + auto& entry = *(Ext2::RawDirectoryEntry*)&buf[dir_offset]; + + if (entry.inum != 0) + { + auto inode = TRY(m_fs->find_inode_by_number(entry.inum)); + + VFS::DirectoryEntry vfs_entry { inode, "" }; + vfs_entry.name.adopt(entry.name, + m_fs->m_dirs_have_type_field ? entry.name_length_low : entry.name_length); + +#ifdef EXT2_DEBUG + kdbgln("ext2: Read new directory entry: inum=%u, name=%s, namelen=%lu", entry.inum, + vfs_entry.name.chars(), vfs_entry.name.length()); +#endif + + TRY(m_entries.try_append(move(vfs_entry))); + } + + dir_offset += entry.size; + } + } + + m_dir_already_lazily_initialized = true; + + return {}; + } + + Result Inode::replace_entry(SharedPtr inode, const char* name) + { + if (m_type != VFS::InodeType::Directory) return err(ENOTDIR); + if (!m_dir_already_lazily_initialized) TRY(lazy_initialize_dir()); + + for (auto& entry : m_entries) + { + if (!strcmp(name, entry.name.chars())) + { + entry.inode = inode; + return {}; + } + } + + return err(ENOENT); + } + + Result> Inode::find(const char* name) const + { + if (m_type != VFS::InodeType::Directory) return err(ENOTDIR); + if (!m_dir_already_lazily_initialized) TRY(lazy_initialize_dir()); + + for (const auto& entry : m_entries) + { + if (!strcmp(name, entry.name.chars())) return entry.inode; + } + + return err(ENOENT); + } + + Option Inode::get(usize index) const + { + if (m_type != VFS::InodeType::Directory) return {}; + if (!m_dir_already_lazily_initialized) + if (lazy_initialize_dir().has_error()) return {}; + + if (index >= m_entries.size()) return {}; + + return m_entries[index]; + } } diff --git a/kernel/src/fs/ext2/Inode.h b/kernel/src/fs/ext2/Inode.h index 53cfd2b8..2084cebd 100644 --- a/kernel/src/fs/ext2/Inode.h +++ b/kernel/src/fs/ext2/Inode.h @@ -11,7 +11,7 @@ namespace Ext2 { - class Inode : public VFS::FileInode + class Inode : public VFS::Inode { public: VFS::InodeType type() const override @@ -19,11 +19,7 @@ namespace Ext2 return m_type; } - usize size() const override - { - // FIXME: If EXT2_REVISION >= 1.0, use size_high as well. - return m_raw_inode.size_low; - } + usize size() const override; mode_t mode() const override { @@ -85,6 +81,54 @@ namespace Ext2 return err(EROFS); } + Result> find(const char*) const override; + + Option get(usize) const override; + + Result> create_file(const char*) override + { + if (m_type != VFS::InodeType::Directory) return err(ENOTDIR); + + return err(EROFS); + } + + Result> create_subdirectory(const char*) override + { + if (m_type != VFS::InodeType::Directory) return err(ENOTDIR); + + return err(EROFS); + } + + Result add_entry(SharedPtr, const char*) override + { + if (m_type != VFS::InodeType::Directory) return err(ENOTDIR); + + return err(EROFS); + } + + Result replace_entry(SharedPtr, const char*) override; + + Result remove_entry(const char*) override + { + if (m_type != VFS::InodeType::Directory) return err(ENOTDIR); + + return err(EROFS); + } + + usize entries() const override + { + return m_entries.size(); + } + + bool blocking() const override + { + return false; + } + + Result lazy_initialize_dir() const; + + // FIXME: Implement readlink() and device numbers. + Inode(Badge, FileSystem* fs); virtual ~Inode() = default; @@ -94,6 +138,9 @@ namespace Ext2 FileSystem* m_fs; ino_t m_inum; + mutable Vector m_entries; + mutable bool m_dir_already_lazily_initialized { false }; + usize find_block(usize index) const; friend class FileSystem; diff --git a/libluna/include/luna/StaticString.h b/libluna/include/luna/StaticString.h index 33e79b11..19be34a6 100644 --- a/libluna/include/luna/StaticString.h +++ b/libluna/include/luna/StaticString.h @@ -25,6 +25,16 @@ template class StaticString m_length = length; } + void adopt(const char* string, usize length) + { + if (length > Size) length = Size; + + memcpy(m_buffer, string, length); + + m_buffer[length] = 0; + m_length = length; + } + void adopt(StringView string) { usize length = strlcpy(m_buffer, string.chars(),