aboutsummaryrefslogtreecommitdiff
path: root/kernel/src/filesystem
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/src/filesystem')
-rw-r--r--kernel/src/filesystem/dentry.cpp9
-rw-r--r--kernel/src/filesystem/dentry.tests.cpp135
-rw-r--r--kernel/src/filesystem/devfs/filesystem.cpp5
-rw-r--r--kernel/src/filesystem/devfs/filesystem.tests.cpp72
-rw-r--r--kernel/src/filesystem/devfs/inode.tests.cpp54
-rw-r--r--kernel/src/filesystem/device_inode.cpp4
-rw-r--r--kernel/src/filesystem/device_inode.tests.cpp108
-rw-r--r--kernel/src/filesystem/ext2/filesystem.cpp241
-rw-r--r--kernel/src/filesystem/ext2/filesystem.tests.cpp132
-rw-r--r--kernel/src/filesystem/ext2/inode.cpp47
-rw-r--r--kernel/src/filesystem/ext2/inode.tests.cpp159
-rw-r--r--kernel/src/filesystem/file_descriptor_table.cpp20
-rw-r--r--kernel/src/filesystem/file_descriptor_table.tests.cpp113
-rw-r--r--kernel/src/filesystem/filesystem.cpp44
-rw-r--r--kernel/src/filesystem/mount.cpp11
-rw-r--r--kernel/src/filesystem/mount.tests.cpp49
-rw-r--r--kernel/src/filesystem/mount_table.cpp71
-rw-r--r--kernel/src/filesystem/mount_table.tests.cpp163
-rw-r--r--kernel/src/filesystem/open_file_description.cpp6
-rw-r--r--kernel/src/filesystem/open_file_description.tests.cpp114
-rw-r--r--kernel/src/filesystem/rootfs/filesystem.cpp5
-rw-r--r--kernel/src/filesystem/rootfs/filesystem.tests.cpp45
-rw-r--r--kernel/src/filesystem/rootfs/inode.tests.cpp83
-rw-r--r--kernel/src/filesystem/vfs.cpp74
-rw-r--r--kernel/src/filesystem/vfs.tests.cpp166
25 files changed, 1835 insertions, 95 deletions
diff --git a/kernel/src/filesystem/dentry.cpp b/kernel/src/filesystem/dentry.cpp
index 2f99e91..6591011 100644
--- a/kernel/src/filesystem/dentry.cpp
+++ b/kernel/src/filesystem/dentry.cpp
@@ -12,10 +12,10 @@
namespace kernel::filesystem
{
- dentry::dentry(kstd::shared_ptr<dentry> const & parent, kstd::shared_ptr<inode> const & node, std::string_view name)
+ dentry::dentry(kstd::shared_ptr<dentry> const & parent, kstd::shared_ptr<inode> const & inode, std::string_view name)
: m_name(name)
, m_parent(parent)
- , m_inode(node)
+ , m_inode(inode)
{
if (!m_inode)
{
@@ -33,6 +33,11 @@ namespace kernel::filesystem
return m_parent;
}
+ auto dentry::get_name() const -> std::string_view
+ {
+ return m_name.view();
+ }
+
auto dentry::add_child(kstd::shared_ptr<dentry> const & child) -> void
{
m_children.push_back(child);
diff --git a/kernel/src/filesystem/dentry.tests.cpp b/kernel/src/filesystem/dentry.tests.cpp
new file mode 100644
index 0000000..a6620d3
--- /dev/null
+++ b/kernel/src/filesystem/dentry.tests.cpp
@@ -0,0 +1,135 @@
+#include "kernel/filesystem/dentry.hpp"
+
+#include "kernel/test_support/cpu.hpp"
+#include "kernel/test_support/filesystem/inode.hpp"
+
+#include <kstd/memory>
+#include <kstd/print>
+
+#include <catch2/catch_test_macros.hpp>
+
+SCENARIO("Dentry construction", "[filesystem][dentry]")
+{
+ GIVEN("A parent dentry and inode")
+ {
+ auto inode = kstd::make_shared<kernel::tests::filesystem::inode>();
+ auto parent_dentry = kstd::make_shared<kernel::filesystem::dentry>(nullptr, inode);
+
+ WHEN("constructing a dentry")
+ {
+ auto child_dentry = kernel::filesystem::dentry{parent_dentry, inode, "child"};
+
+ THEN("the dentry has the correct parent, inode, and name")
+ {
+ REQUIRE(child_dentry.get_parent() == parent_dentry);
+ REQUIRE(child_dentry.get_inode() == inode);
+ REQUIRE(child_dentry.get_name() == "child");
+ }
+
+ THEN("no flag is set")
+ {
+ REQUIRE_FALSE(child_dentry.has_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted));
+ }
+ }
+
+ WHEN("constructing a dentry with an empty name")
+ {
+ auto child_dentry = kernel::filesystem::dentry{parent_dentry, inode};
+
+ THEN("the dentry has the correct parent and inode, and an empty name")
+ {
+ REQUIRE(child_dentry.get_parent() == parent_dentry);
+ REQUIRE(child_dentry.get_inode() == inode);
+ REQUIRE(child_dentry.get_name().empty());
+ }
+
+ THEN("no flag is set")
+ {
+ REQUIRE_FALSE(child_dentry.has_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted));
+ }
+ }
+
+ WHEN("constructing a dentry with a null parent")
+ {
+ auto child_dentry = kernel::filesystem::dentry{nullptr, inode, "child"};
+
+ THEN("the dentry has a null parent, the correct inode, and the correct name")
+ {
+ REQUIRE(child_dentry.get_parent() == nullptr);
+ REQUIRE(child_dentry.get_inode() == inode);
+ REQUIRE(child_dentry.get_name() == "child");
+ }
+
+ THEN("no flag is set")
+ {
+ REQUIRE_FALSE(child_dentry.has_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted));
+ }
+ }
+
+ WHEN("constructing a dentry with a null inode")
+ {
+ THEN("the system panics")
+ {
+ REQUIRE_THROWS_AS((kernel::filesystem::dentry{parent_dentry, nullptr, "child"}), kernel::tests::cpu::halt);
+ }
+ }
+ }
+}
+
+SCENARIO("Dentry child logic", "[filesystem][dentry]")
+{
+ GIVEN("A parent dentry and inode")
+ {
+ auto inode = kstd::make_shared<kernel::tests::filesystem::inode>();
+ auto parent_dentry = kstd::make_shared<kernel::filesystem::dentry>(nullptr, inode);
+
+ WHEN("adding child dentries")
+ {
+ auto child1 = kstd::make_shared<kernel::filesystem::dentry>(parent_dentry, inode, "child1");
+ auto child2 = kstd::make_shared<kernel::filesystem::dentry>(parent_dentry, inode, "child2");
+ parent_dentry->add_child(child1);
+ parent_dentry->add_child(child2);
+
+ THEN("the children can be found by name")
+ {
+ REQUIRE(parent_dentry->find_child("child1") == child1);
+ REQUIRE(parent_dentry->find_child("child2") == child2);
+ }
+
+ THEN("finding a non-existent child returns null")
+ {
+ REQUIRE(parent_dentry->find_child("nonexistent") == nullptr);
+ }
+ }
+ }
+}
+
+SCENARIO("Dentry Flag logic", "[filesystem][dentry]")
+{
+ GIVEN("A dentry")
+ {
+ auto inode = kstd::make_shared<kernel::tests::filesystem::inode>();
+ auto dentry = kernel::filesystem::dentry{nullptr, inode, "test"};
+
+ WHEN("setting a flag")
+ {
+ dentry.set_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted);
+
+ THEN("the flag is set")
+ {
+ REQUIRE(dentry.has_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted));
+ }
+ }
+
+ WHEN("unsetting a flag")
+ {
+ dentry.set_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted);
+ dentry.unset_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted);
+
+ THEN("the flag is unset")
+ {
+ REQUIRE_FALSE(dentry.has_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted));
+ }
+ }
+ }
+}
diff --git a/kernel/src/filesystem/devfs/filesystem.cpp b/kernel/src/filesystem/devfs/filesystem.cpp
index 9043ac5..03b4218 100644
--- a/kernel/src/filesystem/devfs/filesystem.cpp
+++ b/kernel/src/filesystem/devfs/filesystem.cpp
@@ -1,6 +1,7 @@
#include "kernel/filesystem/devfs/filesystem.hpp"
#include "kapi/devices/device.hpp"
+
#include "kernel/devices/storage/management.hpp"
#include "kernel/filesystem/devfs/inode.hpp"
#include "kernel/filesystem/device_inode.hpp"
@@ -13,12 +14,12 @@
namespace kernel::filesystem::devfs
{
- auto filesystem::mount(kstd::shared_ptr<kapi::devices::device> const &) -> int
+ auto filesystem::mount(kstd::shared_ptr<kapi::devices::device> const &) -> operation_result
{
m_root_inode = kstd::make_shared<inode>();
build_device_inode_table();
- return 0;
+ return operation_result::success;
}
auto filesystem::lookup(kstd::shared_ptr<kernel::filesystem::inode> const & parent, std::string_view name)
diff --git a/kernel/src/filesystem/devfs/filesystem.tests.cpp b/kernel/src/filesystem/devfs/filesystem.tests.cpp
new file mode 100644
index 0000000..f8c4764
--- /dev/null
+++ b/kernel/src/filesystem/devfs/filesystem.tests.cpp
@@ -0,0 +1,72 @@
+#include "kernel/filesystem/devfs/filesystem.hpp"
+
+#include "kernel/filesystem/filesystem.hpp"
+#include "kernel/test_support/filesystem/storage_boot_module_fixture.hpp"
+
+#include <catch2/catch_test_macros.hpp>
+
+SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture,
+ "Devfs filesystem lookup uses storage management devices", "[filesystem][devfs][filesystem]")
+{
+ GIVEN("a boot module registry with one module")
+ {
+ setup_modules(1);
+
+ auto fs = kernel::filesystem::devfs::filesystem{};
+ auto result = fs.mount(nullptr);
+
+ THEN("mount succeeds")
+ {
+ REQUIRE(result == kernel::filesystem::filesystem::operation_result::success);
+ REQUIRE(fs.root_inode() != nullptr);
+ }
+
+ THEN("lookup on root finds ram0 device inode")
+ {
+ auto inode = fs.lookup(fs.root_inode(), "ram0");
+ REQUIRE(inode != nullptr);
+ REQUIRE(inode->is_device());
+ }
+
+ THEN("lookup of an unknown device returns null")
+ {
+ auto inode = fs.lookup(fs.root_inode(), "ram99");
+ REQUIRE(inode == nullptr);
+ }
+
+ THEN("lookup with wrong parent returns null")
+ {
+ auto other_fs = kernel::filesystem::devfs::filesystem{};
+ other_fs.mount(nullptr);
+
+ auto inode = fs.lookup(other_fs.root_inode(), "ram0");
+ REQUIRE(inode == nullptr);
+ }
+
+ THEN("lookup with a non-directory parent returns null")
+ {
+ auto non_directory_inode = fs.lookup(fs.root_inode(), "ram0");
+ REQUIRE(non_directory_inode != nullptr);
+ REQUIRE(!non_directory_inode->is_directory());
+
+ auto result = fs.lookup(non_directory_inode, "anything");
+ REQUIRE(result == nullptr);
+ }
+ }
+
+ GIVEN("a boot module registry with three modules")
+ {
+ setup_modules(3, 2048);
+
+ auto fs = kernel::filesystem::devfs::filesystem{};
+ auto result = fs.mount(nullptr);
+ REQUIRE(result == kernel::filesystem::filesystem::operation_result::success);
+
+ THEN("lookup finds all generated RAM devices")
+ {
+ REQUIRE(fs.lookup(fs.root_inode(), "ram0") != nullptr);
+ REQUIRE(fs.lookup(fs.root_inode(), "ram16") != nullptr);
+ REQUIRE(fs.lookup(fs.root_inode(), "ram32") != nullptr);
+ }
+ }
+}
diff --git a/kernel/src/filesystem/devfs/inode.tests.cpp b/kernel/src/filesystem/devfs/inode.tests.cpp
new file mode 100644
index 0000000..50e34a7
--- /dev/null
+++ b/kernel/src/filesystem/devfs/inode.tests.cpp
@@ -0,0 +1,54 @@
+#include "kernel/filesystem/devfs/inode.hpp"
+
+#include <kstd/memory>
+#include <kstd/print>
+#include <kstd/vector>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <cstdint>
+
+SCENARIO("Devfs inode creation", "[filesystem][devfs][inode]")
+{
+ GIVEN("a devfs inode")
+ {
+ auto inode = kernel::filesystem::devfs::inode{};
+
+ THEN("the inode has the correct kind")
+ {
+ REQUIRE(inode.is_directory());
+ REQUIRE_FALSE(inode.is_device());
+ REQUIRE_FALSE(inode.is_regular());
+ }
+ }
+}
+
+SCENARIO("Devfs inode read/write", "[filesystem][devfs][inode]")
+{
+ GIVEN("a devfs inode")
+ {
+ auto inode = kernel::filesystem::devfs::inode{};
+
+ WHEN("attempting to read from the devfs inode")
+ {
+ kstd::vector<uint8_t> buffer(512);
+ auto bytes_read = inode.read(buffer.data(), 0, buffer.size());
+
+ THEN("no bytes are read")
+ {
+ REQUIRE(bytes_read == 0);
+ }
+ }
+
+ WHEN("attempting to write to the devfs inode")
+ {
+ kstd::vector<uint8_t> buffer(512);
+ auto bytes_written = inode.write(buffer.data(), 0, buffer.size());
+
+ THEN("no bytes are written")
+ {
+ REQUIRE(bytes_written == 0);
+ }
+ }
+ }
+}
diff --git a/kernel/src/filesystem/device_inode.cpp b/kernel/src/filesystem/device_inode.cpp
index af8cecc..397a0fd 100644
--- a/kernel/src/filesystem/device_inode.cpp
+++ b/kernel/src/filesystem/device_inode.cpp
@@ -1,14 +1,12 @@
#include "kernel/filesystem/device_inode.hpp"
+#include "kapi/devices/device.hpp"
#include "kapi/system.hpp"
#include "kernel/devices/block_device_utils.hpp"
-#include "kapi/devices/device.hpp"
#include "kernel/filesystem/inode.hpp"
-#include <kstd/cstring>
#include <kstd/memory>
-#include <kstd/vector>
#include <cstddef>
diff --git a/kernel/src/filesystem/device_inode.tests.cpp b/kernel/src/filesystem/device_inode.tests.cpp
new file mode 100644
index 0000000..4e31812
--- /dev/null
+++ b/kernel/src/filesystem/device_inode.tests.cpp
@@ -0,0 +1,108 @@
+#include "kernel/filesystem/device_inode.hpp"
+
+#include "kernel/test_support/cpu.hpp"
+#include "kernel/test_support/devices/block_device.hpp"
+#include "kernel/test_support/devices/character_device.hpp"
+
+#include <kstd/memory>
+#include <kstd/print>
+#include <kstd/vector>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <cstddef>
+#include <cstdint>
+
+SCENARIO("Device inode construction", "[filesystem][device_inode]")
+{
+ GIVEN("a block device")
+ {
+ auto device = kstd::make_shared<kernel::tests::devices::block_device>(0, 0, "test_block_device", 512, 3 * 512);
+
+ WHEN("constructing a device inode with the block device")
+ {
+ auto inode = kernel::filesystem::device_inode{device};
+
+ THEN("the device inode has the correct device")
+ {
+ REQUIRE(inode.device() == device);
+ }
+
+ THEN("the device inode has the correct kind")
+ {
+ REQUIRE(inode.is_device());
+ REQUIRE_FALSE(inode.is_directory());
+ REQUIRE_FALSE(inode.is_regular());
+ }
+ }
+
+ WHEN("constructing a device inode with a null device")
+ {
+ THEN("the constructor panics")
+ {
+ REQUIRE_THROWS_AS((kernel::filesystem::device_inode{nullptr}), kernel::tests::cpu::halt);
+ }
+ }
+ }
+}
+
+SCENARIO("Device inode read/write", "[filesystem][device_inode]")
+{
+ GIVEN("a block device and a device inode for that device")
+ {
+ auto device = kstd::make_shared<kernel::tests::devices::block_device>(0, 0, "test_block_device", 512, 3 * 512);
+ auto inode = kernel::filesystem::device_inode{device};
+
+ WHEN("writing to the device inode")
+ {
+ kstd::vector<uint8_t> write_buffer(1024);
+ for (size_t i = 0; i < write_buffer.size(); ++i)
+ {
+ write_buffer[i] = static_cast<uint8_t>(i % 256);
+ }
+
+ auto bytes_written = inode.write(write_buffer.data(), 256, write_buffer.size());
+
+ THEN("the correct number of bytes is written")
+ {
+ REQUIRE(bytes_written == 1024);
+ }
+
+ THEN("the data written matches the data read back from the device inode")
+ {
+ kstd::vector<uint8_t> read_buffer(1024);
+ auto bytes_read = inode.read(read_buffer.data(), 256, read_buffer.size());
+
+ REQUIRE(bytes_read == write_buffer.size());
+ REQUIRE(read_buffer == write_buffer);
+ }
+ }
+ }
+}
+
+SCENARIO("Device inode read/write with a non-block device", "[filesystem][device_inode]")
+{
+ GIVEN("a non-block device and a device inode for that device")
+ {
+ auto device = kstd::make_shared<kernel::tests::devices::character_device>(0, 0, "test_character_device");
+ auto inode = kernel::filesystem::device_inode{device};
+
+ WHEN("reading from the device inode")
+ {
+ kstd::vector<uint8_t> read_buffer(512);
+ THEN("the system panics")
+ {
+ REQUIRE_THROWS_AS(inode.read(read_buffer.data(), 0, read_buffer.size()), kernel::tests::cpu::halt);
+ }
+ }
+
+ WHEN("writing to the device inode")
+ {
+ kstd::vector<uint8_t> write_buffer(512);
+ THEN("the system panics")
+ {
+ REQUIRE_THROWS_AS(inode.write(write_buffer.data(), 0, write_buffer.size()), kernel::tests::cpu::halt);
+ }
+ }
+ }
+}
diff --git a/kernel/src/filesystem/ext2/filesystem.cpp b/kernel/src/filesystem/ext2/filesystem.cpp
index eb9edc4..0ad5c97 100644
--- a/kernel/src/filesystem/ext2/filesystem.cpp
+++ b/kernel/src/filesystem/ext2/filesystem.cpp
@@ -1,13 +1,17 @@
#include "kernel/filesystem/ext2/filesystem.hpp"
-#include "kernel/devices/block_device_utils.hpp"
#include "kapi/devices/device.hpp"
+
+#include "kernel/devices/block_device_utils.hpp"
+#include "kernel/filesystem/ext2/block_group_descriptor.hpp"
#include "kernel/filesystem/ext2/inode.hpp"
+#include "kernel/filesystem/ext2/linked_directory_entry.hpp"
#include "kernel/filesystem/ext2/superblock.hpp"
#include "kernel/filesystem/filesystem.hpp"
#include "kernel/filesystem/inode.hpp"
#include <kstd/memory>
+#include <kstd/vector>
#include <cstddef>
#include <cstdint>
@@ -17,56 +21,211 @@ namespace kernel::filesystem::ext2
{
namespace
{
- // constexpr size_t SUPERBLOCK_OFFSET = 1024;
- // constexpr uint16_t EXT2_MAGIC = 0xEF53;
-
- // // Mode bits
- // constexpr uint16_t S_IFMT = 0xF000;
- // constexpr uint16_t S_IFREG = 0x8000;
- // constexpr uint16_t S_IFDIR = 0x4000;
-
- // auto S_ISREG(uint16_t mode) -> bool
- // {
- // return (mode & S_IFMT) == S_IFREG;
- // }
- // auto S_ISDIR(uint16_t mode) -> bool
- // {
- // return (mode & S_IFMT) == S_IFDIR;
- // }
-
- // auto get_block_size(superblock const & superblock) -> size_t
- // {
- // return 1024U << superblock.log_block_size;
- // }
-
- // auto get_inode_size(superblock const & superblock) -> size_t
- // {
- // return superblock.rev_level == 0 ? 128 : superblock.inode_size;
- // }
+ auto S_ISREG(uint16_t mode) -> bool
+ {
+ return (mode & constants::mode_mask) == constants::mode_regular;
+ }
+
+ auto S_ISDIR(uint16_t mode) -> bool
+ {
+ return (mode & constants::mode_mask) == constants::mode_directory;
+ }
} // namespace
- auto filesystem::mount(kstd::shared_ptr<kapi::devices::device> const & device) -> int
+ auto filesystem::mount(kstd::shared_ptr<kapi::devices::device> const & device) -> operation_result
{
- kernel::filesystem::filesystem::mount(device); // TODO BA-FS26 error handling?
- // TODO BA-FS26 load proper root inode from ext2 metadata
- // m_root_inode = inode{inode_kind::directory};
-
- // TODO BA-FS26 implement
- m_root_inode = kstd::make_shared<inode>();
- // devices::block_device_utils::read(device, nullptr, 0, 0); // TODO BA-FS26 just for testing
- return 0;
+ kernel::filesystem::filesystem::mount(device);
+
+ kernel::devices::block_device_utils::read(m_device, &m_superblock, constants::superblock_offset,
+ sizeof(m_superblock));
+
+ if (m_superblock.magic != constants::magic_number)
+ {
+ return operation_result::invalid_magic_number;
+ }
+
+ auto const block_size = get_block_size();
+ auto const blocks_per_group = m_superblock.blocks_per_group;
+ auto const num_block_groups = (m_superblock.blocks_count + blocks_per_group - 1) / blocks_per_group;
+
+ m_block_group_descriptors = kstd::vector<block_group_descriptor>(num_block_groups);
+
+ auto const block_group_descriptor_table_offset = block_size == 1024 ? 2 * block_size : block_size;
+ kernel::devices::block_device_utils::read(m_device, m_block_group_descriptors.data(),
+ block_group_descriptor_table_offset,
+ num_block_groups * sizeof(block_group_descriptor));
+
+ m_root_inode = read_inode(constants::root_inode_number);
+
+ if (!m_root_inode || !m_root_inode->is_directory())
+ {
+ return operation_result::invalid_root_inode;
+ }
+ return operation_result::success;
}
- auto filesystem::lookup(kstd::shared_ptr<kernel::filesystem::inode> const & /*parent*/, std::string_view name)
+ auto filesystem::lookup(kstd::shared_ptr<kernel::filesystem::inode> const & parent, std::string_view name)
-> kstd::shared_ptr<kernel::filesystem::inode>
{
- // TODO BA-FS26 implement ext2 directory traversal and inode loading
- if (name == "dev")
+ if (!parent || !parent->is_directory())
+ {
+ return nullptr;
+ }
+
+ auto * ext2_parent = static_cast<inode *>(parent.get());
+ if (!ext2_parent)
+ {
+ return nullptr;
+ }
+
+ auto const block_size = get_block_size();
+ auto const & inode_data = ext2_parent->m_data;
+ kstd::vector<uint8_t> buffer(block_size);
+
+ for (uint32_t i = 0; i < get_inode_block_count(inode_data); ++i)
+ {
+ auto const global_block_number = map_inode_block_index_to_global_block_number(i, inode_data);
+ auto const block_offset = global_block_number * block_size;
+ kernel::devices::block_device_utils::read(m_device, buffer.data(), block_offset, block_size);
+
+ auto const * entry = reinterpret_cast<linked_directory_entry const *>(buffer.data());
+ auto bytes_read = 0uz;
+
+ while (bytes_read < block_size && entry->inode != 0)
+ {
+ auto const entry_name = std::string_view{entry->name.data(), entry->name_len};
+ if (entry_name == name)
+ {
+ return read_inode(entry->inode);
+ }
+
+ bytes_read += entry->rec_len;
+ entry = reinterpret_cast<linked_directory_entry const *>(buffer.data() + bytes_read);
+ }
+ }
+
+ return nullptr;
+ }
+
+ auto filesystem::read_inode(uint32_t inode_number) -> kstd::shared_ptr<inode>
+ {
+ auto const block_size = get_block_size();
+ auto const inodes_per_group = m_superblock.inodes_per_group;
+ auto const block_group_index = (inode_number - 1) / inodes_per_group;
+ auto const inode_index_within_group = (inode_number - 1) % inodes_per_group;
+
+ if (block_group_index >= m_block_group_descriptors.size())
{
- // TODO BA-FS26 just for testing
return nullptr;
}
- return kstd::make_shared<inode>();
+ auto const & block_group_descriptor = m_block_group_descriptors.at(block_group_index);
+ auto const inode_table_start_block = block_group_descriptor.inode_table;
+ auto const inode_table_offset = static_cast<size_t>(inode_table_start_block) * block_size;
+ auto const inode_offset = inode_table_offset + inode_index_within_group * get_inode_size();
+
+ auto new_inode = kstd::make_shared<inode>(this);
+ kernel::devices::block_device_utils::read(m_device, &new_inode->m_data, inode_offset, sizeof(inode_data));
+
+ // TODO BA-FS26 improve inode_kind really needed? or just map it to the mode bits?
+ if (S_ISREG(new_inode->m_data.mode))
+ {
+ new_inode->m_kind = inode::inode_kind::regular;
+ }
+ else if (S_ISDIR(new_inode->m_data.mode))
+ {
+ new_inode->m_kind = inode::inode_kind::directory;
+ }
+ else
+ {
+ // TODO BA-FS26 really correct??
+ return nullptr;
+ }
+
+ return new_inode;
+ }
+
+ auto filesystem::map_inode_block_index_to_global_block_number(uint32_t inode_block_index, inode_data data) -> uint32_t
+ {
+ if (inode_block_index < constants::direct_block_count)
+ {
+ return data.block.at(inode_block_index);
+ }
+ inode_block_index -= constants::direct_block_count;
+
+ auto const block_size = get_block_size();
+ auto const numbers_per_block = block_size / sizeof(uint32_t);
+
+ auto const block_numbers_per_singly_indirect_block = numbers_per_block;
+ auto const block_numbers_per_doubly_indirect_block = numbers_per_block * block_numbers_per_singly_indirect_block;
+ auto const block_numbers_per_triply_indirect_block = numbers_per_block * block_numbers_per_doubly_indirect_block;
+
+ if (inode_block_index < block_numbers_per_singly_indirect_block)
+ {
+ auto const singly_indirect_block_number = data.block.at(constants::singly_indirect_block_index);
+ return read_block_number_at_index(singly_indirect_block_number, inode_block_index);
+ }
+ inode_block_index -= block_numbers_per_singly_indirect_block;
+
+ if (inode_block_index < block_numbers_per_doubly_indirect_block)
+ {
+ auto const doubly_indirect_block_number = data.block.at(constants::doubly_indirect_block_index);
+ auto const singly_indirect_block_index_in_doubly_indirect_block =
+ inode_block_index / block_numbers_per_singly_indirect_block;
+ auto const singly_indirect_block_number = read_block_number_at_index(
+ doubly_indirect_block_number, singly_indirect_block_index_in_doubly_indirect_block);
+
+ auto const block_index_in_singly_indirect_block = inode_block_index % block_numbers_per_singly_indirect_block;
+ return read_block_number_at_index(singly_indirect_block_number, block_index_in_singly_indirect_block);
+ }
+ inode_block_index -= block_numbers_per_doubly_indirect_block;
+
+ if (inode_block_index < block_numbers_per_triply_indirect_block)
+ {
+ auto const triply_indirect_block_number = data.block.at(constants::triply_indirect_block_index);
+ auto const doubly_indirect_block_index_in_triply_indirect_block =
+ inode_block_index / block_numbers_per_doubly_indirect_block;
+ auto const doubly_indirect_block_number = read_block_number_at_index(
+ triply_indirect_block_number, doubly_indirect_block_index_in_triply_indirect_block);
+
+ auto const remaining_block_numbers = inode_block_index % block_numbers_per_doubly_indirect_block;
+
+ auto const singly_indirect_block_index_in_doubly_indirect_block =
+ remaining_block_numbers / block_numbers_per_singly_indirect_block;
+ auto const singly_indirect_block_number = read_block_number_at_index(
+ doubly_indirect_block_number, singly_indirect_block_index_in_doubly_indirect_block);
+
+ auto const block_index_in_singly_indirect_block =
+ remaining_block_numbers % block_numbers_per_singly_indirect_block;
+ return read_block_number_at_index(singly_indirect_block_number, block_index_in_singly_indirect_block);
+ }
+
+ return 0; // TODO BA-FS26 really correct??
+ }
+
+ auto filesystem::read_block_number_at_index(uint32_t block_number, uint32_t index) -> uint32_t
+ {
+ uint32_t block_number_buffer = 0;
+
+ auto const block_start_offset = block_number * get_block_size();
+ auto const number_start_address = block_start_offset + index * sizeof(uint32_t);
+ kernel::devices::block_device_utils::read(m_device, &block_number_buffer, number_start_address, sizeof(uint32_t));
+
+ return block_number_buffer;
+ }
+
+ auto filesystem::get_block_size() -> size_t
+ {
+ return constants::base_block_size << m_superblock.log_block_size;
+ }
+
+ auto filesystem::get_inode_size() -> size_t
+ {
+ return m_superblock.rev_level == 0 ? 128 : m_superblock.inode_size;
+ }
+
+ auto filesystem::get_inode_block_count(inode_data const & data) -> uint32_t
+ {
+ return data.blocks / (2 << m_superblock.log_block_size);
}
} // namespace kernel::filesystem::ext2
diff --git a/kernel/src/filesystem/ext2/filesystem.tests.cpp b/kernel/src/filesystem/ext2/filesystem.tests.cpp
new file mode 100644
index 0000000..b13ebf3
--- /dev/null
+++ b/kernel/src/filesystem/ext2/filesystem.tests.cpp
@@ -0,0 +1,132 @@
+#include "kernel/filesystem/ext2/filesystem.hpp"
+
+#include "kernel/devices/storage/management.hpp"
+#include "kernel/filesystem/ext2/inode.hpp"
+#include "kernel/filesystem/filesystem.hpp"
+#include "kernel/test_support/devices/block_device.hpp"
+#include "kernel/test_support/filesystem/ext2.hpp"
+#include "kernel/test_support/filesystem/storage_boot_module_fixture.hpp"
+
+#include <kstd/memory>
+#include <kstd/vector>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <array>
+#include <cstdint>
+#include <filesystem>
+
+SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture,
+ "Ext2 filesystem mount and lookup with real image", "[filesystem][ext2][filesystem][img]")
+{
+ auto const image_path = std::filesystem::path{KERNEL_TEST_ASSETS_DIR} / "ext2_1KB_fs.img";
+
+ GIVEN("a mounted ext2 filesystem from a real image")
+ {
+ REQUIRE(std::filesystem::exists(image_path));
+ REQUIRE_NOTHROW(setup_modules_from_img({"test_img_module"}, {image_path}));
+
+ auto boot_device = kernel::devices::storage::management::get().determine_boot_device();
+ REQUIRE(boot_device != nullptr);
+
+ auto fs = kernel::filesystem::ext2::filesystem{};
+ REQUIRE(fs.mount(boot_device) == kernel::filesystem::filesystem::operation_result::success);
+
+ THEN("the root inode is available and is a directory")
+ {
+ REQUIRE(fs.root_inode() != nullptr);
+ REQUIRE(fs.root_inode()->is_directory());
+ }
+
+ THEN("lookup resolves known entries from the image")
+ {
+ auto information = fs.lookup(fs.root_inode(), "information");
+ REQUIRE(information != nullptr);
+ REQUIRE(information->is_directory());
+
+ auto info_1 = fs.lookup(information, "info_1.txt");
+ REQUIRE(info_1 != nullptr);
+ REQUIRE(info_1->is_regular());
+ }
+
+ THEN("lookup returns null for invalid inputs")
+ {
+ REQUIRE(fs.lookup(nullptr, "information") == nullptr);
+
+ auto information = fs.lookup(fs.root_inode(), "information");
+ REQUIRE(information != nullptr);
+ auto info_1 = fs.lookup(information, "info_1.txt");
+ REQUIRE(info_1 != nullptr);
+
+ REQUIRE(fs.lookup(info_1, "anything") == nullptr);
+ REQUIRE(fs.lookup(fs.root_inode(), "does_not_exist") == nullptr);
+ }
+ }
+}
+
+SCENARIO("Ext2 filesystem rejects invalid magic", "[filesystem][ext2][filesystem]")
+{
+ auto const block_size = 1024;
+ GIVEN("a block device that does not contain an ext2 superblock")
+ {
+ auto device = kstd::make_shared<kernel::tests::devices::block_device>(0, 0, "mock", block_size, 2 * block_size);
+ REQUIRE(device != nullptr);
+
+ auto fs = kernel::filesystem::ext2::filesystem{};
+
+ THEN("mount fails with invalid_magic_number")
+ {
+ REQUIRE(fs.mount(device) == kernel::filesystem::filesystem::operation_result::invalid_magic_number);
+ }
+ }
+}
+
+SCENARIO("Ext2 block mapping includes direct and all indirect levels", "[filesystem][ext2][filesystem]")
+{
+ auto const block_size = 1024;