From 77686b26f8c6fee9e630c70d5b9c1cffa9193e2f Mon Sep 17 00:00:00 2001 From: apio Date: Wed, 21 Jun 2023 21:32:28 +0200 Subject: [PATCH] kernel/Ext2: Read the root inode metadata from the disk --- initrd/etc/init/02-ext2fs | 3 + initrd/sbin/test-ext2fs | 4 ++ kernel/CMakeLists.txt | 1 + kernel/src/fs/VFS.h | 5 ++ kernel/src/fs/ext2/FileSystem.cpp | 75 ++++++++++++++++++++++- kernel/src/fs/ext2/FileSystem.h | 58 +++++++++++++++++- kernel/src/fs/ext2/Inode.cpp | 13 ++++ kernel/src/fs/ext2/Inode.h | 99 +++++++++++++++++++++++++++++++ kernel/src/sys/open.cpp | 2 + libluna/include/luna/HashMap.h | 7 +++ 10 files changed, 262 insertions(+), 5 deletions(-) create mode 100644 initrd/etc/init/02-ext2fs create mode 100644 initrd/sbin/test-ext2fs create mode 100644 kernel/src/fs/ext2/Inode.cpp create mode 100644 kernel/src/fs/ext2/Inode.h diff --git a/initrd/etc/init/02-ext2fs b/initrd/etc/init/02-ext2fs new file mode 100644 index 00000000..eeaf3c3e --- /dev/null +++ b/initrd/etc/init/02-ext2fs @@ -0,0 +1,3 @@ +Name=ext2fs +Script=/sbin/test-ext2fs +Wait=true diff --git a/initrd/sbin/test-ext2fs b/initrd/sbin/test-ext2fs new file mode 100644 index 00000000..e172354c --- /dev/null +++ b/initrd/sbin/test-ext2fs @@ -0,0 +1,4 @@ +# (for testing) Remove this automatic mount once ext2 is working. + +mkdir /mnt +mount -t ext2 /dev/cd0p2 /mnt diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index 31b8b940..de8c9847 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -48,6 +48,7 @@ set(SOURCES src/fs/tmpfs/FileSystem.cpp src/fs/tmpfs/Inode.cpp src/fs/ext2/FileSystem.cpp + src/fs/ext2/Inode.cpp src/fs/devices/DeviceRegistry.cpp src/fs/devices/NullDevice.cpp src/fs/devices/ZeroDevice.cpp diff --git a/kernel/src/fs/VFS.h b/kernel/src/fs/VFS.h index 4533033c..cdb5b6ff 100644 --- a/kernel/src/fs/VFS.h +++ b/kernel/src/fs/VFS.h @@ -36,6 +36,11 @@ namespace VFS virtual Result set_mount_dir(SharedPtr parent) = 0; + virtual bool is_readonly() const + { + return false; + } + virtual u64 handles() const { return m_handles; diff --git a/kernel/src/fs/ext2/FileSystem.cpp b/kernel/src/fs/ext2/FileSystem.cpp index b4cf7862..36695b00 100644 --- a/kernel/src/fs/ext2/FileSystem.cpp +++ b/kernel/src/fs/ext2/FileSystem.cpp @@ -1,4 +1,23 @@ #include "fs/ext2/FileSystem.h" +#include "fs/ext2/Inode.h" +#include + +static VFS::InodeType vfs_type_from_ext2_type(mode_t mode) +{ + auto type = mode & 0xf000; + + switch (type) + { + case EXT2_FIFO: return VFS::InodeType::FIFO; + case EXT2_CHR: return VFS::InodeType::CharacterDevice; + case EXT2_DIR: return VFS::InodeType::Directory; + case EXT2_BLK: return VFS::InodeType::BlockDevice; + case EXT2_REG: return VFS::InodeType::RegularFile; + case EXT2_LNK: return VFS::InodeType::Symlink; + case EXT2_SOCK: [[fallthrough]]; // TODO: Sockets not supported on Luna at the moment. + default: fail("ext2: Unknown or unsupported inode type"); + } +} namespace Ext2 { @@ -6,15 +25,54 @@ namespace Ext2 { } - Result> FileSystem::find_inode_by_number(ino_t inode) + Result> FileSystem::find_inode_by_number(ino_t inum) { - auto maybe_inode = m_inode_cache.try_get(inode); + check(inum < m_superblock.nr_inodes); + + auto maybe_inode = m_inode_cache.try_get(inum); if (maybe_inode.has_value()) return maybe_inode.value(); + const u32 block_group = (u32)((inum - 1) / m_superblock.inodes_per_block_group); + + 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. + 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); + + 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; + + 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()); + // TODO: Locate the inode's block group descriptor and find it in the block group's inode table. return err(ENOENT); } + Result FileSystem::find_block_group_descriptor(u32 index) + { + check(index < m_block_groups); + + auto maybe_desc = m_block_group_descriptor_cache.try_get_ref(index); + if (maybe_desc) return maybe_desc; + + const u64 address = (m_superblock.first_data_block + 1) * m_block_size + (index * sizeof(BlockGroupDescriptor)); + + BlockGroupDescriptor descriptor; + TRY(m_host_device->read((u8*)&descriptor, address, sizeof(descriptor))); + + check(TRY(m_block_group_descriptor_cache.try_set(index, descriptor))); + + return m_block_group_descriptor_cache.try_get_ref(index); + } + Result> FileSystem::create(SharedPtr host_device) { SharedPtr fs = TRY(adopt_shared_if_nonnull(new (std::nothrow) FileSystem())); @@ -22,6 +80,19 @@ 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. + 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); + + 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); + + // Lookup the root inode. + fs->find_inode_by_number(2); + // TODO: Implement basic Ext2 reading, enough to be able to mount a volume. return err(ENOTSUP); } diff --git a/kernel/src/fs/ext2/FileSystem.h b/kernel/src/fs/ext2/FileSystem.h index c6801f61..f7f424a0 100644 --- a/kernel/src/fs/ext2/FileSystem.h +++ b/kernel/src/fs/ext2/FileSystem.h @@ -11,10 +11,10 @@ namespace Ext2 { u32 nr_inodes; u32 nr_blocks; - u32 nr_reserved; - u32 nr_free_inodes; + u32 nr_reserved_blocks; u32 nr_free_blocks; - u32 superblock_block; + u32 nr_free_inodes; + u32 first_data_block; u32 log_block_size; u32 log_fragment_size; u32 blocks_per_block_group; @@ -22,23 +22,64 @@ namespace Ext2 u32 inodes_per_block_group; u32 last_mount_time; u32 last_write_time; + u16 mounts_since_last_fsck; u16 mounts_allowed_before_fsck; u16 signature; u16 fs_state; u16 error_action; u16 minor_version; + u32 last_fsck_time; u32 fsck_time_interval; u32 os_id; u32 major_version; + u16 reserved_block_uid; u16 reserved_block_gid; // TODO: Add extended superblock fields. u8 padding[1024 - 84]; }; + struct [[gnu::packed]] BlockGroupDescriptor + { + u32 block_usage_addr; + u32 inode_usage_addr; + u32 inode_table_start; + u16 nr_free_blocks; + u16 nr_free_inodes; + u16 nr_directories; + u8 padding[32 - 18]; + }; + + struct [[gnu::packed]] RawInode + { + u16 mode; + u16 uid; + u32 size_low; + u32 atime; + u32 create_time; + u32 mtime; + u32 delete_time; + u16 gid; + u16 nlinks; + u32 sectors_used; + u32 flags; + u32 os_specific_1; + u32 direct_pointers[12]; + u32 singly_indirect_ptr; + u32 doubly_indirect_ptr; + u32 triply_indirect_ptr; + u32 gen_number; + u32 extended_attrs; + u32 size_high; + u32 frag_addr; + u32 os_specific_2[3]; + }; + static_assert(sizeof(Superblock) == 1024); + static_assert(sizeof(BlockGroupDescriptor) == 32); + static_assert(sizeof(RawInode) == 128); class FileSystem : public VFS::FileSystem { @@ -73,6 +114,11 @@ namespace Ext2 return {}; } + bool is_readonly() const override + { + return true; + } + static Result> create(SharedPtr host_device); dev_t host_device_id() const override @@ -81,6 +127,7 @@ namespace Ext2 } Result> find_inode_by_number(ino_t inode); + Result find_block_group_descriptor(u32 index); virtual ~FileSystem() = default; @@ -94,8 +141,13 @@ namespace Ext2 Superblock m_superblock; + u64 m_block_size; + u64 m_block_groups; + // 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; + + HashMap m_block_group_descriptor_cache; }; } diff --git a/kernel/src/fs/ext2/Inode.cpp b/kernel/src/fs/ext2/Inode.cpp new file mode 100644 index 00000000..e1af5fd1 --- /dev/null +++ b/kernel/src/fs/ext2/Inode.cpp @@ -0,0 +1,13 @@ +#include "fs/ext2/Inode.h" + +namespace Ext2 +{ + Inode::Inode(Badge, FileSystem* fs) : m_fs(fs) + { + } + + Result Inode::read(u8* /*buf*/, usize /*offset*/, usize /*length*/) const + { + fail("FIXME: Ext2::Inode::read()"); + } +} diff --git a/kernel/src/fs/ext2/Inode.h b/kernel/src/fs/ext2/Inode.h new file mode 100644 index 00000000..0e69bd33 --- /dev/null +++ b/kernel/src/fs/ext2/Inode.h @@ -0,0 +1,99 @@ +#pragma once +#include "fs/ext2/FileSystem.h" + +#define EXT2_FIFO 0x1000 +#define EXT2_CHR 0x2000 +#define EXT2_DIR 0x4000 +#define EXT2_BLK 0x6000 +#define EXT2_REG 0x8000 +#define EXT2_LNK 0xA000 +#define EXT2_SOCK 0xC000 + +namespace Ext2 +{ + class Inode : public VFS::FileInode + { + public: + VFS::InodeType type() const override + { + return m_type; + } + + usize size() const override + { + // FIXME: If EXT2_REVISION >= 1.0, use size_high as well. + return m_raw_inode.size_low; + } + + mode_t mode() const override + { + return m_raw_inode.mode & 07777; + } + + nlink_t nlinks() const override + { + return m_raw_inode.nlinks; + } + + u32 uid() const override + { + return m_raw_inode.uid; + } + + u32 gid() const override + { + return m_raw_inode.gid; + } + + usize inode_number() const override + { + return m_inum; + } + + VFS::FileSystem* fs() const override + { + return m_fs; + } + + void did_link() override + { + } + + void did_unlink() override + { + } + + Result chmod(mode_t) override + { + return err(EROFS); + } + + Result chown(u32, u32) override + { + return err(EROFS); + } + + Result read(u8* buf, usize offset, usize length) const override; + + Result write(const u8*, usize, usize) override + { + return err(EROFS); + } + + Result truncate(usize) override + { + return err(EROFS); + } + + Inode(Badge, FileSystem* fs); + virtual ~Inode() = default; + + private: + VFS::InodeType m_type; + RawInode m_raw_inode; + FileSystem* m_fs; + ino_t m_inum; + + friend class FileSystem; + }; +} diff --git a/kernel/src/sys/open.cpp b/kernel/src/sys/open.cpp index 4bb2ffc6..ce160a19 100644 --- a/kernel/src/sys/open.cpp +++ b/kernel/src/sys/open.cpp @@ -63,6 +63,8 @@ Result sys_openat(Registers*, SyscallArgs args) inode->chown(current->auth.euid, current->auth.egid); } + if ((flags & O_WRONLY) && inode->fs() && inode->fs()->is_readonly()) return err(EROFS); + if (inode->type() != VFS::InodeType::Directory && (flags & O_DIRECTORY)) return err(ENOTDIR); if (inode->type() == VFS::InodeType::Directory) diff --git a/libluna/include/luna/HashMap.h b/libluna/include/luna/HashMap.h index ba149304..639151d5 100644 --- a/libluna/include/luna/HashMap.h +++ b/libluna/include/luna/HashMap.h @@ -32,6 +32,13 @@ template struct HashMap return p->value; } + V* try_get_ref(const K& key) + { + auto* p = m_table.try_find(HashPair { key, {} }); + if (!p) return nullptr; + return p->value.value_ptr(); + } + bool try_remove(const K& key) { return m_table.try_remove(HashPair { key, {} });