diff options
| -rw-r--r-- | kernel/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | kernel/kernel/filesystem/ext2/directory_iterator.cpp | 87 | ||||
| -rw-r--r-- | kernel/kernel/filesystem/ext2/directory_iterator.hpp | 48 | ||||
| -rw-r--r-- | kernel/kernel/filesystem/ext2/directory_iterator.tests.cpp | 118 | ||||
| -rw-r--r-- | kernel/kernel/filesystem/ext2/filesystem.cpp | 36 | ||||
| -rw-r--r-- | kernel/kernel/filesystem/ext2/filesystem.tests.cpp | 8 | ||||
| -rw-r--r-- | kernel/kernel/filesystem/ext2/inode.tests.cpp | 5 | ||||
| -rw-r--r-- | kernel/kernel/filesystem/ext2/linked_directory_entry.hpp | 6 |
8 files changed, 282 insertions, 27 deletions
diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index 6f998084..1a5e2421 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -75,6 +75,7 @@ target_sources("kernel_lib" PRIVATE "kernel/filesystem/devfs/module.cpp" # ext2 Filesystem + "kernel/filesystem/ext2/directory_iterator.cpp" "kernel/filesystem/ext2/error.cpp" "kernel/filesystem/ext2/filesystem.cpp" "kernel/filesystem/ext2/inode.cpp" diff --git a/kernel/kernel/filesystem/ext2/directory_iterator.cpp b/kernel/kernel/filesystem/ext2/directory_iterator.cpp new file mode 100644 index 00000000..f37c87b6 --- /dev/null +++ b/kernel/kernel/filesystem/ext2/directory_iterator.cpp @@ -0,0 +1,87 @@ +#include <kernel/filesystem/ext2/directory_iterator.hpp> + +#include <kernel/filesystem/ext2/inode.hpp> + +#include <kstd/units.hpp> +#include <kstd/vector.hpp> + +#include <system.hpp> + +namespace kernel::filesystem::ext2 +{ + + directory_iterator::directory_iterator(inode const & inode) + : m_inode(&inode) + , m_buffer{sizeof(value_type)} + { + if (!inode.is_directory()) + { + kapi::system::panic("[FS:ext2] Tried perform directory iteration on non-directory inode {}", inode.number()); + } + read(); + } + + auto directory_iterator::operator*() const -> reference + { + return *reinterpret_cast<pointer>(m_buffer.data()); + } + + auto directory_iterator::operator->() const -> pointer + { + return reinterpret_cast<pointer>(m_buffer.data()); + } + + auto directory_iterator::operator++() -> directory_iterator & + { + if (!m_inode || m_file_offset >= m_inode->size()) + { + m_inode = nullptr; + return *this; + } + read(); + return *this; + } + + auto directory_iterator::operator++(int) -> directory_iterator + { + auto copy = *this; + ++(*this); + return copy; + } + + auto operator==(directory_iterator const & lhs, directory_iterator const & rhs) -> bool + { + if (lhs.m_inode == rhs.m_inode) + { + return lhs.m_inode == nullptr || lhs.m_file_offset == rhs.m_file_offset; + } + return false; + } + + auto directory_iterator::read() -> void + { + if (auto result = m_inode->read(m_buffer, m_file_offset); !result) + { + kapi::system::panic("[FS:ext2] failed to read directory entry", result.error()); + } + + auto entry = reinterpret_cast<pointer>(m_buffer.data()); + + auto const remainder = entry->name_len - 1; + + if (remainder > 0) + { + m_buffer.resize(m_buffer.size() + remainder); + + if (auto result = m_inode->read(m_buffer, m_file_offset); !result) + { + kapi::system::panic("[FS:ext2] failed to read directory entry", result.error()); + } + + entry = reinterpret_cast<pointer>(m_buffer.data()); + } + + m_file_offset = m_file_offset + static_cast<kstd::bytes>(entry->rec_len); + } + +} // namespace kernel::filesystem::ext2
\ No newline at end of file diff --git a/kernel/kernel/filesystem/ext2/directory_iterator.hpp b/kernel/kernel/filesystem/ext2/directory_iterator.hpp new file mode 100644 index 00000000..92dd2b51 --- /dev/null +++ b/kernel/kernel/filesystem/ext2/directory_iterator.hpp @@ -0,0 +1,48 @@ +#ifndef TEACHOS_KERNEL_FILESYSTEM_EXT2_DIRECTORY_ITERATOR_HPP +#define TEACHOS_KERNEL_FILESYSTEM_EXT2_DIRECTORY_ITERATOR_HPP + +#include <kernel/filesystem/ext2/inode.hpp> +#include <kernel/filesystem/ext2/linked_directory_entry.hpp> + +#include <kstd/memory.hpp> +#include <kstd/units.hpp> +#include <kstd/vector.hpp> + +#include <cstddef> +#include <iterator> + +namespace kernel::filesystem::ext2 +{ + + struct directory_iterator + { + using iterator_category = std::forward_iterator_tag; + using iterator_concept = std::forward_iterator_tag; + using value_type = linked_directory_entry; + using pointer = linked_directory_entry const *; + using reference = linked_directory_entry const &; + + constexpr directory_iterator() = default; + explicit directory_iterator(inode const & inode); + + auto operator*() const -> reference; + + auto operator->() const -> pointer; + + auto operator++() -> directory_iterator &; + + auto operator++(int) -> directory_iterator; + + auto friend operator==(directory_iterator const & lhs, directory_iterator const & rhs) -> bool; + + private: + auto read() -> void; + + kstd::observer_ptr<inode const> m_inode{}; + kstd::bytes m_file_offset{}; + kstd::vector<std::byte> m_buffer{}; + }; + +} // namespace kernel::filesystem::ext2 + +#endif
\ No newline at end of file diff --git a/kernel/kernel/filesystem/ext2/directory_iterator.tests.cpp b/kernel/kernel/filesystem/ext2/directory_iterator.tests.cpp new file mode 100644 index 00000000..20ef7009 --- /dev/null +++ b/kernel/kernel/filesystem/ext2/directory_iterator.tests.cpp @@ -0,0 +1,118 @@ +#include <kernel/filesystem/ext2/directory_iterator.hpp> + +#include <kernel/devices/storage.hpp> +#include <kernel/filesystem/device_inode.hpp> +#include <kernel/filesystem/ext2/filesystem.hpp> +#include <kernel/filesystem/ext2/inode.hpp> +#include <kernel/filesystem/mount.hpp> +#include <kernel/test_support/filesystem/storage_boot_module_fixture.hpp> + +#include <kstd/memory.hpp> + +#include <catch2/catch_test_macros.hpp> + +#include <algorithm> +#include <filesystem> +#include <string> +#include <vector> + +SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, + "Ext2 directory_iterator walks a real directory and terminates", "[filesystem][ext2][readdir]") +{ + auto const image_path = std::filesystem::path{KERNEL_TEST_ASSETS_DIR} / "ext2_1KB_fs.img"; + + GIVEN("a mounted ext2 filesystem and its root directory") + { + REQUIRE(std::filesystem::exists(image_path)); + REQUIRE_NOTHROW(setup_modules_from_img({"test_img_module"}, {image_path})); + + auto boot_device = kernel::devices::storage::determine_boot_device(); + REQUIRE(boot_device != nullptr); + + auto dev_inode = kstd::make_shared<kernel::filesystem::device_inode>(boot_device); + auto fs = kstd::make_shared<kernel::filesystem::ext2::filesystem>(); + auto mount = kernel::filesystem::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); + REQUIRE(mount); + + auto root = (*mount)->root_dentry()->inode(); + auto const & root_ext2_inode = static_cast<kernel::filesystem::ext2::inode const &>(*root); + + WHEN("iterating from begin to the default-constructed end sentinel") + { + auto names = std::vector<std::string>{}; + auto guard = 0; + + for (auto it = kernel::filesystem::ext2::directory_iterator{root_ext2_inode}; + it != kernel::filesystem::ext2::directory_iterator{}; ++it) + { + REQUIRE(guard++ < 64); // fails loudly on a non-terminating loop rather than hanging the suite + names.emplace_back(&it->name_start, it->name_len); + } + + THEN("iteration terminates on its own and finds the expected entries") + { + REQUIRE(guard < 64); + REQUIRE(std::ranges::find(names, ".") != names.end()); + REQUIRE(std::ranges::find(names, "..") != names.end()); + REQUIRE(std::ranges::find(names, "information") != names.end()); + } + } + } +} + +SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, + "Ext2 directory_iterator satisfies the forward-iterator multi-pass guarantee", + "[filesystem][ext2][readdir]") +{ + auto const image_path = std::filesystem::path{KERNEL_TEST_ASSETS_DIR} / "ext2_1KB_fs.img"; + + GIVEN("an iterator positioned at the first entry of the root directory") + { + REQUIRE(std::filesystem::exists(image_path)); + REQUIRE_NOTHROW(setup_modules_from_img({"test_img_module"}, {image_path})); + + auto boot_device = kernel::devices::storage::determine_boot_device(); + REQUIRE(boot_device != nullptr); + + auto dev_inode = kstd::make_shared<kernel::filesystem::device_inode>(boot_device); + auto fs = kstd::make_shared<kernel::filesystem::ext2::filesystem>(); + auto mount = kernel::filesystem::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); + REQUIRE(mount); + + auto root = (*mount)->root_dentry()->inode(); + auto const & root_ext2_inode = static_cast<kernel::filesystem::ext2::inode const &>(*root); + + auto original = kernel::filesystem::ext2::directory_iterator{root_ext2_inode}; + auto const first_name = std::string{&original->name_start, original->name_len}; + + WHEN("the iterator is copied, then only the original is advanced") + { + auto copy = original; + ++original; + + THEN("the copy still refers to the first entry, unaffected by advancing the original") + { + auto const copy_name = std::string{©->name_start, copy->name_len}; + REQUIRE(copy_name == first_name); + + auto const advanced_name = std::string{&original->name_start, original->name_len}; + REQUIRE(advanced_name != first_name); + } + } + } +} + +SCENARIO("Ext2 directory_iterator's default-constructed value is a valid, comparable end sentinel", + "[filesystem][ext2][readdir]") +{ + GIVEN("two independently default-constructed iterators") + { + auto first = kernel::filesystem::ext2::directory_iterator{}; + auto second = kernel::filesystem::ext2::directory_iterator{}; + + THEN("they compare equal to each other") + { + REQUIRE(first == second); + } + } +}
\ No newline at end of file diff --git a/kernel/kernel/filesystem/ext2/filesystem.cpp b/kernel/kernel/filesystem/ext2/filesystem.cpp index 18af5a61..7031cf5f 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.cpp +++ b/kernel/kernel/filesystem/ext2/filesystem.cpp @@ -2,6 +2,7 @@ #include <kernel/filesystem/error.hpp> #include <kernel/filesystem/ext2/block_group_descriptor.hpp> +#include <kernel/filesystem/ext2/directory_iterator.hpp> #include <kernel/filesystem/ext2/error.hpp> #include <kernel/filesystem/ext2/inode.hpp> #include <kernel/filesystem/ext2/linked_directory_entry.hpp> @@ -186,13 +187,13 @@ namespace kernel::filesystem::ext2 auto filesystem::lookup(inode_ptr const & parent, std::string_view name, driver_data_ptr driver_data) const -> kstd::result<inode_ptr> { - auto const ext2_parent = static_pointer_cast<inode>(parent); - if (!ext2_parent) + auto const directory = static_pointer_cast<inode>(parent); + if (!directory) { return kstd::failure(vfs_errc::invalid_inode); } - if (!ext2_parent->is_directory()) + if (!directory->is_directory()) { return kstd::failure(vfs_errc::not_a_directory); } @@ -203,34 +204,16 @@ namespace kernel::filesystem::ext2 return kstd::failure(vfs_errc::not_mounted); } - auto buffer = kstd::vector<std::byte>{block_size(*mount_state).value}; - - for (auto i = 0u; i < block_count(*ext2_parent, *mount_state); ++i) + for (auto it = directory_iterator{*directory}; it != directory_iterator{}; ++it) { - auto const global_block_number = inode_block_number(i, *ext2_parent, *mount_state); - if (!global_block_number) - { - return kstd::failure(global_block_number.error()); - } - - if (auto result = read_block(*global_block_number, buffer, *mount_state); !result) + if (it->inode == 0) { - return kstd::failure(result.error()); + continue; } - auto entry = reinterpret_cast<linked_directory_entry const *>(buffer.data()); - auto read = 0_B; - - while (read < block_size(*mount_state) && entry->inode != 0) + if (it->name() == name) { - auto const entry_name = std::string_view{&entry->name_start, entry->name_len}; - if (entry_name == name) - { - return read_inode(entry->inode, *mount_state); - } - - read += kstd::bytes{entry->rec_len}; - entry = reinterpret_cast<linked_directory_entry const *>(buffer.data() + read); + return read_inode(it->inode, *mount_state); } } @@ -309,6 +292,7 @@ namespace kernel::filesystem::ext2 { return kstd::failure(result.error()); } + created->set_size(block_size(*mount_state)); } if (auto result = write_inode(inode_number, created->data(), *mount_state); !result) diff --git a/kernel/kernel/filesystem/ext2/filesystem.tests.cpp b/kernel/kernel/filesystem/ext2/filesystem.tests.cpp index 6303959d..8d1fc3d9 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.tests.cpp +++ b/kernel/kernel/filesystem/ext2/filesystem.tests.cpp @@ -67,10 +67,12 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, auto information = fs->lookup(root, "information", driver_data); REQUIRE(information); REQUIRE(information.value()->is_directory()); + (*information)->set_owning_mount(*mount); auto info_1 = fs->lookup(*information, "info_1.txt", driver_data); REQUIRE(info_1); REQUIRE(info_1.value()->is_regular()); + (*info_1)->set_owning_mount(*mount); } THEN("lookup returns null for invalid inputs") @@ -79,8 +81,10 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, auto information = fs->lookup(root, "information", driver_data); REQUIRE(information); + (*information)->set_owning_mount(*mount); auto info_1 = fs->lookup(*information, "info_1.txt", driver_data); REQUIRE(info_1); + (*info_1)->set_owning_mount(*mount); REQUIRE(!fs->lookup(*info_1, "anything", driver_data)); REQUIRE(!fs->lookup(root, "does_not_exist", driver_data)); @@ -117,9 +121,11 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, auto new_inode = fs->create_inode(root, "blub", kapi::filesystem::file_type::regular, driver_data); REQUIRE(new_inode); REQUIRE(new_inode.value()->is_regular()); + (*new_inode)->set_owning_mount(*mount); lookup_result = fs->lookup(root, "blub", driver_data); REQUIRE(lookup_result); + (*lookup_result)->set_owning_mount(*mount); } THEN("a directory can be created") @@ -139,10 +145,12 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, { auto new_directory = fs->create_inode(root, "blub", kapi::filesystem::file_type::directory, driver_data); REQUIRE(new_directory); + (*new_directory)->set_owning_mount(*mount); auto new_file = fs->create_inode(new_directory.value(), "blub_file", kapi::filesystem::file_type::regular, driver_data); REQUIRE(new_file); + (*new_file)->set_owning_mount(*mount); auto lookup_result = fs->lookup(new_directory.value(), "blub_file", driver_data); REQUIRE(lookup_result); diff --git a/kernel/kernel/filesystem/ext2/inode.tests.cpp b/kernel/kernel/filesystem/ext2/inode.tests.cpp index 2d3a2ec2..4d57768b 100644 --- a/kernel/kernel/filesystem/ext2/inode.tests.cpp +++ b/kernel/kernel/filesystem/ext2/inode.tests.cpp @@ -111,6 +111,8 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, "Ext2 in auto information = fs->lookup(root, "information", driver_data); REQUIRE(information); + (*information)->set_owning_mount(*mount); + auto file = fs->lookup(*information, "info_1.txt", driver_data); REQUIRE(file); REQUIRE(file.value()->is_regular()); @@ -322,10 +324,11 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, "Ext2 in auto information = fs->lookup(root, "information", driver_data); REQUIRE(information); + (*information)->set_owning_mount(*mount); + auto file = fs->lookup(*information, "info_1.txt", driver_data); REQUIRE(file); REQUIRE(file.value()->is_regular()); - (*file)->set_owning_mount(*mount); auto mount_state = static_pointer_cast<kernel::filesystem::ext2::mount_state>(driver_data); diff --git a/kernel/kernel/filesystem/ext2/linked_directory_entry.hpp b/kernel/kernel/filesystem/ext2/linked_directory_entry.hpp index 08999cb8..2d364f05 100644 --- a/kernel/kernel/filesystem/ext2/linked_directory_entry.hpp +++ b/kernel/kernel/filesystem/ext2/linked_directory_entry.hpp @@ -2,12 +2,18 @@ #define TEACHOS_KERNEL_FILESYSTEM_EXT2_LINKED_DIRECTORY_ENTRY_HPP #include <cstdint> +#include <string_view> namespace kernel::filesystem::ext2 { //! A linked directory entry in the ext2 filesystem. struct [[gnu::packed]] linked_directory_entry { + [[nodiscard]] constexpr auto name() const noexcept -> std::string_view + { + return std::string_view{&name_start, name_len}; + } + uint32_t inode; uint16_t rec_len; uint8_t name_len; |
