From b3209ac2564f21f3b78ecf5e0c05ca346a4a4276 Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Tue, 28 Apr 2026 10:49:34 +0200 Subject: refactor inode kind --- kernel/include/kernel/filesystem/devfs/inode.hpp | 10 +++--- kernel/include/kernel/filesystem/device_inode.hpp | 5 +++ kernel/include/kernel/filesystem/ext2/inode.hpp | 21 +++++++++--- kernel/include/kernel/filesystem/inode.hpp | 25 +++------------ .../include/kernel/filesystem/open_file_table.hpp | 4 +-- kernel/include/kernel/filesystem/rootfs/inode.hpp | 10 +++--- .../kernel/test_support/filesystem/inode.hpp | 4 +-- kernel/src/filesystem/devfs/inode.cpp | 11 +++---- kernel/src/filesystem/device_inode.cpp | 9 ++++-- kernel/src/filesystem/ext2/filesystem.cpp | 36 +++------------------ kernel/src/filesystem/ext2/inode.cpp | 21 ++++++++++-- kernel/src/filesystem/ext2/inode.tests.cpp | 37 +++++++++++++++------- kernel/src/filesystem/inode.cpp | 10 ++---- kernel/src/filesystem/rootfs/inode.cpp | 9 +++--- kernel/src/test_support/filesystem/inode.cpp | 9 +++--- 15 files changed, 113 insertions(+), 108 deletions(-) diff --git a/kernel/include/kernel/filesystem/devfs/inode.hpp b/kernel/include/kernel/filesystem/devfs/inode.hpp index 0fab280..5589730 100644 --- a/kernel/include/kernel/filesystem/devfs/inode.hpp +++ b/kernel/include/kernel/filesystem/devfs/inode.hpp @@ -13,11 +13,6 @@ namespace kernel::filesystem::devfs */ 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. @@ -35,6 +30,11 @@ namespace kernel::filesystem::devfs @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; + + /** + // % TODO BA-FS26 + */ + [[nodiscard]] auto is_directory() const -> bool override; }; } // namespace kernel::filesystem::devfs diff --git a/kernel/include/kernel/filesystem/device_inode.hpp b/kernel/include/kernel/filesystem/device_inode.hpp index 2f79fca..fb60524 100644 --- a/kernel/include/kernel/filesystem/device_inode.hpp +++ b/kernel/include/kernel/filesystem/device_inode.hpp @@ -50,6 +50,11 @@ namespace kernel::filesystem */ [[nodiscard]] auto device() const -> kstd::shared_ptr const &; + /** + // TODO BA-FS26 + */ + [[nodiscard]] auto is_device() const -> bool override; + private: kstd::shared_ptr m_device; }; diff --git a/kernel/include/kernel/filesystem/ext2/inode.hpp b/kernel/include/kernel/filesystem/ext2/inode.hpp index 9291eea..64cdb06 100644 --- a/kernel/include/kernel/filesystem/ext2/inode.hpp +++ b/kernel/include/kernel/filesystem/ext2/inode.hpp @@ -42,8 +42,10 @@ namespace kernel::filesystem::ext2 { /** @brief Create an ext2 inode associated with the given filesystem. + @param fs The ext2 filesystem that this inode belongs to. + @param data The data associated with this inode, read from the disk. */ - explicit inode(filesystem * fs); + explicit inode(filesystem * fs, inode_data const & data); /** @brief Reads from the ext2 inode into a @p buffer, starting at the specified @p offset and for a given @p size. @@ -65,12 +67,23 @@ namespace kernel::filesystem::ext2 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{}; + // TODO BA-FS26 + */ + [[nodiscard]] auto data() const -> inode_data const &; + + /** + // TODO BA-FS26 + */ + [[nodiscard]] auto is_directory() const -> bool override; + + /** + // TODO BA-FS26 + */ + [[nodiscard]] auto is_regular() const -> bool override; private: filesystem * m_filesystem; + inode_data m_data{}; }; } // namespace kernel::filesystem::ext2 diff --git a/kernel/include/kernel/filesystem/inode.hpp b/kernel/include/kernel/filesystem/inode.hpp index 5208be2..d071a6b 100644 --- a/kernel/include/kernel/filesystem/inode.hpp +++ b/kernel/include/kernel/filesystem/inode.hpp @@ -11,20 +11,9 @@ namespace kernel::filesystem struct inode { /** - @brief Represents the kind of an inode. + @brief Create an inode. */ - enum class inode_kind - { - regular, - directory, - device - }; - - /** - @brief Create an inode with the given @p kind. - @param kind The kind of the inode. - */ - explicit inode(inode_kind kind); + inode() = default; /** @brief Virtual destructor for the inode. @@ -55,23 +44,19 @@ namespace kernel::filesystem @brief Returns whether the inode is a directory. @return true if the inode is a directory, false otherwise. */ - [[nodiscard]] auto is_directory() const -> bool; + [[nodiscard]] virtual 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; + [[nodiscard]] virtual 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; - - // TODO BA-FS26 avoid public member - public: - inode_kind m_kind{inode_kind::regular}; + [[nodiscard]] virtual auto is_device() const -> bool; }; } // namespace kernel::filesystem diff --git a/kernel/include/kernel/filesystem/open_file_table.hpp b/kernel/include/kernel/filesystem/open_file_table.hpp index 2f9a421..694f3b6 100644 --- a/kernel/include/kernel/filesystem/open_file_table.hpp +++ b/kernel/include/kernel/filesystem/open_file_table.hpp @@ -34,10 +34,10 @@ namespace kernel::filesystem /** @brief Add a file to the open file table. - @param f The file descriptor to add. + @param fd The file descriptor to add. @return The file descriptor index assigned to the file, or -1 on failure. */ - auto add_file(kstd::shared_ptr const & f) -> int; + auto add_file(kstd::shared_ptr const & fd) -> int; /** @brief Get a file from the open file table. diff --git a/kernel/include/kernel/filesystem/rootfs/inode.hpp b/kernel/include/kernel/filesystem/rootfs/inode.hpp index 37d0a30..e7c7eff 100644 --- a/kernel/include/kernel/filesystem/rootfs/inode.hpp +++ b/kernel/include/kernel/filesystem/rootfs/inode.hpp @@ -21,11 +21,6 @@ namespace kernel::filesystem::rootfs */ 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. @@ -57,6 +52,11 @@ namespace kernel::filesystem::rootfs */ auto lookup_child(std::string_view name) -> kstd::shared_ptr; + /** + // TODO BA-FS26 + */ + [[nodiscard]] auto is_directory() const -> bool override; + private: kstd::vector>> m_children; }; diff --git a/kernel/include/kernel/test_support/filesystem/inode.hpp b/kernel/include/kernel/test_support/filesystem/inode.hpp index 9d17917..8a76437 100644 --- a/kernel/include/kernel/test_support/filesystem/inode.hpp +++ b/kernel/include/kernel/test_support/filesystem/inode.hpp @@ -9,10 +9,10 @@ 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; + + [[nodiscard]] auto is_regular() const -> bool override; }; } // namespace kernel::tests::filesystem diff --git a/kernel/src/filesystem/devfs/inode.cpp b/kernel/src/filesystem/devfs/inode.cpp index 0ed66ad..2029a7f 100644 --- a/kernel/src/filesystem/devfs/inode.cpp +++ b/kernel/src/filesystem/devfs/inode.cpp @@ -1,15 +1,9 @@ #include -#include - #include namespace kernel::filesystem::devfs { - inode::inode() - : kernel::filesystem::inode(inode_kind::directory) - {} - auto inode::read(void * /*buffer*/, size_t /*offset*/, size_t /*size*/) const -> size_t { return 0; @@ -19,4 +13,9 @@ namespace kernel::filesystem::devfs { return 0; } + + auto inode::is_directory() const -> bool + { + return true; + } } // namespace kernel::filesystem::devfs \ No newline at end of file diff --git a/kernel/src/filesystem/device_inode.cpp b/kernel/src/filesystem/device_inode.cpp index 3bafe06..81a784c 100644 --- a/kernel/src/filesystem/device_inode.cpp +++ b/kernel/src/filesystem/device_inode.cpp @@ -1,7 +1,6 @@ #include #include -#include #include #include @@ -13,8 +12,7 @@ namespace kernel::filesystem { device_inode::device_inode(kstd::shared_ptr const & device) - : inode(inode_kind::device) - , m_device(device) + : m_device(device) { if (!device) { @@ -51,4 +49,9 @@ namespace kernel::filesystem return m_device; } + auto device_inode::is_device() const -> bool + { + return true; + } + } // namespace kernel::filesystem \ No newline at end of file diff --git a/kernel/src/filesystem/ext2/filesystem.cpp b/kernel/src/filesystem/ext2/filesystem.cpp index 41572ee..47e54fe 100644 --- a/kernel/src/filesystem/ext2/filesystem.cpp +++ b/kernel/src/filesystem/ext2/filesystem.cpp @@ -16,19 +16,6 @@ namespace kernel::filesystem::ext2 { - namespace - { - 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 const & backing_inode) -> operation_result { kernel::filesystem::filesystem::mount(backing_inode); @@ -74,7 +61,7 @@ namespace kernel::filesystem::ext2 } auto const block_size = get_block_size(); - auto const & inode_data = ext2_parent->m_data; + auto const & inode_data = ext2_parent->data(); kstd::vector buffer(block_size); for (uint32_t i = 0; i < get_inode_block_count(inode_data); ++i) @@ -119,25 +106,10 @@ namespace kernel::filesystem::ext2 auto const inode_table_offset = static_cast(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(this); - m_backing_inode->read(&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; - } + auto new_inode_data = inode_data{}; + m_backing_inode->read(&new_inode_data, inode_offset, sizeof(inode_data)); - return new_inode; + return kstd::make_shared(this, new_inode_data); } auto filesystem::map_inode_block_index_to_global_block_number(uint32_t inode_block_index, inode_data data) -> uint32_t diff --git a/kernel/src/filesystem/ext2/inode.cpp b/kernel/src/filesystem/ext2/inode.cpp index c45c41e..279c84f 100644 --- a/kernel/src/filesystem/ext2/inode.cpp +++ b/kernel/src/filesystem/ext2/inode.cpp @@ -11,9 +11,9 @@ namespace kernel::filesystem::ext2 { - inode::inode(filesystem * fs) - : kernel::filesystem::inode(inode_kind::regular) - , m_filesystem(fs) + inode::inode(filesystem * fs, inode_data const & data) + : m_filesystem(fs) + , m_data(data) { if (!m_filesystem) { @@ -57,4 +57,19 @@ namespace kernel::filesystem::ext2 kapi::system::panic("[EXT2] inode::write is not implemented yet"); return 0; } + + [[nodiscard]] auto inode::data() const -> inode_data const & + { + return m_data; + } + + auto inode::is_regular() const -> bool + { + return (m_data.mode & constants::mode_mask) == constants::mode_regular; + } + + auto inode::is_directory() const -> bool + { + return (m_data.mode & constants::mode_mask) == constants::mode_directory; + } } // namespace kernel::filesystem::ext2 diff --git a/kernel/src/filesystem/ext2/inode.tests.cpp b/kernel/src/filesystem/ext2/inode.tests.cpp index 4d61790..e68352f 100644 --- a/kernel/src/filesystem/ext2/inode.tests.cpp +++ b/kernel/src/filesystem/ext2/inode.tests.cpp @@ -23,21 +23,34 @@ SCENARIO("Ext2 inode initialization and properties", "[filesystem][ext2][inode]" GIVEN("an ext2 filesystem") { auto fs = kernel::filesystem::ext2::filesystem{}; + auto data = kernel::filesystem::ext2::inode_data{}; - THEN("the inode is initialized and has the kind regular") + THEN("the inode is initialized with regular file mode in data and has the kind regular") { - auto inode = kernel::filesystem::ext2::inode{&fs}; + data.mode = kernel::filesystem::ext2::constants::mode_regular; + auto inode = kernel::filesystem::ext2::inode(&fs, data); + REQUIRE(inode.is_regular()); REQUIRE(!inode.is_directory()); REQUIRE(!inode.is_device()); } + + THEN("the inode is initialized with directory mode in data and has the kind directory") + { + data.mode = kernel::filesystem::ext2::constants::mode_directory; + auto inode = kernel::filesystem::ext2::inode(&fs, data); + + 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); + REQUIRE_THROWS_AS(kernel::filesystem::ext2::inode(nullptr, {}), kernel::tests::cpu::halt); } } } @@ -102,9 +115,10 @@ SCENARIO("Ext2 inode read stops when block mapping resolves to zero", "[filesyst auto fs = kernel::filesystem::ext2::filesystem{}; REQUIRE(fs.mount(dev_inode) == 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 data = kernel::filesystem::ext2::inode_data{}; + data.blocks = 2; + data.block[0] = 0; + auto inode = kernel::filesystem::ext2::inode{&fs, data}; auto buffer = kstd::vector(32, std::byte{0xAB}); @@ -130,12 +144,13 @@ SCENARIO("Ext2 inode read across block boundaries", "[filesystem][ext2][inode]") auto fs = kernel::filesystem::ext2::filesystem{}; REQUIRE(fs.mount(dev_inode) == kernel::filesystem::filesystem::operation_result::success); - auto inode = kernel::filesystem::ext2::inode{&fs}; - inode.m_data.blocks = 2; - inode.m_data.block[0] = 20; + auto inode_data = kernel::filesystem::ext2::inode_data{}; + inode_data.blocks = 2; + inode_data.block[0] = 20; kernel::tests::filesystem::ext2::write_bytes(*device, 21 * block_size - 6, "Hello ", 6); - inode.m_data.block[1] = 21; + inode_data.block[1] = 21; kernel::tests::filesystem::ext2::write_bytes(*device, 21 * block_size, "World!", 6); + auto inode = kernel::filesystem::ext2::inode{&fs, inode_data}; auto buffer = kstd::vector(12, std::byte{0x00}); @@ -155,7 +170,7 @@ 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}; + auto inode = kernel::filesystem::ext2::inode{&fs, kernel::filesystem::ext2::inode_data{}}; THEN("writing to the inode panics") { diff --git a/kernel/src/filesystem/inode.cpp b/kernel/src/filesystem/inode.cpp index 2f0764c..f2a7741 100644 --- a/kernel/src/filesystem/inode.cpp +++ b/kernel/src/filesystem/inode.cpp @@ -2,22 +2,18 @@ namespace kernel::filesystem { - inode::inode(inode_kind kind) - : m_kind(kind) - {} - auto inode::is_directory() const -> bool { - return m_kind == inode_kind::directory; + return false; } auto inode::is_regular() const -> bool { - return m_kind == inode_kind::regular; + return false; } auto inode::is_device() const -> bool { - return m_kind == inode_kind::device; + return false; } } // namespace kernel::filesystem \ No newline at end of file diff --git a/kernel/src/filesystem/rootfs/inode.cpp b/kernel/src/filesystem/rootfs/inode.cpp index eeea3fe..d099676 100644 --- a/kernel/src/filesystem/rootfs/inode.cpp +++ b/kernel/src/filesystem/rootfs/inode.cpp @@ -12,10 +12,6 @@ namespace kernel::filesystem::rootfs { - inode::inode() - : kernel::filesystem::inode(inode_kind::directory) - {} - auto inode::read(void * /*buffer*/, size_t /*offset*/, size_t /*size*/) const -> size_t { return 0; @@ -36,4 +32,9 @@ namespace kernel::filesystem::rootfs auto it = std::ranges::find_if(m_children, [&](auto const & pair) { return pair.first == name; }); return (it != m_children.end()) ? it->second : nullptr; } + + auto inode::is_directory() const -> bool + { + return true; + } } // namespace kernel::filesystem::rootfs diff --git a/kernel/src/test_support/filesystem/inode.cpp b/kernel/src/test_support/filesystem/inode.cpp index 54bd7e0..0c8d956 100644 --- a/kernel/src/test_support/filesystem/inode.cpp +++ b/kernel/src/test_support/filesystem/inode.cpp @@ -6,10 +6,6 @@ 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; @@ -19,4 +15,9 @@ namespace kernel::tests::filesystem { return size; } + + auto inode::is_regular() const -> bool + { + return true; + } } // namespace kernel::tests::filesystem \ No newline at end of file -- cgit v1.2.3 From 6790ab170578594f26e8e84a3e57b80cb6094e21 Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Tue, 28 Apr 2026 10:55:37 +0200 Subject: add is_symbolic_link method in ext2 inode --- .../include/kernel/filesystem/ext2/filesystem.hpp | 1 + kernel/include/kernel/filesystem/ext2/inode.hpp | 5 +++++ kernel/include/kernel/filesystem/inode.hpp | 6 ++++++ kernel/src/filesystem/ext2/inode.cpp | 5 +++++ kernel/src/filesystem/ext2/inode.tests.cpp | 24 ++++++++++++++++++++++ kernel/src/filesystem/inode.cpp | 5 +++++ 6 files changed, 46 insertions(+) diff --git a/kernel/include/kernel/filesystem/ext2/filesystem.hpp b/kernel/include/kernel/filesystem/ext2/filesystem.hpp index 516e71d..d22433f 100644 --- a/kernel/include/kernel/filesystem/ext2/filesystem.hpp +++ b/kernel/include/kernel/filesystem/ext2/filesystem.hpp @@ -35,6 +35,7 @@ namespace kernel::filesystem::ext2 constexpr uint16_t inline mode_mask = 0xF000; constexpr uint16_t inline mode_regular = 0x8000; constexpr uint16_t inline mode_directory = 0x4000; + constexpr uint16_t inline mode_symbolic_link = 0xA000; } // namespace constants /** diff --git a/kernel/include/kernel/filesystem/ext2/inode.hpp b/kernel/include/kernel/filesystem/ext2/inode.hpp index 64cdb06..688a1d8 100644 --- a/kernel/include/kernel/filesystem/ext2/inode.hpp +++ b/kernel/include/kernel/filesystem/ext2/inode.hpp @@ -81,6 +81,11 @@ namespace kernel::filesystem::ext2 */ [[nodiscard]] auto is_regular() const -> bool override; + /** + // TODO BA-FS26 + */ + [[nodiscard]] auto is_symbolic_link() const -> bool override; + private: filesystem * m_filesystem; inode_data m_data{}; diff --git a/kernel/include/kernel/filesystem/inode.hpp b/kernel/include/kernel/filesystem/inode.hpp index d071a6b..b34b921 100644 --- a/kernel/include/kernel/filesystem/inode.hpp +++ b/kernel/include/kernel/filesystem/inode.hpp @@ -57,6 +57,12 @@ namespace kernel::filesystem @return true if the inode is a device, false otherwise. */ [[nodiscard]] virtual auto is_device() const -> bool; + + /** + @brief Returns whether the inode is a symbolic link. + @return true if the inode is a symbolic link, false otherwise. + */ + [[nodiscard]] virtual auto is_symbolic_link() const -> bool; }; } // namespace kernel::filesystem diff --git a/kernel/src/filesystem/ext2/inode.cpp b/kernel/src/filesystem/ext2/inode.cpp index 279c84f..5b6bcb3 100644 --- a/kernel/src/filesystem/ext2/inode.cpp +++ b/kernel/src/filesystem/ext2/inode.cpp @@ -72,4 +72,9 @@ namespace kernel::filesystem::ext2 { return (m_data.mode & constants::mode_mask) == constants::mode_directory; } + + auto inode::is_symbolic_link() const -> bool + { + return (m_data.mode & constants::mode_mask) == constants::mode_symbolic_link; + } } // namespace kernel::filesystem::ext2 diff --git a/kernel/src/filesystem/ext2/inode.tests.cpp b/kernel/src/filesystem/ext2/inode.tests.cpp index e68352f..f0f4aaf 100644 --- a/kernel/src/filesystem/ext2/inode.tests.cpp +++ b/kernel/src/filesystem/ext2/inode.tests.cpp @@ -33,6 +33,7 @@ SCENARIO("Ext2 inode initialization and properties", "[filesystem][ext2][inode]" REQUIRE(inode.is_regular()); REQUIRE(!inode.is_directory()); REQUIRE(!inode.is_device()); + REQUIRE(!inode.is_symbolic_link()); } THEN("the inode is initialized with directory mode in data and has the kind directory") @@ -43,6 +44,29 @@ SCENARIO("Ext2 inode initialization and properties", "[filesystem][ext2][inode]" REQUIRE(!inode.is_regular()); REQUIRE(inode.is_directory()); REQUIRE(!inode.is_device()); + REQUIRE(!inode.is_symbolic_link()); + } + + THEN("the inode is initialized with symbolic link mode in data and has the kind symbolic link") + { + data.mode = kernel::filesystem::ext2::constants::mode_symbolic_link; + auto inode = kernel::filesystem::ext2::inode(&fs, data); + + REQUIRE(!inode.is_regular()); + REQUIRE(!inode.is_directory()); + REQUIRE(!inode.is_device()); + REQUIRE(inode.is_symbolic_link()); + } + + THEN("the inode is initialized with zero mode in data and has no specific kind") + { + data.mode = 0; + auto inode = kernel::filesystem::ext2::inode(&fs, data); + + REQUIRE(!inode.is_regular()); + REQUIRE(!inode.is_directory()); + REQUIRE(!inode.is_device()); + REQUIRE(!inode.is_symbolic_link()); } } diff --git a/kernel/src/filesystem/inode.cpp b/kernel/src/filesystem/inode.cpp index f2a7741..c188917 100644 --- a/kernel/src/filesystem/inode.cpp +++ b/kernel/src/filesystem/inode.cpp @@ -16,4 +16,9 @@ namespace kernel::filesystem { return false; } + + auto inode::is_symbolic_link() const -> bool + { + return false; + } } // namespace kernel::filesystem \ No newline at end of file -- cgit v1.2.3 From 62da1b8c8d1c59abc7ca33c144591839f126937e Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Thu, 30 Apr 2026 21:32:49 +0200 Subject: resolve todos --- kernel/include/kernel/filesystem/devfs/inode.hpp | 3 ++- kernel/include/kernel/filesystem/device_inode.hpp | 3 ++- kernel/include/kernel/filesystem/ext2/inode.hpp | 12 ++++++++---- kernel/include/kernel/filesystem/rootfs/inode.hpp | 3 ++- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/kernel/include/kernel/filesystem/devfs/inode.hpp b/kernel/include/kernel/filesystem/devfs/inode.hpp index 5589730..e428891 100644 --- a/kernel/include/kernel/filesystem/devfs/inode.hpp +++ b/kernel/include/kernel/filesystem/devfs/inode.hpp @@ -32,7 +32,8 @@ namespace kernel::filesystem::devfs auto write(void const * buffer, size_t offset, size_t size) -> size_t override; /** - // % TODO BA-FS26 + @brief Check if this inode represents a directory. + @return returns true, since this inode represents the /dev directory in the devfs filesystem. */ [[nodiscard]] auto is_directory() const -> bool override; }; diff --git a/kernel/include/kernel/filesystem/device_inode.hpp b/kernel/include/kernel/filesystem/device_inode.hpp index fb60524..f4aa2d1 100644 --- a/kernel/include/kernel/filesystem/device_inode.hpp +++ b/kernel/include/kernel/filesystem/device_inode.hpp @@ -51,7 +51,8 @@ namespace kernel::filesystem [[nodiscard]] auto device() const -> kstd::shared_ptr const &; /** - // TODO BA-FS26 + @brief Check if this inode represents a device. + @return returns true, since this indoe is a device inode and represents a device. */ [[nodiscard]] auto is_device() const -> bool override; diff --git a/kernel/include/kernel/filesystem/ext2/inode.hpp b/kernel/include/kernel/filesystem/ext2/inode.hpp index 688a1d8..b4a3cc4 100644 --- a/kernel/include/kernel/filesystem/ext2/inode.hpp +++ b/kernel/include/kernel/filesystem/ext2/inode.hpp @@ -67,22 +67,26 @@ namespace kernel::filesystem::ext2 auto write(void const * buffer, size_t offset, size_t size) -> size_t override; /** - // TODO BA-FS26 + @brief Get the data associated with this inode. + @return A reference to the inode data. */ [[nodiscard]] auto data() const -> inode_data const &; /** - // TODO BA-FS26 + @brief Check if this inode represents a directory. + @return returns true if this inode represents a directory, false otherwise. */ [[nodiscard]] auto is_directory() const -> bool override; /** - // TODO BA-FS26 + @brief Check if this inode represents a regular file. + @return returns true if this inode represents a regular file, false otherwise. */ [[nodiscard]] auto is_regular() const -> bool override; /** - // TODO BA-FS26 + @brief Check if this inode represents a symbolic link. + @return returns true if this inode represents a symbolic link, false otherwise. */ [[nodiscard]] auto is_symbolic_link() const -> bool override; diff --git a/kernel/include/kernel/filesystem/rootfs/inode.hpp b/kernel/include/kernel/filesystem/rootfs/inode.hpp index e7c7eff..58035ea 100644 --- a/kernel/include/kernel/filesystem/rootfs/inode.hpp +++ b/kernel/include/kernel/filesystem/rootfs/inode.hpp @@ -53,7 +53,8 @@ namespace kernel::filesystem::rootfs auto lookup_child(std::string_view name) -> kstd::shared_ptr; /** - // TODO BA-FS26 + @brief Check if this inode represents a directory. + @return returns true, since this inode represents the root directory in the rootfs filesystem. */ [[nodiscard]] auto is_directory() const -> bool override; -- cgit v1.2.3 From 8550b6a1aacc2bfce733dcac7a44065b7e9116a1 Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Sat, 2 May 2026 14:14:24 +0200 Subject: relative path support --- kernel/include/kernel/filesystem/dentry.hpp | 2 +- kernel/src/filesystem/mount_table.cpp | 4 +- kernel/src/filesystem/vfs.cpp | 115 ++++++++++++++++++++++++---- 3 files changed, 103 insertions(+), 18 deletions(-) diff --git a/kernel/include/kernel/filesystem/dentry.hpp b/kernel/include/kernel/filesystem/dentry.hpp index 94fa39a..45d2ca2 100644 --- a/kernel/include/kernel/filesystem/dentry.hpp +++ b/kernel/include/kernel/filesystem/dentry.hpp @@ -24,7 +24,7 @@ namespace kernel::filesystem */ enum class dentry_flags : uint32_t { - dcache_mounted = 1 << 15 + mounted = 1 << 15 }; /** diff --git a/kernel/src/filesystem/mount_table.cpp b/kernel/src/filesystem/mount_table.cpp index da3c451..e21e497 100644 --- a/kernel/src/filesystem/mount_table.cpp +++ b/kernel/src/filesystem/mount_table.cpp @@ -54,7 +54,7 @@ namespace kernel::filesystem m_mounts.push_back(mount); if (auto mount_dentry = mount->get_mount_dentry()) { - mount_dentry->set_flag(dentry::dentry_flags::dcache_mounted); + mount_dentry->set_flag(dentry::dentry_flags::mounted); } } @@ -75,7 +75,7 @@ namespace kernel::filesystem return operation_result::has_child_mounts; } - mount->get_mount_dentry()->unset_flag(dentry::dentry_flags::dcache_mounted); + mount->get_mount_dentry()->unset_flag(dentry::dentry_flags::mounted); m_mounts.erase(std::ranges::find(m_mounts, mount)); return operation_result::removed; } diff --git a/kernel/src/filesystem/vfs.cpp b/kernel/src/filesystem/vfs.cpp index 5b454f6..41afad3 100644 --- a/kernel/src/filesystem/vfs.cpp +++ b/kernel/src/filesystem/vfs.cpp @@ -10,6 +10,8 @@ #include #include +#include +#include #include #include @@ -35,21 +37,38 @@ namespace kernel::filesystem auto vfs::init_internal() -> void { + // Mount rootfs at / as the temporary base auto root_fs = kstd::make_shared(); root_fs->mount(nullptr); - auto root_fs_root_dentry = kstd::make_shared(nullptr, root_fs->root_inode()); - m_mount_table.add_mount(kstd::make_shared(nullptr, root_fs_root_dentry, root_fs, "", nullptr)); + auto root_fs_root_dentry = kstd::make_shared(nullptr, root_fs->root_inode(), "/"); + m_mount_table.add_mount(kstd::make_shared(nullptr, root_fs_root_dentry, root_fs, "/", nullptr)); + // Mount devfs at /dev in rootfs (temporary, will be shadowed) auto device_fs = kstd::make_shared(); device_fs->mount(nullptr); - do_mount_internal("/dev", root_fs_root_dentry, device_fs); + auto dev_mount_point_dentry = resolve_path("/dev"); + if (!dev_mount_point_dentry) + { + kapi::system::panic("[FILESYSTEM] failed to resolve /dev for initial devfs mount."); + } + do_mount_internal("/dev", dev_mount_point_dentry, device_fs); - if (auto boot_device_dentry = resolve_path("/dev/ram0")) // TODO BA-FS26 better boot device detection + // Mount boot filesystem at / (will shadow rootfs) + if (auto boot_device_dentry = resolve_path("/dev/ram0")) { if (auto boot_root_fs = kernel::filesystem::filesystem::probe_and_mount(boot_device_dentry->get_inode())) { do_mount_internal("/", root_fs_root_dentry, boot_root_fs); + + // Resolve / to get the boot root dentry + if (auto boot_root_dentry = resolve_path("/")) + { + auto dev_dentry = kstd::make_shared(boot_root_dentry, device_fs->root_inode(), "dev"); + boot_root_dentry->add_child(dev_dentry); + + do_mount_internal("/dev", dev_dentry, device_fs); + } } } } @@ -117,33 +136,46 @@ namespace kernel::filesystem kstd::shared_ptr const & fs) -> void { auto parent_mount = m_mount_table.find_longest_prefix_mount(path); - auto new_fs_root = kstd::make_shared(mount_point_dentry, fs->root_inode()); + auto new_fs_root = + kstd::make_shared(mount_point_dentry->get_parent(), fs->root_inode(), mount_point_dentry->get_name()); auto new_mount = kstd::make_shared(mount_point_dentry, new_fs_root, fs, path, parent_mount); m_mount_table.add_mount(new_mount); } auto vfs::resolve_path(std::string_view path) -> kstd::shared_ptr { - // TODO BA-FS26 implement full path resolution semantics. // TODO BA-FS26 better path validation // TODO BA-FS26 implement a path parser (maybe in libs?) and use it here and in do_mount - if (path.empty() || path.front() != '/') return nullptr; - // TODO BA-FS26 longest match in mount_table -> jump into final fs directly - // TODO BA-FS26 performance optimization first check mounted_flag O(1) then check mount_table O(n) - - auto best_mount = m_mount_table.find_longest_prefix_mount(path); - if (!best_mount) + auto current_mount = m_mount_table.find_longest_prefix_mount("/"); + if (!current_mount) { kapi::system::panic("[FILESYSTEM] no root mount found."); } - auto current_dentry = best_mount->root_dentry(); - auto current_fs = best_mount->get_filesystem(); + auto current_dentry = current_mount->root_dentry(); + kstd::vector resolved_parts{}; + + // TODO BA-FS26 remove again an get path out of the dentires instead of building it up again here + auto build_resolved_path = [&resolved_parts]() { + kstd::string resolved_path{"/"}; + + for (auto const & resolved_part : resolved_parts) + { + if (resolved_path.size() > 1) + { + resolved_path += '/'; + } + + resolved_path += resolved_part; + } - std::string_view remaining = path.substr(best_mount->get_mount_path().size()); + return resolved_path; + }; + + std::string_view remaining = path.substr(current_mount->get_mount_path().size()); auto path_parts = std::views::split(remaining, '/') | std::views::filter([](auto const & part) { return !part.empty(); }); @@ -152,9 +184,50 @@ namespace kernel::filesystem { std::string_view part_view{part}; + if (part_view == ".") + { + continue; + } + + if (part_view == "..") + { + if (!resolved_parts.empty()) + { + resolved_parts.pop_back(); + } + + auto parent_dentry = current_dentry->get_parent(); + + if (current_dentry == current_mount->root_dentry()) + { + // change the mount point + if (auto parent_mount = current_mount->get_parent_mount()) + { + current_mount = parent_mount; + current_dentry = current_dentry->get_parent(); + + if (!parent_dentry) + { + parent_dentry = current_mount->root_dentry(); + } + } + } + + if (!parent_dentry) + { + return nullptr; + } + + current_dentry = parent_dentry; + continue; + } + + resolved_parts.push_back(part_view); + auto next_dentry = current_dentry->find_child(part_view); if (!next_dentry) { + auto current_fs = current_mount->get_filesystem(); auto found_inode = current_fs->lookup(current_dentry->get_inode(), part_view); if (!found_inode) return nullptr; @@ -162,6 +235,18 @@ namespace kernel::filesystem next_dentry = kstd::make_shared(current_dentry, found_inode, part_view); current_dentry->add_child(next_dentry); } + else if (next_dentry->has_flag(dentry::dentry_flags::mounted)) + { + // change the mount point + // TODO BA-FS26 get resolved path out of the dentry... + current_mount = m_mount_table.find_longest_prefix_mount(build_resolved_path().view()); + if (!current_mount) + { + kapi::system::panic("[FILESYSTEM] mount for dentry with mounted flag not found."); + } + + next_dentry = current_mount->root_dentry(); + } current_dentry = next_dentry; } -- cgit v1.2.3 From a096d84920aa078b5775149a95e5d3f010ae995e Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Sat, 2 May 2026 14:14:55 +0200 Subject: fix bht build --- kernel/src/filesystem/dentry.tests.cpp | 16 ++++++++-------- kernel/src/filesystem/mount_table.tests.cpp | 10 +++++----- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/kernel/src/filesystem/dentry.tests.cpp b/kernel/src/filesystem/dentry.tests.cpp index f81c260..c42c405 100644 --- a/kernel/src/filesystem/dentry.tests.cpp +++ b/kernel/src/filesystem/dentry.tests.cpp @@ -28,7 +28,7 @@ SCENARIO("Dentry construction", "[filesystem][dentry]") THEN("no flag is set") { - REQUIRE_FALSE(child_dentry.has_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted)); + REQUIRE_FALSE(child_dentry.has_flag(kernel::filesystem::dentry::dentry_flags::mounted)); } } @@ -45,7 +45,7 @@ SCENARIO("Dentry construction", "[filesystem][dentry]") THEN("no flag is set") { - REQUIRE_FALSE(child_dentry.has_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted)); + REQUIRE_FALSE(child_dentry.has_flag(kernel::filesystem::dentry::dentry_flags::mounted)); } } @@ -62,7 +62,7 @@ SCENARIO("Dentry construction", "[filesystem][dentry]") THEN("no flag is set") { - REQUIRE_FALSE(child_dentry.has_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted)); + REQUIRE_FALSE(child_dentry.has_flag(kernel::filesystem::dentry::dentry_flags::mounted)); } } @@ -113,22 +113,22 @@ SCENARIO("Dentry Flag logic", "[filesystem][dentry]") WHEN("setting a flag") { - dentry.set_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted); + dentry.set_flag(kernel::filesystem::dentry::dentry_flags::mounted); THEN("the flag is set") { - REQUIRE(dentry.has_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted)); + REQUIRE(dentry.has_flag(kernel::filesystem::dentry::dentry_flags::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); + dentry.set_flag(kernel::filesystem::dentry::dentry_flags::mounted); + dentry.unset_flag(kernel::filesystem::dentry::dentry_flags::mounted); THEN("the flag is unset") { - REQUIRE_FALSE(dentry.has_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted)); + REQUIRE_FALSE(dentry.has_flag(kernel::filesystem::dentry::dentry_flags::mounted)); } } } diff --git a/kernel/src/filesystem/mount_table.tests.cpp b/kernel/src/filesystem/mount_table.tests.cpp index 747ffdf..60b1755 100644 --- a/kernel/src/filesystem/mount_table.tests.cpp +++ b/kernel/src/filesystem/mount_table.tests.cpp @@ -54,8 +54,8 @@ SCENARIO("Adding, finding and removing mounts in the mount table", "[filesystem] 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)); + REQUIRE(root_dentry1->has_flag(kernel::filesystem::dentry::dentry_flags::mounted)); + REQUIRE(!root_dentry2->has_flag(kernel::filesystem::dentry::dentry_flags::mounted)); } THEN("finding mounts by path returns the correct mount") @@ -70,7 +70,7 @@ SCENARIO("Adding, finding and removing mounts in the mount table", "[filesystem] 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(!root_dentry2->has_flag(kernel::filesystem::dentry::dentry_flags::mounted)); REQUIRE(table.find_longest_prefix_mount("/mnt") == mount1); } @@ -109,7 +109,7 @@ SCENARIO("Adding, finding and removing mounts in the mount table", "[filesystem] 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(!root_dentry2->has_flag(kernel::filesystem::dentry::dentry_flags::mounted)); REQUIRE(table.find_longest_prefix_mount("/") == mount1); } } @@ -156,7 +156,7 @@ SCENARIO("Adding, finding and removing mounts in the mount table", "[filesystem] 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(!root_dentry3->has_flag(kernel::filesystem::dentry::dentry_flags::mounted)); REQUIRE(table.find_longest_prefix_mount("/mnt/submnt") == mount2); } } -- cgit v1.2.3 From 46f3992c10e960d33dbb314dad597650902686da Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Sat, 2 May 2026 14:15:26 +0200 Subject: add relative path tests --- kernel/src/filesystem/vfs.tests.cpp | 46 +++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/kernel/src/filesystem/vfs.tests.cpp b/kernel/src/filesystem/vfs.tests.cpp index 979ea42..109ee89 100644 --- a/kernel/src/filesystem/vfs.tests.cpp +++ b/kernel/src/filesystem/vfs.tests.cpp @@ -168,6 +168,52 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, "VFS REQUIRE(vfs.unmount("/information/nonexistent") == kernel::filesystem::vfs::operation_result::mount_point_not_found); } + + THEN("a file can be access if . in the path") + { + auto info_1 = vfs.open("/information/./info_1.txt"); + REQUIRE(info_1 != nullptr); + } + + THEN("a file can be accessed within the same mount if path contains .. ") + { + auto info_1 = vfs.open("/archiv/../information/info_1.txt"); + REQUIRE(info_1 != nullptr); + + auto img = vfs.open("/archiv/../information/../archiv/2024.img"); + REQUIRE(img != nullptr); + } + + THEN("a file can be accessed over multiple mounts if path contains .. or . ") + { + REQUIRE(vfs.do_mount("/dev/ram16", "/information") == kernel::filesystem::vfs::operation_result::success); + + auto img = vfs.open("/information/monkey_house/caretaker/../../../archiv/2024.img"); + REQUIRE(img != nullptr); + + auto dev_32 = vfs.open("/information/monkey_house/caretaker/../../../dev/ram32"); + REQUIRE(dev_32 != nullptr); + + auto water = vfs.open("/information/./monkey_house/infrastructure/water.txt"); + REQUIRE(water != nullptr); + } + + THEN("a file can be accessed over multiple mounts (device and file) if path contains .. ") + { + REQUIRE(vfs.do_mount("/dev/ram16", "/information") == kernel::filesystem::vfs::operation_result::success); + REQUIRE(vfs.do_mount("/archiv/2024.img", "/information/monkey_house/infrastructure") == + kernel::filesystem::vfs::operation_result::success); + + auto pig_1 = vfs.open("/information/monkey_house/infrastructure/stable/pig_1.txt"); + REQUIRE(pig_1 != nullptr); + + auto isabelle = + vfs.open("/information/monkey_house/infrastructure/stable/../../../monkey_house/caretaker/isabelle.txt"); + REQUIRE(isabelle != nullptr); + + auto closed = vfs.open("/information/monkey_house/infrastructure/stable/../../../../closed.txt"); + REQUIRE(closed != nullptr); + } } GIVEN("A real image file containing as filesystem formatted files") -- cgit v1.2.3 From d3c8b74020bfeee554394b7e41c58d5ddda6f396 Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Sat, 2 May 2026 14:23:19 +0200 Subject: refactoring --- kernel/include/kernel/filesystem/dentry.hpp | 6 ++++++ kernel/src/filesystem/dentry.cpp | 17 +++++++++++++++ kernel/src/filesystem/vfs.cpp | 32 ++--------------------------- 3 files changed, 25 insertions(+), 30 deletions(-) diff --git a/kernel/include/kernel/filesystem/dentry.hpp b/kernel/include/kernel/filesystem/dentry.hpp index 45d2ca2..bd62735 100644 --- a/kernel/include/kernel/filesystem/dentry.hpp +++ b/kernel/include/kernel/filesystem/dentry.hpp @@ -54,6 +54,12 @@ namespace kernel::filesystem */ [[nodiscard]] auto get_name() const -> std::string_view; + /** + @brief Get the full path of the dentry by traversing up to the root. + @return The full path of the dentry. + */ + [[nodiscard]] auto get_full_path() const -> kstd::string; + /** @brief Add a @p child dentry. @param child The child dentry to add. diff --git a/kernel/src/filesystem/dentry.cpp b/kernel/src/filesystem/dentry.cpp index 572dd82..72500fd 100644 --- a/kernel/src/filesystem/dentry.cpp +++ b/kernel/src/filesystem/dentry.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -38,6 +39,22 @@ namespace kernel::filesystem return m_name.view(); } + // TODO BA-FS26 fix warning (do not use recursion) + auto dentry::get_full_path() const -> kstd::string + { + if (m_parent) + { + auto parent_path = m_parent->get_full_path(); + if (parent_path != "/") + { + parent_path += '/'; + } + return parent_path + m_name.view(); + } + + return m_name.empty() ? "/" : m_name.view(); + } + auto dentry::add_child(kstd::shared_ptr const & child) -> void { m_children.push_back(child); diff --git a/kernel/src/filesystem/vfs.cpp b/kernel/src/filesystem/vfs.cpp index 41afad3..9a6625d 100644 --- a/kernel/src/filesystem/vfs.cpp +++ b/kernel/src/filesystem/vfs.cpp @@ -11,7 +11,6 @@ #include #include -#include #include #include @@ -156,25 +155,6 @@ namespace kernel::filesystem } auto current_dentry = current_mount->root_dentry(); - kstd::vector resolved_parts{}; - - // TODO BA-FS26 remove again an get path out of the dentires instead of building it up again here - auto build_resolved_path = [&resolved_parts]() { - kstd::string resolved_path{"/"}; - - for (auto const & resolved_part : resolved_parts) - { - if (resolved_path.size() > 1) - { - resolved_path += '/'; - } - - resolved_path += resolved_part; - } - - return resolved_path; - }; - std::string_view remaining = path.substr(current_mount->get_mount_path().size()); auto path_parts = @@ -191,11 +171,6 @@ namespace kernel::filesystem if (part_view == "..") { - if (!resolved_parts.empty()) - { - resolved_parts.pop_back(); - } - auto parent_dentry = current_dentry->get_parent(); if (current_dentry == current_mount->root_dentry()) @@ -222,8 +197,6 @@ namespace kernel::filesystem continue; } - resolved_parts.push_back(part_view); - auto next_dentry = current_dentry->find_child(part_view); if (!next_dentry) { @@ -237,9 +210,8 @@ namespace kernel::filesystem } else if (next_dentry->has_flag(dentry::dentry_flags::mounted)) { - // change the mount point - // TODO BA-FS26 get resolved path out of the dentry... - current_mount = m_mount_table.find_longest_prefix_mount(build_resolved_path().view()); + // TODO BA-FS26 really do it like this? or just call "get" without longes_prefix stuff + current_mount = m_mount_table.find_longest_prefix_mount(next_dentry->get_full_path().view()); if (!current_mount) { kapi::system::panic("[FILESYSTEM] mount for dentry with mounted flag not found."); -- cgit v1.2.3 From e74e56559ae99fd14715371ebceb2545f68008de Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Sat, 2 May 2026 14:24:46 +0200 Subject: add todos --- kernel/src/filesystem/vfs.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/src/filesystem/vfs.cpp b/kernel/src/filesystem/vfs.cpp index 9a6625d..a3da258 100644 --- a/kernel/src/filesystem/vfs.cpp +++ b/kernel/src/filesystem/vfs.cpp @@ -89,6 +89,7 @@ namespace kernel::filesystem auto vfs::do_mount(std::string_view source, std::string_view target) -> operation_result { + // TODO BA-FS26 better path validation if (target.empty() || target.front() != '/' || (target.size() > 1 && target.back() == '/')) { return operation_result::invalid_path; @@ -112,6 +113,7 @@ namespace kernel::filesystem auto vfs::unmount(std::string_view path) -> operation_result { + // TODO BA-FS26 better path validation if (path.empty() || path.front() != '/' || (path.size() > 1 && path.back() == '/')) { return operation_result::invalid_path; -- cgit v1.2.3 From 40fbefab704695b905e3de3e80668447cc64b20e Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Sat, 2 May 2026 15:36:19 +0200 Subject: refactoring and extend tests --- kernel/src/filesystem/devfs/filesystem.tests.cpp | 2 +- kernel/src/filesystem/devfs/inode.tests.cpp | 1 + kernel/src/filesystem/device_inode.tests.cpp | 1 + kernel/src/filesystem/ext2/inode.tests.cpp | 26 ++++++++++++------------ kernel/src/filesystem/mount_table.tests.cpp | 8 ++++---- kernel/src/filesystem/rootfs/inode.tests.cpp | 1 + libs/kstd/kstd/string.test.cpp | 12 +++++------ 7 files changed, 27 insertions(+), 24 deletions(-) diff --git a/kernel/src/filesystem/devfs/filesystem.tests.cpp b/kernel/src/filesystem/devfs/filesystem.tests.cpp index 2b6c09b..36cb411 100644 --- a/kernel/src/filesystem/devfs/filesystem.tests.cpp +++ b/kernel/src/filesystem/devfs/filesystem.tests.cpp @@ -47,7 +47,7 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, { auto non_directory_inode = fs.lookup(fs.root_inode(), "ram0"); REQUIRE(non_directory_inode != nullptr); - REQUIRE(!non_directory_inode->is_directory()); + REQUIRE_FALSE(non_directory_inode->is_directory()); auto result = fs.lookup(non_directory_inode, "anything"); REQUIRE(result == nullptr); diff --git a/kernel/src/filesystem/devfs/inode.tests.cpp b/kernel/src/filesystem/devfs/inode.tests.cpp index 030d709..ae26e74 100644 --- a/kernel/src/filesystem/devfs/inode.tests.cpp +++ b/kernel/src/filesystem/devfs/inode.tests.cpp @@ -19,6 +19,7 @@ SCENARIO("Devfs inode creation", "[filesystem][devfs][inode]") REQUIRE(inode.is_directory()); REQUIRE_FALSE(inode.is_device()); REQUIRE_FALSE(inode.is_regular()); + REQUIRE_FALSE(inode.is_symbolic_link()); } } } diff --git a/kernel/src/filesystem/device_inode.tests.cpp b/kernel/src/filesystem/device_inode.tests.cpp index 8ac4eff..025a22a 100644 --- a/kernel/src/filesystem/device_inode.tests.cpp +++ b/kernel/src/filesystem/device_inode.tests.cpp @@ -33,6 +33,7 @@ SCENARIO("Device inode construction", "[filesystem][device_inode]") REQUIRE(inode.is_device()); REQUIRE_FALSE(inode.is_directory()); REQUIRE_FALSE(inode.is_regular()); + REQUIRE_FALSE(inode.is_symbolic_link()); } } diff --git a/kernel/src/filesystem/ext2/inode.tests.cpp b/kernel/src/filesystem/ext2/inode.tests.cpp index f0f4aaf..49ba21b 100644 --- a/kernel/src/filesystem/ext2/inode.tests.cpp +++ b/kernel/src/filesystem/ext2/inode.tests.cpp @@ -31,9 +31,9 @@ SCENARIO("Ext2 inode initialization and properties", "[filesystem][ext2][inode]" auto inode = kernel::filesystem::ext2::inode(&fs, data); REQUIRE(inode.is_regular()); - REQUIRE(!inode.is_directory()); - REQUIRE(!inode.is_device()); - REQUIRE(!inode.is_symbolic_link()); + REQUIRE_FALSE(inode.is_directory()); + REQUIRE_FALSE(inode.is_device()); + REQUIRE_FALSE(inode.is_symbolic_link()); } THEN("the inode is initialized with directory mode in data and has the kind directory") @@ -41,10 +41,10 @@ SCENARIO("Ext2 inode initialization and properties", "[filesystem][ext2][inode]" data.mode = kernel::filesystem::ext2::constants::mode_directory; auto inode = kernel::filesystem::ext2::inode(&fs, data); - REQUIRE(!inode.is_regular()); + REQUIRE_FALSE(inode.is_regular()); REQUIRE(inode.is_directory()); - REQUIRE(!inode.is_device()); - REQUIRE(!inode.is_symbolic_link()); + REQUIRE_FALSE(inode.is_device()); + REQUIRE_FALSE(inode.is_symbolic_link()); } THEN("the inode is initialized with symbolic link mode in data and has the kind symbolic link") @@ -52,9 +52,9 @@ SCENARIO("Ext2 inode initialization and properties", "[filesystem][ext2][inode]" data.mode = kernel::filesystem::ext2::constants::mode_symbolic_link; auto inode = kernel::filesystem::ext2::inode(&fs, data); - REQUIRE(!inode.is_regular()); - REQUIRE(!inode.is_directory()); - REQUIRE(!inode.is_device()); + REQUIRE_FALSE(inode.is_regular()); + REQUIRE_FALSE(inode.is_directory()); + REQUIRE_FALSE(inode.is_device()); REQUIRE(inode.is_symbolic_link()); } @@ -63,10 +63,10 @@ SCENARIO("Ext2 inode initialization and properties", "[filesystem][ext2][inode]" data.mode = 0; auto inode = kernel::filesystem::ext2::inode(&fs, data); - REQUIRE(!inode.is_regular()); - REQUIRE(!inode.is_directory()); - REQUIRE(!inode.is_device()); - REQUIRE(!inode.is_symbolic_link()); + REQUIRE_FALSE(inode.is_regular()); + REQUIRE_FALSE(inode.is_directory()); + REQUIRE_FALSE(inode.is_device()); + REQUIRE_FALSE(inode.is_symbolic_link()); } } diff --git a/kernel/src/filesystem/mount_table.tests.cpp b/kernel/src/filesystem/mount_table.tests.cpp index 60b1755..e028ac8 100644 --- a/kernel/src/filesystem/mount_table.tests.cpp +++ b/kernel/src/filesystem/mount_table.tests.cpp @@ -55,7 +55,7 @@ SCENARIO("Adding, finding and removing mounts in the mount table", "[filesystem] THEN("dentry flags are set correctly for mounted dentries") { REQUIRE(root_dentry1->has_flag(kernel::filesystem::dentry::dentry_flags::mounted)); - REQUIRE(!root_dentry2->has_flag(kernel::filesystem::dentry::dentry_flags::mounted)); + REQUIRE_FALSE(root_dentry2->has_flag(kernel::filesystem::dentry::dentry_flags::mounted)); } THEN("finding mounts by path returns the correct mount") @@ -70,7 +70,7 @@ SCENARIO("Adding, finding and removing mounts in the mount table", "[filesystem] 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::mounted)); + REQUIRE_FALSE(root_dentry2->has_flag(kernel::filesystem::dentry::dentry_flags::mounted)); REQUIRE(table.find_longest_prefix_mount("/mnt") == mount1); } @@ -109,7 +109,7 @@ SCENARIO("Adding, finding and removing mounts in the mount table", "[filesystem] 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::mounted)); + REQUIRE_FALSE(root_dentry2->has_flag(kernel::filesystem::dentry::dentry_flags::mounted)); REQUIRE(table.find_longest_prefix_mount("/") == mount1); } } @@ -156,7 +156,7 @@ SCENARIO("Adding, finding and removing mounts in the mount table", "[filesystem] 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::mounted)); + REQUIRE_FALSE(root_dentry3->has_flag(kernel::filesystem::dentry::dentry_flags::mounted)); REQUIRE(table.find_longest_prefix_mount("/mnt/submnt") == mount2); } } diff --git a/kernel/src/filesystem/rootfs/inode.tests.cpp b/kernel/src/filesystem/rootfs/inode.tests.cpp index 879818c..7cc217f 100644 --- a/kernel/src/filesystem/rootfs/inode.tests.cpp +++ b/kernel/src/filesystem/rootfs/inode.tests.cpp @@ -17,6 +17,7 @@ SCENARIO("Rootfs inode creation", "[filesystem][rootfs][inode]") REQUIRE(inode.is_directory()); REQUIRE_FALSE(inode.is_device()); REQUIRE_FALSE(inode.is_regular()); + REQUIRE_FALSE(inode.is_symbolic_link()); } THEN("the inode has no children") diff --git a/libs/kstd/kstd/string.test.cpp b/libs/kstd/kstd/string.test.cpp index 53d7c9a..9755676 100644 --- a/libs/kstd/kstd/string.test.cpp +++ b/libs/kstd/kstd/string.test.cpp @@ -37,7 +37,7 @@ SCENARIO("String initialization and construction", "[string]") THEN("the string is not empty and has the same size as the view") { - REQUIRE(!str.empty()); + REQUIRE_FALSE(str.empty()); REQUIRE(str.size() == view.size()); } @@ -58,7 +58,7 @@ SCENARIO("String initialization and construction", "[string]") THEN("the string is not empty and has the same size as the C-style string") { - REQUIRE(!str.empty()); + REQUIRE_FALSE(str.empty()); REQUIRE(str.size() == std::strlen(c_str)); } @@ -79,7 +79,7 @@ SCENARIO("String initialization and construction", "[string]") THEN("the string is not empty and has size 1") { - REQUIRE(!str.empty()); + REQUIRE_FALSE(str.empty()); REQUIRE(str.size() == 1); } @@ -294,7 +294,7 @@ SCENARIO("String conversion and comparison", "[string]") THEN("the strings are not unequal") { - REQUIRE(!(str1 != str2)); + REQUIRE_FALSE(str1 != str2); } } @@ -311,8 +311,8 @@ SCENARIO("String conversion and comparison", "[string]") THEN("the string and the string view are not unequal") { - REQUIRE(!(str != view)); - REQUIRE(!(view != str)); + REQUIRE_FALSE(str != view); + REQUIRE_FALSE(view != str); } } } -- cgit v1.2.3 From 1269410c4c733ed38859b3e70713728afb273443 Mon Sep 17 00:00:00 2001 From: Marcel Braun Date: Sun, 3 May 2026 15:01:49 +0200 Subject: Use iterator in path resolution, start with symlink implementation --- kernel/src/filesystem/vfs.cpp | 40 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/kernel/src/filesystem/vfs.cpp b/kernel/src/filesystem/vfs.cpp index a3da258..ba4f404 100644 --- a/kernel/src/filesystem/vfs.cpp +++ b/kernel/src/filesystem/vfs.cpp @@ -94,7 +94,7 @@ namespace kernel::filesystem { return operation_result::invalid_path; } - + // TODO BA-FS26 check if target is directory? if (auto mount_point_dentry = resolve_path(target)) { if (auto source_dentry = resolve_path(source)) @@ -162,9 +162,9 @@ namespace kernel::filesystem auto path_parts = std::views::split(remaining, '/') | std::views::filter([](auto const & part) { return !part.empty(); }); - for (auto const & part : path_parts) + for (auto it = path_parts.begin(); it != path_parts.end(); ++it) { - std::string_view part_view{part}; + std::string_view part_view{*it}; if (part_view == ".") { @@ -205,7 +205,9 @@ namespace kernel::filesystem auto current_fs = current_mount->get_filesystem(); auto found_inode = current_fs->lookup(current_dentry->get_inode(), part_view); if (!found_inode) + { return nullptr; + } next_dentry = kstd::make_shared(current_dentry, found_inode, part_view); current_dentry->add_child(next_dentry); @@ -222,6 +224,38 @@ namespace kernel::filesystem next_dentry = current_mount->root_dentry(); } + if (next_dentry->get_inode()->is_symbolic_link()) + { + // TODO BA-FS26 this for loop is ugly --> fix + kstd::string symlink_path = "halo"; + if (symlink_path.size() > 0 && symlink_path.front() == '/') + { + for (auto it2 = std::next(it); it2 != path_parts.end(); ++it2) + { + symlink_path += "/"; + symlink_path.append(std::string_view{*it2}); + } + return resolve_path(symlink_path.view()); + } + else + { + kstd::string combined; + for (auto it3 = path_parts.begin(); it3 != it; ++it3) + { + combined += "/"; + combined.append(std::string_view{*it3}); + } + combined += "/"; + combined += symlink_path; + for (auto it4 = std::next(it); it4 != path_parts.end(); ++it4) + { + combined += "/"; + combined.append(std::string_view{*it4}); + } + return resolve_path(combined.view()); + } + } + current_dentry = next_dentry; } -- cgit v1.2.3 From 72d686961a26789b7659f17fb090511ee28604ec Mon Sep 17 00:00:00 2001 From: Marcel Braun Date: Mon, 4 May 2026 19:47:15 +0200 Subject: Add helper functions for path validation and splitting --- kernel/include/kernel/filesystem/path.hpp | 56 +++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 kernel/include/kernel/filesystem/path.hpp diff --git a/kernel/include/kernel/filesystem/path.hpp b/kernel/include/kernel/filesystem/path.hpp new file mode 100644 index 0000000..976926f --- /dev/null +++ b/kernel/include/kernel/filesystem/path.hpp @@ -0,0 +1,56 @@ +#ifndef TEACH_OS_KERNEL_FILESYSTEM_PATH_HPP +#define TEACH_OS_KERNEL_FILESYSTEM_PATH_HPP + +#include +#include + +namespace kernel::filesystem::path +{ + /** + @brief Provides utilities for handling filesystem paths, including validation and splitting into components. + */ + + /** + @brief Checks if the given path is a valid absolute path (starts with '/'). + @param path The path to check. + @return true if the path is a valid absolute path, false otherwise. + */ + auto inline is_valid_absolute_path(std::string_view path) -> bool + { + return !path.empty() && path.front() == '/'; + } + + /** + @brief Checks if the given path is a valid relative path (doesn't start with '/'). + @param path The path to check. + @return true if the path is a valid relative path, false otherwise. + */ + auto inline is_valid_relative_path(std::string_view path) -> bool + { + return !path.empty() && path.front() != '/'; + } + + /** + @brief Checks if the given path is a valid path (either absolute or relative). + @param path The path to check. + @return true if the path is a valid path, false otherwise. + */ + auto inline is_valid_path(std::string_view path) -> bool + { + return is_valid_absolute_path(path) || is_valid_relative_path(path); + } + + /** + @brief Splits the given path into its components. + @param path The path to split. + @return A range of string views representing the components of the path. + */ + auto inline split(std::string_view path) + { + return std::views::split(path, '/') | std::views::filter([](auto const & part) { return !part.empty(); }) | + std::views::transform([](auto const & part) { return std::string_view(part.begin(), part.end()); }); + } + +} // namespace kernel::filesystem::path + +#endif // TEACH_OS_KERNEL_FILESYSTEM_PATH_HPP \ No newline at end of file -- cgit v1.2.3 From 7ff97f0fe861bb7382f41ecd4bab26cbb62b1f7d Mon Sep 17 00:00:00 2001 From: Marcel Braun Date: Mon, 4 May 2026 19:47:57 +0200 Subject: Resolve TODOs concerning path validation --- kernel/src/filesystem/vfs.cpp | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/kernel/src/filesystem/vfs.cpp b/kernel/src/filesystem/vfs.cpp index ba4f404..8f48820 100644 --- a/kernel/src/filesystem/vfs.cpp +++ b/kernel/src/filesystem/vfs.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -89,12 +90,11 @@ namespace kernel::filesystem auto vfs::do_mount(std::string_view source, std::string_view target) -> operation_result { - // TODO BA-FS26 better path validation - if (target.empty() || target.front() != '/' || (target.size() > 1 && target.back() == '/')) + if (!kernel::filesystem::path::is_valid_path(source) || !kernel::filesystem::path::is_valid_path(target)) { return operation_result::invalid_path; } - // TODO BA-FS26 check if target is directory? + if (auto mount_point_dentry = resolve_path(target)) { if (auto source_dentry = resolve_path(source)) @@ -113,8 +113,7 @@ namespace kernel::filesystem auto vfs::unmount(std::string_view path) -> operation_result { - // TODO BA-FS26 better path validation - if (path.empty() || path.front() != '/' || (path.size() > 1 && path.back() == '/')) + if (!kernel::filesystem::path::is_valid_path(path)) { return operation_result::invalid_path; } @@ -145,11 +144,12 @@ namespace kernel::filesystem auto vfs::resolve_path(std::string_view path) -> kstd::shared_ptr { - // TODO BA-FS26 better path validation - // TODO BA-FS26 implement a path parser (maybe in libs?) and use it here and in do_mount - if (path.empty() || path.front() != '/') + if (!kernel::filesystem::path::is_valid_absolute_path(path)) + { return nullptr; + } + // TODO BA-FS26 refactor (get mount by path, no more prefix matching) auto current_mount = m_mount_table.find_longest_prefix_mount("/"); if (!current_mount) { @@ -157,10 +157,8 @@ namespace kernel::filesystem } auto current_dentry = current_mount->root_dentry(); - std::string_view remaining = path.substr(current_mount->get_mount_path().size()); - auto path_parts = - std::views::split(remaining, '/') | std::views::filter([](auto const & part) { return !part.empty(); }); + auto path_parts = kernel::filesystem::path::split(path); for (auto it = path_parts.begin(); it != path_parts.end(); ++it) { -- cgit v1.2.3 From d90d6bb4b4820df6cb4b0747439293bb85b8cbec Mon Sep 17 00:00:00 2001 From: Marcel Braun Date: Mon, 4 May 2026 20:17:43 +0200 Subject: Implement symlink read in inode, fix max amount of bytes to read --- kernel/include/kernel/filesystem/ext2/inode.hpp | 2 +- kernel/src/filesystem/ext2/inode.cpp | 16 +++++++++++++++- kernel/src/filesystem/open_file_descriptor.tests.cpp | 13 ++++--------- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/kernel/include/kernel/filesystem/ext2/inode.hpp b/kernel/include/kernel/filesystem/ext2/inode.hpp index b4a3cc4..b8f892a 100644 --- a/kernel/include/kernel/filesystem/ext2/inode.hpp +++ b/kernel/include/kernel/filesystem/ext2/inode.hpp @@ -20,7 +20,7 @@ namespace kernel::filesystem::ext2 { uint16_t mode; uint16_t uid; - uint32_t size; + uint32_t size; // TODO BA-FS26 signed? uint32_t atime; uint32_t ctime; uint32_t mtime; diff --git a/kernel/src/filesystem/ext2/inode.cpp b/kernel/src/filesystem/ext2/inode.cpp index 5b6bcb3..1914c70 100644 --- a/kernel/src/filesystem/ext2/inode.cpp +++ b/kernel/src/filesystem/ext2/inode.cpp @@ -5,6 +5,8 @@ #include +#include + #include #include #include @@ -23,6 +25,17 @@ namespace kernel::filesystem::ext2 auto inode::read(void * buffer, size_t offset, size_t size) const -> size_t { + // TODO BA-FS26 use revision 1 size + auto const max_readable = static_cast(m_data.size) - offset; + auto const requested_size = std::min(size, max_readable); + + if (is_symbolic_link() && m_data.size <= sizeof(m_data.block)) + { + auto inline_target = reinterpret_cast(m_data.block.data()); + kstd::libc::memcpy(static_cast(buffer), inline_target + offset, requested_size); + return requested_size; + } + auto block_index = offset / m_filesystem->get_block_size(); auto in_block_offset = offset % m_filesystem->get_block_size(); @@ -40,7 +53,8 @@ namespace kernel::filesystem::ext2 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); + auto const bytes_to_read = + std::min(requested_size - bytes_read, m_filesystem->get_block_size() - in_block_offset); bytes_read += m_filesystem->backing_inode()->read(static_cast(buffer) + bytes_read, read_offset, bytes_to_read); diff --git a/kernel/src/filesystem/open_file_descriptor.tests.cpp b/kernel/src/filesystem/open_file_descriptor.tests.cpp index 095e203..53835ba 100644 --- a/kernel/src/filesystem/open_file_descriptor.tests.cpp +++ b/kernel/src/filesystem/open_file_descriptor.tests.cpp @@ -1,4 +1,5 @@ #include + #include #include #include @@ -83,17 +84,11 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, "Ope { kstd::vector buffer(32); auto bytes_read = ofd->read(buffer.data(), buffer.size()); - REQUIRE(bytes_read == 32); - REQUIRE(ofd->offset() == 32); + REQUIRE(bytes_read == 7); + REQUIRE(ofd->offset() == 7); std::string_view buffer_as_str{reinterpret_cast(buffer.data()), static_cast(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'); - } + REQUIRE(buffer_as_str == "info_1\n"); } THEN("the file can be read multiple times") -- cgit v1.2.3 From d1e64340abb960c579651635d580660c12f94e6e Mon Sep 17 00:00:00 2001 From: Marcel Braun Date: Mon, 4 May 2026 20:31:58 +0200 Subject: Refactor path resolution, use vector and while instead of iterator and for-loop --- kernel/src/filesystem/vfs.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/kernel/src/filesystem/vfs.cpp b/kernel/src/filesystem/vfs.cpp index 8f48820..c9939fa 100644 --- a/kernel/src/filesystem/vfs.cpp +++ b/kernel/src/filesystem/vfs.cpp @@ -12,7 +12,9 @@ #include #include +#include +#include #include #include #include @@ -159,10 +161,13 @@ namespace kernel::filesystem auto current_dentry = current_mount->root_dentry(); auto path_parts = kernel::filesystem::path::split(path); + kstd::vector path_parts_vector(path_parts.begin(), path_parts.end()); + std::ranges::reverse(path_parts_vector); - for (auto it = path_parts.begin(); it != path_parts.end(); ++it) + while (!path_parts_vector.empty()) { - std::string_view part_view{*it}; + std::string_view part_view{path_parts_vector.back()}; + path_parts_vector.pop_back(); if (part_view == ".") { -- cgit v1.2.3 From bddee0f0cb77eb247eceea18005dc575b433bb69 Mon Sep 17 00:00:00 2001 From: Marcel Braun Date: Mon, 4 May 2026 20:51:49 +0200 Subject: Implement symlink non-recursive --- kernel/src/filesystem/vfs.cpp | 41 +++++++++++++++-------------------------- 1 file changed, 15 insertions(+), 26 deletions(-) diff --git a/kernel/src/filesystem/vfs.cpp b/kernel/src/filesystem/vfs.cpp index c9939fa..06d36ce 100644 --- a/kernel/src/filesystem/vfs.cpp +++ b/kernel/src/filesystem/vfs.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -229,33 +230,21 @@ namespace kernel::filesystem if (next_dentry->get_inode()->is_symbolic_link()) { - // TODO BA-FS26 this for loop is ugly --> fix - kstd::string symlink_path = "halo"; - if (symlink_path.size() > 0 && symlink_path.front() == '/') - { - for (auto it2 = std::next(it); it2 != path_parts.end(); ++it2) - { - symlink_path += "/"; - symlink_path.append(std::string_view{*it2}); - } - return resolve_path(symlink_path.view()); - } - else + // TODO BA-FS26 max symbolic link depth to prevent infinite loops + kstd::vector buffer(4096); // TODO BA-FS26 max symlink size? + auto const bytes_read = next_dentry->get_inode()->read(buffer.data(), 0, buffer.size()); + auto const symbolic_link_path = std::string_view{reinterpret_cast(buffer.data()), bytes_read}; + + auto symbolic_link_parts = kernel::filesystem::path::split(symbolic_link_path); + kstd::vector symbolic_link_parts_vector(symbolic_link_parts.begin(), symbolic_link_parts.end()); + std::ranges::reverse(symbolic_link_parts_vector); + + path_parts_vector.insert_range(path_parts_vector.end(), symbolic_link_parts_vector); + + if (kernel::filesystem::path::is_valid_absolute_path(symbolic_link_path)) { - kstd::string combined; - for (auto it3 = path_parts.begin(); it3 != it; ++it3) - { - combined += "/"; - combined.append(std::string_view{*it3}); - } - combined += "/"; - combined += symlink_path; - for (auto it4 = std::next(it); it4 != path_parts.end(); ++it4) - { - combined += "/"; - combined.append(std::string_view{*it4}); - } - return resolve_path(combined.view()); + current_mount = m_mount_table.find_longest_prefix_mount("/"); + next_dentry = current_mount->root_dentry(); } } -- cgit v1.2.3 From 9aaabb2cf73f20cc4d4c68588e7bf4592837626f Mon Sep 17 00:00:00 2001 From: Marcel Braun Date: Mon, 4 May 2026 23:25:16 +0200 Subject: Add simple symlink tests --- arch/x86_64/support/modules/README.md | 5 ++++- arch/x86_64/support/modules/ext2_1KB_fs.img | 2 +- kernel/src/filesystem/vfs.tests.cpp | 20 ++++++++++++++++++++ .../filesystem/test_assets/ext2_1KB_fs.img | 2 +- 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/arch/x86_64/support/modules/README.md b/arch/x86_64/support/modules/README.md index a04b9af..fac5daf 100644 --- a/arch/x86_64/support/modules/README.md +++ b/arch/x86_64/support/modules/README.md @@ -8,10 +8,13 @@ The ext2_4KB_fs image is intentionally fragmented, as some files were created an ./archiv ./archiv/2024.img ./archiv/2025.img +./closed.txt ./information ./information/info_1.txt ./information/info_2.txt -./closed.txt +./symlinks +./symlinks/info_1_absolute -> /information/info_1.txt +./symlinks/info_1_relative -> ../information/info_1.txt ### 2024.img (ext2 filesystem with 2KB Block size) diff --git a/arch/x86_64/support/modules/ext2_1KB_fs.img b/arch/x86_64/support/modules/ext2_1KB_fs.img index 5bbb76d..0312727 100644 --- a/arch/x86_64/support/modules/ext2_1KB_fs.img +++ b/arch/x86_64/support/modules/ext2_1KB_fs.img @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c2ef9536a439825520d9e230eedaa9ae327f9763350eddbc0f24bf5b9b5d2bf2 +oid sha256:7cf40c3cf3d8e3be614cadf2da1c76138c492734c3730eadbca645f50ed99a50 size 10485760 diff --git a/kernel/src/filesystem/vfs.tests.cpp b/kernel/src/filesystem/vfs.tests.cpp index 109ee89..829c9d2 100644 --- a/kernel/src/filesystem/vfs.tests.cpp +++ b/kernel/src/filesystem/vfs.tests.cpp @@ -283,4 +283,24 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, "VFS REQUIRE(unmounted_sheep_1 == nullptr); } } + + GIVEN("one real image files, containing symbolic links") + { + REQUIRE(std::filesystem::exists(image_path_1)); + REQUIRE_NOTHROW(setup_modules_from_img_and_init_vfs({"test_img_module_1"}, {image_path_1})); + + THEN("file can be opened through absolute symbolic link") + { + auto & vfs = kernel::filesystem::vfs::get(); + auto info_1 = vfs.open("/symlinks/info_1_absolute"); + REQUIRE(info_1 != nullptr); + } + + THEN("file can be opened through relative symbolic link") + { + auto & vfs = kernel::filesystem::vfs::get(); + auto info_1 = vfs.open("/symlinks/info_1_relative"); + REQUIRE(info_1 != nullptr); + } + } } 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 index 5bbb76d..0312727 100644 --- a/kernel/src/test_support/filesystem/test_assets/ext2_1KB_fs.img +++ b/kernel/src/test_support/filesystem/test_assets/ext2_1KB_fs.img @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c2ef9536a439825520d9e230eedaa9ae327f9763350eddbc0f24bf5b9b5d2bf2 +oid sha256:7cf40c3cf3d8e3be614cadf2da1c76138c492734c3730eadbca645f50ed99a50 size 10485760 -- cgit v1.2.3 From 2ab9d6ffbc4aad8ab3a393bd32191e3c07103c0a Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Tue, 5 May 2026 14:37:47 +0200 Subject: fix problem with string_view lifetime --- kernel/include/kernel/filesystem/path.hpp | 5 +++-- kernel/src/filesystem/vfs.cpp | 15 ++++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/kernel/include/kernel/filesystem/path.hpp b/kernel/include/kernel/filesystem/path.hpp index 976926f..298ac5f 100644 --- a/kernel/include/kernel/filesystem/path.hpp +++ b/kernel/include/kernel/filesystem/path.hpp @@ -3,6 +3,7 @@ #include #include +#include namespace kernel::filesystem::path { @@ -43,12 +44,12 @@ namespace kernel::filesystem::path /** @brief Splits the given path into its components. @param path The path to split. - @return A range of string views representing the components of the path. + @return A range of strings representing the components of the path. */ auto inline split(std::string_view path) { return std::views::split(path, '/') | std::views::filter([](auto const & part) { return !part.empty(); }) | - std::views::transform([](auto const & part) { return std::string_view(part.begin(), part.end()); }); + std::views::transform([](auto const & part) { return kstd::string(std::string_view(part.begin(), part.end())); }); } } // namespace kernel::filesystem::path diff --git a/kernel/src/filesystem/vfs.cpp b/kernel/src/filesystem/vfs.cpp index 06d36ce..03921df 100644 --- a/kernel/src/filesystem/vfs.cpp +++ b/kernel/src/filesystem/vfs.cpp @@ -167,15 +167,15 @@ namespace kernel::filesystem while (!path_parts_vector.empty()) { - std::string_view part_view{path_parts_vector.back()}; + auto part = path_parts_vector.back(); path_parts_vector.pop_back(); - if (part_view == ".") + if (part == ".") { continue; } - if (part_view == "..") + if (part == "..") { auto parent_dentry = current_dentry->get_parent(); @@ -203,17 +203,17 @@ namespace kernel::filesystem continue; } - auto next_dentry = current_dentry->find_child(part_view); + auto next_dentry = current_dentry->find_child(part.view()); if (!next_dentry) { auto current_fs = current_mount->get_filesystem(); - auto found_inode = current_fs->lookup(current_dentry->get_inode(), part_view); + auto found_inode = current_fs->lookup(current_dentry->get_inode(), part.view()); if (!found_inode) { return nullptr; } - next_dentry = kstd::make_shared(current_dentry, found_inode, part_view); + next_dentry = kstd::make_shared(current_dentry, found_inode, part.view()); current_dentry->add_child(next_dentry); } else if (next_dentry->has_flag(dentry::dentry_flags::mounted)) @@ -244,8 +244,9 @@ namespace kernel::filesystem if (kernel::filesystem::path::is_valid_absolute_path(symbolic_link_path)) { current_mount = m_mount_table.find_longest_prefix_mount("/"); - next_dentry = current_mount->root_dentry(); + current_dentry = current_mount->root_dentry(); } + continue; } current_dentry = next_dentry; -- cgit v1.2.3 From 88ebc124ed0e703e02dfe96006d0d490e83de4fe Mon Sep 17 00:00:00 2001 From: Marcel Braun Date: Tue, 5 May 2026 14:46:21 +0200 Subject: Fix vfs tests --- kernel/src/filesystem/vfs.tests.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/kernel/src/filesystem/vfs.tests.cpp b/kernel/src/filesystem/vfs.tests.cpp index 829c9d2..cbd67be 100644 --- a/kernel/src/filesystem/vfs.tests.cpp +++ b/kernel/src/filesystem/vfs.tests.cpp @@ -140,8 +140,8 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, "VFS THEN("mount with invalid path fails") { REQUIRE(vfs.do_mount("/dev/ram16", "") == kernel::filesystem::vfs::operation_result::invalid_path); - REQUIRE(vfs.do_mount("/dev/ram16", "information") == kernel::filesystem::vfs::operation_result::invalid_path); - REQUIRE(vfs.do_mount("/dev/ram16", "/information/") == kernel::filesystem::vfs::operation_result::invalid_path); + REQUIRE(vfs.do_mount("/dev/ram16", "information") == + kernel::filesystem::vfs::operation_result::mount_point_not_found); } THEN("mount with non-existent source path fails") @@ -159,8 +159,7 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, "VFS 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); + REQUIRE(vfs.unmount("information") == kernel::filesystem::vfs::operation_result::mount_point_not_found); } THEN("unmounting non-existent mount point returns expected error code") -- cgit v1.2.3 From 156925495d7bc8bd684a346e2ab9eea12f8187cc Mon Sep 17 00:00:00 2001 From: Marcel Braun Date: Tue, 5 May 2026 15:59:08 +0200 Subject: Add constants --- kernel/include/kernel/filesystem/constants.hpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 kernel/include/kernel/filesystem/constants.hpp diff --git a/kernel/include/kernel/filesystem/constants.hpp b/kernel/include/kernel/filesystem/constants.hpp new file mode 100644 index 0000000..aff512a --- /dev/null +++ b/kernel/include/kernel/filesystem/constants.hpp @@ -0,0 +1,13 @@ +#ifndef TEACH_OS_KERNEL_FILESYSTEM_CONSTANTS_HPP +#define TEACH_OS_KERNEL_FILESYSTEM_CONSTANTS_HPP + +#include +namespace kernel::filesystem::constants +{ + constexpr size_t inline max_path_length = 4096; + + constexpr size_t inline symlink_max_path_length = 4096; + constexpr size_t inline symloop_max = 40; +} // namespace kernel::filesystem::constants + +#endif \ No newline at end of file -- cgit v1.2.3 From 9f6353679f4052b2f685ad74994bfb6d54678d44 Mon Sep 17 00:00:00 2001 From: Marcel Braun Date: Tue, 5 May 2026 16:00:11 +0200 Subject: Add check for valid path length --- kernel/include/kernel/filesystem/path.hpp | 32 ++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/kernel/include/kernel/filesystem/path.hpp b/kernel/include/kernel/filesystem/path.hpp index 298ac5f..4845bf1 100644 --- a/kernel/include/kernel/filesystem/path.hpp +++ b/kernel/include/kernel/filesystem/path.hpp @@ -1,41 +1,54 @@ #ifndef TEACH_OS_KERNEL_FILESYSTEM_PATH_HPP #define TEACH_OS_KERNEL_FILESYSTEM_PATH_HPP +#include + +#include + #include #include -#include namespace kernel::filesystem::path { /** @brief Provides utilities for handling filesystem paths, including validation and splitting into components. - */ + */ + + /** + @brief Checks if the given path is within the maximum allowed length. + @param path The path to check. + @return true if the path length is valid, false otherwise. + */ + auto inline is_valid_path_length(std::string_view path) -> bool + { + return path.length() < kernel::filesystem::constants::max_path_length; + } /** @brief Checks if the given path is a valid absolute path (starts with '/'). @param path The path to check. @return true if the path is a valid absolute path, false otherwise. - */ + */ auto inline is_valid_absolute_path(std::string_view path) -> bool { - return !path.empty() && path.front() == '/'; + return !path.empty() && path.front() == '/' && is_valid_path_length(path); } /** @brief Checks if the given path is a valid relative path (doesn't start with '/'). @param path The path to check. @return true if the path is a valid relative path, false otherwise. - */ + */ auto inline is_valid_relative_path(std::string_view path) -> bool { - return !path.empty() && path.front() != '/'; + return !path.empty() && path.front() != '/' && is_valid_path_length(path); } /** @brief Checks if the given path is a valid path (either absolute or relative). @param path The path to check. @return true if the path is a valid path, false otherwise. - */ + */ auto inline is_valid_path(std::string_view path) -> bool { return is_valid_absolute_path(path) || is_valid_relative_path(path); @@ -45,11 +58,12 @@ namespace kernel::filesystem::path @brief Splits the given path into its components. @param path The path to split. @return A range of strings representing the components of the path. - */ + */ auto inline split(std::string_view path) { return std::views::split(path, '/') | std::views::filter([](auto const & part) { return !part.empty(); }) | - std::views::transform([](auto const & part) { return kstd::string(std::string_view(part.begin(), part.end())); }); + std::views::transform( + [](auto const & part) { return kstd::string(std::string_view(part.begin(), part.end())); }); } } // namespace kernel::filesystem::path -- cgit v1.2.3 From b6cc02c3bac002ebac8e6ba734b00f0e60b24778 Mon Sep 17 00:00:00 2001 From: Marcel Braun Date: Tue, 5 May 2026 16:05:48 +0200 Subject: Add check for max symlink count --- kernel/src/filesystem/vfs.cpp | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/kernel/src/filesystem/vfs.cpp b/kernel/src/filesystem/vfs.cpp index 03921df..e6d9241 100644 --- a/kernel/src/filesystem/vfs.cpp +++ b/kernel/src/filesystem/vfs.cpp @@ -1,5 +1,6 @@ #include +#include #include #include #include @@ -93,7 +94,7 @@ namespace kernel::filesystem auto vfs::do_mount(std::string_view source, std::string_view target) -> operation_result { - if (!kernel::filesystem::path::is_valid_path(source) || !kernel::filesystem::path::is_valid_path(target)) + if (!path::is_valid_path(source) || !path::is_valid_path(target)) { return operation_result::invalid_path; } @@ -116,7 +117,7 @@ namespace kernel::filesystem auto vfs::unmount(std::string_view path) -> operation_result { - if (!kernel::filesystem::path::is_valid_path(path)) + if (!path::is_valid_path(path)) { return operation_result::invalid_path; } @@ -147,7 +148,7 @@ namespace kernel::filesystem auto vfs::resolve_path(std::string_view path) -> kstd::shared_ptr { - if (!kernel::filesystem::path::is_valid_absolute_path(path)) + if (!path::is_valid_absolute_path(path)) { return nullptr; } @@ -161,10 +162,12 @@ namespace kernel::filesystem auto current_dentry = current_mount->root_dentry(); - auto path_parts = kernel::filesystem::path::split(path); + auto path_parts = path::split(path); kstd::vector path_parts_vector(path_parts.begin(), path_parts.end()); std::ranges::reverse(path_parts_vector); + auto symlink_counter = 0uz; + while (!path_parts_vector.empty()) { auto part = path_parts_vector.back(); @@ -230,18 +233,22 @@ namespace kernel::filesystem if (next_dentry->get_inode()->is_symbolic_link()) { - // TODO BA-FS26 max symbolic link depth to prevent infinite loops - kstd::vector buffer(4096); // TODO BA-FS26 max symlink size? + if (symlink_counter++ > constants::symloop_max) + { + return nullptr; + } + + kstd::vector buffer(constants::symlink_max_path_length); auto const bytes_read = next_dentry->get_inode()->read(buffer.data(), 0, buffer.size()); auto const symbolic_link_path = std::string_view{reinterpret_cast(buffer.data()), bytes_read}; - auto symbolic_link_parts = kernel::filesystem::path::split(symbolic_link_path); + auto symbolic_link_parts = path::split(symbolic_link_path); kstd::vector symbolic_link_parts_vector(symbolic_link_parts.begin(), symbolic_link_parts.end()); std::ranges::reverse(symbolic_link_parts_vector); path_parts_vector.insert_range(path_parts_vector.end(), symbolic_link_parts_vector); - if (kernel::filesystem::path::is_valid_absolute_path(symbolic_link_path)) + if (path::is_valid_absolute_path(symbolic_link_path)) { current_mount = m_mount_table.find_longest_prefix_mount("/"); current_dentry = current_mount->root_dentry(); -- cgit v1.2.3 From 94232ac15ce987cf6792be0c4d5b0141d7cac0b1 Mon Sep 17 00:00:00 2001 From: Marcel Braun Date: Tue, 5 May 2026 16:18:54 +0200 Subject: Add tests for path utility --- kernel/CMakeLists.txt | 1 + kernel/src/filesystem/path.tests.cpp | 69 ++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 kernel/src/filesystem/path.tests.cpp diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index 909ccf7..860e28b 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -188,6 +188,7 @@ if(BUILD_TESTING) "src/filesystem/devfs/inode.tests.cpp" "src/filesystem/ext2/filesystem.tests.cpp" "src/filesystem/ext2/inode.tests.cpp" + "src/filesystem/path.tests.cpp" "src/filesystem/rootfs/filesystem.tests.cpp" "src/filesystem/rootfs/inode.tests.cpp" "src/filesystem/dentry.tests.cpp" diff --git a/kernel/src/filesystem/path.tests.cpp b/kernel/src/filesystem/path.tests.cpp new file mode 100644 index 0000000..3c18b5c --- /dev/null +++ b/kernel/src/filesystem/path.tests.cpp @@ -0,0 +1,69 @@ +#include + +#include + +#include +#include +#include +#include + +SCENARIO("path utilities", "[filesystem][path]") +{ + GIVEN("valid and invalid paths") + { + THEN("valid absolute paths are recognized as valid") + { + REQUIRE(kernel::filesystem::path::is_valid_path("/valid/absolute/path")); + REQUIRE(kernel::filesystem::path::is_valid_path("/")); + } + + THEN("valid relative paths are recognized as valid") + { + REQUIRE(kernel::filesystem::path::is_valid_path("valid/../relative/.././path")); + REQUIRE(kernel::filesystem::path::is_valid_path("valid/relative/path")); + REQUIRE(kernel::filesystem::path::is_valid_path("file.txt")); + } + + THEN("invalid paths are recognized as invalid") + { + REQUIRE_FALSE(kernel::filesystem::path::is_valid_path("")); + REQUIRE_FALSE(kernel::filesystem::path::is_valid_path(std::string(4096, 'a'))); + } + + THEN("valid absolute paths are recognized as absolute") + { + REQUIRE(kernel::filesystem::path::is_valid_absolute_path("/valid/absolute/path")); + REQUIRE_FALSE(kernel::filesystem::path::is_valid_absolute_path("valid/relative/path")); + } + + THEN("invalid paths are not recognized as absolute") + { + REQUIRE_FALSE(kernel::filesystem::path::is_valid_absolute_path("")); + REQUIRE_FALSE(kernel::filesystem::path::is_valid_absolute_path(std::string(4096, 'a'))); + REQUIRE_FALSE(kernel::filesystem::path::is_valid_absolute_path("invalid/absolute/path")); + } + + THEN("valid relative paths are recognized as relative") + { + REQUIRE(kernel::filesystem::path::is_valid_relative_path("valid/relative/path")); + REQUIRE_FALSE(kernel::filesystem::path::is_valid_relative_path("/valid/absolute/path")); + } + + THEN("invalid paths are not recognized as relative") + { + REQUIRE_FALSE(kernel::filesystem::path::is_valid_relative_path("")); + REQUIRE_FALSE(kernel::filesystem::path::is_valid_relative_path(std::string(4096, 'a'))); + REQUIRE_FALSE(kernel::filesystem::path::is_valid_relative_path("/invalid/absolute/path")); + } + } + + GIVEN("a valid path") + { + THEN("it can be split into components") + { + auto components = kernel::filesystem::path::split("/a/b///c/d.txt"); + std::vector expected = {"a", "b", "c", "d.txt"}; + REQUIRE(std::ranges::equal(components, expected)); + } + } +} \ No newline at end of file -- cgit v1.2.3 From c60f2e345231e03176e0882a88f8675c1814e456 Mon Sep 17 00:00:00 2001 From: Marcel Braun Date: Tue, 5 May 2026 16:22:23 +0200 Subject: Remove manual tests --- kernel/src/main.cpp | 168 +--------------------------------------------------- 1 file changed, 1 insertion(+), 167 deletions(-) diff --git a/kernel/src/main.cpp b/kernel/src/main.cpp index ffea979..bfb731a 100644 --- a/kernel/src/main.cpp +++ b/kernel/src/main.cpp @@ -21,173 +21,11 @@ #include #include -#include #include #include using namespace kstd::units_literals; -auto test_device_names() -> void -{ - auto storage_mgmt = kernel::devices::storage::management::get(); - std::ranges::for_each(storage_mgmt.all_controllers(), [](auto const & controller) { - std::ranges::for_each(controller->all_devices(), - [](auto const & device) { kstd::println("{}", device->name().view()); }); - }); -} - -auto test_file_descriptor_manually() -> void -{ - // setup - auto fd_table = kernel::filesystem::open_file_table::get(); - auto storage_mgmt = kernel::devices::storage::management::get(); - auto device = storage_mgmt.device_by_major_minor(1, 0); - - auto dev_node = kstd::make_shared(device); - - auto ofd = kstd::make_shared(dev_node); - auto fd_index = fd_table.add_file(ofd); - - // use: read two bytes and write two again - auto fd = fd_table.get_file(fd_index); - if (!fd) - { - kstd::os::panic("test code failed"); - } - - kstd::vector buffer{2}; - auto number_of_read_bytes = fd->read(buffer.data(), buffer.size()); - kstd::println("read bytes: {}", number_of_read_bytes); - kstd::println("buffer: {::#04x}", buffer); - - // write half of the file new - auto const value1 = std::byte{0xAA}; - auto const value2 = std::byte{0xBB}; - kstd::vector write_buffer{value1, value2}; - auto written_bytes = fd->write(write_buffer.data(), write_buffer.size()); - - kstd::println("written bytes: {}", written_bytes); - - fd_table.remove_file(fd_index); - - // use: read four bytes again -> two old bytes two new bytes - auto ofd1 = kstd::make_shared(dev_node); - fd_index = fd_table.add_file(ofd1); - auto fd1 = fd_table.get_file(fd_index); - - if (!fd1) - { - kstd::os::panic("test code failed"); - } - - kstd::vector buffer1{4}; - number_of_read_bytes = fd1->read(buffer1.data(), buffer1.size()); - kstd::println("read bytes: {}", number_of_read_bytes); - kstd::println("buffer: {::#04x}", buffer1); -} - -auto test_device_with_vfs() -> void -{ - auto vfs = kernel::filesystem::vfs::get(); - auto dentry = vfs.open("/dev/ram0"); - if (!dentry) - { - kstd::os::panic("test code failed"); - } - - auto fd_table = kernel::filesystem::open_file_table::get(); - auto ofd = kstd::make_shared(dentry->get_inode()); - auto fd = fd_table.add_file(ofd); - kstd::vector buffer{2}; - auto file = fd_table.get_file(fd); - if (!file) - { - kstd::os::panic("test code failed"); - } - - auto number_of_read_bytes = file->read(buffer.data(), buffer.size()); - kstd::println("read bytes: {}", number_of_read_bytes); - kstd::println("buffer: {::#04x}", buffer); -} - -auto test_file_lookup() -> void -{ - 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 dentry = vfs.open(path); - if (!dentry) - { - kstd::os::panic("test code failed"); - } - - kstd::vector buffer{32}; - auto ofd = kstd::make_shared(dentry->get_inode()); - 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(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"); - - vfs.do_mount("/dev/ram16", "/enclosures/aquarium"); - read_and_write_file("/enclosures/aquarium/closed.txt"); - read_and_write_file("/enclosures/aquarium/information/info_2.txt"); - - vfs.unmount("/enclosures/aquarium"); - read_and_write_file("/enclosures/aquarium/tank_1/fish_4.txt"); - - vfs.do_mount("/dev/ram32", "/enclosures/elephant_house"); - read_and_write_file("/enclosures/elephant_house/monkey_house/infrastructure/info.txt"); - - vfs.do_mount("/dev/ram16", "/enclosures/elephant_house/monkey_house"); - 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::println("[TEST] Unmount failed as expected due to active child mount."); - } - - vfs.unmount("/enclosures/elephant_house/monkey_house"); - result = vfs.unmount("/enclosures/elephant_house"); - if (result == kernel::filesystem::vfs::operation_result::success) - { - kstd::println("[TEST] Unmount succeeded after unmounting child mount."); - } -} - -auto run_test_code() -> void -{ - kstd::println("[TEST] Running test code..."); - - kstd::println("[TEST] device names"); - test_device_names(); - kstd::println("---------------------------------"); - - kstd::println("[TEST] file descriptor manually"); - test_file_descriptor_manually(); - kstd::println("---------------------------------"); - - kstd::println("[TEST] device with VFS"); - test_device_with_vfs(); - kstd::println("---------------------------------"); - - kstd::println("[TEST] file lookup"); - test_file_lookup(); - kstd::println("---------------------------------"); -} - auto run_demo() -> void { // 1) open a file @@ -280,12 +118,8 @@ auto main() -> int kernel::filesystem::vfs::init(); kstd::println("[OS] Virtual filesystem initialized."); - // TODO BA-FS26 remove demo and test code again? + // TODO BA-FS26 remove demo code? // run_demo(); - // TODO BA-FS26 remove demo and test code again? - run_test_code(); - kstd::println("[TEST] All tests completed."); - kapi::system::panic("Returning from kernel main!"); } -- cgit v1.2.3 From 9f729ef76de79c8dabace2b4a6255f006385f6df Mon Sep 17 00:00:00 2001 From: Marcel Braun Date: Tue, 5 May 2026 16:29:50 +0200 Subject: Disable recursion-warning --- kernel/src/filesystem/dentry.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/src/filesystem/dentry.cpp b/kernel/src/filesystem/dentry.cpp index 72500fd..1cf8730 100644 --- a/kernel/src/filesystem/dentry.cpp +++ b/kernel/src/filesystem/dentry.cpp @@ -39,7 +39,7 @@ namespace kernel::filesystem return m_name.view(); } - // TODO BA-FS26 fix warning (do not use recursion) + // NOLINTNEXTLINE(misc-no-recursion) auto dentry::get_full_path() const -> kstd::string { if (m_parent) @@ -52,7 +52,7 @@ namespace kernel::filesystem return parent_path + m_name.view(); } - return m_name.empty() ? "/" : m_name.view(); + return m_name.view(); } auto dentry::add_child(kstd::shared_ptr const & child) -> void -- cgit v1.2.3 From 2c24321681974e1aa8b1240155420f49a16f3c2e Mon Sep 17 00:00:00 2001 From: Marcel Braun Date: Tue, 5 May 2026 17:15:09 +0200 Subject: Add testing symlinks to filesystem images --- arch/x86_64/support/modules/ext2_1KB_fs.img | 2 +- arch/x86_64/support/modules/ext2_2KB_fs.img | 2 +- arch/x86_64/support/modules/ext2_4KB_fs.img | 2 +- kernel/src/test_support/filesystem/test_assets/ext2_1KB_fs.img | 2 +- kernel/src/test_support/filesystem/test_assets/ext2_2KB_fs.img | 2 +- kernel/src/test_support/filesystem/test_assets/ext2_4KB_fs.img | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/arch/x86_64/support/modules/ext2_1KB_fs.img b/arch/x86_64/support/modules/ext2_1KB_fs.img index 0312727..a5202ca 100644 --- a/arch/x86_64/support/modules/ext2_1KB_fs.img +++ b/arch/x86_64/support/modules/ext2_1KB_fs.img @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7cf40c3cf3d8e3be614cadf2da1c76138c492734c3730eadbca645f50ed99a50 +oid sha256:98ac3c1be872806e25fb14eea168ca79a91959f4e6a5ac3d00c5d8224c1f73a3 size 10485760 diff --git a/arch/x86_64/support/modules/ext2_2KB_fs.img b/arch/x86_64/support/modules/ext2_2KB_fs.img index 1880911..8327022 100644 --- a/arch/x86_64/support/modules/ext2_2KB_fs.img +++ b/arch/x86_64/support/modules/ext2_2KB_fs.img @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9a13da5abb9c65c737105b1da0d4344c7cd7604c7952c762c4f4e3d3f96fd42d +oid sha256:a1d102f2e40083613060d43b2b32d31031137bbef99761a2d1bf4d38e155adb7 size 5242880 diff --git a/arch/x86_64/support/modules/ext2_4KB_fs.img b/arch/x86_64/support/modules/ext2_4KB_fs.img index 3aaceb8..2cd452f 100644 --- a/arch/x86_64/support/modules/ext2_4KB_fs.img +++ b/arch/x86_64/support/modules/ext2_4KB_fs.img @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4ce6a1aea277906e1af6de223c017ff900b96569f076b4d99fc04eaa1ee986f4 +oid sha256:62e1fa40f95e0cb037c43e3f55d0094ab6cb4d68e00914f555a90faf7cc00c31 size 10485760 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 index 0312727..a5202ca 100644 --- a/kernel/src/test_support/filesystem/test_assets/ext2_1KB_fs.img +++ b/kernel/src/test_support/filesystem/test_assets/ext2_1KB_fs.img @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7cf40c3cf3d8e3be614cadf2da1c76138c492734c3730eadbca645f50ed99a50 +oid sha256:98ac3c1be872806e25fb14eea168ca79a91959f4e6a5ac3d00c5d8224c1f73a3 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 index 1880911..8327022 100644 --- a/kernel/src/test_support/filesystem/test_assets/ext2_2KB_fs.img +++ b/kernel/src/test_support/filesystem/test_assets/ext2_2KB_fs.img @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9a13da5abb9c65c737105b1da0d4344c7cd7604c7952c762c4f4e3d3f96fd42d +oid sha256:a1d102f2e40083613060d43b2b32d31031137bbef99761a2d1bf4d38e155adb7 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 index 3aaceb8..2cd452f 100644 --- a/kernel/src/test_support/filesystem/test_assets/ext2_4KB_fs.img +++ b/kernel/src/test_support/filesystem/test_assets/ext2_4KB_fs.img @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4ce6a1aea277906e1af6de223c017ff900b96569f076b4d99fc04eaa1ee986f4 +oid sha256:62e1fa40f95e0cb037c43e3f55d0094ab6cb4d68e00914f555a90faf7cc00c31 size 10485760 -- cgit v1.2.3 From 491d7cb23f48b995e954b7cbb836ab3efb47ea88 Mon Sep 17 00:00:00 2001 From: Marcel Braun Date: Tue, 5 May 2026 17:22:27 +0200 Subject: Update readme for images, add symlink to readme into test_assets folder --- arch/x86_64/support/modules/README.md | 18 +++++++++++++++--- .../src/test_support/filesystem/test_assets/README.md | 1 + 2 files changed, 16 insertions(+), 3 deletions(-) create mode 120000 kernel/src/test_support/filesystem/test_assets/README.md diff --git a/arch/x86_64/support/modules/README.md b/arch/x86_64/support/modules/README.md index fac5daf..67b1217 100644 --- a/arch/x86_64/support/modules/README.md +++ b/arch/x86_64/support/modules/README.md @@ -8,13 +8,21 @@ The ext2_4KB_fs image is intentionally fragmented, as some files were created an ./archiv ./archiv/2024.img ./archiv/2025.img +./archiv/mnt ./closed.txt ./information ./information/info_1.txt ./information/info_2.txt -./symlinks -./symlinks/info_1_absolute -> /information/info_1.txt -./symlinks/info_1_relative -> ../information/info_1.txt +./symlinks +./symlinks/info_1_absolute -> /information/info_1.txt +./symlinks/info_1_relative -> ../information/info_1.txt +./symlinks/information_directory_absolute -> /information +./symlinks/information_directory_relative -> ../information +./symlinks/invalid_absolute -> ../invalid/non_existant.txt +./symlinks/invalid_relative -> ../invalid/non_existant.txt +./symlinks/symloop_a -> ./symloop_b +./symlinks/symloop_b -> /symlinks/symloop_a +./symlinks/traverse_back_5_times -> ../../../../../ ### 2024.img (ext2 filesystem with 2KB Block size) @@ -25,6 +33,8 @@ The ext2_4KB_fs image is intentionally fragmented, as some files were created an ./stable/pig_1.txt ./stable/pig_2.txt ./stable/pig_3.txt +./symlinks +./symlinks/traverse_back_twice -> ../../ ### 2025.img (ext2 filesystem 4KB Block size) @@ -52,6 +62,7 @@ The ext2_4KB_fs image is intentionally fragmented, as some files were created an ./monkey_house/caretaker ./monkey_house/caretaker/isabelle.txt ./monkey_house/caretaker/peter.txt +./symlinks/leave_and_reenter_mount -> ../../archiv/../information/monkey_house ## ext2_4KB_fs . @@ -109,3 +120,4 @@ The ext2_4KB_fs image is intentionally fragmented, as some files were created an ./enclosures/aquarium/spawn_fish.sh ./enclosures/info.txt ./info.txt +./symlinks/very_long_symlink -> ../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 diff --git a/kernel/src/test_support/filesystem/test_assets/README.md b/kernel/src/test_support/filesystem/test_assets/README.md new file mode 120000 index 0000000..718a227 --- /dev/null +++ b/kernel/src/test_support/filesystem/test_assets/README.md @@ -0,0 +1 @@ +/arch/x86_64/support/modules/README.md \ No newline at end of file -- cgit v1.2.3 From c7e3528e9d40f62e6acf72b206a31105f252b529 Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Tue, 5 May 2026 19:40:55 +0200 Subject: fix readme --- arch/x86_64/support/modules/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/x86_64/support/modules/README.md b/arch/x86_64/support/modules/README.md index 67b1217..6893176 100644 --- a/arch/x86_64/support/modules/README.md +++ b/arch/x86_64/support/modules/README.md @@ -18,7 +18,7 @@ The ext2_4KB_fs image is intentionally fragmented, as some files were created an ./symlinks/info_1_relative -> ../information/info_1.txt ./symlinks/information_directory_absolute -> /information ./symlinks/information_directory_relative -> ../information -./symlinks/invalid_absolute -> ../invalid/non_existant.txt +./symlinks/invalid_absolute -> /invalid/non_existant.txt ./symlinks/invalid_relative -> ../invalid/non_existant.txt ./symlinks/symloop_a -> ./symloop_b ./symlinks/symloop_b -> /symlinks/symloop_a -- cgit v1.2.3 From 265ac82029665921bd95d74411682968ee0d4ada Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Tue, 5 May 2026 19:41:50 +0200 Subject: refactoring do_mount_internal (retrieve path from dentry), handle .. correctly in relative path --- kernel/include/kernel/filesystem/vfs.hpp | 4 ++-- kernel/src/filesystem/vfs.cpp | 29 +++++++++++++++-------------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/kernel/include/kernel/filesystem/vfs.hpp b/kernel/include/kernel/filesystem/vfs.hpp index 881f458..7e66fb7 100644 --- a/kernel/include/kernel/filesystem/vfs.hpp +++ b/kernel/include/kernel/filesystem/vfs.hpp @@ -77,8 +77,8 @@ namespace kernel::filesystem auto init_internal() -> void; [[nodiscard]] auto resolve_path(std::string_view path) -> kstd::shared_ptr; - auto do_mount_internal(std::string_view path, kstd::shared_ptr const & mount_point_dentry, - kstd::shared_ptr const & fs) -> void; + auto do_mount_internal(kstd::shared_ptr const & mount_point_dentry, kstd::shared_ptr const & fs) + -> void; mount_table m_mount_table; }; diff --git a/kernel/src/filesystem/vfs.cpp b/kernel/src/filesystem/vfs.cpp index e6d9241..4defc81 100644 --- a/kernel/src/filesystem/vfs.cpp +++ b/kernel/src/filesystem/vfs.cpp @@ -56,14 +56,14 @@ namespace kernel::filesystem { kapi::system::panic("[FILESYSTEM] failed to resolve /dev for initial devfs mount."); } - do_mount_internal("/dev", dev_mount_point_dentry, device_fs); + do_mount_internal(dev_mount_point_dentry, device_fs); // Mount boot filesystem at / (will shadow rootfs) if (auto boot_device_dentry = resolve_path("/dev/ram0")) { if (auto boot_root_fs = kernel::filesystem::filesystem::probe_and_mount(boot_device_dentry->get_inode())) { - do_mount_internal("/", root_fs_root_dentry, boot_root_fs); + do_mount_internal(root_fs_root_dentry, boot_root_fs); // Resolve / to get the boot root dentry if (auto boot_root_dentry = resolve_path("/")) @@ -71,7 +71,7 @@ namespace kernel::filesystem auto dev_dentry = kstd::make_shared(boot_root_dentry, device_fs->root_inode(), "dev"); boot_root_dentry->add_child(dev_dentry); - do_mount_internal("/dev", dev_dentry, device_fs); + do_mount_internal(dev_dentry, device_fs); } } } @@ -105,7 +105,7 @@ namespace kernel::filesystem { if (auto fs = kernel::filesystem::filesystem::probe_and_mount(source_dentry->get_inode())) { - do_mount_internal(target, mount_point_dentry, fs); + do_mount_internal(mount_point_dentry, fs); return operation_result::success; } return operation_result::invalid_filesystem; @@ -136,13 +136,15 @@ namespace kernel::filesystem return operation_result::mount_point_not_found; } - auto vfs::do_mount_internal(std::string_view path, kstd::shared_ptr const & mount_point_dentry, + auto vfs::do_mount_internal(kstd::shared_ptr const & mount_point_dentry, kstd::shared_ptr const & fs) -> void { - auto parent_mount = m_mount_table.find_longest_prefix_mount(path); + auto mount_path = mount_point_dentry->get_full_path(); + + auto parent_mount = m_mount_table.find_longest_prefix_mount(mount_path.view()); auto new_fs_root = kstd::make_shared(mount_point_dentry->get_parent(), fs->root_inode(), mount_point_dentry->get_name()); - auto new_mount = kstd::make_shared(mount_point_dentry, new_fs_root, fs, path, parent_mount); + auto new_mount = kstd::make_shared(mount_point_dentry, new_fs_root, fs, mount_path.view(), parent_mount); m_mount_table.add_mount(new_mount); } @@ -188,16 +190,15 @@ namespace kernel::filesystem if (auto parent_mount = current_mount->get_parent_mount()) { current_mount = parent_mount; - current_dentry = current_dentry->get_parent(); + current_dentry = parent_dentry; + } - if (!parent_dentry) - { - parent_dentry = current_mount->root_dentry(); - } + if (!parent_dentry) + { + parent_dentry = current_mount->root_dentry(); } } - - if (!parent_dentry) + else if (!parent_dentry) { return nullptr; } -- cgit v1.2.3 From b7fba373341dd44b53642d7233396efbef4526b2 Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Tue, 5 May 2026 20:07:37 +0200 Subject: avoid to traverse back over the root --- kernel/src/filesystem/vfs.cpp | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/kernel/src/filesystem/vfs.cpp b/kernel/src/filesystem/vfs.cpp index 4defc81..7f21a5c 100644 --- a/kernel/src/filesystem/vfs.cpp +++ b/kernel/src/filesystem/vfs.cpp @@ -186,22 +186,17 @@ namespace kernel::filesystem if (current_dentry == current_mount->root_dentry()) { - // change the mount point - if (auto parent_mount = current_mount->get_parent_mount()) + if (current_mount->get_mount_path() == "/") { - current_mount = parent_mount; - current_dentry = parent_dentry; + continue; } - if (!parent_dentry) + if (auto parent_mount = current_mount->get_parent_mount()) { - parent_dentry = current_mount->root_dentry(); + current_mount = parent_mount; + current_dentry = parent_dentry; } } - else if (!parent_dentry) - { - return nullptr; - } current_dentry = parent_dentry; continue; -- cgit v1.2.3 From a0f1c6f2199f55d2faaa5d9eafa9f763b2b299e4 Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Tue, 5 May 2026 20:08:00 +0200 Subject: add symbolic link tests --- kernel/src/filesystem/vfs.tests.cpp | 42 +++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/kernel/src/filesystem/vfs.tests.cpp b/kernel/src/filesystem/vfs.tests.cpp index cbd67be..5160d15 100644 --- a/kernel/src/filesystem/vfs.tests.cpp +++ b/kernel/src/filesystem/vfs.tests.cpp @@ -301,5 +301,47 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, "VFS auto info_1 = vfs.open("/symlinks/info_1_relative"); REQUIRE(info_1 != nullptr); } + + THEN("file can be opened through symbolic link pointing absolute to the directory containing the file") + { + auto & vfs = kernel::filesystem::vfs::get(); + auto info_1 = vfs.open("/symlinks/information_directory_absolute/info_1.txt"); + REQUIRE(info_1 != nullptr); + } + + THEN("file can be opened through symbolic link pointing relative to the directory containing the file") + { + auto & vfs = kernel::filesystem::vfs::get(); + auto info_1 = vfs.open("/symlinks/information_directory_relative/info_1.txt"); + REQUIRE(info_1 != nullptr); + } + + THEN("symbolic link with path traversing back to the root") + { + auto & vfs = kernel::filesystem::vfs::get(); + auto info_1 = vfs.open("/symlinks/traverse_back_5_times/information/info_1.txt"); + REQUIRE(info_1 != nullptr); + } + + THEN("symbolic link containing an invalid absolute path is handled correctly") + { + auto & vfs = kernel::filesystem::vfs::get(); + auto invalid_symlink = vfs.open("/symlinks/invalid_absolute"); + REQUIRE(invalid_symlink == nullptr); + } + + THEN("symbolic link containing an invalid relative path is handled correctly") + { + auto & vfs = kernel::filesystem::vfs::get(); + auto invalid_symlink = vfs.open("/symlinks/invalid_relative"); + REQUIRE(invalid_symlink == nullptr); + } + + THEN("circular symbolic links are detected and handled correctly") + { + auto & vfs = kernel::filesystem::vfs::get(); + auto circular_symlink = vfs.open("/symlinks/symloop_a"); + REQUIRE(circular_symlink == nullptr); + } } } -- cgit v1.2.3 From 7414148b662a33cf6c69f89b7b0c3162f6880d6c Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Tue, 5 May 2026 20:33:28 +0200 Subject: refactoring mount_table lookup --- kernel/include/kernel/filesystem/mount_table.hpp | 10 ++++++++-- kernel/src/filesystem/mount_table.cpp | 8 ++++++++ kernel/src/filesystem/vfs.cpp | 10 ++++------ 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/kernel/include/kernel/filesystem/mount_table.hpp b/kernel/include/kernel/filesystem/mount_table.hpp index 00277ea..8e57d9e 100644 --- a/kernel/include/kernel/filesystem/mount_table.hpp +++ b/kernel/include/kernel/filesystem/mount_table.hpp @@ -39,13 +39,19 @@ namespace kernel::filesystem [[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. + @brief Finds the mount with the longest prefix matching the given @p path. @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; + /** + @brief Finds the mount with the exact mount path matching the given @p path. + @param path The path to match against the mount paths in the table. + @return A pointer to the mount with the exact matching path, or a null pointer if no mount matches the path. + */ + [[nodiscard]] auto find_exact_mount(std::string_view path) const -> kstd::shared_ptr; + private: [[nodiscard]] auto has_child_mounts(kstd::shared_ptr const & parent_mount) const -> bool; diff --git a/kernel/src/filesystem/mount_table.cpp b/kernel/src/filesystem/mount_table.cpp index e21e497..965e83b 100644 --- a/kernel/src/filesystem/mount_table.cpp +++ b/kernel/src/filesystem/mount_table.cpp @@ -102,4 +102,12 @@ namespace kernel::filesystem return mount_with_longest_prefix; } + + auto mount_table::find_exact_mount(std::string_view path) const -> kstd::shared_ptr + { + auto reversed_mounts = std::ranges::reverse_view(m_mounts); + auto mount_it = + std::ranges::find_if(reversed_mounts, [&](auto const & mount) { return mount->get_mount_path() == path; }); + return (mount_it != reversed_mounts.end()) ? *mount_it : nullptr; + } } // namespace kernel::filesystem \ No newline at end of file diff --git a/kernel/src/filesystem/vfs.cpp b/kernel/src/filesystem/vfs.cpp index 7f21a5c..19f5f48 100644 --- a/kernel/src/filesystem/vfs.cpp +++ b/kernel/src/filesystem/vfs.cpp @@ -41,7 +41,6 @@ namespace kernel::filesystem auto vfs::init_internal() -> void { - // Mount rootfs at / as the temporary base auto root_fs = kstd::make_shared(); root_fs->mount(nullptr); @@ -140,8 +139,8 @@ namespace kernel::filesystem kstd::shared_ptr const & fs) -> void { auto mount_path = mount_point_dentry->get_full_path(); - auto parent_mount = m_mount_table.find_longest_prefix_mount(mount_path.view()); + auto new_fs_root = kstd::make_shared(mount_point_dentry->get_parent(), fs->root_inode(), mount_point_dentry->get_name()); auto new_mount = kstd::make_shared(mount_point_dentry, new_fs_root, fs, mount_path.view(), parent_mount); @@ -156,7 +155,7 @@ namespace kernel::filesystem } // TODO BA-FS26 refactor (get mount by path, no more prefix matching) - auto current_mount = m_mount_table.find_longest_prefix_mount("/"); + auto current_mount = m_mount_table.find_exact_mount("/"); if (!current_mount) { kapi::system::panic("[FILESYSTEM] no root mount found."); @@ -217,8 +216,7 @@ namespace kernel::filesystem } else if (next_dentry->has_flag(dentry::dentry_flags::mounted)) { - // TODO BA-FS26 really do it like this? or just call "get" without longes_prefix stuff - current_mount = m_mount_table.find_longest_prefix_mount(next_dentry->get_full_path().view()); + current_mount = m_mount_table.find_exact_mount(next_dentry->get_full_path().view()); if (!current_mount) { kapi::system::panic("[FILESYSTEM] mount for dentry with mounted flag not found."); @@ -246,7 +244,7 @@ namespace kernel::filesystem if (path::is_valid_absolute_path(symbolic_link_path)) { - current_mount = m_mount_table.find_longest_prefix_mount("/"); + current_mount = m_mount_table.find_exact_mount("/"); current_dentry = current_mount->root_dentry(); } continue; -- cgit v1.2.3 From 8739d65ea50f13dbbb5bd1a27f461c367deb5b99 Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Tue, 5 May 2026 20:59:52 +0200 Subject: add symlink chain to image --- arch/x86_64/support/modules/README.md | 6 +++++- arch/x86_64/support/modules/ext2_4KB_fs.img | 2 +- kernel/src/test_support/filesystem/test_assets/ext2_4KB_fs.img | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/arch/x86_64/support/modules/README.md b/arch/x86_64/support/modules/README.md index 6893176..1a29ce5 100644 --- a/arch/x86_64/support/modules/README.md +++ b/arch/x86_64/support/modules/README.md @@ -78,9 +78,12 @@ The ext2_4KB_fs image is intentionally fragmented, as some files were created an ./enclosures/lion_house/cage_b ./enclosures/lion_house/cage_b/animals.txt ./enclosures/lion_house/cage_b/history.txt +./enclosures/lion_house/symlink_chain_2 -> /entrance/../enclosures/aquarium/symlink_chain_3 ./enclosures/elephant_house ./enclosures/elephant_house/elephant_1.txt ./enclosures/aquarium +./enclosures/aquarium/spawn_fish.sh +./enclosures/aquarium/symlink_chain_3 -> ../../entrance ./enclosures/aquarium/tank_1 ./enclosures/aquarium/tank_1/fish_1.txt ./enclosures/aquarium/tank_1/fish_2.txt @@ -117,7 +120,8 @@ The ext2_4KB_fs image is intentionally fragmented, as some files were created an ./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_28.txt ./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_29.txt ./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 -./enclosures/aquarium/spawn_fish.sh ./enclosures/info.txt ./info.txt +./symlinks +./symlinks/symlink_chain_1 -> ../enclosures/lion_house/symlink_chain_2 ./symlinks/very_long_symlink -> ../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 diff --git a/arch/x86_64/support/modules/ext2_4KB_fs.img b/arch/x86_64/support/modules/ext2_4KB_fs.img index 2cd452f..c3f6daf 100644 --- a/arch/x86_64/support/modules/ext2_4KB_fs.img +++ b/arch/x86_64/support/modules/ext2_4KB_fs.img @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:62e1fa40f95e0cb037c43e3f55d0094ab6cb4d68e00914f555a90faf7cc00c31 +oid sha256:026ca30269dbd80beb2dd74677c94676d1d4a7f6b5fe406c4ddb82836ba2dc00 size 10485760 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 index 2cd452f..c3f6daf 100644 --- a/kernel/src/test_support/filesystem/test_assets/ext2_4KB_fs.img +++ b/kernel/src/test_support/filesystem/test_assets/ext2_4KB_fs.img @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:62e1fa40f95e0cb037c43e3f55d0094ab6cb4d68e00914f555a90faf7cc00c31 +oid sha256:026ca30269dbd80beb2dd74677c94676d1d4a7f6b5fe406c4ddb82836ba2dc00 size 10485760 -- cgit v1.2.3 From 9d1fc7cbe7d3851541f1986dc4bc66a2bc944c89 Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Tue, 5 May 2026 21:00:17 +0200 Subject: add vfs symlink tests --- kernel/src/filesystem/vfs.tests.cpp | 63 ++++++++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/kernel/src/filesystem/vfs.tests.cpp b/kernel/src/filesystem/vfs.tests.cpp index 5160d15..2eec572 100644 --- a/kernel/src/filesystem/vfs.tests.cpp +++ b/kernel/src/filesystem/vfs.tests.cpp @@ -283,7 +283,7 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, "VFS } } - GIVEN("one real image files, containing symbolic links") + GIVEN("A real image files, containing symbolic links") { REQUIRE(std::filesystem::exists(image_path_1)); REQUIRE_NOTHROW(setup_modules_from_img_and_init_vfs({"test_img_module_1"}, {image_path_1})); @@ -344,4 +344,65 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, "VFS REQUIRE(circular_symlink == nullptr); } } + + GIVEN("A real image file containing as filesystem formatted files and this filesystem contains a symbolic link") + { + REQUIRE(std::filesystem::exists(image_path_1)); + REQUIRE_NOTHROW(setup_modules_from_img_and_init_vfs({"test_img_module_1"}, {image_path_1})); + + auto & vfs = kernel::filesystem::vfs::get(); + vfs.do_mount("/archiv/2024.img", "/information"); + + THEN("file can be opened through symbolic link pointing to the parent filesystem") + { + auto closed_file = vfs.open("/information/symlinks/traverse_back_twice/closed.txt"); + REQUIRE(closed_file != nullptr); + } + } + + GIVEN("Two real images, one containing a symbolic link leaving and reentering filesystem again") + { + REQUIRE(std::filesystem::exists(image_path_1)); + REQUIRE(std::filesystem::exists(image_path_2)); + REQUIRE_NOTHROW( + setup_modules_from_img_and_init_vfs({"test_img_module_1", "test_img_module_2"}, {image_path_1, image_path_2})); + + auto & vfs = kernel::filesystem::vfs::get(); + vfs.do_mount("/dev/ram16", "/information"); + + THEN("file can be opened through symbolic link pointing to the parent filesystem and back into the mounted " + "filesystem again") + { + auto monkey_1 = vfs.open("/information/symlinks/leave_and_reenter_mount/monkey_1.txt"); + REQUIRE(monkey_1 != nullptr); + } + } + + GIVEN("One real image containing a very long symbolic link") + { + REQUIRE(std::filesystem::exists(image_path_3)); + REQUIRE_NOTHROW(setup_modules_from_img_and_init_vfs({"test_img_module_3"}, {image_path_3})); + + auto & vfs = kernel::filesystem::vfs::get(); + + THEN("file can be opened through symbolic link with a long path") + { + auto fish_30 = vfs.open("/symlinks/very_long_symlink"); + REQUIRE(fish_30 != nullptr); + } + } + + GIVEN("One real image containing a valid symbolic link chain") + { + REQUIRE(std::filesystem::exists(image_path_3)); + REQUIRE_NOTHROW(setup_modules_from_img_and_init_vfs({"test_img_module_3"}, {image_path_3})); + + auto & vfs = kernel::filesystem::vfs::get(); + + THEN("file can be opened through symbolic link chain") + { + auto map = vfs.open("/symlinks/symlink_chain_1/map.txt"); + REQUIRE(map != nullptr); + } + } } -- cgit v1.2.3 From a2dff6812af354f282b731bc138e98a18bf7f35c Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Tue, 5 May 2026 21:13:45 +0200 Subject: add filesystem interface tests --- kernel/kapi/filesystem.tests.cpp | 49 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/kernel/kapi/filesystem.tests.cpp b/kernel/kapi/filesystem.tests.cpp index baa8613..8a532bb 100644 --- a/kernel/kapi/filesystem.tests.cpp +++ b/kernel/kapi/filesystem.tests.cpp @@ -16,7 +16,7 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, "Kap 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"; - GIVEN("a real image file") + GIVEN("Two real image files") { REQUIRE(std::filesystem::exists(image_path_1)); REQUIRE(std::filesystem::exists(image_path_2)); @@ -38,6 +38,53 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, "Kap REQUIRE(kapi::filesystem::close(fd) == 0); } + THEN("files can be opened through absolute symbolic link, read and closed again") + { + auto fd = kapi::filesystem::open("/symlinks/information_directory_absolute/info_1.txt"); + REQUIRE(fd >= 0); + + auto buffer = std::vector(6); + auto bytes_read = kapi::filesystem::read(fd, buffer.data(), buffer.size()); + REQUIRE(bytes_read >= 0); + + std::string_view buffer_as_str{reinterpret_cast(buffer.data()), static_cast(bytes_read)}; + REQUIRE(buffer_as_str == "info_1"); + + REQUIRE(kapi::filesystem::close(fd) == 0); + } + + THEN("files can be opened through relative symbolic link, read and closed again") + { + auto fd = kapi::filesystem::open("/symlinks/information_directory_relative/info_1.txt"); + REQUIRE(fd >= 0); + + auto buffer = std::vector(6); + auto bytes_read = kapi::filesystem::read(fd, buffer.data(), buffer.size()); + REQUIRE(bytes_read >= 0); + + std::string_view buffer_as_str{reinterpret_cast(buffer.data()), static_cast(bytes_read)}; + REQUIRE(buffer_as_str == "info_1"); + + REQUIRE(kapi::filesystem::close(fd) == 0); + } + + THEN("files can be opened through relative symbolic link over multiple mount points, read and closed again") + { + kapi::filesystem::mount("/archiv/2024.img", "/information"); + + auto fd = kapi::filesystem::open("/information/symlinks/traverse_back_twice/information/sheep_1.txt"); + REQUIRE(fd >= 0); + + auto buffer = std::vector(7); + auto bytes_read = kapi::filesystem::read(fd, buffer.data(), buffer.size()); + REQUIRE(bytes_read >= 0); + + std::string_view buffer_as_str{reinterpret_cast(buffer.data()), static_cast(bytes_read)}; + REQUIRE(buffer_as_str == "sheep_1"); + + REQUIRE(kapi::filesystem::close(fd) == 0); + } + THEN("a filesystem can be mounted, files can be opened, read and closed again and unmounted") { REQUIRE(kapi::filesystem::mount("/dev/ram16", "/information") == 0); -- cgit v1.2.3 From 7ccfa26a3dde4d4266f8c59f4e3de8bd6f760059 Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Tue, 5 May 2026 21:17:23 +0200 Subject: small refactoring, add todo --- kernel/kapi/filesystem.tests.cpp | 6 ++++-- kernel/src/filesystem/vfs.cpp | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/kernel/kapi/filesystem.tests.cpp b/kernel/kapi/filesystem.tests.cpp index 8a532bb..1d1f8ee 100644 --- a/kernel/kapi/filesystem.tests.cpp +++ b/kernel/kapi/filesystem.tests.cpp @@ -161,14 +161,16 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, "Kap THEN("not opened files cannot be read from") { std::vector buffer(10); - auto bytes_read = kapi::filesystem::read(999, buffer.data(), buffer.size()); + auto const invalid_fd = 999uz; + auto bytes_read = kapi::filesystem::read(invalid_fd, buffer.data(), buffer.size()); REQUIRE(bytes_read < 0); } THEN("not opened files cannot be written to") { std::vector buffer(10); - auto bytes_written = kapi::filesystem::write(999, buffer.data(), buffer.size()); + auto const invalid_fd = 999uz; + auto bytes_written = kapi::filesystem::write(invalid_fd, buffer.data(), buffer.size()); REQUIRE(bytes_written < 0); } } diff --git a/kernel/src/filesystem/vfs.cpp b/kernel/src/filesystem/vfs.cpp index 19f5f48..b84f690 100644 --- a/kernel/src/filesystem/vfs.cpp +++ b/kernel/src/filesystem/vfs.cpp @@ -139,6 +139,7 @@ namespace kernel::filesystem kstd::shared_ptr const & fs) -> void { auto mount_path = mount_point_dentry->get_full_path(); + // TODO BA-FS26 refactoring, implement dentry lookup to get the parent mount... auto parent_mount = m_mount_table.find_longest_prefix_mount(mount_path.view()); auto new_fs_root = -- cgit v1.2.3 From 870039d48d28f479eea88c7548b8d2b2a28c09bc Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Tue, 5 May 2026 21:18:48 +0200 Subject: add mount table find_exact_mount tests, remove todo --- kernel/src/filesystem/mount_table.tests.cpp | 21 +++++++++++++++++++-- kernel/src/filesystem/vfs.cpp | 1 - 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/kernel/src/filesystem/mount_table.tests.cpp b/kernel/src/filesystem/mount_table.tests.cpp index e028ac8..efacdfe 100644 --- a/kernel/src/filesystem/mount_table.tests.cpp +++ b/kernel/src/filesystem/mount_table.tests.cpp @@ -58,7 +58,7 @@ SCENARIO("Adding, finding and removing mounts in the mount table", "[filesystem] REQUIRE_FALSE(root_dentry2->has_flag(kernel::filesystem::dentry::dentry_flags::mounted)); } - THEN("finding mounts by path returns the correct mount") + THEN("finding mounts by longest prefix returns the correct mount") { REQUIRE(table.find_longest_prefix_mount("/") == mount1); REQUIRE(table.find_longest_prefix_mount("/file") == mount1); @@ -67,6 +67,18 @@ SCENARIO("Adding, finding and removing mounts in the mount table", "[filesystem] REQUIRE(table.find_longest_prefix_mount("/other") == mount1); } + THEN("finding mounts by exact valid path returns the correct mount") + { + REQUIRE(table.find_exact_mount("/") == mount1); + REQUIRE(table.find_exact_mount("/mnt") == mount2); + } + + THEN("finding mounts by exact invalid path returns null") + { + REQUIRE(table.find_exact_mount("/nonexistent") == nullptr); + REQUIRE(table.find_exact_mount("/mnt/file") == nullptr); + } + THEN("removing a mount that has no child mounts succeeds") { REQUIRE(table.remove_mount("/mnt") == kernel::filesystem::mount_table::operation_result::removed); @@ -97,7 +109,7 @@ SCENARIO("Adding, finding and removing mounts in the mount table", "[filesystem] table.add_mount(mount1); table.add_mount(mount2); - THEN("finding mounts by path returns the correct mount based on longest prefix") + THEN("finding mounts by longest prefix returns the correct mount") { REQUIRE(table.find_longest_prefix_mount("/") == mount2); REQUIRE(table.find_longest_prefix_mount("/file") == mount2); @@ -106,6 +118,11 @@ SCENARIO("Adding, finding and removing mounts in the mount table", "[filesystem] REQUIRE(table.find_longest_prefix_mount("/other") == mount2); } + THEN("finding mounts by exact valid path returns the correct mount") + { + REQUIRE(table.find_exact_mount("/") == mount2); + } + THEN("removing the topmost mount with the same path succeeds") { REQUIRE(table.remove_mount("/") == kernel::filesystem::mount_table::operation_result::removed); diff --git a/kernel/src/filesystem/vfs.cpp b/kernel/src/filesystem/vfs.cpp index b84f690..519550b 100644 --- a/kernel/src/filesystem/vfs.cpp +++ b/kernel/src/filesystem/vfs.cpp @@ -155,7 +155,6 @@ namespace kernel::filesystem return nullptr; } - // TODO BA-FS26 refactor (get mount by path, no more prefix matching) auto current_mount = m_mount_table.find_exact_mount("/"); if (!current_mount) { -- cgit v1.2.3 From 4522374b902ee9a30c83c2ec23880522e80febea Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Tue, 5 May 2026 21:37:12 +0200 Subject: .. int the root directory remains in the root --- kernel/src/filesystem/vfs.tests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/src/filesystem/vfs.tests.cpp b/kernel/src/filesystem/vfs.tests.cpp index 2eec572..8e4cb70 100644 --- a/kernel/src/filesystem/vfs.tests.cpp +++ b/kernel/src/filesystem/vfs.tests.cpp @@ -187,7 +187,7 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, "VFS { REQUIRE(vfs.do_mount("/dev/ram16", "/information") == kernel::filesystem::vfs::operation_result::success); - auto img = vfs.open("/information/monkey_house/caretaker/../../../archiv/2024.img"); + auto img = vfs.open("/information/monkey_house/caretaker/../../../../../../archiv/2024.img"); REQUIRE(img != nullptr); auto dev_32 = vfs.open("/information/monkey_house/caretaker/../../../dev/ram32"); -- cgit v1.2.3