diff options
| author | Felix Morgner <felix.morgner@ost.ch> | 2026-07-22 17:47:12 +0200 |
|---|---|---|
| committer | Felix Morgner <felix.morgner@ost.ch> | 2026-07-22 17:47:12 +0200 |
| commit | a3d3a321147d579c889ff49da065563650515b6d (patch) | |
| tree | bfa44fdf92d220925554ef58cffd07e9294e9e7f /kernel | |
| parent | 9aa1c7fa234f0ee62017246eab42fa2b1cfdd8a4 (diff) | |
| download | kernel-a3d3a321147d579c889ff49da065563650515b6d.tar.xz kernel-a3d3a321147d579c889ff49da065563650515b6d.zip | |
kernel/vfs: implement persistent device nodes
Diffstat (limited to 'kernel')
| -rw-r--r-- | kernel/kernel/filesystem/device_inode.cpp | 24 | ||||
| -rw-r--r-- | kernel/kernel/filesystem/device_inode.hpp | 8 | ||||
| -rw-r--r-- | kernel/kernel/filesystem/device_inode.tests.cpp | 69 | ||||
| -rw-r--r-- | kernel/kernel/filesystem/vfs.cpp | 39 | ||||
| -rw-r--r-- | kernel/kernel/filesystem/vfs.hpp | 7 | ||||
| -rw-r--r-- | kernel/kernel/filesystem/vfs.tests.cpp | 64 |
6 files changed, 210 insertions, 1 deletions
diff --git a/kernel/kernel/filesystem/device_inode.cpp b/kernel/kernel/filesystem/device_inode.cpp index 063319a4..33037a52 100644 --- a/kernel/kernel/filesystem/device_inode.cpp +++ b/kernel/kernel/filesystem/device_inode.cpp @@ -3,6 +3,7 @@ #include <kernel/devices/block_device_utils.hpp> #include <kernel/filesystem/device_number_registry.hpp> #include <kernel/filesystem/error.hpp> +#include <kernel/filesystem/inode.hpp> #include <kapi/devices.hpp> #include <kapi/devices/device.hpp> @@ -18,7 +19,18 @@ namespace kernel::filesystem { device_inode::device_inode(kstd::shared_ptr<kapi::devices::device> const & device) - : m_device(device) + : device_inode{device, nullptr} + { + if (!device) + { + kapi::system::panic("[FILESYSTEM] device_inode constructed with null device."); + } + } + + device_inode::device_inode(kstd::shared_ptr<kapi::devices::device> const & device, + kstd::shared_ptr<inode> const & origin) + : m_device{device} + , m_origin{origin} { if (!device) { @@ -58,6 +70,16 @@ namespace kernel::filesystem return true; } + auto device_inode::status() const -> kstd::result<kapi::filesystem::file_status> + { + if (m_origin) + { + return m_origin->status(); + } + + return inode::status(); + } + auto device_inode::raw_device() const -> std::optional<kapi::filesystem::device_number> { if (auto number = device_number_registry::get().number_of(*m_device)) diff --git a/kernel/kernel/filesystem/device_inode.hpp b/kernel/kernel/filesystem/device_inode.hpp index 698124f2..e2167131 100644 --- a/kernel/kernel/filesystem/device_inode.hpp +++ b/kernel/kernel/filesystem/device_inode.hpp @@ -27,6 +27,11 @@ namespace kernel::filesystem */ explicit device_inode(kstd::shared_ptr<kapi::devices::device> const & device); + //! Create a device inode wrapping a device and carrying another inodes metadata. + //! + //! This constructor enable reconstituting a persistend device node from a filesystem. + device_inode(kstd::shared_ptr<kapi::devices::device> const & device, kstd::shared_ptr<inode> const & origin); + /** @brief Read data from the device inode (and in the background from the associated device) into a @p buffer, starting at @p offset and reading @p size bytes. @@ -61,10 +66,13 @@ namespace kernel::filesystem */ [[nodiscard]] auto is_block_device() const -> bool override; + [[nodiscard]] auto status() const -> kstd::result<kapi::filesystem::file_status> override; + [[nodiscard]] auto raw_device() const -> std::optional<kapi::filesystem::device_number> override; private: kstd::shared_ptr<kapi::devices::device> m_device; + kstd::shared_ptr<inode> m_origin; }; } // namespace kernel::filesystem diff --git a/kernel/kernel/filesystem/device_inode.tests.cpp b/kernel/kernel/filesystem/device_inode.tests.cpp index 4c6e0338..09979030 100644 --- a/kernel/kernel/filesystem/device_inode.tests.cpp +++ b/kernel/kernel/filesystem/device_inode.tests.cpp @@ -21,6 +21,7 @@ #include <cstddef> #include <cstdint> +#include <utility> using namespace kstd::units_literals; @@ -33,6 +34,30 @@ namespace kernel::tests::filesystem::device_number_registry::deinit(); } }; + + struct mock_origin_inode : kernel::filesystem::inode + { + auto read(void *, kstd::units::bytes, kstd::units::bytes) const -> kstd::result<kstd::units::bytes> override + { + return 0_B; + } + + auto write(void const *, kstd::units::bytes, kstd::units::bytes) -> kstd::result<kstd::units::bytes> override + { + return 0_B; + } + + [[nodiscard]] auto status() const -> kstd::result<kapi::filesystem::file_status> override + { + return kapi::filesystem::file_status{ + .mode = std::to_underlying(kapi::filesystem::file_type::block_device), + .size = 4096_B, + .raw_device = kapi::filesystem::device_number{.major = 1, .minor = 0}, + .inode_number = 40, + .link_count = 1, + }; + } + }; } // namespace SCENARIO("Device inode construction", "[filesystem][device_inode]") @@ -168,3 +193,47 @@ SCENARIO("Device inode raw_device()", "[filesystem][device-inode]") } } } + +SCENARIO("Device inode status()", "[filesystem][device_inode]") +{ + registry_reset_guard const guard{}; + + GIVEN("a device inode with no origin") + { + auto device = kstd::make_shared<kernel::tests::devices::block_device>("ram0", 512_B); + auto inode = kernel::filesystem::device_inode{device}; + + THEN("status() falls back to the minimal default") + { + auto const result = inode.status(); + REQUIRE(result.has_value()); + REQUIRE(result->mode == std::to_underlying(kapi::filesystem::file_type::block_device)); + REQUIRE(result->size == 0_B); + REQUIRE(result->inode_number == 0); + } + } + + GIVEN("a device inode constructed with an origin inode") + { + auto device = kstd::make_shared<kernel::tests::devices::block_device>("ram0", 512_B, 512_B); + auto origin = kstd::make_shared<mock_origin_inode>(); + auto inode = kernel::filesystem::device_inode{device, origin}; + + THEN("status() forwards to the origin outright, not device_inode's own default") + { + auto const result = inode.status(); + REQUIRE(result.has_value()); + REQUIRE(result->size == 4096_B); + REQUIRE(result->inode_number == 40); + REQUIRE(result->link_count == 1); + REQUIRE(result->raw_device == kapi::filesystem::device_number{.major = 1, .minor = 0}); + } + + THEN("read/write still delegate to the device, never to the origin") + { + kstd::vector<uint8_t> buffer(512); + auto const result = inode.read(buffer.data(), 0_B, 512_B); + REQUIRE(result == 512_B); + } + } +}
\ No newline at end of file diff --git a/kernel/kernel/filesystem/vfs.cpp b/kernel/kernel/filesystem/vfs.cpp index c1a36856..def08012 100644 --- a/kernel/kernel/filesystem/vfs.cpp +++ b/kernel/kernel/filesystem/vfs.cpp @@ -3,8 +3,12 @@ #include <kernel/filesystem/constants.hpp> #include <kernel/filesystem/dentry.hpp> #include <kernel/filesystem/devfs/filesystem.hpp> +#include <kernel/filesystem/device_inode.hpp> +#include <kernel/filesystem/device_number_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> @@ -197,6 +201,21 @@ namespace kernel::filesystem return (*resolved)->get_inode()->status(); } + auto vfs::create_device_node(std::string_view path, std::uint32_t mode, kapi::filesystem::device_number device) + -> kstd::result<void> + { + if (kapi::filesystem::is_block_device(mode)) + { + return create_inode(path, vfs_types::inode_type::block_device, device); + } + else if (kapi::filesystem::is_character_device(mode)) + { + return create_inode(path, vfs_types::inode_type::character_device, device); + } + + return kstd::failure(vfs_errc::invalid_argument); + } + auto vfs::create_inode(std::string_view path, vfs_types::inode_type type, std::optional<kapi::filesystem::device_number> raw_device) -> kstd::result<void> { @@ -375,6 +394,26 @@ namespace kernel::filesystem } continue; } + else if (next_dentry->get_inode()->is_device()) + { + auto number = (next_dentry->get_inode())->raw_device(); + if (!number) + { + return kstd::failure(vfs_errc::no_such_device); + } + + // TODO: in the future we may need to support fifos, UNIX domain socket, etc. here + auto type = next_dentry->get_inode()->is_block_device() ? file_type::block : file_type::character; + auto resolved = device_number_registry::get().resolve(*number, type); + if (!resolved) + { + return kstd::failure(resolved.error()); + } + + next_dentry = kstd::make_shared<dentry>( + current_dentry, + kstd::shared_ptr<inode>(kstd::make_shared<device_inode>(*resolved, next_dentry->get_inode())), part); + } current_dentry = next_dentry; } diff --git a/kernel/kernel/filesystem/vfs.hpp b/kernel/kernel/filesystem/vfs.hpp index 6ab67ef8..445952a0 100644 --- a/kernel/kernel/filesystem/vfs.hpp +++ b/kernel/kernel/filesystem/vfs.hpp @@ -14,6 +14,7 @@ #include <kstd/result.hpp> #include <kstd/system_error.hpp> +#include <cstdint> #include <optional> #include <string_view> #include <utility> @@ -101,6 +102,12 @@ namespace kernel::filesystem //! @return A populated file status object on success, an error otherwise. [[nodiscard]] auto status(std::string_view path) const -> kstd::result<kapi::filesystem::file_status>; + //! Create a new device node at a given path. + //! + //! @return Nothing on success, an error otherwise. + auto create_device_node(std::string_view path, std::uint32_t mode, kapi::filesystem::device_number device) + -> kstd::result<void>; + private: /** * Note: Resolving a dentry requires traversing mount points; since the diff --git a/kernel/kernel/filesystem/vfs.tests.cpp b/kernel/kernel/filesystem/vfs.tests.cpp index 6b8411b0..8667a914 100644 --- a/kernel/kernel/filesystem/vfs.tests.cpp +++ b/kernel/kernel/filesystem/vfs.tests.cpp @@ -9,6 +9,7 @@ #include <kstd/memory.hpp> #include <kstd/system_error.hpp> +#include <kstd/units.hpp> #include <kstd/vector.hpp> #include <catch2/catch_test_macros.hpp> @@ -17,6 +18,9 @@ #include <filesystem> #include <stdexcept> #include <string_view> +#include <utility> + +using namespace kstd::units_literals; SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, "VFS with dummy modules", "[filesystem][vfs]") @@ -634,5 +638,65 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, "VFS REQUIRE(kapi::filesystem::is_regular(result->mode)); REQUIRE(result->raw_device == kapi::filesystem::device_number{}); } + + WHEN("creating a persistent device node on the ext2 filesystem, resolving to /dev/ram0") + { + auto ram0_status = vfs.status("/dev/ram0"); + REQUIRE(ram0_status); + + auto number = kapi::filesystem::device_number{ + ram0_status->raw_device.major, + ram0_status->raw_device.minor, + }; + + REQUIRE(vfs.create_device_node("/persistent_ram0", + std::to_underlying(kapi::filesystem::file_type::block_device) | 0660, number)); + + THEN("opening it reaches the same device as /dev/ram0") + { + auto persistent_node_status = vfs.status("/persistent_ram0"); + REQUIRE(persistent_node_status); + REQUIRE(persistent_node_status->raw_device == ram0_status->raw_device); + + auto via_devfs = vfs.open("/dev/ram0"); + REQUIRE(via_devfs != nullptr); + + auto via_persistent_fs = vfs.open("/persistent_ram0"); + 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.data(), 0_B, kstd::units::bytes{marker.size()}); + REQUIRE(written == kstd::units::bytes{marker.size()}); + + auto read_back = kstd::vector<std::byte>(marker.size()); + auto read = (*via_devfs)->get_inode()->read(read_back.data(), 0_B, kstd::units::bytes{read_back.size()}); + REQUIRE(read == kstd::units::bytes{marker.size()}); + + REQUIRE(read_back == marker); + } + } + + WHEN("create_device_node() is called with a number that matches no live device") + { + auto const number = kapi::filesystem::device_number{.major = 250, .minor = 250}; + REQUIRE(vfs.create_device_node("/nowhere", std::to_underlying(kapi::filesystem::file_type::block_device) | 0660, + number)); + + THEN("opening it fails with ENODEV, not a panic") + { + auto result = vfs.open("/nowhere"); + REQUIRE(!result); + REQUIRE(result.error() == kstd::errc::no_such_device); + } + } + + THEN("create_device_node() with a mode that isn't a device type fails with invalid_argument") + { + auto const number = kapi::filesystem::device_number{.major = 1, .minor = 0}; + auto result = vfs.create_device_node("/not_a_device", + std::to_underlying(kapi::filesystem::file_type::regular) | 0644, number); + REQUIRE(!result); + REQUIRE(result.error() == kstd::errc::invalid_argument); + } } } |
