diff options
Diffstat (limited to 'kernel')
65 files changed, 2585 insertions, 1351 deletions
diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index c7cf9498..35c3a957 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -60,28 +60,32 @@ target_sources("kernel_lib" PRIVATE "kernel/filesystem/dentry.cpp" "kernel/filesystem/device_inode.cpp" "kernel/filesystem/device_number_registry.cpp" + "kernel/filesystem/driver_registry.cpp" "kernel/filesystem/error.cpp" - "kernel/filesystem/filesystem.cpp" "kernel/filesystem/inode.cpp" "kernel/filesystem/mount_table.cpp" "kernel/filesystem/mount.cpp" "kernel/filesystem/open_file_descriptor.cpp" "kernel/filesystem/open_file_table.cpp" - "kernel/filesystem/type_registry.cpp" "kernel/filesystem/vfs.cpp" # DevFS Filesystem "kernel/filesystem/devfs/filesystem.cpp" "kernel/filesystem/devfs/inode.cpp" + "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" + "kernel/filesystem/ext2/module.cpp" + "kernel/filesystem/ext2/write_batch.cpp" # Rootfs Filesystem "kernel/filesystem/rootfs/filesystem.cpp" "kernel/filesystem/rootfs/inode.cpp" + "kernel/filesystem/rootfs/module.cpp" ) file(GLOB_RECURSE KERNEL_HEADERS diff --git a/kernel/kernel/filesystem/dentry.cpp b/kernel/kernel/filesystem/dentry.cpp index 9321453a..e82c5419 100644 --- a/kernel/kernel/filesystem/dentry.cpp +++ b/kernel/kernel/filesystem/dentry.cpp @@ -13,7 +13,8 @@ namespace kernel::filesystem { - dentry::dentry(kstd::shared_ptr<dentry> const & parent, kstd::shared_ptr<inode> const & inode, std::string_view name) + dentry::dentry(kstd::shared_ptr<dentry> const & parent, kstd::shared_ptr<kernel::filesystem::inode> const & inode, + std::string_view name) : m_name(name) , m_parent(parent) , m_inode(inode) @@ -29,7 +30,7 @@ namespace kernel::filesystem } } - auto dentry::get_inode() const -> kstd::shared_ptr<inode> const & + auto dentry::inode() const -> kstd::shared_ptr<kernel::filesystem::inode> const & { return m_inode; } diff --git a/kernel/kernel/filesystem/dentry.hpp b/kernel/kernel/filesystem/dentry.hpp index a33ff22d..f7897764 100644 --- a/kernel/kernel/filesystem/dentry.hpp +++ b/kernel/kernel/filesystem/dentry.hpp @@ -15,6 +15,9 @@ namespace kernel::filesystem //! A directory entry in the filesystem. struct dentry { + using dentry_ptr = kstd::shared_ptr<dentry>; + using inode_ptr = kstd::shared_ptr<kernel::filesystem::inode>; + //! Flags for the dentry. enum class dentry_flags : uint32_t { @@ -28,17 +31,17 @@ namespace kernel::filesystem //! @param parent The parent directory entry, if any. //! @param inode The associated inode for this dentry. //! @param name The name of the dentry. - dentry(kstd::shared_ptr<dentry> const & parent, kstd::shared_ptr<inode> const & inode, std::string_view name); + dentry(dentry_ptr const & parent, inode_ptr const & inode, std::string_view name); //! Get this entrys associated inode. //! //! @return The inode associated with this directory entry. - [[nodiscard]] auto get_inode() const -> kstd::shared_ptr<inode> const &; + [[nodiscard]] auto inode() const -> inode_ptr const &; //! Get this entrys parent directory entry. //! //! @return The parent directory entry, or @p nullptr if this entry has no parent. - [[nodiscard]] auto parent() const -> kstd::shared_ptr<dentry>; + [[nodiscard]] auto parent() const -> dentry_ptr; //! Get this entrys name. //! @@ -56,7 +59,7 @@ namespace kernel::filesystem //! Add a child to this entry. //! //! @param child The child directory entry to add. - auto add_child(kstd::shared_ptr<dentry> const & child) -> void; + auto add_child(dentry_ptr const & child) -> void; //! Find a child entry with the given name. //! @@ -65,7 +68,7 @@ namespace kernel::filesystem //! //! @param name The name of the child directory entry to find. //! @return A pointer to the child dentry if it exists, a null pointer otherwise. - [[nodiscard]] auto find_child(std::string_view name) const -> kstd::shared_ptr<dentry>; + [[nodiscard]] auto find_child(std::string_view name) const -> dentry_ptr; //! Set a flag for this entry. //! @@ -86,8 +89,8 @@ namespace kernel::filesystem private: kstd::string m_name; kstd::weak_ptr<dentry> m_parent; - kstd::vector<kstd::shared_ptr<dentry>> m_children; - kstd::shared_ptr<inode> m_inode; + kstd::vector<dentry_ptr> m_children; + inode_ptr m_inode; uint32_t m_flags; }; } // namespace kernel::filesystem diff --git a/kernel/kernel/filesystem/dentry.tests.cpp b/kernel/kernel/filesystem/dentry.tests.cpp index 55cdf21e..cf3915de 100644 --- a/kernel/kernel/filesystem/dentry.tests.cpp +++ b/kernel/kernel/filesystem/dentry.tests.cpp @@ -22,7 +22,7 @@ SCENARIO("Dentry construction", "[filesystem][dentry]") THEN("the dentry has the correct parent, inode, and name") { REQUIRE(child_dentry.parent() == parent_dentry); - REQUIRE(child_dentry.get_inode() == inode); + REQUIRE(child_dentry.inode() == inode); REQUIRE(child_dentry.name() == "child"); } @@ -47,7 +47,7 @@ SCENARIO("Dentry construction", "[filesystem][dentry]") THEN("the dentry has a null parent, the correct inode, and the correct name") { REQUIRE(child_dentry.parent() == nullptr); - REQUIRE(child_dentry.get_inode() == inode); + REQUIRE(child_dentry.inode() == inode); REQUIRE(child_dentry.name() == "child"); } diff --git a/kernel/kernel/filesystem/devfs/filesystem.cpp b/kernel/kernel/filesystem/devfs/filesystem.cpp index 92756a65..0de513a7 100644 --- a/kernel/kernel/filesystem/devfs/filesystem.cpp +++ b/kernel/kernel/filesystem/devfs/filesystem.cpp @@ -6,11 +6,9 @@ #include <kernel/filesystem/error.hpp> #include <kernel/filesystem/filesystem.hpp> #include <kernel/filesystem/inode.hpp> -#include <kernel/filesystem/type.hpp> #include <kapi/filesystem.hpp> -#include <kstd/flat_map.hpp> #include <kstd/memory.hpp> #include <kstd/result.hpp> #include <kstd/system_error.hpp> @@ -22,50 +20,27 @@ namespace kernel::filesystem::devfs { - struct type final : kernel::filesystem::type - { - [[nodiscard]] auto name() const noexcept -> std::string_view override - { - return "devfs"; - } - [[nodiscard]] auto probe(kstd::shared_ptr<kernel::filesystem::inode> const & backing_inode) const noexcept - -> kstd::result<std::uint32_t> override - { - if (backing_inode) - { - return kstd::failure(vfs_errc::invalid_argument); - } - return 0; - } + static_assert(sizeof(kernel::filesystem::filesystem) == sizeof(devfs::filesystem)); - [[nodiscard]] auto make_instance() const -> kstd::shared_ptr<kernel::filesystem::filesystem> override + auto filesystem::probe(inode_ptr const & inode) const -> kstd::result<std::uint32_t> + { + if (inode) { - return kstd::make_shared<filesystem>(); + return kstd::failure(vfs_errc::invalid_argument); } - }; - [[gnu::used]] - constexpr auto registration = type_registration<type>{}; + return 0; + } - auto filesystem::mount(kstd::shared_ptr<kernel::filesystem::inode> const &) -> kstd::result<void> + auto filesystem::mount(inode_ptr const &) -> kstd::result<mount_result> { - m_root_inode = kstd::make_shared<inode>(); - - std::ranges::for_each(device_number_registry::get().all(), [&](auto const & numbered) { - if (auto device = numbered.device.lock()) - { - m_inodes.emplace(numbered.name, kstd::make_shared<device_inode>(device)); - } - }); - - device_number_registry::get().subscribe(shared_from_this()); - - return kstd::success(); + auto root = kstd::make_shared<inode>(); + return mount_result{root, root}; } - auto filesystem::lookup(kstd::shared_ptr<kernel::filesystem::inode> const & parent, std::string_view name) const - -> kstd::result<kstd::shared_ptr<kernel::filesystem::inode>> + auto filesystem::lookup(inode_ptr const & parent, std::string_view name, driver_data_ptr driver_data) const + -> kstd::result<inode_ptr> { if (!parent) { @@ -77,38 +52,31 @@ namespace kernel::filesystem::devfs return kstd::failure(vfs_errc::not_a_directory); } - if (parent.get() != m_root_inode.get()) + if (parent.get() != driver_data.get()) { return kstd::failure(vfs_errc::invalid_inode); } - auto found = std::ranges::find_if(m_inodes, [&](auto const & entry) { return entry.first == name; }); - if (found != m_inodes.end()) + auto entries = device_number_registry::get().all(); + auto found = std::ranges::find_if(entries, [&](auto const & entry) { return entry.name == name; }); + + if (found == entries.end()) + { + return kstd::failure(vfs_errc::no_such_file_or_directory); + } + + if (auto device = found->device.lock()) { - return found->second; + return kstd::make_shared<device_inode>(device); } return kstd::failure(vfs_errc::no_such_file_or_directory); } - auto filesystem::create_inode(kstd::shared_ptr<kernel::filesystem::inode> const &, std::string_view, - kapi::filesystem::file_type, std::optional<kapi::filesystem::device_number>) - -> kstd::result<kstd::shared_ptr<kernel::filesystem::inode>> + auto filesystem::create_inode(inode_ptr const &, std::string_view, kapi::filesystem::file_type, driver_data_ptr, + std::optional<kapi::filesystem::device_number>) -> kstd::result<inode_ptr> { return kstd::failure(vfs_errc::read_only_file_system); } - auto filesystem::on_device_numbered(device_number_registry::entry const & numbered) -> void - { - if (auto device = numbered.device.lock()) - { - m_inodes.emplace(numbered.name, kstd::make_shared<device_inode>(device)); - } - } - - auto filesystem::on_device_unnumbered(device_number_registry::entry const & numbered) -> void - { - erase_if(m_inodes, [&](auto const & entry) { return entry.first == numbered.name; }); - } - } // namespace kernel::filesystem::devfs
\ No newline at end of file diff --git a/kernel/kernel/filesystem/devfs/filesystem.hpp b/kernel/kernel/filesystem/devfs/filesystem.hpp index 2b0221d0..aa33c58b 100644 --- a/kernel/kernel/filesystem/devfs/filesystem.hpp +++ b/kernel/kernel/filesystem/devfs/filesystem.hpp @@ -3,17 +3,18 @@ #include <kernel/filesystem/device_inode.hpp> #include <kernel/filesystem/device_number_registry.hpp> +#include <kernel/filesystem/error.hpp> #include <kernel/filesystem/filesystem.hpp> #include <kernel/filesystem/inode.hpp> #include <kapi/filesystem.hpp> -#include <kstd/flat_map.hpp> #include <kstd/memory.hpp> #include <kstd/result.hpp> #include <kstd/string.hpp> #include <kstd/system_error.hpp> +#include <cstdint> #include <optional> #include <string_view> @@ -26,27 +27,21 @@ namespace kernel::filesystem::devfs //! applications to interact with devices using standard file operations. The devfs filesystem dynamically creates //! inodes for devices registered in the system, enabling seamless access to device functionality through the //! filesystem interface. - struct filesystem : kernel::filesystem::filesystem, - kstd::enable_shared_from_this<filesystem>, - device_number_registry_observer + struct filesystem final : kernel::filesystem::filesystem { - auto mount(kstd::shared_ptr<kernel::filesystem::inode> const & backing_inode) -> kstd::result<void> override; + [[nodiscard]] auto probe(inode_ptr const & inode) const -> kstd::result<std::uint32_t> override; - [[nodiscard]] auto lookup(kstd::shared_ptr<kernel::filesystem::inode> const & parent, std::string_view name) const - -> kstd::result<kstd::shared_ptr<kernel::filesystem::inode>> override; + auto mount(inode_ptr const & backing_inode) -> kstd::result<mount_result> override; + + [[nodiscard]] auto lookup(inode_ptr const & parent, std::string_view name, driver_data_ptr driver_data) const + -> kstd::result<inode_ptr> override; //! @copydoc kernel::filesystem::filesystem::create_inode //! @return Always returns an error, as creating new inodes is not supported in devfs. - [[nodiscard]] auto create_inode(kstd::shared_ptr<kernel::filesystem::inode> const & parent, std::string_view name, - kapi::filesystem::file_type type, + [[nodiscard]] auto create_inode(inode_ptr const & parent, std::string_view name, kapi::filesystem::file_type type, + driver_data_ptr driver_data, std::optional<kapi::filesystem::device_number> raw_device = std::nullopt) - -> kstd::result<kstd::shared_ptr<kernel::filesystem::inode>> override; - - private: - auto on_device_numbered(device_number_registry::entry const & numbered) -> void override; - auto on_device_unnumbered(device_number_registry::entry const & numbered) -> void override; - - kstd::flat_map<kstd::string, kstd::shared_ptr<device_inode>> m_inodes{}; + -> kstd::result<inode_ptr> override; }; } // namespace kernel::filesystem::devfs diff --git a/kernel/kernel/filesystem/devfs/filesystem.tests.cpp b/kernel/kernel/filesystem/devfs/filesystem.tests.cpp index b5a07a22..ec0c1144 100644 --- a/kernel/kernel/filesystem/devfs/filesystem.tests.cpp +++ b/kernel/kernel/filesystem/devfs/filesystem.tests.cpp @@ -25,49 +25,47 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, setup_modules(1); auto fs = kstd::make_shared<kernel::filesystem::devfs::filesystem>(); - auto result = fs->mount(nullptr); - - THEN("mount succeeds") - { - REQUIRE(result); - REQUIRE(fs->root_inode()); - } + auto mount = fs->mount(nullptr); + REQUIRE(mount); + auto [root, driver_data] = *mount; + REQUIRE(root); THEN("lookup on root finds ram0 device inode") { - auto inode = fs->lookup(fs->root_inode(), "ram0"); + auto inode = fs->lookup(root, "ram0", driver_data); REQUIRE(inode); REQUIRE(inode.value()->is_device()); } THEN("lookup of an unknown device returns error") { - auto inode = fs->lookup(fs->root_inode(), "ram99"); + auto inode = fs->lookup(root, "ram99", driver_data); REQUIRE(!inode); } THEN("lookup with wrong parent returns error") { auto other_fs = kstd::make_shared<kernel::filesystem::devfs::filesystem>(); - CHECK(other_fs->mount(nullptr)); + auto other_root = other_fs->mount(nullptr); + CHECK(other_root); - auto inode = fs->lookup(other_fs->root_inode(), "ram0"); + auto inode = fs->lookup(other_root->first, "ram0", driver_data); REQUIRE(!inode); } THEN("lookup with a non-directory parent returns error") { - auto non_directory_inode = fs->lookup(fs->root_inode(), "ram0"); + auto non_directory_inode = fs->lookup(root, "ram0", driver_data); REQUIRE(non_directory_inode); REQUIRE_FALSE(non_directory_inode.value()->is_directory()); - auto result = fs->lookup(*non_directory_inode, "anything"); + auto result = fs->lookup(*non_directory_inode, "anything", driver_data); REQUIRE(!result); } THEN("create_inode always returns an error") { - auto result = fs->create_inode(fs->root_inode(), "new_device", kapi::filesystem::file_type::regular); + auto result = fs->create_inode(root, "new_device", kapi::filesystem::file_type::regular, driver_data); REQUIRE(!result); } } @@ -77,14 +75,16 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, setup_modules(3, 2048); auto fs = kstd::make_shared<kernel::filesystem::devfs::filesystem>(); - auto result = fs->mount(nullptr); - REQUIRE(result); + auto mount = fs->mount(nullptr); + REQUIRE(mount); + auto [root, driver_data] = *mount; + REQUIRE(root); THEN("lookup finds all generated RAM devices") { - REQUIRE(fs->lookup(fs->root_inode(), "ram0")); - REQUIRE(fs->lookup(fs->root_inode(), "ram1")); - REQUIRE(fs->lookup(fs->root_inode(), "ram2")); + REQUIRE(fs->lookup(root, "ram0", driver_data)); + REQUIRE(fs->lookup(root, "ram1", driver_data)); + REQUIRE(fs->lookup(root, "ram2", driver_data)); } } } @@ -94,12 +94,15 @@ SCENARIO("Devfs filesystem updates", "[filesystem][devfs]") GIVEN("A mounted devfs, and a bus attached to the root without a device attached") { auto fs = kstd::make_shared<kernel::filesystem::devfs::filesystem>(); - REQUIRE(fs->mount(nullptr)); + auto mount = fs->mount(nullptr); + REQUIRE(mount); + auto [root, driver_data] = *mount; + REQUIRE(root); auto bus = kstd::make_shared<kapi::devices::bus>("devfs_live_update_bus"); kapi::devices::get_root_bus()->add_child(bus); - REQUIRE_FALSE(fs->lookup(fs->root_inode(), "devfs_live_update_device_node")); + REQUIRE_FALSE(fs->lookup(root, "devfs_live_update_device_node", driver_data)); WHEN("a device is attached, bound, and published") { @@ -112,7 +115,7 @@ SCENARIO("Devfs filesystem updates", "[filesystem][devfs]") THEN("devfs finds it immediately") { - REQUIRE(fs->lookup(fs->root_inode(), "devfs_live_update_device_node")); + REQUIRE(fs->lookup(root, "devfs_live_update_device_node", driver_data)); } WHEN("the device is detached") @@ -121,7 +124,7 @@ SCENARIO("Devfs filesystem updates", "[filesystem][devfs]") THEN("devfs does not find it anymore") { - REQUIRE_FALSE(fs->lookup(fs->root_inode(), "devfs_live_update_device_node")); + REQUIRE_FALSE(fs->lookup(root, "devfs_live_update_device_node", driver_data)); } } } diff --git a/kernel/kernel/filesystem/devfs/inode.cpp b/kernel/kernel/filesystem/devfs/inode.cpp index 4153d3ac..8813eb2a 100644 --- a/kernel/kernel/filesystem/devfs/inode.cpp +++ b/kernel/kernel/filesystem/devfs/inode.cpp @@ -1,15 +1,35 @@ #include <kernel/filesystem/devfs/inode.hpp> +#include <kernel/filesystem/device_number_registry.hpp> +#include <kernel/filesystem/directory_listing_cursor.hpp> +#include <kernel/filesystem/directory_listing_entry.hpp> +#include <kernel/filesystem/error.hpp> + +#include <kapi/filesystem.hpp> + #include <kstd/result.hpp> #include <kstd/units.hpp> +#include <algorithm> +#include <bit> #include <cstddef> +#include <cstdint> +#include <functional> +#include <iterator> +#include <limits> +#include <ranges> #include <span> +#include <utility> using namespace kstd::units_literals; namespace kernel::filesystem::devfs { + namespace + { + constexpr auto static sentinel = directory_listing_cursor{std::numeric_limits<std::uint64_t>::max()}; + } + auto inode::read(std::span<std::byte>, kstd::bytes) const -> kstd::result<kstd::bytes> { return 0_B; @@ -24,4 +44,56 @@ namespace kernel::filesystem::devfs { return true; } + + auto inode::read_directory(directory_listing_cursor position, std::span<directory_listing_entry> entries) const + -> kstd::result<std::pair<std::size_t, directory_listing_cursor>> + { + auto const requested = entries.size(); + if (requested == 0) + { + return kstd::failure(vfs_errc::invalid_argument); + } + + if (position == sentinel) + { + return std::pair{0, sentinel}; + } + + auto devices = kernel::filesystem::device_number_registry::get().all(); + if (devices.empty()) + { + return std::pair{0, position}; + } + + std::ranges::stable_sort(devices, std::ranges::less{}, &kernel::filesystem::device_number_registry::entry::number); + + auto const device_number = kapi::filesystem::device_number{static_cast<std::uint8_t>(position.value >> 8), + static_cast<std::uint8_t>(position.value & 0xff)}; + auto const begin = std::ranges::lower_bound(devices, device_number, std::ranges::less{}, + &kernel::filesystem::device_number_registry::entry::number); + auto const end = std::ranges::end(devices); + auto const to_read = std::min(requested, static_cast<std::size_t>(std::ranges::distance(begin, end))); + + if (to_read == 0) + { + return std::pair{0, position}; + } + + std::ranges::transform(begin, begin + to_read, std::ranges::begin(entries), [](auto const & entry) { + return directory_listing_entry{.name = entry.name, + .type = entry.type, + .inode_number = std::bit_cast<std::uint16_t>(entry.number)}; + }); + + auto const last_copied = begin + to_read; + if (last_copied == end) + { + return std::pair{to_read, sentinel}; + } + + auto const new_device_number = last_copied->number; + auto const new_cursor = directory_listing_cursor{static_cast<std::uint64_t>(new_device_number.minor) | + (static_cast<std::uint64_t>(new_device_number.major) << 8)}; + return std::pair{to_read, new_cursor}; + } } // namespace kernel::filesystem::devfs
\ No newline at end of file diff --git a/kernel/kernel/filesystem/devfs/inode.hpp b/kernel/kernel/filesystem/devfs/inode.hpp index e907c02e..b83d484c 100644 --- a/kernel/kernel/filesystem/devfs/inode.hpp +++ b/kernel/kernel/filesystem/devfs/inode.hpp @@ -1,6 +1,8 @@ #ifndef TEACHOS_KERNEL_FILESYSTEM_DEVFS_INODE_HPP #define TEACHOS_KERNEL_FILESYSTEM_DEVFS_INODE_HPP +#include <kernel/filesystem/directory_listing_cursor.hpp> +#include <kernel/filesystem/directory_listing_entry.hpp> #include <kernel/filesystem/inode.hpp> #include <kstd/result.hpp> @@ -8,13 +10,14 @@ #include <cstddef> #include <span> +#include <utility> namespace kernel::filesystem::devfs { //! Inode implementation for the devfs filesystem. //! //! This inode represents root device node in the /dev directory. - struct inode : kernel::filesystem::inode + struct inode final : kernel::filesystem::inode { [[nodiscard]] auto read(std::span<std::byte> buffer, kstd::bytes offset) const -> kstd::result<kstd::bytes> override; @@ -22,6 +25,10 @@ namespace kernel::filesystem::devfs auto write(std::span<std::byte const> buffer, kstd::bytes offset) -> kstd::result<kstd::bytes> override; [[nodiscard]] auto is_directory() const -> bool override; + + [[nodiscard]] auto read_directory(directory_listing_cursor position, + std::span<directory_listing_entry> entries) const + -> kstd::result<std::pair<std::size_t, directory_listing_cursor>> override; }; } // namespace kernel::filesystem::devfs diff --git a/kernel/kernel/filesystem/devfs/inode.tests.cpp b/kernel/kernel/filesystem/devfs/inode.tests.cpp index 0d8ed5d1..581bf6c5 100644 --- a/kernel/kernel/filesystem/devfs/inode.tests.cpp +++ b/kernel/kernel/filesystem/devfs/inode.tests.cpp @@ -1,16 +1,42 @@ #include <kernel/filesystem/devfs/inode.hpp> +#include <kernel/filesystem/device_inode.hpp> +#include <kernel/filesystem/directory_listing_cursor.hpp> +#include <kernel/filesystem/directory_listing_entry.hpp> +#include <kernel/test_support/devices/block_device.hpp> +#include <kernel/test_support/devices/driver.hpp> + +#include <kapi/devices.hpp> +#include <kapi/filesystem.hpp> + #include <kstd/memory.hpp> #include <kstd/print.hpp> +#include <kstd/string.hpp> #include <kstd/units.hpp> #include <kstd/vector.hpp> #include <catch2/catch_test_macros.hpp> +#include <algorithm> #include <cstddef> +#include <tuple> using namespace kstd::units_literals; +namespace +{ + auto make_and_publish(kstd::shared_ptr<kapi::devices::bus> const & bus, kstd::string name) + -> kstd::shared_ptr<kapi::devices::device> + + { + auto device = kstd::make_shared<kernel::tests::devices::block_device>(name, 512_B); + bus->add_child(device); + REQUIRE(kernel::tests::devices::bind(*device)); + REQUIRE(kapi::devices::publish_facet<kapi::filesystem::block_special_file>(device, name)); + return device; + } +} // namespace + SCENARIO("Devfs inode creation", "[filesystem][devfs][inode]") { GIVEN("a devfs inode") @@ -56,3 +82,154 @@ SCENARIO("Devfs inode read/write", "[filesystem][devfs][inode]") } } } + +SCENARIO("Devfs inode directory listing", "[filesystem][devfs][inode]") +{ + GIVEN("a devfs inode and no registered devices") + { + auto root = kernel::filesystem::devfs::inode{}; + + THEN("the directory listing is empty") + { + auto entries = kstd::vector<kernel::filesystem::directory_listing_entry>{4}; + auto result = root.read_directory(kernel::filesystem::directory_listing_cursor{}, entries); + + REQUIRE(result); + auto [count, next] = *result; + REQUIRE(count == 0); + } + } + + GIVEN("a devfs inode and three registered devices") + { + auto root = kernel::filesystem::devfs::inode{}; + + auto bus = kstd::make_shared<kapi::devices::bus>("devfs_readdir_bus"); + kapi::devices::get_root_bus()->add_child(bus); + + auto device1 = make_and_publish(bus, "devfs_readdir_device1"); + auto device2 = make_and_publish(bus, "devfs_readdir_device2"); + auto device3 = make_and_publish(bus, "devfs_readdir_device3"); + + WHEN("the directory listing is requested with a buffer large enough for all of them") + { + auto entries = kstd::vector<kernel::filesystem::directory_listing_entry>{4}; + auto result = root.read_directory(kernel::filesystem::directory_listing_cursor{}, entries); + + THEN("all devices are listed") + { + REQUIRE(result); + auto [count, next] = *result; + REQUIRE(count == 3); + + auto names = kstd::vector<kstd::string>{3}; + for (auto i = 0uz; i < count; ++i) + { + REQUIRE(entries[i].type == kapi::filesystem::file_type::block_device); + names.push_back(entries[i].name); + } + + REQUIRE(std::ranges::find(names, "devfs_readdir_device1") != names.end()); + REQUIRE(std::ranges::find(names, "devfs_readdir_device2") != names.end()); + REQUIRE(std::ranges::find(names, "devfs_readdir_device3") != names.end()); + + AND_THEN("listing again with the returned cursor reports 'end-of-directory'") + { + auto result = root.read_directory(next, entries); + + REQUIRE(result); + auto [count, cursor] = *result; + REQUIRE(count == 0); + REQUIRE(cursor == next); + } + } + } + + WHEN("read_directory is called one entry at a time, chaining the returned cursor") + { + auto seen = kstd::vector<kstd::string>{}; + auto position = kernel::filesystem::directory_listing_cursor{}; + + for (auto guard = 0; guard < 4; ++guard) + { + auto one = kstd::vector<kernel::filesystem::directory_listing_entry>(1); + auto result = root.read_directory(position, one); + REQUIRE(result); + auto [count, next] = *result; + if (count == 0) + { + break; + } + seen.push_back(one[0].name); + position = next; + } + + THEN("nothing is skipped or duplicated across the paginated calls") + { + REQUIRE(seen.size() == 3); + auto sorted = seen; + std::ranges::sort(sorted); + std::ignore = std::ranges::unique(sorted); + REQUIRE(sorted.size() == 3); + } + } + + REQUIRE(kapi::devices::remove_device(*bus)); + } +} + +SCENARIO("Devfs inode read_directory orders by device number, not registration order", + "[filesystem][devfs][inode][readdir]") +{ + GIVEN("a device that frees the lowest minor number before a later device reuses it") + { + auto root = kernel::filesystem::devfs::inode{}; + + auto bus = kstd::make_shared<kapi::devices::bus>("devfs_readdir_reuse_bus"); + kapi::devices::get_root_bus()->add_child(bus); + + auto first_out = make_and_publish(bus, "readdir_reuse_first_out"); + auto still_in = make_and_publish(bus, "readdir_reuse_still_in"); + REQUIRE(kapi::devices::remove_device(*first_out)); + auto later_in = make_and_publish(bus, "readdir_reuse_later_in"); + + WHEN("read_directory is called with room for both remaining entries") + { + auto entries = kstd::vector<kernel::filesystem::directory_listing_entry>(2); + auto result = root.read_directory(kernel::filesystem::directory_listing_cursor{}, entries); + + THEN("later_in (the reused, lower device number) comes back before still_in, by number, not by " + "when each was registered") + { + REQUIRE(result); + auto [count, next] = *result; + REQUIRE(count == 2); + REQUIRE(entries[0].name == "readdir_reuse_later_in"); + REQUIRE(entries[1].name == "readdir_reuse_still_in"); + } + } + + REQUIRE(kapi::devices::remove_device(*bus)); + } +} + +SCENARIO("Devfs read_directory rejects non-directory inodes", "[filesystem][devfs][inode][readdir]") +{ + GIVEN("a device_inode, which does not represent a directory") + { + auto bus = kstd::make_shared<kapi::devices::bus>("devfs_readdir_nondir_bus"); + kapi::devices::get_root_bus()->add_child(bus); + auto device = make_and_publish(bus, "readdir_nondir_device"); + auto node = kernel::filesystem::device_inode{device}; + + THEN("read_directory falls through to the base class default rather than enumerating anything") + { + auto entries = kstd::vector<kernel::filesystem::directory_listing_entry>(1); + auto result = node.read_directory(kernel::filesystem::directory_listing_cursor{}, entries); + + REQUIRE_FALSE(result); + } + + REQUIRE(kapi::devices::remove_device(*bus)); + } +}
\ No newline at end of file diff --git a/kernel/kernel/filesystem/devfs/module.cpp b/kernel/kernel/filesystem/devfs/module.cpp new file mode 100644 index 00000000..a7a1aca9 --- /dev/null +++ b/kernel/kernel/filesystem/devfs/module.cpp @@ -0,0 +1,28 @@ +#include <kernel/filesystem/devfs/filesystem.hpp> +#include <kernel/filesystem/driver_registry.hpp> +#include <kernel/filesystem/filesystem.hpp> + +#include <kstd/memory.hpp> + +#include <string_view> + +namespace +{ + + struct descriptor final : kernel::filesystem::driver_descriptor + { + [[nodiscard]] auto name() const noexcept -> std::string_view override + { + return "devfs"; + } + + [[nodiscard]] auto make_instance() const -> kernel::filesystem::filesystem::filesystem_ptr override + { + return kstd::make_shared<kernel::filesystem::devfs::filesystem>(); + } + }; + + [[gnu::used]] + constexpr auto registration = kernel::filesystem::driver_module<descriptor>{}; + +} // namespace
\ No newline at end of file diff --git a/kernel/kernel/filesystem/device_number_registry.cpp b/kernel/kernel/filesystem/device_number_registry.cpp index 42663dba..64cdb062 100644 --- a/kernel/kernel/filesystem/device_number_registry.cpp +++ b/kernel/kernel/filesystem/device_number_registry.cpp @@ -1,7 +1,6 @@ #include <kernel/filesystem/device_number_registry.hpp> #include <kernel/filesystem/error.hpp> -#include <kernel/filesystem/file_type.hpp> #include <kernel/filesystem/file_type_bindings.hpp> #include <kapi/devices.hpp> diff --git a/kernel/kernel/filesystem/device_number_registry.hpp b/kernel/kernel/filesystem/device_number_registry.hpp index 2391f179..2f0f0226 100644 --- a/kernel/kernel/filesystem/device_number_registry.hpp +++ b/kernel/kernel/filesystem/device_number_registry.hpp @@ -1,8 +1,6 @@ #ifndef TEACHOS_KERNEL_FILESYSTEM_DEVICE_NUMBER_REGISTRY_HPP #define TEACHOS_KERNEL_FILESYSTEM_DEVICE_NUMBER_REGISTRY_HPP -#include <kernel/filesystem/file_type.hpp> - #include <kapi/devices.hpp> #include <kapi/filesystem.hpp> #include <kapi/tracked_mutex.hpp> @@ -23,6 +21,8 @@ namespace kernel::filesystem //! A registry to assign stable device numbers to published devices. struct device_number_registry final : kapi::devices::facet_registry_observer { + using file_type = kapi::filesystem::file_type; + //! A single numbered device. //! //! Theses entries are used, for example, in devfs to provide access to devices. diff --git a/kernel/kernel/filesystem/device_number_registry.tests.cpp b/kernel/kernel/filesystem/device_number_registry.tests.cpp index 913acf9a..60e17337 100644 --- a/kernel/kernel/filesystem/device_number_registry.tests.cpp +++ b/kernel/kernel/filesystem/device_number_registry.tests.cpp @@ -1,6 +1,5 @@ #include <kernel/filesystem/device_number_registry.hpp> -#include <kernel/filesystem/file_type.hpp> #include <kernel/test_support/devices/block_device.hpp> #include <kernel/test_support/devices/driver.hpp> @@ -66,7 +65,7 @@ SCENARIO("Device number registry resolves and numbers published block devices", AND_THEN("resolve() finds the device by number") { - REQUIRE(registry.resolve(all[0].number, kernel::filesystem::file_type::block)); + REQUIRE(registry.resolve(all[0].number, kapi::filesystem::file_type::block_device)); } AND_THEN("number_of() finds the device number") @@ -79,7 +78,7 @@ SCENARIO("Device number registry resolves and numbers published block devices", auto number = registry.number_of(*device); CHECK(number); - auto resolved = registry.resolve(*number, kernel::filesystem::file_type::block); + auto resolved = registry.resolve(*number, kapi::filesystem::file_type::block_device); CHECK(resolved); REQUIRE(resolved == device); @@ -105,7 +104,7 @@ SCENARIO("Device number registry resolves and numbers published block devices", THEN("resolve() does not report it anymore") { - REQUIRE(!registry.resolve(*number, kernel::filesystem::file_type::block)); + REQUIRE(!registry.resolve(*number, kapi::filesystem::file_type::block_device)); } } } diff --git a/kernel/kernel/filesystem/directory_listing_cursor.hpp b/kernel/kernel/filesystem/directory_listing_cursor.hpp new file mode 100644 index 00000000..5eb36c7a --- /dev/null +++ b/kernel/kernel/filesystem/directory_listing_cursor.hpp @@ -0,0 +1,23 @@ +#ifndef TEACHOS_KERNEL_FILESYSTEM_DIRECTORY_LISTING_CURSOR_HPP +#define TEACHOS_KERNEL_FILESYSTEM_DIRECTORY_LISTING_CURSOR_HPP + +#include <compare> +#include <cstdint> + +namespace kernel::filesystem +{ + + //! A cursor used to keep track of positions in directory listings. + struct directory_listing_cursor + { + constexpr auto friend operator<=>(directory_listing_cursor const &, directory_listing_cursor const &) noexcept + -> std::strong_ordering = default; + constexpr auto friend operator==(directory_listing_cursor const &, directory_listing_cursor const &) noexcept + -> bool = default; + + std::uint64_t value; + }; + +}; // namespace kernel::filesystem + +#endif
\ No newline at end of file diff --git a/kernel/kernel/filesystem/directory_listing_entry.hpp b/kernel/kernel/filesystem/directory_listing_entry.hpp new file mode 100644 index 00000000..f3dcf367 --- /dev/null +++ b/kernel/kernel/filesystem/directory_listing_entry.hpp @@ -0,0 +1,23 @@ +#ifndef TEACHOS_KERNEL_FILESYSTEM_DIRECTORY_LISTING_ENTRY_HPP +#define TEACHOS_KERNEL_FILESYSTEM_DIRECTORY_LISTING_ENTRY_HPP + +#include <kapi/filesystem.hpp> + +#include <kstd/string.hpp> + +#include <cstdint> + +namespace kernel::filesystem +{ + + //! An entry in a directory listing. + struct directory_listing_entry + { + kstd::string name; + kapi::filesystem::file_type type; + std::uint64_t inode_number; + }; + +}; // namespace kernel::filesystem + +#endif
\ No newline at end of file diff --git a/kernel/kernel/filesystem/driver_registry.cpp b/kernel/kernel/filesystem/driver_registry.cpp new file mode 100644 index 00000000..9fa2d474 --- /dev/null +++ b/kernel/kernel/filesystem/driver_registry.cpp @@ -0,0 +1,130 @@ +#include <kernel/filesystem/driver_registry.hpp> + +#include <kernel/filesystem/error.hpp> +#include <kernel/filesystem/filesystem.hpp> +#include <kernel/filesystem/inode.hpp> + +#include <kapi/system.hpp> + +#include <kstd/memory.hpp> +#include <kstd/print.hpp> +#include <kstd/result.hpp> +#include <kstd/vector.hpp> + +#include <algorithm> +#include <cstdint> +#include <functional> +#include <optional> +#include <ranges> +#include <span> +#include <string_view> +#include <utility> + +namespace kernel::filesystem +{ + + extern "C" + { + // We need to suppress clang-tidy linting warnings here, since these symbols are generated by the linker and we + // cannot choose their names, unless we wanted to extend the linker script needlessly. + // NOLINTBEGIN(readability-identifier-naming) + extern kstd::observer_ptr<driver_descriptor const> const __start_filesystem_drivers; + extern kstd::observer_ptr<driver_descriptor const> const __stop_filesystem_drivers; + // NOLINTEND(readability-identifier-naming) + } + + namespace + { + auto constinit instance = std::optional<driver_registry>{}; + } + + auto driver_registry::init() -> void + { + if (instance) + { + kapi::system::panic("[OS:FS] The filesystem driver registry was already initialized!"); + } + + instance.emplace(); + + auto instances = + std::span{&__start_filesystem_drivers, &__stop_filesystem_drivers} | + std::views::filter([](auto descriptor) { return descriptor != nullptr; }) | + std::views::transform([](auto descriptor) { return std::pair{descriptor, descriptor->make_instance()}; }) | + std::views::filter([](auto entry) { return entry.second != nullptr; }); + + std::ranges::for_each(instances, [](auto entry) { + auto [descriptor, driver] = entry; + kstd::println("[OS:FS] Registering filesystem driver '{}'", descriptor->name()); + instance->add(descriptor->name(), driver); + }); + } + + auto driver_registry::get() -> driver_registry & + { + if (!instance) + { + kapi::system::panic("[OS:FS] The filesystem driver registry has not been initialized!"); + } + + return *instance; + } + + auto driver_registry::find(std::string_view name) const -> std::optional<kstd::shared_ptr<filesystem>> + { + if (auto it = m_drivers.find(name); it != m_drivers.end()) + { + return it->second; + } + return std::nullopt; + } + + auto driver_registry::add(std::string_view name, kstd::shared_ptr<filesystem> driver) -> bool + { + auto result = m_drivers.emplace(name, driver); + if (!result.second) + { + kstd::println(kstd::print_sink::stderr, "[OS:FS] Tried to register duplicate filesystem driver '{}'!", name); + } + + return result.second; + } + + auto driver_registry::match(kstd::shared_ptr<inode> const & inode) const -> kstd::result<kstd::shared_ptr<filesystem>> + { + struct candidate + { + std::uint32_t priority; + kstd::shared_ptr<filesystem> driver; + }; + + auto candidates = kstd::vector<candidate>{}; + + for (auto const & entry : m_drivers) + { + auto const & [name, driver] = entry; + auto priority = driver->probe(inode); + if (priority) + { + candidates.push_back({*priority, driver}); + } + } + + std::ranges::stable_sort(candidates, std::ranges::greater{}, &candidate::priority); + + if (candidates.empty()) + { + return kstd::failure(vfs_errc::unsupported_filesystem); + } + + return candidates.front().driver; + } +} // namespace kernel::filesystem + +namespace kernel::tests::filesystem::driver_registry +{ + auto deinit() -> void + { + kernel::filesystem::instance.reset(); + } +} // namespace kernel::tests::filesystem::driver_registry
\ No newline at end of file diff --git a/kernel/kernel/filesystem/driver_registry.hpp b/kernel/kernel/filesystem/driver_registry.hpp new file mode 100644 index 00000000..1a6cd47e --- /dev/null +++ b/kernel/kernel/filesystem/driver_registry.hpp @@ -0,0 +1,73 @@ +#ifndef TEACHOS_KERNEL_DRIVER_REGISTRY_HPP +#define TEACHOS_KERNEL_DRIVER_REGISTRY_HPP + +#include <kernel/filesystem/filesystem.hpp> +#include <kernel/filesystem/inode.hpp> + +#include <kstd/flat_map.hpp> +#include <kstd/memory.hpp> +#include <kstd/result.hpp> +#include <kstd/string.hpp> +#include <kstd/vector.hpp> + +#include <concepts> +#include <optional> +#include <string_view> + +namespace kernel::filesystem +{ + + struct driver_descriptor + { + virtual ~driver_descriptor() = default; + + //! Get the name of the driver. + [[nodiscard]] virtual auto name() const noexcept -> std::string_view = 0; + + //! Create a new instance of the driver represented by this descriptor. + [[nodiscard]] virtual auto make_instance() const -> kstd::shared_ptr<filesystem> = 0; + }; + + template<std::derived_from<driver_descriptor> Type> + struct driver_module + { + constexpr auto static instance = Type{}; + [[using gnu: section("filesystem_drivers"), used, visibility("hidden")]] constexpr auto static pointer{ + kstd::make_observer<driver_descriptor const>(&instance), + }; + }; + + struct driver_registry + { + constexpr driver_registry() noexcept = default; + + auto static init() -> void; + + auto static get() -> driver_registry &; + + //! Register a new filesystem. + //! + //! @param name The name of the filesystem. + //! @param driver The filesystem driver to register. + //! @return @c true iff. the driver was successfully added, @c false if not. + auto add(std::string_view name, kstd::shared_ptr<filesystem> driver) -> bool; + + //! Find a registered filesystem driver + //! + //! @param name The name of the filesystem + [[nodiscard]] auto find(std::string_view name) const -> std::optional<kstd::shared_ptr<filesystem>>; + + //! Match all currently registered filesystem drivers against a given inode. + //! + //! @param inode The inode to match on, if any. + //! @return a match priority if th + [[nodiscard]] auto match(kstd::shared_ptr<inode> const & inode) const -> kstd::result<kstd::shared_ptr<filesystem>>; + + private: + //! A map from filesystem names to filesystem drivers. + kstd::flat_map<std::string_view, kstd::shared_ptr<filesystem>> m_drivers{}; + }; + +} // namespace kernel::filesystem + +#endif diff --git a/kernel/kernel/filesystem/error.cpp b/kernel/kernel/filesystem/error.cpp index 80ad1097..0d6ae01e 100644 --- a/kernel/kernel/filesystem/error.cpp +++ b/kernel/kernel/filesystem/error.cpp @@ -54,6 +54,10 @@ namespace kernel::filesystem return "no such device"; case vfs_errc::invalid_argument: return "invalid argument"; + case vfs_errc::not_mounted: + return "filesystem is not mounted"; + case vfs_errc::invalid_driver_data: + return "invalid driver data"; default: return "unknown VFS error"; } @@ -67,6 +71,7 @@ namespace kernel::filesystem case vfs_errc::invalid_filesystem: case vfs_errc::invalid_inode: case vfs_errc::invalid_argument: + case vfs_errc::invalid_driver_data: return make_error_condition(kstd::errc::invalid_argument); case vfs_errc::no_such_file_or_directory: case vfs_errc::mount_point_not_found: @@ -91,6 +96,8 @@ namespace kernel::filesystem return make_error_condition(kstd::errc::file_exists); case vfs_errc::no_such_device: return make_error_condition(kstd::errc::no_such_device); + case vfs_errc::not_mounted: + return make_error_condition(kstd::errc::not_connected); default: return kstd::error_condition{value, *this}; } diff --git a/kernel/kernel/filesystem/error.hpp b/kernel/kernel/filesystem/error.hpp index 83b11902..6b167355 100644 --- a/kernel/kernel/filesystem/error.hpp +++ b/kernel/kernel/filesystem/error.hpp @@ -27,6 +27,8 @@ namespace kernel::filesystem file_exists, no_such_device, invalid_argument, + not_mounted, + invalid_driver_data, }; [[nodiscard]] auto vfs_category() noexcept -> kstd::error_category const &; diff --git a/kernel/kernel/filesystem/ext2/directory_iterator.cpp b/kernel/kernel/filesystem/ext2/directory_iterator.cpp new file mode 100644 index 00000000..3e614f26 --- /dev/null +++ b/kernel/kernel/filesystem/ext2/directory_iterator.cpp @@ -0,0 +1,91 @@ +#include <kernel/filesystem/ext2/directory_iterator.hpp> + +#include <kernel/filesystem/ext2/filesystem.hpp> +#include <kernel/filesystem/ext2/inode.hpp> +#include <kernel/filesystem/ext2/mount_state.hpp> + +#include <kapi/system.hpp> + +#include <kstd/units.hpp> +#include <kstd/vector.hpp> + +namespace kernel::filesystem::ext2 +{ + + directory_iterator::directory_iterator(inode const & inode, filesystem const & filesystem, mount_state & state) + : m_inode{&inode} + , m_filesystem{&filesystem} + , m_state{&state} + , 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_filesystem->data_size(*m_inode, *m_state)) + { + 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_filesystem->do_read(*m_inode, m_buffer, m_file_offset, *m_state); !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_filesystem->do_read(*m_inode, m_buffer, m_file_offset, *m_state); !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..4e2fd9d8 --- /dev/null +++ b/kernel/kernel/filesystem/ext2/directory_iterator.hpp @@ -0,0 +1,51 @@ +#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 <kernel/filesystem/ext2/mount_state.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; + directory_iterator(inode const & inode, filesystem const & filesystem, mount_state & state); + + 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::observer_ptr<filesystem const> m_filesystem{}; + kstd::observer_ptr<mount_state> m_state{}; + 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..2d817e0b --- /dev/null +++ b/kernel/kernel/filesystem/ext2/directory_iterator.tests.cpp @@ -0,0 +1,123 @@ +#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/ext2/mount_state.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); + + auto mount_state = static_pointer_cast<kernel::filesystem::ext2::mount_state>((*mount)->driver_data()); + + 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, *fs, *mount_state}; + 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 mount_state = static_pointer_cast<kernel::filesystem::ext2::mount_state>((*mount)->driver_data()); + + auto original = kernel::filesystem::ext2::directory_iterator{root_ext2_inode, *fs, *mount_state}; + 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/error.cpp b/kernel/kernel/filesystem/ext2/error.cpp index ffe49460..73921379 100644 --- a/kernel/kernel/filesystem/ext2/error.cpp +++ b/kernel/kernel/filesystem/ext2/error.cpp @@ -42,6 +42,8 @@ namespace kernel::filesystem::ext2 return "not enough inodes"; case ext2_errc::unsupported_features_present: return "unsupported features present"; + case ext2_errc::name_too_long: + return "name too long"; default: return "unknown ext2 error"; }; @@ -54,6 +56,7 @@ namespace kernel::filesystem::ext2 { case ext2_errc::invalid_magic_number: case ext2_errc::invalid_root_inode: + case ext2_errc::name_too_long: if (condition.category() == kernel::filesystem::vfs_category()) { return condition.value() == static_cast<int>(kernel::filesystem::vfs_errc::invalid_filesystem); diff --git a/kernel/kernel/filesystem/ext2/error.hpp b/kernel/kernel/filesystem/ext2/error.hpp index 59df1669..edd0d017 100644 --- a/kernel/kernel/filesystem/ext2/error.hpp +++ b/kernel/kernel/filesystem/ext2/error.hpp @@ -21,7 +21,8 @@ namespace kernel::filesystem::ext2 failed_to_read_block_group_descriptors, not_enough_free_blocks, not_enough_inodes, - unsupported_features_present + unsupported_features_present, + name_too_long, }; [[nodiscard]] auto ext2_category() noexcept -> kstd::error_category const &; diff --git a/kernel/kernel/filesystem/ext2/filesystem.cpp b/kernel/kernel/filesystem/ext2/filesystem.cpp index fcccad8f..bf977c46 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.cpp +++ b/kernel/kernel/filesystem/ext2/filesystem.cpp @@ -2,24 +2,29 @@ #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> +#include <kernel/filesystem/ext2/mount_state.hpp> #include <kernel/filesystem/ext2/superblock.hpp> +#include <kernel/filesystem/ext2/write_batch.hpp> #include <kernel/filesystem/filesystem.hpp> #include <kernel/filesystem/inode.hpp> -#include <kernel/filesystem/type.hpp> #include <kapi/filesystem.hpp> #include <kapi/system.hpp> #include <kstd/cstring.hpp> #include <kstd/memory.hpp> +#include <kstd/mutex.hpp> #include <kstd/result.hpp> +#include <kstd/span.hpp> #include <kstd/system_error.hpp> #include <kstd/units.hpp> #include <kstd/vector.hpp> +#include <algorithm> #include <array> #include <cstddef> #include <cstdint> @@ -27,178 +32,201 @@ #include <optional> #include <span> #include <string_view> -#include <tuple> #include <utility> using namespace kstd::units_literals; namespace kernel::filesystem::ext2 { + + //! Check that this driver class is stateless. + static_assert(sizeof(kernel::filesystem::filesystem) == sizeof(kernel::filesystem::ext2::filesystem)); + namespace { - [[nodiscard]] auto bitmap_is_set(kstd::vector<uint8_t> const & bitmap, size_t index) -> bool + struct indirect_level { - auto const byte_index = index / 8; - auto const bit_index = index % 8; - return (bitmap.at(byte_index) & static_cast<uint8_t>(1u << bit_index)) != 0; - } + uint32_t slot_index; + size_t capacity; + }; - auto bitmap_set(kstd::vector<uint8_t> & bitmap, size_t index) -> void + //! Check if the given bit has the bit at the given index set. + //! + //! @warning This function will panic if the bit index is out-of-bounds for the given bitmap. + //! + //! @param bitmap The bitmap to check. + //! @param index The index of the bit to check. + [[nodiscard]] auto bitmap_is_set(kstd::vector<std::byte> const & bitmap, size_t index) -> bool { auto const byte_index = index / 8; auto const bit_index = index % 8; - bitmap.at(byte_index) |= static_cast<uint8_t>(1u << bit_index); + return (bitmap.at(byte_index) & static_cast<std::byte>(1u << bit_index)) != std::byte{}; } - auto supported_incompatible_features = incompatible_features::file_types_in_directory_entries; - auto supported_ro_compatible_features = read_only_compatible_features::sparse_superblock_copies | // - read_only_compatible_features::large_file_support; - } // namespace - - struct type final : kernel::filesystem::type - { - [[nodiscard]] auto name() const noexcept -> std::string_view override + //! Set the bit at the given index in the given bitmap. + //! + //! @warning This function will panic if the bit index is out-of-bounds for the given bitmap. + //! + //! @param bitmap The bitmap to manipulate. + //! @param index The index of the bit to set. + auto bitmap_set(kstd::vector<std::byte> & bitmap, size_t index) -> void { - return "ext2"; + auto const byte_index = index / 8; + auto const bit_index = index % 8; + bitmap.at(byte_index) |= static_cast<std::byte>(1u << bit_index); } - [[nodiscard]] auto probe(kstd::shared_ptr<kernel::filesystem::inode> const & backing_inode) const noexcept - -> kstd::result<std::uint32_t> override + //! Get the number of block numbers storable on each level + //! + //! @param level The level to check. + //! @param block_size The size of a single block. + [[nodiscard]] constexpr auto block_numbers(std::size_t level, kstd::bytes block_size) noexcept -> std::size_t { - auto superblock = ext2::superblock{}; - auto bytes = std::as_writable_bytes(std::span{&superblock, 1}); - auto read_result = backing_inode->read(bytes, constants::superblock_offset); - - if (!read_result) - { - return kstd::failure(read_result.error()); - } - - if (superblock.magic != constants::magic_number) + if (level == 0) { - return kstd::failure(ext2_errc::invalid_magic_number); + return constants::direct_block_count; } - if (superblock.feature_incompat & std::to_underlying(~supported_incompatible_features) || - superblock.feature_ro_compat & std::to_underlying(~supported_ro_compatible_features)) + auto const numbers_per_block = block_size / kstd::size_of<std::uint32_t>(); + auto result = numbers_per_block; + for (auto current_level = level; current_level > 1; --current_level) { - return kstd::failure(ext2_errc::unsupported_features_present); + result *= numbers_per_block; } - return 0; + return result; } - [[nodiscard]] auto make_instance() const -> kstd::shared_ptr<kernel::filesystem::filesystem> override + [[nodiscard]] constexpr auto indirect_levels(kstd::bytes block_size) -> std::array<indirect_level, 3> { - return kstd::make_shared<filesystem>(); + return { + {{constants::singly_indirect_block_index, block_numbers(1, block_size)}, + {constants::doubly_indirect_block_index, block_numbers(2, block_size)}, + {constants::triply_indirect_block_index, block_numbers(3, block_size)}} + }; } - }; - [[gnu::used]] - constexpr auto registration = type_registration<type>{}; + //! The set of "incompatible" Extended Filesystem features supported by this driver. + constexpr auto supported_incompatible_features = incompatible_features::file_types_in_directory_entries; - auto filesystem::indirect_levels() const -> std::array<indirect_level, 3> + //! The set of "read-only" Extended Filesystem features supported by this driver. + constexpr auto supported_ro_compatible_features = read_only_compatible_features::sparse_superblock_copies | // + read_only_compatible_features::large_file_support; + + //! A set of permissions allowing all access to everyone. + constexpr auto no_access_restrictions = std::uint16_t{0x01FF}; + } // namespace + + auto filesystem::probe(inode_ptr const & backing_inode) const -> kstd::result<std::uint32_t> { - return { - {{constants::singly_indirect_block_index, block_numbers_per_singly_indirect_block()}, - {constants::doubly_indirect_block_index, block_numbers_per_doubly_indirect_block()}, - {constants::triply_indirect_block_index, block_numbers_per_triply_indirect_block()}} - }; + auto superblock = ext2::superblock{}; + auto const bytes = kstd::raw_bytes(superblock); + auto const read_result = backing_inode->read(bytes, constants::superblock_offset); + + if (!read_result) + { + return kstd::failure(read_result.error()); + } + + if (superblock.magic != constants::magic_number) + { + return kstd::failure(ext2_errc::invalid_magic_number); + } + + if (superblock.feature_incompat & std::to_underlying(~supported_incompatible_features) || + superblock.feature_ro_compat & std::to_underlying(~supported_ro_compatible_features)) + { + return kstd::failure(ext2_errc::unsupported_features_present); + } + + return 0; } - auto filesystem::mount(kstd::shared_ptr<kernel::filesystem::inode> const & backing_inode) -> kstd::result<void> + auto filesystem::mount(inode_ptr const & backing_inode) -> kstd::result<mount_result> { - std::ignore = kernel::filesystem::filesystem::mount(backing_inode); + auto const mount_state = kstd::make_shared<struct mount_state>(); + auto & [superblock, block_group_descriptors, backing, _] = *mount_state; - if (auto read_result = - m_backing_inode->read(std::as_writable_bytes(std::span{&m_superblock, 1}), constants::superblock_offset); - !read_result) + backing = backing_inode; + + auto const superblock_bytes = kstd::raw_bytes(superblock); + if (auto read_result = backing->read(superblock_bytes, constants::superblock_offset); !read_result) { return kstd::failure(read_result.error()); } - if (m_superblock.magic != constants::magic_number) + if (superblock.magic != constants::magic_number) { return kstd::failure(ext2_errc::invalid_magic_number); } - 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; + auto const blocks_per_group = superblock.blocks_per_group; + auto const num_block_groups = (superblock.blocks_count + blocks_per_group - 1) / blocks_per_group; - m_block_group_descriptors = kstd::vector<block_group_descriptor>(num_block_groups); - - if (auto read_result = m_backing_inode->read(std::as_writable_bytes(std::span{m_block_group_descriptors}), - block_group_descriptor_table_offset()); + block_group_descriptors = kstd::vector<block_group_descriptor>(num_block_groups); + auto const block_group_descriptors_bytes = kstd::raw_bytes(block_group_descriptors); + if (auto read_result = + backing->read(block_group_descriptors_bytes, block_group_descriptor_table_offset(*mount_state)); !read_result) { return kstd::failure(read_result.error()); } - if (auto root_inode = read_inode(constants::root_inode_number)) + auto const root = read_inode(constants::root_inode_number, *mount_state); + + if (!root) { - m_root_inode = root_inode.value(); + return kstd::failure(root.error()); } - if (!m_root_inode || !m_root_inode->is_directory()) + if (!(*root)->is_directory()) { return kstd::failure(ext2_errc::invalid_root_inode); } - return {}; + return mount_result{*root, mount_state}; } - auto filesystem::lookup(kstd::shared_ptr<kernel::filesystem::inode> const & parent, std::string_view name) const - -> kstd::result<kstd::shared_ptr<kernel::filesystem::inode>> + auto filesystem::lookup(inode_ptr const & parent, std::string_view name, driver_data_ptr driver_data) const + -> kstd::result<inode_ptr> { - if (!parent) + auto const directory = static_pointer_cast<inode>(parent); + if (!directory) { return kstd::failure(vfs_errc::invalid_inode); } - if (!parent->is_directory()) + if (!directory->is_directory()) { return kstd::failure(vfs_errc::not_a_directory); } - auto * ext2_parent = static_cast<inode *>(parent.get()); - auto const & inode_data = ext2_parent->data(); - kstd::vector<uint8_t> buffer(block_size().value); - - for (uint32_t i = 0; i < inode_block_count(inode_data); ++i) + auto const mount_state = static_pointer_cast<struct mount_state>(driver_data); + if (!mount_state) { - auto const global_block_number = map_inode_block_index_to_global_block_number(i, inode_data); - if (!global_block_number) - { - return kstd::failure(global_block_number.error()); - } + return kstd::failure(vfs_errc::not_mounted); + } + + auto guard = kstd::lock_guard{mount_state->lock}; - if (auto read_result = read_block(*global_block_number, buffer.data()); !read_result) + for (auto it = directory_iterator{*directory, *this, *mount_state}; it != directory_iterator{}; ++it) + { + if (it->inode == 0) { - return kstd::failure(read_result.error()); + continue; } - auto const * entry = reinterpret_cast<linked_directory_entry const *>(buffer.data()); - auto bytes_read = 0_B; - - while (bytes_read < block_size() && entry->inode != 0) + if (it->name() == name) { - auto const entry_name = std::string_view{entry->name.data(), entry->name_len}; - if (entry_name == name) - { - return read_inode(entry->inode); - } - - bytes_read += kstd::bytes{entry->rec_len}; - entry = reinterpret_cast<linked_directory_entry const *>(buffer.data() + bytes_read); + return read_inode(it->inode, *mount_state); } } return kstd::failure(vfs_errc::no_such_file_or_directory); } - auto filesystem::create_inode(kstd::shared_ptr<kernel::filesystem::inode> const & parent, std::string_view name, - kapi::filesystem::file_type type, - std::optional<kapi::filesystem::device_number> raw_device) + auto filesystem::create_inode(inode_ptr const & parent, std::string_view name, kapi::filesystem::file_type type, + driver_data_ptr driver_data, std::optional<kapi::filesystem::device_number> raw_device) -> kstd::result<kstd::shared_ptr<kernel::filesystem::inode>> { if (!parent) @@ -211,20 +239,28 @@ namespace kernel::filesystem::ext2 return kstd::failure(vfs_errc::not_a_directory); } - auto is_device = kapi::filesystem::is_device(type); - + auto const is_device = kapi::filesystem::is_device(type); if (is_device && !raw_device) { return kstd::failure(vfs_errc::invalid_argument); } - auto * ext2_parent = static_cast<inode *>(parent.get()); + auto const ext2_parent = static_pointer_cast<inode>(parent); if (!ext2_parent) { - return nullptr; + return kstd::failure(vfs_errc::invalid_argument); } - auto maybe_inode_number = allocate_inode(); + auto const mount_state = static_pointer_cast<struct mount_state>(driver_data); + if (!mount_state) + { + return kstd::failure(vfs_errc::invalid_driver_data); + } + + auto guard = kstd::lock_guard{mount_state->lock}; + auto batch = write_batch{*this, *mount_state}; + + auto maybe_inode_number = allocate_inode(*mount_state, batch); if (!maybe_inode_number) { return kstd::failure(maybe_inode_number.error()); @@ -232,63 +268,105 @@ namespace kernel::filesystem::ext2 auto inode_number = maybe_inode_number.value(); - inode_data new_inode_data{}; + auto data = inode_data{}; - uint16_t const full_access_mode = 0x01FF; // TODO use correct access rights - new_inode_data.mode = map_vfs_inode_type_into_inode_mode(type, full_access_mode); - new_inode_data.size = 0; - new_inode_data.dir_acl = 0; - new_inode_data.links_count = 1; - new_inode_data.blocks = 0; - new_inode_data.block.fill(0); + // TODO: use correct access rights + data.mode = map_vfs_inode_type_into_inode_mode(type, no_access_restrictions); + data.size = 0; + data.dir_acl = 0; + data.links_count = 1; + data.blocks = 0; + data.block.fill(0); if (is_device) { - new_inode_data.block[0] = static_cast<std::uint32_t>(raw_device->minor) | // - (static_cast<std::uint32_t>(raw_device->major) << 8); + data.block[0] = static_cast<std::uint32_t>(raw_device->minor) | // + (static_cast<std::uint32_t>(raw_device->major) << 8); } - // TODO preallocate blocks depending on type and m_superblock.prealloc_dir_blocks or m_superblock.prealloc_blocks; + auto created = kstd::make_shared<inode>(inode_number, data); + created->set_owning_mount(ext2_parent->owning_mount().lock()); - if (auto result = add_directory_entry(ext2_parent, name, inode_number, new_inode_data.mode); !result) + // TODO: preallocate blocks depending on type and m_superblock.prealloc_dir_blocks or m_superblock.prealloc_blocks; + if (auto result = add_directory_entry(*ext2_parent, name, *created, data.mode, *mount_state, batch); !result) { return kstd::failure(result.error()); } - auto new_inode = kstd::make_shared<inode>(this, inode_number, new_inode_data); - - if (new_inode->is_directory()) + if (created->is_directory()) { // TODO increment used_dirs_count in block group descriptor - if (auto result = init_directory(new_inode, ext2_parent); !result) + if (auto result = init_directory(*created, *ext2_parent, *mount_state, batch); !result) { return kstd::failure(result.error()); } + set_data_size(*created, block_size(*mount_state), *mount_state); + } + + batch.mark_inode(*created); + + return created; + } + + auto filesystem::block_size(mount_state const & state) const -> kstd::bytes + { + return kstd::bytes{constants::base_block_size.value << state.superblock.log_block_size}; + } + + auto filesystem::revision_level(mount_state const & state) const -> uint32_t + { + return state.superblock.rev_level; + } + + auto filesystem::inode_size(mount_state const & state) const -> kstd::bytes + { + return kstd::bytes(revision_level(state) == constants::good_old_revision ? 128 : state.superblock.inode_size); + } + + auto filesystem::block_count(inode const & inode, mount_state const & state) const -> uint32_t + { + return inode.data().blocks / (2 << state.superblock.log_block_size); + } + + auto filesystem::read_block(uint32_t block_number, std::span<std::byte> buffer, mount_state const & state) const + -> kstd::result<kstd::bytes> + { + if (buffer.size() < block_size(state).value) + { + return kstd::failure(vfs_errc::invalid_argument); } - if (auto write_result = write_inode(inode_number, new_inode->data()); !write_result) + auto const block_offset = static_cast<size_t>(block_number) * block_size(state); + return state.backing_inode->read(buffer, block_offset); + } + + auto filesystem::write_block(uint32_t block_number, std::span<std::byte const> buffer, mount_state & state) + -> kstd::result<kstd::bytes> + { + if (buffer.size() < block_size(state).value) { - return kstd::failure(write_result.error()); + return kstd::failure(vfs_errc::invalid_argument); } - return new_inode; + auto const block_offset = static_cast<size_t>(block_number) * block_size(state); + return state.backing_inode->write(buffer, block_offset); } - auto filesystem::add_directory_entry(inode * ext2_parent, std::string_view name, uint32_t child_inode_number, - [[maybe_unused]] uint16_t mode) -> kstd::result<void> + auto filesystem::add_directory_entry(inode & directory, std::string_view name, inode & child, std::uint16_t mode, + mount_state & state, write_batch & batch) -> kstd::result<void> { - auto const last_block_index = inode_block_count(ext2_parent->data()) - 1; - auto const global_block = map_inode_block_index_to_global_block_number(last_block_index, ext2_parent->data()); + auto const last_block_index = block_count(directory, state) - 1; + auto const global_block = inode_block_number(last_block_index, directory, state); if (!global_block) { return kstd::failure(global_block.error()); } - kstd::vector<uint8_t> buffer(block_size().value); - if (auto read_result = read_block(*global_block, buffer.data()); !read_result) + auto buffer = kstd::vector<std::byte>{block_size(state).value}; + if (auto result = read_block(*global_block, buffer, state); !result) { - return kstd::failure(read_result.error()); + return kstd::failure(result.error()); } // TODO handle "gaps" in directory entries (e.g., after deletions) in the current implementation, we only @@ -297,7 +375,7 @@ namespace kernel::filesystem::ext2 while (true) { auto const * entry = reinterpret_cast<linked_directory_entry const *>(buffer.data() + offset); - if (offset + kstd::bytes{entry->rec_len} >= block_size()) + if (offset + kstd::bytes{entry->rec_len} >= block_size(state)) { break; } @@ -314,98 +392,154 @@ namespace kernel::filesystem::ext2 if (kstd::bytes{last_entry->rec_len} - last_entry_actual_len >= needed_rec_len) { last_entry->rec_len = last_entry_actual_len.value; - write_directory_entry_to_buffer(buffer.data(), offset + last_entry_actual_len, child_inode_number, name, - file_type); - return global_block.and_then([&](auto block_number) { return write_block(block_number, buffer.data()); }) + auto entry_offset = offset + last_entry_actual_len; + auto entry = write_directory_entry_to_buffer(buffer, entry_offset, child, name, file_type); + if (!entry) + { + return kstd::failure(entry.error()); + } + + auto remainder = block_size(state) - entry_offset; + (*entry)->rec_len = remainder.value; + + return global_block.and_then([&](auto block_number) { return write_block(block_number, buffer, state); }) .transform([](auto) {}); } else { - if (!ext2_parent->append_blocks(1)) + if (!directory.append_blocks(1, batch)) { return kstd::failure(ext2_errc::not_enough_free_blocks); } - auto const new_block_index = inode_block_count(ext2_parent->data()) - 1; - auto const new_global_block = map_inode_block_index_to_global_block_number(new_block_index, ext2_parent->data()); + auto const new_block_index = block_count(directory, state) - 1; + auto const new_global_block = inode_block_number(new_block_index, directory, state); if (!new_global_block) { return kstd::failure(new_global_block.error()); } - kstd::libc::memset(buffer.data(), 0, block_size().value); - write_directory_entry_to_buffer(buffer.data(), 0_B, child_inode_number, name, file_type); - if (auto update_result = - new_global_block.and_then([&](auto block_number) { return write_block(block_number, buffer.data()); }); - !update_result) + kstd::libc::memset(buffer.data(), 0, block_size(state).value); + auto entry = write_directory_entry_to_buffer(buffer, 0_B, child, name, file_type); + if (!entry) + { + return kstd::failure(entry.error()); + } + + auto remainder = block_size(state); + (*entry)->rec_len = remainder.value; + + if (auto result = + new_global_block.and_then([&](auto block_number) { return write_block(block_number, buffer, state); }); + !result) { - return kstd::failure(update_result.error()); + return kstd::failure(result.error()); } } - ext2_parent->data_mutable().size = (inode_block_count(ext2_parent->data()) * block_size()).value; - return write_inode(ext2_parent->number(), ext2_parent->data()); + auto size = block_count(directory, state) * block_size(state); + directory.data().size = size.value; + batch.mark_inode(directory); + return kstd::success(); } - auto filesystem::init_directory(kstd::shared_ptr<kernel::filesystem::ext2::inode> const & new_inode, - kernel::filesystem::ext2::inode * parent_inode) -> kstd::result<void> + auto filesystem::init_directory(inode & directory, inode & parent, mount_state & state, write_batch & batch) + -> kstd::result<void> { - if (!new_inode->append_blocks(1)) + if (!directory.append_blocks(1, batch)) { return kstd::failure(ext2_errc::not_enough_free_blocks); } - auto const global_block = map_inode_block_index_to_global_block_number(0, new_inode->data()); + auto const global_block = inode_block_number(0, directory, state); if (!global_block) { return kstd::failure(global_block.error()); } - kstd::vector<uint8_t> buffer(block_size().value, 0); - write_directory_entry_to_buffer(buffer.data(), 0_B, new_inode->number(), ".", 2); - auto const dot_actual_len = static_cast<kstd::bytes>((8u + 1 + 3u) & ~3u); - auto * entry = reinterpret_cast<linked_directory_entry *>(buffer.data()); - entry->rec_len = dot_actual_len.value; + auto buffer = kstd::vector<std::byte>{block_size(state).value}; + auto dot = write_directory_entry_to_buffer(buffer, 0_B, directory, ".", kapi::filesystem::file_type::directory); - write_directory_entry_to_buffer(buffer.data(), dot_actual_len, parent_inode->number(), "..", 2); + if (!dot) + { + return kstd::failure(dot.error()); + } + + auto dot_dot_offset = kstd::bytes{(*dot)->rec_len}; + auto dot_dot = + write_directory_entry_to_buffer(buffer, dot_dot_offset, parent, "..", kapi::filesystem::file_type::directory); + if (!dot_dot) + { + return kstd::failure(dot_dot.error()); + } + auto remainder = block_size(state) - dot_dot_offset; + (*dot_dot)->rec_len = remainder.value; // '..' inside the new dir counts as an extra hard link to the parent - auto & parent_inode_data = parent_inode->data_mutable(); + auto & parent_inode_data = parent.data(); parent_inode_data.links_count++; - return write_inode(parent_inode->number(), parent_inode_data) - .and_then([&]() { return global_block; }) - .and_then([&](auto block_number) { return write_block(block_number, buffer.data()); }) - .transform([](auto) {}); + batch.mark_inode(parent); + + return global_block.and_then([&](auto number) { return write_block(number, buffer, state); }).transform([](auto) { + }); } - auto filesystem::write_directory_entry_to_buffer(uint8_t * block_buffer, kstd::bytes offset, - uint32_t child_inode_number, std::string_view name, - uint8_t file_type) -> void + auto filesystem::write_directory_entry_to_buffer(std::span<std::byte> buffer, kstd::bytes offset, inode & target, + std::string_view name, kapi::filesystem::file_type type) + -> kstd::result<linked_directory_entry *> { - auto const name_len = static_cast<uint8_t>(name.size()); - auto * entry = reinterpret_cast<linked_directory_entry *>(block_buffer + offset); - entry->inode = child_inode_number; - entry->rec_len = static_cast<uint16_t>(block_size() - offset); - entry->name_len = name_len; - entry->file_type = file_type; - kstd::libc::memcpy(entry->name.data(), name.data(), name_len); + auto const name_length = static_cast<uint8_t>(name.size()); + auto const record_size = (sizeof(linked_directory_entry) - 1 + name_length + 3u) & ~3u; + if (buffer.size() - offset.value < record_size) + { + // TODO: improve error code. + return kstd::failure(vfs_errc::invalid_argument); + } + + auto * entry = reinterpret_cast<linked_directory_entry *>(buffer.data() + offset); + entry->inode = target.number(); + entry->rec_len = record_size; + entry->name_len = name_length; + entry->file_type = [=] { + switch (type) + { + case kapi::filesystem::file_type::regular: + return 1; + case kapi::filesystem::file_type::directory: + return 2; + case kapi::filesystem::file_type::character_device: + return 3; + case kapi::filesystem::file_type::block_device: + return 4; + case kapi::filesystem::file_type::fifo: + return 5; + case kapi::filesystem::file_type::socket: + return 6; + case kapi::filesystem::file_type::symbolic_link: + return 7; + default: + return 0; + } + }(); + kstd::libc::memcpy(&entry->name_start, name.data(), name_length); + return kstd::success(entry); } - auto filesystem::read_inode(uint32_t inode_number) const -> kstd::result<kstd::shared_ptr<inode>> + auto filesystem::read_inode(uint32_t inode_number, mount_state const & state) const + -> kstd::result<kstd::shared_ptr<inode>> { - if (auto inode_offset = calculate_inode_offset(inode_number)) + if (auto inode_offset = calculate_inode_offset(inode_number, state)) { auto new_inode_data = inode_data{}; - if (auto read_result = m_backing_inode->read(as_writable_bytes(std::span{&new_inode_data, 1}), *inode_offset); - !read_result) + if (auto read_result = state.backing_inode->read(kstd::raw_bytes(new_inode_data), *inode_offset); !read_result) { return kstd::failure(read_result.error()); } else { - return kstd::make_shared<inode>(const_cast<filesystem *>(this), inode_number, new_inode_data); + return kstd::make_shared<struct inode>(inode_number, new_inode_data); } } else @@ -414,11 +548,12 @@ namespace kernel::filesystem::ext2 } } - auto filesystem::write_inode(uint32_t inode_number, inode_data const & data) -> kstd::result<void> + auto filesystem::write_inode(uint32_t inode_number, inode_data const & data, mount_state & state) + -> kstd::result<void> { - if (auto inode_offset = calculate_inode_offset(inode_number)) + if (auto inode_offset = calculate_inode_offset(inode_number, state)) { - return m_backing_inode->write(as_bytes(std::span{&data, 1}), *inode_offset).transform([](auto) {}); + return state.backing_inode->write(kstd::raw_bytes(data), *inode_offset).transform([](auto) {}); } else { @@ -426,146 +561,208 @@ namespace kernel::filesystem::ext2 } } - auto filesystem::calculate_inode_offset(uint32_t inode_number) const -> kstd::result<kstd::bytes> + auto filesystem::calculate_inode_offset(uint32_t inode_number, mount_state const & state) const + -> kstd::result<kstd::bytes> { - auto const inodes_per_group = m_superblock.inodes_per_group; + auto const inodes_per_group = state.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()) + if (block_group_index >= state.block_group_descriptors.size()) { return kstd::failure(ext2_errc::invalid_block_group_index); } - auto const & block_group_descriptor = m_block_group_descriptors.at(block_group_index); + auto const & block_group_descriptor = state.block_group_descriptors.at(block_group_index); auto const inode_table_start_block = block_group_descriptor.inode_table; - auto const inode_table_offset = inode_table_start_block * block_size(); - auto const inode_offset = inode_table_offset + inode_index_within_group * inode_size(); + auto const inode_table_offset = inode_table_start_block * block_size(state); + auto const inode_offset = inode_table_offset + inode_index_within_group * inode_size(state); return inode_offset; } - auto filesystem::allocate_blocks(size_t count) -> kstd::result<kstd::vector<uint32_t>> + auto filesystem::allocate_blocks(size_t count, mount_state & state, write_batch & batch) + -> kstd::result<kstd::vector<uint32_t>> { - if (m_block_group_descriptors.empty() || count > m_superblock.free_blocks_count) + auto & [superblock, block_group_descriptors, backing_inode, _] = state; + + if (block_group_descriptors.empty() || count > superblock.free_blocks_count) { return kstd::failure(ext2_errc::not_enough_free_blocks); } - kstd::vector<uint32_t> allocated_blocks{}; + struct pending_group + { + std::size_t index; + std::size_t claimed; + kstd::vector<std::byte> bitmap; + }; - for (auto block_group_descriptor_index = 0uz; block_group_descriptor_index < m_block_group_descriptors.size(); - ++block_group_descriptor_index) + auto pending = kstd::vector<pending_group>{}; + auto allocated_blocks = kstd::vector<uint32_t>{}; + + for (auto group_index = 0uz; group_index < block_group_descriptors.size() && allocated_blocks.size() < count; + ++group_index) { - auto & block_group_descriptor = m_block_group_descriptors.at(block_group_descriptor_index); - if (block_group_descriptor.free_blocks_count == 0) + auto & descriptor = block_group_descriptors.at(group_index); + if (descriptor.free_blocks_count == 0) { continue; } - kstd::vector<uint8_t> block_bitmap(block_size().value, 0); - if (auto read_result = read_block(block_group_descriptor.block_bitmap, block_bitmap.data()); !read_result) + auto block_bitmap = kstd::vector<std::byte>{block_size(state).value}; + if (auto read_result = read_block(descriptor.block_bitmap, block_bitmap, state); !read_result) { return kstd::failure(read_result.error()); } - bool is_bitmap_dirty = false; - for (auto i = 0uz; i < static_cast<size_t>(m_superblock.blocks_per_group) && allocated_blocks.size() < count; ++i) + auto claimed = 0uz; + for (auto i = 0uz; i < static_cast<size_t>(superblock.blocks_per_group) && allocated_blocks.size() < count; ++i) { if (!bitmap_is_set(block_bitmap, i)) { - is_bitmap_dirty = true; - bitmap_set(block_bitmap, i); - block_group_descriptor.free_blocks_count--; - m_superblock.free_blocks_count--; - - allocated_blocks.push_back(i + block_group_descriptor_index * m_superblock.blocks_per_group); + ++claimed; + allocated_blocks.push_back(i + group_index * superblock.blocks_per_group); } } - if (is_bitmap_dirty) + if (claimed > 0) { - if (auto write_result = write_block(block_group_descriptor.block_bitmap, block_bitmap.data()); !write_result) - { - return kstd::failure(write_result.error()); - } - - // TODO update all block group descriptors - if (auto write_result = - m_backing_inode->write(as_bytes(std::span{&block_group_descriptor, 1}), - block_group_descriptor_table_offset() + - block_group_descriptor_index * kstd::size_of(block_group_descriptor)); - !write_result) - { - return kstd::failure(write_result.error()); - } + pending.push_back({group_index, claimed, std::move(block_bitmap)}); } } - // TODO update all superblocks - if (auto write_result = m_backing_inode->write(as_bytes(std::span{&m_superblock, 1}), constants::superblock_offset); - !write_result) + if (allocated_blocks.size() != count) { - return kstd::failure(write_result.error()); + return kstd::failure(ext2_errc::not_enough_free_blocks); } - if (allocated_blocks.size() != count) + for (auto & group : pending) { - return kstd::failure(ext2_errc::not_enough_free_blocks); + auto & descriptor = block_group_descriptors.at(group.index); + descriptor.free_blocks_count -= group.claimed; + superblock.free_blocks_count -= group.claimed; + + if (auto result = write_block(descriptor.block_bitmap, group.bitmap, state); !result) + { + return kstd::failure(result.error()); + } + batch.mark_group(group.index); } return allocated_blocks; } - auto filesystem::allocate_inode() -> kstd::result<uint32_t> + auto filesystem::data_size(inode const & inode, mount_state const & state) const -> kstd::bytes + { + uint64_t size = inode.data().size; + + if (revision_level(state) > constants::good_old_revision && inode.is_regular()) + { + size |= static_cast<uint64_t>(inode.data().dir_acl) << 32; + } + + return kstd::bytes{size}; + } + + auto filesystem::set_data_size(inode & inode, kstd::bytes new_size, mount_state const & state) const -> void + { + if (revision_level(state) > constants::good_old_revision && inode.is_regular()) + { + inode.data().dir_acl = static_cast<uint32_t>(new_size.value >> 32); + } + inode.data().size = static_cast<uint32_t>(new_size.value); + } + + auto filesystem::do_read(inode const & inode, std::span<std::byte> buffer, kstd::bytes offset, + mount_state const & state) const -> kstd::result<kstd::bytes> { - if (m_block_group_descriptors.empty() || m_superblock.free_inodes_count == 0) + auto const max_readable = data_size(inode, state) - offset; + auto const requested_size = std::min(kstd::bytes{buffer.size()}, max_readable); + + auto const block_size = this->block_size(state); + auto block_index = offset / block_size; + auto in_block_offset = offset % block_size; + + auto bytes_read = 0_B; + + while (bytes_read < requested_size) + { + auto const block_number = inode_block_number(block_index, inode, state); + if (!block_number) + { + break; + } + + auto const bytes_to_read = std::min(requested_size - bytes_read, block_size - in_block_offset); + if (block_number == 0) + { + kstd::libc::memset(buffer.data() + bytes_read, 0, bytes_to_read.value); + bytes_read += bytes_to_read; + } + else + { + auto const block_start_offset = block_number.value() * block_size; + auto const read_offset = block_start_offset + in_block_offset; + + auto const read_result = + state.backing_inode->read(buffer.subspan(bytes_read.value, bytes_to_read.value), read_offset); + if (!read_result) + { + return kstd::failure(read_result.error()); + } + + bytes_read += read_result.value(); + } + + block_index++; + in_block_offset = 0_B; + } + + return bytes_read; + } + + auto filesystem::allocate_inode(mount_state & state, write_batch & batch) -> kstd::result<uint32_t> + { + auto & [superblock, block_group_descriptors, _, __] = state; + + if (block_group_descriptors.empty() || superblock.free_inodes_count == 0) { return kstd::failure(ext2_errc::not_enough_inodes); } - for (auto block_group_descriptor_index = 0uz; block_group_descriptor_index < m_block_group_descriptors.size(); + for (auto block_group_descriptor_index = 0uz; block_group_descriptor_index < block_group_descriptors.size(); ++block_group_descriptor_index) { - auto & block_group_descriptor = m_block_group_descriptors.at(block_group_descriptor_index); + auto & block_group_descriptor = block_group_descriptors.at(block_group_descriptor_index); if (block_group_descriptor.free_inodes_count == 0) { continue; } - kstd::vector<uint8_t> inode_bitmap(block_size().value, 0); - if (auto read_result = read_block(block_group_descriptor.inode_bitmap, inode_bitmap.data()); !read_result) + auto inode_bitmap = kstd::vector<std::byte>{block_size(state).value}; + if (auto read_result = read_block(block_group_descriptor.inode_bitmap, inode_bitmap, state); !read_result) { return kstd::failure(read_result.error()); } - for (auto i = 0uz; i < static_cast<size_t>(m_superblock.inodes_per_group); ++i) + for (auto i = 0uz; i < static_cast<size_t>(superblock.inodes_per_group); ++i) { if (!bitmap_is_set(inode_bitmap, i)) { bitmap_set(inode_bitmap, i); block_group_descriptor.free_inodes_count--; - m_superblock.free_inodes_count--; + superblock.free_inodes_count--; - if (auto write_result = write_block(block_group_descriptor.inode_bitmap, inode_bitmap.data()); !write_result) - { - return kstd::failure(write_result.error()); - } - - if (auto write_result = write_block_group_descriptor(block_group_descriptor, block_group_descriptor_index); - !write_result) + if (auto write_result = write_block(block_group_descriptor.inode_bitmap, inode_bitmap, state); !write_result) { return kstd::failure(write_result.error()); } - // TODO update all superblocks - if (auto write_result = write_superblock(); !write_result) - { - return kstd::failure(write_result.error()); - } + batch.mark_group(block_group_descriptor_index); - return block_group_descriptor_index * m_superblock.inodes_per_group + i + 1; + return block_group_descriptor_index * superblock.inodes_per_group + i + 1; } } } @@ -573,36 +770,40 @@ namespace kernel::filesystem::ext2 return kstd::failure(ext2_errc::not_enough_inodes); } - auto filesystem::map_inode_block_index_to_global_block_number(size_t inode_block_index, inode_data data) const + auto filesystem::inode_block_number(size_t index, inode const & inode, mount_state const & state) const -> kstd::result<std::size_t> { - if (inode_block_index < constants::direct_block_count) + auto const & block_array = inode.data().block; + + if (index < constants::direct_block_count) { - return data.block.at(inode_block_index); + return block_array.at(index); } - inode_block_index -= constants::direct_block_count; - - for (auto const & level : indirect_levels()) + index -= constants::direct_block_count; + auto const block_size = this->block_size(state); + auto const numbers_per_block = block_numbers(1, block_size); + for (auto const & level : indirect_levels(block_size)) { - if (inode_block_index >= level.capacity) + auto const capacity = level.capacity; + if (index >= capacity) { - inode_block_index -= level.capacity; + index -= capacity; continue; } - auto block_number = data.block[level.slot_index]; + auto block_number = block_array[level.slot_index]; if (block_number == 0) { return 0; } - for (auto stride = level.capacity / block_numbers_per_block();; stride /= block_numbers_per_block()) + for (auto stride = capacity / numbers_per_block;; stride /= numbers_per_block) { - auto const idx = inode_block_index / stride; - inode_block_index %= stride; + auto const idx = index / stride; + index %= stride; - if (auto read_result = read_block_number_at_index(block_number, idx); !read_result) + if (auto read_result = read_block_number_at_index(block_number, idx, state); !read_result) { return kstd::failure(read_result.error()); } @@ -628,14 +829,14 @@ namespace kernel::filesystem::ext2 return kstd::failure(ext2_errc::invalid_block_index); } - auto filesystem::read_block_number_at_index(uint32_t block_number, size_t index) const -> kstd::result<uint32_t> + auto filesystem::read_block_number_at_index(uint32_t block_number, size_t index, mount_state const & state) const + -> kstd::result<uint32_t> { uint32_t block_number_buffer = 0; - auto const block_start_offset = block_number * block_size(); + auto const block_start_offset = block_number * block_size(state); auto const number_start_address = block_start_offset + index * kstd::size_of<uint32_t>(); - if (auto read_result = - m_backing_inode->read(as_writable_bytes(std::span{&block_number_buffer, 1}), number_start_address); + if (auto read_result = state.backing_inode->read(kstd::raw_bytes(block_number_buffer), number_start_address); !read_result) { return kstd::failure(read_result.error()); @@ -645,7 +846,8 @@ namespace kernel::filesystem::ext2 } auto filesystem::write_global_block_number_to_inode_block_index(size_t inode_block_index, inode_data & data, - uint32_t global_block_number) -> kstd::result<void> + uint32_t global_block_number, mount_state & state, + write_batch & batch) -> kstd::result<void> { if (inode_block_index < constants::direct_block_count) { @@ -654,7 +856,9 @@ namespace kernel::filesystem::ext2 } inode_block_index -= constants::direct_block_count; - for (auto const & level : indirect_levels()) + auto const block_size = this->block_size(state); + auto const numbers_per_block = block_numbers(1, block_size); + for (auto const & level : indirect_levels(block_size)) { if (inode_block_index >= level.capacity) { @@ -662,18 +866,19 @@ namespace kernel::filesystem::ext2 continue; } + auto & backing_inode = state.backing_inode; auto block_number = data.block[level.slot_index]; auto parent_byte_offset = 0_B; bool is_root = true; - for (auto stride = level.capacity / block_numbers_per_block();; stride /= block_numbers_per_block()) + for (auto stride = level.capacity / numbers_per_block;; stride /= numbers_per_block) { auto const idx = inode_block_index / stride; inode_block_index %= stride; if (idx == 0 && inode_block_index == 0) { - auto allocated = allocate_blocks(1); + auto allocated = allocate_blocks(1, state, batch); if (!allocated) { return kstd::failure(allocated.error()); @@ -687,7 +892,7 @@ namespace kernel::filesystem::ext2 } else { - if (auto write_result = m_backing_inode->write(as_bytes(std::span{&block_number, 1}), parent_byte_offset); + if (auto write_result = backing_inode->write(kstd::raw_bytes(block_number), parent_byte_offset); !write_result) { return kstd::failure(write_result.error()); @@ -695,11 +900,11 @@ namespace kernel::filesystem::ext2 } } - auto const byte_offset = block_number * block_size() + idx * kstd::size_of<uint32_t>(); + auto const byte_offset = block_number * block_size + idx * kstd::size_of<uint32_t>(); if (stride == 1) { - if (auto write_result = m_backing_inode->write(as_bytes(std::span{&global_block_number, 1}), byte_offset); + if (auto write_result = backing_inode->write(kstd::raw_bytes(global_block_number), byte_offset); !write_result) { return kstd::failure(write_result.error()); @@ -709,7 +914,7 @@ namespace kernel::filesystem::ext2 parent_byte_offset = byte_offset; is_root = false; - if (auto read_result = read_block_number_at_index(block_number, idx); !read_result) + if (auto read_result = read_block_number_at_index(block_number, idx, state); !read_result) { return kstd::failure(read_result.error()); } @@ -723,118 +928,81 @@ namespace kernel::filesystem::ext2 return kstd::success(); } - auto filesystem::read_block(uint32_t block_number, void * buffer) const -> kstd::result<kstd::bytes> - { - auto const block_offset = static_cast<size_t>(block_number) * block_size(); - return m_backing_inode->read(std::span{static_cast<std::byte *>(buffer), block_size().value}, block_offset); - } - - auto filesystem::write_block(uint32_t block_number, void const * buffer) -> kstd::result<kstd::bytes> - { - auto const block_offset = static_cast<size_t>(block_number) * block_size(); - return m_backing_inode->write(std::span{static_cast<std::byte const *>(buffer), block_size().value}, block_offset); - } - auto filesystem::write_block_group_descriptor(block_group_descriptor const & block_group_descriptor, - size_t block_group_descriptor_index) const -> kstd::result<void> + size_t block_group_descriptor_index, mount_state const & state) const + -> kstd::result<void> { // TODO update all block group descriptors - return m_backing_inode - ->write(as_bytes(std::span{&block_group_descriptor, 1}), - block_group_descriptor_table_offset() + + return state.backing_inode + ->write(kstd::raw_bytes(block_group_descriptor), + block_group_descriptor_table_offset(state) + block_group_descriptor_index * kstd::size_of(block_group_descriptor)) .transform([](auto) {}); } - auto filesystem::write_superblock() const -> kstd::result<void> + auto filesystem::write_superblock(mount_state & state) const -> kstd::result<void> { - // TODO update all superblocks - return m_backing_inode->write(as_bytes(std::span{&m_superblock, 1}), constants::superblock_offset) + // TODO update all superblock copies + return state.backing_inode->write(kstd::raw_bytes(state.superblock), constants::superblock_offset) .transform([](auto) {}); } - auto filesystem::map_inode_mode_to_file_type(uint16_t mode) -> uint8_t + auto filesystem::map_inode_mode_to_file_type(uint16_t mode) -> kapi::filesystem::file_type { switch (mode & constants::mode_mask) { case constants::mode_regular: - return 1; + return kapi::filesystem::file_type::regular; case constants::mode_directory: - return 2; + return kapi::filesystem::file_type::directory; + case constants::mode_block_device: + return kapi::filesystem::file_type::block_device; + case constants::mode_character_device: + return kapi::filesystem::file_type::character_device; case constants::mode_symbolic_link: - return 7; + return kapi::filesystem::file_type::symbolic_link; + case constants::mode_fifo: + return kapi::filesystem::file_type::fifo; + case constants::mode_socket: + return kapi::filesystem::file_type::socket; default: - return 0; + return kapi::filesystem::file_type{0}; } } auto filesystem::map_vfs_inode_type_into_inode_mode(kapi::filesystem::file_type type, uint16_t mode) -> uint16_t { - switch (type) - { - case kapi::filesystem::file_type::directory: - return mode | constants::mode_directory; - case kapi::filesystem::file_type::regular: - return mode | constants::mode_regular; - case kapi::filesystem::file_type::block_device: - return mode | constants::mode_block_device; - case kapi::filesystem::file_type::character_device: - return mode | constants::mode_character_device; - case kapi::filesystem::file_type::symbolic_link: - // return mode | constants::mode_symbolic_link; // TODO implement - default: - kapi::system::panic("[EXT2] Not implemented."); - } - } - - auto filesystem::block_numbers_per_block() const -> size_t - { - return block_size() / kstd::size_of<uint32_t>(); - } - - auto filesystem::block_numbers_per_singly_indirect_block() const -> size_t - { - return block_numbers_per_block(); - } - - auto filesystem::block_numbers_per_doubly_indirect_block() const -> size_t - { - return block_numbers_per_singly_indirect_block() * block_numbers_per_block(); - } - - auto filesystem::block_numbers_per_triply_indirect_block() const -> size_t - { - return block_numbers_per_doubly_indirect_block() * block_numbers_per_block(); - } - - auto filesystem::block_size() const -> kstd::bytes - { - return kstd::bytes{constants::base_block_size.value << m_superblock.log_block_size}; - } - - auto filesystem::revision_level() const -> uint32_t - { - return m_superblock.rev_level; - } - - auto filesystem::inode_size() const -> kstd::bytes - { - return kstd::bytes(revision_level() == constants::good_old_revision ? 128 : m_superblock.inode_size); - } - - auto filesystem::inode_block_count(inode_data const & data) const -> uint32_t - { - return data.blocks / (2 << m_superblock.log_block_size); + return mode | [=] { + switch (type) + { + case kapi::filesystem::file_type::directory: + return constants::mode_directory; + case kapi::filesystem::file_type::regular: + return constants::mode_regular; + case kapi::filesystem::file_type::block_device: + return constants::mode_block_device; + case kapi::filesystem::file_type::character_device: + return constants::mode_character_device; + case kapi::filesystem::file_type::symbolic_link: + return constants::mode_symbolic_link; // TODO implement + case kapi::filesystem::file_type::fifo: + return constants::mode_fifo; + case kapi::filesystem::file_type::socket: + return constants::mode_socket; + default: + kapi::system::panic("[EXT2] Not implemented."); + } + }(); } - auto filesystem::update_inode_block_count(inode_data & data, uint32_t delta) -> void + auto filesystem::update_inode_block_count(inode_data & data, uint32_t delta, mount_state & state) -> void { - data.blocks += delta * (2 << m_superblock.log_block_size); + data.blocks += delta * (2 << state.superblock.log_block_size); } - auto filesystem::block_group_descriptor_table_offset() const -> kstd::bytes + auto filesystem::block_group_descriptor_table_offset(mount_state const & state) const -> kstd::bytes { - return block_size() == 1024_B ? 2 * block_size() : block_size(); + return block_size(state) == 1024_B ? 2 * block_size(state) : block_size(state); } } // namespace kernel::filesystem::ext2 diff --git a/kernel/kernel/filesystem/ext2/filesystem.hpp b/kernel/kernel/filesystem/ext2/filesystem.hpp index d70358fd..6d5720a2 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.hpp +++ b/kernel/kernel/filesystem/ext2/filesystem.hpp @@ -2,8 +2,12 @@ #define TEACHOS_KERNEL_FILESYSTEM_EXT2_FILESYSTEM_HPP #include <kernel/filesystem/ext2/block_group_descriptor.hpp> +#include <kernel/filesystem/ext2/directory_iterator.hpp> #include <kernel/filesystem/ext2/inode.hpp> +#include <kernel/filesystem/ext2/linked_directory_entry.hpp> +#include <kernel/filesystem/ext2/mount_state.hpp> #include <kernel/filesystem/ext2/superblock.hpp> +#include <kernel/filesystem/ext2/write_batch.hpp> #include <kernel/filesystem/filesystem.hpp> #include <kernel/filesystem/inode.hpp> @@ -16,10 +20,10 @@ #include <kstd/units.hpp> #include <kstd/vector.hpp> -#include <array> #include <cstddef> #include <cstdint> #include <optional> +#include <span> #include <string_view> #include <type_traits> @@ -48,6 +52,9 @@ namespace kernel::filesystem::ext2 constexpr uint16_t inline mode_symbolic_link = 0xA000; constexpr uint16_t inline mode_block_device = 0x6000; constexpr uint16_t inline mode_character_device = 0x2000; + constexpr uint16_t inline mode_fifo = 0x1000; + constexpr uint16_t inline mode_socket = 0xC000; + } // namespace constants //! Ext2 incompatibility features @@ -75,75 +82,106 @@ namespace kernel::filesystem::ext2 //! The Second Extended Filesystem (ext2) struct filesystem final : kernel::filesystem::filesystem { - auto mount(kstd::shared_ptr<kernel::filesystem::inode> const & backing_inode) -> kstd::result<void> override; + //! @name Driver Interface Implementation + //! @{ + + [[nodiscard]] auto probe(inode_ptr const & backing_inode) const -> kstd::result<std::uint32_t> override; + + auto mount(inode_ptr const & backing_inode) -> kstd::result<mount_result> override; - [[nodiscard]] auto lookup(kstd::shared_ptr<kernel::filesystem::inode> const & parent, std::string_view name) const - -> kstd::result<kstd::shared_ptr<kernel::filesystem::inode>> override; + [[nodiscard]] auto lookup(inode_ptr const & parent, std::string_view name, driver_data_ptr driver_data) const + -> kstd::result<inode_ptr> override; - [[nodiscard]] auto create_inode(kstd::shared_ptr<kernel::filesystem::inode> const & parent, std::string_view name, - kapi::filesystem::file_type type, + [[nodiscard]] auto create_inode(inode_ptr const & parent, std::string_view name, kapi::filesystem::file_type type, + driver_data_ptr driver_data, std::optional<kapi::filesystem::device_number> raw_device = std::nullopt) - -> kstd::result<kstd::shared_ptr<kernel::filesystem::inode>> override; + -> kstd::result<inode_ptr> override; + + //! @} + + //! @name Filesystem Information Queries + //! @{ //! Get the size of a block in the filesystem. //! + //! @param state The driver state to operate on. //! @return The size of a block in bytes. - [[nodiscard]] auto block_size() const -> kstd::bytes; + [[nodiscard]] auto block_size(mount_state const & state) const -> kstd::bytes; //! Get the revision level of the filesystem. //! + //! @param state The driver state to operate on. //! @return The revision level. - [[nodiscard]] auto revision_level() const -> uint32_t; + [[nodiscard]] auto revision_level(mount_state const & state) const -> uint32_t; + + //! Get the size of an inode in the filesystem. + //! + //! @param state The driver state to operate on. + //! @return The size of an inode in bytes. + [[nodiscard]] auto inode_size(mount_state const & state) const -> kstd::bytes; + + //! Get the number of blocks allocated to an inode. + //! + //! @param inode The inode whose block count to query. + //! @param state The driver state to operate on. + //! @return The number of blocks allocated to the inode. + [[nodiscard]] auto block_count(inode const & inode, mount_state const & state) const -> uint32_t; + + //! @} + + //! @name Block I/O + //! @{ //! Read a block from the backing inode into the provided buffer. //! //! @param block_number The number of the block to read. //! @param buffer The buffer to read the block data into. + //! @param state The state to operate on. //! @return The number of bytes read. - auto read_block(uint32_t block_number, void * buffer) const -> kstd::result<kstd::bytes>; + [[nodiscard]] auto read_block(uint32_t block_number, std::span<std::byte> buffer, mount_state const & state) const + -> kstd::result<kstd::bytes>; //! Write a block of data from the provided buffer to the backing inode. //! //! @param block_number The number of the block to write. //! @param buffer The buffer containing the data to write. + //! @param state The state to operate on. //! @return The number of bytes written. - auto write_block(uint32_t block_number, void const * buffer) -> kstd::result<kstd::bytes>; + [[nodiscard]] auto write_block(uint32_t block_number, std::span<std::byte const> buffer, mount_state & state) + -> kstd::result<kstd::bytes>; + + //! @} //! Allocate a specified number of blocks. //! //! @param count The number of blocks to allocate. + //! @param state The state to operate on. + //! @param batch The active write batch. //! @return A vector of the allocated block numbers. - auto allocate_blocks(size_t count) -> kstd::result<kstd::vector<uint32_t>>; - - //! Get the size of an inode in the filesystem. - //! - //! @return The size of an inode in bytes. - [[nodiscard]] auto inode_size() const -> kstd::bytes; - - //! Get the number of blocks allocated to an inode. - //! - //! @param data The inode data. - //! @return The number of blocks allocated to the inode. - [[nodiscard]] auto inode_block_count(inode_data const & data) const -> uint32_t; + auto allocate_blocks(size_t count, mount_state & state, write_batch & batch) + -> kstd::result<kstd::vector<uint32_t>>; //! Update the number of blocks allocated to an inode. //! //! @param data The inode data. + //! @param state The state to operate on. //! @param delta The change in the number of blocks. - auto update_inode_block_count(inode_data & data, uint32_t delta) -> void; + auto update_inode_block_count(inode_data & data, uint32_t delta, mount_state & state) -> void; //! Write an inode to the backing inode. //! //! @param inode_number The number of the inode to write. + //! @param state The state to operate on. //! @param data The inode data to write. - auto write_inode(uint32_t inode_number, inode_data const & data) -> kstd::result<void>; + auto write_inode(uint32_t inode_number, inode_data const & data, mount_state & state) -> kstd::result<void>; - //! Map an inode block index to a global block number. + //! Get the block number associated with the nth block of an inode. //! - //! @param inode_block_index The index of the block within the inode. - //! @param data The inode data. + //! @param index The index of the block within the inode. + //! @param inode The inode. + //! @param state The state to operate on. //! @return The global block number on success, an error otherwise. - [[nodiscard]] auto map_inode_block_index_to_global_block_number(size_t inode_block_index, inode_data data) const + [[nodiscard]] auto inode_block_number(size_t index, inode const & inode, mount_state const & state) const -> kstd::result<std::size_t>; //! Write a global block number to an inode block index. @@ -151,46 +189,101 @@ namespace kernel::filesystem::ext2 //! @param block_index The index of the block within the inode. //! @param data The inode data. //! @param global_block_number The global block number to write. + //! @param state The state to operate on. + //! @param batch The active write batch. auto write_global_block_number_to_inode_block_index(size_t block_index, inode_data & data, - uint32_t global_block_number) -> kstd::result<void>; + uint32_t global_block_number, mount_state & state, + write_batch & batch) -> kstd::result<void>; private: - struct indirect_level - { - uint32_t slot_index; - size_t capacity; - }; + friend write_batch; + friend inode; + friend directory_iterator; - [[nodiscard]] auto indirect_levels() const -> std::array<indirect_level, 3>; + //! Perform the actual read of the requested data. + //! + //! @param inode The inode whose data to read. + //! @param buffer The buffer to read into. + //! @param offset The offset inside the inode's data to start at. + //! @param state The driver state to operate on. + //! @return The number of bytes read on success, an error otherwise. + [[nodiscard]] auto do_read(inode const & inode, std::span<std::byte> buffer, kstd::bytes offset, + mount_state const & state) const -> kstd::result<kstd::bytes>; + + //! Determine the size of an inode's data on disk. + //! + //! @param inode The inode whose data size to determine. + //! @param state The driver state to operate on. + //! @return The size of the data referenced by the inode. + [[nodiscard]] auto data_size(inode const & inode, mount_state const & state) const -> kstd::bytes; - auto allocate_inode() -> kstd::result<uint32_t>; - auto add_directory_entry(inode * ext2_parent, std::string_view name, uint32_t child_inode_number, uint16_t mode) - -> kstd::result<void>; - auto init_directory(kstd::shared_ptr<kernel::filesystem::ext2::inode> const & new_inode, - kernel::filesystem::ext2::inode * parent_inode) -> kstd::result<void>; - auto write_directory_entry_to_buffer(uint8_t * block_buffer, kstd::bytes offset, uint32_t child_inode_number, - std::string_view name, uint8_t file_type) -> void; + //! Set the size of an inode's data on disk. + //! + //! @param inode The inode whose data size to set. + //! @param new_size The size to set. + //! @param state The driver state to operate on. + auto set_data_size(inode & inode, kstd::bytes new_size, mount_state const & state) const -> void; - [[nodiscard]] auto calculate_inode_offset(uint32_t inode_number) const -> kstd::result<kstd::bytes>; - [[nodiscard]] auto read_inode(uint32_t inode_number) const -> kstd::result<kstd::shared_ptr<inode>>; - [[nodiscard]] auto read_block_number_at_index(uint32_t block_number, size_t index) const -> kstd::result<uint32_t>; + //! Allocate a single inode in the file system. + //! + //! @param state The driver state to operate on. + //! @param batch The active write batch. + //! @return An inode number on success, an error otherwise. + auto allocate_inode(mount_state & state, write_batch & batch) -> kstd::result<uint32_t>; - [[nodiscard]] auto block_group_descriptor_table_offset() const -> kstd::bytes; + //! Add a new directory entry to a given directory. + //! + //! @param directory The directory to add the entry to. + //! @param name The name for the entry. + //! @param child The inode number of the child. + //! @param mode The file mode of the entry. + //! @param state The driver state to operate on. + //! @param batch The active write batch. + //! @return Nothing on success, an error code otherwise. + auto add_directory_entry(inode & directory, std::string_view name, inode & child, std::uint16_t mode, + mount_state & state, write_batch & batch) -> kstd::result<void>; + + //! Initialize a new directory inode. + //! + //! This function initializes an inode to be an empty directory. On success, the inode will be a directory with only + //! two children, "." and "..". + //! + //! @param directory The inode to initialize. + //! @param parent The parent of this newly initialized directory. + //! @param state The driver state to operate on. + //! @param batch The active write batch. + //! @return Nothing on success, an error otherwise. + auto init_directory(inode & directory, inode & parent, mount_state & state, write_batch & batch) + -> kstd::result<void>; - [[nodiscard]] auto block_numbers_per_block() const -> size_t; - [[nodiscard]] auto block_numbers_per_singly_indirect_block() const -> size_t; - [[nodiscard]] auto block_numbers_per_doubly_indirect_block() const -> size_t; - [[nodiscard]] auto block_numbers_per_triply_indirect_block() const -> size_t; + //! Write a new directory entry into the given buffer. + //! + //! @param buffer The buffer to write to. + //! @param offset The offset inside to the buffer. + //! @param target The inode the entry targets. + //! @param name The name for the entry. + //! @param type The filet type of the entry. + //! @return A pointer to the written entry on success, an error otherwise. + auto write_directory_entry_to_buffer(std::span<std::byte> buffer, kstd::bytes offset, inode & target, + std::string_view name, kapi::filesystem::file_type type) + -> kstd::result<linked_directory_entry *>; + + [[nodiscard]] auto calculate_inode_offset(uint32_t inode_number, mount_state const & state) const + -> kstd::result<kstd::bytes>; + [[nodiscard]] auto read_inode(uint32_t inode_number, mount_state const & state) const + -> kstd::result<kstd::shared_ptr<inode>>; + [[nodiscard]] auto read_block_number_at_index(uint32_t block_number, size_t index, mount_state const & state) const + -> kstd::result<uint32_t>; + + [[nodiscard]] auto block_group_descriptor_table_offset(mount_state const & state) const -> kstd::bytes; [[nodiscard]] auto write_block_group_descriptor(block_group_descriptor const & block_group_descriptor, - size_t block_group_descriptor_index) const -> kstd::result<void>; - [[nodiscard]] auto write_superblock() const -> kstd::result<void>; + size_t block_group_descriptor_index, + mount_state const & state) const -> kstd::result<void>; + [[nodiscard]] auto write_superblock(mount_state & state) const -> kstd::result<void>; - auto map_inode_mode_to_file_type(uint16_t mode) -> uint8_t; + auto map_inode_mode_to_file_type(uint16_t mode) -> kapi::filesystem::file_type; auto map_vfs_inode_type_into_inode_mode(kapi::filesystem::file_type type, uint16_t mode) -> uint16_t; - - superblock m_superblock{}; - kstd::vector<block_group_descriptor> m_block_group_descriptors; }; } // namespace kernel::filesystem::ext2 diff --git a/kernel/kernel/filesystem/ext2/filesystem.tests.cpp b/kernel/kernel/filesystem/ext2/filesystem.tests.cpp index 51ba2f30..28aabed7 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.tests.cpp +++ b/kernel/kernel/filesystem/ext2/filesystem.tests.cpp @@ -5,8 +5,12 @@ #include <kernel/filesystem/ext2/block_group_descriptor.hpp> #include <kernel/filesystem/ext2/error.hpp> #include <kernel/filesystem/ext2/inode.hpp> +#include <kernel/filesystem/ext2/mount_state.hpp> #include <kernel/filesystem/ext2/superblock.hpp> +#include <kernel/filesystem/ext2/write_batch.hpp> #include <kernel/filesystem/filesystem.hpp> +#include <kernel/filesystem/inode.hpp> +#include <kernel/filesystem/mount.hpp> #include <kernel/test_support/cpu.hpp> #include <kernel/test_support/devices/block_device.hpp> #include <kernel/test_support/filesystem/ext2.hpp> @@ -47,37 +51,44 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, auto dev_inode = kstd::make_shared<kernel::filesystem::device_inode>(boot_device); - auto fs = kernel::filesystem::ext2::filesystem{}; - REQUIRE(fs.mount(dev_inode)); + auto fs = kstd::make_shared<kernel::filesystem::ext2::filesystem>(); + auto mount = kernel::filesystem::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); + auto root = (*mount)->root_dentry()->inode(); + auto driver_data = (*mount)->driver_data(); + REQUIRE(mount); THEN("the root inode is available and is a directory") { - REQUIRE(fs.root_inode()); - REQUIRE(fs.root_inode()->is_directory()); + REQUIRE(root); + REQUIRE((root)->is_directory()); } THEN("lookup resolves known entries from the image") { - auto information = fs.lookup(fs.root_inode(), "information"); + 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"); + 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") { - REQUIRE(!fs.lookup(nullptr, "information")); + REQUIRE(!fs->lookup(nullptr, "information", driver_data)); - auto information = fs.lookup(fs.root_inode(), "information"); + auto information = fs->lookup(root, "information", driver_data); REQUIRE(information); - auto info_1 = fs.lookup(*information, "info_1.txt"); + (*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")); - REQUIRE(!fs.lookup(fs.root_inode(), "does_not_exist")); + REQUIRE(!fs->lookup(*info_1, "anything", driver_data)); + REQUIRE(!fs->lookup(root, "does_not_exist", driver_data)); } } } @@ -97,51 +108,59 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, auto dev_inode = kstd::make_shared<kernel::filesystem::device_inode>(boot_device); - auto fs = kernel::filesystem::ext2::filesystem{}; - REQUIRE(fs.mount(dev_inode)); + auto fs = kstd::make_shared<kernel::filesystem::ext2::filesystem>(); + auto mount = kernel::filesystem::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); + auto root = (*mount)->root_dentry()->inode(); + auto driver_data = (*mount)->driver_data(); + REQUIRE(mount); THEN("a file can be created") { - auto lookup_result = fs.lookup(fs.root_inode(), "blub"); + auto lookup_result = fs->lookup(root, "blub", driver_data); REQUIRE(!lookup_result); - auto new_inode = fs.create_inode(fs.root_inode(), "blub", kapi::filesystem::file_type::regular); + 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(fs.root_inode(), "blub"); + lookup_result = fs->lookup(root, "blub", driver_data); REQUIRE(lookup_result); + (*lookup_result)->set_owning_mount(*mount); } THEN("a directory can be created") { - auto lookup_result = fs.lookup(fs.root_inode(), "blub"); + auto lookup_result = fs->lookup(root, "blub", driver_data); REQUIRE(!lookup_result); - auto new_directory = fs.create_inode(fs.root_inode(), "blub", kapi::filesystem::file_type::directory); + auto new_directory = fs->create_inode(root, "blub", kapi::filesystem::file_type::directory, driver_data); REQUIRE(new_directory); REQUIRE(new_directory.value()->is_directory()); - lookup_result = fs.lookup(fs.root_inode(), "blub"); + lookup_result = fs->lookup(root, "blub", driver_data); REQUIRE(lookup_result); } THEN("a directory and a file within can be created") { - auto new_directory = fs.create_inode(fs.root_inode(), "blub", kapi::filesystem::file_type::directory); + 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); + 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"); + auto lookup_result = fs->lookup(new_directory.value(), "blub_file", driver_data); REQUIRE(lookup_result); REQUIRE(lookup_result.value()->is_regular()); } THEN("a file can be created and written to") { - auto new_inode = fs.create_inode(fs.root_inode(), "blub", kapi::filesystem::file_type::regular); + auto new_inode = fs->create_inode(root, "blub", kapi::filesystem::file_type::regular, driver_data); REQUIRE(new_inode); auto buffer = @@ -157,12 +176,6 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, std::string_view result{reinterpret_cast<char *>(read_buffer.data()), static_cast<size_t>(*bytes_read)}; REQUIRE(result == "Hello"); } - - THEN("a symbolic link is not implemented yet") - { - REQUIRE_THROWS_AS(fs.create_inode(fs.root_inode(), "blub", kapi::filesystem::file_type::symbolic_link), - kernel::tests::cpu::halt); - } } } @@ -176,11 +189,11 @@ SCENARIO("Ext2 filesystem rejects invalid magic", "[filesystem][ext2][filesystem auto dev_inode = kstd::make_shared<kernel::filesystem::device_inode>(device); - auto fs = kernel::filesystem::ext2::filesystem{}; + auto fs = kstd::make_shared<kernel::filesystem::ext2::filesystem>(); THEN("mount fails with invalid_magic_number") { - REQUIRE(fs.mount(dev_inode).error() == kernel::filesystem::ext2::ext2_errc::invalid_magic_number); + REQUIRE(fs->mount(dev_inode).error() == kernel::filesystem::ext2::ext2_errc::invalid_magic_number); } } } @@ -198,8 +211,11 @@ SCENARIO("Ext2 block mapping includes direct and all indirect levels", "[filesys auto dev_inode = kstd::make_shared<kernel::filesystem::device_inode>(device); - auto fs = kernel::filesystem::ext2::filesystem{}; - REQUIRE(fs.mount(dev_inode)); + auto fs = kstd::make_shared<kernel::filesystem::ext2::filesystem>(); + auto mount = fs->mount(dev_inode); + REQUIRE(mount); + auto mount_state = static_pointer_cast<kernel::filesystem::ext2::mount_state>(mount->second); + REQUIRE(mount_state); auto inode_data = kernel::filesystem::ext2::inode_data{}; inode_data.block[0] = 7; @@ -221,18 +237,20 @@ SCENARIO("Ext2 block mapping includes direct and all indirect levels", "[filesys auto const doubly_start = singly_start + numbers_per_block; auto const triply_start = doubly_start + numbers_per_block * numbers_per_block; + auto inode = kernel::filesystem::ext2::inode{42, inode_data}; + THEN("mapping resolves direct, singly, doubly and triply indirect indexes") { - REQUIRE(fs.map_inode_block_index_to_global_block_number(0, inode_data) == 7); - REQUIRE(fs.map_inode_block_index_to_global_block_number(singly_start, inode_data) == 31); - REQUIRE(fs.map_inode_block_index_to_global_block_number(doubly_start, inode_data) == 42); - REQUIRE(fs.map_inode_block_index_to_global_block_number(triply_start, inode_data) == 53); + REQUIRE(fs->inode_block_number(0, inode, *mount_state) == 7); + REQUIRE(fs->inode_block_number(singly_start, inode, *mount_state) == 31); + REQUIRE(fs->inode_block_number(doubly_start, inode, *mount_state) == 42); + REQUIRE(fs->inode_block_number(triply_start, inode, *mount_state) == 53); } THEN("mapping returns error for out-of-range indexes") { auto const beyond_triply = triply_start + numbers_per_block * numbers_per_block * numbers_per_block; - REQUIRE(!fs.map_inode_block_index_to_global_block_number(beyond_triply, inode_data)); + REQUIRE(!fs->inode_block_number(beyond_triply, inode, *mount_state)); } } } @@ -270,8 +288,11 @@ SCENARIO("Ext2 block writing includes direct and all indirect levels", "[filesys kernel::tests::filesystem::ext2::setup_mock_ext2_layout(*device, superblock, block_group_descriptor); - auto fs = kernel::filesystem::ext2::filesystem{}; - REQUIRE(fs.mount(dev_inode)); + auto fs = kstd::make_shared<kernel::filesystem::ext2::filesystem>(); + auto mount = fs->mount(dev_inode); + REQUIRE(mount); + auto mount_state = static_pointer_cast<kernel::filesystem::ext2::mount_state>(mount->second); + REQUIRE(mount_state); auto read_u32 = [&](kstd::bytes offset) -> uint32_t { auto value = uint32_t{0}; @@ -293,26 +314,32 @@ SCENARIO("Ext2 block writing includes direct and all indirect levels", "[filesys constexpr auto doubly_global_block_number = 303; constexpr auto triply_global_block_number = 404; - REQUIRE(fs.write_global_block_number_to_inode_block_index(0, inode_data, direct_global_block_number)); + auto batch = kernel::filesystem::ext2::write_batch{*fs, *mount_state}; + + REQUIRE(fs->write_global_block_number_to_inode_block_index(0, inode_data, direct_global_block_number, + *mount_state, batch)); REQUIRE(inode_data.block[0] == direct_global_block_number); - REQUIRE(fs.write_global_block_number_to_inode_block_index(singly_start, inode_data, singly_global_block_number)); + REQUIRE(fs->write_global_block_number_to_inode_block_index(singly_start, inode_data, singly_global_block_number, + *mount_state, batch)); REQUIRE(inode_data.block[12] == 16); REQUIRE(read_u32(static_cast<kstd::bytes>(inode_data.block[12]) * block_size.value) == singly_global_block_number); - REQUIRE(fs.write_global_block_number_to_inode_block_index(singly_start + 1, inode_data, - singly_global_block_number + 1)); + REQUIRE(fs->write_global_block_number_to_inode_block_index(singly_start + 1, inode_data, + singly_global_block_number + 1, *mount_state, batch)); REQUIRE(read_u32(static_cast<kstd::bytes>(inode_data.block[12]) * block_size.value + 1 * kstd::size_of<uint32_t>()) == singly_global_block_number + 1); - REQUIRE(fs.write_global_block_number_to_inode_block_index(doubly_start, inode_data, doubly_global_block_number)); + REQUIRE(fs->write_global_block_number_to_inode_block_index(doubly_start, inode_data, doubly_global_block_number, + *mount_state, batch)); REQUIRE(inode_data.block[13] == 17); auto const doubly_leaf_table = read_u32(static_cast<size_t>(inode_data.block[13]) * block_size); REQUIRE(doubly_leaf_table == 18); REQUIRE(read_u32(static_cast<size_t>(doubly_leaf_table) * block_size) == doubly_global_block_number); - REQUIRE(fs.write_global_block_number_to_inode_block_index(triply_start, inode_data, triply_global_block_number)); + REQUIRE(fs->write_global_block_number_to_inode_block_index(triply_start, inode_data, triply_global_block_number, + *mount_state, batch)); REQUIRE(inode_data.block[14] == 19); auto const triply_middle_table = read_u32(static_cast<size_t>(inode_data.block[14]) * block_size); REQUIRE(triply_middle_table == 20); @@ -320,10 +347,12 @@ SCENARIO("Ext2 block writing includes direct and all indirect levels", "[filesys REQUIRE(triply_leaf_table == 21); REQUIRE(read_u32(static_cast<size_t>(triply_leaf_table) * block_size) == triply_global_block_number); - REQUIRE(fs.map_inode_block_index_to_global_block_number(0, inode_data) == direct_global_block_number); - REQUIRE(fs.map_inode_block_index_to_global_block_number(singly_start, inode_data) == singly_global_block_number); - REQUIRE(fs.map_inode_block_index_to_global_block_number(doubly_start, inode_data) == doubly_global_block_number); - REQUIRE(fs.map_inode_block_index_to_global_block_number(triply_start, inode_data) == triply_global_block_number); + auto inode = kernel::filesystem::ext2::inode{42, inode_data}; + + REQUIRE(fs->inode_block_number(0, inode, *mount_state) == direct_global_block_number); + REQUIRE(fs->inode_block_number(singly_start, inode, *mount_state) == singly_global_block_number); + REQUIRE(fs->inode_block_number(doubly_start, inode, *mount_state) == doubly_global_block_number); + REQUIRE(fs->inode_block_number(triply_start, inode, *mount_state) == triply_global_block_number); } } } diff --git a/kernel/kernel/filesystem/ext2/inode.cpp b/kernel/kernel/filesystem/ext2/inode.cpp index 8da80191..075d6c5f 100644 --- a/kernel/kernel/filesystem/ext2/inode.cpp +++ b/kernel/kernel/filesystem/ext2/inode.cpp @@ -1,12 +1,19 @@ #include <kernel/filesystem/ext2/inode.hpp> +#include <kernel/filesystem/error.hpp> +#include <kernel/filesystem/ext2/error.hpp> #include <kernel/filesystem/ext2/filesystem.hpp> +#include <kernel/filesystem/ext2/mount_state.hpp> +#include <kernel/filesystem/ext2/write_batch.hpp> #include <kernel/filesystem/inode.hpp> +#include <kernel/filesystem/mount.hpp> #include <kapi/filesystem.hpp> #include <kapi/system.hpp> #include <kstd/cstring.hpp> +#include <kstd/memory.hpp> +#include <kstd/mutex.hpp> #include <kstd/result.hpp> #include <kstd/units.hpp> @@ -20,67 +27,42 @@ using namespace kstd::units_literals; namespace kernel::filesystem::ext2 { - inode::inode(filesystem * fs, uint32_t inode_number, inode_data const & data) - : m_filesystem(fs) - , m_inode_number(inode_number) + inode::inode(uint32_t inode_number, inode_data const & data) + : m_inode_number(inode_number) , m_data(data) - { - if (!m_filesystem) - { - kapi::system::panic("[EXT2] ext2::inode constructed with filesystem null pointer"); - } - } + {} auto inode::read(std::span<std::byte> buffer, kstd::bytes offset) const -> kstd::result<kstd::bytes> { - auto const max_readable = this->size() - offset; - auto const requested_size = std::min(kstd::bytes{buffer.size()}, max_readable); - if (is_symbolic_link() && this->size() <= kstd::bytes{sizeof(m_data.block)}) { + auto const max_readable = this->size() - offset; + auto const requested_size = std::min(kstd::bytes{buffer.size()}, max_readable); auto inline_target = reinterpret_cast<uint8_t const *>(m_data.block.data()); kstd::libc::memcpy(buffer.data(), inline_target + offset, requested_size.value); return requested_size; } - auto block_index = offset / m_filesystem->block_size(); - auto in_block_offset = offset % m_filesystem->block_size(); - - auto bytes_read = 0_B; - - while (bytes_read < requested_size) + auto filesystem = get_filesystem(); + if (!filesystem) { - auto const block_number = m_filesystem->map_inode_block_index_to_global_block_number(block_index, m_data); - if (!block_number) - { - break; - } + return kstd::failure(filesystem.error()); + } - auto const bytes_to_read = std::min(requested_size - bytes_read, m_filesystem->block_size() - in_block_offset); - if (block_number == 0) - { - kstd::libc::memset(buffer.data() + bytes_read, 0, bytes_to_read.value); - bytes_read += bytes_to_read; - } - else - { - auto const block_start_offset = block_number.value() * m_filesystem->block_size(); - auto const read_offset = block_start_offset + in_block_offset; - auto const read_result = - m_filesystem->backing_inode()->read(buffer.subspan(bytes_read.value, bytes_to_read.value), read_offset); - if (!read_result) - { - return kstd::failure(read_result.error()); - } - - bytes_read += read_result.value(); - } + auto state = get_driver_data(); + if (!state) + { + return kstd::failure(state.error()); + } - block_index++; - in_block_offset = 0_B; // After the first block, we always start at the beginning of the block + auto backing_inode = (*state)->backing_inode; + if (!(*state)->backing_inode) + { + return kstd::failure(vfs_errc::not_mounted); } - return bytes_read; + auto guard = kstd::lock_guard{(*state)->lock}; + return (*filesystem)->do_read(*this, buffer, offset, **state); } auto inode::write(std::span<std::byte const> buffer, kstd::bytes offset) -> kstd::result<kstd::bytes> @@ -93,46 +75,65 @@ namespace kernel::filesystem::ext2 // TODO check maximum file size of filesystem // TODO handle sparse files - auto const new_inode_size = std::max(this->size(), offset + kstd::bytes{buffer.size()}); + auto filesystem = get_filesystem(); + if (!filesystem) + { + return kstd::failure(filesystem.error()); + } + + auto state = get_driver_data(); + if (!state) + { + return kstd::failure(state.error()); + } + + auto backing_inode = (*state)->backing_inode; + if (!(*state)->backing_inode) + { + return kstd::failure(vfs_errc::not_mounted); + } + + auto guard = kstd::lock_guard{(*state)->lock}; + auto batch = write_batch{**filesystem, **state}; + + auto const new_inode_size = std::max((*filesystem)->data_size(*this, **state), offset + kstd::bytes{buffer.size()}); - auto const current_block_count = m_filesystem->inode_block_count(m_data); - auto const max_new_inode_size_without_new_blocks = current_block_count * m_filesystem->block_size(); + auto block_size = (*filesystem)->block_size(**state); + auto const current_block_count = (*filesystem)->block_count(*this, **state); + auto const max_new_inode_size_without_new_blocks = current_block_count * block_size; if (new_inode_size > max_new_inode_size_without_new_blocks) { auto const additional_blocks_needed = - (new_inode_size - max_new_inode_size_without_new_blocks + m_filesystem->block_size() - 1_B) / - m_filesystem->block_size(); + (new_inode_size - max_new_inode_size_without_new_blocks + block_size - 1_B) / block_size; - if (!append_blocks(additional_blocks_needed)) + if (!append_blocks(additional_blocks_needed, batch)) { - // TODO handle out of space - kapi::system::panic("[EXT2] Failed to allocate blocks for inode write"); + return kstd::failure(ext2_errc::not_enough_free_blocks); } } - auto block_index = offset / m_filesystem->block_size(); - auto in_block_offset = offset % m_filesystem->block_size(); + auto block_index = offset / block_size; + auto in_block_offset = offset % block_size; auto bytes_written = 0_B; while (bytes_written < kstd::bytes{buffer.size()}) { - auto const block_number = m_filesystem->map_inode_block_index_to_global_block_number(block_index, m_data); + auto const block_number = (*filesystem)->inode_block_number(block_index, *this, **state); // TODO BA-FS26 if blocknumber == 0 --> handle sparse file if (!block_number) { return kstd::failure(block_number.error()); } - auto const bytes_to_write = - std::min(kstd::bytes{buffer.size()} - bytes_written, m_filesystem->block_size() - in_block_offset); + auto const bytes_to_write = std::min(kstd::bytes{buffer.size()} - bytes_written, block_size - in_block_offset); - auto const block_start_offset = block_number.value() * m_filesystem->block_size(); + auto const block_start_offset = block_number.value() * block_size; auto const write_offset = block_start_offset + in_block_offset; auto const write_result = - m_filesystem->backing_inode()->write(buffer.subspan(bytes_written.value, bytes_to_write.value), write_offset); + backing_inode->write(buffer.subspan(bytes_written.value, bytes_to_write.value), write_offset); if (!write_result) { return kstd::failure(write_result.error()); @@ -144,48 +145,25 @@ namespace kernel::filesystem::ext2 in_block_offset = 0_B; } - set_size(std::max(this->size(), offset + bytes_written)); - if (auto write_result = m_filesystem->write_inode(m_inode_number, m_data); !write_result) - { - return kstd::failure(write_result.error()); - } - + (*filesystem) + ->set_data_size(*this, std::max((*filesystem)->data_size(*this, **state), offset + bytes_written), **state); + batch.mark_inode(*this); return bytes_written; } - auto inode::append_blocks(size_t count) -> bool + auto inode::is_block_device() const -> bool { - auto new_blocks = m_filesystem->allocate_blocks(count); - if (!new_blocks) - { - return false; - } - - for (auto i = 0uz; i < new_blocks->size(); ++i) - { - auto const block_index = m_filesystem->inode_block_count(m_data) + i; - auto const global_block_number = new_blocks->at(i); - - if (auto write_result = - m_filesystem->write_global_block_number_to_inode_block_index(block_index, m_data, global_block_number); - !write_result) - { - return false; - } - } - - m_filesystem->update_inode_block_count(m_data, count); - return true; + return (m_data.mode & constants::mode_mask) == constants::mode_block_device; } - [[nodiscard]] auto inode::data() const -> inode_data const & + auto inode::is_character_device() const -> bool { - return m_data; + return (m_data.mode & constants::mode_mask) == constants::mode_character_device; } - [[nodiscard]] auto inode::data_mutable() -> inode_data & + auto inode::is_directory() const -> bool { - return m_data; + return (m_data.mode & constants::mode_mask) == constants::mode_directory; } auto inode::is_regular() const -> bool @@ -193,24 +171,24 @@ namespace kernel::filesystem::ext2 return (m_data.mode & constants::mode_mask) == constants::mode_regular; } - auto inode::is_directory() const -> bool - { - return (m_data.mode & constants::mode_mask) == constants::mode_directory; - } - auto inode::is_symbolic_link() const -> bool { return (m_data.mode & constants::mode_mask) == constants::mode_symbolic_link; } - auto inode::is_block_device() const -> bool + auto inode::raw_device() const -> std::optional<kapi::filesystem::device_number> { - return (m_data.mode & constants::mode_mask) == constants::mode_block_device; - } + if (!is_device()) + { + return std::nullopt; + } - auto inode::is_character_device() const -> bool - { - return (m_data.mode & constants::mode_mask) == constants::mode_character_device; + auto const raw_device_number = m_data.block[0]; + + return kapi::filesystem::device_number{ + .major = static_cast<std::uint8_t>((raw_device_number >> 8) & 0xff), + .minor = static_cast<std::uint8_t>(raw_device_number & 0xff), + }; } auto inode::status() const -> kstd::result<kapi::filesystem::file_status> @@ -229,40 +207,89 @@ namespace kernel::filesystem::ext2 return result; } - auto inode::raw_device() const -> std::optional<kapi::filesystem::device_number> + auto inode::append_blocks(size_t count, write_batch & batch) -> bool { - if (!is_device()) + auto filesystem = get_filesystem(); + if (!filesystem) { - return std::nullopt; + return false; } - auto const raw_device_number = m_data.block[0]; + auto state = get_driver_data(); + if (!state) + { + return false; + } - return kapi::filesystem::device_number{ - .major = static_cast<std::uint8_t>((raw_device_number >> 8) & 0xff), - .minor = static_cast<std::uint8_t>(raw_device_number & 0xff), - }; + auto new_blocks = (*filesystem)->allocate_blocks(count, **state, batch); + if (!new_blocks) + { + return false; + } + + for (auto i = 0uz; i < new_blocks->size(); ++i) + { + auto const block_index = (*filesystem)->block_count(*this, **state) + i; + auto const global_block_number = new_blocks->at(i); + + if (auto write_result = (*filesystem) + ->write_global_block_number_to_inode_block_index(block_index, m_data, + global_block_number, **state, batch); + !write_result) + { + return false; + } + } + + (*filesystem)->update_inode_block_count(m_data, count, **state); + return true; } - auto inode::size() const -> kstd::bytes + auto inode::set_size(kstd::bytes new_size) -> void { - uint64_t size = m_data.size; + auto filesystem = get_filesystem(); + if (!filesystem) + { + return; + } - if (m_filesystem->revision_level() > constants::good_old_revision && is_regular()) + auto driver_data = get_driver_data(); + if (!driver_data) { - size |= static_cast<uint64_t>(m_data.dir_acl) << 32; + return; } - return kstd::bytes{size}; + auto state = static_pointer_cast<mount_state>(*driver_data); + auto guard = kstd::lock_guard{state->lock}; + return (*filesystem)->set_data_size(*this, new_size, *state); } - auto inode::set_size(kstd::bytes new_size) -> void + [[nodiscard]] auto inode::data() -> inode_data & { - if (m_filesystem->revision_level() > constants::good_old_revision && is_regular()) + return m_data; + } + + [[nodiscard]] auto inode::data() const -> inode_data const & + { + return m_data; + } + + auto inode::size() const -> kstd::bytes + { + auto filesystem = get_filesystem(); + if (!filesystem) { - m_data.dir_acl = static_cast<uint32_t>(new_size.value >> 32); + return 0_B; } - m_data.size = static_cast<uint32_t>(new_size.value); + + auto state = get_driver_data(); + if (!state) + { + return 0_B; + } + + auto guard = kstd::lock_guard{(*state)->lock}; + return (*filesystem)->data_size(*this, **state); } [[nodiscard]] auto inode::number() const -> uint32_t @@ -270,4 +297,44 @@ namespace kernel::filesystem::ext2 return m_inode_number; } + auto inode::get_filesystem() -> kstd::result<kstd::shared_ptr<filesystem>> + { + if (auto mount = owning_mount().lock()) + { + return static_pointer_cast<filesystem>(mount->filesystem()); + } + + return kstd::failure(vfs_errc::not_mounted); + } + + auto inode::get_filesystem() const -> kstd::result<kstd::shared_ptr<filesystem const>> + { + if (auto mount = owning_mount().lock()) + { + return static_pointer_cast<filesystem>(mount->filesystem()); + } + + return kstd::failure(vfs_errc::not_mounted); + } + + auto inode::get_driver_data() -> kstd::result<kstd::shared_ptr<mount_state>> + { + if (auto mount = owning_mount().lock()) + { + return static_pointer_cast<mount_state>(mount->driver_data()); + } + + return kstd::failure(vfs_errc::not_mounted); + } + + auto inode::get_driver_data() const -> kstd::result<kstd::shared_ptr<mount_state const>> + { + if (auto mount = owning_mount().lock()) + { + return static_pointer_cast<mount_state const>(mount->driver_data()); + } + + return kstd::failure(vfs_errc::not_mounted); + } + } // namespace kernel::filesystem::ext2 diff --git a/kernel/kernel/filesystem/ext2/inode.hpp b/kernel/kernel/filesystem/ext2/inode.hpp index 61961f6c..5393a853 100644 --- a/kernel/kernel/filesystem/ext2/inode.hpp +++ b/kernel/kernel/filesystem/ext2/inode.hpp @@ -1,6 +1,8 @@ #ifndef TEACHOS_KERNEL_FILESYSTEM_EXT2_INODE_HPP #define TEACHOS_KERNEL_FILESYSTEM_EXT2_INODE_HPP +#include <kernel/filesystem/ext2/mount_state.hpp> +#include <kernel/filesystem/ext2/write_batch.hpp> #include <kernel/filesystem/inode.hpp> #include <kapi/filesystem.hpp> @@ -44,33 +46,33 @@ namespace kernel::filesystem::ext2 struct inode : kernel::filesystem::inode { + //! @name Construction/Destruction + //! @{ + //! Create an ext2 inode associated with the given filesystem. //! - //! @param fs The ext2 filesystem that this inode belongs to. //! @param inode_number The inode number on disk. //! @param data The data associated with this inode, read from the disk. - explicit inode(filesystem * fs, uint32_t inode_number, inode_data const & data); + explicit inode(uint32_t inode_number, inode_data const & data); + + //! @} + + //! @name I/O Operations + //! @{ [[nodiscard]] auto read(std::span<std::byte> buffer, kstd::bytes offset) const -> kstd::result<kstd::bytes> override; auto write(std::span<std::byte const> buffer, kstd::bytes offset) -> kstd::result<kstd::bytes> override; - //! Append the specified number of blocks to this inode. - //! - //! @param count The number of blocks to append. - //! @return true if the blocks were successfully appended, false otherwise. - auto append_blocks(size_t count) -> bool; + //! @} - //! Get the data associated with this inode. - //! - //! @return A const reference to the inode data. - [[nodiscard]] auto data() const -> inode_data const &; + //! @name Property Checking + //! @{ - //! Get the data associated with this inode. - //! - //! @return A reference to the inode data. - [[nodiscard]] auto data_mutable() -> inode_data &; + [[nodiscard]] auto is_block_device() const -> bool override; + + [[nodiscard]] auto is_character_device() const -> bool override; [[nodiscard]] auto is_directory() const -> bool override; @@ -78,32 +80,80 @@ namespace kernel::filesystem::ext2 [[nodiscard]] auto is_symbolic_link() const -> bool override; - [[nodiscard]] auto is_block_device() const -> bool override; + //! @} - [[nodiscard]] auto is_character_device() const -> bool override; + //! @name POSIX Information Access + //! @{ + + [[nodiscard]] auto raw_device() const -> std::optional<kapi::filesystem::device_number> override; [[nodiscard]] auto status() const -> kstd::result<kapi::filesystem::file_status> override; - [[nodiscard]] auto raw_device() const -> std::optional<kapi::filesystem::device_number> override; + //! @} - //! Get the size of the file represented by this inode. + //! @name Property Manipulation + //! @{ + + //! Append the specified number of blocks to this inode. //! - //! @return The size of the file in bytes. - [[nodiscard]] auto size() const -> kstd::bytes; + //! @param count The number of blocks to append. + //! @param batch The active write batch. + //! @return true if the blocks were successfully appended, false otherwise. + auto append_blocks(size_t count, write_batch & batch) -> bool; //! Set the size of the file represented by this inode. //! //! @param new_size The new size of the file in bytes. auto set_size(kstd::bytes new_size) -> void; + //! @} + + //! @name Ext2 Information Queries + //! @{ + + //! Get the data associated with this inode. + //! + //! @return A reference to the inode data. + [[nodiscard]] auto data() -> inode_data &; + + //! Get the data associated with this inode. + //! + //! @return A const reference to the inode data. + [[nodiscard]] auto data() const -> inode_data const &; + + //! Get the size of the file represented by this inode. + //! + //! @return The size of the file in bytes. + [[nodiscard]] auto size() const -> kstd::bytes; + //! @brief Get the inode number of this inode. //! //! @return The inode number. [[nodiscard]] auto number() const -> uint32_t; + //! @} + private: - //! The filesystem this inode belongs to. - filesystem * m_filesystem; + //! Get the filesystem driver associated with this inode. + //! + //! @return The filesystem driver on success, and error otherwise. + [[nodiscard]] auto get_filesystem() -> kstd::result<kstd::shared_ptr<filesystem>>; + + //! Get the filesystem driver associated with this inode. + //! + //! @return The filesystem driver on success, and error otherwise. + [[nodiscard]] auto get_filesystem() const -> kstd::result<kstd::shared_ptr<filesystem const>>; + + //! Get the filesystem driver data associated with this inode. + //! + //! @return The filesystem driver data on success, and error otherwise. + [[nodiscard]] auto get_driver_data() -> kstd::result<kstd::shared_ptr<mount_state>>; + + //! Get the filesystem driver data associated with this inode. + //! + //! @return The filesystem driver data on success, and error otherwise. + [[nodiscard]] auto get_driver_data() const -> kstd::result<kstd::shared_ptr<mount_state const>>; + //! The inode number on disk. uint32_t m_inode_number{}; //! The inode data. diff --git a/kernel/kernel/filesystem/ext2/inode.tests.cpp b/kernel/kernel/filesystem/ext2/inode.tests.cpp index 13e9d414..eab0aabd 100644 --- a/kernel/kernel/filesystem/ext2/inode.tests.cpp +++ b/kernel/kernel/filesystem/ext2/inode.tests.cpp @@ -4,8 +4,10 @@ #include <kernel/filesystem/device_inode.hpp> #include <kernel/filesystem/ext2/block_group_descriptor.hpp> #include <kernel/filesystem/ext2/filesystem.hpp> +#include <kernel/filesystem/ext2/mount_state.hpp> #include <kernel/filesystem/ext2/superblock.hpp> #include <kernel/filesystem/filesystem.hpp> +#include <kernel/filesystem/mount.hpp> #include <kernel/test_support/cpu.hpp> #include <kernel/test_support/devices/block_device.hpp> #include <kernel/test_support/filesystem/ext2.hpp> @@ -14,6 +16,7 @@ #include <kapi/filesystem.hpp> #include <kstd/memory.hpp> +#include <kstd/span.hpp> #include <kstd/units.hpp> #include <kstd/vector.hpp> @@ -42,7 +45,7 @@ SCENARIO("Ext2 inode initialization and properties", "[filesystem][ext2][inode]" THEN("the inode is initialized with regular file mode in data and has the kind regular") { data.mode = kernel::filesystem::ext2::constants::mode_regular; - auto inode = kernel::filesystem::ext2::inode(&fs, 42, data); + auto inode = kernel::filesystem::ext2::inode(42, data); REQUIRE(inode.is_regular()); REQUIRE_FALSE(inode.is_directory()); @@ -53,7 +56,7 @@ SCENARIO("Ext2 inode initialization and properties", "[filesystem][ext2][inode]" THEN("the inode is initialized with directory mode in data and has the kind directory") { data.mode = kernel::filesystem::ext2::constants::mode_directory; - auto inode = kernel::filesystem::ext2::inode(&fs, 42, data); + auto inode = kernel::filesystem::ext2::inode(42, data); REQUIRE_FALSE(inode.is_regular()); REQUIRE(inode.is_directory()); @@ -64,7 +67,7 @@ SCENARIO("Ext2 inode initialization and properties", "[filesystem][ext2][inode]" THEN("the inode is initialized with symbolic link mode in data and has the kind symbolic link") { data.mode = kernel::filesystem::ext2::constants::mode_symbolic_link; - auto inode = kernel::filesystem::ext2::inode(&fs, 42, data); + auto inode = kernel::filesystem::ext2::inode(42, data); REQUIRE_FALSE(inode.is_regular()); REQUIRE_FALSE(inode.is_directory()); @@ -75,7 +78,7 @@ SCENARIO("Ext2 inode initialization and properties", "[filesystem][ext2][inode]" THEN("the inode is initialized with zero mode in data and has no specific kind") { data.mode = 0; - auto inode = kernel::filesystem::ext2::inode(&fs, 42, data); + auto inode = kernel::filesystem::ext2::inode(42, data); REQUIRE_FALSE(inode.is_regular()); REQUIRE_FALSE(inode.is_directory()); @@ -83,14 +86,6 @@ SCENARIO("Ext2 inode initialization and properties", "[filesystem][ext2][inode]" REQUIRE_FALSE(inode.is_symbolic_link()); } } - - GIVEN("no filesystem (null pointer)") - { - THEN("constructing an inode with a null filesystem pointer panics") - { - REQUIRE_THROWS_AS(kernel::filesystem::ext2::inode(nullptr, 42, {}), kernel::tests::cpu::halt); - } - } } SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, "Ext2 inode reads from real image", @@ -108,15 +103,22 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, "Ext2 in auto dev_inode = kstd::make_shared<kernel::filesystem::device_inode>(boot_device); - auto fs = kernel::filesystem::ext2::filesystem{}; - REQUIRE(fs.mount(dev_inode)); + auto fs = kstd::make_shared<kernel::filesystem::ext2::filesystem>(); + auto mount = kernel::filesystem::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); + auto root = (*mount)->root_dentry()->inode(); + auto driver_data = (*mount)->driver_data(); + REQUIRE(mount); - auto information = fs.lookup(fs.root_inode(), "information"); + auto information = fs->lookup(root, "information", driver_data); REQUIRE(information); - auto file = fs.lookup(*information, "info_1.txt"); + (*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); + THEN("reading from offset zero returns expected file prefix") { auto buffer = kstd::vector<std::byte>(6); @@ -150,8 +152,10 @@ SCENARIO("Ext2 inode handles zeros in block mappings as file holes", "[filesyste auto dev_inode = kstd::make_shared<kernel::filesystem::device_inode>(device); - auto fs = kernel::filesystem::ext2::filesystem{}; - REQUIRE(fs.mount(dev_inode)); + auto fs = kstd::make_shared<kernel::filesystem::ext2::filesystem>(); + auto mount = kernel::filesystem::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); + auto root = (*mount)->root_dentry()->inode(); + REQUIRE(mount); auto data = kernel::filesystem::ext2::inode_data{}; data.block[0] = 30; @@ -162,7 +166,8 @@ SCENARIO("Ext2 inode handles zeros in block mappings as file holes", "[filesyste kernel::tests::filesystem::ext2::write_bytes(*device, 30 * block_size, "Hello", 5_B); kernel::tests::filesystem::ext2::write_bytes(*device, 31 * block_size, "World!", 6_B); - auto inode = kernel::filesystem::ext2::inode{&fs, 42, data}; + auto inode = kernel::filesystem::ext2::inode{42, data}; + inode.set_owning_mount(*mount); auto buffer = kstd::vector<std::byte>(data.size, std::byte{0xAB}); @@ -188,8 +193,10 @@ SCENARIO("Ext2 inode handles zeros in block mappings as file holes", "[filesyste auto dev_inode = kstd::make_shared<kernel::filesystem::device_inode>(device); - auto fs = kernel::filesystem::ext2::filesystem{}; - REQUIRE(fs.mount(dev_inode)); + auto fs = kstd::make_shared<kernel::filesystem::ext2::filesystem>(); + auto mount = kernel::filesystem::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); + auto root = (*mount)->root_dentry()->inode(); + REQUIRE(mount); auto data = kernel::filesystem::ext2::inode_data{}; data.block[0] = 30; @@ -204,7 +211,8 @@ SCENARIO("Ext2 inode handles zeros in block mappings as file holes", "[filesyste kernel::tests::filesystem::ext2::write_bytes(*device, 50 * block_size, "Blub", 4_B); kernel::tests::filesystem::ext2::write_bytes(*device, 51 * block_size, "World!", 6_B); - auto inode = kernel::filesystem::ext2::inode{&fs, 42, data}; + auto inode = kernel::filesystem::ext2::inode{42, data}; + inode.set_owning_mount(*mount); auto buffer = kstd::vector<std::byte>(data.size, std::byte{0xAB}); @@ -233,14 +241,17 @@ SCENARIO("Ext2 inode handles zeros in block mappings as file holes", "[filesyste auto dev_inode = kstd::make_shared<kernel::filesystem::device_inode>(device); - auto fs = kernel::filesystem::ext2::filesystem{}; - REQUIRE(fs.mount(dev_inode)); + auto fs = kstd::make_shared<kernel::filesystem::ext2::filesystem>(); + auto mount = kernel::filesystem::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); + auto root = (*mount)->root_dentry()->inode(); + REQUIRE(mount); auto data = kernel::filesystem::ext2::inode_data{}; data.block[12] = 0; data.size = block_size.value * 15; - auto inode = kernel::filesystem::ext2::inode{&fs, 42, data}; + auto inode = kernel::filesystem::ext2::inode{42, data}; + inode.set_owning_mount(*mount); auto buffer = kstd::vector<std::byte>(block_size.value * 15, std::byte{0xAB}); @@ -264,8 +275,10 @@ SCENARIO("Ext2 inode read across block boundaries", "[filesystem][ext2][inode]") auto dev_inode = kstd::make_shared<kernel::filesystem::device_inode>(device); - auto fs = kernel::filesystem::ext2::filesystem{}; - REQUIRE(fs.mount(dev_inode)); + auto fs = kstd::make_shared<kernel::filesystem::ext2::filesystem>(); + auto mount = kernel::filesystem::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); + auto root = (*mount)->root_dentry()->inode(); + REQUIRE(mount); auto inode_data = kernel::filesystem::ext2::inode_data{}; inode_data.size = block_size.value * 2; @@ -273,7 +286,8 @@ SCENARIO("Ext2 inode read across block boundaries", "[filesystem][ext2][inode]") kernel::tests::filesystem::ext2::write_bytes(*device, 21 * block_size - 6_B, "Hello ", 6_B); inode_data.block[1] = 21; kernel::tests::filesystem::ext2::write_bytes(*device, 21 * block_size, "World!", 6_B); - auto inode = kernel::filesystem::ext2::inode{&fs, 42, inode_data}; + auto inode = kernel::filesystem::ext2::inode{42, inode_data}; + inode.set_owning_mount(*mount); auto buffer = kstd::vector<std::byte>(12, std::byte{0x00}); @@ -302,14 +316,23 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, "Ext2 in auto dev_inode = kstd::make_shared<kernel::filesystem::device_inode>(boot_device); - auto fs = kernel::filesystem::ext2::filesystem{}; - REQUIRE(fs.mount(dev_inode)); + auto fs = kstd::make_shared<kernel::filesystem::ext2::filesystem>(); + auto mount = kernel::filesystem::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); + auto root = (*mount)->root_dentry()->inode(); + auto driver_data = (*mount)->driver_data(); + REQUIRE(mount); - auto information = fs.lookup(fs.root_inode(), "information"); + auto information = fs->lookup(root, "information", driver_data); REQUIRE(information); - auto file = fs.lookup(*information, "info_1.txt"); + (*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); + REQUIRE(mount_state); THEN("writing to the inode updates backing device and the inode size") { @@ -333,24 +356,23 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, "Ext2 in THEN("writing allocates new blocks and updates the inode size") { - auto inode_data = static_cast<kernel::filesystem::ext2::inode *>(file.value().get())->data(); + auto inode = static_pointer_cast<kernel::filesystem::ext2::inode>(file.value()); - auto const block_size = fs.block_size(); - auto const expected_allocated_blocks = 32 * fs.inode_block_count(inode_data); + auto const block_size = fs->block_size(*mount_state); + auto const expected_allocated_blocks = 32 * fs->block_count(*inode, *mount_state); auto write_buffer = kstd::vector<std::byte>(block_size.value * expected_allocated_blocks, std::byte{'A'}); - auto const bytes_written = file.value()->write(write_buffer, 0_B); + auto const bytes_written = inode->write(write_buffer, 0_B); REQUIRE(bytes_written == block_size * expected_allocated_blocks); auto read_buffer = kstd::vector<std::byte>(block_size.value * expected_allocated_blocks, std::byte{0x00}); - auto const bytes_read = file.value()->read(read_buffer, 0_B); + auto const bytes_read = inode->read(read_buffer, 0_B); REQUIRE(bytes_read == block_size * expected_allocated_blocks); REQUIRE(std::ranges::all_of(read_buffer, [](std::byte c) { return c == std::byte{'A'}; })); - auto new_inode_data = static_cast<kernel::filesystem::ext2::inode *>(file.value().get())->data(); - REQUIRE(new_inode_data.size == write_buffer.size()); - REQUIRE(fs.inode_block_count(new_inode_data) == expected_allocated_blocks); + REQUIRE(inode->size().value == write_buffer.size()); + REQUIRE(fs->block_count(*inode, *mount_state) == expected_allocated_blocks); } THEN("writing to a an inode of type directory panics") @@ -394,8 +416,12 @@ SCENARIO("Ext2 inode write across block boundaries", "[filesystem][ext2][inode]" REQUIRE(dev_inode->write(block_bitmap, block_group_descriptor.block_bitmap * block_size)); - auto fs = kernel::filesystem::ext2::filesystem{}; - REQUIRE(fs.mount(dev_inode)); + auto fs = kstd::make_shared<kernel::filesystem::ext2::filesystem>(); + auto mount = kernel::filesystem::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); + auto root = (*mount)->root_dentry()->inode(); + REQUIRE(mount); + auto mount_state = static_pointer_cast<kernel::filesystem::ext2::mount_state>((*mount)->driver_data()); + REQUIRE(mount_state); auto inode_data = kernel::filesystem::ext2::inode_data{}; inode_data.size = 5; @@ -405,10 +431,12 @@ SCENARIO("Ext2 inode write across block boundaries", "[filesystem][ext2][inode]" inode_data.mode = kernel::filesystem::ext2::constants::mode_regular; uint32_t inode_number = 3; - auto const inode_data_offset = block_group_descriptor.inode_table * block_size + (inode_number - 1) * fs.inode_size(); + auto const inode_data_offset = + block_group_descriptor.inode_table * block_size + (inode_number - 1) * fs->inode_size(*mount_state); - auto inode = kernel::filesystem::ext2::inode{&fs, inode_number, inode_data}; - REQUIRE(dev_inode->write(as_bytes(std::span{&inode, 1}), inode_data_offset)); + auto inode = kernel::filesystem::ext2::inode{inode_number, inode_data}; + inode.set_owning_mount(*mount); + REQUIRE(dev_inode->write(kstd::raw_bytes(inode), inode_data_offset)); auto const buffer = kstd::vector{std::byte{'H'}, std::byte{'e'}, std::byte{'l'}, std::byte{'l'}, std::byte{'o'}, std::byte{' '}, @@ -418,8 +446,9 @@ SCENARIO("Ext2 inode write across block boundaries", "[filesystem][ext2][inode]" { auto do_read_inode = [&]() { auto inode_data = kernel::filesystem::ext2::inode_data{}; - REQUIRE(dev_inode->read(as_writable_bytes(std::span{&inode_data, 1}), inode_data_offset)); - auto read_inode = kernel::filesystem::ext2::inode{&fs, inode_number, inode_data}; + REQUIRE(dev_inode->read(kstd::raw_bytes(inode_data), inode_data_offset)); + auto read_inode = kernel::filesystem::ext2::inode{inode_number, inode_data}; + read_inode.set_owning_mount(*mount); return read_inode; }; @@ -500,7 +529,7 @@ SCENARIO("Ext2 inode write across block boundaries", "[filesystem][ext2][inode]" THEN("writing over the current size and over the number of free blocks") { auto const offset = 65 * block_size; - REQUIRE_THROWS_AS(inode.write(buffer, offset), kernel::tests::cpu::halt); + REQUIRE_FALSE(inode.write(buffer, offset)); } } } @@ -528,8 +557,10 @@ SCENARIO("Ext2 inode get_size() and set_size() handles size correctly depending auto dev_inode = kstd::make_shared<kernel::filesystem::device_inode>(device); - auto fs = kernel::filesystem::ext2::filesystem{}; - REQUIRE(fs.mount(dev_inode)); + auto fs = kstd::make_shared<kernel::filesystem::ext2::filesystem>(); + auto mount = kernel::filesystem::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); + auto root = (*mount)->root_dentry()->inode(); + REQUIRE(mount); auto data = kernel::filesystem::ext2::inode_data{}; data.size = 256; @@ -539,7 +570,8 @@ SCENARIO("Ext2 inode get_size() and set_size() handles size correctly depending uint32_t expected_dir_acl_after_set_size) { data.mode = mode; - auto inode = kernel::filesystem::ext2::inode{&fs, 42, data}; + auto inode = kernel::filesystem::ext2::inode{42, data}; + inode.set_owning_mount(*mount); REQUIRE(inode.size() == expected_initial_size); @@ -571,8 +603,10 @@ SCENARIO("Ext2 inode get_size() and set_size() handles size correctly depending auto dev_inode = kstd::make_shared<kernel::filesystem::device_inode>(device); - auto fs = kernel::filesystem::ext2::filesystem{}; - REQUIRE(fs.mount(dev_inode)); + auto fs = kstd::make_shared<kernel::filesystem::ext2::filesystem>(); + auto mount = kernel::filesystem::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); + auto root = (*mount)->root_dentry()->inode(); + REQUIRE(mount); auto data = kernel::filesystem::ext2::inode_data{}; data.size = 256; @@ -582,7 +616,8 @@ SCENARIO("Ext2 inode get_size() and set_size() handles size correctly depending uint32_t expected_dir_acl_after_set_size) { data.mode = mode; - auto inode = kernel::filesystem::ext2::inode{&fs, 42, data}; + auto inode = kernel::filesystem::ext2::inode{42, data}; + inode.set_owning_mount(*mount); REQUIRE(inode.size() == expected_initial_size); @@ -615,7 +650,7 @@ SCENARIO("Ext2 inode status()", "[filesystem][ext2][inode]") data.block[0] = (1 << 8); data.links_count = 1; - auto inode = kernel::filesystem::ext2::inode(&fs, 42, data); + auto inode = kernel::filesystem::ext2::inode(42, data); THEN("status() reports the real inode number, link count, and decoded raw device") { diff --git a/kernel/kernel/filesystem/ext2/linked_directory_entry.hpp b/kernel/kernel/filesystem/ext2/linked_directory_entry.hpp index 9c9d162d..2d364f05 100644 --- a/kernel/kernel/filesystem/ext2/linked_directory_entry.hpp +++ b/kernel/kernel/filesystem/ext2/linked_directory_entry.hpp @@ -1,19 +1,24 @@ #ifndef TEACHOS_KERNEL_FILESYSTEM_EXT2_LINKED_DIRECTORY_ENTRY_HPP #define TEACHOS_KERNEL_FILESYSTEM_EXT2_LINKED_DIRECTORY_ENTRY_HPP -#include <array> #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; uint8_t file_type; - std::array<char, 255> name; // NOLINT(readability-magic-numbers) + char name_start; // NOLINT(readability-magic-numbers) }; } // namespace kernel::filesystem::ext2 diff --git a/kernel/kernel/filesystem/ext2/module.cpp b/kernel/kernel/filesystem/ext2/module.cpp new file mode 100644 index 00000000..e6b14307 --- /dev/null +++ b/kernel/kernel/filesystem/ext2/module.cpp @@ -0,0 +1,26 @@ +#include <kernel/filesystem/driver_registry.hpp> +#include <kernel/filesystem/ext2/filesystem.hpp> +#include <kernel/filesystem/filesystem.hpp> + +#include <kstd/memory.hpp> + +#include <string_view> + +namespace +{ + struct descriptor final : kernel::filesystem::driver_descriptor + { + [[nodiscard]] auto name() const noexcept -> std::string_view override + { + return "ext2"; + } + + [[nodiscard]] auto make_instance() const -> kernel::filesystem::filesystem::filesystem_ptr override + { + return kstd::make_shared<kernel::filesystem::ext2::filesystem>(); + } + }; + + [[gnu::used]] + constexpr auto registration = kernel::filesystem::driver_module<descriptor>{}; +} // namespace diff --git a/kernel/kernel/filesystem/ext2/mount_state.hpp b/kernel/kernel/filesystem/ext2/mount_state.hpp new file mode 100644 index 00000000..ba705382 --- /dev/null +++ b/kernel/kernel/filesystem/ext2/mount_state.hpp @@ -0,0 +1,26 @@ +#ifndef TEACHOS_KERNEL_FILESYSTEM_EXT2_MOUNT_STATE_HPP +#define TEACHOS_KERNEL_FILESYSTEM_EXT2_MOUNT_STATE_HPP + +#include <kernel/filesystem/ext2/block_group_descriptor.hpp> +#include <kernel/filesystem/ext2/superblock.hpp> +#include <kernel/filesystem/inode.hpp> + +#include <kapi/tracked_mutex.hpp> + +#include <kstd/memory.hpp> +#include <kstd/vector.hpp> + +namespace kernel::filesystem::ext2 +{ + + struct mount_state + { + struct superblock superblock{}; + kstd::vector<block_group_descriptor> block_group_descriptors{}; + kstd::shared_ptr<kernel::filesystem::inode> backing_inode{}; + mutable kapi::tracked_mutex lock{}; + }; + +} // namespace kernel::filesystem::ext2 + +#endif
\ No newline at end of file diff --git a/kernel/kernel/filesystem/ext2/write_batch.cpp b/kernel/kernel/filesystem/ext2/write_batch.cpp new file mode 100644 index 00000000..e8663d21 --- /dev/null +++ b/kernel/kernel/filesystem/ext2/write_batch.cpp @@ -0,0 +1,67 @@ +#include <kernel/filesystem/ext2/write_batch.hpp> + +#include <kernel/filesystem/ext2/filesystem.hpp> +#include <kernel/filesystem/ext2/inode.hpp> +#include <kernel/filesystem/ext2/mount_state.hpp> + +#include <kapi/system.hpp> + +#include <algorithm> +#include <cstddef> +#include <ranges> + +namespace kernel::filesystem::ext2 +{ + + write_batch::write_batch(filesystem & filesystem, mount_state & state) + : m_filesystem{filesystem} + , m_state{state} + {} + + write_batch::~write_batch() + { + std::ranges::for_each(m_dirty_groups, [this](auto index) { + auto & descriptor = m_state.block_group_descriptors.at(index); + auto result = m_filesystem.write_block_group_descriptor(descriptor, index, m_state); + + if (!result) + { + kapi::system::panic("[FS:ext2] failed to write block groups descriptor", result.error()); + } + }); + + if (!m_dirty_groups.empty()) + { + auto result = m_filesystem.write_superblock(m_state); + if (!result) + { + kapi::system::panic("[FS:ext2] failed to write superblock", result.error()); + } + } + + std::ranges::for_each(m_dirty_inodes, [this](auto inode) { + auto result = m_filesystem.write_inode(inode->number(), inode->data(), m_state); + if (!result) + { + kapi::system::panic("[FS:ext2] failed to write inode", result.error()); + } + }); + } + + auto write_batch::mark_group(std::size_t index) -> void + { + if (std::ranges::find(m_dirty_groups, index) == std::ranges::end(m_dirty_groups)) + { + m_dirty_groups.push_back(index); + } + } + + auto write_batch::mark_inode(inode & inode) -> void + { + if (std::ranges::find(m_dirty_inodes, &inode) == std::ranges::end(m_dirty_inodes)) + { + m_dirty_inodes.push_back(&inode); + } + } + +} // namespace kernel::filesystem::ext2 diff --git a/kernel/kernel/filesystem/ext2/write_batch.hpp b/kernel/kernel/filesystem/ext2/write_batch.hpp new file mode 100644 index 00000000..f4000391 --- /dev/null +++ b/kernel/kernel/filesystem/ext2/write_batch.hpp @@ -0,0 +1,53 @@ +#ifndef TEACHOS_KERNEL_FILESYSTEM_EXT2_WRITE_BATCH_HPP +#define TEACHOS_KERNEL_FILESYSTEM_EXT2_WRITE_BATCH_HPP + +#include <kstd/vector.hpp> + +#include <cstddef> + +namespace kernel::filesystem::ext2 +{ + + struct filesystem; + struct inode; + struct mount_state; + + //! A RAII write batching type. + //! + //! Any metadata writes are first appended to the batch. On destruction, the write is committed to the filesystem. + struct write_batch + { + //! Construct a new write batch for the given filesystem and mount state. + //! + //! @param filesystem The filesystem driver to use for writes. + //! @param state The driver state to operate on. + write_batch(filesystem & filesystem, mount_state & state); + + write_batch(write_batch const &) = delete; + write_batch(write_batch &&) = delete; + auto operator=(write_batch const &) = delete; + auto operator=(write_batch &&) = delete; + + //! Commit and destroy the batch. + ~write_batch(); + + //! Mark the group with the given index as dirty. + //! + //! @param index The index of the group to mark. + auto mark_group(std::size_t index) -> void; + + //! Mark the given inode as dirty. + //! + //! @param inode The inode to mark. + auto mark_inode(inode & inode) -> void; + + private: + filesystem & m_filesystem; + mount_state & m_state; + kstd::vector<std::size_t> m_dirty_groups{}; + kstd::vector<inode *> m_dirty_inodes{}; + }; + +} // namespace kernel::filesystem::ext2 + +#endif diff --git a/kernel/kernel/filesystem/file_type.hpp b/kernel/kernel/filesystem/file_type.hpp deleted file mode 100644 index 71370ea6..00000000 --- a/kernel/kernel/filesystem/file_type.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef TEACHOS_KERNEL_FILESYSTEM_FILE_TYPE_HPP -#define TEACHOS_KERNEL_FILESYSTEM_FILE_TYPE_HPP - -namespace kernel::filesystem -{ - - //! POSIX file types. - enum struct file_type - { - block, - character, - pipe, - socket, - }; - -} // namespace kernel::filesystem - -#endif diff --git a/kernel/kernel/filesystem/file_type_bindings.hpp b/kernel/kernel/filesystem/file_type_bindings.hpp index b4338024..da8e4efc 100644 --- a/kernel/kernel/filesystem/file_type_bindings.hpp +++ b/kernel/kernel/filesystem/file_type_bindings.hpp @@ -1,7 +1,6 @@ #ifndef TEACHOS_KERNEL_FILESYSTEM_FILE_TYPE_BINDINGS_HPP #define TEACHOS_KERNEL_FILESYSTEM_FILE_TYPE_BINDINGS_HPP -#include <kernel/filesystem/file_type.hpp> #include <kernel/filesystem/reserved_numbers.hpp> #include <kapi/filesystem.hpp> @@ -15,15 +14,15 @@ namespace kernel::filesystem struct file_type_binding // NOLINT(cppcoreguidelines-pro-type-member-init) { kapi::capabilities::facet_id facet; - file_type posix_type; + kapi::filesystem::file_type posix_type; }; //! Predefined bindings for facet + file type //! //! This array associates published facets with files types. constexpr auto inline file_type_bindings = std::array{ - file_type_binding{kapi::filesystem::character_special_file::id, file_type::character}, - file_type_binding{ kapi::filesystem::block_special_file::id, file_type::block}, + file_type_binding{kapi::filesystem::character_special_file::id, kapi::filesystem::file_type::character_device}, + file_type_binding{ kapi::filesystem::block_special_file::id, kapi::filesystem::file_type::block_device}, }; } // namespace kernel::filesystem diff --git a/kernel/kernel/filesystem/filesystem.cpp b/kernel/kernel/filesystem/filesystem.cpp deleted file mode 100644 index 84bbdef6..00000000 --- a/kernel/kernel/filesystem/filesystem.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include <kernel/filesystem/filesystem.hpp> - -#include <kernel/filesystem/error.hpp> -#include <kernel/filesystem/inode.hpp> -#include <kernel/filesystem/type_registry.hpp> - -#include <kstd/memory.hpp> -#include <kstd/result.hpp> -#include <kstd/system_error.hpp> - -namespace kernel::filesystem -{ - - auto filesystem::probe_and_mount(kstd::shared_ptr<inode> const & backing_inode) - -> kstd::result<kstd::shared_ptr<filesystem>> - { - for (auto & type : type_registry::get().all()) - { - if (!type || !type->probe(backing_inode)) - { - continue; - } - - auto fs = type->make_instance(); - - if (fs->mount(backing_inode)) - { - return fs; - } - } - - return kstd::failure(vfs_errc::unsupported_filesystem); - } - - auto filesystem::mount(kstd::shared_ptr<inode> const & backing_inode) -> kstd::result<void> - { - m_backing_inode = backing_inode; - return kstd::success(); - } - - auto filesystem::root_inode() const -> kstd::shared_ptr<inode> const & - { - return m_root_inode; - } - - auto filesystem::backing_inode() const -> kstd::shared_ptr<inode> const & - { - return m_backing_inode; - } -} // namespace kernel::filesystem
\ No newline at end of file diff --git a/kernel/kernel/filesystem/filesystem.hpp b/kernel/kernel/filesystem/filesystem.hpp index 1a66c11c..f9fb2859 100644 --- a/kernel/kernel/filesystem/filesystem.hpp +++ b/kernel/kernel/filesystem/filesystem.hpp @@ -10,8 +10,10 @@ #include <kstd/system_error.hpp> #include <kstd/vector.hpp> +#include <cstdint> #include <optional> #include <string_view> +#include <utility> namespace kernel::filesystem { @@ -21,17 +23,19 @@ namespace kernel::filesystem //! Filesystem implementations must derive from this class. struct filesystem { + using filesystem_ptr = kstd::shared_ptr<filesystem>; + using driver_data_ptr = kstd::shared_ptr<void>; + using inode_ptr = kstd::shared_ptr<inode>; + using mount_result = std::pair<inode_ptr, driver_data_ptr>; + //! Virtual destructor enabling polymorphic destruction. virtual ~filesystem() = default; - //! Probes the given inode to determine if it contains a recognizable filesystem, and if so, mount it. - //! - //! @warning This function panics if @p backing_inode is null. + //! Check if the data represented by the given inode is recognized as this filesystem. //! - //! @param backing_inode The inode to probe and mount. - //! @return A pointer to the mounted filesystem instance on success, an error otherwise. - auto static probe_and_mount(kstd::shared_ptr<inode> const & backing_inode) - -> kstd::result<kstd::shared_ptr<filesystem>>; + //! @param inode The inode to probe, if any. + //! @return the priority (higher is better) of the match on success, an error otherwise. + [[nodiscard]] virtual auto probe(inode_ptr const & inode) const -> kstd::result<std::uint32_t> = 0; //! Initializes the filesystem with the given inode. //! @@ -39,8 +43,8 @@ namespace kernel::filesystem //! filesystem data (e.g. a file on an already mounted filesystem). //! //! @param backing_inode The inode to use as the backing inode for the filesystem. - //! @return Nothing on success, and error otherwise. - virtual auto mount(kstd::shared_ptr<inode> const & backing_inode) -> kstd::result<void>; + //! @return The root inode of the mounted filesystem on success, and error otherwise. + virtual auto mount(inode_ptr const & backing_inode) -> kstd::result<mount_result> = 0; //! Find a child inode below the given parent inode with the specified name. //! @@ -49,35 +53,23 @@ namespace kernel::filesystem //! //! @param parent The parent inode. //! @param name The name of the child inode to look up. + //! @param driver_data The per-mount data used by the driver. //! @return A pointer to the requested child inode on success, an error otherwise. - [[nodiscard]] virtual auto lookup(kstd::shared_ptr<inode> const & parent, std::string_view name) const - -> kstd::result<kstd::shared_ptr<inode>> = 0; + [[nodiscard]] virtual auto lookup(inode_ptr const & parent, std::string_view name, + driver_data_ptr driver_data) const -> kstd::result<inode_ptr> = 0; //! Create a new inode with the given name below a given parent inode. //! //! @param parent The parent inode. //! @param name The name of the inode to create. //! @param type The file type of inode to be created. + //! @param driver_data The per-mount data used by the driver. //! @param raw_device The device number the new inode should represent, if any. //! @return A pointer to the created inode on success, an error otherwise. - [[nodiscard]] virtual auto create_inode(kstd::shared_ptr<inode> const & parent, std::string_view name, - kapi::filesystem::file_type type, + [[nodiscard]] virtual auto create_inode(inode_ptr const & parent, std::string_view name, + kapi::filesystem::file_type type, driver_data_ptr driver_data, std::optional<kapi::filesystem::device_number> raw_device = std::nullopt) -> kstd::result<kstd::shared_ptr<inode>> = 0; - - //! Get the root inode of the file system. - //! - //! @return A reference to the root inode. - [[nodiscard]] auto root_inode() const -> kstd::shared_ptr<inode> const &; - - //! Get the backing inode of the file system. - //! - //! @return A reference to the backing inode. - [[nodiscard]] auto backing_inode() const -> kstd::shared_ptr<inode> const &; - - protected: - kstd::shared_ptr<inode> m_root_inode{}; - kstd::shared_ptr<inode> m_backing_inode{}; }; } // namespace kernel::filesystem diff --git a/kernel/kernel/filesystem/inode.cpp b/kernel/kernel/filesystem/inode.cpp index d0762472..7b290479 100644 --- a/kernel/kernel/filesystem/inode.cpp +++ b/kernel/kernel/filesystem/inode.cpp @@ -1,10 +1,17 @@ #include <kernel/filesystem/inode.hpp> +#include <kernel/filesystem/directory_listing_cursor.hpp> +#include <kernel/filesystem/directory_listing_entry.hpp> +#include <kernel/filesystem/error.hpp> + #include <kapi/filesystem.hpp> +#include <kstd/memory.hpp> #include <kstd/result.hpp> +#include <cstddef> #include <optional> +#include <span> #include <utility> namespace kernel::filesystem @@ -77,4 +84,20 @@ namespace kernel::filesystem return std::nullopt; } + auto inode::read_directory(directory_listing_cursor, std::span<directory_listing_entry>) const + -> kstd::result<std::pair<std::size_t, directory_listing_cursor>> + { + return kstd::failure(vfs_errc::not_a_directory); + } + + auto inode::owning_mount() const noexcept -> kstd::weak_ptr<mount> + { + return m_owning_mount; + } + + auto inode::set_owning_mount(kstd::shared_ptr<mount> const & source) noexcept -> void + { + m_owning_mount = source; + } + } // namespace kernel::filesystem
\ No newline at end of file diff --git a/kernel/kernel/filesystem/inode.hpp b/kernel/kernel/filesystem/inode.hpp index afe15e7d..e7de9276 100644 --- a/kernel/kernel/filesystem/inode.hpp +++ b/kernel/kernel/filesystem/inode.hpp @@ -1,17 +1,24 @@ #ifndef TEACHOS_KERNEL_FILESYSTEM_INODE_HPP #define TEACHOS_KERNEL_FILESYSTEM_INODE_HPP +#include <kernel/filesystem/directory_listing_cursor.hpp> +#include <kernel/filesystem/directory_listing_entry.hpp> + #include <kapi/filesystem.hpp> +#include <kstd/memory.hpp> #include <kstd/result.hpp> #include <kstd/units.hpp> #include <cstddef> #include <optional> #include <span> +#include <utility> namespace kernel::filesystem { + struct mount; + //! An inode in the filesystem. //! //! Specific filesystems, or other subsystems, may provide their own implementation of this abstract base class. For @@ -101,7 +108,34 @@ namespace kernel::filesystem //! @return a device number if this inode refers to a device, an empty optional otherwise. [[nodiscard]] virtual auto raw_device() const -> std::optional<kapi::filesystem::device_number>; + //! List the contents of a directory. + //! + //! @param position The current directory listing position. + //! @param entries An output span to store directory entries. + //! @return A pair of the number or read entries and the next listing position on success, an error otherwise. + [[nodiscard]] virtual auto read_directory(directory_listing_cursor position, + std::span<directory_listing_entry> entries) const + -> kstd::result<std::pair<std::size_t, directory_listing_cursor>>; + //! @} + + //! @name Mount Resolution + //! @{ + + //! Get the mount this inode belongs to. + //! + //! @return The mount this inode belongs to. + [[nodiscard]] auto owning_mount() const noexcept -> kstd::weak_ptr<mount>; + + //! Define which mount this inode belongs to. + //! + //! @param source The mount this inode belongs to. + auto set_owning_mount(kstd::shared_ptr<mount> const & source) noexcept -> void; + + //! @} + + private: + kstd::weak_ptr<mount> m_owning_mount{}; }; } // namespace kernel::filesystem diff --git a/kernel/kernel/filesystem/mount.cpp b/kernel/kernel/filesystem/mount.cpp index 009ab230..7381a160 100644 --- a/kernel/kernel/filesystem/mount.cpp +++ b/kernel/kernel/filesystem/mount.cpp @@ -6,6 +6,7 @@ #include <kapi/system.hpp> #include <kstd/memory.hpp> +#include <kstd/result.hpp> #include <kstd/string.hpp> #include <cstddef> @@ -13,14 +14,14 @@ namespace kernel::filesystem { - mount::mount(kstd::shared_ptr<dentry> const & mount_dentry, kstd::shared_ptr<dentry> const & root_dentry, - kstd::shared_ptr<filesystem> const & fs, kstd::shared_ptr<mount> const & parent_mount, - kstd::shared_ptr<mount> const & source_mount) + mount::mount(dentry_ptr const & mount_dentry, dentry_ptr const & root_dentry, filesystem_ptr const & fs, + mount_ptr const & parent_mount, mount_ptr const & source_mount, kstd::shared_ptr<void> driver_data) : m_mount_dentry(mount_dentry) , m_root_dentry(root_dentry) , m_filesystem(fs) , m_parent_mount(parent_mount) , m_source_mount(source_mount) + , m_driver_data(driver_data) , m_ref_count(0) { if (!m_filesystem) @@ -29,17 +30,56 @@ namespace kernel::filesystem } } - auto mount::mount_dentry() const -> kstd::shared_ptr<dentry> const & + auto mount::attach(dentry_ptr const & mount_dentry, filesystem_ptr const & fs, + kstd::shared_ptr<inode> const & root_inode, kstd::shared_ptr<void> driver_data, + mount_ptr const & parent_mount, mount_ptr const & source_mount) -> mount_ptr + { + auto root_dentry = mount_dentry + ? kstd::make_shared<dentry>(mount_dentry->parent(), root_inode, mount_dentry->name()) + : kstd::make_shared<dentry>(nullptr, root_inode, "/"); + + auto instance = mount_ptr{ + new mount{mount_dentry, root_dentry, fs, parent_mount, source_mount, driver_data} + }; + + root_inode->set_owning_mount(instance); + + return instance; + } + + auto mount::create(dentry_ptr const & mount_dentry, filesystem_ptr const & fs, mount_ptr const & parent_mount, + mount_ptr const & source_mount, kstd::shared_ptr<inode> const & backing_inode) + -> kstd::result<mount_ptr> + { + if (!fs) + { + kapi::system::panic("[OS:FS] Tried to mount a null filesystem!"); + } + + auto mount_result = fs->mount(backing_inode); + if (!mount_result) + { + return kstd::failure(mount_result.error()); + } + + auto [root_inode, driver_data] = *mount_result; + + auto instance = attach(mount_dentry, fs, root_inode, driver_data, parent_mount, source_mount); + + return instance; + } + + auto mount::mount_dentry() const -> dentry_ptr const & { return m_mount_dentry; } - auto mount::get_filesystem() const -> kstd::shared_ptr<filesystem> const & + auto mount::filesystem() const -> filesystem_ptr const & { return m_filesystem; } - auto mount::root_dentry() const -> kstd::shared_ptr<dentry> const & + auto mount::root_dentry() const -> dentry_ptr const & { return m_root_dentry; } @@ -53,16 +93,21 @@ namespace kernel::filesystem return "/"; } - auto mount::parent_mount() const -> kstd::shared_ptr<mount> const & + auto mount::parent_mount() const -> mount_ptr const & { return m_parent_mount; } - auto mount::source_mount() const -> kstd::shared_ptr<mount> + auto mount::source_mount() const -> mount_ptr { return m_source_mount.lock(); } + auto mount::driver_data() const -> kstd::shared_ptr<void> + { + return m_driver_data; + } + auto mount::increment_ref_count() -> void { m_ref_count += 1; diff --git a/kernel/kernel/filesystem/mount.hpp b/kernel/kernel/filesystem/mount.hpp index b906def5..cc032874 100644 --- a/kernel/kernel/filesystem/mount.hpp +++ b/kernel/kernel/filesystem/mount.hpp @@ -5,6 +5,7 @@ #include <kernel/filesystem/filesystem.hpp> #include <kstd/memory.hpp> +#include <kstd/result.hpp> #include <kstd/string.hpp> #include <atomic> @@ -12,37 +13,58 @@ namespace kernel::filesystem { + struct inode; + //! A mounted filesystem struct mount { + using dentry_ptr = kstd::shared_ptr<dentry>; + using filesystem_ptr = kstd::shared_ptr<struct filesystem>; + using mount_ptr = kstd::shared_ptr<struct mount>; + using inode_ptr = kstd::shared_ptr<struct inode>; + + //! Attach a mounted filesystem with a known root inode + //! + //! @param mount_dentry The directory entry where the filesystem is mounted. + //! @param fs The filesystem instance being mounted. + //! @param root_inode The backing inode for the filesystem, must not be null. + //! @param driver_data The filesystem specific driver data. + //! @param parent_mount The parent mount which contains the mount_dentry. + //! @param source_mount The mount that the filesystem originates from. + auto static attach(dentry_ptr const & mount_dentry, filesystem_ptr const & fs, inode_ptr const & root_inode, + kstd::shared_ptr<void> driver_data, mount_ptr const & parent_mount, + mount_ptr const & source_mount) -> mount_ptr; + //! Create a new mount with the given parameters. //! //! @param mount_dentry The directory entry where the filesystem is mounted. - //! @param root_dentry The root directory entry of the mounted filesystem. //! @param fs The filesystem instance being mounted. //! @param parent_mount The parent mount which contains the mount_dentry. //! @param source_mount The mount that the filesystem originates from. - mount(kstd::shared_ptr<dentry> const & mount_dentry, kstd::shared_ptr<dentry> const & root_dentry, - kstd::shared_ptr<filesystem> const & fs, kstd::shared_ptr<mount> const & parent_mount, - kstd::shared_ptr<mount> const & source_mount); + //! @param backing_inode The backing inode for the filesystem, if any. + auto static create(dentry_ptr const & mount_dentry, filesystem_ptr const & fs, mount_ptr const & parent_mount, + mount_ptr const & source_mount, inode_ptr const & backing_inode) -> kstd::result<mount_ptr>; //! Get the directory entry where the filesystem is mounted. - [[nodiscard]] auto mount_dentry() const -> kstd::shared_ptr<dentry> const &; + [[nodiscard]] auto mount_dentry() const -> dentry_ptr const &; //! Get the root directory entry of the mounted filesystem. - [[nodiscard]] auto root_dentry() const -> kstd::shared_ptr<dentry> const &; + [[nodiscard]] auto root_dentry() const -> dentry_ptr const &; //! Get the filesystem instance being mounted. - [[nodiscard]] auto get_filesystem() const -> kstd::shared_ptr<filesystem> const &; + [[nodiscard]] auto filesystem() const -> filesystem_ptr const &; //! Get the path at which the filesystem is mounted. [[nodiscard]] auto mount_path() const -> kstd::string; //! Get the parent mount that this mount was attached beneath. - [[nodiscard]] auto parent_mount() const -> kstd::shared_ptr<mount> const &; + [[nodiscard]] auto parent_mount() const -> mount_ptr const &; //! Get the source mount where this mount originates from. - [[nodiscard]] auto source_mount() const -> kstd::shared_ptr<mount>; + [[nodiscard]] auto source_mount() const -> mount_ptr; + + //! Get the data of the attached filesystem driver. + [[nodiscard]] auto driver_data() const -> kstd::shared_ptr<void>; //! Increment the reference count for this mount. auto increment_ref_count() -> void; @@ -63,11 +85,23 @@ namespace kernel::filesystem [[nodiscard]] auto ref_count() const -> size_t; private: - kstd::shared_ptr<dentry> m_mount_dentry{}; - kstd::shared_ptr<dentry> m_root_dentry{}; - kstd::shared_ptr<filesystem> m_filesystem{}; - kstd::shared_ptr<mount> m_parent_mount{}; + //! Create a new mount with the given parameters. + //! + //! @param mount_dentry The directory entry where the filesystem is mounted. + //! @param root_dentry The root directory entry of the mounted filesystem. + //! @param fs The filesystem instance being mounted. + //! @param parent_mount The parent mount which contains the mount_dentry. + //! @param source_mount The mount that the filesystem originates from. + //! @param driver_data The driver specific state data. + mount(dentry_ptr const & mount_dentry, dentry_ptr const & root_dentry, filesystem_ptr const & fs, + mount_ptr const & parent_mount, mount_ptr const & source_mount, kstd::shared_ptr<void> driver_data); + + dentry_ptr m_mount_dentry{}; + dentry_ptr m_root_dentry{}; + filesystem_ptr m_filesystem{}; + mount_ptr m_parent_mount{}; kstd::weak_ptr<mount> m_source_mount{}; + kstd::shared_ptr<void> m_driver_data{}; std::atomic_size_t m_ref_count{0}; }; } // namespace kernel::filesystem diff --git a/kernel/kernel/filesystem/mount.tests.cpp b/kernel/kernel/filesystem/mount.tests.cpp index 800a0cfc..a77134e5 100644 --- a/kernel/kernel/filesystem/mount.tests.cpp +++ b/kernel/kernel/filesystem/mount.tests.cpp @@ -15,29 +15,29 @@ SCENARIO("Mount construction", "[filesystem][mount]") { - GIVEN("a filesystem and a root dentry") + GIVEN("a filesystem and a root inode") { auto fs = kstd::make_shared<kernel::tests::filesystem::filesystem>(); auto root_inode = kstd::make_shared<kernel::tests::filesystem::inode>(); - auto root_dentry = kstd::make_shared<kernel::filesystem::dentry>(nullptr, root_inode, "/"); - WHEN("constructing a mount with the filesystem and root dentry") + WHEN("attaching a mount with no mount point") { - auto mount = kernel::filesystem::mount{root_dentry, root_dentry, fs, nullptr, nullptr}; + auto mount = kernel::filesystem::mount::attach(nullptr, fs, root_inode, nullptr, nullptr, nullptr); THEN("the mount has the correct filesystem, root dentry, mount dentry, and mount path") { - REQUIRE(mount.get_filesystem() == fs); - REQUIRE(mount.root_dentry() == root_dentry); - REQUIRE(mount.mount_dentry() == root_dentry); - REQUIRE(mount.mount_path() == "/"); - REQUIRE(mount.is_ready_to_unmount()); + REQUIRE(mount->filesystem() == fs); + REQUIRE(mount->root_dentry()->inode() == root_inode); + REQUIRE(mount->root_dentry()->name() == "/"); + REQUIRE(mount->mount_dentry() == nullptr); + REQUIRE(mount->mount_path() == "/"); + REQUIRE(mount->is_ready_to_unmount()); } THEN("the mount has no parent mount and no source mount") { - REQUIRE(mount.parent_mount() == nullptr); - REQUIRE(mount.source_mount() == nullptr); + REQUIRE(mount->parent_mount() == nullptr); + REQUIRE(mount->source_mount() == nullptr); } } @@ -45,7 +45,7 @@ SCENARIO("Mount construction", "[filesystem][mount]") { THEN("the constructor panics") { - REQUIRE_THROWS_AS((kernel::filesystem::mount{root_dentry, root_dentry, nullptr, nullptr, nullptr}), + REQUIRE_THROWS_AS((kernel::filesystem::mount::create(nullptr, nullptr, nullptr, nullptr, nullptr)), kernel::tests::cpu::halt); } } @@ -63,32 +63,33 @@ SCENARIO("Mount reference counting", "[filesystem][mount]") THEN("reference count can be incremented and decremented, the mount is ready to unmount when the reference " "count == 0") { - auto mount = kernel::filesystem::mount{root_dentry, root_dentry, fs, nullptr, nullptr}; + auto mount = kernel::filesystem::mount::create(root_dentry, fs, nullptr, nullptr, nullptr); + REQUIRE(mount); - mount.increment_ref_count(); - REQUIRE(mount.ref_count() == 1); - REQUIRE_FALSE(mount.is_ready_to_unmount()); + (*mount)->increment_ref_count(); + REQUIRE((*mount)->ref_count() == 1); + REQUIRE_FALSE((*mount)->is_ready_to_unmount()); - mount.increment_ref_count(); - REQUIRE(mount.ref_count() == 2); - REQUIRE_FALSE(mount.is_ready_to_unmount()); + (*mount)->increment_ref_count(); + REQUIRE((*mount)->ref_count() == 2); + REQUIRE_FALSE((*mount)->is_ready_to_unmount()); - mount.decrement_ref_count(); - REQUIRE(mount.ref_count() == 1); - REQUIRE_FALSE(mount.is_ready_to_unmount()); + (*mount)->decrement_ref_count(); + REQUIRE((*mount)->ref_count() == 1); + REQUIRE_FALSE((*mount)->is_ready_to_unmount()); - mount.decrement_ref_count(); - REQUIRE(mount.ref_count() == 0); - REQUIRE(mount.is_ready_to_unmount()); + (*mount)->decrement_ref_count(); + REQUIRE((*mount)->ref_count() == 0); + REQUIRE((*mount)->is_ready_to_unmount()); } THEN("decrementing reference count when it is already zero does not decrement it below zero") { - auto mount = kernel::filesystem::mount{root_dentry, root_dentry, fs, nullptr, nullptr}; + auto mount = kernel::filesystem::mount::create(root_dentry, fs, nullptr, nullptr, nullptr); - REQUIRE_THROWS_AS(mount.decrement_ref_count(), std::runtime_error); - REQUIRE(mount.ref_count() == 0); - REQUIRE(mount.is_ready_to_unmount()); + REQUIRE_THROWS_AS((*mount)->decrement_ref_count(), std::runtime_error); + REQUIRE((*mount)->ref_count() == 0); + REQUIRE((*mount)->is_ready_to_unmount()); } } }
\ No newline at end of file diff --git a/kernel/kernel/filesystem/mount_table.tests.cpp b/kernel/kernel/filesystem/mount_table.tests.cpp index 39ad6b01..bed6cd9d 100644 --- a/kernel/kernel/filesystem/mount_table.tests.cpp +++ b/kernel/kernel/filesystem/mount_table.tests.cpp @@ -38,17 +38,17 @@ SCENARIO("Adding, finding and removing mounts in the mount table", "[filesystem] nullptr, kstd::make_shared<kernel::tests::filesystem::inode>(), "/"); auto mount_dentry1 = kstd::make_shared<kernel::filesystem::dentry>( nullptr, kstd::make_shared<kernel::tests::filesystem::inode>(), "/"); - auto mount1 = kstd::make_shared<kernel::filesystem::mount>(mount_dentry1, root_dentry1, fs1, nullptr, nullptr); + auto mount1 = kernel::filesystem::mount::create(mount_dentry1, fs1, nullptr, nullptr, nullptr); auto fs2 = kstd::make_shared<kernel::tests::filesystem::filesystem>(); auto root_dentry2 = kstd::make_shared<kernel::filesystem::dentry>( nullptr, kstd::make_shared<kernel::tests::filesystem::inode>(), "/"); auto mount_dentry2 = kstd::make_shared<kernel::filesystem::dentry>( nullptr, kstd::make_shared<kernel::tests::filesystem::inode>(), "/mnt"); - auto mount2 = kstd::make_shared<kernel::filesystem::mount>(mount_dentry2, root_dentry2, fs2, nullptr, nullptr); + auto mount2 = kernel::filesystem::mount::create(mount_dentry2, fs2, nullptr, nullptr, nullptr); - table.add_mount(mount1); - table.add_mount(mount2); + table.add_mount(*mount1); + table.add_mount(*mount2); THEN("dentry flags are set correctly for mounted dentries") { @@ -89,17 +89,17 @@ SCENARIO("Adding, finding and removing mounts in the mount table", "[filesystem] nullptr, kstd::make_shared<kernel::tests::filesystem::inode>(), "/"); auto mount_dentry1 = kstd::make_shared<kernel::filesystem::dentry>( nullptr, kstd::make_shared<kernel::tests::filesystem::inode>(), "/"); - auto mount1 = kstd::make_shared<kernel::filesystem::mount>(mount_dentry1, root_dentry1, fs1, nullptr, nullptr); + auto mount1 = kernel::filesystem::mount::create(mount_dentry1, fs1, nullptr, nullptr, nullptr); auto fs2 = kstd::make_shared<kernel::tests::filesystem::filesystem>(); auto root_dentry2 = kstd::make_shared<kernel::filesystem::dentry>( nullptr, kstd::make_shared<kernel::tests::filesystem::inode>(), "/"); auto mount_dentry2 = kstd::make_shared<kernel::filesystem::dentry>( nullptr, kstd::make_shared<kernel::tests::filesystem::inode>(), "/"); - auto mount2 = kstd::make_shared<kernel::filesystem::mount>(mount_dentry2, root_dentry2, fs2, nullptr, nullptr); + auto mount2 = kernel::filesystem::mount::create(mount_dentry2, fs2, nullptr, nullptr, nullptr); - table.add_mount(mount1); - table.add_mount(mount2); + table.add_mount(*mount1); + table.add_mount(*mount2); THEN("finding mounts by exact valid path returns the correct mount") { @@ -122,25 +122,25 @@ SCENARIO("Adding, finding and removing mounts in the mount table", "[filesystem] nullptr, kstd::make_shared<kernel::tests::filesystem::inode>(), "/"); auto mount_dentry1 = kstd::make_shared<kernel::filesystem::dentry>( nullptr, kstd::make_shared<kernel::tests::filesystem::inode>(), "/"); - auto mount1 = kstd::make_shared<kernel::filesystem::mount>(mount_dentry1, root_dentry1, fs1, nullptr, nullptr); + auto mount1 = kernel::filesystem::mount::create(mount_dentry1, fs1, nullptr, nullptr, nullptr); auto fs2 = kstd::make_shared<kernel::tests::filesystem::filesystem>(); auto root_dentry2 = kstd::make_shared<kernel::filesystem::dentry>( nullptr, kstd::make_shared<kernel::tests::filesystem::inode>(), "/"); auto mount_dentry2 = kstd::make_shared<kernel::filesystem::dentry>( mount_dentry1, kstd::make_shared<kernel::tests::filesystem::inode>(), "mnt"); - auto mount2 = kstd::make_shared<kernel::filesystem::mount>(mount_dentry2, root_dentry2, fs2, mount1, nullptr); + auto mount2 = kernel::filesystem::mount::create(mount_dentry2, fs2, *mount1, nullptr, nullptr); auto fs3 = kstd::make_shared<kernel::tests::filesystem::filesystem>(); auto root_dentry3 = kstd::make_shared<kernel::filesystem::dentry>( nullptr, kstd::make_shared<kernel::tests::filesystem::inode>(), "/"); auto mount_dentry3 = kstd::make_shared<kernel::filesystem::dentry>( mount_dentry2, kstd::make_shared<kernel::tests::filesystem::inode>(), "submnt"); - auto mount3 = kstd::make_shared<kernel::filesystem::mount>(mount_dentry3, root_dentry3, fs3, mount2, nullptr); + auto mount3 = kernel::filesystem::mount::create(mount_dentry3, fs3, *mount2, nullptr, nullptr); - table.add_mount(mount1); - table.add_mount(mount2); - table.add_mount(mount3); + table.add_mount(*mount1); + table.add_mount(*mount2); + table.add_mount(*mount3); THEN("removing a mount with child mounts returns has_child_mounts") { @@ -166,19 +166,19 @@ SCENARIO("Mount reference counting", "[filesystem][mount_table]") auto root_inode = kstd::make_shared<kernel::tests::filesystem::inode>(); auto root_dentry = kstd::make_shared<kernel::filesystem::dentry>(nullptr, root_inode, "/"); - auto source_mount = kstd::make_shared<kernel::filesystem::mount>(root_dentry, root_dentry, fs, nullptr, nullptr); - auto mount = kstd::make_shared<kernel::filesystem::mount>(root_dentry, root_dentry, fs, nullptr, source_mount); + auto source_mount = kernel::filesystem::mount::create(root_dentry, fs, nullptr, nullptr, nullptr); + auto mount = kernel::filesystem::mount::create(root_dentry, fs, nullptr, *source_mount, nullptr); THEN("reference count of source mount is incremented when a mount is added to the mount table and decremented when " "the mount is removed") { - REQUIRE(source_mount->ref_count() == 0); + REQUIRE((*source_mount)->ref_count() == 0); - table.add_mount(mount); - REQUIRE(source_mount->ref_count() == 1); + table.add_mount(*mount); + REQUIRE((*source_mount)->ref_count() == 1); REQUIRE(table.remove_mount("/") == kernel::filesystem::mount_table::operation_result::removed); - REQUIRE(source_mount->ref_count() == 0); + REQUIRE((*source_mount)->ref_count() == 0); } } } diff --git a/kernel/kernel/filesystem/open_file_descriptor.cpp b/kernel/kernel/filesystem/open_file_descriptor.cpp index 98520aa8..64c47461 100644 --- a/kernel/kernel/filesystem/open_file_descriptor.cpp +++ b/kernel/kernel/filesystem/open_file_descriptor.cpp @@ -26,7 +26,7 @@ namespace kernel::filesystem auto open_file_descriptor::read(std::span<std::byte> buffer) -> kstd::result<kstd::bytes> { - if (auto result = m_dentry->get_inode()->read(buffer, m_offset); !result) + if (auto result = m_dentry->inode()->read(buffer, m_offset); !result) { return kstd::failure(result.error()); } @@ -40,7 +40,7 @@ namespace kernel::filesystem auto open_file_descriptor::write(std::span<std::byte const> buffer) -> kstd::result<kstd::bytes> { - if (auto result = m_dentry->get_inode()->write(buffer, m_offset); !result) + if (auto result = m_dentry->inode()->write(buffer, m_offset); !result) { return kstd::failure(result.error()); } @@ -68,7 +68,7 @@ namespace kernel::filesystem } case kapi::filesystem::seek_origin::end: { - return m_dentry->get_inode()->status()->size; + return m_dentry->inode()->status()->size; } default: { diff --git a/kernel/kernel/filesystem/rootfs/filesystem.cpp b/kernel/kernel/filesystem/rootfs/filesystem.cpp index cccfa63c..4618fd69 100644 --- a/kernel/kernel/filesystem/rootfs/filesystem.cpp +++ b/kernel/kernel/filesystem/rootfs/filesystem.cpp @@ -2,9 +2,7 @@ #include <kernel/filesystem/error.hpp> #include <kernel/filesystem/filesystem.hpp> -#include <kernel/filesystem/inode.hpp> #include <kernel/filesystem/rootfs/inode.hpp> -#include <kernel/filesystem/type.hpp> #include <kapi/filesystem.hpp> @@ -18,48 +16,31 @@ namespace kernel::filesystem::rootfs { - struct type final : kernel::filesystem::type - { - [[nodiscard]] auto name() const noexcept -> std::string_view override - { - return "rootfs"; - } - - [[nodiscard]] auto probe(kstd::shared_ptr<kernel::filesystem::inode> const & backing_inode) const noexcept - -> kstd::result<std::uint32_t> override - { - if (backing_inode) - { - return kstd::failure(vfs_errc::invalid_argument); - } + static_assert(sizeof(kernel::filesystem::filesystem) == sizeof(rootfs::filesystem)); - return 0; - } - - [[nodiscard]] auto make_instance() const -> kstd::shared_ptr<kernel::filesystem::filesystem> override + auto filesystem::probe(inode_ptr const & inode) const -> kstd::result<std::uint32_t> + { + if (inode) { - return kstd::make_shared<filesystem>(); + return kstd::failure(vfs_errc::invalid_argument); } - }; - [[gnu::used]] - constexpr auto registration = type_registration<type>{}; + return 0; + } - auto filesystem::mount(kstd::shared_ptr<kernel::filesystem::inode> const &) -> kstd::result<void> + auto filesystem::mount(inode_ptr const &) -> kstd::result<mount_result> { - m_root_inode = kstd::make_shared<inode>(); - return kstd::success(); + auto root = kstd::make_shared<inode>(); + return mount_result{root, nullptr}; } - auto filesystem::lookup(kstd::shared_ptr<kernel::filesystem::inode> const &, std::string_view) const - -> kstd::result<kstd::shared_ptr<kernel::filesystem::inode>> + auto filesystem::lookup(inode_ptr const &, std::string_view, driver_data_ptr) const -> kstd::result<inode_ptr> { return kstd::failure(vfs_errc::no_such_file_or_directory); } - auto filesystem::create_inode(kstd::shared_ptr<kernel::filesystem::inode> const &, std::string_view, - kapi::filesystem::file_type, std::optional<kapi::filesystem::device_number>) - -> kstd::result<kstd::shared_ptr<kernel::filesystem::inode>> + auto filesystem::create_inode(inode_ptr const &, std::string_view, kapi::filesystem::file_type, driver_data_ptr, + std::optional<kapi::filesystem::device_number>) -> kstd::result<inode_ptr> { return kstd::failure(vfs_errc::read_only_file_system); } diff --git a/kernel/kernel/filesystem/rootfs/filesystem.hpp b/kernel/kernel/filesystem/rootfs/filesystem.hpp index 2a861747..0195b250 100644 --- a/kernel/kernel/filesystem/rootfs/filesystem.hpp +++ b/kernel/kernel/filesystem/rootfs/filesystem.hpp @@ -12,6 +12,7 @@ #include <kstd/system_error.hpp> #include <kstd/vector.hpp> +#include <cstdint> #include <optional> #include <string_view> @@ -24,20 +25,22 @@ namespace kernel::filesystem::rootfs //! hierarchy. It is responsible for providing a stable and consistent interface to the root directory. struct filesystem : kernel::filesystem::filesystem { + [[nodiscard]] auto probe(inode_ptr const & inode) const -> kstd::result<std::uint32_t> override; + //! @copydoc kernel::filesystem::filesystem::mount - auto mount(kstd::shared_ptr<kernel::filesystem::inode> const & backing_inode) -> kstd::result<void> override; + auto mount(inode_ptr const & backing_inode) -> kstd::result<mount_result> override; //! @copydoc kernel::filesystem::filesystem::lookup. //! @return Always returns an error, as rootfs is always empty. - [[nodiscard]] auto lookup(kstd::shared_ptr<kernel::filesystem::inode> const & parent, std::string_view name) const - -> kstd::result<kstd::shared_ptr<kernel::filesystem::inode>> override; + [[nodiscard]] auto lookup(inode_ptr const & parent, std::string_view name, driver_data_ptr driver_data) const + -> kstd::result<inode_ptr> override; //! @copydoc kernel::filesystem::filesystem::create_inode. //! @return Always returns an error, as creating new inodes is not supported in rootfs. - [[nodiscard]] auto create_inode(kstd::shared_ptr<kernel::filesystem::inode> const & parent, std::string_view name, - kapi::filesystem::file_type type, + [[nodiscard]] auto create_inode(inode_ptr const & parent, std::string_view name, kapi::filesystem::file_type type, + driver_data_ptr driver_data, std::optional<kapi::filesystem::device_number> raw_device = std::nullopt) - -> kstd::result<kstd::shared_ptr<kernel::filesystem::inode>> override; + -> kstd::result<inode_ptr> override; }; } // namespace kernel::filesystem::rootfs diff --git a/kernel/kernel/filesystem/rootfs/filesystem.tests.cpp b/kernel/kernel/filesystem/rootfs/filesystem.tests.cpp index ba1aab0e..15c8e313 100644 --- a/kernel/kernel/filesystem/rootfs/filesystem.tests.cpp +++ b/kernel/kernel/filesystem/rootfs/filesystem.tests.cpp @@ -14,32 +14,34 @@ SCENARIO("Rootfs filesystem mount and lookup", "[filesystem][rootfs][filesystem] { GIVEN("a mounted rootfs filesystem") { - auto fs = kernel::filesystem::rootfs::filesystem{}; - auto result = fs.mount(nullptr); + auto fs = kstd::make_shared<kernel::filesystem::rootfs::filesystem>(); + auto mount = fs->mount(nullptr); + REQUIRE(mount); + auto [root, driver_data] = *mount; + REQUIRE(root); THEN("the filesystem can be mounted successfully") { - REQUIRE(result); - REQUIRE(fs.root_inode()); + REQUIRE(mount); } THEN("looking up a non-existent directory returns an error") { - auto non_existent_inode_1 = fs.lookup(fs.root_inode(), ""); + auto non_existent_inode_1 = fs->lookup(root, "", driver_data); REQUIRE(!non_existent_inode_1); - auto non_existent_inode_2 = fs.lookup(fs.root_inode(), "nonexistent"); + auto non_existent_inode_2 = fs->lookup(root, "nonexistent", driver_data); REQUIRE(!non_existent_inode_2); } THEN("looking up with a null parent inode returns an error") { - auto result = fs.lookup(nullptr, "dev"); + auto result = fs->lookup(nullptr, "dev", driver_data); REQUIRE(!result); } THEN("create_inode always returns an error") { - auto result = fs.create_inode(fs.root_inode(), "new_device", kapi::filesystem::file_type::directory); + auto result = fs->create_inode(root, "new_device", kapi::filesystem::file_type::directory, driver_data); REQUIRE(!result); } } diff --git a/kernel/kernel/filesystem/rootfs/module.cpp b/kernel/kernel/filesystem/rootfs/module.cpp new file mode 100644 index 00000000..464120b2 --- /dev/null +++ b/kernel/kernel/filesystem/rootfs/module.cpp @@ -0,0 +1,28 @@ +#include <kernel/filesystem/driver_registry.hpp> +#include <kernel/filesystem/filesystem.hpp> +#include <kernel/filesystem/rootfs/filesystem.hpp> + +#include <kstd/memory.hpp> + +#include <string_view> + +namespace +{ + + struct descriptor final : kernel::filesystem::driver_descriptor + { + [[nodiscard]] auto name() const noexcept -> std::string_view override + { + return "rootfs"; + } + + [[nodiscard]] auto make_instance() const -> kernel::filesystem::filesystem::filesystem_ptr override + { + return kstd::make_shared<kernel::filesystem::rootfs::filesystem>(); + } + }; + + [[gnu::used]] + constexpr auto registration = kernel::filesystem::driver_module<descriptor>{}; + +} // namespace
\ No newline at end of file diff --git a/kernel/kernel/filesystem/type.hpp b/kernel/kernel/filesystem/type.hpp deleted file mode 100644 index c2f2595b..00000000 --- a/kernel/kernel/filesystem/type.hpp +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef TEACHOS_KERNEL_FILESYSTEM_TYPE_HPP -#define TEACHOS_KERNEL_FILESYSTEM_TYPE_HPP - -#include <kernel/filesystem/filesystem.hpp> -#include <kernel/filesystem/inode.hpp> - -#include <kstd/memory.hpp> -#include <kstd/result.hpp> - -#include <cstdint> -#include <string_view> - -namespace kernel::filesystem -{ - - //! A type descriptor for a filesystem driver. - //! - //! Each filesystem must expose an instance of a class derived from this type in order to be registered with the vfs - //! filesystem registry. - struct type - { - virtual ~type() = default; - - //! Get the name of the filesystem represented by this descriptor. - [[nodiscard]] virtual auto name() const noexcept -> std::string_view = 0; - - //! Check if the filesystem is applicable. - //! - //! @return the priority of this filesystem for the given inode. - [[nodiscard]] virtual auto probe(kstd::shared_ptr<inode> const & backing_inode) const noexcept - -> kstd::result<std::uint32_t> = 0; - - //! Create a new instance of the filesystem represented by this descriptor. - [[nodiscard]] virtual auto make_instance() const -> kstd::shared_ptr<filesystem> = 0; - }; - - template<typename Type> - struct type_registration - { - constexpr auto static instance = Type{}; - [[using gnu: section("fs_types"), used, visibility("hidden")]] constexpr auto static pointer{ - kstd::make_observer<type const>(&instance), - }; - }; - -} // namespace kernel::filesystem - -#endif diff --git a/kernel/kernel/filesystem/type_registry.cpp b/kernel/kernel/filesystem/type_registry.cpp deleted file mode 100644 index 05fb116a..00000000 --- a/kernel/kernel/filesystem/type_registry.cpp +++ /dev/null @@ -1,94 +0,0 @@ -#include <kernel/filesystem/type_registry.hpp> - -#include <kapi/system.hpp> - -#include <kstd/memory.hpp> -#include <kstd/print.hpp> -#include <kstd/vector.hpp> - -#include <algorithm> -#include <cstddef> -#include <optional> -#include <ranges> -#include <span> - -namespace kernel::filesystem -{ - - extern "C" - { - // We need to suppress clang-tidy linting warnings here, since these symbols are generated by the linker and we - // cannot choose their names, unless we wanted to extend the linker script needlessly. - // NOLINTBEGIN(readability-identifier-naming) - extern type_registry::pointer const __start_fs_types; - extern type_registry::pointer const __stop_fs_types; - // NOLINTEND(readability-identifier-naming) - } - - namespace - { - auto constinit instance = std::optional<type_registry>{}; - } - - auto type_registry::init() -> void - { - if (instance) - { - kapi::system::panic("[FILESYSTEM] tried to initialize type registry more than once!"); - } - - instance.emplace(); - - auto type_descriptors = std::span{&__start_fs_types, &__stop_fs_types} | // - std::views::filter([](auto p) { return p != nullptr; }); - - std::ranges::for_each(type_descriptors, [](auto descriptor) { - kstd::println("[FILESYSTEM] registering '{}'", descriptor->name()); - instance->add(descriptor); - }); - } - - auto type_registry::get() -> type_registry & - { - if (!instance) - { - kapi::system::panic("[FILESYSTEM] type registry has not been initialized!"); - } - - return *instance; - } - - auto type_registry::find(std::string_view name) const -> std::optional<pointer> - { - if (auto it = m_descriptors.find(name); it != m_descriptors.end()) - { - return it->second; - } - return std::nullopt; - } - - auto type_registry::add(pointer descriptor) -> bool - { - auto result = m_descriptors.emplace(descriptor->name(), descriptor); - return result.second; - } - - auto type_registry::all() const noexcept -> kstd::vector<pointer> - { - return m_descriptors.values(); - } - - auto type_registry::size() const noexcept -> std::size_t - { - return m_descriptors.size(); - } - -} // namespace kernel::filesystem - -namespace kernel::tests::filesystem::type_registry -{ - auto deinit() -> void - { - kernel::filesystem::instance.reset(); - } -} // namespace kernel::tests::filesystem::type_registry
\ No newline at end of file diff --git a/kernel/kernel/filesystem/type_registry.hpp b/kernel/kernel/filesystem/type_registry.hpp deleted file mode 100644 index 11669ab3..00000000 --- a/kernel/kernel/filesystem/type_registry.hpp +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef TEACHOS_KERNEL_TYPE_REGISTRY_HPP -#define TEACHOS_KERNEL_TYPE_REGISTRY_HPP - -#include <kernel/filesystem/type.hpp> - -#include <kstd/flat_map.hpp> -#include <kstd/memory.hpp> -#include <kstd/string.hpp> -#include <kstd/vector.hpp> - -#include <cstddef> -#include <optional> -#include <string_view> - -namespace kernel::filesystem -{ - - struct type_registry - { - using value_type = type; - using pointer = kstd::observer_ptr<value_type const>; - - auto static init() -> void; - auto static get() -> type_registry &; - - constexpr type_registry() noexcept = default; - - //! Add a type descriptor to this registry. - //! - //! This function will register the given descriptor with this registry, given that no descriptor for a filesystem - //! with the same name exists in this registry already. - //! - //! @param descriptor The filesystem type descriptor to add to the registry. - //! @return @p true iff. the descriptor was successfully added, @p false if not. - auto add(pointer descriptor) -> bool; - - //! Find a registered type descriptor by name - //! - //! @param name The name of the filesystem - [[nodiscard]] auto find(std::string_view name) const -> std::optional<pointer>; - - //! Get all currently registered type descriptors. - //! - //! @return A span containing all currently registered filesystem type descriptors. - [[nodiscard]] auto all() const noexcept -> kstd::vector<pointer>; - - //! Get the number of registered filesystem types. - //! - //! @return The number of filesystem descriptors currently registered with this registry. - [[nodiscard]] auto size() const noexcept -> std::size_t; - - private: - //! A map from filesystem names (identifiers) to filesystem type descriptors. - kstd::flat_map<std::string_view, pointer> m_descriptors{}; - }; - -} // namespace kernel::filesystem - -#endif diff --git a/kernel/kernel/filesystem/type_registry.tests.cpp b/kernel/kernel/filesystem/type_registry.tests.cpp deleted file mode 100644 index b38c16f8..00000000 --- a/kernel/kernel/filesystem/type_registry.tests.cpp +++ /dev/null @@ -1,81 +0,0 @@ -#include <kernel/filesystem/type_registry.hpp> - -#include <kernel/filesystem/filesystem.hpp> -#include <kernel/filesystem/inode.hpp> -#include <kernel/filesystem/type.hpp> - -#include <kstd/memory.hpp> -#include <kstd/result.hpp> - -#include <catch2/catch_test_macros.hpp> - -#include <cstdint> -#include <iterator> -#include <string_view> - -struct test_type final : kernel::filesystem::type -{ - [[nodiscard]] auto name() const noexcept -> std::string_view override - { - return "bht_testfs"; - } - - [[nodiscard]] auto probe(kstd::shared_ptr<kernel::filesystem::inode> const &) const noexcept - -> kstd::result<std::uint32_t> override - { - return false; - } - - [[nodiscard]] auto make_instance() const -> kstd::shared_ptr<kernel::filesystem::filesystem> override - { - return nullptr; - } -}; - -SCENARIO("Filesystem type registry initialization and construction", "[filesystem]") -{ - GIVEN("A default constructed type_registry") - { - auto instance = kernel::filesystem::type_registry{}; - - WHEN("getting the span of filesystem descriptors") - { - auto descriptors = instance.all(); - - THEN("the span is empty") - { - REQUIRE(descriptors.empty()); - } - } - } -} - -SCENARIO("Filesystem type registry modifiers", "[filesystem]") -{ - GIVEN("A default constructed type_registry") - { - auto instance = kernel::filesystem::type_registry{}; - - WHEN("adding a type descriptor") - { - auto descriptor = test_type{}; - - instance.add(kstd::make_observer(&descriptor)); - - THEN("the size of the registry is one") - { - REQUIRE(instance.size() == 1); - } - - THEN("the span is not empty") - { - REQUIRE_FALSE(instance.all().empty()); - } - - THEN("the span's size is equal to the registry size") - { - REQUIRE(std::size(instance.all()) == std::size(instance)); - } - } - } -} diff --git a/kernel/kernel/filesystem/vfs.cpp b/kernel/kernel/filesystem/vfs.cpp index 0cb46cac..4645fd54 100644 --- a/kernel/kernel/filesystem/vfs.cpp +++ b/kernel/kernel/filesystem/vfs.cpp @@ -4,19 +4,19 @@ #include <kernel/filesystem/dentry.hpp> #include <kernel/filesystem/device_inode.hpp> #include <kernel/filesystem/device_number_registry.hpp> +#include <kernel/filesystem/driver_registry.hpp> #include <kernel/filesystem/error.hpp> -#include <kernel/filesystem/file_type.hpp> #include <kernel/filesystem/filesystem.hpp> #include <kernel/filesystem/inode.hpp> #include <kernel/filesystem/mount.hpp> #include <kernel/filesystem/mount_table.hpp> #include <kernel/filesystem/path.hpp> -#include <kernel/filesystem/type_registry.hpp> #include <kapi/filesystem.hpp> #include <kapi/system.hpp> #include <kstd/memory.hpp> +#include <kstd/print.hpp> #include <kstd/result.hpp> #include <kstd/system_error.hpp> #include <kstd/units.hpp> @@ -53,49 +53,67 @@ namespace kernel::filesystem vfs::vfs() { // mount rootfs at / - auto root_fs_descriptor = type_registry::get().find("rootfs"); - if (!root_fs_descriptor) + auto rootfs = driver_registry::get().find("rootfs"); + if (!rootfs) { kapi::system::panic("[OS:FS] rootfs driver is missing!"); } - auto root_fs = (*root_fs_descriptor)->make_instance(); - if (auto result = root_fs->mount(nullptr); !result) + auto root_mount_result = (*rootfs)->mount(nullptr); + if (!root_mount_result) { - kapi::system::panic("[FILESYSTEM] failed to mount root FS", result.error()); + kapi::system::panic("[FILESYSTEM] failed to mount root FS", root_mount_result.error()); } - auto root_fs_root_dentry = kstd::make_shared<dentry>(nullptr, root_fs->root_inode(), "/"); - auto root_mount = kstd::make_shared<struct mount>(nullptr, root_fs_root_dentry, root_fs, nullptr, nullptr); + auto [root_inode, root_state] = *root_mount_result; + + auto root_mount = mount::attach(nullptr, *rootfs, root_inode, root_state, nullptr, nullptr); m_mount_table.add_mount(root_mount); // mount devfs at /dev (inside rootfs, temporary, will be shadowed) - auto device_fs_descriptor = type_registry::get().find("devfs"); - if (!device_fs_descriptor) + auto devfs = driver_registry::get().find("devfs"); + if (!devfs) { kapi::system::panic("[OS:FS] devfs driver is missing!"); } - auto device_fs = (*device_fs_descriptor)->make_instance(); - if (auto result = device_fs->mount(nullptr); !result) + auto device_fs_mount_result = (*devfs)->mount(nullptr); + if (!device_fs_mount_result) { - kapi::system::panic("[FILESYSTEM] failed to mount device FS", result.error()); + kapi::system::panic("[FILESYSTEM] failed to mount device FS", device_fs_mount_result.error()); } - graft_persistent_device_fs(device_fs); + + auto [device_fs_root, device_fs_data] = *device_fs_mount_result; + graft_persistent_device_fs(*devfs, device_fs_root, device_fs_data); // mount boot fs at / (shadows rootfs), re-graft devfs auto [boot_device_dentry, boot_device_mount_context] = resolve_path_internal("/dev/ram0").value_or(std::pair{nullptr, nullptr}); if (boot_device_dentry && boot_device_mount_context) { - if (auto boot_root_fs = kernel::filesystem::filesystem::probe_and_mount(boot_device_dentry->get_inode())) + auto driver = driver_registry::get().match(boot_device_dentry->inode()); + if (!driver) { - if (auto root_dentry = resolve_path("/")) - { - do_mount_internal(*root_dentry, root_mount, *boot_root_fs, boot_device_mount_context); - graft_persistent_device_fs(device_fs); - } + kstd::println(kstd::print_sink::stderr, "[OS:FS] Missing driver for root disk!"); + return; + } + + auto root_dentry = resolve_path("/"); + if (!root_dentry) + { + kapi::system::panic("[OS:FS] No root directory found!"); + } + + auto mount = + mount::create(*root_dentry, *driver, root_mount, boot_device_mount_context, boot_device_dentry->inode()); + + if (!mount) + { + kapi::system::panic("[OS:FS] failed to mount boot FS", mount.error()); } + m_mount_table.add_mount(*mount); + + graft_persistent_device_fs(*devfs, device_fs_root, device_fs_data); } } @@ -118,7 +136,7 @@ namespace kernel::filesystem } auto [dentry, mount] = resolved_path.value(); - auto inode = dentry->get_inode(); + auto inode = dentry->inode(); if (inode->is_device()) { @@ -128,7 +146,8 @@ namespace kernel::filesystem return kstd::failure(vfs_errc::no_such_device); } - auto type = inode->is_block_device() ? file_type::block : file_type::character; + auto type = inode->is_block_device() ? kapi::filesystem::file_type::block_device + : kapi::filesystem::file_type::character_device; auto resolved_device = device_number_registry::get().resolve(*number, type); if (!resolved_device) { @@ -177,13 +196,20 @@ namespace kernel::filesystem } auto [source_dentry, source_mount_context] = *resolved_source; - auto fs = kernel::filesystem::filesystem::probe_and_mount(source_dentry->get_inode()); - if (!fs) + auto driver = driver_registry::get().match(source_dentry->inode()); + if (!driver) + { + return kstd::failure(driver.error()); + } + + auto mount = + mount::create(mount_point_dentry, *driver, mount_context, source_mount_context, source_dentry->inode()); + if (!mount) { - return kstd::failure(fs.error()); + return kstd::failure(mount.error()); } + m_mount_table.add_mount(*mount); - do_mount_internal(mount_point_dentry, mount_context, *fs, source_mount_context); return kstd::success(); } @@ -233,7 +259,7 @@ namespace kernel::filesystem return kstd::failure(resolved.error()); } - return (*resolved)->get_inode()->status(); + return (*resolved)->inode()->status(); } auto vfs::create_device_node(std::string_view path, std::uint32_t mode, kapi::filesystem::device_number device) @@ -278,10 +304,13 @@ namespace kernel::filesystem auto [parent_dentry, mount_context] = *resolved_parent; - if (auto fs = mount_context->get_filesystem()) + if (auto fs = mount_context->filesystem()) { - if (auto new_inode = fs->create_inode(parent_dentry->get_inode(), name, type, raw_device)) + if (auto new_inode = + fs->create_inode(parent_dentry->inode(), name, type, mount_context->driver_data(), raw_device)) { + (*new_inode)->set_owning_mount(mount_context); + auto new_dentry = kstd::make_shared<dentry>(parent_dentry, *new_inode, name); parent_dentry->add_child(new_dentry); @@ -292,16 +321,9 @@ namespace kernel::filesystem return kstd::failure(vfs_errc::no_such_file_or_directory); } - auto vfs::do_mount_internal(dentry_ptr const & mount_point_dentry, mount_ptr const & parent_mount, fs_ptr const & fs, - mount_ptr const & source_mount) -> void - { - auto new_fs_root = - kstd::make_shared<dentry>(mount_point_dentry->parent(), fs->root_inode(), mount_point_dentry->name()); - auto new_mount = kstd::make_shared<struct mount>(mount_point_dentry, new_fs_root, fs, parent_mount, source_mount); - m_mount_table.add_mount(new_mount); - } - - auto vfs::graft_persistent_device_fs(kstd::shared_ptr<filesystem> const & device_fs) -> void + auto vfs::graft_persistent_device_fs(kstd::shared_ptr<filesystem> const & device_fs, + kstd::shared_ptr<inode> const & root_inode, kstd::shared_ptr<void> driver_data) + -> void { auto [root_mount_point_dentry, root_mount] = resolve_path_internal("/").value_or(std::pair{nullptr, nullptr}); if (root_mount_point_dentry && root_mount) @@ -309,11 +331,12 @@ namespace kernel::filesystem auto dev_dentry = root_mount_point_dentry->find_child("dev"); if (!dev_dentry) { - dev_dentry = kstd::make_shared<dentry>(root_mount_point_dentry, device_fs->root_inode(), "dev"); + dev_dentry = kstd::make_shared<dentry>(root_mount_point_dentry, root_inode, "dev"); root_mount_point_dentry->add_child(dev_dentry); } - do_mount_internal(dev_dentry, root_mount, device_fs); + auto new_mount = mount::attach(dev_dentry, device_fs, root_inode, driver_data, root_mount, nullptr); + m_mount_table.add_mount(new_mount); } } @@ -343,7 +366,7 @@ namespace kernel::filesystem auto part = path_parts_vector.back(); path_parts_vector.pop_back(); - if (!current_dentry->get_inode()->is_directory()) + if (!current_dentry->inode()->is_directory()) { return kstd::failure(vfs_errc::not_a_directory); } @@ -378,13 +401,15 @@ namespace kernel::filesystem auto next_dentry = current_dentry->find_child(part); if (!next_dentry) { - auto current_fs = current_mount->get_filesystem(); - auto found_inode = current_fs->lookup(current_dentry->get_inode(), part); + auto current_fs = current_mount->filesystem(); + auto found_inode = current_fs->lookup(current_dentry->inode(), part, current_mount->driver_data()); if (!found_inode) { return kstd::failure(found_inode.error()); } + (*found_inode)->set_owning_mount(current_mount); + next_dentry = kstd::make_shared<dentry>(current_dentry, *found_inode, part); current_dentry->add_child(next_dentry); } @@ -399,7 +424,7 @@ namespace kernel::filesystem next_dentry = current_mount->root_dentry(); } - if (next_dentry->get_inode()->is_symbolic_link()) + if (next_dentry->inode()->is_symbolic_link()) { if (symlink_counter++ > constants::symloop_max) { @@ -407,7 +432,7 @@ namespace kernel::filesystem } kstd::vector<std::byte> buffer(constants::symlink_max_path_length); - auto const bytes_read = next_dentry->get_inode()->read(buffer, 0_B); + auto const bytes_read = next_dentry->inode()->read(buffer, 0_B); if (!bytes_read) { return kstd::failure(bytes_read.error()); diff --git a/kernel/kernel/filesystem/vfs.hpp b/kernel/kernel/filesystem/vfs.hpp index 2f30539b..a53ab532 100644 --- a/kernel/kernel/filesystem/vfs.hpp +++ b/kernel/kernel/filesystem/vfs.hpp @@ -111,10 +111,9 @@ namespace kernel::filesystem [[nodiscard]] auto find_mount(std::string_view path) const -> kstd::result<mount_ptr>; - auto do_mount_internal(dentry_ptr const & mount_point_dentry, mount_ptr const & parent_mount, fs_ptr const & fs, - mount_ptr const & source_mount = nullptr) -> void; - - auto graft_persistent_device_fs(kstd::shared_ptr<filesystem> const & device_fs) -> void; + auto graft_persistent_device_fs(kstd::shared_ptr<filesystem> const & device_fs, + kstd::shared_ptr<inode> const & root_inode, kstd::shared_ptr<void> driver_data) + -> void; auto create_inode(std::string_view path, kapi::filesystem::file_type type, std::optional<kapi::filesystem::device_number> raw_device = std::nullopt) -> kstd::result<void>; diff --git a/kernel/kernel/filesystem/vfs.tests.cpp b/kernel/kernel/filesystem/vfs.tests.cpp index a035e083..5b7ea621 100644 --- a/kernel/kernel/filesystem/vfs.tests.cpp +++ b/kernel/kernel/filesystem/vfs.tests.cpp @@ -665,11 +665,11 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, "VFS REQUIRE(via_persistent_fs != nullptr); auto marker = kstd::vector{std::byte{'M'}, std::byte{'K'}, std::byte{'N'}, std::byte{'O'}, std::byte{'D'}}; - auto written = (*via_persistent_fs)->get_inode()->write(marker, 0_B); + auto written = (*via_persistent_fs)->inode()->write(marker, 0_B); REQUIRE(written == kstd::bytes{marker.size()}); auto read_back = kstd::vector<std::byte>(marker.size()); - auto read = (*via_devfs)->get_inode()->read(read_back, 0_B); + auto read = (*via_devfs)->inode()->read(read_back, 0_B); REQUIRE(read == kstd::bytes{marker.size()}); REQUIRE(read_back == marker); diff --git a/kernel/kernel/main.cpp b/kernel/kernel/main.cpp index db47f167..f8322b31 100644 --- a/kernel/kernel/main.cpp +++ b/kernel/kernel/main.cpp @@ -1,4 +1,4 @@ -#include "kernel/filesystem/type_registry.hpp" +#include "kernel/filesystem/driver_registry.hpp" #include <kernel/devices/init.hpp> #include <kernel/devices/storage.hpp> #include <kernel/drivers/init.hpp> @@ -248,7 +248,7 @@ auto main() -> int kernel::filesystem::open_file_table::init(); kstd::println("[OS] Global open file table initialized."); - kernel::filesystem::type_registry::init(); + kernel::filesystem::driver_registry::init(); kstd::println("[OS] Builtin filesystems registered."); kernel::filesystem::vfs::init(); diff --git a/kernel/kernel/test_support/filesystem/driver_registry.hpp b/kernel/kernel/test_support/filesystem/driver_registry.hpp new file mode 100644 index 00000000..b91acd57 --- /dev/null +++ b/kernel/kernel/test_support/filesystem/driver_registry.hpp @@ -0,0 +1,10 @@ +#ifndef TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_DRIVER_REGISTRY_HPP +#define TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_DRIVER_REGISTRY_HPP + +namespace kernel::tests::filesystem::driver_registry +{ + //! Deinitialize the FS type registry singleton. + auto deinit() -> void; +} // namespace kernel::tests::filesystem::driver_registry + +#endif
\ No newline at end of file diff --git a/kernel/kernel/test_support/filesystem/filesystem.cpp b/kernel/kernel/test_support/filesystem/filesystem.cpp index cd08f961..0d4610ab 100644 --- a/kernel/kernel/test_support/filesystem/filesystem.cpp +++ b/kernel/kernel/test_support/filesystem/filesystem.cpp @@ -9,20 +9,30 @@ #include <kstd/result.hpp> #include <kstd/system_error.hpp> +#include <cstdint> #include <optional> #include <string_view> +#include <utility> namespace kernel::tests::filesystem { - auto filesystem::lookup(kstd::shared_ptr<kernel::filesystem::inode> const &, std::string_view) const - -> kstd::result<kstd::shared_ptr<kernel::filesystem::inode>> + auto filesystem::probe(inode_ptr const &) const -> kstd::result<std::uint32_t> + { + return 0; + } + + auto filesystem::mount(inode_ptr const &) -> kstd::result<mount_result> + { + return std::pair{kstd::make_shared<inode>(), nullptr}; + } + + auto filesystem::lookup(inode_ptr const &, std::string_view, driver_data_ptr) const -> kstd::result<inode_ptr> { return kstd::make_shared<inode>(); } - auto filesystem::create_inode(kstd::shared_ptr<kernel::filesystem::inode> const &, std::string_view, - kapi::filesystem::file_type, std::optional<kapi::filesystem::device_number>) - -> kstd::result<kstd::shared_ptr<kernel::filesystem::inode>> + auto filesystem::create_inode(inode_ptr const &, std::string_view, kapi::filesystem::file_type, driver_data_ptr, + std::optional<kapi::filesystem::device_number>) -> kstd::result<inode_ptr> { return kstd::make_shared<inode>(); } diff --git a/kernel/kernel/test_support/filesystem/filesystem.hpp b/kernel/kernel/test_support/filesystem/filesystem.hpp index 7aa14a9f..3df2cccb 100644 --- a/kernel/kernel/test_support/filesystem/filesystem.hpp +++ b/kernel/kernel/test_support/filesystem/filesystem.hpp @@ -10,6 +10,7 @@ #include <kstd/result.hpp> #include <kstd/system_error.hpp> +#include <cstdint> #include <optional> #include <string_view> @@ -19,13 +20,19 @@ namespace kernel::tests::filesystem { filesystem() = default; - [[nodiscard]] auto lookup(kstd::shared_ptr<kernel::filesystem::inode> const & parent, std::string_view name) const + [[nodiscard]] auto probe(inode_ptr const & inode) const -> kstd::result<std::uint32_t> override; + + auto mount(kstd::shared_ptr<kernel::filesystem::inode> const & backing_inode) + -> kstd::result<mount_result> override; + + [[nodiscard]] auto lookup(inode_ptr const & parent, std::string_view name, + driver_data_ptr driver_data = nullptr) const -> kstd::result<kstd::shared_ptr<kernel::filesystem::inode>> override; - [[nodiscard]] auto create_inode(kstd::shared_ptr<kernel::filesystem::inode> const & parent, std::string_view name, - kapi::filesystem::file_type type, + [[nodiscard]] auto create_inode(inode_ptr const & parent, std::string_view name, kapi::filesystem::file_type type, + driver_data_ptr driver_data = nullptr, std::optional<kapi::filesystem::device_number> = std::nullopt) - -> kstd::result<kstd::shared_ptr<kernel::filesystem::inode>> override; + -> kstd::result<inode_ptr> override; }; } // namespace kernel::tests::filesystem diff --git a/kernel/kernel/test_support/filesystem/storage_boot_module_fixture.cpp b/kernel/kernel/test_support/filesystem/storage_boot_module_fixture.cpp index 3df23751..b9228c69 100644 --- a/kernel/kernel/test_support/filesystem/storage_boot_module_fixture.cpp +++ b/kernel/kernel/test_support/filesystem/storage_boot_module_fixture.cpp @@ -2,8 +2,6 @@ #include <kernel/bus/boot_modules.hpp> #include <kernel/devices/storage.hpp> -#include <kernel/filesystem/type.hpp> -#include <kernel/filesystem/type_registry.hpp> #include <kapi/boot_modules.hpp> #include <kapi/boot_modules/device.hpp> diff --git a/kernel/kernel/test_support/filesystem/type_registry.hpp b/kernel/kernel/test_support/filesystem/type_registry.hpp deleted file mode 100644 index 8d5b6029..00000000 --- a/kernel/kernel/test_support/filesystem/type_registry.hpp +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_TYPE_REGISTRY_HPP -#define TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_TYPE_REGISTRY_HPP - -namespace kernel::tests::filesystem::type_registry -{ - //! Deinitialize the FS type registry singleton. - auto deinit() -> void; -} // namespace kernel::tests::filesystem::type_registry - -#endif
\ No newline at end of file diff --git a/kernel/kernel/test_support/state_reset_listener.cpp b/kernel/kernel/test_support/state_reset_listener.cpp index e900b31c..59f103b7 100644 --- a/kernel/kernel/test_support/state_reset_listener.cpp +++ b/kernel/kernel/test_support/state_reset_listener.cpp @@ -1,13 +1,14 @@ #include <kernel/devices/storage.hpp> #include <kernel/drivers/init.hpp> +#include <kernel/filesystem/device_number_registry.hpp> +#include <kernel/filesystem/driver_registry.hpp> #include <kernel/filesystem/open_file_table.hpp> -#include <kernel/filesystem/type_registry.hpp> #include <kernel/filesystem/vfs.hpp> #include <kernel/test_support/cio.hpp> #include <kernel/test_support/cpu.hpp> #include <kernel/test_support/filesystem/device_number_registry.hpp> +#include <kernel/test_support/filesystem/driver_registry.hpp> #include <kernel/test_support/filesystem/open_file_table.hpp> -#include <kernel/test_support/filesystem/type_registry.hpp> #include <kernel/test_support/filesystem/vfs.hpp> #include <kernel/test_support/memory.hpp> @@ -42,15 +43,15 @@ struct state_reset_listener : Catch::EventListenerBase std::ignore = kernel::devices::storage::init(); kernel::filesystem::open_file_table::init(); - kernel::filesystem::type_registry::init(); + std::ignore = kernel::filesystem::device_number_registry::get(); + kernel::filesystem::driver_registry::init(); } void testCaseEnded(Catch::TestCaseStats const &) override { - kernel::tests::filesystem::type_registry::deinit(); + kernel::tests::filesystem::driver_registry::deinit(); kernel::tests::filesystem::device_number_registry::deinit(); kernel::tests::filesystem::vfs::deinit(); - kernel::tests::filesystem::type_registry::deinit(); kernel::tests::filesystem::open_file_table::deinit(); kapi::test_support::devices::deinit(); |
