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/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 ++++---- 7 files changed, 67 insertions(+), 66 deletions(-) (limited to 'kernel/src/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 -- 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 --- kernel/src/filesystem/ext2/inode.cpp | 5 +++++ kernel/src/filesystem/ext2/inode.tests.cpp | 24 ++++++++++++++++++++++++ kernel/src/filesystem/inode.cpp | 5 +++++ 3 files changed, 34 insertions(+) (limited to 'kernel/src/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 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/src/filesystem/mount_table.cpp | 4 +- kernel/src/filesystem/vfs.cpp | 115 +++++++++++++++++++++++++++++----- 2 files changed, 102 insertions(+), 17 deletions(-) (limited to 'kernel/src/filesystem') 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(-) (limited to 'kernel/src/filesystem') 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(+) (limited to 'kernel/src/filesystem') 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/src/filesystem/dentry.cpp | 17 +++++++++++++++++ kernel/src/filesystem/vfs.cpp | 32 ++------------------------------ 2 files changed, 19 insertions(+), 30 deletions(-) (limited to 'kernel/src/filesystem') 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(+) (limited to 'kernel/src/filesystem') 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 + 6 files changed, 21 insertions(+), 18 deletions(-) (limited to 'kernel/src/filesystem') 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") -- 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(-) (limited to 'kernel/src/filesystem') 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 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(-) (limited to 'kernel/src/filesystem') 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/src/filesystem/ext2/inode.cpp | 16 +++++++++++++++- kernel/src/filesystem/open_file_descriptor.tests.cpp | 13 ++++--------- 2 files changed, 19 insertions(+), 10 deletions(-) (limited to 'kernel/src/filesystem') 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(-) (limited to 'kernel/src/filesystem') 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(-) (limited to 'kernel/src/filesystem') 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 --- kernel/src/filesystem/vfs.tests.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'kernel/src/filesystem') 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); + } + } } -- 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/src/filesystem/vfs.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'kernel/src/filesystem') 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(-) (limited to 'kernel/src/filesystem') 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 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(-) (limited to 'kernel/src/filesystem') 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/src/filesystem/path.tests.cpp | 69 ++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 kernel/src/filesystem/path.tests.cpp (limited to 'kernel/src/filesystem') 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 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(-) (limited to 'kernel/src/filesystem') 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 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/src/filesystem/vfs.cpp | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) (limited to 'kernel/src/filesystem') 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(-) (limited to 'kernel/src/filesystem') 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(+) (limited to 'kernel/src/filesystem') 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/src/filesystem/mount_table.cpp | 8 ++++++++ kernel/src/filesystem/vfs.cpp | 10 ++++------ 2 files changed, 12 insertions(+), 6 deletions(-) (limited to 'kernel/src/filesystem') 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 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(-) (limited to 'kernel/src/filesystem') 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 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/src/filesystem/vfs.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'kernel/src/filesystem') 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(-) (limited to 'kernel/src/filesystem') 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(-) (limited to 'kernel/src/filesystem') 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