aboutsummaryrefslogtreecommitdiff
path: root/kernel
diff options
context:
space:
mode:
authorMarcel Braun <marcel.braun@ost.ch>2026-04-12 19:15:38 +0200
committerMarcel Braun <marcel.braun@ost.ch>2026-04-12 19:15:38 +0200
commit4d2a1d028f8ba28b655026b93124e71a12562619 (patch)
treef49deef4dd3e8728fd1000b04c0908966f37663f /kernel
parent21fd1281cf19572e202d583689b99c33ec68da50 (diff)
parentcb7edbe6d4454ee5b217b522f62f4a7b92475a32 (diff)
downloadkernel-4d2a1d028f8ba28b655026b93124e71a12562619.tar.xz
kernel-4d2a1d028f8ba28b655026b93124e71a12562619.zip
Merge branch 'ext2' into 'develop-BA-FS26'
ext2 and tests See merge request teachos/kernel!22
Diffstat (limited to 'kernel')
-rw-r--r--kernel/CMakeLists.txt29
-rw-r--r--kernel/include/kernel/devices/block_device_utils.hpp29
-rw-r--r--kernel/include/kernel/filesystem/dentry.hpp57
-rw-r--r--kernel/include/kernel/filesystem/devfs/filesystem.hpp21
-rw-r--r--kernel/include/kernel/filesystem/devfs/inode.hpp22
-rw-r--r--kernel/include/kernel/filesystem/device_inode.hpp31
-rw-r--r--kernel/include/kernel/filesystem/ext2/block_group_descriptor.hpp5
-rw-r--r--kernel/include/kernel/filesystem/ext2/filesystem.hpp70
-rw-r--r--kernel/include/kernel/filesystem/ext2/inode.hpp49
-rw-r--r--kernel/include/kernel/filesystem/ext2/linked_directory_entry.hpp8
-rw-r--r--kernel/include/kernel/filesystem/ext2/superblock.hpp10
-rw-r--r--kernel/include/kernel/filesystem/file_descriptor_table.hpp34
-rw-r--r--kernel/include/kernel/filesystem/filesystem.hpp51
-rw-r--r--kernel/include/kernel/filesystem/inode.hpp47
-rw-r--r--kernel/include/kernel/filesystem/mount.hpp38
-rw-r--r--kernel/include/kernel/filesystem/mount_table.hpp35
-rw-r--r--kernel/include/kernel/filesystem/open_file_description.hpp34
-rw-r--r--kernel/include/kernel/filesystem/rootfs/filesystem.hpp21
-rw-r--r--kernel/include/kernel/filesystem/rootfs/inode.hpp34
-rw-r--r--kernel/include/kernel/filesystem/vfs.hpp52
-rw-r--r--kernel/include/kernel/test_support/boot_modules.hpp10
-rw-r--r--kernel/include/kernel/test_support/devices/block_device.hpp31
-rw-r--r--kernel/include/kernel/test_support/devices/character_device.hpp23
-rw-r--r--kernel/include/kernel/test_support/devices/storage/management.hpp10
-rw-r--r--kernel/include/kernel/test_support/filesystem/ext2.hpp17
-rw-r--r--kernel/include/kernel/test_support/filesystem/file_descriptor_table.hpp10
-rw-r--r--kernel/include/kernel/test_support/filesystem/filesystem.hpp22
-rw-r--r--kernel/include/kernel/test_support/filesystem/inode.hpp19
-rw-r--r--kernel/include/kernel/test_support/filesystem/storage_boot_module_fixture.hpp32
-rw-r--r--kernel/include/kernel/test_support/filesystem/storage_boot_module_vfs_fixture.hpp24
-rw-r--r--kernel/include/kernel/test_support/filesystem/vfs.hpp10
-rw-r--r--kernel/kapi/boot_modules.cpp19
-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
74 files changed, 3385 insertions, 175 deletions
diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt
index 74233cb..2f6113a 100644
--- a/kernel/CMakeLists.txt
+++ b/kernel/CMakeLists.txt
@@ -101,7 +101,13 @@ else()
"src/test_support/kapi/cio.cpp"
"src/test_support/kapi/interrupts.cpp"
"src/test_support/kapi/memory.cpp"
- "src/test_support/log_buffer.cpp"
+ "src/test_support/devices/block_device.cpp"
+ "src/test_support/devices/character_device.cpp"
+ "src/test_support/filesystem/inode.cpp"
+ "src/test_support/filesystem/filesystem.cpp"
+ "src/test_support/filesystem/ext2.cpp"
+ "src/test_support/filesystem/storage_boot_module_fixture.cpp"
+ "src/test_support/filesystem/storage_boot_module_vfs_fixture.cpp" "src/test_support/log_buffer.cpp"
"src/test_support/output_device.cpp"
"src/test_support/page_mapper.cpp"
"src/test_support/simulated_memory.cpp"
@@ -126,7 +132,24 @@ else()
"src/memory/bitmap_allocator.tests.cpp"
"src/memory/block_list_allocator.tests.cpp"
+ # Filesystem Subsystem Tests
+ "src/filesystem/devfs/filesystem.tests.cpp"
+ "src/filesystem/devfs/inode.tests.cpp"
+ "src/filesystem/ext2/filesystem.tests.cpp"
+ "src/filesystem/ext2/inode.tests.cpp"
+ "src/filesystem/rootfs/filesystem.tests.cpp"
+ "src/filesystem/rootfs/inode.tests.cpp"
+ "src/filesystem/dentry.tests.cpp"
+ "src/filesystem/device_inode.tests.cpp"
+ "src/filesystem/file_descriptor_table.tests.cpp"
+ "src/filesystem/mount_table.tests.cpp"
+ "src/filesystem/mount.tests.cpp"
+ "src/filesystem/open_file_description.tests.cpp"
+ "src/filesystem/vfs.tests.cpp"
+
# Storage Subsystem Tests
+ "src/devices/block_device_utils.tests.cpp"
+ "src/devices/block_device.tests.cpp"
"src/devices/storage/ram_disk/device.tests.cpp"
)
add_executable("os::kernel_tests" ALIAS "kernel_tests")
@@ -136,6 +159,10 @@ else()
"os::kernel_test_support"
)
+ target_compile_definitions("kernel_tests" PRIVATE
+ KERNEL_TEST_ASSETS_DIR="${CMAKE_CURRENT_SOURCE_DIR}/src/test_support/filesystem/test_assets"
+ )
+
set_target_properties("kernel_tests" PROPERTIES
C_CLANG_TIDY ""
CXX_CLANG_TIDY ""
diff --git a/kernel/include/kernel/devices/block_device_utils.hpp b/kernel/include/kernel/devices/block_device_utils.hpp
index 5e862ba..7b1daec 100644
--- a/kernel/include/kernel/devices/block_device_utils.hpp
+++ b/kernel/include/kernel/devices/block_device_utils.hpp
@@ -9,7 +9,34 @@
namespace kernel::devices::block_device_utils
{
- auto read(kstd::shared_ptr<kapi::devices::device> const & device, void * buffer, size_t offset, size_t size) -> size_t;
+ /**
+ @brief Utility functions for block devices, such as reading/writing data at specific offsets. These functions handle
+ the necessary logic to interact with block devices, such as calculating block boundaries and ensuring proper access
+ patterns. They abstract away the details of block device interactions, providing a simple interface for reading and
+ writing data to block devices.
+ */
+
+ /**
+ @brief Reads data from a @p device into a @p buffer, starting at a specific @p offset and for a given @p size.
+ @warning Panics if @p buffer or @p device is null.
+ @param device The block device to read from.
+ @param buffer The buffer to read data into.
+ @param offset The offset on the block device to start reading from.
+ @param size The number of bytes to read.
+ @return The number of bytes actually read, which may be less than the requested size.
+ */
+ auto read(kstd::shared_ptr<kapi::devices::device> const & device, void * buffer, size_t offset, size_t size)
+ -> size_t;
+
+ /**
+ @brief Writes data from a @p buffer to a @p device, starting at a specific @p offset and for a given @p size.
+ @warning Panics if @p buffer or @p device is null.
+ @param device The block device to write to.
+ @param buffer The buffer to write data from.
+ @param offset The offset on the block device to start writing to.
+ @param size The number of bytes to write.
+ @return The number of bytes actually written, which may be less than the requested size.
+ */
auto write(kstd::shared_ptr<kapi::devices::device> const & device, void const * buffer, size_t offset, size_t size)
-> size_t;
} // namespace kernel::devices::block_device_utils
diff --git a/kernel/include/kernel/filesystem/dentry.hpp b/kernel/include/kernel/filesystem/dentry.hpp
index fc85a7d..58a918f 100644
--- a/kernel/include/kernel/filesystem/dentry.hpp
+++ b/kernel/include/kernel/filesystem/dentry.hpp
@@ -12,23 +12,78 @@
namespace kernel::filesystem
{
+ /**
+ @brief Represents a directory entry (dentry) in the filesystem. A dentry is a node in the directory tree that
+ represents a file or directory. It contains a reference to its parent dentry, a reference to the associated real
+ filesystem inode, and a list of child dentries.
+ */
struct dentry
{
+ /**
+ @brief Flags for the dentry.
+ */
enum class dentry_flags : uint32_t
{
dcache_mounted = 1 << 15
};
- dentry(kstd::shared_ptr<dentry> const & parent, kstd::shared_ptr<inode> const & node, std::string_view name = {});
+ /**
+ @brief Create a dentry with the given @p parent, associated @p inode, and optional @p name. The dentry is
+ initialized with the provided parent and inode, and the name is stored for lookup purposes.
+ @param parent The parent dentry.
+ @param inode The associated inode for this dentry.
+ @param name The name of the dentry (optional).
+ */
+ dentry(kstd::shared_ptr<dentry> const & parent, kstd::shared_ptr<inode> const & inode, std::string_view name = {});
+ /**
+ @brief Get the associated inode.
+ @return A reference to the associated inode.
+ */
[[nodiscard]] auto get_inode() const -> kstd::shared_ptr<inode> const &;
+
+ /**
+ @brief Get the parent dentry.
+ @return A reference to the parent dentry.
+ */
[[nodiscard]] auto get_parent() const -> kstd::shared_ptr<dentry> const &;
+ /**
+ @brief Get the name of the dentry.
+ @return The name of the dentry.
+ */
+ [[nodiscard]] auto get_name() const -> std::string_view;
+
+ /**
+ @brief Add a @p child dentry.
+ @param child The child dentry to add.
+ */
auto add_child(kstd::shared_ptr<dentry> const & child) -> void;
+
+ /**
+ @brief Find a child dentry by @p name.
+ @param name The name of the child dentry to find.
+ @return A pointer to the found child dentry, or a null pointer if not found.
+ */
[[nodiscard]] auto find_child(std::string_view name) const -> kstd::shared_ptr<dentry>;
+ /**
+ @brief Set a @p flag for the dentry.
+ @param flag The flag to set.
+ */
auto set_flag(dentry_flags flag) -> void;
+
+ /**
+ @brief Unset a @p flag for the dentry.
+ @param flag The flag to unset.
+ */
auto unset_flag(dentry_flags flag) -> void;
+
+ /**
+ @brief Check if the dentry has a specific @p flag.
+ @param flag The flag to check.
+ @return True if the dentry has the flag, false otherwise.
+ */
[[nodiscard]] auto has_flag(dentry_flags flag) const -> bool;
private:
diff --git a/kernel/include/kernel/filesystem/devfs/filesystem.hpp b/kernel/include/kernel/filesystem/devfs/filesystem.hpp
index 29ae388..137eca3 100644
--- a/kernel/include/kernel/filesystem/devfs/filesystem.hpp
+++ b/kernel/include/kernel/filesystem/devfs/filesystem.hpp
@@ -2,6 +2,7 @@
#define TEACH_OS_KERNEL_FILESYSTEM_DEVFS_FILESYSTEM_HPP
#include "kapi/devices/device.hpp"
+
#include "kernel/filesystem/filesystem.hpp"
#include "kernel/filesystem/inode.hpp"
@@ -12,9 +13,27 @@
namespace kernel::filesystem::devfs
{
+ /**
+ @brief A filesystem for managing device nodes in the virtual filesystem. This filesystem provides a way to represent
+ devices as files in the /dev directory, allowing user-space 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
{
- auto mount(kstd::shared_ptr<kapi::devices::device> const & device) -> int override;
+ /**
+ @brief Initializes the devfs instance and builds the device inode table.
+ @param device Backing device passed by the generic filesystem interface (not required by devfs).
+ @return The result of the mount operation.
+ */
+ auto mount(kstd::shared_ptr<kapi::devices::device> const & device) -> operation_result override;
+
+ /**
+ @brief Looks up an inode by @p name within a @p parent directory.
+ @param parent The parent directory inode.
+ @param name The name of the inode to look up.
+ @return A pointer to the found inode, or a null pointer if not found.
+ */
auto lookup(kstd::shared_ptr<kernel::filesystem::inode> const & parent, std::string_view name)
-> kstd::shared_ptr<kernel::filesystem::inode> override;
diff --git a/kernel/include/kernel/filesystem/devfs/inode.hpp b/kernel/include/kernel/filesystem/devfs/inode.hpp
index 9c11edf..c117bd2 100644
--- a/kernel/include/kernel/filesystem/devfs/inode.hpp
+++ b/kernel/include/kernel/filesystem/devfs/inode.hpp
@@ -7,11 +7,33 @@
namespace kernel::filesystem::devfs
{
+ /**
+ @brief Inode implementation for the devfs filesystem.
+ This inode represents root device node in the /dev directory.
+ */
struct inode : kernel::filesystem::inode
{
+ /**
+ @brief Create a devfs inode. The inode is initialized with the appropriate kind (directory).
+ */
inode();
+ /**
+ @brief Reads from the devfs directory inode.
+ @param buffer Destination buffer.
+ @param offset Read offset in bytes.
+ @param size Number of bytes requested.
+ @return Number of bytes read (always 0 because this inode does not expose file data).
+ */
auto read(void * buffer, size_t offset, size_t size) const -> size_t override;
+
+ /**
+ @brief Writes to the devfs directory inode.
+ @param buffer Source buffer.
+ @param offset Write offset in bytes.
+ @param size Number of bytes requested.
+ @return Number of bytes written (always 0 because writes are not supported for this inode).
+ */
auto write(void const * buffer, size_t offset, size_t size) -> size_t override;
};
} // namespace kernel::filesystem::devfs
diff --git a/kernel/include/kernel/filesystem/device_inode.hpp b/kernel/include/kernel/filesystem/device_inode.hpp
index 18a98f5..c33be2a 100644
--- a/kernel/include/kernel/filesystem/device_inode.hpp
+++ b/kernel/include/kernel/filesystem/device_inode.hpp
@@ -2,6 +2,7 @@
#define TEACH_OS_KERNEL_FILESYSTEM_DEVICE_INODE_HPP
#include "kapi/devices/device.hpp"
+
#include "kernel/filesystem/inode.hpp"
#include <kstd/memory>
@@ -10,13 +11,43 @@
namespace kernel::filesystem
{
+ /**
+ @brief Inode implementation for device inodes in the filesystem. This inode represents a device file that provides
+ access to a device registered in the system. The device inode allows reading from and writing to the associated
+ device.
+ */
struct device_inode : inode
{
+ /**
+ @brief Create a device inode with the given @p device.
+ @param device The device to associate with the inode.
+ */
explicit device_inode(kstd::shared_ptr<kapi::devices::device> const & device);
+ /**
+ @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.
+ @param buffer The buffer to read data into.
+ @param offset The offset to read from.
+ @param size The number of bytes to read.
+ @return The number of bytes read.
+ */
auto read(void * buffer, size_t offset, size_t size) const -> size_t override;
+
+ /**
+ @brief Write data to the device inode (and in the background from the associated device) from a @p buffer, starting
+ at @p offset and writing @p size bytes.
+ @param buffer The buffer containing data to write.
+ @param offset The offset to write to.
+ @param size The number of bytes to write.
+ @return The number of bytes written.
+ */
auto write(void const * buffer, size_t offset, size_t size) -> size_t override;
+ /**
+ @brief Get the associated device.
+ @return A reference to the associated device.
+ */
[[nodiscard]] auto device() const -> kstd::shared_ptr<kapi::devices::device> const &;
private:
diff --git a/kernel/include/kernel/filesystem/ext2/block_group_descriptor.hpp b/kernel/include/kernel/filesystem/ext2/block_group_descriptor.hpp
index a23c045..7fbba3f 100644
--- a/kernel/include/kernel/filesystem/ext2/block_group_descriptor.hpp
+++ b/kernel/include/kernel/filesystem/ext2/block_group_descriptor.hpp
@@ -6,7 +6,10 @@
namespace kernel::filesystem::ext2
{
- struct block_group_descriptor
+ /**
+ @brief Represents a block group descriptor in the ext2 filesystem.
+ */
+ struct [[gnu::packed]] block_group_descriptor
{
uint32_t block_bitmap;
uint32_t inode_bitmap;
diff --git a/kernel/include/kernel/filesystem/ext2/filesystem.hpp b/kernel/include/kernel/filesystem/ext2/filesystem.hpp
index 078da31..a71385f 100644
--- a/kernel/include/kernel/filesystem/ext2/filesystem.hpp
+++ b/kernel/include/kernel/filesystem/ext2/filesystem.hpp
@@ -2,20 +2,88 @@
#define TEACH_OS_KERNEL_FILESYSTEM_EXT2_FILESYSTEM_HPP
#include "kapi/devices/device.hpp"
+
+#include "kernel/filesystem/ext2/block_group_descriptor.hpp"
+#include "kernel/filesystem/ext2/inode.hpp"
+#include "kernel/filesystem/ext2/superblock.hpp"
#include "kernel/filesystem/filesystem.hpp"
#include "kernel/filesystem/inode.hpp"
#include <kstd/memory>
+#include <kstd/vector>
+#include <cstddef>
+#include <cstdint>
#include <string_view>
namespace kernel::filesystem::ext2
{
+ /**
+ @brief Constants related to the ext2 filesystem.
+ */
+ namespace constants
+ {
+ constexpr size_t inline base_block_size = 1024;
+ constexpr size_t inline superblock_offset = base_block_size;
+ constexpr uint16_t inline magic_number = 0xEF53;
+
+ constexpr uint32_t inline root_inode_number = 2;
+
+ constexpr size_t inline direct_block_count = 12;
+ constexpr size_t inline singly_indirect_block_index = direct_block_count;
+ constexpr size_t inline doubly_indirect_block_index = singly_indirect_block_index + 1;
+ constexpr size_t inline triply_indirect_block_index = doubly_indirect_block_index + 1;
+
+ constexpr uint16_t inline mode_mask = 0xF000;
+ constexpr uint16_t inline mode_regular = 0x8000;
+ constexpr uint16_t inline mode_directory = 0x4000;
+ } // namespace constants
+
+ /**
+ @brief A filesystem implementation for the ext2 filesystem format. This class provides methods for mounting an ext2
+ filesystem, and looking up inodes.
+ */
struct filesystem : kernel::filesystem::filesystem
{
- auto mount(kstd::shared_ptr<kapi::devices::device> const & device) -> int override;
+ /**
+ @brief Initializes the ext2 filesystem with the given @p device.
+ @param device The device to mount.
+ @return The result of the mount operation.
+ */
+ auto mount(kstd::shared_ptr<kapi::devices::device> const & device) -> operation_result override;
+
+ /**
+ @brief Looks up an inode by @p name within a @p parent directory.
+ @param parent The parent directory inode.
+ @param name The name of the inode to look up.
+ @return A pointer to the found inode, or a null pointer if not found.
+ */
auto lookup(kstd::shared_ptr<kernel::filesystem::inode> const & parent, std::string_view name)
-> kstd::shared_ptr<kernel::filesystem::inode> override;
+
+ /**
+ @brief Gets the size of a block in the filesystem.
+ @return The size of a block in bytes.
+ */
+ auto get_block_size() -> size_t;
+
+ /**
+ @brief Maps an inode block index to a global block number.
+ @param inode_block_index The index of the block within the inode.
+ @param data The inode data.
+ @return The global block number.
+ */
+ auto map_inode_block_index_to_global_block_number(uint32_t inode_block_index, inode_data data) -> uint32_t;
+
+ private:
+ auto read_inode(uint32_t inode_number) -> kstd::shared_ptr<kernel::filesystem::ext2::inode>;
+ auto read_block_number_at_index(uint32_t block_number, uint32_t index) -> uint32_t;
+
+ auto get_inode_size() -> size_t;
+ auto get_inode_block_count(inode_data const & data) -> uint32_t;
+
+ superblock m_superblock{};
+ kstd::vector<block_group_descriptor> m_block_group_descriptors;
};
} // namespace kernel::filesystem::ext2
diff --git a/kernel/include/kernel/filesystem/ext2/inode.hpp b/kernel/include/kernel/filesystem/ext2/inode.hpp
index 2c27c17..a1645cd 100644
--- a/kernel/include/kernel/filesystem/ext2/inode.hpp
+++ b/kernel/include/kernel/filesystem/ext2/inode.hpp
@@ -11,13 +11,13 @@
namespace kernel::filesystem::ext2
{
- struct inode : kernel::filesystem::inode
- {
- inode();
-
- auto read(void * buffer, size_t offset, size_t size) const -> size_t override;
- auto write(void const * buffer, size_t offset, size_t size) -> size_t override;
+ struct filesystem;
+ /**
+ @brief Represents the data associated with an ext2 inode.
+ */
+ struct [[gnu::packed]] inode_data
+ {
uint16_t mode;
uint16_t uid;
uint32_t size;
@@ -30,15 +30,48 @@ namespace kernel::filesystem::ext2
uint32_t blocks;
uint32_t flags;
uint32_t osd1;
- // uint32_t block[15]; // TODO BA-FS26 really correct?
std::array<uint32_t, 15> block; // NOLINT(readability-magic-numbers)
uint32_t generation;
uint32_t file_acl;
uint32_t dir_acl;
uint32_t faddr;
- // uint8_t osd2[12]; // TODO BA-FS26 really correct?
std::array<uint8_t, 12> osd2; // NOLINT(readability-magic-numbers)
};
+
+ struct inode : kernel::filesystem::inode
+ {
+ /**
+ @brief Create an ext2 inode associated with the given filesystem.
+ */
+ explicit inode(filesystem * fs);
+
+ /**
+ @brief Reads from the ext2 inode into a @p buffer, starting at the specified @p offset and for a given @p size.
+ @param buffer Destination buffer.
+ @param offset Read offset in bytes.
+ @param size Number of bytes requested.
+ @return Number of bytes read.
+ */
+ auto read(void * buffer, size_t offset, size_t size) const -> size_t override;
+
+ /**
+ @brief Writes to the ext2 inode into a @p buffer, starting at the specified @p offset and for a given @p size.
+ @warning This method is not implemented yet and will panic if called.
+ @param buffer Source buffer.
+ @param offset Write offset in bytes.
+ @param size Number of bytes requested.
+ @return Number of bytes written.
+ */
+ auto write(void const * buffer, size_t offset, size_t size) -> size_t override;
+
+ /**
+ @brief The raw inode data as read from the disk.
+ */
+ inode_data m_data{};
+
+ private:
+ filesystem * m_filesystem;
+ };
} // namespace kernel::filesystem::ext2
#endif \ No newline at end of file
diff --git a/kernel/include/kernel/filesystem/ext2/linked_directory_entry.hpp b/kernel/include/kernel/filesystem/ext2/linked_directory_entry.hpp
index 8dd42a1..4097cbb 100644
--- a/kernel/include/kernel/filesystem/ext2/linked_directory_entry.hpp
+++ b/kernel/include/kernel/filesystem/ext2/linked_directory_entry.hpp
@@ -6,14 +6,16 @@
namespace kernel::filesystem::ext2
{
- struct linked_directory_entry
+ /**
+ @brief Represents a linked directory entry in the ext2 filesystem.
+ */
+ struct [[gnu::packed]] linked_directory_entry
{
uint32_t inode;
uint16_t rec_len;
uint8_t name_len;
uint8_t file_type;
- uint8_t pad;
- std::array<uint8_t, 255> name; // NOLINT(readability-magic-numbers)
+ std::array<char, 255> name; // NOLINT(readability-magic-numbers)
};
} // namespace kernel::filesystem::ext2
diff --git a/kernel/include/kernel/filesystem/ext2/superblock.hpp b/kernel/include/kernel/filesystem/ext2/superblock.hpp
index 8600b4c..41ad935 100644
--- a/kernel/include/kernel/filesystem/ext2/superblock.hpp
+++ b/kernel/include/kernel/filesystem/ext2/superblock.hpp
@@ -6,7 +6,10 @@
namespace kernel::filesystem::ext2
{
- struct superblock
+ /**
+ @brief Represents the superblock in the ext2 filesystem.
+ */
+ struct [[gnu::packed]] superblock
{
uint32_t inodes_count;
uint32_t blocks_count;
@@ -41,11 +44,8 @@ namespace kernel::filesystem::ext2
uint32_t feature_compat;
uint32_t feature_incompat;
uint32_t feature_ro_compat;
- // uint8_t uuid[16]; // TODO BA-FS26 really correct?
std::array<uint8_t, 16> uuid;
- // uint8_t volume_name[16]; // TODO BA-FS26 really correct?
std::array<uint8_t, 16> volume_name;
- // uint8_t last_mounted[64]; // TODO BA-FS26 really correct?
std::array<uint8_t, 64> last_mounted;
uint32_t algorithm_usage_bitmap;
@@ -55,14 +55,12 @@ namespace kernel::filesystem::ext2
uint16_t padding1;
// Journaling Support
- // uint8_t journal_uuid[16]; // TODO BA-FS26 really correct?
std::array<uint8_t, 16> journal_uuid;
uint32_t journal_inum;
uint32_t journal_dev;
uint32_t last_orphan;
// Directory Indexing Support
- // uint32_t hash_seed[4]; // TODO BA-FS26 really correct?
std::array<uint32_t, 4> hash_seed;
uint8_t def_hash_version;
std::array<uint8_t, 3> padding2;
diff --git a/kernel/include/kernel/filesystem/file_descriptor_table.hpp b/kernel/include/kernel/filesystem/file_descriptor_table.hpp
index 91e2960..5d52c19 100644
--- a/kernel/include/kernel/filesystem/file_descriptor_table.hpp
+++ b/kernel/include/kernel/filesystem/file_descriptor_table.hpp
@@ -8,15 +8,49 @@
namespace kernel::filesystem
{
+ /**
+ @brief A table for managing file descriptors in the filesystem. This class provides methods for adding, retrieving,
+ and removing open file descriptions.
+ */
struct file_descriptor_table
{
+ /**
+ @brief Initialize the global file descriptor table. This method creates the singleton instance of the file
+ descriptor table.
+ @warning Panics if called more than once.
+ */
auto static init() -> void;
+
+ /**
+ @brief Get the global file descriptor table instance.
+ @return A reference to the global file descriptor table.
+ @warning Panics if the file descriptor table has not been initialized.
+ */
auto static get() -> file_descriptor_table &;
+ /**
+ @brief Destructor for the file descriptor table.
+ */
~file_descriptor_table() = default;
+ /**
+ @brief Add a file to the descriptor table.
+ @param f The file description to add.
+ @return The file descriptor index assigned to the file, or -1 on failure.
+ */
auto add_file(kstd::shared_ptr<open_file_description> const & f) -> int;
+
+ /**
+ @brief Get a file from the descriptor table.
+ @param fd The file descriptor index to retrieve.
+ @return A pointer to the requested file description, or a null pointer if not found.
+ */
[[nodiscard]] auto get_file(int fd) const -> kstd::shared_ptr<open_file_description>;
+
+ /**
+ @brief Remove a file from the descriptor table.
+ @param fd The file descriptor index to remove.
+ */
auto remove_file(int fd) -> void;
private:
diff --git a/kernel/include/kernel/filesystem/filesystem.hpp b/kernel/include/kernel/filesystem/filesystem.hpp
index 1d86178..ef6929a 100644
--- a/kernel/include/kernel/filesystem/filesystem.hpp
+++ b/kernel/include/kernel/filesystem/filesystem.hpp
@@ -2,6 +2,7 @@
#define TEACH_OS_KERNEL_FILESYSTEM_FILESYSTEM_HPP
#include "kapi/devices/device.hpp"
+
#include "kernel/filesystem/inode.hpp"
#include <kstd/memory>
@@ -11,15 +12,63 @@
namespace kernel::filesystem
{
+ /**
+ @brief A base class for implementing filesystems in the kernel. This class provides a common interface for managing
+ files and directories within the virtual filesystem.
+ */
struct filesystem
{
+ enum class operation_result : int
+ {
+ success = 0,
+ invalid_magic_number = -1,
+ invalid_root_inode = -2,
+ unmount_failed = -3
+ };
+
+ /**
+ @brief Virtual destructor for the filesystem.
+ */
virtual ~filesystem() = default;
- virtual auto mount(kstd::shared_ptr<kapi::devices::device> const & device) -> int;
+ /**
+ @brief Probes the given @p device to determine if it contains a recognizable filesystem, and if so, mounts it and
+ returns a pointer to the mounted filesystem instance. This method iterates through known filesystem types and
+ attempts to initialize it with the device until the mount was successful or all types have been tried.
+ @param device The device to probe and mount.
+ @return A pointer to the mounted filesystem instance if successful, or a null pointer if no recognizable filesystem
+ is found on the device.
+ @warning Panics if @p device is null.
+ */
+ auto static probe_and_mount(kstd::shared_ptr<kapi::devices::device> const & device) -> kstd::shared_ptr<filesystem>;
+
+ /**
+ @brief Initializes the filesystem with the given @p device.
+ @param device The device to mount.
+ @return The result of the mount operation.
+ */
+ virtual auto mount(kstd::shared_ptr<kapi::devices::device> const & device) -> operation_result;
+
+ /**
+ @brief Looks up a child inode within the given @p parent inode with the specified @p name. This method must be
+ implemented by concrete filesystem subclasses to provide the logic for traversing the filesystem structure and
+ finding the requested inode.
+ @param parent The parent inode.
+ @param name The name of the child inode to look up.
+ @return A pointer to the requested child inode, or a null pointer if not found.
+ */
virtual auto lookup(kstd::shared_ptr<inode> const & parent, std::string_view name) -> kstd::shared_ptr<inode> = 0;
+ /**
+ @brief Returns a reference to the root inode of the filesystem.
+ */
[[nodiscard]] auto root_inode() const -> kstd::shared_ptr<inode> const &;
+ /**
+ @brief Returns a reference to the device associated with the filesystem.
+ */
+ [[nodiscard]] auto device() const -> kstd::shared_ptr<kapi::devices::device> const &;
+
protected:
kstd::shared_ptr<inode> m_root_inode{};
kstd::shared_ptr<kapi::devices::device> m_device{};
diff --git a/kernel/include/kernel/filesystem/inode.hpp b/kernel/include/kernel/filesystem/inode.hpp
index d97b5ab..5208be2 100644
--- a/kernel/include/kernel/filesystem/inode.hpp
+++ b/kernel/include/kernel/filesystem/inode.hpp
@@ -5,8 +5,14 @@
namespace kernel::filesystem
{
+ /**
+ @brief Represents an inode in the filesystem.
+ */
struct inode
{
+ /**
+ @brief Represents the kind of an inode.
+ */
enum class inode_kind
{
regular,
@@ -14,18 +20,57 @@ namespace kernel::filesystem
device
};
+ /**
+ @brief Create an inode with the given @p kind.
+ @param kind The kind of the inode.
+ */
explicit inode(inode_kind kind);
+ /**
+ @brief Virtual destructor for the inode.
+ */
virtual ~inode() = default;
+ /**
+ @brief Reads from the inode into a @p buffer, starting at the specified @p offset and for a given @p size. This
+ method must be implemented by concrete inode subclasses.
+ @param buffer Destination buffer.
+ @param offset Read offset in bytes.
+ @param size Number of bytes requested.
+ @return Number of bytes read.
+ */
virtual auto read(void * buffer, size_t offset, size_t size) const -> size_t = 0;
+
+ /**
+ @brief Writes to the inode into a @p buffer, starting at the specified @p offset and for a given @p size. This
+ method must be implemented by concrete inode subclasses.
+ @param buffer Source buffer.
+ @param offset Write offset in bytes.
+ @param size Number of bytes to write.
+ @return Number of bytes written.
+ */
virtual auto write(void const * buffer, size_t offset, size_t size) -> size_t = 0;
+ /**
+ @brief Returns whether the inode is a directory.
+ @return true if the inode is a directory, false otherwise.
+ */
[[nodiscard]] auto is_directory() const -> bool;
+
+ /**
+ @brief Returns whether the inode is a regular file.
+ @return true if the inode is a regular file, false otherwise.
+ */
[[nodiscard]] auto is_regular() const -> bool;
+
+ /**
+ @brief Returns whether the inode is a device.
+ @return true if the inode is a device, false otherwise.
+ */
[[nodiscard]] auto is_device() const -> bool;
- private:
+ // TODO BA-FS26 avoid public member
+ public:
inode_kind m_kind{inode_kind::regular};
};
} // namespace kernel::filesystem
diff --git a/kernel/include/kernel/filesystem/mount.hpp b/kernel/include/kernel/filesystem/mount.hpp
index a054750..0ac6b2f 100644
--- a/kernel/include/kernel/filesystem/mount.hpp
+++ b/kernel/include/kernel/filesystem/mount.hpp
@@ -11,22 +11,56 @@
namespace kernel::filesystem
{
+ /**
+ @brief Represents a mounted filesystem in the kernel.
+ */
struct mount
{
+ /**
+ @brief Creates a mount for the given @p filesystem at the specified @p mount_path. The @p mount_dentry represents
+ the dentry where the filesystem is mounted, and the @p root_dentry represents the root dentry of the mounted
+ filesystem.
+ @param mount_dentry The dentry where the filesystem is mounted.
+ @param root_dentry The root dentry of the mounted filesystem.
+ @param fs The filesystem instance being mounted.
+ @param mount_path The path at which the filesystem is mounted.
+ @param parent_mount The parent mount that this mount is attached beneath.
+ */
mount(kstd::shared_ptr<dentry> const & mount_dentry, kstd::shared_ptr<dentry> const & root_dentry,
- kstd::shared_ptr<filesystem> const & fs, std::string_view mount_path);
+ kstd::shared_ptr<filesystem> const & fs, std::string_view mount_path,
+ kstd::shared_ptr<mount> const & parent_mount);
- [[nodiscard]] auto get_mount_dentry() const -> kstd::shared_ptr<dentry>;
+ /**
+ @brief Get the dentry where the filesystem is mounted.
+ */
+ [[nodiscard]] auto get_mount_dentry() const -> kstd::shared_ptr<dentry> const &;
+
+ /**
+ @brief Get the root dentry of the mounted filesystem.
+ */
[[nodiscard]] auto root_dentry() const -> kstd::shared_ptr<dentry> const &;
+ /**
+ @brief Get the filesystem instance being mounted.
+ */
[[nodiscard]] auto get_filesystem() const -> kstd::shared_ptr<filesystem> const &;
+
+ /**
+ @brief Get the path at which the filesystem is mounted.
+ */
[[nodiscard]] auto get_mount_path() const -> std::string_view;
+ /**
+ @brief Get the parent mount that this mount was attached beneath.
+ */
+ [[nodiscard]] auto get_parent_mount() const -> kstd::shared_ptr<mount> const &;
+
private:
kstd::string m_mount_path;
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{};
};
} // namespace kernel::filesystem
diff --git a/kernel/include/kernel/filesystem/mount_table.hpp b/kernel/include/kernel/filesystem/mount_table.hpp
index 6dc2218..a5cdde6 100644
--- a/kernel/include/kernel/filesystem/mount_table.hpp
+++ b/kernel/include/kernel/filesystem/mount_table.hpp
@@ -10,14 +10,45 @@
namespace kernel::filesystem
{
+ /**
+ @brief A table for managing mounted filesystems in the kernel.
+ */
struct mount_table
{
- public:
- void add_mount(kstd::shared_ptr<mount>);
+ /**
+ @brief Results for mount table operations.
+ */
+ enum class operation_result : int
+ {
+ removed = 0,
+ has_child_mounts = -1,
+ mount_not_found = -2
+ };
+ /**
+ @brief Adds a mount to the table.
+ @param mount The mount to add.
+ */
+ auto add_mount(kstd::shared_ptr<mount> const & mount) -> void;
+
+ /**
+ @brief Removes the topmost mount at the given @p path.
+ @param path The mount path to remove.
+ @return The result of the removal operation.
+ */
+ [[nodiscard]] auto remove_mount(std::string_view path) -> operation_result;
+
+ /**
+ @brief Finds the mount with the longest prefix matching the given @p path. This method is used to determine which
+ mounted filesystem should handle a given path lookup.
+ @param path The path to match against the mount paths in the table.
+ @return A pointer to the mount with the longest matching prefix, or a null pointer if no mount matches the path.
+ */
[[nodiscard]] auto find_longest_prefix_mount(std::string_view path) const -> kstd::shared_ptr<mount>;
private:
+ [[nodiscard]] auto has_child_mounts(kstd::shared_ptr<mount> const & parent_mount) const -> bool;
+
kstd::vector<kstd::shared_ptr<mount>> m_mounts;
};
} // namespace kernel::filesystem
diff --git a/kernel/include/kernel/filesystem/open_file_description.hpp b/kernel/include/kernel/filesystem/open_file_description.hpp
index 45719cf..738afd4 100644
--- a/kernel/include/kernel/filesystem/open_file_description.hpp
+++ b/kernel/include/kernel/filesystem/open_file_description.hpp
@@ -9,15 +9,49 @@
namespace kernel::filesystem
{
+ /**
+ @brief Represents an open file description in the filesystem. This class encapsulates the state of an open file,
+ including a reference to the associated inode and the current file offset.
+ */
struct open_file_description
{
+ /**
+ @brief Constructs an open file description for the given @p inode.
+ @param inode The inode to associate with the open file description.
+ */
explicit open_file_description(kstd::shared_ptr<inode> const & inode);
+ /**
+ @brief Destructor for the open file description.
+ */
~open_file_description() = default;
+ /**
+ @brief Reads data from the open file description into a @p buffer, starting at the current file offset and for a
+ given
+ @p size. The file offset is advanced by the number of bytes read.
+ @param buffer The buffer to read data into.
+ @param size The number of bytes to read.
+ @return The number of bytes read.
+ */
auto read(void * buffer, size_t size) -> size_t;
+
+ /**
+ @brief Writes data to the open file description from a @p buffer, starting at the current file offset and for a
+ given
+ @p size. The file offset is advanced by the number of bytes written.
+ @param buffer The buffer to write data from.
+ @param size The number of bytes to write.
+ @return The number of bytes written.
+ */
auto write(void const * buffer, size_t size) -> size_t;
+ /**
+ @brief Returns the current file offset for this open file description.
+ @return The current file offset in bytes.
+ */
+ [[nodiscard]] auto offset() const -> size_t;
+
private:
kstd::shared_ptr<inode> m_inode;
size_t m_offset;
diff --git a/kernel/include/kernel/filesystem/rootfs/filesystem.hpp b/kernel/include/kernel/filesystem/rootfs/filesystem.hpp
index 5632d86..0155c41 100644
--- a/kernel/include/kernel/filesystem/rootfs/filesystem.hpp
+++ b/kernel/include/kernel/filesystem/rootfs/filesystem.hpp
@@ -2,6 +2,7 @@
#define TEACH_OS_KERNEL_FILESYSTEM_ROOTFS_FILESYSTEM_HPP
#include "kapi/devices/device.hpp"
+
#include "kernel/filesystem/filesystem.hpp"
#include "kernel/filesystem/inode.hpp"
@@ -13,9 +14,27 @@
namespace kernel::filesystem::rootfs
{
+ /**
+ @brief A filesystem for the root filesystem. This filesystem provides access to the root directory and its contents,
+ which are typically populated by the init process during system startup. The rootfs filesystem serves as the top-level
+ directory in the filesystem hierarchy. It is responsible for providing a stable and consistent interface to the root
+ directory.
+ */
struct filesystem : kernel::filesystem::filesystem
{
- auto mount(kstd::shared_ptr<kapi::devices::device> const & device) -> int override;
+ /**
+ @brief Initializes the rootfs filesystem with the given @p device.
+ @param device The device to mount (not required by rootfs).
+ @return The result of the mount operation.
+ */
+ auto mount(kstd::shared_ptr<kapi::devices::device> const & device) -> operation_result override;
+
+ /**
+ @brief Looks up an inode by @p name within a @p parent directory.
+ @param parent The parent directory inode.
+ @param name The name of the inode to look up.
+ @return A pointer to the found inode, or a null pointer if not found.
+ */
auto lookup(kstd::shared_ptr<kernel::filesystem::inode> const & parent, std::string_view name)
-> kstd::shared_ptr<kernel::filesystem::inode> override;
};
diff --git a/kernel/include/kernel/filesystem/rootfs/inode.hpp b/kernel/include/kernel/filesystem/rootfs/inode.hpp
index 24d3e6b..469e47a 100644
--- a/kernel/include/kernel/filesystem/rootfs/inode.hpp
+++ b/kernel/include/kernel/filesystem/rootfs/inode.hpp
@@ -13,14 +13,48 @@
namespace kernel::filesystem::rootfs
{
+ /**
+ @brief Represents an inode in the rootfs filesystem. This inode represents a directory in the root filesystem and
+ maintains a list of child inodes corresponding to files and subdirectories within the root directory. The rootfs inode
+ provides methods for reading and writing data (which are no-ops for the root directory), as well as adding and looking
+ up child inodes by name.
+ */
struct inode : kernel::filesystem::inode
{
+ /**
+ @brief Create a rootfs inode. The inode is initialized with the appropriate kind (directory).
+ */
inode();
+ /**
+ @brief Reads from the rootfs directory inode.
+ @param buffer Destination buffer.
+ @param offset Read offset in bytes.
+ @param size Number of bytes requested.
+ @return Number of bytes read (always 0 because this inode does not expose file data).
+ */
auto read(void * buffer, size_t offset, size_t size) const -> size_t override;
+
+ /**
+ @brief Writes to the rootfs directory inode.
+ @param buffer Source buffer.
+ @param offset Write offset in bytes.
+ @param size Number of bytes requested.
+ @return Number of bytes written (always 0 because writes are not supported for this inode).
+ */
auto write(void const * buffer, size_t offset, size_t size) -> size_t override;
+ /**
+ @brief Adds a child inode to the rootfs directory inode with the specified @p name.
+ @param name The name of the child inode.
+ */
auto add_child(std::string_view name) -> void;
+
+ /**
+ @brief Looks up a child inode by @p name.
+ @param name The name of the child inode to look up.
+ @return A pointer to the found child inode, or a null pointer if not found.
+ */
auto lookup_child(std::string_view name) -> kstd::shared_ptr<inode>;
private:
diff --git a/kernel/include/kernel/filesystem/vfs.hpp b/kernel/include/kernel/filesystem/vfs.hpp
index 5823a83..4dd2a83 100644
--- a/kernel/include/kernel/filesystem/vfs.hpp
+++ b/kernel/include/kernel/filesystem/vfs.hpp
@@ -12,15 +12,65 @@
namespace kernel::filesystem
{
+ /**
+ @brief The virtual filesystem (VFS) is responsible for managing mounted filesystems and providing a unified interface
+ for file operations across different filesystem types. The VFS maintains a mount table to keep track of mounted
+ filesystems and their associated mount points. It provides methods for opening files by path, which involvesresolving
+ the path to the appropriate mounted filesystem and delegating the file operation to that filesystem's implementation.
+ */
struct vfs
{
+ /**
+ @brief Results for VFS operations.
+ */
+ enum class operation_result : int
+ {
+ success = 0,
+ invalid_path = -1,
+ mount_point_not_found = -2,
+ filesystem_null = -3,
+ unmount_failed = -4
+ };
+
+ /**
+ @brief Initialize the virtual filesystem.
+ @warning Panics if the VFS has already been initialized.
+ */
auto static init() -> void;
+
+ /**
+ @brief Get the singleton instance of the virtual filesystem.
+ @return A reference to the VFS instance.
+ @warning Panics if the VFS has not been initialized yet.
+ */
auto static get() -> vfs &;
+ /**
+ @brief Destructor for the VFS.
+ */
~vfs() = default;
+ /**
+ @brief Open a file by its @p path. This method resolves the path and creates an open file description.
+ @param path The path to the file to open.
+ @return A shared pointer to the open file description or a null pointer if the file could not be opened.
+ */
auto open(std::string_view path) -> kstd::shared_ptr<open_file_description>;
- auto do_mount(std::string_view path, kstd::shared_ptr<filesystem> const & filesystem) -> int;
+
+ /**
+ @brief Mount a @p filesystem at a specific @p path.
+ @param path The path where the filesystem should be mounted.
+ @param filesystem The filesystem to mount.
+ @return The result of the mount operation.
+ */
+ auto do_mount(std::string_view path, kstd::shared_ptr<filesystem> const & filesystem) -> operation_result;
+
+ /**
+ @brief Unmount the filesystem mounted at the specified @p path.
+ @param path The path where the filesystem is mounted.
+ @return The result of the unmount operation.
+ */
+ auto unmount(std::string_view path) -> operation_result;
private:
vfs() = default;
diff --git a/kernel/include/kernel/test_support/boot_modules.hpp b/kernel/include/kernel/test_support/boot_modules.hpp
new file mode 100644
index 0000000..2343a10
--- /dev/null
+++ b/kernel/include/kernel/test_support/boot_modules.hpp
@@ -0,0 +1,10 @@
+#ifndef TEACHOS_KERNEL_TEST_SUPPORT_BOOT_MODULES_HPP
+#define TEACHOS_KERNEL_TEST_SUPPORT_BOOT_MODULES_HPP
+
+namespace kernel::tests::boot_modules
+{
+ //! Deinitialize the boot module registry.
+ auto deinit() -> void;
+} // namespace kernel::tests::boot_modules
+
+#endif \ No newline at end of file
diff --git a/kernel/include/kernel/test_support/devices/block_device.hpp b/kernel/include/kernel/test_support/devices/block_device.hpp
new file mode 100644
index 0000000..110872f
--- /dev/null
+++ b/kernel/include/kernel/test_support/devices/block_device.hpp
@@ -0,0 +1,31 @@
+#ifndef TEACHOS_KERNEL_TEST_SUPPORT_DEVICES_BLOCK_DEVICE_HPP
+#define TEACHOS_KERNEL_TEST_SUPPORT_DEVICES_BLOCK_DEVICE_HPP
+
+#include "kernel/devices/block_device.hpp"
+
+#include <kstd/string>
+#include <kstd/vector>
+
+#include <cstddef>
+#include <cstdint>
+
+namespace kernel::tests::devices
+{
+
+ struct block_device : kernel::devices::block_device
+ {
+ block_device(size_t major, size_t minor, kstd::string const & name, size_t block_size, size_t initial_size = 0);
+
+ auto init() -> bool override;
+
+ auto read_block(size_t block_index, void * buffer) const -> void override;
+ auto write_block(size_t block_index, void const * buffer) -> void override;
+
+ [[nodiscard]] auto size() const -> size_t override;
+
+ kstd::vector<uint8_t> data{};
+ };
+
+} // namespace kernel::tests::devices
+
+#endif \ No newline at end of file
diff --git a/kernel/include/kernel/test_support/devices/character_device.hpp b/kernel/include/kernel/test_support/devices/character_device.hpp
new file mode 100644
index 0000000..a106cfb
--- /dev/null
+++ b/kernel/include/kernel/test_support/devices/character_device.hpp
@@ -0,0 +1,23 @@
+#ifndef TEACHOS_KERNEL_TEST_SUPPORT_DEVICES_CHARACTER_DEVICE_HPP
+#define TEACHOS_KERNEL_TEST_SUPPORT_DEVICES_CHARACTER_DEVICE_HPP
+
+#include "kapi/devices/device.hpp"
+
+#include <kstd/string>
+#include <kstd/vector>
+
+#include <cstddef>
+
+namespace kernel::tests::devices
+{
+ // TODO fix inheritance when character devices are implemented
+ struct character_device : kapi::devices::device
+ {
+ character_device(size_t major, size_t minor, kstd::string const & name);
+
+ auto init() -> bool override;
+ };
+
+} // namespace kernel::tests::devices
+
+#endif \ No newline at end of file
diff --git a/kernel/include/kernel/test_support/devices/storage/management.hpp b/kernel/include/kernel/test_support/devices/storage/management.hpp
new file mode 100644
index 0000000..581aa91
--- /dev/null
+++ b/kernel/include/kernel/test_support/devices/storage/management.hpp
@@ -0,0 +1,10 @@
+#ifndef TEACHOS_KERNEL_TEST_SUPPORT_DEVICES_STORAGE_MANAGEMENT_HPP
+#define TEACHOS_KERNEL_TEST_SUPPORT_DEVICES_STORAGE_MANAGEMENT_HPP
+
+namespace kernel::tests::devices::storage::management
+{
+ //! Deinitialize the storage management singleton.
+ auto deinit() -> void;
+} // namespace kernel::tests::devices::storage::management
+
+#endif \ No newline at end of file
diff --git a/kernel/include/kernel/test_support/filesystem/ext2.hpp b/kernel/include/kernel/test_support/filesystem/ext2.hpp
new file mode 100644
index 0000000..edbda29
--- /dev/null
+++ b/kernel/include/kernel/test_support/filesystem/ext2.hpp
@@ -0,0 +1,17 @@
+#ifndef TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_EXT2_HPP
+#define TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_EXT2_HPP
+
+#include "kernel/test_support/devices/block_device.hpp"
+
+#include <cstddef>
+#include <cstdint>
+
+namespace kernel::tests::filesystem::ext2
+{
+ auto write_bytes(kernel::tests::devices::block_device & device, size_t offset, void const * source, size_t size)
+ -> void;
+ auto write_u32(kernel::tests::devices::block_device & device, size_t offset, uint32_t value) -> void;
+ auto setup_mock_ext2_layout(kernel::tests::devices::block_device & device) -> void;
+} // namespace kernel::tests::filesystem::ext2
+
+#endif \ No newline at end of file
diff --git a/kernel/include/kernel/test_support/filesystem/file_descriptor_table.hpp b/kernel/include/kernel/test_support/filesystem/file_descriptor_table.hpp
new file mode 100644
index 0000000..bbc0f4a
--- /dev/null
+++ b/kernel/include/kernel/test_support/filesystem/file_descriptor_table.hpp
@@ -0,0 +1,10 @@
+#ifndef TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_FILE_DESCRIPTOR_TABLE_HPP
+#define TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_FILE_DESCRIPTOR_TABLE_HPP
+
+namespace kernel::tests::filesystem::file_descriptor_table
+{
+ //! Deinitialize the file descriptor table singleton.
+ auto deinit() -> void;
+} // namespace kernel::tests::filesystem::file_descriptor_table
+
+#endif \ No newline at end of file
diff --git a/kernel/include/kernel/test_support/filesystem/filesystem.hpp b/kernel/include/kernel/test_support/filesystem/filesystem.hpp
new file mode 100644
index 0000000..13aade4
--- /dev/null
+++ b/kernel/include/kernel/test_support/filesystem/filesystem.hpp
@@ -0,0 +1,22 @@
+#ifndef TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_FILESYSTEM_HPP
+#define TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_FILESYSTEM_HPP
+
+#include "kernel/filesystem/filesystem.hpp"
+#include "kernel/filesystem/inode.hpp"
+
+#include <kstd/memory>
+
+#include <string_view>
+
+namespace kernel::tests::filesystem
+{
+ struct filesystem : kernel::filesystem::filesystem
+ {
+ filesystem() = default;
+
+ auto lookup(kstd::shared_ptr<kernel::filesystem::inode> const & parent, std::string_view name)
+ -> kstd::shared_ptr<kernel::filesystem::inode> override;
+ };
+} // namespace kernel::tests::filesystem
+
+#endif
diff --git a/kernel/include/kernel/test_support/filesystem/inode.hpp b/kernel/include/kernel/test_support/filesystem/inode.hpp
new file mode 100644
index 0000000..6568f24
--- /dev/null
+++ b/kernel/include/kernel/test_support/filesystem/inode.hpp
@@ -0,0 +1,19 @@
+#ifndef TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_INODE_HPP
+#define TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_INODE_HPP
+
+#include "kernel/filesystem/inode.hpp"
+
+#include <cstddef>
+
+namespace kernel::tests::filesystem
+{
+ struct inode : kernel::filesystem::inode
+ {
+ inode();
+
+ auto read(void * buffer, size_t offset, size_t size) const -> size_t override;
+ auto write(void const * buffer, size_t offset, size_t size) -> size_t override;
+ };
+} // namespace kernel::tests::filesystem
+
+#endif
diff --git a/kernel/include/kernel/test_support/filesystem/storage_boot_module_fixture.hpp b/kernel/include/kernel/test_support/filesystem/storage_boot_module_fixture.hpp
new file mode 100644
index 0000000..ee658e2
--- /dev/null
+++ b/kernel/include/kernel/test_support/filesystem/storage_boot_module_fixture.hpp
@@ -0,0 +1,32 @@
+#ifndef TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_STORAGE_BOOT_MODULE_FIXTURE_HPP
+#define TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_STORAGE_BOOT_MODULE_FIXTURE_HPP
+
+#include "kapi/boot_module/boot_module_registry.hpp"
+
+#include <kstd/string>
+#include <kstd/vector>
+
+#include <cstddef>
+#include <filesystem>
+
+namespace kernel::tests::filesystem
+{
+ struct storage_boot_module_fixture
+ {
+ ~storage_boot_module_fixture();
+
+ auto setup_modules(std::size_t module_count, std::size_t module_size = 4096) -> void;
+ auto setup_modules_from_img(kstd::vector<kstd::string> const & module_names,
+ kstd::vector<std::filesystem::path> const & img_paths) -> void;
+
+ protected:
+ kapi::boot_modules::boot_module_registry m_registry{};
+ kstd::vector<kstd::string> m_module_names{};
+ kstd::vector<kstd::vector<std::byte>> m_module_data{};
+
+ private:
+ auto setup_module_from_img(kstd::string const & module_name, std::filesystem::path const & img_path) -> void;
+ };
+} // namespace kernel::tests::filesystem
+
+#endif \ No newline at end of file
diff --git a/kernel/include/kernel/test_support/filesystem/storage_boot_module_vfs_fixture.hpp b/kernel/include/kernel/test_support/filesystem/storage_boot_module_vfs_fixture.hpp
new file mode 100644
index 0000000..98012b0
--- /dev/null
+++ b/kernel/include/kernel/test_support/filesystem/storage_boot_module_vfs_fixture.hpp
@@ -0,0 +1,24 @@
+#ifndef TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_STORAGE_BOOT_MODULE_VFS_FIXTURE_HPP
+#define TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_STORAGE_BOOT_MODULE_VFS_FIXTURE_HPP
+
+#include "kernel/test_support/filesystem/storage_boot_module_fixture.hpp"
+
+#include <kstd/string>
+#include <kstd/vector>
+
+#include <cstddef>
+#include <filesystem>
+
+namespace kernel::tests::filesystem
+{
+ struct storage_boot_module_vfs_fixture : storage_boot_module_fixture
+ {
+ ~storage_boot_module_vfs_fixture();
+
+ auto setup_modules_and_init_vfs(std::size_t module_count, std::size_t module_size = 4096) -> void;
+ auto setup_modules_from_img_and_init_vfs(kstd::vector<kstd::string> const & module_names,
+ kstd::vector<std::filesystem::path> const & img_paths) -> void;
+ };
+} // namespace kernel::tests::filesystem
+
+#endif \ No newline at end of file
diff --git a/kernel/include/kernel/test_support/filesystem/vfs.hpp b/kernel/include/kernel/test_support/filesystem/vfs.hpp
new file mode 100644
index 0000000..739e353
--- /dev/null
+++ b/kernel/include/kernel/test_support/filesystem/vfs.hpp
@@ -0,0 +1,10 @@
+#ifndef TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_VFS_HPP
+#define TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_VFS_HPP
+
+namespace kernel::tests::filesystem::vfs
+{
+ //! Deinitialize the VFS singleton.
+ auto deinit() -> void;
+} // namespace kernel::tests::filesystem::vfs
+
+#endif \ No newline at end of file
diff --git a/kernel/kapi/boot_modules.cpp b/kernel/kapi/boot_modules.cpp
index 5a0ef7f..0549368 100644
--- a/kernel/kapi/boot_modules.cpp
+++ b/kernel/kapi/boot_modules.cpp
@@ -4,14 +4,13 @@
#include <optional>
-namespace kapi::boot_modules
+namespace
{
+ constinit auto static registry = std::optional<kapi::boot_modules::boot_module_registry>{};
+} // namespace
- namespace
- {
- constinit auto static registry = std::optional<kapi::boot_modules::boot_module_registry>{};
- } // namespace
-
+namespace kapi::boot_modules
+{
auto set_boot_module_registry(boot_module_registry & new_registry) -> void
{
if (registry)
@@ -32,3 +31,11 @@ namespace kapi::boot_modules
return *registry;
}
} // namespace kapi::boot_modules
+
+namespace kernel::tests::boot_modules
+{
+ auto deinit() -> void
+ {
+ registry.reset();
+ }
+} // namespace kernel::tests::boot_modules
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
new file mode 100644
index 0000000..4e31812
--- /dev/null
+++ b/kernel/src/filesystem/device_inode.tests.cpp
@@ -0,0 +1,108 @@
+#include "kernel/filesystem/device_inode.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("Device inode construction", "[filesystem][device_inode]")
+{
+ GIVEN("a block device")
+ {
+ auto device = kstd::make_shared<kernel::tests::devices::block_device>(0, 0, "test_block_device", 512, 3 * 512);
+
+ WHEN("constructing a device inode with the block device")
+ {
+ auto inode = kernel::filesystem::device_inode{device};
+
+ THEN("the device inode has the correct device")
+ {
+ REQUIRE(inode.device() == device);
+ }
+
+ THEN("the device inode has the correct kind")
+ {
+ REQUIRE(inode.is_device());
+ REQUIRE_FALSE(inode.is_directory());
+ REQUIRE_FALSE(inode.is_regular());
+ }
+ }
+
+ WHEN("constructing a device inode with a null device")
+ {
+ THEN("the constructor panics")
+ {
+ REQUIRE_THROWS_AS((kernel::filesystem::device_inode{nullptr}), kernel::tests::cpu::halt);
+ }
+ }
+ }
+}
+
+SCENARIO("Device inode read/write", "[filesystem][device_inode]")
+{
+ GIVEN("a block device and a device inode for that device")
+ {
+ auto device = kstd::make_shared<kernel::tests::devices::block_device>(0, 0, "test_block_device", 512, 3 * 512);
+ auto inode = kernel::filesystem::device_inode{device};
+
+ WHEN("writing to the device inode")
+ {
+ kstd::vector<uint8_t> write_buffer(1024);
+ for (size_t i = 0; i < write_buffer.size(); ++i)
+ {
+ write_buffer[i] = static_cast<uint8_t>(i % 256);
+ }
+
+ auto bytes_written = inode.write(write_buffer.data(), 256, write_buffer.size());
+
+ THEN("the correct number of bytes is written")
+ {
+ REQUIRE(bytes_written == 1024);
+ }
+
+ THEN("the data written matches the data read back from the device inode")
+ {
+ kstd::vector<uint8_t> read_buffer(1024);
+ auto bytes_read = inode.read(read_buffer.data(), 256, read_buffer.size());
+
+ REQUIRE(bytes_read == write_buffer.size());
+ REQUIRE(read_buffer == write_buffer);
+ }
+ }
+ }
+}
+
+SCENARIO("Device inode read/write with a non-block device", "[filesystem][device_inode]")
+{
+ GIVEN("a non-block device and a device inode for that device")
+ {
+ auto device = kstd::make_shared<kernel::tests::devices::character_device>(0, 0, "test_character_device");
+ auto inode = kernel::filesystem::device_inode{device};
+
+ WHEN("reading from the device inode")
+ {
+ kstd::vector<uint8_t> read_buffer(512);
+ THEN("the system panics")
+ {
+ REQUIRE_THROWS_AS(inode.read(read_buffer.data(), 0, read_buffer.size()), kernel::tests::cpu::halt);
+ }
+ }
+
+ WHEN("writing to the device inode")
+ {
+ kstd::vector<uint8_t> write_buffer(512);
+ THEN("the system panics")
+ {
+ REQUIRE_THROWS_AS(inode.write(write_buffer.data(), 0, write_buffer.size()), kernel::tests::cpu::halt);
+ }
+ }
+ }
+}
diff --git a/kernel/src/filesystem/ext2/filesystem.cpp b/kernel/src/filesystem/ext2/filesystem.cpp
index eb9edc4..0ad5c97 100644
--- a/kernel/src/filesystem/ext2/filesystem.cpp
+++ b/kernel/src/filesystem/ext2/filesystem.cpp
@@ -1,13 +1,17 @@
#include "kernel/filesystem/ext2/filesystem.hpp"
-#include "kernel/devices/block_device_utils.hpp"
#include "kapi/devices/device.hpp"
+
+#include "kernel/devices/block_device_utils.hpp"
+#include "kernel/filesystem/ext2/block_group_descriptor.hpp"
#include "kernel/filesystem/ext2/inode.hpp"
+#include "kernel/filesystem/ext2/linked_directory_entry.hpp"
#include "kernel/filesystem/ext2/superblock.hpp"
#include "kernel/filesystem/filesystem.hpp"
#include "kernel/filesystem/inode.hpp"
#include <kstd/memory>
+#include <kstd/vector>
#include <cstddef>
#include <cstdint>
@@ -17,56 +21,211 @@ namespace kernel::filesystem::ext2
{
namespace
{
- // constexpr size_t SUPERBLOCK_OFFSET = 1024;
- // constexpr uint16_t EXT2_MAGIC = 0xEF53;
-
- // // Mode bits
- // constexpr uint16_t S_IFMT = 0xF000;
- // constexpr uint16_t S_IFREG = 0x8000;
- // constexpr uint16_t S_IFDIR = 0x4000;
-
- // auto S_ISREG(uint16_t mode) -> bool
- // {
- // return (mode & S_IFMT) == S_IFREG;
- // }
- // auto S_ISDIR(uint16_t mode) -> bool
- // {
- // return (mode & S_IFMT) == S_IFDIR;
- // }
-
- // auto get_block_size(superblock const & superblock) -> size_t
- // {
- // return 1024U << superblock.log_block_size;
- // }
-
- // auto get_inode_size(superblock const & superblock) -> size_t
- // {
- // return superblock.rev_level == 0 ? 128 : superblock.inode_size;
- // }
+ auto S_ISREG(uint16_t mode) -> bool
+ {
+ return (mode & constants::mode_mask) == constants::mode_regular;
+ }
+
+ auto S_ISDIR(uint16_t mode) -> bool
+ {
+ return (mode & constants::mode_mask) == constants::mode_directory;
+ }
} // namespace
- auto filesystem::mount(kstd::shared_ptr<kapi::devices::device> const & device) -> int
+ auto filesystem::mount(kstd::shared_ptr<kapi::devices::device> const & device) -> operation_result
{
- kernel::filesystem::filesystem::mount(device); // TODO BA-FS26 error handling?
- // TODO BA-FS26 load proper root inode from ext2 metadata
- // m_root_inode = inode{inode_kind::directory};
-
- // TODO BA-FS26 implement
- m_root_inode = kstd::make_shared<inode>();
- // devices::block_device_utils::read(device, nullptr, 0, 0); // TODO BA-FS26 just for testing
- return 0;
+ kernel::filesystem::filesystem::mount(device);
+
+ kernel::devices::block_device_utils::read(m_device, &m_superblock, constants::superblock_offset,
+ sizeof(m_superblock));
+
+ if (m_superblock.magic != constants::magic_number)
+ {
+ return operation_result::invalid_magic_number;
+ }
+
+ auto const block_size = get_block_size();
+ 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;
+
+ m_block_group_descriptors = kstd::vector<block_group_descriptor>(num_block_groups);
+
+ auto const block_group_descriptor_table_offset = block_size == 1024 ? 2 * block_size : block_size;
+ kernel::devices::block_device_utils::read(m_device, m_block_group_descriptors.data(),
+ block_group_descriptor_table_offset,
+ num_block_groups * sizeof(block_group_descriptor));
+
+ m_root_inode = read_inode(constants::root_inode_number);
+
+ if (!m_root_inode || !m_root_inode->is_directory())
+ {
+ return operation_result::invalid_root_inode;
+ }
+ return operation_result::success;
}
- auto filesystem::lookup(kstd::shared_ptr<kernel::filesystem::inode> const & /*parent*/, std::string_view name)
+ auto filesystem::lookup(kstd::shared_ptr<kernel::filesystem::inode> const & parent, std::string_view name)
-> kstd::shared_ptr<kernel::filesystem::inode>
{
- // TODO BA-FS26 implement ext2 directory traversal and inode loading
- if (name == "dev")
+ if (!parent || !parent->is_directory())
+ {
+ return nullptr;
+ }
+
+ auto * ext2_parent = static_cast<inode *>(parent.get());
+ if (!ext2_parent)
+ {
+ return nullptr;
+ }
+
+ auto const block_size = get_block_size();
+ auto const & inode_data = ext2_parent->m_data;
+ kstd::vector<uint8_t> buffer(block_size);
+
+ for (uint32_t i = 0; i < get_inode_block_count(inode_data); ++i)
+ {
+ auto const global_block_number = map_inode_block_index_to_global_block_number(i, inode_data);
+ auto const block_offset = global_block_number * block_size;
+ kernel::devices::block_device_utils::read(m_device, buffer.data(), block_offset, block_size);
+
+ auto const * entry = reinterpret_cast<linked_directory_entry const *>(buffer.data());
+ auto bytes_read = 0uz;
+
+ while (bytes_read < block_size && entry->inode != 0)
+ {
+ auto const entry_name = std::string_view{entry->name.data(), entry->name_len};
+ if (entry_name == name)
+ {
+ return read_inode(entry->inode);
+ }
+
+ bytes_read += entry->rec_len;
+ entry = reinterpret_cast<linked_directory_entry const *>(buffer.data() + bytes_read);
+ }
+ }
+
+ return nullptr;
+ }
+
+ auto filesystem::read_inode(uint32_t inode_number) -> kstd::shared_ptr<inode>
+ {
+ auto const block_size = get_block_size();
+ auto const inodes_per_group = m_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())
{
- // TODO BA-FS26 just for testing
return nullptr;
}
- return kstd::make_shared<inode>();
+ auto const & block_group_descriptor = m_block_group_descriptors.at(block_group_index);
+ auto const inode_table_start_block = block_group_descriptor.inode_table;
+ auto const inode_table_offset = static_cast<size_t>(inode_table_start_block) * block_size;
+ auto const inode_offset = inode_table_offset + inode_index_within_group * get_inode_size();
+
+ auto new_inode = kstd::make_shared<inode>(this);
+ kernel::devices::block_device_utils::read(m_device, &new_inode->m_data, inode_offset, sizeof(inode_data));
+
+ // TODO BA-FS26 improve inode_kind really needed? or just map it to the mode bits?
+ if (S_ISREG(new_inode->m_data.mode))
+ {
+ new_inode->m_kind = inode::inode_kind::regular;
+ }
+ else if (S_ISDIR(new_inode->m_data.mode))
+ {
+ new_inode->m_kind = inode::inode_kind::directory;
+ }
+ else
+ {
+ // TODO BA-FS26 really correct??
+ return nullptr;
+ }
+
+ return new_inode;
+ }
+
+ auto filesystem::map_inode_block_index_to_global_block_number(uint32_t inode_block_index, inode_data data) -> uint32_t
+ {
+ if (inode_block_index < constants::direct_block_count)
+ {
+ return data.block.at(inode_block_index);
+ }
+ inode_block_index -= constants::direct_block_count;
+
+ auto const block_size = get_block_size();
+ auto const numbers_per_block = block_size / sizeof(uint32_t);
+
+ auto const block_numbers_per_singly_indirect_block = numbers_per_block;
+ auto const block_numbers_per_doubly_indirect_block = numbers_per_block * block_numbers_per_singly_indirect_block;
+ auto const block_numbers_per_triply_indirect_block = numbers_per_block * block_numbers_per_doubly_indirect_block;
+
+ if (inode_block_index < block_numbers_per_singly_indirect_block)
+ {
+ auto const singly_indirect_block_number = data.block.at(constants::singly_indirect_block_index);
+ return read_block_number_at_index(singly_indirect_block_number, inode_block_index);
+ }
+ inode_block_index -= block_numbers_per_singly_indirect_block;
+
+ if (inode_block_index < block_numbers_per_doubly_indirect_block)
+ {
+ auto const doubly_indirect_block_number = data.block.at(constants::doubly_indirect_block_index);
+ auto const singly_indirect_block_index_in_doubly_indirect_block =
+ inode_block_index / block_numbers_per_singly_indirect_block;
+ auto const singly_indirect_block_number = read_block_number_at_index(
+ doubly_indirect_block_number, singly_indirect_block_index_in_doubly_indirect_block);
+
+ auto const block_index_in_singly_indirect_block = inode_block_index % block_numbers_per_singly_indirect_block;
+ return read_block_number_at_index(singly_indirect_block_number, block_index_in_singly_indirect_block);
+ }
+ inode_block_index -= block_numbers_per_doubly_indirect_block;
+
+ if (inode_block_index < block_numbers_per_triply_indirect_block)
+ {
+ auto const triply_indirect_block_number = data.block.at(constants::triply_indirect_block_index);
+ auto const doubly_indirect_block_index_in_triply_indirect_block =
+ inode_block_index / block_numbers_per_doubly_indirect_block;
+ auto const doubly_indirect_block_number = read_block_number_at_index(
+ triply_indirect_block_number, doubly_indirect_block_index_in_triply_indirect_block);
+
+ auto const remaining_block_numbers = inode_block_index % block_numbers_per_doubly_indirect_block;
+
+ auto const singly_indirect_block_index_in_doubly_indirect_block =
+ remaining_block_numbers / block_numbers_per_singly_indirect_block;
+ auto const singly_indirect_block_number = read_block_number_at_index(
+ doubly_indirect_block_number, singly_indirect_block_index_in_doubly_indirect_block);
+
+ auto const block_index_in_singly_indirect_block =
+ remaining_block_numbers % block_numbers_per_singly_indirect_block;
+ return read_block_number_at_index(singly_indirect_block_number, block_index_in_singly_indirect_block);
+ }
+
+ return 0; // TODO BA-FS26 really correct??
+ }
+
+ auto filesystem::read_block_number_at_index(uint32_t block_number, uint32_t index) -> uint32_t
+ {
+ uint32_t block_number_buffer = 0;
+
+ auto const block_start_offset = block_number * get_block_size();
+ auto const number_start_address = block_start_offset + index * sizeof(uint32_t);
+ kernel::devices::block_device_utils::read(m_device, &block_number_buffer, number_start_address, sizeof(uint32_t));
+
+ return block_number_buffer;
+ }
+
+ auto filesystem::get_block_size() -> size_t
+ {
+ return constants::base_block_size << m_superblock.log_block_size;
+ }
+
+ auto filesystem::get_inode_size() -> size_t
+ {
+ return m_superblock.rev_level == 0 ? 128 : m_superblock.inode_size;
+ }
+
+ auto filesystem::get_inode_block_count(inode_data const & data) -> uint32_t
+ {
+ return data.blocks / (2 << m_superblock.log_block_size);
}
} // namespace kernel::filesystem::ext2
diff --git a/kernel/src/filesystem/ext2/filesystem.tests.cpp b/kernel/src/filesystem/ext2/filesystem.tests.cpp
new file mode 100644
index 0000000..b13ebf3
--- /dev/null
+++ b/kernel/src/filesystem/ext2/filesystem.tests.cpp
@@ -0,0 +1,132 @@
+#include "kernel/filesystem/ext2/filesystem.hpp"
+
+#include "kernel/devices/storage/management.hpp"
+#include "kernel/filesystem/ext2/inode.hpp"
+#include "kernel/filesystem/filesystem.hpp"
+#include "kernel/test_support/devices/block_device.hpp"
+#include "kernel/test_support/filesystem/ext2.hpp"
+#include "kernel/test_support/filesystem/storage_boot_module_fixture.hpp"
+
+#include <kstd/memory>
+#include <kstd/vector>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <array>
+#include <cstdint>
+#include <filesystem>
+
+SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture,
+ "Ext2 filesystem mount and lookup with real image", "[filesystem][ext2][filesystem][img]")
+{
+ auto const image_path = std::filesystem::path{KERNEL_TEST_ASSETS_DIR} / "ext2_1KB_fs.img";
+
+ GIVEN("a mounted ext2 filesystem from a real image")
+ {
+ REQUIRE(std::filesystem::exists(image_path));
+ REQUIRE_NOTHROW(setup_modules_from_img({"test_img_module"}, {image_path}));
+
+ auto boot_device = kernel::devices::storage::management::get().determine_boot_device();
+ REQUIRE(boot_device != nullptr);
+
+ auto fs = kernel::filesystem::ext2::filesystem{};
+ REQUIRE(fs.mount(boot_device) == kernel::filesystem::filesystem::operation_result::success);
+
+ THEN("the root inode is available and is a directory")
+ {
+ REQUIRE(fs.root_inode() != nullptr);
+ REQUIRE(fs.root_inode()->is_directory());
+ }
+
+ THEN("lookup resolves known entries from the image")
+ {
+ auto information = fs.lookup(fs.root_inode(), "information");
+ REQUIRE(information != nullptr);
+ REQUIRE(information->is_directory());
+
+ auto info_1 = fs.lookup(information, "info_1.txt");
+ REQUIRE(info_1 != nullptr);
+ REQUIRE(info_1->is_regular());
+ }
+
+ THEN("lookup returns null for invalid inputs")
+ {
+ REQUIRE(fs.lookup(nullptr, "information") == nullptr);
+
+ auto information = fs.lookup(fs.root_inode(), "information");
+ REQUIRE(information != nullptr);
+ auto info_1 = fs.lookup(information, "info_1.txt");
+ REQUIRE(info_1 != nullptr);
+
+ REQUIRE(fs.lookup(info_1, "anything") == nullptr);
+ REQUIRE(fs.lookup(fs.root_inode(), "does_not_exist") == nullptr);
+ }
+ }
+}
+
+SCENARIO("Ext2 filesystem rejects invalid magic", "[filesystem][ext2][filesystem]")
+{
+ auto const block_size = 1024;
+ GIVEN("a block device that does not contain an ext2 superblock")
+ {
+ auto device = kstd::make_shared<kernel::tests::devices::block_device>(0, 0, "mock", block_size, 2 * block_size);
+ REQUIRE(device != nullptr);
+
+ auto fs = kernel::filesystem::ext2::filesystem{};
+
+ THEN("mount fails with invalid_magic_number")
+ {
+ REQUIRE(fs.mount(device) == kernel::filesystem::filesystem::operation_result::invalid_magic_number);
+ }
+ }
+}
+
+SCENARIO("Ext2 block mapping includes direct and all indirect levels", "[filesystem][ext2][filesystem]")
+{
+ auto const block_size = 1024;
+
+ GIVEN("a minimally valid ext2 layout with configured indirect block tables")
+ {
+ auto device = kstd::make_shared<kernel::tests::devices::block_device>(0, 0, "mock", block_size, 128 * block_size);
+ REQUIRE(device != nullptr);
+
+ kernel::tests::filesystem::ext2::setup_mock_ext2_layout(*device);
+
+ auto fs = kernel::filesystem::ext2::filesystem{};
+ REQUIRE(fs.mount(device) == kernel::filesystem::filesystem::operation_result::success);
+
+ auto inode_data = kernel::filesystem::ext2::inode_data{};
+ inode_data.block[0] = 7;
+
+ inode_data.block[12] = 30;
+ kernel::tests::filesystem::ext2::write_u32(*device, 30 * block_size, 31);
+
+ inode_data.block[13] = 40;
+ kernel::tests::filesystem::ext2::write_u32(*device, 40 * block_size, 41);
+ kernel::tests::filesystem::ext2::write_u32(*device, 41 * block_size, 42);
+
+ inode_data.block[14] = 50;
+ kernel::tests::filesystem::ext2::write_u32(*device, 50 * block_size, 51);
+ kernel::tests::filesystem::ext2::write_u32(*device, 51 * block_size, 52);
+ kernel::tests::filesystem::ext2::write_u32(*device, 52 * block_size, 53);
+
+ auto const numbers_per_block = static_cast<uint32_t>(block_size / sizeof(uint32_t));
+ auto const singly_start = static_cast<uint32_t>(kernel::filesystem::ext2::constants::direct_block_count);
+ auto const doubly_start = singly_start + numbers_per_block;
+ auto const triply_start = doubly_start + numbers_per_block * numbers_per_block;
+
+ 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);
+ }
+
+ THEN("mapping returns zero 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) == 0);
+ }
+ }
+}
diff --git a/kernel/src/filesystem/ext2/inode.cpp b/kernel/src/filesystem/ext2/inode.cpp
index b75969a..a29bb3b 100644
--- a/kernel/src/filesystem/ext2/inode.cpp
+++ b/kernel/src/filesystem/ext2/inode.cpp
@@ -1,24 +1,59 @@
#include "kernel/filesystem/ext2/inode.hpp"
+#include "kapi/system.hpp"
+
+#include "kernel/devices/block_device_utils.hpp"
+#include "kernel/filesystem/ext2/filesystem.hpp"
#include "kernel/filesystem/inode.hpp"
+#include <algorithm>
#include <cstddef>
+#include <cstdint>
namespace kernel::filesystem::ext2
{
- inode::inode()
+ inode::inode(filesystem * fs)
: kernel::filesystem::inode(inode_kind::regular)
- {}
+ , m_filesystem(fs)
+ {
+ if (!m_filesystem)
+ {
+ kapi::system::panic("[EXT2] ext2::inode constructed with filesystem null pointer");
+ }
+ }
- auto inode::read(void * /*buffer*/, size_t /*offset*/, size_t /*size*/) const -> size_t
+ auto inode::read(void * buffer, size_t offset, size_t size) const -> size_t
{
- // TODO BA-FS26 implement
- return 0;
+ auto block_index = offset / m_filesystem->get_block_size();
+ auto in_block_offset = offset % m_filesystem->get_block_size();
+
+ auto bytes_read = 0uz;
+
+ while (bytes_read < size)
+ {
+ auto const block_number = m_filesystem->map_inode_block_index_to_global_block_number(block_index, m_data);
+ if (block_number == 0) // TODO BA-FS26 really correct?
+ {
+ break;
+ }
+
+ auto const block_start_offset = block_number * m_filesystem->get_block_size();
+ auto const read_offset = block_start_offset + in_block_offset;
+ auto const bytes_to_read = std::min(size - bytes_read, m_filesystem->get_block_size() - in_block_offset);
+
+ bytes_read += kernel::devices::block_device_utils::read(
+ m_filesystem->device(), static_cast<uint8_t *>(buffer) + bytes_read, read_offset, bytes_to_read);
+
+ block_index++;
+ in_block_offset = 0; // After the first block, we always start at the beginning of the block
+ }
+
+ return bytes_read;
}
auto inode::write(void const * /*buffer*/, size_t /*offset*/, size_t /*size*/) -> size_t
{
- // TODO BA-FS26 implement
+ kapi::system::panic("[EXT2] inode::write is not implemented yet");
return 0;
}
} // namespace kernel::filesystem::ext2 \ No newline at end of file
diff --git a/kernel/src/filesystem/ext2/inode.tests.cpp b/kernel/src/filesystem/ext2/inode.tests.cpp
new file mode 100644
index 0000000..795ff10
--- /dev/null
+++ b/kernel/src/filesystem/ext2/inode.tests.cpp
@@ -0,0 +1,159 @@
+#include "kernel/filesystem/ext2/inode.hpp"
+
+#include "kernel/devices/storage/management.hpp"
+#include "kernel/filesystem/ext2/filesystem.hpp"
+#include "kernel/filesystem/filesystem.hpp"
+#include "kernel/test_support/cpu.hpp"
+#include "kernel/test_support/devices/block_device.hpp"
+#include "kernel/test_support/filesystem/ext2.hpp"
+#include "kernel/test_support/filesystem/storage_boot_module_fixture.hpp"
+
+#include <kstd/memory>
+#include <kstd/vector>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <cstddef>
+#include <filesystem>
+#include <string_view>
+
+SCENARIO("Ext2 inode initialization and properties", "[filesystem][ext2][inode]")
+{
+ GIVEN("an ext2 filesystem")
+ {
+ auto fs = kernel::filesystem::ext2::filesystem{};
+
+ THEN("the inode is initialized and has the kind regular")
+ {
+ auto inode = kernel::filesystem::ext2::inode{&fs};
+ REQUIRE(inode.is_regular());
+ REQUIRE(!inode.is_directory());
+ REQUIRE(!inode.is_device());
+ }
+ }
+
+ GIVEN("no filesystem (null pointer)")
+ {
+ THEN("constructing an inode with a null filesystem pointer panics")
+ {
+ REQUIRE_THROWS_AS(kernel::filesystem::ext2::inode{nullptr}, kernel::tests::cpu::halt);
+ }
+ }
+}
+
+SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, "Ext2 inode reads from real image",
+ "[filesystem][ext2][inode][img]")
+{
+ auto const image_path = std::filesystem::path{KERNEL_TEST_ASSETS_DIR} / "ext2_1KB_fs.img";
+
+ GIVEN("a mounted ext2 filesystem and a regular file inode")
+ {
+ REQUIRE(std::filesystem::exists(image_path));
+ REQUIRE_NOTHROW(setup_modules_from_img({"test_img_module"}, {image_path}));
+
+ auto boot_device = kernel::devices::storage::management::get().determine_boot_device();
+ REQUIRE(boot_device != nullptr);
+
+ auto fs = kernel::filesystem::ext2::filesystem{};
+ REQUIRE(fs.mount(boot_device) == kernel::filesystem::filesystem::operation_result::success);
+
+ auto information = fs.lookup(fs.root_inode(), "information");
+ REQUIRE(information != nullptr);
+ auto file = fs.lookup(information, "info_1.txt");
+ REQUIRE(file != nullptr);
+ REQUIRE(file->is_regular());
+
+ THEN("reading from offset zero returns expected file prefix")
+ {
+ auto buffer = kstd::vector<std::byte>(6);
+ auto const bytes_read = file->read(buffer.data(), 0, buffer.size());
+
+ REQUIRE(bytes_read == 6);
+
+ auto const text = std::string_view{reinterpret_cast<char const *>(buffer.data()), bytes_read};
+ REQUIRE(text == "info_1");
+ }
+
+ THEN("reading with an offset returns the expected byte")
+ {
+ auto buffer = kstd::vector<std::byte>(1);
+ auto const bytes_read = file->read(buffer.data(), 5, buffer.size());
+
+ REQUIRE(bytes_read == 1);
+ REQUIRE(static_cast<char>(buffer[0]) == '1');
+ }
+ }
+}
+
+SCENARIO("Ext2 inode read stops when block mapping resolves to zero", "[filesystem][ext2][inode]")
+{
+ auto const block_size = 1024;
+ GIVEN("an ext2 inode without mapped data blocks")
+ {
+ auto device = kstd::make_shared<kernel::tests::devices::block_device>(0, 0, "mock", block_size, 64 * block_size);
+ REQUIRE(device != nullptr);
+ kernel::tests::filesystem::ext2::setup_mock_ext2_layout(*device);
+
+ auto fs = kernel::filesystem::ext2::filesystem{};
+ REQUIRE(fs.mount(device) == kernel::filesystem::filesystem::operation_result::success);
+
+ auto inode = kernel::filesystem::ext2::inode{&fs};
+ inode.m_data.blocks = 2;
+ inode.m_data.block[0] = 0;
+
+ auto buffer = kstd::vector<std::byte>(32, std::byte{0xAB});
+
+ THEN("no bytes are read")
+ {
+ auto const bytes_read = inode.read(buffer.data(), 0, buffer.size());
+ REQUIRE(bytes_read == 0);
+ }
+ }
+}
+
+SCENARIO("Ext2 inode read across block boundaries", "[filesystem][ext2][inode]")
+{
+ auto const block_size = 1024;
+ GIVEN("an ext2 inode with two direct blocks and a block size of 1024 bytes")
+ {
+ auto device = kstd::make_shared<kernel::tests::devices::block_device>(0, 0, "mock", block_size, 64 * block_size);
+ REQUIRE(device != nullptr);
+ kernel::tests::filesystem::ext2::setup_mock_ext2_layout(*device);
+
+ auto fs = kernel::filesystem::ext2::filesystem{};
+ REQUIRE(fs.mount(device) == kernel::filesystem::filesystem::operation_result::success);
+
+ auto inode = kernel::filesystem::ext2::inode{&fs};
+ inode.m_data.blocks = 2;
+ inode.m_data.block[0] = 20;
+ kernel::tests::filesystem::ext2::write_bytes(*device, 21 * block_size - 6, "Hello ", 6);
+ inode.m_data.block[1] = 21;
+ kernel::tests::filesystem::ext2::write_bytes(*device, 21 * block_size, "World!", 6);
+
+ auto buffer = kstd::vector<std::byte>(12, std::byte{0x00});
+
+ THEN("reading across the block boundary returns the combined content")
+ {
+ auto const bytes_read = inode.read(buffer.data(), block_size - 6, buffer.size());
+ REQUIRE(bytes_read == 12);
+
+ auto const text = std::string_view{reinterpret_cast<char const *>(buffer.data()), bytes_read};
+ REQUIRE(text == "Hello World!");
+ }
+ }
+}
+
+SCENARIO("Ext2 inode write is not implemented", "[filesystem][ext2][inode]")
+{
+ GIVEN("an ext2 inode")
+ {
+ auto fs = kernel::filesystem::ext2::filesystem{};
+ auto inode = kernel::filesystem::ext2::inode{&fs};
+
+ THEN("writing to the inode panics")
+ {
+ auto buffer = kstd::vector<std::byte>(32, std::byte{0x00});
+ REQUIRE_THROWS_AS(inode.write(buffer.data(), 0, buffer.size()), kernel::tests::cpu::halt);
+ }
+ }
+}
diff --git a/kernel/src/filesystem/file_descriptor_table.cpp b/kernel/src/filesystem/file_descriptor_table.cpp
index 287aea2..1c062a1 100644
--- a/kernel/src/filesystem/file_descriptor_table.cpp
+++ b/kernel/src/filesystem/file_descriptor_table.cpp
@@ -10,13 +10,13 @@
#include <cstddef>
#include <optional>
-namespace kernel::filesystem
+namespace
{
- namespace
- {
- constinit auto static global_file_descriptor_table = std::optional<file_descriptor_table>{};
- } // namespace
+ constinit auto static global_file_descriptor_table = std::optional<kernel::filesystem::file_descriptor_table>{};
+} // namespace
+namespace kernel::filesystem
+{
auto file_descriptor_table::init() -> void
{
if (global_file_descriptor_table)
@@ -87,4 +87,12 @@ namespace kernel::filesystem
m_open_files.at(index) = nullptr;
}
-} // namespace kernel::filesystem \ No newline at end of file
+} // namespace kernel::filesystem
+
+namespace kernel::tests::filesystem::file_descriptor_table
+{
+ auto deinit() -> void
+ {
+ global_file_descriptor_table.reset();
+ }
+} // namespace kernel::tests::filesystem::file_descriptor_table
diff --git a/kernel/src/filesystem/file_descriptor_table.tests.cpp b/kernel/src/filesystem/file_descriptor_table.tests.cpp
new file mode 100644
index 0000000..5aeadb2
--- /dev/null
+++ b/kernel/src/filesystem/file_descriptor_table.tests.cpp
@@ -0,0 +1,113 @@
+#include "kernel/filesystem/file_descriptor_table.hpp"
+
+#include "kernel/filesystem/open_file_description.hpp"
+#include "kernel/test_support/filesystem/inode.hpp"
+
+#include <kstd/memory>
+#include <kstd/print>
+#include <kstd/vector>
+
+#include <catch2/catch_test_macros.hpp>
+
+SCENARIO("File descriptor table add/get file", "[filesystem][file_descriptor_table]")
+{
+ GIVEN("a file descriptor table and an open file description")
+ {
+ auto & table = kernel::filesystem::file_descriptor_table::get();
+ auto inode = kstd::make_shared<kernel::tests::filesystem::inode>();
+ auto file_description_1 = kstd::make_shared<kernel::filesystem::open_file_description>(inode);
+ auto file_description_2 = kstd::make_shared<kernel::filesystem::open_file_description>(inode);
+
+ WHEN("adding the open file description to the file descriptor table")
+ {
+ auto fd_1 = table.add_file(file_description_1);
+ auto fd_2 = table.add_file(file_description_2);
+ auto fd_3 = table.add_file(file_description_2);
+
+ THEN("a valid file descriptor is returned")
+ {
+ REQUIRE(fd_1 == 0);
+ REQUIRE(fd_2 == 1);
+ REQUIRE(fd_3 == 2);
+ }
+
+ THEN("the file description can be retrieved using the returned file descriptor")
+ {
+ auto retrieved_description = table.get_file(fd_1);
+ REQUIRE(retrieved_description == file_description_1);
+ }
+ }
+ }
+
+ GIVEN("a invalid open file description")
+ {
+ auto & table = kernel::filesystem::file_descriptor_table::get();
+
+ THEN("adding a null file description returns an error code")
+ {
+ auto fd = table.add_file(nullptr);
+ REQUIRE(fd == -1);
+ }
+
+ THEN("retrieving a file description with a negative file descriptor returns a null pointer")
+ {
+ auto retrieved_description = table.get_file(-1);
+ REQUIRE(retrieved_description == nullptr);
+ }
+
+ THEN("retrieving a file description with an out-of-bounds file descriptor returns a null pointer")
+ {
+ auto retrieved_description = table.get_file(1000);
+ REQUIRE(retrieved_description == nullptr);
+ }
+ }
+}
+
+SCENARIO("File descriptor table remove file", "[filesystem][file_descriptor_table]")
+{
+ GIVEN("a file descriptor table with an open file description")
+ {
+ auto & table = kernel::filesystem::file_descriptor_table::get();
+ auto inode = kstd::make_shared<kernel::tests::filesystem::inode>();
+ auto file_description = kstd::make_shared<kernel::filesystem::open_file_description>(inode);
+ auto fd = table.add_file(file_description);
+
+ WHEN("removing the file description using the file descriptor")
+ {
+ table.remove_file(fd);
+
+ THEN("the file description can no longer be retrieved using the file descriptor")
+ {
+ auto retrieved_description = table.get_file(fd);
+ REQUIRE(retrieved_description == nullptr);
+ }
+ }
+
+ WHEN("removing a file description the other file descriptor keep the same index")
+ {
+ auto fd2 = table.add_file(file_description);
+ table.remove_file(fd);
+
+ THEN("the second file description can still be retrieved using its file descriptor")
+ {
+ auto retrieved_description = table.get_file(fd2);
+ REQUIRE(retrieved_description == file_description);
+ }
+ }
+ }
+
+ GIVEN("an invalid file descriptor")
+ {
+ auto & table = kernel::filesystem::file_descriptor_table::get();
+
+ THEN("removing a file with a negative file descriptor does nothing")
+ {
+ REQUIRE_NOTHROW(table.remove_file(-1));
+ }
+
+ THEN("removing a file with an out-of-bounds file descriptor does nothing")
+ {
+ REQUIRE_NOTHROW(table.remove_file(1000));
+ }
+ }
+} \ No newline at end of file
diff --git a/kernel/src/filesystem/filesystem.cpp b/kernel/src/filesystem/filesystem.cpp
index 0ac9cf8..d8b04eb 100644
--- a/kernel/src/filesystem/filesystem.cpp
+++ b/kernel/src/filesystem/filesystem.cpp
@@ -1,24 +1,62 @@
#include "kernel/filesystem/filesystem.hpp"
#include "kapi/devices/device.hpp"
+#include "kapi/system.hpp"
+
+#include "kernel/filesystem/ext2/filesystem.hpp"
#include "kernel/filesystem/inode.hpp"
#include <kstd/memory>
+#include <array>
+
namespace kernel::filesystem
{
- auto filesystem::mount(kstd::shared_ptr<kapi::devices::device> const & device) -> int
+ namespace
+ {
+ constexpr auto static filesystem_factories = std::array{
+ []() { return kstd::make_shared<ext2::filesystem>(); },
+ };
+ } // namespace
+
+ auto filesystem::probe_and_mount(kstd::shared_ptr<kapi::devices::device> const & device)
+ -> kstd::shared_ptr<filesystem>
+ {
+ if (!device)
+ {
+ kapi::system::panic("[FILESYSTEM] cannot mount filesystem: device is null.");
+ }
+
+ for (auto & factory : filesystem_factories)
+ {
+ auto fs = factory();
+ if (fs->mount(device) == operation_result::success)
+ {
+ return fs;
+ }
+ }
+
+ return nullptr;
+ }
+
+ auto filesystem::mount(kstd::shared_ptr<kapi::devices::device> const & device) -> operation_result
{
if (!device)
{
- return -1; // TODO BA-FS26 panic or errorcode?
+ kapi::system::panic("[FILESYSTEM] cannot mount filesystem: device is null.");
}
+
m_device = device;
- return 0;
+ return operation_result::success;
}
auto filesystem::root_inode() const -> kstd::shared_ptr<inode> const &
{
return m_root_inode;
}
+
+ auto filesystem::device() const -> kstd::shared_ptr<kapi::devices::device> const &
+ {
+ return m_device;
+ }
} // namespace kernel::filesystem \ No newline at end of file
diff --git a/kernel/src/filesystem/mount.cpp b/kernel/src/filesystem/mount.cpp
index f9e709c..d165385 100644
--- a/kernel/src/filesystem/mount.cpp
+++ b/kernel/src/filesystem/mount.cpp
@@ -13,11 +13,13 @@
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, std::string_view mount_path)
+ kstd::shared_ptr<filesystem> const & fs, std::string_view mount_path,
+ kstd::shared_ptr<mount> const & parent_mount)
: m_mount_path(mount_path)
, m_mount_dentry(mount_dentry)
, m_root_dentry(root_dentry)
, m_filesystem(fs)
+ , m_parent_mount(parent_mount)
{
if (!m_filesystem)
{
@@ -25,7 +27,7 @@ namespace kernel::filesystem
}
}
- auto mount::get_mount_dentry() const -> kstd::shared_ptr<dentry>
+ auto mount::get_mount_dentry() const -> kstd::shared_ptr<dentry> const &
{
return m_mount_dentry;
}
@@ -44,4 +46,9 @@ namespace kernel::filesystem
{
return m_mount_path.view();
}
+
+ auto mount::get_parent_mount() const -> kstd::shared_ptr<mount> const &
+ {
+ return m_parent_mount;
+ }
} // namespace kernel::filesystem \ No newline at end of file
diff --git a/kernel/src/filesystem/mount.tests.cpp b/kernel/src/filesystem/mount.tests.cpp
new file mode 100644
index 0000000..4c4393a
--- /dev/null
+++ b/kernel/src/filesystem/mount.tests.cpp
@@ -0,0 +1,49 @@
+#include "kernel/filesystem/mount.hpp"
+
+#include "kernel/filesystem/dentry.hpp"
+#include "kernel/test_support/cpu.hpp"
+#include "kernel/test_support/filesystem/filesystem.hpp"
+#include "kernel/test_support/filesystem/inode.hpp"
+
+#include <kstd/memory>
+#include <kstd/print>
+#include <kstd/vector>
+
+#include <catch2/catch_test_macros.hpp>
+
+SCENARIO("Mount construction", "[filesystem][mount]")
+{
+ GIVEN("a filesystem and a root dentry")
+ {
+ 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")
+ {
+ auto mount = kernel::filesystem::mount{root_dentry, root_dentry, fs, "/", 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.get_mount_dentry() == root_dentry);
+ REQUIRE(mount.get_mount_path() == "/");
+ }
+
+ THEN("the mount has no parent mount")
+ {
+ REQUIRE(mount.get_parent_mount() == nullptr);
+ }
+ }
+
+ WHEN("constructing a mount with a null filesystem")
+ {
+ THEN("the constructor panics")
+ {
+ REQUIRE_THROWS_AS((kernel::filesystem::mount{root_dentry, root_dentry, nullptr, "/", nullptr}),
+ kernel::tests::cpu::halt);
+ }
+ }
+ }
+}
diff --git a/kernel/src/filesystem/mount_table.cpp b/kernel/src/filesystem/mount_table.cpp
index 737434e..3b1dee3 100644
--- a/kernel/src/filesystem/mount_table.cpp
+++ b/kernel/src/filesystem/mount_table.cpp
@@ -1,17 +1,83 @@
#include "kernel/filesystem/mount_table.hpp"
+#include "kernel/filesystem/dentry.hpp"
#include "kernel/filesystem/mount.hpp"
#include <kstd/memory>
+#include <kstd/vector>
+#include <algorithm>
#include <cstddef>
+#include <ranges>
#include <string_view>
namespace kernel::filesystem
{
- void mount_table::add_mount(kstd::shared_ptr<mount> mount)
+ namespace
+ {
+ auto is_descendant_of(kstd::shared_ptr<mount> const & candidate, kstd::shared_ptr<mount> const & ancestor) -> bool
+ {
+ for (auto current = candidate; current; current = current->get_parent_mount())
+ {
+ if (current == ancestor)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ auto is_strict_prefix(std::string_view prefix, std::string_view path) -> bool
+ {
+ return prefix != "/" && path.starts_with(prefix) && path.size() > prefix.size() && path[prefix.size()] == '/';
+ }
+
+ auto is_visible_mount(kstd::shared_ptr<mount> const & candidate,
+ kstd::vector<kstd::shared_ptr<mount>> const & mounts) -> bool
+ {
+ return std::ranges::none_of(mounts, [&](auto const & other) {
+ return other != candidate && is_strict_prefix(other->get_mount_path(), candidate->get_mount_path()) &&
+ !is_descendant_of(candidate, other);
+ });
+ }
+ } // namespace
+
+ auto mount_table::has_child_mounts(kstd::shared_ptr<mount> const & parent_mount) const -> bool
+ {
+ return std::ranges::any_of(
+ m_mounts, [&parent_mount](auto const & mount) { return mount->get_parent_mount() == parent_mount; });
+ }
+
+ void mount_table::add_mount(kstd::shared_ptr<mount> const & mount)
{
m_mounts.push_back(mount);
+ if (auto mount_dentry = mount->get_mount_dentry())
+ {
+ mount_dentry->set_flag(dentry::dentry_flags::dcache_mounted);
+ }
+ }
+
+ auto mount_table::remove_mount(std::string_view path) -> operation_result
+ {
+ auto mount_it = std::ranges::find_if(std::ranges::reverse_view(m_mounts), [&](auto const & mount) {
+ return mount->get_mount_path() == path && is_visible_mount(mount, m_mounts);
+ });
+
+ if (mount_it == std::ranges::reverse_view(m_mounts).end())
+ {
+ return operation_result::mount_not_found;
+ }
+
+ auto const & mount = *mount_it;
+ if (has_child_mounts(mount))
+ {
+ return operation_result::has_child_mounts;
+ }
+
+ mount->get_mount_dentry()->unset_flag(dentry::dentry_flags::dcache_mounted);
+ m_mounts.erase(std::ranges::find(m_mounts, mount));
+ return operation_result::removed;
}
auto mount_table::find_longest_prefix_mount(std::string_view path) const -> kstd::shared_ptr<mount>
@@ -25,8 +91,9 @@ namespace kernel::filesystem
// /a/b/c should match /a/b but not /a/bb or /a/b/c/d, / should match everything
bool is_prefix = path.starts_with(mp) && (mp == "/" || path.size() == mp.size() || path[mp.size()] == '/');
+ bool visible = is_visible_mount(mount, m_mounts);
- if (is_prefix && mp.size() >= best_len)
+ if (is_prefix && visible && mp.size() >= best_len)
{
mount_with_longest_prefix = mount;
best_len = mp.size();
diff --git a/kernel/src/filesystem/mount_table.tests.cpp b/kernel/src/filesystem/mount_table.tests.cpp
new file mode 100644
index 0000000..439fe97
--- /dev/null
+++ b/kernel/src/filesystem/mount_table.tests.cpp
@@ -0,0 +1,163 @@
+#include "kernel/filesystem/mount_table.hpp"
+
+#include "kernel/filesystem/dentry.hpp"
+#include "kernel/filesystem/mount.hpp"
+#include "kernel/test_support/filesystem/filesystem.hpp"
+#include "kernel/test_support/filesystem/inode.hpp"
+
+#include <kstd/memory>
+#include <kstd/print>
+#include <kstd/vector>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <string_view>
+
+SCENARIO("Mount table construction", "[filesystem][mount_table]")
+{
+ GIVEN("an empty mount table")
+ {
+ kernel::filesystem::mount_table table;
+
+ THEN("finding any mount returns null")
+ {
+ REQUIRE(table.find_longest_prefix_mount("/") == nullptr);
+ REQUIRE(table.find_longest_prefix_mount("/any/path") == nullptr);
+ }
+
+ THEN("removing any mount returns mount_not_found")
+ {
+ REQUIRE(table.remove_mount("/") == kernel::filesystem::mount_table::operation_result::mount_not_found);
+ REQUIRE(table.remove_mount("/any/path") == kernel::filesystem::mount_table::operation_result::mount_not_found);
+ }
+ }
+}
+
+SCENARIO("Adding, finding and removing mounts in the mount table", "[filesystem][mount_table]")
+{
+ GIVEN("a mount table and some mounts")
+ {
+ kernel::filesystem::mount_table table;
+
+ auto fs1 = kstd::make_shared<kernel::tests::filesystem::filesystem>();
+ auto root_inode1 = kstd::make_shared<kernel::tests::filesystem::inode>();
+ auto root_dentry1 = kstd::make_shared<kernel::filesystem::dentry>(nullptr, root_inode1, "/");
+ auto mount1 = kstd::make_shared<kernel::filesystem::mount>(root_dentry1, root_dentry1, fs1, "/", nullptr);
+
+ auto fs2 = kstd::make_shared<kernel::tests::filesystem::filesystem>();
+ auto root_inode2 = kstd::make_shared<kernel::tests::filesystem::inode>();
+ auto root_dentry2 = kstd::make_shared<kernel::filesystem::dentry>(nullptr, root_inode2, "/");
+ auto mount2 = kstd::make_shared<kernel::filesystem::mount>(root_dentry1, root_dentry2, fs2, "/mnt", nullptr);
+
+ table.add_mount(mount1);
+ table.add_mount(mount2);
+
+ THEN("dentry flags are set correctly for mounted dentries")
+ {
+ REQUIRE(root_dentry1->has_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted));
+ REQUIRE(!root_dentry2->has_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted));
+ }
+
+ THEN("finding mounts by path returns the correct mount")
+ {
+ REQUIRE(table.find_longest_prefix_mount("/") == mount1);
+ REQUIRE(table.find_longest_prefix_mount("/file") == mount1);
+ REQUIRE(table.find_longest_prefix_mount("/mnt") == mount2);
+ REQUIRE(table.find_longest_prefix_mount("/mnt/file") == mount2);
+ REQUIRE(table.find_longest_prefix_mount("/other") == mount1);
+ }
+
+ THEN("removing a mount that has no child mounts succeeds")
+ {
+ REQUIRE(table.remove_mount("/mnt") == kernel::filesystem::mount_table::operation_result::removed);
+ REQUIRE(!root_dentry2->has_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted));
+ REQUIRE(table.find_longest_prefix_mount("/mnt") == mount1);
+ }
+
+ THEN("removing a mount that does not exist returns mount_not_found")
+ {
+ REQUIRE(table.remove_mount("/nonexistent") == kernel::filesystem::mount_table::operation_result::mount_not_found);
+ }
+ }
+
+ GIVEN("multiple mounts with the same path")
+ {
+ kernel::filesystem::mount_table table;
+
+ auto fs1 = kstd::make_shared<kernel::tests::filesystem::filesystem>();
+ auto root_inode1 = kstd::make_shared<kernel::tests::filesystem::inode>();
+ auto root_dentry1 = kstd::make_shared<kernel::filesystem::dentry>(nullptr, root_inode1, "/");
+ auto mount1 = kstd::make_shared<kernel::filesystem::mount>(root_dentry1, root_dentry1, fs1, "/", nullptr);
+
+ auto fs2 = kstd::make_shared<kernel::tests::filesystem::filesystem>();
+ auto root_inode2 = kstd::make_shared<kernel::tests::filesystem::inode>();
+ auto root_dentry2 = kstd::make_shared<kernel::filesystem::dentry>(nullptr, root_inode2, "/");
+ auto mount2 = kstd::make_shared<kernel::filesystem::mount>(root_dentry1, root_dentry2, fs2, "/", mount1);
+
+ table.add_mount(mount1);
+ table.add_mount(mount2);
+
+ THEN("finding mounts by path returns the correct mount based on longest prefix")
+ {
+ REQUIRE(table.find_longest_prefix_mount("/") == mount2);
+ REQUIRE(table.find_longest_prefix_mount("/file") == mount2);
+ REQUIRE(table.find_longest_prefix_mount("/mnt") == mount2);
+ REQUIRE(table.find_longest_prefix_mount("/mnt/file") == mount2);
+ REQUIRE(table.find_longest_prefix_mount("/other") == mount2);
+ }
+
+ THEN("removing the topmost mount with the same path succeeds")
+ {
+ REQUIRE(table.remove_mount("/") == kernel::filesystem::mount_table::operation_result::removed);
+ REQUIRE(!root_dentry2->has_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted));
+ REQUIRE(table.find_longest_prefix_mount("/") == mount1);
+ }
+ }
+
+ GIVEN("a mount with child mounts")
+ {
+ kernel::filesystem::mount_table table;
+
+ auto fs1 = kstd::make_shared<kernel::tests::filesystem::filesystem>();
+ auto root_inode1 = kstd::make_shared<kernel::tests::filesystem::inode>();
+ auto root_dentry1 = kstd::make_shared<kernel::filesystem::dentry>(nullptr, root_inode1, "/");
+ auto mount1 = kstd::make_shared<kernel::filesystem::mount>(root_dentry1, root_dentry1, fs1, "/", nullptr);
+
+ auto fs2 = kstd::make_shared<kernel::tests::filesystem::filesystem>();
+ auto root_inode2 = kstd::make_shared<kernel::tests::filesystem::inode>();
+ auto root_dentry2 = kstd::make_shared<kernel::filesystem::dentry>(nullptr, root_inode2, "/");
+ auto mount2 = kstd::make_shared<kernel::filesystem::mount>(root_dentry1, root_dentry2, fs2, "/mnt", mount1);
+
+ auto fs3 = kstd::make_shared<kernel::tests::filesystem::filesystem>();
+ auto root_inode3 = kstd::make_shared<kernel::tests::filesystem::inode>();
+ auto root_dentry3 = kstd::make_shared<kernel::filesystem::dentry>(nullptr, root_inode3, "/");
+ auto mount3 = kstd::make_shared<kernel::filesystem::mount>(root_dentry2, root_dentry3, fs3, "/mnt/submnt", mount2);
+
+ table.add_mount(mount1);
+ table.add_mount(mount2);
+ table.add_mount(mount3);
+
+ THEN("finding mounts by path returns the correct mount based on longest prefix")
+ {
+ REQUIRE(table.find_longest_prefix_mount("/") == mount1);
+ REQUIRE(table.find_longest_prefix_mount("/file") == mount1);
+ REQUIRE(table.find_longest_prefix_mount("/mnt") == mount2);
+ REQUIRE(table.find_longest_prefix_mount("/mnt/file") == mount2);
+ REQUIRE(table.find_longest_prefix_mount("/mnt/submnt") == mount3);
+ REQUIRE(table.find_longest_prefix_mount("/other") == mount1);
+ }
+
+ THEN("removing a mount with child mounts returns has_child_mounts")
+ {
+ REQUIRE(table.remove_mount("/") == kernel::filesystem::mount_table::operation_result::has_child_mounts);
+ REQUIRE(table.remove_mount("/mnt") == kernel::filesystem::mount_table::operation_result::has_child_mounts);
+ }
+
+ THEN("removing a leaf mount succeeds")
+ {
+ REQUIRE(table.remove_mount("/mnt/submnt") == kernel::filesystem::mount_table::operation_result::removed);
+ REQUIRE(!root_dentry3->has_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted));
+ REQUIRE(table.find_longest_prefix_mount("/mnt/submnt") == mount2);
+ }
+ }
+}
diff --git a/kernel/src/filesystem/open_file_description.cpp b/kernel/src/filesystem/open_file_description.cpp
index 8c04225..f049a34 100644
--- a/kernel/src/filesystem/open_file_description.cpp
+++ b/kernel/src/filesystem/open_file_description.cpp
@@ -32,4 +32,10 @@ namespace kernel::filesystem
m_offset += written_bytes;
return written_bytes;
}
+
+ auto open_file_description::offset() const -> size_t
+ {
+ return m_offset;
+ }
+
} // namespace kernel::filesystem \ No newline at end of file
diff --git a/kernel/src/filesystem/open_file_description.tests.cpp b/kernel/src/filesystem/open_file_description.tests.cpp
new file mode 100644
index 0000000..db8eb49
--- /dev/null
+++ b/kernel/src/filesystem/open_file_description.tests.cpp
@@ -0,0 +1,114 @@
+#include "kernel/filesystem/open_file_description.hpp"
+
+#include "kernel/filesystem/vfs.hpp"
+#include "kernel/test_support/filesystem/inode.hpp"
+#include "kernel/test_support/filesystem/storage_boot_module_vfs_fixture.hpp"
+
+#include <kstd/memory>
+#include <kstd/print>
+#include <kstd/vector>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <cstddef>
+#include <filesystem>
+#include <string_view>
+
+SCENARIO("Open file description construction", "[filesystem][open_file_description]")
+{
+ GIVEN("an inode and an open file description for that inode")
+ {
+ auto inode = kstd::make_shared<kernel::tests::filesystem::inode>();
+ auto file_description = kernel::filesystem::open_file_description{inode};
+
+ THEN("the initial offset is zero")
+ {
+ REQUIRE(file_description.offset() == 0);
+ }
+ }
+}
+
+SCENARIO("Open file description read/write offset management", "[filesystem][open_file_description]")
+{
+ GIVEN("an inode that tracks read/write calls and an open file description for that inode")
+ {
+ auto inode = kstd::make_shared<kernel::tests::filesystem::inode>();
+ auto file_description = kernel::filesystem::open_file_description{inode};
+
+ THEN("the offset is updated correctly after reads")
+ {
+ REQUIRE(file_description.read(nullptr, 100) == 100);
+ REQUIRE(file_description.offset() == 100);
+ REQUIRE(file_description.read(nullptr, 50) == 50);
+ REQUIRE(file_description.offset() == 150);
+ }
+
+ THEN("the offset is updated correctly after writes")
+ {
+ REQUIRE(file_description.write(nullptr, 200) == 200);
+ REQUIRE(file_description.offset() == 200);
+ REQUIRE(file_description.write(nullptr, 25) == 25);
+ REQUIRE(file_description.offset() == 225);
+ }
+
+ THEN("reads and writes both update the same offset")
+ {
+ REQUIRE(file_description.read(nullptr, 10) == 10);
+ REQUIRE(file_description.offset() == 10);
+ REQUIRE(file_description.write(nullptr, 20) == 20);
+ REQUIRE(file_description.offset() == 30);
+ REQUIRE(file_description.read(nullptr, 5) == 5);
+ REQUIRE(file_description.offset() == 35);
+ REQUIRE(file_description.write(nullptr, 15) == 15);
+ REQUIRE(file_description.offset() == 50);
+ }
+ }
+}
+
+SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture,
+ "Open file description read with real image", "[filesystem][open_file_description][img]")
+{
+ auto const image_path = std::filesystem::path{KERNEL_TEST_ASSETS_DIR} / "ext2_1KB_fs.img";
+
+ GIVEN("an open file description for a file in a real image")
+ {
+ REQUIRE(std::filesystem::exists(image_path));
+ REQUIRE_NOTHROW(setup_modules_from_img_and_init_vfs({"test_img_module"}, {image_path}));
+
+ auto & vfs = kernel::filesystem::vfs::get();
+ auto ofd = vfs.open("/information/info_1.txt");
+ REQUIRE(ofd != nullptr);
+
+ THEN("the file can be read and the offset is updated")
+ {
+ kstd::vector<std::byte> buffer(32);
+ auto bytes_read = ofd->read(buffer.data(), buffer.size());
+ REQUIRE(bytes_read == 32);
+ REQUIRE(ofd->offset() == 32);
+
+ std::string_view buffer_as_str{reinterpret_cast<char *>(buffer.data()), static_cast<size_t>(bytes_read)};
+ auto const content_end = buffer_as_str.find('\0');
+ REQUIRE(buffer_as_str.substr(0, content_end) == "info_1\n");
+
+ for (auto i = content_end; i < buffer_as_str.size(); ++i)
+ {
+ REQUIRE(buffer_as_str[i] == '\0');
+ }
+ }
+
+ THEN("the file can be read multiple times")
+ {
+ kstd::vector<std::byte> buffer(4);
+ auto bytes_read_1 = ofd->read(buffer.data(), buffer.size() / 2);
+ REQUIRE(bytes_read_1 == buffer.size() / 2);
+ REQUIRE(ofd->offset() == buffer.size() / 2);
+
+ auto bytes_read_2 = ofd->read(buffer.data() + buffer.size() / 2, buffer.size() / 2);
+ REQUIRE(bytes_read_2 == buffer.size() / 2);
+ REQUIRE(ofd->offset() == buffer.size());
+
+ std::string_view buffer_as_str{reinterpret_cast<char *>(buffer.data()), bytes_read_1 + bytes_read_2};
+ REQUIRE(buffer_as_str == "info");
+ }
+ }
+}
diff --git a/kernel/src/filesystem/rootfs/filesystem.cpp b/kernel/src/filesystem/rootfs/filesystem.cpp
index 37bf588..dffef99 100644
--- a/kernel/src/filesystem/rootfs/filesystem.cpp
+++ b/kernel/src/filesystem/rootfs/filesystem.cpp
@@ -1,6 +1,7 @@
#include "kernel/filesystem/rootfs/filesystem.hpp"
#include "kapi/devices/device.hpp"
+
#include "kernel/filesystem/inode.hpp"
#include "kernel/filesystem/rootfs/inode.hpp"
@@ -10,13 +11,13 @@
namespace kernel::filesystem::rootfs
{
- auto filesystem::mount(kstd::shared_ptr<kapi::devices::device> const &) -> int
+ auto filesystem::mount(kstd::shared_ptr<kapi::devices::device> const &) -> operation_result
{
auto rfs_inode = kstd::make_shared<inode>();
rfs_inode->add_child("dev");
m_root_inode = rfs_inode;
- 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/rootfs/filesystem.tests.cpp b/kernel/src/filesystem/rootfs/filesystem.tests.cpp
new file mode 100644
index 0000000..b013f78
--- /dev/null
+++ b/kernel/src/filesystem/rootfs/filesystem.tests.cpp
@@ -0,0 +1,45 @@
+#include "kernel/filesystem/rootfs/filesystem.hpp"
+
+#include "kernel/filesystem/filesystem.hpp"
+
+#include <kstd/memory>
+#include <kstd/print>
+#include <kstd/vector>
+
+#include <catch2/catch_test_macros.hpp>
+
+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);
+
+ THEN("the filesystem can be mounted successfully")
+ {
+ REQUIRE(result == kernel::filesystem::filesystem::operation_result::success);
+ REQUIRE(fs.root_inode() != nullptr);
+ }
+
+ THEN("looking up the 'dev' directory returns a valid inode")
+ {
+ auto dev_inode = fs.lookup(fs.root_inode(), "dev");
+ REQUIRE(dev_inode != nullptr);
+ REQUIRE(dev_inode->is_directory());
+ }
+
+ THEN("looking up a non-existent directory returns null")
+ {
+ auto non_existent_inode_1 = fs.lookup(fs.root_inode(), "");
+ REQUIRE(non_existent_inode_1 == nullptr);
+ auto non_existent_inode_2 = fs.lookup(fs.root_inode(), "nonexistent");
+ REQUIRE(non_existent_inode_2 == nullptr);
+ }
+
+ THEN("looking up with a null parent inode returns null")
+ {
+ auto result = fs.lookup(nullptr, "dev");
+ REQUIRE(result == nullptr);
+ }
+ }
+}
diff --git a/kernel/src/filesystem/rootfs/inode.tests.cpp b/kernel/src/filesystem/rootfs/inode.tests.cpp
new file mode 100644
index 0000000..a0c5938
--- /dev/null
+++ b/kernel/src/filesystem/rootfs/inode.tests.cpp
@@ -0,0 +1,83 @@
+#include "kernel/filesystem/rootfs/inode.hpp"
+
+#include <kstd/memory>
+#include <kstd/print>
+#include <kstd/vector>
+
+#include <catch2/catch_test_macros.hpp>
+
+SCENARIO("Rootfs inode creation", "[filesystem][rootfs][inode]")
+{
+ GIVEN("a rootfs inode")
+ {
+ auto inode = kernel::filesystem::rootfs::inode{};
+
+ THEN("the inode has the correct kind")
+ {
+ REQUIRE(inode.is_directory());
+ REQUIRE_FALSE(inode.is_device());
+ REQUIRE_FALSE(inode.is_regular());
+ }
+
+ THEN("the inode has no children")
+ {
+ REQUIRE(inode.lookup_child("child") == nullptr);
+ }
+ }
+}
+
+SCENARIO("Rootfs inode child management", "[filesystem][rootfs][inode]")
+{
+ GIVEN("a rootfs inode")
+ {
+ auto inode = kernel::filesystem::rootfs::inode{};
+
+ WHEN("adding a child inode")
+ {
+ inode.add_child("child");
+ inode.add_child("another child");
+
+ THEN("the child can be looked up by name")
+ {
+ auto child_inode = inode.lookup_child("child");
+ REQUIRE(child_inode != nullptr);
+ REQUIRE(child_inode->is_directory());
+ }
+
+ THEN("looking up a non-existent child returns null")
+ {
+ REQUIRE(inode.lookup_child("nonexistent") == nullptr);
+ }
+ }
+ }
+}
+
+SCENARIO("Rootfs inode read/write", "[filesystem][rootfs][inode]")
+{
+ GIVEN("a rootfs inode")
+ {
+ auto inode = kernel::filesystem::rootfs::inode{};
+
+ WHEN("reading from the inode")
+ {
+ kstd::vector<char> buffer(10);
+ auto bytes_read = inode.read(buffer.data(), 0, buffer.size());
+
+ THEN("no bytes are read")
+ {
+ REQUIRE(bytes_read == 0);
+ }
+ }
+
+ WHEN("writing to the inode")
+ {
+ kstd::vector<char> buffer(10, 'x');
+ 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/vfs.cpp b/kernel/src/filesystem/vfs.cpp
index 06214d2..67d1af2 100644
--- a/kernel/src/filesystem/vfs.cpp
+++ b/kernel/src/filesystem/vfs.cpp
@@ -5,9 +5,9 @@
#include "kernel/devices/storage/management.hpp"
#include "kernel/filesystem/dentry.hpp"
#include "kernel/filesystem/devfs/filesystem.hpp"
-#include "kernel/filesystem/ext2/filesystem.hpp"
#include "kernel/filesystem/filesystem.hpp"
#include "kernel/filesystem/mount.hpp"
+#include "kernel/filesystem/mount_table.hpp"
#include "kernel/filesystem/open_file_description.hpp"
#include "kernel/filesystem/rootfs/filesystem.hpp"
@@ -17,13 +17,13 @@
#include <ranges>
#include <string_view>
-namespace kernel::filesystem
+namespace
{
- namespace
- {
- constinit auto static active_vfs = std::optional<vfs>{};
- } // namespace
+ constinit auto static active_vfs = std::optional<kernel::filesystem::vfs>{};
+} // namespace
+namespace kernel::filesystem
+{
auto vfs::init() -> void
{
if (active_vfs)
@@ -41,15 +41,16 @@ namespace kernel::filesystem
root_fs->mount(nullptr);
auto root_fs_root_dentry = kstd::make_shared<dentry>(nullptr, root_fs->root_inode());
- m_mount_table.add_mount(kstd::make_shared<mount>(nullptr, root_fs_root_dentry, root_fs, ""));
+ m_mount_table.add_mount(kstd::make_shared<mount>(nullptr, root_fs_root_dentry, root_fs, "", nullptr));
auto storage_mgmt = devices::storage::management::get();
if (auto boot_device = storage_mgmt.determine_boot_device())
{
- // TODO BA-FS26 detect fs type from boot device and load corresponding fs, for now just assume ext2
- auto boot_root_fs = kstd::make_shared<ext2::filesystem>();
- boot_root_fs->mount(boot_device);
- do_mount_internal("/", root_fs_root_dentry, boot_root_fs);
+ auto boot_root_fs = kernel::filesystem::filesystem::probe_and_mount(boot_device);
+ if (boot_root_fs)
+ {
+ do_mount_internal("/", root_fs_root_dentry, boot_root_fs);
+ }
}
auto device_fs = kstd::make_shared<devfs::filesystem>();
@@ -77,41 +78,55 @@ namespace kernel::filesystem
return nullptr;
}
- auto vfs::do_mount(std::string_view path, kstd::shared_ptr<filesystem> const & filesystem) -> int
+ auto vfs::do_mount(std::string_view path, kstd::shared_ptr<filesystem> const & filesystem) -> operation_result
{
if (!filesystem)
{
- return -1; // TODO BA-FS26 panic or errorcode?
+ return operation_result::filesystem_null;
}
- if (path.empty() || path.front() != '/')
+ if (path.empty() || path.front() != '/' || (path.size() > 1 && path.back() == '/'))
{
- return -1; // TODO BA-FS26 panic or errorcode?
+ return operation_result::invalid_path;
}
- // TODO BA-FS26 better path validation
- if ((path.size() > 1 && path.back() == '/'))
+ if (auto mount_point_dentry = resolve_path(path))
{
- return -1; // TODO BA-FS26 panic or errorcode?
+ do_mount_internal(path, mount_point_dentry, filesystem);
+ return operation_result::success;
}
- if (auto mount_point_dentry = resolve_path(path))
+ return operation_result::mount_point_not_found;
+ }
+
+ auto vfs::unmount(std::string_view path) -> operation_result
+ {
+ if (path.empty() || path.front() != '/' || (path.size() > 1 && path.back() == '/'))
{
- do_mount_internal(path, mount_point_dentry, filesystem);
- return 0;
+ return operation_result::invalid_path;
+ }
+
+ auto remove_result = m_mount_table.remove_mount(path);
+ if (remove_result == mount_table::operation_result::removed)
+ {
+ return operation_result::success;
}
- return -1;
+ if (remove_result == mount_table::operation_result::has_child_mounts)
+ {
+ return operation_result::unmount_failed;
+ }
+
+ return operation_result::mount_point_not_found;
}
auto vfs::do_mount_internal(std::string_view path, kstd::shared_ptr<dentry> const & mount_point_dentry,
kstd::shared_ptr<filesystem> const & fs) -> void
{
- // TODO BA-FS26 check if mount point is already mounted and handle it (unmount old fs, fail, etc.)
+ auto parent_mount = m_mount_table.find_longest_prefix_mount(path);
auto new_fs_root = kstd::make_shared<dentry>(mount_point_dentry, fs->root_inode());
- auto new_mount = kstd::make_shared<mount>(mount_point_dentry, new_fs_root, fs, path);
+ auto new_mount = kstd::make_shared<mount>(mount_point_dentry, new_fs_root, fs, path, parent_mount);
m_mount_table.add_mount(new_mount);
- mount_point_dentry->set_flag(dentry::dentry_flags::dcache_mounted);
}
auto vfs::resolve_path(std::string_view path) -> kstd::shared_ptr<dentry>
@@ -160,5 +175,12 @@ namespace kernel::filesystem
return current_dentry;
}
+} // namespace kernel::filesystem
-} // namespace kernel::filesystem \ No newline at end of file
+namespace kernel::tests::filesystem::vfs
+{
+ auto deinit() -> void
+ {
+ active_vfs.reset();
+ }
+} // namespace kernel::tests::filesystem::vfs
diff --git a/kernel/src/filesystem/vfs.tests.cpp b/kernel/src/filesystem/vfs.tests.cpp
new file mode 100644
index 0000000..f363041
--- /dev/null
+++ b/kernel/src/filesystem/vfs.tests.cpp
@@ -0,0 +1,166 @@
+#include "kernel/filesystem/vfs.hpp"
+
+#include "kernel/devices/storage/management.hpp"
+#include "kernel/filesystem/filesystem.hpp"
+#include "kernel/test_support/filesystem/storage_boot_module_vfs_fixture.hpp"
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <filesystem>
+
+SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, "VFS with dummy modules",
+ "[filesystem][vfs]")
+{
+ GIVEN("an initialized boot module registry with multiple modules")
+ {
+ REQUIRE_NOTHROW(setup_modules_and_init_vfs(5));
+
+ THEN("vfs initializes and provides /dev mount")
+ {
+ auto & vfs = kernel::filesystem::vfs::get();
+ auto dev = vfs.open("/dev");
+
+ REQUIRE(dev != nullptr);
+ }
+
+ THEN("vfs initializes root filesystem with boot device if boot module is present")
+ {
+ auto & vfs = kernel::filesystem::vfs::get();
+ auto root_file = vfs.open("/");
+
+ REQUIRE(root_file != nullptr);
+ }
+ }
+}
+
+SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, "VFS with file backed image",
+ "[filesystem][vfs][img]")
+{
+ auto const image_path_1 = std::filesystem::path{KERNEL_TEST_ASSETS_DIR} / "ext2_1KB_fs.img";
+ auto const image_path_2 = std::filesystem::path{KERNEL_TEST_ASSETS_DIR} / "ext2_2KB_fs.img";
+ auto const image_path_3 = std::filesystem::path{KERNEL_TEST_ASSETS_DIR} / "ext2_4KB_fs.img";
+
+ GIVEN("a real image file")
+ {
+ REQUIRE(std::filesystem::exists(image_path_1));
+ REQUIRE_NOTHROW(setup_modules_from_img_and_init_vfs({"test_img_module"}, {image_path_1}));
+
+ THEN("vfs initializes and provides expected mount points")
+ {
+ auto & vfs = kernel::filesystem::vfs::get();
+ auto root = vfs.open("/");
+ auto dev = vfs.open("/dev");
+ auto information = vfs.open("/information/info_1.txt");
+
+ REQUIRE(root != nullptr);
+ REQUIRE(dev != nullptr);
+ REQUIRE(information != nullptr);
+ }
+ }
+
+ GIVEN("three real image files")
+ {
+ REQUIRE(std::filesystem::exists(image_path_1));
+ REQUIRE(std::filesystem::exists(image_path_2));
+ REQUIRE(std::filesystem::exists(image_path_3));
+ REQUIRE_NOTHROW(setup_modules_from_img_and_init_vfs({"test_img_module_1", "test_img_module_2", "test_img_module_3"},
+ {image_path_1, image_path_2, image_path_3}));
+
+ auto & vfs = kernel::filesystem::vfs::get();
+ auto storage_mgmt = kernel::devices::storage::management::get();
+ auto device_1 = storage_mgmt.device_by_major_minor(1, 16);
+ auto fs_1 = kernel::filesystem::filesystem::probe_and_mount(device_1);
+ auto device_2 = storage_mgmt.device_by_major_minor(1, 32);
+ auto fs_2 = kernel::filesystem::filesystem::probe_and_mount(device_2);
+
+ THEN("vfs initializes first module as root")
+ {
+ auto & vfs = kernel::filesystem::vfs::get();
+ auto info1 = vfs.open("/information/info_1.txt");
+ auto info2 = vfs.open("/information/info_2.txt");
+
+ REQUIRE(info1 != nullptr);
+ REQUIRE(info2 != nullptr);
+ }
+
+ THEN("second image can be mounted, data retrieved and unmounted again")
+ {
+ REQUIRE(vfs.do_mount("/information", fs_1) == kernel::filesystem::vfs::operation_result::success);
+
+ auto mounted_monkey_1 = vfs.open("/information/monkey_house/monkey_1.txt");
+ REQUIRE(mounted_monkey_1 != nullptr);
+
+ REQUIRE(vfs.unmount("/information") == kernel::filesystem::vfs::operation_result::success);
+ auto unmounted_monkey_1 = vfs.open("/information/monkey_house/monkey_1.txt");
+ REQUIRE(unmounted_monkey_1 == nullptr);
+
+ auto info_1 = vfs.open("/information/info_1.txt");
+ REQUIRE(info_1 != nullptr);
+ }
+
+ THEN("third image can be mounted in a mounted file system, unmount only if no child mount exists")
+ {
+ REQUIRE(vfs.do_mount("/information", fs_1) == kernel::filesystem::vfs::operation_result::success);
+ REQUIRE(vfs.do_mount("/information/monkey_house/infrastructure", fs_2) ==
+ kernel::filesystem::vfs::operation_result::success);
+
+ auto mounted_monkey_1 = vfs.open("/information/monkey_house/monkey_1.txt");
+ auto mounted_fish1 = vfs.open("/information/monkey_house/infrastructure/enclosures/aquarium/tank_1/fish_1.txt");
+
+ REQUIRE(mounted_monkey_1 != nullptr);
+ REQUIRE(mounted_fish1 != nullptr);
+
+ REQUIRE(vfs.unmount("/information") == kernel::filesystem::vfs::operation_result::unmount_failed);
+ REQUIRE(vfs.unmount("/information/monkey_house/infrastructure") ==
+ kernel::filesystem::vfs::operation_result::success);
+ REQUIRE(vfs.unmount("/information") == kernel::filesystem::vfs::operation_result::success);
+ }
+
+ THEN("images can be stacked mounted and correct file system is unmounted again")
+ {
+ REQUIRE(vfs.do_mount("/information", fs_1) == kernel::filesystem::vfs::operation_result::success);
+ REQUIRE(vfs.do_mount("/information", fs_2) == kernel::filesystem::vfs::operation_result::success);
+
+ auto mounted_tickets = vfs.open("/information/entrance/tickets.txt");
+ REQUIRE(mounted_tickets != nullptr);
+
+ REQUIRE(vfs.unmount("/information") == kernel::filesystem::vfs::operation_result::success);
+ mounted_tickets = vfs.open("/information/entrance/tickets.txt");
+ REQUIRE(mounted_tickets == nullptr);
+
+ auto mounted_monkey = vfs.open("/information/monkey_house/monkey_1.txt");
+ REQUIRE(mounted_monkey != nullptr);
+ }
+
+ THEN("mount with null file system fails")
+ {
+ REQUIRE(vfs.do_mount("/information", nullptr) == kernel::filesystem::vfs::operation_result::filesystem_null);
+ }
+
+ THEN("mount with invalid path fails")
+ {
+ REQUIRE(vfs.do_mount("", fs_1) == kernel::filesystem::vfs::operation_result::invalid_path);
+ REQUIRE(vfs.do_mount("information", fs_1) == kernel::filesystem::vfs::operation_result::invalid_path);
+ REQUIRE(vfs.do_mount("/information/", fs_1) == kernel::filesystem::vfs::operation_result::invalid_path);
+ }
+
+ THEN("mount with non-existent mount point fails")
+ {
+ REQUIRE(vfs.do_mount("/information/nonexistent", fs_1) ==
+ kernel::filesystem::vfs::operation_result::mount_point_not_found);
+ }
+
+ THEN("unmount with invalid path fails")
+ {
+ REQUIRE(vfs.unmount("") == kernel::filesystem::vfs::operation_result::invalid_path);
+ REQUIRE(vfs.unmount("information") == kernel::filesystem::vfs::operation_result::invalid_path);
+ REQUIRE(vfs.unmount("/information/") == kernel::filesystem::vfs::operation_result::invalid_path);
+ }
+
+ THEN("unmounting non-existent mount point returns expected error code")
+ {
+ REQUIRE(vfs.unmount("/information/nonexistent") ==
+ kernel::filesystem::vfs::operation_result::mount_point_not_found);
+ }
+ }
+}
diff --git a/kernel/src/main.cpp b/kernel/src/main.cpp
index b920674..79ed703 100644
--- a/kernel/src/main.cpp
+++ b/kernel/src/main.cpp
@@ -8,8 +8,8 @@
#include "kernel/devices/storage/management.hpp"
#include "kernel/filesystem/device_inode.hpp"
-#include "kernel/filesystem/ext2/filesystem.hpp"
#include "kernel/filesystem/file_descriptor_table.hpp"
+#include "kernel/filesystem/filesystem.hpp"
#include "kernel/filesystem/open_file_description.hpp"
#include "kernel/filesystem/vfs.hpp"
#include "kernel/memory.hpp"
@@ -23,6 +23,7 @@
#include <algorithm>
#include <cstddef>
+#include <string_view>
using namespace kstd::units_literals;
@@ -87,8 +88,6 @@ auto test_file_description_manually() -> void
auto test_device_with_vfs() -> void
{
- // TODO BA-FS26
-
auto vfs = kernel::filesystem::vfs::get();
auto ofd = vfs.open("/dev/ram0");
if (!ofd)
@@ -112,40 +111,64 @@ auto test_device_with_vfs() -> void
auto test_file_lookup() -> void
{
- // TODO BA-FS26 implement a more complete test with multiple files and directories and mounts etc.
-
auto vfs = kernel::filesystem::vfs::get();
+ auto read_and_write_file = [&vfs](std::string_view path) {
+ kstd::println("[TEST] Reading and writing file at path: {}", path);
+ auto ofd = vfs.open(path);
+ if (!ofd)
+ {
+ kstd::os::panic("test code failed");
+ }
+
+ kstd::vector<std::byte> buffer{32};
+ auto number_of_read_bytes = ofd->read(buffer.data(), buffer.size());
+ kstd::println("read bytes: {}", number_of_read_bytes);
+ kstd::println("buffer: {::#04x}", buffer);
+
+ std::string_view buffer_as_str{reinterpret_cast<char *>(buffer.data()), number_of_read_bytes};
+ kstd::println("buffer_as_str: {}", buffer_as_str);
+ };
+
+ read_and_write_file("/info.txt");
+ read_and_write_file("/enclosures/info.txt");
+ read_and_write_file("/enclosures/aquarium/tank_1/fish_4.txt");
+ read_and_write_file("/enclosures/elephant_house/elephant_1.txt");
+ read_and_write_file(
+ "/enclosures/aquarium/tank_2/"
+ "this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_"
+ "limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_30.txt");
+
auto storage_mgmt = kernel::devices::storage::management::get();
+ auto device_1 = storage_mgmt.device_by_major_minor(1, 16);
+ auto fs_1 = kernel::filesystem::filesystem::probe_and_mount(device_1);
- auto ofd1 = vfs.open("/a/b/c");
- auto ofd2 = vfs.open("/dev/ram0");
- auto ofd3 = vfs.open("/a/d/e");
- if (!ofd1 || !ofd2 || !ofd3)
- {
- kstd::os::panic("test code failed");
- }
+ vfs.do_mount("/enclosures/aquarium", fs_1);
+ read_and_write_file("/enclosures/aquarium/closed.txt");
+ read_and_write_file("/enclosures/aquarium/information/info_2.txt");
- if (auto ofd4 = vfs.open("/dev/xxx"))
- {
- kstd::os::panic("test code failed");
- }
+ vfs.unmount("/enclosures/aquarium");
+ read_and_write_file("/enclosures/aquarium/tank_1/fish_4.txt");
- auto new_filesystem = kstd::make_shared<kernel::filesystem::ext2::filesystem>();
- auto device = storage_mgmt.device_by_major_minor(1, 16);
- new_filesystem->mount(device);
- if (vfs.do_mount("/a/b", new_filesystem) != 0)
- {
- kstd::os::panic("test code failed");
- }
- auto ofd5 = vfs.open("/a/b/c");
- if (!ofd5)
+ auto device_2 = storage_mgmt.device_by_major_minor(1, 32);
+ auto fs_2 = kernel::filesystem::filesystem::probe_and_mount(device_2);
+
+ vfs.do_mount("/enclosures/elephant_house", fs_2);
+ read_and_write_file("/enclosures/elephant_house/monkey_house/infrastructure/info.txt");
+
+ vfs.do_mount("/enclosures/elephant_house/monkey_house", fs_1);
+ read_and_write_file("/enclosures/elephant_house/monkey_house/information/info_2.txt");
+
+ auto result = vfs.unmount("/enclosures/elephant_house");
+ if (result == kernel::filesystem::vfs::operation_result::unmount_failed)
{
- kstd::os::panic("test code failed");
+ kstd::println("[TEST] Unmount failed as expected due to active child mount.");
}
- if (auto ofd6 = vfs.open("x/y/z"))
+ vfs.unmount("/enclosures/elephant_house/monkey_house");
+ result = vfs.unmount("/enclosures/elephant_house");
+ if (result == kernel::filesystem::vfs::operation_result::success)
{
- kstd::os::panic("test code failed");
+ kstd::println("[TEST] Unmount succeeded after unmounting child mount.");
}
}
diff --git a/kernel/src/test_support/devices/block_device.cpp b/kernel/src/test_support/devices/block_device.cpp
new file mode 100644
index 0000000..d1d4101
--- /dev/null
+++ b/kernel/src/test_support/devices/block_device.cpp
@@ -0,0 +1,61 @@
+#include "kernel/test_support/devices/block_device.hpp"
+
+#include "kernel/devices/block_device.hpp"
+
+#include <kstd/string>
+#include <kstd/vector>
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+#include <string.h>
+
+namespace kernel::tests::devices
+{
+ block_device::block_device(size_t major, size_t minor, kstd::string const & name, size_t block_size,
+ size_t initial_size)
+ : kernel::devices::block_device(major, minor, name, block_size)
+ {
+ data.resize(initial_size, 0);
+ }
+
+ auto block_device::init() -> bool
+ {
+ return true;
+ }
+
+ auto block_device::read_block(size_t block_index, void * buffer) const -> void
+ {
+ auto const offset = block_index * block_size();
+ if (offset >= data.size())
+ {
+ kstd::libc::memset(buffer, 0, block_size());
+ return;
+ }
+
+ auto const bytes_to_read = std::min(block_size(), data.size() - offset);
+ kstd::libc::memcpy(buffer, data.data() + offset, bytes_to_read);
+ if (bytes_to_read < block_size())
+ {
+ kstd::libc::memset(static_cast<uint8_t *>(buffer) + bytes_to_read, 0, block_size() - bytes_to_read);
+ }
+ }
+
+ auto block_device::write_block(size_t block_index, void const * buffer) -> void
+ {
+ auto const offset = block_index * block_size();
+ auto const write_end = offset + block_size();
+ if (write_end > data.size())
+ {
+ data.resize(write_end, 0);
+ }
+
+ kstd::libc::memcpy(data.data() + offset, static_cast<uint8_t const *>(buffer), block_size());
+ }
+
+ auto block_device::size() const -> size_t
+ {
+ return data.size();
+ }
+} // namespace kernel::tests::devices \ No newline at end of file
diff --git a/kernel/src/test_support/devices/character_device.cpp b/kernel/src/test_support/devices/character_device.cpp
new file mode 100644
index 0000000..9e9227d
--- /dev/null
+++ b/kernel/src/test_support/devices/character_device.cpp
@@ -0,0 +1,20 @@
+
+#include "kernel/test_support/devices/character_device.hpp"
+
+#include "kapi/devices.hpp"
+
+#include <kstd/string>
+
+#include <cstddef>
+
+namespace kernel::tests::devices
+{
+ character_device::character_device(size_t major, size_t minor, kstd::string const & name)
+ : kapi::devices::device(major, minor, name)
+ {}
+
+ auto character_device::init() -> bool
+ {
+ return true;
+ }
+} // namespace kernel::tests::devices \ No newline at end of file
diff --git a/kernel/src/test_support/filesystem/ext2.cpp b/kernel/src/test_support/filesystem/ext2.cpp
new file mode 100644
index 0000000..3627373
--- /dev/null
+++ b/kernel/src/test_support/filesystem/ext2.cpp
@@ -0,0 +1,62 @@
+#include "kernel/test_support/filesystem/ext2.hpp"
+
+#include "kernel/filesystem/ext2/block_group_descriptor.hpp"
+#include "kernel/filesystem/ext2/filesystem.hpp"
+#include "kernel/filesystem/ext2/inode.hpp"
+#include "kernel/filesystem/ext2/superblock.hpp"
+#include "kernel/test_support/devices/block_device.hpp"
+
+#include <cstdint>
+#include <cstring>
+
+namespace kernel::tests::filesystem::ext2
+{
+ namespace
+ {
+ constexpr uint32_t root_directory_data_block = 20;
+ } // namespace
+
+ auto write_bytes(kernel::tests::devices::block_device & device, size_t offset, void const * source, size_t size)
+ -> void
+ {
+ auto const required_size = offset + size;
+ if (device.data.size() < required_size)
+ {
+ device.data.resize(required_size, 0);
+ }
+
+ std::memcpy(device.data.data() + offset, source, size);
+ }
+
+ auto write_u32(kernel::tests::devices::block_device & device, size_t offset, uint32_t value) -> void
+ {
+ write_bytes(device, offset, &value, sizeof(value));
+ }
+
+ auto setup_mock_ext2_layout(kernel::tests::devices::block_device & device) -> void
+ {
+ auto superblock = kernel::filesystem::ext2::superblock{};
+ superblock.magic = kernel::filesystem::ext2::constants::magic_number;
+ superblock.log_block_size = 0;
+ superblock.blocks_count = 64;
+ superblock.blocks_per_group = 64;
+ superblock.inodes_per_group = 32;
+ superblock.rev_level = 1;
+ superblock.inode_size = 128;
+ write_bytes(device, kernel::filesystem::ext2::constants::superblock_offset, &superblock, sizeof(superblock));
+
+ auto group_descriptor = kernel::filesystem::ext2::block_group_descriptor{};
+ group_descriptor.inode_table = 5;
+ write_bytes(device, 2048, &group_descriptor, sizeof(group_descriptor));
+
+ auto root_inode_data = kernel::filesystem::ext2::inode_data{};
+ root_inode_data.mode = kernel::filesystem::ext2::constants::mode_directory;
+ root_inode_data.blocks = 2;
+ root_inode_data.block[0] = root_directory_data_block;
+
+ auto const root_inode_offset =
+ static_cast<size_t>(group_descriptor.inode_table) * kernel::filesystem::ext2::constants::base_block_size +
+ (kernel::filesystem::ext2::constants::root_inode_number - 1) * superblock.inode_size;
+ write_bytes(device, root_inode_offset, &root_inode_data, sizeof(root_inode_data));
+ }
+} // namespace kernel::tests::filesystem::ext2 \ No newline at end of file
diff --git a/kernel/src/test_support/filesystem/filesystem.cpp b/kernel/src/test_support/filesystem/filesystem.cpp
new file mode 100644
index 0000000..225d096
--- /dev/null
+++ b/kernel/src/test_support/filesystem/filesystem.cpp
@@ -0,0 +1,17 @@
+#include "kernel/test_support/filesystem/filesystem.hpp"
+
+#include "kernel/filesystem/inode.hpp"
+#include "kernel/test_support/filesystem/inode.hpp"
+
+#include <kstd/memory>
+
+#include <string_view>
+
+namespace kernel::tests::filesystem
+{
+ auto filesystem::lookup(kstd::shared_ptr<kernel::filesystem::inode> const &, std::string_view)
+ -> kstd::shared_ptr<kernel::filesystem::inode>
+ {
+ return kstd::make_shared<inode>();
+ }
+} // namespace kernel::tests::filesystem \ No newline at end of file
diff --git a/kernel/src/test_support/filesystem/inode.cpp b/kernel/src/test_support/filesystem/inode.cpp
new file mode 100644
index 0000000..5df7bcd
--- /dev/null
+++ b/kernel/src/test_support/filesystem/inode.cpp
@@ -0,0 +1,22 @@
+#include "kernel/test_support/filesystem/inode.hpp"
+
+#include "kernel/filesystem/inode.hpp"
+
+#include <cstddef>
+
+namespace kernel::tests::filesystem
+{
+ inode::inode()
+ : kernel::filesystem::inode(inode_kind::regular)
+ {}
+
+ auto inode::read(void *, size_t, size_t size) const -> size_t
+ {
+ return size;
+ }
+
+ auto inode::write(void const *, size_t, size_t size) -> size_t
+ {
+ return size;
+ }
+} // namespace kernel::tests::filesystem \ No newline at end of file
diff --git a/kernel/src/test_support/filesystem/storage_boot_module_fixture.cpp b/kernel/src/test_support/filesystem/storage_boot_module_fixture.cpp
new file mode 100644
index 0000000..a139f63
--- /dev/null
+++ b/kernel/src/test_support/filesystem/storage_boot_module_fixture.cpp
@@ -0,0 +1,109 @@
+#include "kernel/test_support/filesystem/storage_boot_module_fixture.hpp"
+
+#include "kapi/boot_module/boot_module.hpp"
+#include "kapi/boot_modules.hpp"
+#include "kapi/memory.hpp"
+
+#include "kernel/devices/storage/management.hpp"
+#include "kernel/test_support/boot_modules.hpp"
+#include "kernel/test_support/devices/storage/management.hpp"
+
+#include <kstd/string>
+#include <kstd/vector>
+
+#include <cstddef>
+#include <filesystem>
+#include <fstream>
+#include <ios>
+#include <stdexcept>
+
+namespace kernel::tests::filesystem
+{
+ storage_boot_module_fixture::~storage_boot_module_fixture()
+ {
+ kernel::tests::devices::storage::management::deinit();
+ kernel::tests::boot_modules::deinit();
+ }
+
+ auto storage_boot_module_fixture::setup_modules(std::size_t module_count, std::size_t module_size) -> void
+ {
+ m_module_names.clear();
+ m_module_data.clear();
+ m_registry = {};
+
+ m_module_names.reserve(module_count);
+ m_module_data.reserve(module_count);
+
+ for (std::size_t i = 0; i < module_count; ++i)
+ {
+ m_module_names.push_back("test_mod" + kstd::to_string(i));
+ m_module_data.emplace_back(module_size, std::byte{static_cast<unsigned char>(0x40 + (i % 16))});
+ }
+
+ for (std::size_t i = 0; i < module_count; ++i)
+ {
+ m_registry.add_boot_module(kapi::boot_modules::boot_module{
+ m_module_names[i].view(), kapi::memory::linear_address{m_module_data[i].data()}, m_module_data[i].size()});
+ }
+
+ kapi::boot_modules::set_boot_module_registry(m_registry);
+ kernel::devices::storage::management::init();
+ }
+
+ auto storage_boot_module_fixture::setup_modules_from_img(kstd::vector<kstd::string> const & module_names,
+ kstd::vector<std::filesystem::path> const & img_paths)
+ -> void
+ {
+ m_module_names.clear();
+ m_module_data.clear();
+ m_registry = {};
+
+ if (module_names.size() != img_paths.size())
+ {
+ throw std::invalid_argument{"Module names and image paths vectors must have the same size."};
+ }
+
+ for (size_t i = 0; i < module_names.size(); ++i)
+ {
+ setup_module_from_img(module_names[i], img_paths[i]);
+ }
+
+ kapi::boot_modules::set_boot_module_registry(m_registry);
+ kernel::devices::storage::management::init();
+ }
+
+ auto storage_boot_module_fixture::setup_module_from_img(kstd::string const & module_name,
+ std::filesystem::path const & img_path) -> void
+ {
+ auto file = std::ifstream{img_path, std::ios::binary | std::ios::ate};
+ if (!file)
+ {
+ throw std::runtime_error{"Failed to open image file for test boot module: " + img_path.string()};
+ }
+
+ auto const end_pos = file.tellg();
+ if (end_pos < 0)
+ {
+ throw std::runtime_error{"Failed to determine image file size for test boot module: " + img_path.string()};
+ }
+
+ auto const size = static_cast<std::size_t>(end_pos);
+ file.seekg(0, std::ios::beg);
+
+ m_module_names.push_back(module_name);
+ m_module_data.emplace_back(size);
+
+ if (size > 0)
+ {
+ file.read(reinterpret_cast<char *>(m_module_data.back().data()), static_cast<std::streamsize>(size));
+ if (!file)
+ {
+ throw std::runtime_error{"Failed to read image file content for test boot module: " + img_path.string()};
+ }
+ }
+
+ m_registry.add_boot_module(kapi::boot_modules::boot_module{
+ m_module_names.back().view(), kapi::memory::linear_address{m_module_data.back().data()},
+ m_module_data.back().size()});
+ }
+} // namespace kernel::tests::filesystem \ No newline at end of file
diff --git a/kernel/src/test_support/filesystem/storage_boot_module_vfs_fixture.cpp b/kernel/src/test_support/filesystem/storage_boot_module_vfs_fixture.cpp
new file mode 100644
index 0000000..cba7278
--- /dev/null
+++ b/kernel/src/test_support/filesystem/storage_boot_module_vfs_fixture.cpp
@@ -0,0 +1,32 @@
+#include "kernel/test_support/filesystem/storage_boot_module_vfs_fixture.hpp"
+
+#include "kernel/filesystem/vfs.hpp"
+#include "kernel/test_support/filesystem/vfs.hpp"
+
+#include <kstd/string>
+#include <kstd/vector>
+
+#include <cstddef>
+#include <filesystem>
+
+namespace kernel::tests::filesystem
+{
+ storage_boot_module_vfs_fixture::~storage_boot_module_vfs_fixture()
+ {
+ kernel::tests::filesystem::vfs::deinit();
+ }
+
+ auto storage_boot_module_vfs_fixture::setup_modules_and_init_vfs(std::size_t module_count, std::size_t module_size)
+ -> void
+ {
+ setup_modules(module_count, module_size);
+ kernel::filesystem::vfs::init();
+ }
+
+ auto storage_boot_module_vfs_fixture::setup_modules_from_img_and_init_vfs(
+ kstd::vector<kstd::string> const & module_names, kstd::vector<std::filesystem::path> const & img_paths) -> void
+ {
+ setup_modules_from_img(module_names, img_paths);
+ kernel::filesystem::vfs::init();
+ }
+} // namespace kernel::tests::filesystem \ No newline at end of file
diff --git a/kernel/src/test_support/filesystem/test_assets/ext2_1KB_fs.img b/kernel/src/test_support/filesystem/test_assets/ext2_1KB_fs.img
new file mode 100644
index 0000000..9f1ee4a
--- /dev/null
+++ b/kernel/src/test_support/filesystem/test_assets/ext2_1KB_fs.img
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:94d3988bc309eb9e81f06042c72bf4c4fb5991cd7fdd597eb00c518a96c792d8
+size 10485760
diff --git a/kernel/src/test_support/filesystem/test_assets/ext2_2KB_fs.img b/kernel/src/test_support/filesystem/test_assets/ext2_2KB_fs.img
new file mode 100644
index 0000000..1880911
--- /dev/null
+++ b/kernel/src/test_support/filesystem/test_assets/ext2_2KB_fs.img
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9a13da5abb9c65c737105b1da0d4344c7cd7604c7952c762c4f4e3d3f96fd42d
+size 5242880
diff --git a/kernel/src/test_support/filesystem/test_assets/ext2_4KB_fs.img b/kernel/src/test_support/filesystem/test_assets/ext2_4KB_fs.img
new file mode 100644
index 0000000..3aaceb8
--- /dev/null
+++ b/kernel/src/test_support/filesystem/test_assets/ext2_4KB_fs.img
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4ce6a1aea277906e1af6de223c017ff900b96569f076b4d99fc04eaa1ee986f4
+size 10485760
diff --git a/kernel/src/test_support/state_reset_listener.cpp b/kernel/src/test_support/state_reset_listener.cpp
index 8c95cc8..9120c89 100644
--- a/kernel/src/test_support/state_reset_listener.cpp
+++ b/kernel/src/test_support/state_reset_listener.cpp
@@ -2,8 +2,13 @@
#include "kapi/cpu.hpp"
#include "kapi/memory.hpp"
+#include "kernel/filesystem/file_descriptor_table.hpp"
+#include "kernel/test_support/boot_modules.hpp"
#include "kernel/test_support/cio.hpp"
#include "kernel/test_support/cpu.hpp"
+#include "kernel/test_support/devices/storage/management.hpp"
+#include "kernel/test_support/filesystem/file_descriptor_table.hpp"
+#include "kernel/test_support/filesystem/vfs.hpp"
#include "kernel/test_support/memory.hpp"
#include <catch2/catch_test_case_info.hpp>
@@ -17,6 +22,8 @@ struct state_reset_listener : Catch::EventListenerBase
void testCaseStarting(Catch::TestCaseInfo const &) override
{
+ kernel::filesystem::file_descriptor_table::init();
+
kapi::cio::init();
kapi::cpu::init();
kapi::memory::init();
@@ -24,6 +31,11 @@ struct state_reset_listener : Catch::EventListenerBase
void testCaseEnded(Catch::TestCaseStats const &) override
{
+ kernel::tests::filesystem::file_descriptor_table::deinit();
+ kernel::tests::filesystem::vfs::deinit();
+ kernel::tests::boot_modules::deinit();
+ kernel::tests::devices::storage::management::deinit();
+
kernel::tests::memory::deinit();
kernel::tests::cpu::deinit();
kernel::tests::cio::deinit();