aboutsummaryrefslogtreecommitdiff
path: root/kernel/src
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/src')
-rw-r--r--kernel/src/devices/block_device.cpp3
-rw-r--r--kernel/src/devices/block_device.tests.cpp46
-rw-r--r--kernel/src/devices/block_device_utils.cpp7
-rw-r--r--kernel/src/devices/block_device_utils.tests.cpp219
-rw-r--r--kernel/src/devices/storage/management.cpp28
-rw-r--r--kernel/src/filesystem/dentry.cpp9
-rw-r--r--kernel/src/filesystem/dentry.tests.cpp135
-rw-r--r--kernel/src/filesystem/devfs/filesystem.cpp5
-rw-r--r--kernel/src/filesystem/devfs/filesystem.tests.cpp72
-rw-r--r--kernel/src/filesystem/devfs/inode.tests.cpp54
-rw-r--r--kernel/src/filesystem/device_inode.cpp4
-rw-r--r--kernel/src/filesystem/device_inode.tests.cpp108
-rw-r--r--kernel/src/filesystem/ext2/filesystem.cpp241
-rw-r--r--kernel/src/filesystem/ext2/filesystem.tests.cpp132
-rw-r--r--kernel/src/filesystem/ext2/inode.cpp47
-rw-r--r--kernel/src/filesystem/ext2/inode.tests.cpp159
-rw-r--r--kernel/src/filesystem/file_descriptor_table.cpp20
-rw-r--r--kernel/src/filesystem/file_descriptor_table.tests.cpp113
-rw-r--r--kernel/src/filesystem/filesystem.cpp44
-rw-r--r--kernel/src/filesystem/mount.cpp11
-rw-r--r--kernel/src/filesystem/mount.tests.cpp49
-rw-r--r--kernel/src/filesystem/mount_table.cpp71
-rw-r--r--kernel/src/filesystem/mount_table.tests.cpp163
-rw-r--r--kernel/src/filesystem/open_file_description.cpp6
-rw-r--r--kernel/src/filesystem/open_file_description.tests.cpp114
-rw-r--r--kernel/src/filesystem/rootfs/filesystem.cpp5
-rw-r--r--kernel/src/filesystem/rootfs/filesystem.tests.cpp45
-rw-r--r--kernel/src/filesystem/rootfs/inode.tests.cpp83
-rw-r--r--kernel/src/filesystem/vfs.cpp74
-rw-r--r--kernel/src/filesystem/vfs.tests.cpp166
-rw-r--r--kernel/src/main.cpp79
-rw-r--r--kernel/src/test_support/devices/block_device.cpp61
-rw-r--r--kernel/src/test_support/devices/character_device.cpp20
-rw-r--r--kernel/src/test_support/filesystem/ext2.cpp62
-rw-r--r--kernel/src/test_support/filesystem/filesystem.cpp17
-rw-r--r--kernel/src/test_support/filesystem/inode.cpp22
-rw-r--r--kernel/src/test_support/filesystem/storage_boot_module_fixture.cpp109
-rw-r--r--kernel/src/test_support/filesystem/storage_boot_module_vfs_fixture.cpp32
-rw-r--r--kernel/src/test_support/filesystem/test_assets/ext2_1KB_fs.img3
-rw-r--r--kernel/src/test_support/filesystem/test_assets/ext2_2KB_fs.img3
-rw-r--r--kernel/src/test_support/filesystem/test_assets/ext2_4KB_fs.img3
-rw-r--r--kernel/src/test_support/state_reset_listener.cpp12
42 files changed, 2518 insertions, 138 deletions
diff --git a/kernel/src/devices/block_device.cpp b/kernel/src/devices/block_device.cpp
index c006198..b7cb26e 100644
--- a/kernel/src/devices/block_device.cpp
+++ b/kernel/src/devices/block_device.cpp
@@ -1,8 +1,7 @@
#include "kernel/devices/block_device.hpp"
-#include "kapi/system.hpp"
-
#include "kapi/devices/device.hpp"
+#include "kapi/system.hpp"
#include <kstd/string>
diff --git a/kernel/src/devices/block_device.tests.cpp b/kernel/src/devices/block_device.tests.cpp
new file mode 100644
index 0000000..378437e
--- /dev/null
+++ b/kernel/src/devices/block_device.tests.cpp
@@ -0,0 +1,46 @@
+#include "kernel/test_support/devices/block_device.hpp"
+
+#include "kernel/test_support/cpu.hpp"
+
+#include <kstd/memory>
+#include <kstd/print>
+#include <kstd/string>
+#include <kstd/vector>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <cstddef>
+
+SCENARIO("Block device construction", "[devices][block_device]")
+{
+ GIVEN("parameters for a block device")
+ {
+ size_t major = 1;
+ size_t minor = 0;
+ kstd::string name = "test_block_device";
+ size_t block_size = 512;
+
+ WHEN("constructing a block device")
+ {
+ auto device =
+ kstd::make_shared<kernel::tests::devices::block_device>(major, minor, name, block_size, 3 * block_size);
+
+ THEN("the block device has the correct properties")
+ {
+ REQUIRE(device->major() == major);
+ REQUIRE(device->minor() == minor);
+ REQUIRE(device->name() == name);
+ REQUIRE(device->block_size() == block_size);
+ REQUIRE(device->capacity() == 3 * 512);
+ }
+ }
+
+ WHEN("constructing a block device with zero block size")
+ {
+ THEN("the constructor panics")
+ {
+ REQUIRE_THROWS_AS((kernel::tests::devices::block_device(major, minor, name, 0)), kernel::tests::cpu::halt);
+ }
+ }
+ }
+}
diff --git a/kernel/src/devices/block_device_utils.cpp b/kernel/src/devices/block_device_utils.cpp
index 6fe89fe..59e9b97 100644
--- a/kernel/src/devices/block_device_utils.cpp
+++ b/kernel/src/devices/block_device_utils.cpp
@@ -1,9 +1,9 @@
#include "kernel/devices/block_device_utils.hpp"
+#include "kapi/devices/device.hpp"
#include "kapi/system.hpp"
#include "kernel/devices/block_device.hpp"
-#include "kapi/devices/device.hpp"
#include <kstd/cstring>
#include <kstd/memory>
@@ -31,12 +31,13 @@ namespace kernel::devices::block_device_utils
return 0;
}
- auto * block_dev = static_cast<devices::block_device *>(device.get());
- if (block_dev == nullptr)
+ if (!device->is_block_device())
{
kapi::system::panic("[FILESYSTEM] device_file: expected block_device.");
}
+ auto * block_dev = static_cast<devices::block_device *>(device.get());
+
size_t const block_size = block_dev->block_size();
size_t const capacity = block_dev->capacity();
diff --git a/kernel/src/devices/block_device_utils.tests.cpp b/kernel/src/devices/block_device_utils.tests.cpp
new file mode 100644
index 0000000..f78e477
--- /dev/null
+++ b/kernel/src/devices/block_device_utils.tests.cpp
@@ -0,0 +1,219 @@
+#include "kernel/devices/block_device_utils.hpp"
+
+#include "kernel/test_support/cpu.hpp"
+#include "kernel/test_support/devices/block_device.hpp"
+#include "kernel/test_support/devices/character_device.hpp"
+
+#include <kstd/memory>
+#include <kstd/print>
+#include <kstd/vector>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <cstddef>
+#include <cstdint>
+
+SCENARIO("reading from a block device with block_device_utils", "[devices][block_device_utils]")
+{
+ GIVEN("a block device with known data")
+ {
+ auto const block_size = 512;
+ auto device = kstd::make_shared<kernel::tests::devices::block_device>(0, 0, "test_block_device", block_size);
+ kstd::vector<uint8_t> block_data(block_size);
+ for (size_t i = 0; i < block_data.size(); ++i)
+ {
+ block_data[i] = static_cast<uint8_t>(i % 256);
+ }
+ device->write_block(0, block_data.data());
+ device->write_block(1, block_data.data());
+
+ WHEN("reading from the block device using block_device_utils")
+ {
+ kstd::vector<uint8_t> read_buffer(block_size);
+ auto bytes_read = kernel::devices::block_device_utils::read(device, read_buffer.data(), 0, read_buffer.size());
+
+ THEN("the correct number of bytes is read")
+ {
+ REQUIRE(bytes_read == read_buffer.size());
+ }
+
+ THEN("the data read matches the data written to the block device")
+ {
+ REQUIRE(read_buffer == block_data);
+ }
+ }
+
+ WHEN("reading over block boundaries")
+ {
+ kstd::vector<uint8_t> read_buffer(1024);
+ auto bytes_read = kernel::devices::block_device_utils::read(device, read_buffer.data(), 256, read_buffer.size());
+
+ THEN("the correct number of bytes is read")
+ {
+ REQUIRE(bytes_read == 1.5 * block_size);
+ }
+
+ THEN("the data read matches the expected data across block boundaries")
+ {
+ for (size_t i = 0; i < bytes_read; ++i)
+ {
+ uint8_t expected_value = static_cast<uint8_t>((256 + i) % 256);
+ REQUIRE(read_buffer[i] == expected_value);
+ }
+ }
+ }
+
+ WHEN("reading beyond the device capacity")
+ {
+ kstd::vector<uint8_t> read_buffer(block_size);
+ auto bytes_read = kernel::devices::block_device_utils::read(device, read_buffer.data(), 1024, read_buffer.size());
+
+ THEN("no bytes are read")
+ {
+ REQUIRE(bytes_read == 0);
+ }
+ }
+
+ WHEN("reading nothing")
+ {
+ kstd::vector<uint8_t> read_buffer(block_size);
+ auto bytes_read = kernel::devices::block_device_utils::read(device, read_buffer.data(), 0, 0);
+
+ THEN("no bytes are read")
+ {
+ REQUIRE(bytes_read == 0);
+ }
+ }
+
+ WHEN("reading with a null buffer")
+ {
+ THEN("the system panics")
+ {
+ REQUIRE_THROWS_AS(kernel::devices::block_device_utils::read(device, nullptr, 0, 512), kernel::tests::cpu::halt);
+ }
+ }
+ }
+}
+
+SCENARIO("writing to a block device using block_device_utils", "[devices][block_device_utils]")
+{
+ GIVEN("a block device")
+ {
+ auto const block_size = 512;
+ auto device =
+ kstd::make_shared<kernel::tests::devices::block_device>(0, 0, "test_block_device", block_size, 2 * block_size);
+
+ WHEN("writing to the block device using block_device_utils")
+ {
+ kstd::vector<uint8_t> write_buffer(block_size);
+ for (size_t i = 0; i < write_buffer.size(); ++i)
+ {
+ write_buffer[i] = static_cast<uint8_t>(i % 256);
+ }
+
+ auto bytes_written =
+ kernel::devices::block_device_utils::write(device, write_buffer.data(), 0, write_buffer.size());
+
+ THEN("the correct number of bytes is written")
+ {
+ REQUIRE(bytes_written == write_buffer.size());
+ }
+
+ THEN("the data written matches the data read back from the block device")
+ {
+ kstd::vector<uint8_t> read_buffer(block_size);
+ device->read_block(0, read_buffer.data());
+ REQUIRE(read_buffer == write_buffer);
+ }
+ }
+
+ WHEN("writing over block boundaries")
+ {
+ kstd::vector<uint8_t> write_buffer(2 * block_size);
+ for (size_t i = 0; i < write_buffer.size(); ++i)
+ {
+ write_buffer[i] = static_cast<uint8_t>(i % 256);
+ }
+
+ auto bytes_written =
+ kernel::devices::block_device_utils::write(device, write_buffer.data(), 256, write_buffer.size());
+
+ THEN("the correct number of bytes is written")
+ {
+ REQUIRE(bytes_written == 1.5 * block_size);
+ }
+
+ THEN("the data written matches the data read back from the block device across block boundaries")
+ {
+ kstd::vector<uint8_t> read_buffer(2 * block_size);
+ auto bytes_read = kernel::devices::block_device_utils::read(device, read_buffer.data(), 256, 2 * block_size);
+
+ for (size_t i = 0; i < bytes_read; ++i)
+ {
+ REQUIRE(read_buffer[i] == write_buffer[i]);
+ }
+ }
+ }
+
+ WHEN("writing beyond the device capacity")
+ {
+ kstd::vector<uint8_t> write_buffer(block_size);
+ auto bytes_written =
+ kernel::devices::block_device_utils::write(device, write_buffer.data(), 1024, write_buffer.size());
+
+ THEN("no bytes are written")
+ {
+ REQUIRE(bytes_written == 0);
+ }
+ }
+
+ WHEN("writing nothing")
+ {
+ kstd::vector<uint8_t> write_buffer(block_size);
+ auto bytes_written = kernel::devices::block_device_utils::write(device, write_buffer.data(), 0, 0);
+
+ THEN("no bytes are written")
+ {
+ REQUIRE(bytes_written == 0);
+ }
+ }
+
+ WHEN("writing with a null buffer")
+ {
+ THEN("the system panics")
+ {
+ REQUIRE_THROWS_AS(kernel::devices::block_device_utils::write(device, nullptr, 0, block_size),
+ kernel::tests::cpu::halt);
+ }
+ }
+ }
+}
+
+SCENARIO("block_device_utils with a non-block device", "[devices][block_device_utils]")
+{
+ GIVEN("a non-block device")
+ {
+ auto device = kstd::make_shared<kernel::tests::devices::character_device>(0, 0, "test_character_device");
+
+ WHEN("attempting to read from the non-block device using block_device_utils")
+ {
+ kstd::vector<uint8_t> read_buffer(512);
+ THEN("the system panics")
+ {
+ REQUIRE_THROWS_AS(kernel::devices::block_device_utils::read(device, read_buffer.data(), 0, read_buffer.size()),
+ kernel::tests::cpu::halt);
+ }
+ }
+
+ WHEN("attempting to write to the non-block device using block_device_utils")
+ {
+ kstd::vector<uint8_t> write_buffer(512);
+ THEN("the system panics")
+ {
+ REQUIRE_THROWS_AS(
+ kernel::devices::block_device_utils::write(device, write_buffer.data(), 0, write_buffer.size()),
+ kernel::tests::cpu::halt);
+ }
+ }
+ }
+}
diff --git a/kernel/src/devices/storage/management.cpp b/kernel/src/devices/storage/management.cpp
index d440bf0..c9fa0a8 100644
--- a/kernel/src/devices/storage/management.cpp
+++ b/kernel/src/devices/storage/management.cpp
@@ -1,9 +1,9 @@
#include "kernel/devices/storage/management.hpp"
#include "kapi/boot_modules.hpp"
+#include "kapi/devices/device.hpp"
#include "kapi/system.hpp"
-#include "kapi/devices/device.hpp"
#include "kernel/devices/storage/controller.hpp"
#include "kernel/devices/storage/ram_disk/controller.hpp"
@@ -14,17 +14,17 @@
#include <cstddef>
#include <optional>
-namespace kernel::devices::storage
+namespace
{
- namespace
- {
- constexpr size_t static MINORS_PER_DEVICE = 16;
- constexpr size_t static START_MAJOR = 1;
- constinit size_t static next_free_major = START_MAJOR;
+ constexpr size_t static MINORS_PER_DEVICE = 16;
+ constexpr size_t static START_MAJOR = 1;
+ constinit size_t static next_free_major = START_MAJOR;
- constinit auto static active_storage_management = std::optional<management>{};
- } // namespace
+ constinit auto static active_storage_management = std::optional<kernel::devices::storage::management>{};
+} // namespace
+namespace kernel::devices::storage
+{
auto management::init() -> void
{
if (active_storage_management)
@@ -81,5 +81,13 @@ namespace kernel::devices::storage
{
return device_by_major_minor(START_MAJOR, 0);
}
+} // namespace kernel::devices::storage
-} // namespace kernel::devices::storage \ No newline at end of file
+namespace kernel::tests::devices::storage::management
+{
+ auto deinit() -> void
+ {
+ active_storage_management.reset();
+ next_free_major = START_MAJOR;
+ }
+} // namespace kernel::tests::devices::storage::management
diff --git a/kernel/src/filesystem/dentry.cpp b/kernel/src/filesystem/dentry.cpp
index 2f99e91..6591011 100644
--- a/kernel/src/filesystem/dentry.cpp
+++ b/kernel/src/filesystem/dentry.cpp
@@ -12,10 +12,10 @@
namespace kernel::filesystem
{
- dentry::dentry(kstd::shared_ptr<dentry> const & parent, kstd::shared_ptr<inode> const & node, std::string_view name)
+ dentry::dentry(kstd::shared_ptr<dentry> const & parent, kstd::shared_ptr<inode> const & inode, std::string_view name)
: m_name(name)
, m_parent(parent)
- , m_inode(node)
+ , m_inode(inode)
{
if (!m_inode)
{
@@ -33,6 +33,11 @@ namespace kernel::filesystem
return m_parent;
}
+ auto dentry::get_name() const -> std::string_view
+ {
+ return m_name.view();
+ }
+
auto dentry::add_child(kstd::shared_ptr<dentry> const & child) -> void
{
m_children.push_back(child);
diff --git a/kernel/src/filesystem/dentry.tests.cpp b/kernel/src/filesystem/dentry.tests.cpp
new file mode 100644
index 0000000..a6620d3
--- /dev/null
+++ b/kernel/src/filesystem/dentry.tests.cpp
@@ -0,0 +1,135 @@
+#include "kernel/filesystem/dentry.hpp"
+
+#include "kernel/test_support/cpu.hpp"
+#include "kernel/test_support/filesystem/inode.hpp"
+
+#include <kstd/memory>
+#include <kstd/print>
+
+#include <catch2/catch_test_macros.hpp>
+
+SCENARIO("Dentry construction", "[filesystem][dentry]")
+{
+ GIVEN("A parent dentry and inode")
+ {
+ auto inode = kstd::make_shared<kernel::tests::filesystem::inode>();
+ auto parent_dentry = kstd::make_shared<kernel::filesystem::dentry>(nullptr, inode);
+
+ WHEN("constructing a dentry")
+ {
+ auto child_dentry = kernel::filesystem::dentry{parent_dentry, inode, "child"};
+
+ THEN("the dentry has the correct parent, inode, and name")
+ {
+ REQUIRE(child_dentry.get_parent() == parent_dentry);
+ REQUIRE(child_dentry.get_inode() == inode);
+ REQUIRE(child_dentry.get_name() == "child");
+ }
+
+ THEN("no flag is set")
+ {
+ REQUIRE_FALSE(child_dentry.has_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted));
+ }
+ }
+
+ WHEN("constructing a dentry with an empty name")
+ {
+ auto child_dentry = kernel::filesystem::dentry{parent_dentry, inode};
+
+ THEN("the dentry has the correct parent and inode, and an empty name")
+ {
+ REQUIRE(child_dentry.get_parent() == parent_dentry);
+ REQUIRE(child_dentry.get_inode() == inode);
+ REQUIRE(child_dentry.get_name().empty());
+ }
+
+ THEN("no flag is set")
+ {
+ REQUIRE_FALSE(child_dentry.has_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted));
+ }
+ }
+
+ WHEN("constructing a dentry with a null parent")
+ {
+ auto child_dentry = kernel::filesystem::dentry{nullptr, inode, "child"};
+
+ THEN("the dentry has a null parent, the correct inode, and the correct name")
+ {
+ REQUIRE(child_dentry.get_parent() == nullptr);
+ REQUIRE(child_dentry.get_inode() == inode);
+ REQUIRE(child_dentry.get_name() == "child");
+ }
+
+ THEN("no flag is set")
+ {
+ REQUIRE_FALSE(child_dentry.has_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted));
+ }
+ }
+
+ WHEN("constructing a dentry with a null inode")
+ {
+ THEN("the system panics")
+ {
+ REQUIRE_THROWS_AS((kernel::filesystem::dentry{parent_dentry, nullptr, "child"}), kernel::tests::cpu::halt);
+ }
+ }
+ }
+}
+
+SCENARIO("Dentry child logic", "[filesystem][dentry]")
+{
+ GIVEN("A parent dentry and inode")
+ {
+ auto inode = kstd::make_shared<kernel::tests::filesystem::inode>();
+ auto parent_dentry = kstd::make_shared<kernel::filesystem::dentry>(nullptr, inode);
+
+ WHEN("adding child dentries")
+ {
+ auto child1 = kstd::make_shared<kernel::filesystem::dentry>(parent_dentry, inode, "child1");
+ auto child2 = kstd::make_shared<kernel::filesystem::dentry>(parent_dentry, inode, "child2");
+ parent_dentry->add_child(child1);
+ parent_dentry->add_child(child2);
+
+ THEN("the children can be found by name")
+ {
+ REQUIRE(parent_dentry->find_child("child1") == child1);
+ REQUIRE(parent_dentry->find_child("child2") == child2);
+ }
+
+ THEN("finding a non-existent child returns null")
+ {
+ REQUIRE(parent_dentry->find_child("nonexistent") == nullptr);
+ }
+ }
+ }
+}
+
+SCENARIO("Dentry Flag logic", "[filesystem][dentry]")
+{
+ GIVEN("A dentry")
+ {
+ auto inode = kstd::make_shared<kernel::tests::filesystem::inode>();
+ auto dentry = kernel::filesystem::dentry{nullptr, inode, "test"};
+
+ WHEN("setting a flag")
+ {
+ dentry.set_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted);
+
+ THEN("the flag is set")
+ {
+ REQUIRE(dentry.has_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted));
+ }
+ }
+
+ WHEN("unsetting a flag")
+ {
+ dentry.set_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted);
+ dentry.unset_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted);
+
+ THEN("the flag is unset")
+ {
+ REQUIRE_FALSE(dentry.has_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted));
+ }
+ }
+ }
+}
diff --git a/kernel/src/filesystem/devfs/filesystem.cpp b/kernel/src/filesystem/devfs/filesystem.cpp
index 9043ac5..03b4218 100644
--- a/kernel/src/filesystem/devfs/filesystem.cpp
+++ b/kernel/src/filesystem/devfs/filesystem.cpp
@@ -1,6 +1,7 @@
#include "kernel/filesystem/devfs/filesystem.hpp"
#include "kapi/devices/device.hpp"
+
#include "kernel/devices/storage/management.hpp"
#include "kernel/filesystem/devfs/inode.hpp"
#include "kernel/filesystem/device_inode.hpp"
@@ -13,12 +14,12 @@
namespace kernel::filesystem::devfs
{
- auto filesystem::mount(kstd::shared_ptr<kapi::devices::device> const &) -> int
+ auto filesystem::mount(kstd::shared_ptr<kapi::devices::device> const &) -> operation_result
{
m_root_inode = kstd::make_shared<inode>();
build_device_inode_table();
- return 0;
+ return operation_result::success;
}
auto filesystem::lookup(kstd::shared_ptr<kernel::filesystem::inode> const & parent, std::string_view name)
diff --git a/kernel/src/filesystem/devfs/filesystem.tests.cpp b/kernel/src/filesystem/devfs/filesystem.tests.cpp
new file mode 100644
index 0000000..f8c4764
--- /dev/null
+++ b/kernel/src/filesystem/devfs/filesystem.tests.cpp
@@ -0,0 +1,72 @@
+#include "kernel/filesystem/devfs/filesystem.hpp"
+
+#include "kernel/filesystem/filesystem.hpp"
+#include "kernel/test_support/filesystem/storage_boot_module_fixture.hpp"
+
+#include <catch2/catch_test_macros.hpp>
+
+SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture,
+ "Devfs filesystem lookup uses storage management devices", "[filesystem][devfs][filesystem]")
+{
+ GIVEN("a boot module registry with one module")
+ {
+ setup_modules(1);
+
+ auto fs = kernel::filesystem::devfs::filesystem{};
+ auto result = fs.mount(nullptr);
+
+ THEN("mount succeeds")
+ {
+ REQUIRE(result == kernel::filesystem::filesystem::operation_result::success);
+ REQUIRE(fs.root_inode() != nullptr);
+ }
+
+ THEN("lookup on root finds ram0 device inode")
+ {
+ auto inode = fs.lookup(fs.root_inode(), "ram0");
+ REQUIRE(inode != nullptr);
+ REQUIRE(inode->is_device());
+ }
+
+ THEN("lookup of an unknown device returns null")
+ {
+ auto inode = fs.lookup(fs.root_inode(), "ram99");
+ REQUIRE(inode == nullptr);
+ }
+
+ THEN("lookup with wrong parent returns null")
+ {
+ auto other_fs = kernel::filesystem::devfs::filesystem{};
+ other_fs.mount(nullptr);
+
+ auto inode = fs.lookup(other_fs.root_inode(), "ram0");
+ REQUIRE(inode == nullptr);
+ }
+
+ THEN("lookup with a non-directory parent returns null")
+ {
+ auto non_directory_inode = fs.lookup(fs.root_inode(), "ram0");
+ REQUIRE(non_directory_inode != nullptr);
+ REQUIRE(!non_directory_inode->is_directory());
+
+ auto result = fs.lookup(non_directory_inode, "anything");
+ REQUIRE(result == nullptr);
+ }
+ }
+
+ GIVEN("a boot module registry with three modules")
+ {
+ setup_modules(3, 2048);
+
+ auto fs = kernel::filesystem::devfs::filesystem{};
+ auto result = fs.mount(nullptr);
+ REQUIRE(result == kernel::filesystem::filesystem::operation_result::success);
+
+ THEN("lookup finds all generated RAM devices")
+ {
+ REQUIRE(fs.lookup(fs.root_inode(), "ram0") != nullptr);
+ REQUIRE(fs.lookup(fs.root_inode(), "ram16") != nullptr);
+ REQUIRE(fs.lookup(fs.root_inode(), "ram32") != nullptr);
+ }
+ }
+}
diff --git a/kernel/src/filesystem/devfs/inode.tests.cpp b/kernel/src/filesystem/devfs/inode.tests.cpp
new file mode 100644
index 0000000..50e34a7
--- /dev/null
+++ b/kernel/src/filesystem/devfs/inode.tests.cpp
@@ -0,0 +1,54 @@
+#include "kernel/filesystem/devfs/inode.hpp"
+
+#include <kstd/memory>
+#include <kstd/print>
+#include <kstd/vector>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <cstdint>
+
+SCENARIO("Devfs inode creation", "[filesystem][devfs][inode]")
+{
+ GIVEN("a devfs inode")
+ {
+ auto inode = kernel::filesystem::devfs::inode{};
+
+ THEN("the inode has the correct kind")
+ {
+ REQUIRE(inode.is_directory());
+ REQUIRE_FALSE(inode.is_device());
+ REQUIRE_FALSE(inode.is_regular());
+ }
+ }
+}
+
+SCENARIO("Devfs inode read/write", "[filesystem][devfs][inode]")
+{
+ GIVEN("a devfs inode")
+ {
+ auto inode = kernel::filesystem::devfs::inode{};
+
+ WHEN("attempting to read from the devfs inode")
+ {
+ kstd::vector<uint8_t> buffer(512);
+ auto bytes_read = inode.read(buffer.data(), 0, buffer.size());
+
+ THEN("no bytes are read")
+ {
+ REQUIRE(bytes_read == 0);
+ }
+ }
+
+ WHEN("attempting to write to the devfs inode")
+ {
+ kstd::vector<uint8_t> buffer(512);
+ auto bytes_written = inode.write(buffer.data(), 0, buffer.size());
+
+ THEN("no bytes are written")
+ {
+ REQUIRE(bytes_written == 0);
+ }
+ }
+ }
+}
diff --git a/kernel/src/filesystem/device_inode.cpp b/kernel/src/filesystem/device_inode.cpp
index af8cecc..397a0fd 100644
--- a/kernel/src/filesystem/device_inode.cpp
+++ b/kernel/src/filesystem/device_inode.cpp
@@ -1,14 +1,12 @@
#include "kernel/filesystem/device_inode.hpp"
+#include "kapi/devices/device.hpp"
#include "kapi/system.hpp"
#include "kernel/devices/block_device_utils.hpp"
-#include "kapi/devices/device.hpp"
#include "kernel/filesystem/inode.hpp"
-#include <kstd/cstring>
#include <kstd/memory>
-#include <kstd/vector>
#include <cstddef>
diff --git a/kernel/src/filesystem/device_inode.tests.cpp b/kernel/src/filesystem/device_inode.tests.cpp
n