aboutsummaryrefslogtreecommitdiff
path: root/kernel/src/filesystem
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/src/filesystem')
-rw-r--r--kernel/src/filesystem/dentry.cpp17
-rw-r--r--kernel/src/filesystem/dentry.tests.cpp16
-rw-r--r--kernel/src/filesystem/devfs/filesystem.tests.cpp2
-rw-r--r--kernel/src/filesystem/devfs/inode.cpp11
-rw-r--r--kernel/src/filesystem/devfs/inode.tests.cpp1
-rw-r--r--kernel/src/filesystem/device_inode.cpp9
-rw-r--r--kernel/src/filesystem/device_inode.tests.cpp1
-rw-r--r--kernel/src/filesystem/ext2/filesystem.cpp36
-rw-r--r--kernel/src/filesystem/ext2/inode.cpp42
-rw-r--r--kernel/src/filesystem/ext2/inode.tests.cpp65
-rw-r--r--kernel/src/filesystem/inode.cpp15
-rw-r--r--kernel/src/filesystem/mount_table.cpp12
-rw-r--r--kernel/src/filesystem/mount_table.tests.cpp31
-rw-r--r--kernel/src/filesystem/open_file_descriptor.tests.cpp13
-rw-r--r--kernel/src/filesystem/path.tests.cpp69
-rw-r--r--kernel/src/filesystem/rootfs/inode.cpp9
-rw-r--r--kernel/src/filesystem/rootfs/inode.tests.cpp1
-rw-r--r--kernel/src/filesystem/vfs.cpp151
-rw-r--r--kernel/src/filesystem/vfs.tests.cpp176
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);
+ }
+ }
}