aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFelix Morgner <felix.morgner@ost.ch>2026-07-22 17:47:12 +0200
committerFelix Morgner <felix.morgner@ost.ch>2026-07-22 17:47:12 +0200
commita3d3a321147d579c889ff49da065563650515b6d (patch)
treebfa44fdf92d220925554ef58cffd07e9294e9e7f
parent9aa1c7fa234f0ee62017246eab42fa2b1cfdd8a4 (diff)
downloadkernel-a3d3a321147d579c889ff49da065563650515b6d.tar.xz
kernel-a3d3a321147d579c889ff49da065563650515b6d.zip
kernel/vfs: implement persistent device nodes
-rw-r--r--kernel/kernel/filesystem/device_inode.cpp24
-rw-r--r--kernel/kernel/filesystem/device_inode.hpp8
-rw-r--r--kernel/kernel/filesystem/device_inode.tests.cpp69
-rw-r--r--kernel/kernel/filesystem/vfs.cpp39
-rw-r--r--kernel/kernel/filesystem/vfs.hpp7
-rw-r--r--kernel/kernel/filesystem/vfs.tests.cpp64
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);
+ }
}
}