diff options
| author | Marcel Braun <marcel.braun@ost.ch> | 2026-05-05 23:25:47 +0200 |
|---|---|---|
| committer | Marcel Braun <marcel.braun@ost.ch> | 2026-05-05 23:25:47 +0200 |
| commit | ea450f4ed742a37b40a4e1dcaf8d17328d635472 (patch) | |
| tree | cd9511ff9145dd993f257c2fcca4d8d06e857625 /kernel/src/filesystem | |
| parent | 3082340fa8ab3c7c0da5d2f9d321d2367d399b20 (diff) | |
| parent | 4522374b902ee9a30c83c2ec23880522e80febea (diff) | |
| download | kernel-ea450f4ed742a37b40a4e1dcaf8d17328d635472.tar.xz kernel-ea450f4ed742a37b40a4e1dcaf8d17328d635472.zip | |
Merge branch 'symbolic-links' into 'develop-BA-FS26'
Symbolic links
See merge request teachos/kernel!29
Diffstat (limited to 'kernel/src/filesystem')
| -rw-r--r-- | kernel/src/filesystem/dentry.cpp | 17 | ||||
| -rw-r--r-- | kernel/src/filesystem/dentry.tests.cpp | 16 | ||||
| -rw-r--r-- | kernel/src/filesystem/devfs/filesystem.tests.cpp | 2 | ||||
| -rw-r--r-- | kernel/src/filesystem/devfs/inode.cpp | 11 | ||||
| -rw-r--r-- | kernel/src/filesystem/devfs/inode.tests.cpp | 1 | ||||
| -rw-r--r-- | kernel/src/filesystem/device_inode.cpp | 9 | ||||
| -rw-r--r-- | kernel/src/filesystem/device_inode.tests.cpp | 1 | ||||
| -rw-r--r-- | kernel/src/filesystem/ext2/filesystem.cpp | 36 | ||||
| -rw-r--r-- | kernel/src/filesystem/ext2/inode.cpp | 42 | ||||
| -rw-r--r-- | kernel/src/filesystem/ext2/inode.tests.cpp | 65 | ||||
| -rw-r--r-- | kernel/src/filesystem/inode.cpp | 15 | ||||
| -rw-r--r-- | kernel/src/filesystem/mount_table.cpp | 12 | ||||
| -rw-r--r-- | kernel/src/filesystem/mount_table.tests.cpp | 31 | ||||
| -rw-r--r-- | kernel/src/filesystem/open_file_descriptor.tests.cpp | 13 | ||||
| -rw-r--r-- | kernel/src/filesystem/path.tests.cpp | 69 | ||||
| -rw-r--r-- | kernel/src/filesystem/rootfs/inode.cpp | 9 | ||||
| -rw-r--r-- | kernel/src/filesystem/rootfs/inode.tests.cpp | 1 | ||||
| -rw-r--r-- | kernel/src/filesystem/vfs.cpp | 151 | ||||
| -rw-r--r-- | kernel/src/filesystem/vfs.tests.cpp | 176 |
19 files changed, 545 insertions, 132 deletions
diff --git a/kernel/src/filesystem/dentry.cpp b/kernel/src/filesystem/dentry.cpp index 572dd82..1cf8730 100644 --- a/kernel/src/filesystem/dentry.cpp +++ b/kernel/src/filesystem/dentry.cpp @@ -5,6 +5,7 @@ #include <kapi/system.hpp> #include <kstd/memory> +#include <kstd/string> #include <algorithm> #include <cstdint> @@ -38,6 +39,22 @@ namespace kernel::filesystem return m_name.view(); } + // NOLINTNEXTLINE(misc-no-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.view(); + } + auto dentry::add_child(kstd::shared_ptr<dentry> const & child) -> void { m_children.push_back(child); diff --git a/kernel/src/filesystem/dentry.tests.cpp b/kernel/src/filesystem/dentry.tests.cpp 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/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.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 <kernel/filesystem/devfs/inode.hpp> -#include <kernel/filesystem/inode.hpp> - #include <cstddef> 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/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.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 <kernel/filesystem/device_inode.hpp> #include <kernel/devices/block_device_utils.hpp> -#include <kernel/filesystem/inode.hpp> #include <kapi/devices/device.hpp> #include <kapi/system.hpp> @@ -13,8 +12,7 @@ namespace kernel::filesystem { device_inode::device_inode(kstd::shared_ptr<kapi::devices::device> 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/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/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<kernel::filesystem::inode> 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<uint8_t> 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<size_t>(inode_table_start_block) * block_size; auto const inode_offset = inode_table_offset + inode_index_within_group * get_inode_size(); - auto new_inode = kstd::make_shared<inode>(this); - 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<inode>(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..1914c70 100644 --- a/kernel/src/filesystem/ext2/inode.cpp +++ b/kernel/src/filesystem/ext2/inode.cpp @@ -5,15 +5,17 @@ #include <kapi/system.hpp> +#include <kstd/cstring> + #include <algorithm> #include <cstddef> #include <cstdint> 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) { @@ -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<size_t>(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<uint8_t const *>(m_data.block.data()); + kstd::libc::memcpy(static_cast<uint8_t *>(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<uint8_t *>(buffer) + bytes_read, read_offset, bytes_to_read); @@ -57,4 +71,24 @@ 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; + } + + 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 4d61790..49ba21b 100644 --- a/kernel/src/filesystem/ext2/inode.tests.cpp +++ b/kernel/src/filesystem/ext2/inode.tests.cpp @@ -23,13 +23,50 @@ 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()); + 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") + { + data.mode = kernel::filesystem::ext2::constants::mode_directory; + auto inode = kernel::filesystem::ext2::inode(&fs, data); + + REQUIRE_FALSE(inode.is_regular()); + REQUIRE(inode.is_directory()); + 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") + { + data.mode = kernel::filesystem::ext2::constants::mode_symbolic_link; + auto inode = kernel::filesystem::ext2::inode(&fs, data); + + REQUIRE_FALSE(inode.is_regular()); + REQUIRE_FALSE(inode.is_directory()); + REQUIRE_FALSE(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_FALSE(inode.is_regular()); + REQUIRE_FALSE(inode.is_directory()); + REQUIRE_FALSE(inode.is_device()); + REQUIRE_FALSE(inode.is_symbolic_link()); } } @@ -37,7 +74,7 @@ SCENARIO("Ext2 inode initialization and properties", "[filesystem][ext2][inode]" { 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 +139,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<std::byte>(32, std::byte{0xAB}); @@ -130,12 +168,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<std::byte>(12, std::byte{0x00}); @@ -155,7 +194,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..c188917 100644 --- a/kernel/src/filesystem/inode.cpp +++ b/kernel/src/filesystem/inode.cpp @@ -2,22 +2,23 @@ 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; + } + + auto inode::is_symbolic_link() const -> bool + { + return false; } } // namespace kernel::filesystem
\ No newline at end of file diff --git a/kernel/src/filesystem/mount_table.cpp b/kernel/src/filesystem/mount_table.cpp index da3c451..965e83b 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; } @@ -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<mount> + { + 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/mount_table.tests.cpp b/kernel/src/filesystem/mount_table.tests.cpp index 747ffdf..efacdfe 100644 --- a/kernel/src/filesystem/mount_table.tests.cpp +++ b/kernel/src/filesystem/mount_table.tests.cpp @@ -54,11 +54,11 @@ 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_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,10 +67,22 @@ 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); - REQUIRE(!root_dentry2->has_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted)); + REQUIRE_FALSE(root_dentry2->has_flag(kernel::filesystem::dentry::dentry_flags::mounted)); REQUIRE(table.find_longest_prefix_mount("/mnt") == mount1); } @@ -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,10 +118,15 @@ 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); - REQUIRE(!root_dentry2->has_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted)); + REQUIRE_FALSE(root_dentry2->has_flag(kernel::filesystem::dentry::dentry_flags::mounted)); REQUIRE(table.find_longest_prefix_mount("/") == mount1); } } @@ -156,7 +173,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_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/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 <kernel/filesystem/open_file_descriptor.hpp> + #include <kernel/filesystem/vfs.hpp> #include <kernel/test_support/filesystem/inode.hpp> #include <kernel/test_support/filesystem/storage_boot_module_vfs_fixture.hpp> @@ -83,17 +84,11 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, "Ope { kstd::vector<std::byte> 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<char *>(buffer.data()), static_cast<size_t>(bytes_read)}; - auto const content_end = buffer_as_str.find('\0'); - REQUIRE(buffer_as_str.substr(0, content_end) == "info_1\n"); - - for (auto i = content_end; i < buffer_as_str.size(); ++i) - { - REQUIRE(buffer_as_str[i] == '\0'); - } + REQUIRE(buffer_as_str == "info_1\n"); } THEN("the file can be read multiple times") 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 <kernel/filesystem/path.hpp> + +#include <catch2/catch_test_macros.hpp> + +#include <algorithm> +#include <string> +#include <string_view> +#include <vector> + +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<std::string_view> expected = {"a", "b", "c", "d.txt"}; + REQUIRE(std::ranges::equal(components, expected)); + } + } +}
\ 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/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/kernel/src/filesystem/vfs.cpp b/kernel/src/filesystem/vfs.cpp index 5b454f6..519550b 100644 --- a/kernel/src/filesystem/vfs.cpp +++ b/kernel/src/filesystem/vfs.cpp @@ -1,16 +1,22 @@ #include <kernel/filesystem/vfs.hpp> +#include <kernel/filesystem/constants.hpp> #include <kernel/filesystem/dentry.hpp> #include <kernel/filesystem/devfs/filesystem.hpp> #include <kernel/filesystem/filesystem.hpp> #include <kernel/filesystem/mount.hpp> #include <kernel/filesystem/mount_table.hpp> +#include <kernel/filesystem/path.hpp> #include <kernel/filesystem/rootfs/filesystem.hpp> #include <kapi/system.hpp> #include <kstd/memory> +#include <kstd/string> +#include <kstd/vector> +#include <algorithm> +#include <cstdint> #include <optional> #include <ranges> #include <string_view> @@ -38,18 +44,34 @@ namespace kernel::filesystem auto root_fs = kstd::make_shared<rootfs::filesystem>(); root_fs->mount(nullptr); - auto root_fs_root_dentry = kstd::make_shared<dentry>(nullptr, root_fs->root_inode()); - m_mount_table.add_mount(kstd::make_shared<mount>(nullptr, root_fs_root_dentry, root_fs, "", nullptr)); + auto root_fs_root_dentry = kstd::make_shared<dentry>(nullptr, root_fs->root_inode(), "/"); + m_mount_table.add_mount(kstd::make_shared<mount>(nullptr, root_fs_root_dentry, root_fs, "/", nullptr)); + // Mount devfs at /dev in rootfs (temporary, will be shadowed) auto device_fs = kstd::make_shared<devfs::filesystem>(); 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_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); + 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<dentry>(boot_root_dentry, device_fs->root_inode(), "dev"); + boot_root_dentry->add_child(dev_dentry); + + do_mount_internal(dev_dentry, device_fs); + } } } } @@ -71,7 +93,7 @@ namespace kernel::filesystem auto vfs::do_mount(std::string_view source, std::string_view target) -> operation_result { - if (target.empty() || target.front() != '/' || (target.size() > 1 && target.back() == '/')) + if (!path::is_valid_path(source) || !path::is_valid_path(target)) { return operation_result::invalid_path; } @@ -82,7 +104,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; @@ -94,7 +116,7 @@ namespace kernel::filesystem auto vfs::unmount(std::string_view path) -> operation_result { - if (path.empty() || path.front() != '/' || (path.size() > 1 && path.back() == '/')) + if (!path::is_valid_path(path)) { return operation_result::invalid_path; } @@ -113,55 +135,120 @@ namespace kernel::filesystem return operation_result::mount_point_not_found; } - auto vfs::do_mount_internal(std::string_view path, kstd::shared_ptr<dentry> const & mount_point_dentry, + auto vfs::do_mount_internal(kstd::shared_ptr<dentry> const & mount_point_dentry, kstd::shared_ptr<filesystem> const & fs) -> void { - auto parent_mount = m_mount_table.find_longest_prefix_mount(path); - auto new_fs_root = kstd::make_shared<dentry>(mount_point_dentry, fs->root_inode()); - auto new_mount = kstd::make_shared<mount>(mount_point_dentry, new_fs_root, fs, path, parent_mount); + 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 = + kstd::make_shared<dentry>(mount_point_dentry->get_parent(), fs->root_inode(), mount_point_dentry->get_name()); + auto new_mount = kstd::make_shared<mount>(mount_point_dentry, new_fs_root, fs, mount_path.view(), parent_mount); m_mount_table.add_mount(new_mount); } auto vfs::resolve_path(std::string_view path) -> kstd::shared_ptr<dentry> { - // 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() != '/') + if (!path::is_valid_absolute_path(path)) + { 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_exact_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(); - std::string_view remaining = path.substr(best_mount->get_mount_path().size()); + auto path_parts = path::split(path); + kstd::vector path_parts_vector(path_parts.begin(), path_parts.end()); + std::ranges::reverse(path_parts_vector); - auto path_parts = - std::views::split(remaining, '/') | std::views::filter([](auto const & part) { return !part.empty(); }); + auto symlink_counter = 0uz; - for (auto const & part : path_parts) + while (!path_parts_vector.empty()) { - std::string_view part_view{part}; + auto part = path_parts_vector.back(); + path_parts_vector.pop_back(); + + if (part == ".") + { + continue; + } + + if (part == "..") + { + auto parent_dentry = current_dentry->get_parent(); + + if (current_dentry == current_mount->root_dentry()) + { + if (current_mount->get_mount_path() == "/") + { + continue; + } + + if (auto parent_mount = current_mount->get_parent_mount()) + { + current_mount = parent_mount; + current_dentry = parent_dentry; + } + } + + current_dentry = parent_dentry; + continue; + } - auto next_dentry = current_dentry->find_child(part_view); + auto next_dentry = current_dentry->find_child(part.view()); if (!next_dentry) { - auto found_inode = current_fs->lookup(current_dentry->get_inode(), part_view); + 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<dentry>(current_dentry, found_inode, part_view); + next_dentry = kstd::make_shared<dentry>(current_dentry, found_inode, part.view()); current_dentry->add_child(next_dentry); } + else if (next_dentry->has_flag(dentry::dentry_flags::mounted)) + { + 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."); + } + + next_dentry = current_mount->root_dentry(); + } + + if (next_dentry->get_inode()->is_symbolic_link()) + { + if (symlink_counter++ > constants::symloop_max) + { + return nullptr; + } + + kstd::vector<uint8_t> 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<char const *>(buffer.data()), bytes_read}; + + 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 (path::is_valid_absolute_path(symbolic_link_path)) + { + current_mount = m_mount_table.find_exact_mount("/"); + current_dentry = current_mount->root_dentry(); + } + continue; + } current_dentry = next_dentry; } diff --git a/kernel/src/filesystem/vfs.tests.cpp b/kernel/src/filesystem/vfs.tests.cpp index 979ea42..8e4cb70 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") @@ -168,6 +167,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") @@ -237,4 +282,127 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, "VFS REQUIRE(unmounted_sheep_1 == nullptr); } } + + 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})); + + 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); + } + + 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); + } + } + + 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); + } + } } |
