aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFelix Morgner <felix.morgner@ost.ch>2026-07-15 09:27:43 +0200
committerFelix Morgner <felix.morgner@ost.ch>2026-07-15 09:27:43 +0200
commitd7ad126367675fb50276f96db760dd1af8332352 (patch)
treeffa17592e4eff7ad60d3d8e05c54496590ce98c6
parentd6735726d9cc6e98142e94001250076592279d36 (diff)
parent8170017c8fcea4b9047c7a1d586bb82473c1eed5 (diff)
downloadkernel-d7ad126367675fb50276f96db760dd1af8332352.tar.xz
kernel-d7ad126367675fb50276f96db760dd1af8332352.zip
Merge branch 'experimental/ext2-write-support' into 'develop'
ext2: add basic write support See merge request teachos/kernel!55
-rw-r--r--kapi/kapi/filesystem.hpp18
-rw-r--r--kernel/kapi/filesystem.cpp12
-rw-r--r--kernel/kapi/filesystem.tests.cpp82
-rw-r--r--kernel/kernel/filesystem/devfs/filesystem.cpp10
-rw-r--r--kernel/kernel/filesystem/devfs/filesystem.hpp12
-rw-r--r--kernel/kernel/filesystem/devfs/filesystem.tests.cpp7
-rw-r--r--kernel/kernel/filesystem/error.hpp10
-rw-r--r--kernel/kernel/filesystem/ext2/error.hpp13
-rw-r--r--kernel/kernel/filesystem/ext2/filesystem.cpp526
-rw-r--r--kernel/kernel/filesystem/ext2/filesystem.hpp90
-rw-r--r--kernel/kernel/filesystem/ext2/filesystem.tests.cpp194
-rw-r--r--kernel/kernel/filesystem/ext2/inode.cpp116
-rw-r--r--kernel/kernel/filesystem/ext2/inode.hpp34
-rw-r--r--kernel/kernel/filesystem/ext2/inode.tests.cpp293
-rw-r--r--kernel/kernel/filesystem/filesystem.hpp12
-rw-r--r--kernel/kernel/filesystem/open_file_descriptor.tests.cpp22
-rw-r--r--kernel/kernel/filesystem/path.hpp35
-rw-r--r--kernel/kernel/filesystem/path.tests.cpp59
-rw-r--r--kernel/kernel/filesystem/rootfs/filesystem.cpp9
-rw-r--r--kernel/kernel/filesystem/rootfs/filesystem.hpp12
-rw-r--r--kernel/kernel/filesystem/rootfs/filesystem.tests.cpp8
-rw-r--r--kernel/kernel/filesystem/vfs.cpp51
-rw-r--r--kernel/kernel/filesystem/vfs.hpp18
-rw-r--r--kernel/kernel/filesystem/vfs.tests.cpp47
-rw-r--r--kernel/kernel/filesystem/vfs_types.hpp16
-rw-r--r--kernel/kernel/main.cpp33
-rw-r--r--kernel/kernel/test_support/filesystem/ext2.cpp12
-rw-r--r--kernel/kernel/test_support/filesystem/ext2.hpp4
-rw-r--r--kernel/kernel/test_support/filesystem/filesystem.cpp12
-rw-r--r--kernel/kernel/test_support/filesystem/filesystem.hpp9
30 files changed, 1704 insertions, 72 deletions
diff --git a/kapi/kapi/filesystem.hpp b/kapi/kapi/filesystem.hpp
index 5346a96d..1656a17f 100644
--- a/kapi/kapi/filesystem.hpp
+++ b/kapi/kapi/filesystem.hpp
@@ -1,6 +1,7 @@
#ifndef TEACHOS_KAPI_FILESYSTEM_HPP
#define TEACHOS_KAPI_FILESYSTEM_HPP
+#include <kstd/result.hpp>
#include <kstd/system_error.hpp>
#include <cstddef>
@@ -69,6 +70,23 @@ namespace kapi::filesystem
@qualifier kernel-defined
*/
auto write(size_t file_descriptor, void const * buffer, size_t size) -> std::expected<std::size_t, kstd::error_code>;
+
+ /**
+ @brief Creates a new directory at the specified @p path.
+ @param path The path where the new directory should be created.
+ @return 0 on success, -1 on failure.
+ @qualifier kernel-defined
+ */
+ auto mkdir(std::string_view path) -> kstd::result<void>;
+
+ /**
+ @brief Creates a new file at the specified @p path.
+ @param path The path where the new file should be created.
+ @return 0 on success, -1 on failure.
+ @qualifier kernel-defined
+ */
+ // TODO remove again after the open method supports an optional create flag
+ auto create(std::string_view path) -> kstd::result<void>;
} // namespace kapi::filesystem
#endif // TEACHOS_KAPI_FILESYSTEM_HPP \ No newline at end of file
diff --git a/kernel/kapi/filesystem.cpp b/kernel/kapi/filesystem.cpp
index efde1336..822095e6 100644
--- a/kernel/kapi/filesystem.cpp
+++ b/kernel/kapi/filesystem.cpp
@@ -5,6 +5,7 @@
#include <kernel/filesystem/vfs.hpp>
#include <kstd/memory.hpp>
+#include <kstd/result.hpp>
#include <kstd/system_error.hpp>
#include <cstddef>
@@ -53,4 +54,15 @@ namespace kapi::filesystem
return descriptor->write(buffer, size);
});
}
+
+ auto mkdir(std::string_view path) -> kstd::result<void>
+ {
+ return kernel::filesystem::vfs::get().mkdir(path);
+ }
+
+ auto create(std::string_view path) -> kstd::result<void>
+ {
+ return kernel::filesystem::vfs::get().create(path);
+ }
+
} // namespace kapi::filesystem \ No newline at end of file
diff --git a/kernel/kapi/filesystem.tests.cpp b/kernel/kapi/filesystem.tests.cpp
index d1e01db6..f2a6a988 100644
--- a/kernel/kapi/filesystem.tests.cpp
+++ b/kernel/kapi/filesystem.tests.cpp
@@ -39,6 +39,30 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, "Kap
REQUIRE(kapi::filesystem::close(fd));
}
+ THEN("files can be opened, written to, read from and closed again")
+ {
+ auto write_fd = kapi::filesystem::open("/information/info_1.txt");
+ REQUIRE(write_fd);
+
+ auto write_buffer = std::vector<std::byte>{std::byte{'H'}, std::byte{'e'}};
+
+ auto const bytes_written = kapi::filesystem::write(*write_fd, write_buffer.data(), write_buffer.size());
+ REQUIRE(bytes_written == 2);
+
+ auto read_fd = kapi::filesystem::open("/information/info_1.txt");
+ REQUIRE(read_fd);
+
+ auto read_buffer = std::vector<std::byte>(6);
+ auto const bytes_read = kapi::filesystem::read(*read_fd, read_buffer.data(), read_buffer.size());
+ REQUIRE(bytes_read);
+
+ std::string_view buffer_as_str{reinterpret_cast<char *>(read_buffer.data()), static_cast<size_t>(*bytes_read)};
+ REQUIRE(buffer_as_str == "Hefo_1");
+
+ REQUIRE(kapi::filesystem::close(*write_fd));
+ REQUIRE(kapi::filesystem::close(*read_fd));
+ }
+
THEN("files can be opened through absolute symbolic link, read and closed again")
{
auto fd = kapi::filesystem::open("/symlinks/information_directory_absolute/info_1.txt").value();
@@ -187,5 +211,63 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, "Kap
auto bytes_written = kapi::filesystem::write(invalid_fd, buffer.data(), buffer.size());
REQUIRE(!bytes_written);
}
+
+ THEN("a new file can be created, written to, read from, and closed again")
+ {
+ auto new_file_path = "/information/new_information.txt";
+
+ REQUIRE(!kapi::filesystem::open(new_file_path));
+
+ REQUIRE(kapi::filesystem::create(new_file_path));
+
+ auto fd = kapi::filesystem::open(new_file_path);
+ REQUIRE(fd);
+
+ auto write_buffer = std::vector<std::byte>{std::byte{'T'}, std::byte{'e'}, std::byte{'s'}, std::byte{'t'}};
+ CHECK(kapi::filesystem::write(*fd, write_buffer.data(), write_buffer.size()));
+
+ REQUIRE(kapi::filesystem::close(*fd));
+
+ fd = kapi::filesystem::open(new_file_path);
+ REQUIRE(fd);
+
+ auto read_buffer = std::vector<std::byte>(4);
+ auto bytes_read = kapi::filesystem::read(*fd, read_buffer.data(), read_buffer.size());
+
+ std::string_view buffer_as_str{reinterpret_cast<char *>(read_buffer.data()), static_cast<size_t>(*bytes_read)};
+ REQUIRE(buffer_as_str == "Test");
+
+ REQUIRE(kapi::filesystem::close(*fd));
+ }
+
+ THEN("a new directory can be created and a file within can be created, opened, and closed again")
+ {
+ auto new_directory_path = "/information/new_directory";
+ auto new_file_path = "/information/new_directory/new_information.txt";
+
+ REQUIRE(!kapi::filesystem::open(new_file_path));
+
+ REQUIRE(kapi::filesystem::mkdir(new_directory_path));
+ REQUIRE(kapi::filesystem::create(new_file_path));
+
+ auto fd = kapi::filesystem::open(new_file_path);
+ REQUIRE(fd);
+ REQUIRE(kapi::filesystem::close(*fd));
+ }
+
+ THEN("a file can be created in a mounted filesystem, opened, and closed again")
+ {
+ CHECK(kapi::filesystem::mount("/dev/ram16", "/information"));
+
+ auto new_file_path = "/information/monkey_house/monkey_4.txt";
+ REQUIRE(!kapi::filesystem::open(new_file_path));
+ REQUIRE(kapi::filesystem::create(new_file_path));
+
+ auto fd = kapi::filesystem::open(new_file_path);
+ REQUIRE(fd);
+ REQUIRE(kapi::filesystem::close(*fd));
+
+ REQUIRE(kapi::filesystem::umount("/information"));
+ }
}
}
diff --git a/kernel/kernel/filesystem/devfs/filesystem.cpp b/kernel/kernel/filesystem/devfs/filesystem.cpp
index 7ffcb3c4..66799988 100644
--- a/kernel/kernel/filesystem/devfs/filesystem.cpp
+++ b/kernel/kernel/filesystem/devfs/filesystem.cpp
@@ -1,12 +1,13 @@
#include <kernel/filesystem/devfs/filesystem.hpp>
-#include "kernel/filesystem/filesystem.hpp"
#include <kernel/devices/storage/management.hpp>
#include <kernel/filesystem/devfs/inode.hpp>
#include <kernel/filesystem/device_inode.hpp>
#include <kernel/filesystem/error.hpp>
+#include <kernel/filesystem/filesystem.hpp>
#include <kernel/filesystem/inode.hpp>
#include <kernel/filesystem/type.hpp>
+#include <kernel/filesystem/vfs_types.hpp>
#include <kapi/devices/device.hpp>
@@ -82,6 +83,13 @@ namespace kernel::filesystem::devfs
return kstd::failure(vfs_errc::no_such_file_or_directory);
}
+ auto filesystem::create_inode(kstd::shared_ptr<kernel::filesystem::inode> const &, std::string_view,
+ kernel::filesystem::vfs_types::inode_type)
+ -> kstd::result<kstd::shared_ptr<kernel::filesystem::inode>>
+ {
+ return kstd::failure(vfs_errc::read_only_file_system);
+ }
+
auto filesystem::build_device_inode_table() -> void
{
m_inodes.clear();
diff --git a/kernel/kernel/filesystem/devfs/filesystem.hpp b/kernel/kernel/filesystem/devfs/filesystem.hpp
index dadeaf7d..be3c119c 100644
--- a/kernel/kernel/filesystem/devfs/filesystem.hpp
+++ b/kernel/kernel/filesystem/devfs/filesystem.hpp
@@ -4,6 +4,7 @@
#include <kernel/filesystem/device_inode.hpp>
#include <kernel/filesystem/filesystem.hpp>
#include <kernel/filesystem/inode.hpp>
+#include <kernel/filesystem/vfs_types.hpp>
#include <kstd/memory.hpp>
#include <kstd/result.hpp>
@@ -38,6 +39,17 @@ namespace kernel::filesystem::devfs
[[nodiscard]] auto lookup(kstd::shared_ptr<kernel::filesystem::inode> const & parent, std::string_view name) const
-> kstd::result<kstd::shared_ptr<kernel::filesystem::inode>> override;
+ /**
+ @brief Creates a new inode with @p name within a @p parent directory.
+ @param parent The parent directory inode.
+ @param name The name of the inode to create.
+ @param inode_type The type of inode to be created.
+ @return Always returns nullptr, as creating new inodes is not supported in devfs.
+ */
+ [[nodiscard]] auto create_inode(kstd::shared_ptr<kernel::filesystem::inode> const & parent, std::string_view name,
+ kernel::filesystem::vfs_types::inode_type inode_type)
+ -> kstd::result<kstd::shared_ptr<kernel::filesystem::inode>> override;
+
private:
auto build_device_inode_table() -> void;
diff --git a/kernel/kernel/filesystem/devfs/filesystem.tests.cpp b/kernel/kernel/filesystem/devfs/filesystem.tests.cpp
index d5028231..c1aad37c 100644
--- a/kernel/kernel/filesystem/devfs/filesystem.tests.cpp
+++ b/kernel/kernel/filesystem/devfs/filesystem.tests.cpp
@@ -1,6 +1,7 @@
#include <kernel/filesystem/devfs/filesystem.hpp>
#include <kernel/filesystem/filesystem.hpp>
+#include <kernel/filesystem/vfs_types.hpp>
#include <kernel/test_support/filesystem/storage_boot_module_fixture.hpp>
#include <catch2/catch_test_macros.hpp>
@@ -52,6 +53,12 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture,
auto result = fs.lookup(*non_directory_inode, "anything");
REQUIRE(!result);
}
+
+ THEN("create_inode always returns an error")
+ {
+ auto result = fs.create_inode(fs.root_inode(), "new_device", kernel::filesystem::vfs_types::inode_type::device);
+ REQUIRE(!result);
+ }
}
GIVEN("a boot module registry with three modules")
diff --git a/kernel/kernel/filesystem/error.hpp b/kernel/kernel/filesystem/error.hpp
index 56f56ce4..584bf261 100644
--- a/kernel/kernel/filesystem/error.hpp
+++ b/kernel/kernel/filesystem/error.hpp
@@ -24,6 +24,8 @@ namespace kernel::filesystem
invalid_inode,
not_a_directory,
is_a_directory,
+ read_only_file_system,
+ file_exists,
};
namespace detail
@@ -65,6 +67,10 @@ namespace kernel::filesystem
return "not a directory";
case vfs_errc::is_a_directory:
return "is a directory";
+ case vfs_errc::read_only_file_system:
+ return "readonly filesystem";
+ case vfs_errc::file_exists:
+ return "file already exists";
default:
return "unknown VFS error";
}
@@ -95,6 +101,10 @@ namespace kernel::filesystem
return make_error_condition(kstd::errc::not_a_directory);
case vfs_errc::is_a_directory:
return make_error_condition(kstd::errc::is_a_directory);
+ case vfs_errc::read_only_file_system:
+ return make_error_condition(kstd::errc::read_only_file_system);
+ case vfs_errc::file_exists:
+ return make_error_condition(kstd::errc::file_exists);
default:
return kstd::error_condition{value, *this};
}
diff --git a/kernel/kernel/filesystem/ext2/error.hpp b/kernel/kernel/filesystem/ext2/error.hpp
index 79f40517..7de3b24b 100644
--- a/kernel/kernel/filesystem/ext2/error.hpp
+++ b/kernel/kernel/filesystem/ext2/error.hpp
@@ -20,6 +20,8 @@ namespace kernel::filesystem::ext2
invalid_block_number,
failed_to_read_superblock,
failed_to_read_block_group_descriptors,
+ not_enough_free_blocks,
+ not_enough_inodes
};
namespace detail
@@ -49,6 +51,10 @@ namespace kernel::filesystem::ext2
return "failed to read superblock";
case ext2_errc::failed_to_read_block_group_descriptors:
return "failed to read block group descriptors";
+ case ext2_errc::not_enough_free_blocks:
+ return "not enough free blocks";
+ case ext2_errc::not_enough_inodes:
+ return "not enough inodes";
default:
return "unknown ext2 error";
};
@@ -80,6 +86,13 @@ namespace kernel::filesystem::ext2
return condition.value() == static_cast<int>(kstd::errc::io_error);
}
break;
+ case ext2_errc::not_enough_free_blocks:
+ case ext2_errc::not_enough_inodes:
+ if (condition.category() == kstd::generic_category())
+ {
+ return condition.value() == static_cast<int>(kstd::errc::no_space_on_device);
+ }
+ break;
}
return kstd::error_category::equivalent(code, condition);
}
diff --git a/kernel/kernel/filesystem/ext2/filesystem.cpp b/kernel/kernel/filesystem/ext2/filesystem.cpp
index 34ee1f75..091b1399 100644
--- a/kernel/kernel/filesystem/ext2/filesystem.cpp
+++ b/kernel/kernel/filesystem/ext2/filesystem.cpp
@@ -9,7 +9,11 @@
#include <kernel/filesystem/filesystem.hpp>
#include <kernel/filesystem/inode.hpp>
#include <kernel/filesystem/type.hpp>
+#include <kernel/filesystem/vfs_types.hpp>
+#include <kapi/system.hpp>
+
+#include <kstd/cstring.hpp>
#include <kstd/memory.hpp>
#include <kstd/result.hpp>
#include <kstd/system_error.hpp>
@@ -24,6 +28,22 @@
namespace kernel::filesystem::ext2
{
+ namespace
+ {
+ [[nodiscard]] auto bitmap_is_set(kstd::vector<uint8_t> const & bitmap, size_t index) -> bool
+ {
+ auto const byte_index = index / 8;
+ auto const bit_index = index % 8;
+ return (bitmap.at(byte_index) & static_cast<uint8_t>(1u << bit_index)) != 0;
+ }
+
+ auto bitmap_set(kstd::vector<uint8_t> & bitmap, size_t index) -> void
+ {
+ auto const byte_index = index / 8;
+ auto const bit_index = index % 8;
+ bitmap.at(byte_index) |= static_cast<uint8_t>(1u << bit_index);
+ }
+ } // namespace
struct type final : kernel::filesystem::type
{
@@ -46,6 +66,15 @@ namespace kernel::filesystem::ext2
[[gnu::used]]
constexpr auto registration = type_registration<type>{};
+ auto filesystem::indirect_levels() const -> std::array<indirect_level, 3>
+ {
+ return {
+ {{constants::singly_indirect_block_index, block_numbers_per_singly_indirect_block()},
+ {constants::doubly_indirect_block_index, block_numbers_per_doubly_indirect_block()},
+ {constants::triply_indirect_block_index, block_numbers_per_triply_indirect_block()}}
+ };
+ }
+
auto filesystem::mount(kstd::shared_ptr<kernel::filesystem::inode> const & backing_inode) -> kstd::result<void>
{
std::ignore = kernel::filesystem::filesystem::mount(backing_inode);
@@ -107,14 +136,12 @@ namespace kernel::filesystem::ext2
for (uint32_t i = 0; i < inode_block_count(inode_data); ++i)
{
auto const global_block_number = map_inode_block_index_to_global_block_number(i, inode_data);
-
if (!global_block_number)
{
return kstd::failure(global_block_number.error());
}
- auto const block_offset = global_block_number.value() * block_size();
- if (auto read_result = m_backing_inode->read(buffer.data(), block_offset, block_size()); !read_result)
+ if (auto read_result = read_block(*global_block_number, buffer.data()); !read_result)
{
return kstd::failure(read_result.error());
}
@@ -138,8 +165,222 @@ namespace kernel::filesystem::ext2
return kstd::failure(vfs_errc::no_such_file_or_directory);
}
+ auto filesystem::create_inode(kstd::shared_ptr<kernel::filesystem::inode> const & parent, std::string_view name,
+ kernel::filesystem::vfs_types::inode_type inode_type)
+ -> kstd::result<kstd::shared_ptr<kernel::filesystem::inode>>
+ {
+ if (!parent)
+ {
+ return kstd::failure(vfs_errc::invalid_inode);
+ }
+
+ if (!parent->is_directory())
+ {
+ return kstd::failure(vfs_errc::not_a_directory);
+ }
+
+ auto * ext2_parent = static_cast<inode *>(parent.get());
+ if (!ext2_parent)
+ {
+ return nullptr;
+ }
+
+ auto maybe_inode_number = allocate_inode();
+ if (!maybe_inode_number)
+ {
+ return kstd::failure(maybe_inode_number.error());
+ }
+
+ auto inode_number = maybe_inode_number.value();
+
+ inode_data new_inode_data{};
+
+ uint16_t const full_access_mode = 0x01FF; // TODO use correct access rights
+ new_inode_data.mode = map_vfs_inode_type_into_inode_mode(inode_type, full_access_mode);
+ new_inode_data.size = 0;
+ new_inode_data.dir_acl = 0;
+ new_inode_data.links_count = 1;
+ new_inode_data.blocks = 0;
+ new_inode_data.block.fill(0);
+
+ // TODO preallocate blocks depending on type and m_superblock.prealloc_dir_blocks or m_superblock.prealloc_blocks;
+
+ if (auto result = add_directory_entry(ext2_parent, name, inode_number, new_inode_data.mode); !result)
+ {
+ return kstd::failure(result.error());
+ }
+
+ auto new_inode = kstd::make_shared<inode>(this, inode_number, new_inode_data);
+
+ if (new_inode->is_directory())
+ {
+ // TODO increment used_dirs_count in block group descriptor
+ if (auto result = init_directory(new_inode, ext2_parent); !result)
+ {
+ return kstd::failure(result.error());
+ }
+ }
+
+ if (auto write_result = write_inode(inode_number, new_inode->data()); !write_result)
+ {
+ return kstd::failure(write_result.error());
+ }
+
+ return new_inode;
+ }
+
+ auto filesystem::add_directory_entry(inode * ext2_parent, std::string_view name, uint32_t child_inode_number,
+ [[maybe_unused]] uint16_t mode) -> kstd::result<void>
+ {
+ auto const last_block_index = inode_block_count(ext2_parent->data()) - 1;
+ auto const global_block = map_inode_block_index_to_global_block_number(last_block_index, ext2_parent->data());
+
+ if (!global_block)
+ {
+ return kstd::failure(global_block.error());
+ }
+
+ kstd::vector<uint8_t> buffer(block_size());
+ if (auto read_result = read_block(*global_block, buffer.data()); !read_result)
+ {
+ return kstd::failure(read_result.error());
+ }
+
+ // TODO handle "gaps" in directory entries (e.g., after deletions) in the current implementation, we only
+ // add new entries at the end of the last block, but we could also reuse space from deleted entries
+ auto offset = 0uz;
+ while (true)
+ {
+ auto const * entry = reinterpret_cast<linked_directory_entry const *>(buffer.data() + offset);
+ if (offset + entry->rec_len >= block_size())
+ {
+ break;
+ }
+ offset += entry->rec_len;
+ }
+
+ auto const file_type = map_inode_mode_to_file_type(mode);
+ auto const name_len = static_cast<uint8_t>(name.size());
+ auto const needed_rec_len = static_cast<uint16_t>((8u + name_len + 3u) & ~3u);
+
+ auto * last_entry = reinterpret_cast<linked_directory_entry *>(buffer.data() + offset);
+ auto const last_entry_actual_len = static_cast<uint16_t>((8u + last_entry->name_len + 3u) & ~3u);
+
+ if (last_entry->rec_len - last_entry_actual_len >= needed_rec_len)
+ {
+ last_entry->rec_len = last_entry_actual_len;
+ write_directory_entry_to_buffer(buffer.data(), offset + last_entry_actual_len, child_inode_number, name,
+ file_type);
+ return global_block.and_then([&](auto block_number) { return write_block(block_number, buffer.data()); })
+ .transform([](auto) {});
+ }
+ else
+ {
+ if (!ext2_parent->append_blocks(1))
+ {
+ return kstd::failure(ext2_errc::not_enough_free_blocks);
+ }
+
+ auto const new_block_index = inode_block_count(ext2_parent->data()) - 1;
+ auto const new_global_block = map_inode_block_index_to_global_block_number(new_block_index, ext2_parent->data());
+ if (!new_global_block)
+ {
+ return kstd::failure(new_global_block.error());
+ }
+
+ kstd::libc::memset(buffer.data(), 0, block_size());
+ write_directory_entry_to_buffer(buffer.data(), 0, child_inode_number, name, file_type);
+ if (auto update_result =
+ new_global_block.and_then([&](auto block_number) { return write_block(block_number, buffer.data()); });
+ !update_result)
+ {
+ return kstd::failure(update_result.error());
+ }
+ }
+
+ ext2_parent->data_mutable().size = inode_block_count(ext2_parent->data()) * block_size();
+ return write_inode(ext2_parent->number(), ext2_parent->data());
+ }
+
+ auto filesystem::init_directory(kstd::shared_ptr<kernel::filesystem::ext2::inode> const & new_inode,
+ kernel::filesystem::ext2::inode * parent_inode) -> kstd::result<void>
+ {
+ if (!new_inode->append_blocks(1))
+ {
+ return kstd::failure(ext2_errc::not_enough_free_blocks);
+ }
+
+ auto const global_block = map_inode_block_index_to_global_block_number(0, new_inode->data());
+ if (!global_block)
+ {
+ return kstd::failure(global_block.error());
+ }
+
+ kstd::vector<uint8_t> buffer(block_size(), 0);
+ write_directory_entry_to_buffer(buffer.data(), 0, new_inode->number(), ".", 2);
+ auto const dot_actual_len = static_cast<uint16_t>((8u + 1 + 3u) & ~3u);
+ auto * entry = reinterpret_cast<linked_directory_entry *>(buffer.data());
+ entry->rec_len = dot_actual_len;
+
+ write_directory_entry_to_buffer(buffer.data(), dot_actual_len, parent_inode->number(), "..", 2);
+
+ // '..' inside the new dir counts as an extra hard link to the parent
+ auto & parent_inode_data = parent_inode->data_mutable();
+ parent_inode_data.links_count++;
+
+ return write_inode(parent_inode->number(), parent_inode_data)
+ .and_then([&]() { return global_block; })
+ .and_then([&](auto block_number) { return write_block(block_number, buffer.data()); })
+ .transform([](auto) {});
+ }
+
+ auto filesystem::write_directory_entry_to_buffer(uint8_t * block_buffer, size_t offset, uint32_t child_inode_number,
+ std::string_view name, uint8_t file_type) -> void
+ {
+ auto const name_len = static_cast<uint8_t>(name.size());
+ auto * entry = reinterpret_cast<linked_directory_entry *>(block_buffer + offset);
+ entry->inode = child_inode_number;
+ entry->rec_len = static_cast<uint16_t>(block_size() - offset);
+ entry->name_len = name_len;
+ entry->file_type = file_type;
+ kstd::libc::memcpy(entry->name.data(), name.data(), name_len);
+ }
+
auto filesystem::read_inode(uint32_t inode_number) const -> kstd::result<kstd::shared_ptr<inode>>
{
+ if (auto inode_offset = calculate_inode_offset(inode_number))
+ {
+ auto new_inode_data = inode_data{};
+
+ if (auto read_result = m_backing_inode->read(&new_inode_data, *inode_offset, sizeof(inode_data)); !read_result)
+ {
+ return kstd::failure(read_result.error());
+ }
+ else
+ {
+ return kstd::make_shared<inode>(const_cast<filesystem *>(this), inode_number, new_inode_data);
+ }
+ }
+ else
+ {
+ return kstd::failure(inode_offset.error());
+ }
+ }
+
+ auto filesystem::write_inode(uint32_t inode_number, inode_data const & data) -> kstd::result<void>
+ {
+ if (auto inode_offset = calculate_inode_offset(inode_number))
+ {
+ return m_backing_inode->write(&data, *inode_offset, sizeof(inode_data)).transform([](auto) {});
+ }
+ else
+ {
+ return kstd::failure(inode_offset.error());
+ }
+ }
+
+ auto filesystem::calculate_inode_offset(uint32_t inode_number) const -> kstd::result<size_t>
+ {
auto const inodes_per_group = m_superblock.inodes_per_group;
auto const block_group_index = (inode_number - 1) / inodes_per_group;
auto const inode_index_within_group = (inode_number - 1) % inodes_per_group;
@@ -154,22 +395,135 @@ 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 * inode_size();
- auto new_inode_data = inode_data{};
- if (auto read_result = m_backing_inode->read(&new_inode_data, inode_offset, sizeof(inode_data)); !read_result)
+ return inode_offset;
+ }
+
+ auto filesystem::allocate_blocks(size_t count) -> kstd::result<kstd::vector<uint32_t>>
+ {
+ if (m_block_group_descriptors.empty() || count > m_superblock.free_blocks_count)
{
- return kstd::failure(read_result.error());
+ return kstd::failure(ext2_errc::not_enough_free_blocks);
}
- return kstd::make_shared<inode>(this, new_inode_data);
+ kstd::vector<uint32_t> allocated_blocks{};
+
+ for (auto block_group_descriptor_index = 0uz; block_group_descriptor_index < m_block_group_descriptors.size();
+ ++block_group_descriptor_index)
+ {
+ auto & block_group_descriptor = m_block_group_descriptors.at(block_group_descriptor_index);
+ if (block_group_descriptor.free_blocks_count == 0)
+ {
+ continue;
+ }
+
+ kstd::vector<uint8_t> block_bitmap(block_size(), 0);
+ if (auto read_result = read_block(block_group_descriptor.block_bitmap, block_bitmap.data()); !read_result)
+ {
+ return kstd::failure(read_result.error());
+ }
+
+ bool is_bitmap_dirty = false;
+ for (auto i = 0uz; i < static_cast<size_t>(m_superblock.blocks_per_group) && allocated_blocks.size() < count; ++i)
+ {
+ if (!bitmap_is_set(block_bitmap, i))
+ {
+ is_bitmap_dirty = true;
+
+ bitmap_set(block_bitmap, i);
+ block_group_descriptor.free_blocks_count--;
+ m_superblock.free_blocks_count--;
+
+ allocated_blocks.push_back(i + block_group_descriptor_index * m_superblock.blocks_per_group);
+ }
+ }
+
+ if (is_bitmap_dirty)
+ {
+ if (auto write_result = write_block(block_group_descriptor.block_bitmap, block_bitmap.data()); !write_result)
+ {
+ return kstd::failure(write_result.error());
+ }
+
+ // TODO update all block group descriptors
+ if (auto write_result = m_backing_inode->write(
+ &block_group_descriptor,
+ block_group_descriptor_table_offset() + block_group_descriptor_index * sizeof(block_group_descriptor),
+ sizeof(block_group_descriptor));
+ !write_result)
+ {
+ return kstd::failure(write_result.error());
+ }
+ }
+ }
+
+ // TODO update all superblocks
+ if (auto write_result = m_backing_inode->write(&m_superblock, constants::superblock_offset, sizeof(m_superblock));
+ !write_result)
+ {
+ return kstd::failure(write_result.error());
+ }
+
+ if (allocated_blocks.size() != count)
+ {
+ return kstd::failure(ext2_errc::not_enough_free_blocks);
+ }
+
+ return allocated_blocks;
}
- auto filesystem::indirect_levels() const -> std::array<indirect_level, 3>
+ auto filesystem::allocate_inode() -> kstd::result<uint32_t>
{
- return {
- {{constants::singly_indirect_block_index, block_numbers_per_singly_indirect_block()},
- {constants::doubly_indirect_block_index, block_numbers_per_doubly_indirect_block()},
- {constants::triply_indirect_block_index, block_numbers_per_triply_indirect_block()}}
- };
+ if (m_block_group_descriptors.empty() || m_superblock.free_inodes_count == 0)
+ {
+ return kstd::failure(ext2_errc::not_enough_inodes);
+ }
+
+ for (auto block_group_descriptor_index = 0uz; block_group_descriptor_index < m_block_group_descriptors.size();
+ ++block_group_descriptor_index)
+ {
+ auto & block_group_descriptor = m_block_group_descriptors.at(block_group_descriptor_index);
+ if (block_group_descriptor.free_inodes_count == 0)
+ {
+ continue;
+ }
+
+ kstd::vector<uint8_t> inode_bitmap(block_size(), 0);
+ if (auto read_result = read_block(block_group_descriptor.inode_bitmap, inode_bitmap.data()); !read_result)
+ {
+ return kstd::failure(read_result.error());
+ }
+
+ for (auto i = 0uz; i < static_cast<size_t>(m_superblock.inodes_per_group); ++i)
+ {
+ if (!bitmap_is_set(inode_bitmap, i))
+ {
+ bitmap_set(inode_bitmap, i);
+ block_group_descriptor.free_inodes_count--;
+ m_superblock.free_inodes_count--;
+
+ if (auto write_result = write_block(block_group_descriptor.inode_bitmap, inode_bitmap.data()); !write_result)
+ {
+ return kstd::failure(write_result.error());
+ }
+
+ if (auto write_result = write_block_group_descriptor(block_group_descriptor, block_group_descriptor_index);
+ !write_result)
+ {
+ return kstd::failure(write_result.error());
+ }
+
+ // TODO update all superblocks
+ if (auto write_result = write_superblock(); !write_result)
+ {
+ return kstd::failure(write_result.error());
+ }
+
+ return block_group_descriptor_index * m_superblock.inodes_per_group + i + 1;
+ }
+ }
+ }
+
+ return kstd::failure(ext2_errc::not_enough_inodes);
}
auto filesystem::map_inode_block_index_to_global_block_number(size_t inode_block_index, inode_data data) const
@@ -242,6 +596,146 @@ namespace kernel::filesystem::ext2
return block_number_buffer;
}
+ auto filesystem::write_global_block_number_to_inode_block_index(size_t inode_block_index, inode_data & data,
+ uint32_t global_block_number) -> kstd::result<void>
+ {
+ if (inode_block_index < constants::direct_block_count)
+ {
+ data.block[inode_block_index] = global_block_number;
+ return kstd::success();
+ }
+ inode_block_index -= constants::direct_block_count;
+
+ for (auto const & level : indirect_levels())
+ {
+ if (inode_block_index >= level.capacity)
+ {
+ inode_block_index -= level.capacity;
+ continue;
+ }
+
+ auto block_number = data.block[level.slot_index];
+ auto parent_byte_offset = 0uz;
+ bool is_root = true;
+
+ for (auto stride = level.capacity / block_numbers_per_block();; stride /= block_numbers_per_block())
+ {
+ auto const idx = inode_block_index / stride;
+ inode_block_index %= stride;
+
+ if (idx == 0 && inode_block_index == 0)
+ {
+ auto allocated = allocate_blocks(1);
+ if (!allocated)
+ {
+ return kstd::failure(allocated.error());
+ }
+
+ block_number = allocated->front();
+
+ if (is_root)
+ {
+ data.block[level.slot_index] = block_number;
+ }
+ else
+ {
+ if (auto write_result = m_backing_inode->write(&block_number, parent_byte_offset, sizeof(uint32_t));
+ !write_result)
+ {
+ return kstd::failure(write_result.error());
+ }
+ }
+ }
+
+ auto const byte_offset = block_number * block_size() + idx * sizeof(uint32_t);
+
+ if (stride == 1)
+ {
+ if (auto write_result = m_backing_inode->write(&global_block_number, byte_offset, sizeof(uint32_t));
+ !write_result)
+ {
+ return kstd::failure(write_result.error());
+ }
+ return kstd::success();
+ }
+
+ parent_byte_offset = byte_offset;
+ is_root = false;
+ if (auto read_result = read_block_number_at_index(block_number, idx); !read_result)
+ {
+ return kstd::failure(read_result.error());
+ }
+ else
+ {
+ block_number = read_result.value();
+ }
+ }
+ }
+
+ return kstd::success();
+ }
+
+ auto filesystem::read_block(uint32_t block_number, void * buffer) const -> kstd::result<size_t>
+ {
+ auto const block_offset = static_cast<size_t>(block_number) * block_size();
+ return m_backing_inode->read(buffer, block_offset, block_size());
+ }
+
+ auto filesystem::write_block(uint32_t block_number, void const * buffer) -> kstd::result<size_t>
+ {
+ auto const block_offset = static_cast<size_t>(block_number) * block_size();
+ return m_backing_inode->write(buffer, block_offset, block_size());
+ }
+
+ auto filesystem::write_block_group_descriptor(block_group_descriptor const & block_group_descriptor,
+ size_t block_group_descriptor_index) const -> kstd::result<void>
+ {
+ // TODO update all block group descriptors
+ return m_backing_inode
+ ->write(&block_group_descriptor,
+ block_group_descriptor_table_offset() + block_group_descriptor_index * sizeof(block_group_descriptor),
+ sizeof(block_group_descriptor))
+ .transform([](auto) {});
+ }
+
+ auto filesystem::write_superblock() const -> kstd::result<void>
+ {
+ // TODO update all superblocks
+ return m_backing_inode->write(&m_superblock, constants::superblock_offset, sizeof(m_superblock))
+ .transform([](auto) {});
+ }
+
+ auto filesystem::map_inode_mode_to_file_type(uint16_t mode) -> uint8_t
+ {
+ switch (mode & constants::mode_mask)
+ {
+ case constants::mode_regular:
+ return 1;
+ case constants::mode_directory:
+ return 2;
+ case constants::mode_symbolic_link:
+ return 7;
+ default:
+ return 0;
+ }
+ }
+
+ auto filesystem::map_vfs_inode_type_into_inode_mode(kernel::filesystem::vfs_types::inode_type vfs_type, uint16_t mode)
+ -> uint16_t
+ {
+ switch (vfs_type)
+ {
+ case kernel::filesystem::vfs_types::inode_type::directory:
+ return mode | constants::mode_directory;
+ case kernel::filesystem::vfs_types::inode_type::regular:
+ return mode | constants::mode_regular;
+ case kernel::filesystem::vfs_types::inode_type::symbolic_link:
+ // return mode | constants::mode_symbolic_link; // TODO implement
+ default:
+ kapi::system::panic("[EXT2] Not implemented.");
+ }
+ }
+
auto filesystem::block_numbers_per_block() const -> size_t
{
return block_size() / sizeof(uint32_t);
@@ -282,8 +776,14 @@ namespace kernel::filesystem::ext2
return data.blocks / (2 << m_superblock.log_block_size);
}
+ auto filesystem::update_inode_block_count(inode_data & data, uint32_t delta) -> void
+ {
+ data.blocks += delta * (2 << m_superblock.log_block_size);
+ }
+
auto filesystem::block_group_descriptor_table_offset() const -> size_t
{
return block_size() == 1024 ? 2 * block_size() : block_size();
}
+
} // namespace kernel::filesystem::ext2
diff --git a/kernel/kernel/filesystem/ext2/filesystem.hpp b/kernel/kernel/filesystem/ext2/filesystem.hpp
index 5e5913bf..e5192ec0 100644
--- a/kernel/kernel/filesystem/ext2/filesystem.hpp
+++ b/kernel/kernel/filesystem/ext2/filesystem.hpp
@@ -6,6 +6,7 @@
#include <kernel/filesystem/ext2/superblock.hpp>
#include <kernel/filesystem/filesystem.hpp>
#include <kernel/filesystem/inode.hpp>
+#include <kernel/filesystem/vfs_types.hpp>
#include <kstd/memory.hpp>
#include <kstd/result.hpp>
@@ -67,6 +68,17 @@ namespace kernel::filesystem::ext2
-> kstd::result<kstd::shared_ptr<kernel::filesystem::inode>> override;
/**
+ @brief Creates a new inode with @p name within a @p parent directory.
+ @param parent The parent directory inode.
+ @param name The name of the inode to create.
+ @param inode_type The type of inode to be created.
+ @return A pointer to the created inode, or a null pointer if creation failed.
+ */
+ [[nodiscard]] auto create_inode(kstd::shared_ptr<kernel::filesystem::inode> const & parent, std::string_view name,
+ kernel::filesystem::vfs_types::inode_type inode_type)
+ -> kstd::result<kstd::shared_ptr<kernel::filesystem::inode>> override;
+
+ /**
@brief Gets the size of a block in the filesystem.
@return The size of a block in bytes.
*/
@@ -79,6 +91,56 @@ namespace kernel::filesystem::ext2
[[nodiscard]] auto revision_level() const -> uint32_t;
/**
+ @brief Reads a block from the backing device into the provided buffer.
+ @param block_number The number of the block to read.
+ @param buffer The buffer to read the block data into.
+ @return The number of bytes read.
+ */
+ auto read_block(uint32_t block_number, void * buffer) const -> kstd::result<size_t>;
+
+ /**
+ @brief Writes a block of data from the provided buffer to the backing device.
+ @param block_number The number of the block to write.
+ @param buffer The buffer containing the data to write.
+ @return The number of bytes written.
+ */
+ auto write_block(uint32_t block_number, void const * buffer) -> kstd::result<size_t>;
+
+ /**
+ @brief Allocates a specified number of blocks.
+ @param count The number of blocks to allocate.
+ @return An optional vector of the allocated block numbers, or a nullopt if allocation fails.
+ */
+ auto allocate_blocks(size_t count) -> kstd::result<kstd::vector<uint32_t>>;
+
+ /**
+ @brief Gets the size of an inode in the filesystem.
+ @return The size of an inode in bytes.
+ */
+ [[nodiscard]] auto inode_size() const -> uint16_t;
+
+ /**
+ @brief Gets the number of blocks allocated to an inode.
+ @param data The inode data.
+ @return The number of blocks allocated to the inode.
+ */
+ [[nodiscard]] auto inode_block_count(inode_data const & data) const -> uint32_t;
+
+ /**
+ @brief Updates the number of blocks allocated to an inode.
+ @param data The inode data.
+ @param delta The change in the number of blocks.
+ */
+ auto update_inode_block_count(inode_data & data, uint32_t delta) -> void;
+
+ /**
+ @brief Writes an inode to the backing device.
+ @param inode_number The number of the inode to write.
+ @param data The inode data to write.
+ */
+ auto write_inode(uint32_t inode_number, inode_data const & data) -> kstd::result<void>;
+
+ /**
@brief Maps an inode block index to a global block number.
@param inode_block_index The index of the block within the inode.
@param data The inode data.
@@ -87,6 +149,15 @@ namespace kernel::filesystem::ext2
[[nodiscard]] auto map_inode_block_index_to_global_block_number(size_t inode_block_index, inode_data data) const
-> kstd::result<std::size_t>;
+ /**
+ @brief Writes a global block number to an inode block index.
+ @param block_index The index of the block within the inode.
+ @param data The inode data.
+ @param global_block_number The global block number to write.
+ */
+ auto write_global_block_number_to_inode_block_index(size_t block_index, inode_data & data,
+ uint32_t global_block_number) -> kstd::result<void>;
+
private:
struct indirect_level
{
@@ -96,11 +167,18 @@ namespace kernel::filesystem::ext2
[[nodiscard]] auto indirect_levels() const -> std::array<indirect_level, 3>;
+ auto allocate_inode() -> kstd::result<uint32_t>;
+ auto add_directory_entry(inode * ext2_parent, std::string_view name, uint32_t child_inode_number, uint16_t mode)
+ -> kstd::result<void>;
+ auto init_directory(kstd::shared_ptr<kernel::filesystem::ext2::inode> const & new_inode,
+ kernel::filesystem::ext2::inode * parent_inode) -> kstd::result<void>;
+ auto write_directory_entry_to_buffer(uint8_t * block_buffer, size_t offset, uint32_t child_inode_number,
+ std::string_view name, uint8_t file_type) -> void;
+
+ [[nodiscard]] auto calculate_inode_offset(uint32_t inode_number) const -> kstd::result<size_t>;
[[nodiscard]] auto read_inode(uint32_t inode_number) const -> kstd::result<kstd::shared_ptr<inode>>;
[[nodiscard]] auto read_block_number_at_index(uint32_t block_number, size_t index) const -> kstd::result<uint32_t>;
- [[nodiscard]] auto inode_size() const -> uint16_t;
- [[nodiscard]] auto inode_block_count(inode_data const & data) const -> uint32_t;
[[nodiscard]] auto block_group_descriptor_table_offset() const -> size_t;
[[nodiscard]] auto block_numbers_per_block() const -> size_t;
@@ -108,6 +186,14 @@ namespace kernel::filesystem::ext2
[[nodiscard]] auto block_numbers_per_doubly_indirect_block() const -> size_t;
[[nodiscard]] auto block_numbers_per_triply_indirect_block() const -> size_t;
+ [[nodiscard]] auto write_block_group_descriptor(block_group_descriptor const & block_group_descriptor,
+ size_t block_group_descriptor_index) const -> kstd::result<void>;
+ [[nodiscard]] auto write_superblock() const -> kstd::result<void>;
+
+ auto map_inode_mode_to_file_type(uint16_t mode) -> uint8_t;
+ auto map_vfs_inode_type_into_inode_mode(kernel::filesystem::vfs_types::inode_type vfs_type, uint16_t mode)
+ -> uint16_t;
+
superblock m_superblock{};
kstd::vector<block_group_descriptor> m_block_group_descriptors;
};
diff --git a/kernel/kernel/filesystem/ext2/filesystem.tests.cpp b/kernel/kernel/filesystem/ext2/filesystem.tests.cpp
index 1702526e..8aecc847 100644
--- a/kernel/kernel/filesystem/ext2/filesystem.tests.cpp
+++ b/kernel/kernel/filesystem/ext2/filesystem.tests.cpp
@@ -2,9 +2,13 @@
#include <kernel/devices/storage/management.hpp>
#include <kernel/filesystem/device_inode.hpp>
+#include <kernel/filesystem/ext2/block_group_descriptor.hpp>
#include <kernel/filesystem/ext2/error.hpp>
#include <kernel/filesystem/ext2/inode.hpp>
+#include <kernel/filesystem/ext2/superblock.hpp>
#include <kernel/filesystem/filesystem.hpp>
+#include <kernel/filesystem/vfs_types.hpp>
+#include <kernel/test_support/cpu.hpp>
#include <kernel/test_support/devices/block_device.hpp>
#include <kernel/test_support/filesystem/ext2.hpp>
#include <kernel/test_support/filesystem/storage_boot_module_fixture.hpp>
@@ -15,8 +19,12 @@
#include <catch2/catch_test_macros.hpp>
#include <array>
+#include <cstddef>
#include <cstdint>
+#include <cstring>
#include <filesystem>
+#include <string_view>
+#include <vector>
SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture,
"Ext2 filesystem mount and lookup with real image", "[filesystem][ext2][filesystem][img]")
@@ -29,7 +37,7 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture,
REQUIRE_NOTHROW(setup_modules_from_img({"test_img_module"}, {image_path}));
auto boot_device = kernel::devices::storage::management::get().determine_boot_device();
- REQUIRE(boot_device != nullptr);
+ REQUIRE(boot_device);
auto dev_inode = kstd::make_shared<kernel::filesystem::device_inode>(boot_device);
@@ -38,7 +46,7 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture,
THEN("the root inode is available and is a directory")
{
- REQUIRE(fs.root_inode() != nullptr);
+ REQUIRE(fs.root_inode());
REQUIRE(fs.root_inode()->is_directory());
}
@@ -68,13 +76,101 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture,
}
}
+SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture,
+ "Ext2 filesystem create new inodes with real image", "[filesystem][ext2][filesystem][img]")
+{
+ auto const image_path = std::filesystem::path{KERNEL_TEST_ASSETS_DIR} / "ext2_1KB_fs.img";
+
+ GIVEN("a mounted ext2 filesystem from a real image")
+ {
+ REQUIRE(std::filesystem::exists(image_path));
+ REQUIRE_NOTHROW(setup_modules_from_img({"test_img_module"}, {image_path}));
+
+ auto boot_device = kernel::devices::storage::management::get().determine_boot_device();
+ REQUIRE(boot_device);
+
+ auto dev_inode = kstd::make_shared<kernel::filesystem::device_inode>(boot_device);
+
+ auto fs = kernel::filesystem::ext2::filesystem{};
+ REQUIRE(fs.mount(dev_inode));
+
+ THEN("a file can be created")
+ {
+ auto lookup_result = fs.lookup(fs.root_inode(), "blub");
+ REQUIRE(!lookup_result);
+
+ auto new_inode = fs.create_inode(fs.root_inode(), "blub", kernel::filesystem::vfs_types::inode_type::regular);
+ REQUIRE(new_inode);
+ REQUIRE(new_inode.value()->is_regular());
+
+ lookup_result = fs.lookup(fs.root_inode(), "blub");
+ REQUIRE(lookup_result);
+ }
+
+ THEN("a directory can be created")
+ {
+ auto lookup_result = fs.lookup(fs.root_inode(), "blub");
+ REQUIRE(!lookup_result);
+
+ auto new_directory =
+ fs.create_inode(fs.root_inode(), "blub", kernel::filesystem::vfs_types::inode_type::directory);
+ REQUIRE(new_directory);
+ REQUIRE(new_directory.value()->is_directory());
+
+ lookup_result = fs.lookup(fs.root_inode(), "blub");
+ REQUIRE(lookup_result);
+ }
+
+ THEN("a directory and a file within can be created")
+ {
+ auto new_directory =
+ fs.create_inode(fs.root_inode(), "blub", kernel::filesystem::vfs_types::inode_type::directory);
+ REQUIRE(new_directory);
+
+ auto new_file =
+ fs.create_inode(new_directory.value(), "blub_file", kernel::filesystem::vfs_types::inode_type::regular);
+ REQUIRE(new_file);
+
+ auto lookup_result = fs.lookup(new_directory.value(), "blub_file");
+ REQUIRE(lookup_result);
+ REQUIRE(lookup_result.value()->is_regular());
+ }
+
+ THEN("a file can be created and written to")
+ {
+ auto new_inode = fs.create_inode(fs.root_inode(), "blub", kernel::filesystem::vfs_types::inode_type::regular);
+ REQUIRE(new_inode);
+
+ auto buffer = std::vector<std::byte>{std::byte{'H'}, std::byte{'e'}, std::byte{'l'}, std::byte{'l'},
+ std::byte{'o'}, std::byte{' '}, std::byte{'W'}, std::byte{'o'},
+ std::byte{'r'}, std::byte{'l'}, std::byte{'d'}, std::byte{'!'}};
+ auto bytes_written = new_inode.value()->write(buffer.data(), 0, buffer.size());
+ REQUIRE(bytes_written == buffer.size());
+
+ auto read_buffer = std::vector<std::byte>(5, std::byte{0xAA});
+ auto bytes_read = new_inode.value()->read(read_buffer.data(), 0, read_buffer.size());
+ REQUIRE(bytes_read == read_buffer.size());
+
+ std::string_view result{reinterpret_cast<char *>(read_buffer.data()), static_cast<size_t>(*bytes_read)};
+ REQUIRE(result == "Hello");
+ }
+
+ THEN("a symbolic link is not implemented yet")
+ {
+ REQUIRE_THROWS_AS(
+ fs.create_inode(fs.root_inode(), "blub", kernel::filesystem::vfs_types::inode_type::symbolic_link),
+ kernel::tests::cpu::halt);
+ }
+ }
+}
+
SCENARIO("Ext2 filesystem rejects invalid magic", "[filesystem][ext2][filesystem]")
{
auto const block_size = 1024;
GIVEN("a block device that does not contain an ext2 superblock")
{
auto device = kstd::make_shared<kernel::tests::devices::block_device>(0, 0, "mock", block_size, 2 * block_size);
- REQUIRE(device != nullptr);
+ REQUIRE(device);
auto dev_inode = kstd::make_shared<kernel::filesystem::device_inode>(device);
@@ -94,7 +190,7 @@ SCENARIO("Ext2 block mapping includes direct and all indirect levels", "[filesys
GIVEN("a minimally valid ext2 layout with configured indirect block tables")
{
auto device = kstd::make_shared<kernel::tests::devices::block_device>(0, 0, "mock", block_size, 128 * block_size);
- REQUIRE(device != nullptr);
+ REQUIRE(device);
kernel::tests::filesystem::ext2::setup_mock_ext2_layout(*device);
@@ -138,3 +234,93 @@ SCENARIO("Ext2 block mapping includes direct and all indirect levels", "[filesys
}
}
}
+
+SCENARIO("Ext2 block writing includes direct and all indirect levels", "[filesystem][ext2][filesystem]")
+{
+ auto const block_size = 1024;
+
+ GIVEN("a mounted ext2 filesystem with free blocks available for indirect table allocation")
+ {
+ auto device = kstd::make_shared<kernel::tests::devices::block_device>(0, 0, "mock", block_size, 256 * block_size);
+ REQUIRE(device);
+
+ auto superblock = kernel::filesystem::ext2::superblock{};
+ superblock.magic = kernel::filesystem::ext2::constants::magic_number;
+ superblock.log_block_size = 0;
+ superblock.blocks_count = 256;
+ superblock.free_blocks_count = 128;
+ superblock.blocks_per_group = 256;
+ superblock.inodes_per_group = 32;
+ superblock.rev_level = kernel::filesystem::ext2::constants::dynamic_revision;
+ superblock.inode_size = 128;
+
+ auto block_group_descriptor = kernel::filesystem::ext2::block_group_descriptor{};
+ block_group_descriptor.block_bitmap = 255;
+ block_group_descriptor.inode_table = 5;
+ block_group_descriptor.free_blocks_count = 128;
+
+ auto block_bitmap = std::vector<std::byte>(block_size, std::byte{0x00});
+ block_bitmap[0] = std::byte{0b1111'1111};
+ block_bitmap[1] = std::byte{0b1111'1111};
+
+ auto dev_inode = kstd::make_shared<kernel::filesystem::device_inode>(device);
+ REQUIRE(dev_inode->write(block_bitmap.data(), block_group_descriptor.block_bitmap * block_size, block_size));
+
+ kernel::tests::filesystem::ext2::setup_mock_ext2_layout(*device, superblock, block_group_descriptor);
+
+ auto fs = kernel::filesystem::ext2::filesystem{};
+ REQUIRE(fs.mount(dev_inode));
+
+ auto read_u32 = [&](size_t offset) -> uint32_t {
+ auto value = uint32_t{0};
+ std::memcpy(&value, device->data.data() + offset, sizeof(value));
+ return value;
+ };
+
+ auto inode_data = kernel::filesystem::ext2::inode_data{};
+
+ auto const numbers_per_block = static_cast<uint32_t>(block_size / sizeof(uint32_t));
+ auto const singly_start = static_cast<uint32_t>(kernel::filesystem::ext2::constants::direct_block_count);
+ auto const doubly_start = singly_start + numbers_per_block;
+ auto const triply_start = doubly_start + numbers_per_block * numbers_per_block;
+
+ THEN("writing resolves direct, singly, doubly and triply indirect indexes")
+ {
+ constexpr auto direct_global_block_number = 101;
+ constexpr auto singly_global_block_number = 202;
+ constexpr auto doubly_global_block_number = 303;
+ constexpr auto triply_global_block_number = 404;
+
+ REQUIRE(fs.write_global_block_number_to_inode_block_index(0, inode_data, direct_global_block_number));
+ REQUIRE(inode_data.block[0] == direct_global_block_number);
+
+ REQUIRE(fs.write_global_block_number_to_inode_block_index(singly_start, inode_data, singly_global_block_number));
+ REQUIRE(inode_data.block[12] == 16);
+ REQUIRE(read_u32(static_cast<size_t>(inode_data.block[12]) * block_size) == singly_global_block_number);
+
+ REQUIRE(fs.write_global_block_number_to_inode_block_index(singly_start + 1, inode_data,
+ singly_global_block_number + 1));
+ REQUIRE(read_u32(static_cast<size_t>(inode_data.block[12]) * block_size + 1 * sizeof(uint32_t)) ==
+ singly_global_block_number + 1);
+
+ REQUIRE(fs.write_global_block_number_to_inode_block_index(doubly_start, inode_data, doubly_global_block_number));
+ REQUIRE(inode_data.block[13] == 17);
+ auto const doubly_leaf_table = read_u32(static_cast<size_t>(inode_data.block[13]) * block_size);
+ REQUIRE(doubly_leaf_table == 18);
+ REQUIRE(read_u32(static_cast<size_t>(doubly_leaf_table) * block_size) == doubly_global_block_number);
+
+ REQUIRE(fs.write_global_block_number_to_inode_block_index(triply_start, inode_data, triply_global_block_number));
+ REQUIRE(inode_data.block[14] == 19);
+ auto const triply_middle_table = read_u32(static_cast<size_t>(inode_data.block[14]) * block_size);
+ REQUIRE(triply_middle_table == 20);
+ auto const triply_leaf_table = read_u32(static_cast<size_t>(triply_middle_table) * block_size);
+ REQUIRE(triply_leaf_table == 21);
+ REQUIRE(read_u32(static_cast<size_t>(triply_leaf_table) * block_size) == triply_global_block_number);
+
+ REQUIRE(fs.map_inode_block_index_to_global_block_number(0, inode_data) == direct_global_block_number);
+ REQUIRE(fs.map_inode_block_index_to_global_block_number(singly_start, inode_data) == singly_global_block_number);
+ REQUIRE(fs.map_inode_block_index_to_global_block_number(doubly_start, inode_data) == doubly_global_block_number);
+ REQUIRE(fs.map_inode_block_index_to_global_block_number(triply_start, inode_data) == triply_global_block_number);
+ }
+ }
+}
diff --git a/kernel/kernel/filesystem/ext2/inode.cpp b/kernel/kernel/filesystem/ext2/inode.cpp
index 6dadbec5..8af168b9 100644
--- a/kernel/kernel/filesystem/ext2/inode.cpp
+++ b/kernel/kernel/filesystem/ext2/inode.cpp
@@ -14,8 +14,9 @@
namespace kernel::filesystem::ext2
{
- inode::inode(filesystem const * fs, inode_data const & data)
+ inode::inode(filesystem * fs, uint32_t inode_number, inode_data const & data)
: m_filesystem(fs)
+ , m_inode_number(inode_number)
, m_data(data)
{
if (!m_filesystem)
@@ -76,10 +77,98 @@ namespace kernel::filesystem::ext2
return bytes_read;
}
- auto inode::write(void const *, size_t, size_t) -> kstd::result<size_t>
+ auto inode::write(void const * buffer, size_t offset, size_t size) -> kstd::result<size_t>
{
- kapi::system::panic("[EXT2] inode::write is not implemented yet");
- return 0;
+ if (!is_regular())
+ {
+ kapi::system::panic("[EXT2] ext2::inode write called on inode that is not of type regular");
+ }
+
+ // TODO check maximum file size of filesystem
+ // TODO handle sparse files
+
+ auto const new_inode_size = std::max(this->size(), offset + size);
+
+ auto const current_block_count = m_filesystem->inode_block_count(m_data);
+ auto const max_new_inode_size_without_new_blocks = current_block_count * m_filesystem->block_size();
+
+ if (new_inode_size > max_new_inode_size_without_new_blocks)
+ {
+ auto const additional_blocks_needed =
+ (new_inode_size - max_new_inode_size_without_new_blocks + m_filesystem->block_size() - 1) /
+ m_filesystem->block_size();
+
+ if (!append_blocks(additional_blocks_needed))
+ {
+ // TODO handle out of space
+ kapi::system::panic("[EXT2] Failed to allocate blocks for inode write");
+ }
+ }
+
+ auto block_index = offset / m_filesystem->block_size();
+ auto in_block_offset = offset % m_filesystem->block_size();
+
+ auto bytes_written = 0uz;
+
+ while (bytes_written < size)
+ {
+ auto const block_number = m_filesystem->map_inode_block_index_to_global_block_number(block_index, m_data);
+ // TODO BA-FS26 if blocknumber == 0 --> handle sparse file
+ if (!block_number)
+ {
+ kstd::failure(block_number.error());
+ }
+
+ auto const bytes_to_write = std::min(size - bytes_written, m_filesystem->block_size() - in_block_offset);
+
+ auto const block_start_offset = block_number.value() * m_filesystem->block_size();
+ auto const write_offset = block_start_offset + in_block_offset;
+
+ auto const write_result = m_filesystem->backing_inode()->write(
+ static_cast<uint8_t const *>(buffer) + bytes_written, write_offset, bytes_to_write);
+ if (!write_result)
+ {
+ return kstd::failure(write_result.error());
+ }
+
+ bytes_written += write_result.value();
+
+ block_index++;
+ in_block_offset = 0;
+ }
+
+ set_size(std::max(this->size(), offset + bytes_written));
+ if (auto write_result = m_filesystem->write_inode(m_inode_number, m_data); !write_result)
+ {
+ return kstd::failure(write_result.error());
+ }
+
+ return bytes_written;
+ }
+
+ auto inode::append_blocks(size_t count) -> bool
+ {
+ auto new_blocks = m_filesystem->allocate_blocks(count);
+ if (!new_blocks)
+ {
+ return false;
+ }
+
+ for (auto i = 0uz; i < new_blocks->size(); ++i)
+ {
+ auto const block_index = m_filesystem->inode_block_count(m_data) + i;
+ auto const global_block_number = new_blocks->at(i);
+
+ if (auto write_result =
+ m_filesystem->write_global_block_number_to_inode_block_index(block_index, m_data, global_block_number);
+ !write_result)
+ {
+ return false;
+ }
+ }
+
+ m_filesystem->update_inode_block_count(m_data, count);
+ return true;
}
[[nodiscard]] auto inode::data() const -> inode_data const &
@@ -87,6 +176,11 @@ namespace kernel::filesystem::ext2
return m_data;
}
+ [[nodiscard]] auto inode::data_mutable() -> inode_data &
+ {
+ return m_data;
+ }
+
auto inode::is_regular() const -> bool
{
return (m_data.mode & constants::mode_mask) == constants::mode_regular;
@@ -114,4 +208,18 @@ namespace kernel::filesystem::ext2
return size;
}
+ auto inode::set_size(uint64_t new_size) -> void
+ {
+ if (m_filesystem->revision_level() > constants::good_old_revision && is_regular())
+ {
+ m_data.dir_acl = static_cast<uint32_t>(new_size >> 32);
+ }
+ m_data.size = static_cast<uint32_t>(new_size);
+ }
+
+ [[nodiscard]] auto inode::number() const -> uint32_t
+ {
+ return m_inode_number;
+ }
+
} // namespace kernel::filesystem::ext2
diff --git a/kernel/kernel/filesystem/ext2/inode.hpp b/kernel/kernel/filesystem/ext2/inode.hpp
index 16cb04e0..61542ef8 100644
--- a/kernel/kernel/filesystem/ext2/inode.hpp
+++ b/kernel/kernel/filesystem/ext2/inode.hpp
@@ -44,9 +44,10 @@ namespace kernel::filesystem::ext2
/**
@brief Create an ext2 inode associated with the given filesystem.
@param fs The ext2 filesystem that this inode belongs to.
+ @param inode_number The inode number on disk.
@param data The data associated with this inode, read from the disk.
*/
- explicit inode(filesystem const * fs, inode_data const & data);
+ explicit inode(filesystem * fs, uint32_t inode_number, inode_data const & data);
/**
@brief Reads from the ext2 inode into a @p buffer, starting at the specified @p offset and for a given @p size.
@@ -59,7 +60,6 @@ namespace kernel::filesystem::ext2
/**
@brief Writes to the ext2 inode into a @p buffer, starting at the specified @p offset and for a given @p size.
- @warning This method is not implemented yet and will panic if called.
@param buffer Source buffer.
@param offset Write offset in bytes.
@param size Number of bytes requested.
@@ -68,12 +68,25 @@ namespace kernel::filesystem::ext2
auto write(void const * buffer, size_t offset, size_t size) -> kstd::result<size_t> override;
/**
+ @brief Appends the specified number of blocks to the inode.
+ @param count The number of blocks to append.
+ @return true if the blocks were successfully appended, false otherwise.
+ */
+ auto append_blocks(size_t count) -> bool;
+
+ /**
@brief Get the data associated with this inode.
- @return A reference to the inode data.
+ @return A const reference to the inode data.
*/
[[nodiscard]] auto data() const -> inode_data const &;
/**
+ @brief Get the data associated with this inode.
+ @return A reference to the inode data.
+ */
+ [[nodiscard]] auto data_mutable() -> inode_data &;
+
+ /**
@brief Check if this inode represents a directory.
@return returns true if this inode represents a directory, false otherwise.
*/
@@ -97,8 +110,21 @@ namespace kernel::filesystem::ext2
*/
[[nodiscard]] auto size() const -> uint64_t;
+ /**
+ @brief Set the size of the file represented by this inode.
+ @param new_size The new size of the file in bytes.
+ */
+ auto set_size(uint64_t new_size) -> void;
+
+ /**
+ @brief Get the inode number of this inode.
+ @return The inode number.
+ */
+ [[nodiscard]] auto number() const -> uint32_t;
+
private:
- filesystem const * m_filesystem;
+ filesystem * m_filesystem;
+ uint32_t m_inode_number{};
inode_data m_data{};
};
} // namespace kernel::filesystem::ext2
diff --git a/kernel/kernel/filesystem/ext2/inode.tests.cpp b/kernel/kernel/filesystem/ext2/inode.tests.cpp
index 496e7c4f..7e293d14 100644
--- a/kernel/kernel/filesystem/ext2/inode.tests.cpp
+++ b/kernel/kernel/filesystem/ext2/inode.tests.cpp
@@ -2,6 +2,7 @@
#include <kernel/devices/storage/management.hpp>
#include <kernel/filesystem/device_inode.hpp>
+#include <kernel/filesystem/ext2/block_group_descriptor.hpp>
#include <kernel/filesystem/ext2/filesystem.hpp>
#include <kernel/filesystem/ext2/superblock.hpp>
#include <kernel/filesystem/filesystem.hpp>
@@ -17,8 +18,10 @@
#include <algorithm>
#include <cstddef>
+#include <cstdint>
#include <filesystem>
#include <string_view>
+#include <vector>
// NOLINTBEGIN(readability-magic-numbers)
@@ -32,7 +35,7 @@ SCENARIO("Ext2 inode initialization and properties", "[filesystem][ext2][inode]"
THEN("the inode is initialized with regular file mode in data and has the kind regular")
{
data.mode = kernel::filesystem::ext2::constants::mode_regular;
- auto inode = kernel::filesystem::ext2::inode(&fs, data);
+ auto inode = kernel::filesystem::ext2::inode(&fs, 42, data);
REQUIRE(inode.is_regular());
REQUIRE_FALSE(inode.is_directory());
@@ -43,7 +46,7 @@ SCENARIO("Ext2 inode initialization and properties", "[filesystem][ext2][inode]"
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);
+ auto inode = kernel::filesystem::ext2::inode(&fs, 42, data);
REQUIRE_FALSE(inode.is_regular());
REQUIRE(inode.is_directory());
@@ -54,7 +57,7 @@ SCENARIO("Ext2 inode initialization and properties", "[filesystem][ext2][inode]"
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);
+ auto inode = kernel::filesystem::ext2::inode(&fs, 42, data);
REQUIRE_FALSE(inode.is_regular());
REQUIRE_FALSE(inode.is_directory());
@@ -65,7 +68,7 @@ SCENARIO("Ext2 inode initialization and properties", "[filesystem][ext2][inode]"
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);
+ auto inode = kernel::filesystem::ext2::inode(&fs, 42, data);
REQUIRE_FALSE(inode.is_regular());
REQUIRE_FALSE(inode.is_directory());
@@ -78,7 +81,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, 42, {}), kernel::tests::cpu::halt);
}
}
}
@@ -152,7 +155,7 @@ SCENARIO("Ext2 inode handles zeros in block mappings as file holes", "[filesyste
kernel::tests::filesystem::ext2::write_bytes(*device, 30 * block_size, "Hello", 5);
kernel::tests::filesystem::ext2::write_bytes(*device, 31 * block_size, "World!", 6);
- auto inode = kernel::filesystem::ext2::inode{&fs, data};
+ auto inode = kernel::filesystem::ext2::inode{&fs, 42, data};
auto buffer = kstd::vector<std::byte>(data.size, std::byte{0xAB});
@@ -193,7 +196,7 @@ SCENARIO("Ext2 inode handles zeros in block mappings as file holes", "[filesyste
kernel::tests::filesystem::ext2::write_bytes(*device, 50 * block_size, "Blub", 4);
kernel::tests::filesystem::ext2::write_bytes(*device, 51 * block_size, "World!", 6);
- auto inode = kernel::filesystem::ext2::inode{&fs, data};
+ auto inode = kernel::filesystem::ext2::inode{&fs, 42, data};
auto buffer = kstd::vector<std::byte>(data.size, std::byte{0xAB});
@@ -229,7 +232,7 @@ SCENARIO("Ext2 inode handles zeros in block mappings as file holes", "[filesyste
data.block[12] = 0;
data.size = block_size * 15;
- auto inode = kernel::filesystem::ext2::inode{&fs, data};
+ auto inode = kernel::filesystem::ext2::inode{&fs, 42, data};
auto buffer = kstd::vector<std::byte>(block_size * 15, std::byte{0xAB});
@@ -262,7 +265,7 @@ SCENARIO("Ext2 inode read across block boundaries", "[filesystem][ext2][inode]")
kernel::tests::filesystem::ext2::write_bytes(*device, 21 * block_size - 6, "Hello ", 6);
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 inode = kernel::filesystem::ext2::inode{&fs, 42, inode_data};
auto buffer = kstd::vector<std::byte>(12, std::byte{0x00});
@@ -277,22 +280,226 @@ SCENARIO("Ext2 inode read across block boundaries", "[filesystem][ext2][inode]")
}
}
-SCENARIO("Ext2 inode write is not implemented", "[filesystem][ext2][inode]")
+SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, "Ext2 inode writes to real image",
+ "[filesystem][ext2][inode]")
{
- GIVEN("an ext2 inode")
+ auto const image_path = std::filesystem::path{KERNEL_TEST_ASSETS_DIR} / "ext2_1KB_fs.img";
+ GIVEN("a mounted ext2 filesystem and a regular file inode")
{
+ REQUIRE(std::filesystem::exists(image_path));
+ REQUIRE_NOTHROW(setup_modules_from_img({"test_img_module"}, {image_path}));
+
+ auto boot_device = kernel::devices::storage::management::get().determine_boot_device();
+ REQUIRE(boot_device != nullptr);
+
+ auto dev_inode = kstd::make_shared<kernel::filesystem::device_inode>(boot_device);
+
auto fs = kernel::filesystem::ext2::filesystem{};
- auto inode = kernel::filesystem::ext2::inode{&fs, kernel::filesystem::ext2::inode_data{}};
+ REQUIRE(fs.mount(dev_inode));
+
+ auto information = fs.lookup(fs.root_inode(), "information");
+ REQUIRE(information);
+ auto file = fs.lookup(*information, "info_1.txt");
+ REQUIRE(file);
+ REQUIRE(file.value()->is_regular());
+
+ THEN("writing to the inode updates backing device and the inode size")
+ {
+ auto write_buffer = kstd::vector<std::byte>{std::byte{'H'}, std::byte{'e'}, std::byte{'l'}, std::byte{'l'},
+ std::byte{'o'}, std::byte{' '}, std::byte{'W'}, std::byte{'o'},
+ std::byte{'r'}, std::byte{'l'}, std::byte{'d'}, std::byte{'!'}};
+
+ auto const bytes_written = file.value()->write(write_buffer.data(), 0, write_buffer.size());
+ REQUIRE(bytes_written == 12);
+
+ auto read_buffer = kstd::vector<std::byte>(42, std::byte{0x00});
+ auto const bytes_read = file.value()->read(read_buffer.data(), 0, read_buffer.size());
+ REQUIRE(bytes_read == 12);
+
+ auto const text = std::string_view{reinterpret_cast<char const *>(read_buffer.data()), *bytes_read};
+ REQUIRE(text == "Hello World!");
+
+ auto inode_data = static_cast<kernel::filesystem::ext2::inode *>(file.value().get())->data();
+ REQUIRE(inode_data.size == bytes_read);
+ }
+
+ THEN("writing allocates new blocks and updates the inode size")
+ {
+ auto inode_data = static_cast<kernel::filesystem::ext2::inode *>(file.value().get())->data();
+
+ auto const block_size = fs.block_size();
+ auto const expected_allocated_blocks = 32 * fs.inode_block_count(inode_data);
+
+ auto write_buffer = kstd::vector<std::byte>(block_size * expected_allocated_blocks, std::byte{'A'});
+
+ auto const bytes_written = file.value()->write(write_buffer.data(), 0, write_buffer.size());
+ REQUIRE(bytes_written == block_size * expected_allocated_blocks);
+
+ auto read_buffer = kstd::vector<std::byte>(block_size * expected_allocated_blocks, std::byte{0x00});
+ auto const bytes_read = file.value()->read(read_buffer.data(), 0, read_buffer.size());
+ REQUIRE(bytes_read == block_size * expected_allocated_blocks);
+ REQUIRE(std::ranges::all_of(read_buffer, [](std::byte c) { return c == std::byte{'A'}; }));
+
+ auto new_inode_data = static_cast<kernel::filesystem::ext2::inode *>(file.value().get())->data();
+ REQUIRE(new_inode_data.size == write_buffer.size());
+ REQUIRE(fs.inode_block_count(new_inode_data) == expected_allocated_blocks);
+ }
+
+ THEN("writing to a an inode of type directory panics")
+ {
+ auto write_buffer = kstd::vector<std::byte>(12, std::byte{0xAA});
+ REQUIRE_THROWS_AS(information.value()->write(write_buffer.data(), 0, write_buffer.size()),
+ kernel::tests::cpu::halt);
+ }
+ }
+}
+
+SCENARIO("Ext2 inode write across block boundaries", "[filesystem][ext2][inode]")
+{
+ auto const block_size = 1024uz;
+
+ auto device = kstd::make_shared<kernel::tests::devices::block_device>(0, 0, "mock", block_size, 128 * block_size);
+ REQUIRE(device != nullptr);
+
+ auto superblock = kernel::filesystem::ext2::superblock{};
+ superblock.magic = kernel::filesystem::ext2::constants::magic_number;
+ superblock.log_block_size = 0;
+ superblock.blocks_count = 64;
+ superblock.blocks_per_group = 64;
+ superblock.inodes_per_group = 32;
+ superblock.inode_size = 128;
+ superblock.rev_level = 1;
+ superblock.free_blocks_count = 64 - 15;
+
+ auto block_group_descriptor = kernel::filesystem::ext2::block_group_descriptor{};
+ block_group_descriptor.inode_table = 5;
+ block_group_descriptor.free_blocks_count = 64 - 15;
+ block_group_descriptor.block_bitmap = 10;
+
+ kernel::tests::filesystem::ext2::setup_mock_ext2_layout(*device, superblock, block_group_descriptor);
+
+ auto dev_inode = kstd::make_shared<kernel::filesystem::device_inode>(device);
- THEN("writing to the inode panics")
+ auto block_bitmap = std::vector<std::byte>(block_size, std::byte{0x00});
+ block_bitmap[0] = std::byte{0b1111'1111};
+ block_bitmap[1] = std::byte{0b1111'1111};
+ block_bitmap[2] = std::byte{0b0011'0000};
+
+ REQUIRE(dev_inode->write(block_bitmap.data(), block_group_descriptor.block_bitmap * block_size, block_size));
+
+ auto fs = kernel::filesystem::ext2::filesystem{};
+ REQUIRE(fs.mount(dev_inode));
+
+ auto inode_data = kernel::filesystem::ext2::inode_data{};
+ inode_data.size = 5;
+ inode_data.blocks = 2 * block_size / 512;
+ inode_data.block[0] = 20;
+ inode_data.block[1] = 21;
+ inode_data.mode = kernel::filesystem::ext2::constants::mode_regular;
+
+ uint32_t inode_number = 3;
+ auto const inode_data_offset = block_group_descriptor.inode_table * block_size + (inode_number - 1) * fs.inode_size();
+
+ auto inode = kernel::filesystem::ext2::inode{&fs, inode_number, inode_data};
+ REQUIRE(dev_inode->write(&inode, inode_data_offset, sizeof(kernel::filesystem::ext2::inode_data)));
+
+ auto const buffer = kstd::vector<std::byte>{std::byte{'H'}, std::byte{'e'}, std::byte{'l'}, std::byte{'l'},
+ std::byte{'o'}, std::byte{' '}, std::byte{'W'}, std::byte{'o'},
+ std::byte{'r'}, std::byte{'l'}, std::byte{'d'}, std::byte{'!'}};
+
+ GIVEN("mocked ext2 filesystem")
+ {
+ auto do_read_inode = [&]() {
+ auto inode_data = kernel::filesystem::ext2::inode_data{};
+ REQUIRE(dev_inode->read(&inode_data, inode_data_offset, sizeof(kernel::filesystem::ext2::inode_data)));
+ auto read_inode = kernel::filesystem::ext2::inode{&fs, inode_number, inode_data};
+ return read_inode;
+ };
+
+ THEN("writing in the current size does not update the size and does not allocate new blocks")
+ {
+ auto const expected_bytes_written = 3;
+ auto const bytes_written = inode.write(buffer.data(), 0, expected_bytes_written);
+ REQUIRE(bytes_written == expected_bytes_written);
+
+ auto const text = std::string_view{reinterpret_cast<char const *>(device->data.data() + 20 * block_size), 3};
+ REQUIRE(text == "Hel");
+
+ auto read_block_bitmap = std::vector<std::byte>(block_size, std::byte{0x00});
+ REQUIRE(dev_inode->read(read_block_bitmap.data(), block_group_descriptor.block_bitmap * block_size, block_size));
+ REQUIRE(block_bitmap == read_block_bitmap);
+
+ auto read_inode = do_read_inode();
+ REQUIRE(inode_data.size == read_inode.size());
+ REQUIRE(inode_data.blocks == read_inode.data().blocks);
+
+ auto const current_blocks = inode_data.block;
+ auto const read_blocks = read_inode.data().block;
+ REQUIRE(current_blocks == read_blocks);
+ }
+
+ THEN("writing over the current size (within an existing block) updates the size but does not allocate additional "
+ "blocks")
+ {
+ auto const offset = block_size - 4;
+ auto const bytes_written = inode.write(buffer.data(), offset, buffer.size());
+ REQUIRE(bytes_written == buffer.size());
+
+ auto const text =
+ std::string_view{reinterpret_cast<char const *>(device->data.data() + 21 * block_size - 4), *bytes_written};
+ REQUIRE(text == "Hello World!");
+
+ auto read_block_bitmap = std::vector<std::byte>(block_size, std::byte{0x00});
+ REQUIRE(dev_inode->read(read_block_bitmap.data(), block_group_descriptor.block_bitmap * block_size, block_size));
+ REQUIRE(block_bitmap == read_block_bitmap);
+
+ auto read_inode = do_read_inode();
+ REQUIRE(offset + buffer.size() == read_inode.size());
+ REQUIRE(inode_data.blocks == read_inode.data().blocks);
+
+ auto const current_blocks = inode_data.block;
+ auto const read_blocks = read_inode.data().block;
+ REQUIRE(current_blocks == read_blocks);
+ }
+
+ THEN("writing over the current size and over allocated blocks updates size and allocates a new block")
+ {
+ auto const offset = block_size * 2 - 4;
+ auto const bytes_written = inode.write(buffer.data(), offset, buffer.size());
+ REQUIRE(bytes_written == buffer.size());
+
+ auto read_buffer = std::vector<std::byte>(buffer.size(), std::byte{0x00});
+ auto const bytes_read = inode.read(read_buffer.data(), offset, buffer.size());
+ auto const text = std::string_view{reinterpret_cast<char const *>(read_buffer.data()), *bytes_read};
+ REQUIRE(text == "Hello World!");
+
+ auto read_block_bitmap = std::vector<std::byte>(block_size, std::byte{0x00});
+ REQUIRE(dev_inode->read(read_block_bitmap.data(), block_group_descriptor.block_bitmap * block_size, block_size));
+
+ auto expected_block_bitmap = block_bitmap;
+ expected_block_bitmap[2] = std::byte{0b0011'0001};
+ REQUIRE(expected_block_bitmap == read_block_bitmap);
+
+ auto read_inode = do_read_inode();
+ REQUIRE(offset + buffer.size() == read_inode.size());
+ REQUIRE(inode_data.blocks + 2 == read_inode.data().blocks);
+
+ auto expected_blocks = inode_data.block;
+ expected_blocks[2] = 16;
+ auto const read_blocks = read_inode.data().block;
+ REQUIRE(expected_blocks == read_blocks);
+ }
+
+ THEN("writing over the current size and over the number of free blocks")
{
- auto buffer = kstd::vector<std::byte>(32, std::byte{0x00});
- REQUIRE_THROWS_AS(inode.write(buffer.data(), 0, buffer.size()), kernel::tests::cpu::halt);
+ auto const offset = 65 * block_size;
+ REQUIRE_THROWS_AS(inode.write(buffer.data(), offset, buffer.size()), kernel::tests::cpu::halt);
}
}
}
-SCENARIO("Ext2 inode get_size() correctly returns size depending on revision level", "[filesystem][ext2][inode]")
+SCENARIO("Ext2 inode get_size() and set_size() handles size correctly depending on revision level",
+ "[filesystem][ext2][inode]")
{
auto const block_size = 1024uz;
@@ -321,22 +528,29 @@ SCENARIO("Ext2 inode get_size() correctly returns size depending on revision lev
data.size = 256;
data.dir_acl = 32;
- THEN("the inode size is 256 if mode = regular")
- {
- data.mode = kernel::filesystem::ext2::constants::mode_regular;
+ auto const verify_size_and_set_size = [&](uint16_t mode, uint64_t expected_initial_size,
+ uint32_t expected_dir_acl_after_set_size) {
+ data.mode = mode;
+
+ auto inode = kernel::filesystem::ext2::inode{&fs, 42, data};
+
+ REQUIRE(inode.size() == expected_initial_size);
+
+ inode.set_size(12);
- auto inode = kernel::filesystem::ext2::inode{&fs, data};
+ REQUIRE(inode.size() == 12);
+ REQUIRE(inode.data().size == 12);
+ REQUIRE(inode.data().dir_acl == expected_dir_acl_after_set_size);
+ };
- REQUIRE(inode.size() == 256);
+ THEN("the inode size is 256 if mode = regular")
+ {
+ verify_size_and_set_size(kernel::filesystem::ext2::constants::mode_regular, 256, 32);
}
THEN("the inode size is 256 if mode = directory")
{
- data.mode = kernel::filesystem::ext2::constants::mode_directory;
-
- auto inode = kernel::filesystem::ext2::inode{&fs, data};
-
- REQUIRE(inode.size() == 256);
+ verify_size_and_set_size(kernel::filesystem::ext2::constants::mode_directory, 256, 32);
}
}
@@ -357,22 +571,29 @@ SCENARIO("Ext2 inode get_size() correctly returns size depending on revision lev
data.size = 256;
data.dir_acl = 32;
- THEN("the inode size is 256 if mode = regular")
- {
- data.mode = kernel::filesystem::ext2::constants::mode_regular;
+ auto const verify_size_and_set_size = [&](uint16_t mode, uint64_t expected_initial_size,
+ uint32_t expected_dir_acl_after_set_size) {
+ data.mode = mode;
+
+ auto inode = kernel::filesystem::ext2::inode{&fs, 42, data};
+
+ REQUIRE(inode.size() == expected_initial_size);
- auto inode = kernel::filesystem::ext2::inode{&fs, data};
+ inode.set_size(12);
- REQUIRE(inode.size() == 0x0000'0020'0000'0100);
+ REQUIRE(inode.size() == 12);
+ REQUIRE(inode.data().size == 12);
+ REQUIRE(inode.data().dir_acl == expected_dir_acl_after_set_size);
+ };
+
+ THEN("the inode size is 256 if mode = regular")
+ {
+ verify_size_and_set_size(kernel::filesystem::ext2::constants::mode_regular, 0x0000'0020'0000'0100, 0);
}
THEN("the inode size is 256 if mode = directory")
{
- data.mode = kernel::filesystem::ext2::constants::mode_directory;
-
- auto inode = kernel::filesystem::ext2::inode{&fs, data};
-
- REQUIRE(inode.size() == 256);
+ verify_size_and_set_size(kernel::filesystem::ext2::constants::mode_directory, 256, 32);
}
}
}
diff --git a/kernel/kernel/filesystem/filesystem.hpp b/kernel/kernel/filesystem/filesystem.hpp
index 1264d00d..aa6286bf 100644
--- a/kernel/kernel/filesystem/filesystem.hpp
+++ b/kernel/kernel/filesystem/filesystem.hpp
@@ -2,6 +2,7 @@
#define TEACH_OS_KERNEL_FILESYSTEM_FILESYSTEM_HPP
#include <kernel/filesystem/inode.hpp>
+#include <kernel/filesystem/vfs_types.hpp>
#include <kstd/memory.hpp>
#include <kstd/result.hpp>
@@ -54,6 +55,17 @@ namespace kernel::filesystem
-> kstd::result<kstd::shared_ptr<inode>> = 0;
/**
+ @brief Creates a new inode with @p name within a @p parent directory.
+ @param parent The parent directory inode.
+ @param name The name of the inode to create.
+ @param inode_type The type of inode to be created.
+ @return A pointer to the created inode, or a null pointer if creation failed.
+ */
+ [[nodiscard]] virtual auto create_inode(kstd::shared_ptr<inode> const & parent, std::string_view name,
+ vfs_types::inode_type inode_type)
+ -> kstd::result<kstd::shared_ptr<inode>> = 0;
+
+ /**
@brief Returns a reference to the root inode of the filesystem.
*/
[[nodiscard]] auto root_inode() const -> kstd::shared_ptr<inode> const &;
diff --git a/kernel/kernel/filesystem/open_file_descriptor.tests.cpp b/kernel/kernel/filesystem/open_file_descriptor.tests.cpp
index 06488ba0..67883efd 100644
--- a/kernel/kernel/filesystem/open_file_descriptor.tests.cpp
+++ b/kernel/kernel/filesystem/open_file_descriptor.tests.cpp
@@ -95,7 +95,7 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, "Ope
REQUIRE(buffer_as_str == "info_1\n");
}
- THEN("the file can be read multiple times")
+ THEN("the file can be read multiple times and the offset is updated")
{
kstd::vector<std::byte> buffer(4);
auto bytes_read_1 = ofd->read(buffer.data(), buffer.size() / 2);
@@ -109,5 +109,25 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, "Ope
std::string_view buffer_as_str{reinterpret_cast<char *>(buffer.data()), *bytes_read_1 + *bytes_read_2};
REQUIRE(buffer_as_str == "info");
}
+
+ THEN("the file can be written to and the offset is updated")
+ {
+ auto write_buffer = kstd::vector<std::byte>(12, std::byte{0xAA});
+ auto const bytes_written = ofd->write(write_buffer.data(), write_buffer.size());
+ REQUIRE(bytes_written == 12);
+ REQUIRE(ofd->offset() == 12);
+ }
+
+ THEN("the file can be written to multiple times and the offset is updated")
+ {
+ auto write_buffer = kstd::vector<std::byte>(8, std::byte{0xAA});
+ auto const bytes_written_1 = ofd->write(write_buffer.data(), write_buffer.size() / 2);
+ REQUIRE(bytes_written_1 == write_buffer.size() / 2);
+ REQUIRE(ofd->offset() == write_buffer.size() / 2);
+
+ auto const bytes_written_2 = ofd->write(write_buffer.data() + write_buffer.size() / 2, write_buffer.size() / 2);
+ REQUIRE(bytes_written_2 == write_buffer.size() / 2);
+ REQUIRE(ofd->offset() == write_buffer.size());
+ }
}
}
diff --git a/kernel/kernel/filesystem/path.hpp b/kernel/kernel/filesystem/path.hpp
index 438c22f3..a0dbd1f1 100644
--- a/kernel/kernel/filesystem/path.hpp
+++ b/kernel/kernel/filesystem/path.hpp
@@ -7,6 +7,7 @@
#include <ranges>
#include <string_view>
+#include <utility>
namespace kernel::filesystem::path
{
@@ -66,6 +67,40 @@ namespace kernel::filesystem::path
[](auto const & part) { return kstd::string(std::string_view(part.begin(), part.end())); });
}
+ /**
+ @brief Splits the given path into its parent path and filename components.
+ @param path The path to split.
+ @return A pair of string views representing the parent path and filename.
+ */
+ auto inline split_into_path_and_filename(std::string_view path) -> std::pair<std::string_view, std::string_view>
+ {
+ if (path.empty())
+ {
+ return {"", ""};
+ }
+
+ if (path.length() > 1 && path.back() == '/')
+ {
+ path = path.substr(0, path.length() - 1);
+ }
+
+ auto last_separator_index = path.rfind('/');
+ if (last_separator_index == std::string_view::npos)
+ {
+ return {"", path};
+ }
+ else
+ {
+ auto parent_path = path.substr(0, last_separator_index);
+ if (parent_path.empty())
+ {
+ parent_path = "/";
+ }
+ auto filename = path.substr(last_separator_index + 1);
+ return {parent_path, filename};
+ }
+ }
+
} // namespace kernel::filesystem::path
#endif // TEACH_OS_KERNEL_FILESYSTEM_PATH_HPP \ No newline at end of file
diff --git a/kernel/kernel/filesystem/path.tests.cpp b/kernel/kernel/filesystem/path.tests.cpp
index 3c18b5c2..7bba371b 100644
--- a/kernel/kernel/filesystem/path.tests.cpp
+++ b/kernel/kernel/filesystem/path.tests.cpp
@@ -66,4 +66,63 @@ SCENARIO("path utilities", "[filesystem][path]")
REQUIRE(std::ranges::equal(components, expected));
}
}
+
+ GIVEN("file paths")
+ {
+ THEN("path is split into parent path and filename")
+ {
+ auto [parent, filename] = kernel::filesystem::path::split_into_path_and_filename("/a/b/c/d.txt");
+ REQUIRE(parent == "/a/b/c");
+ REQUIRE(filename == "d.txt");
+ }
+
+ THEN("root path is split into root and empty filename")
+ {
+ auto [parent, filename] = kernel::filesystem::path::split_into_path_and_filename("/");
+ REQUIRE(parent == "/");
+ REQUIRE(filename == "");
+ }
+
+ THEN("file directly under root is split correctly")
+ {
+ auto [parent, filename] = kernel::filesystem::path::split_into_path_and_filename("/file.txt");
+ REQUIRE(parent == "/");
+ REQUIRE(filename == "file.txt");
+ }
+
+ THEN("filename without parent path is split into empty parent and filename")
+ {
+ auto [parent, filename] = kernel::filesystem::path::split_into_path_and_filename("file.txt");
+ REQUIRE(parent == "");
+ REQUIRE(filename == "file.txt");
+ }
+
+ THEN("path with trailing slash is split correctly")
+ {
+ auto [parent, filename] = kernel::filesystem::path::split_into_path_and_filename("/a/b/c/");
+ REQUIRE(parent == "/a/b");
+ REQUIRE(filename == "c");
+ }
+
+ THEN("single component with trailing slash has root as parent")
+ {
+ auto [parent, filename] = kernel::filesystem::path::split_into_path_and_filename("/a/");
+ REQUIRE(parent == "/");
+ REQUIRE(filename == "a");
+ }
+
+ THEN("relative path is split into relative parent and filename")
+ {
+ auto [parent, filename] = kernel::filesystem::path::split_into_path_and_filename("a/b/c.txt");
+ REQUIRE(parent == "a/b");
+ REQUIRE(filename == "c.txt");
+ }
+
+ THEN("an empty path is split into empty parent and filename")
+ {
+ auto [parent, filename] = kernel::filesystem::path::split_into_path_and_filename("");
+ REQUIRE(parent == "");
+ REQUIRE(filename == "");
+ }
+ }
} \ No newline at end of file
diff --git a/kernel/kernel/filesystem/rootfs/filesystem.cpp b/kernel/kernel/filesystem/rootfs/filesystem.cpp
index fef43e28..5ff56547 100644
--- a/kernel/kernel/filesystem/rootfs/filesystem.cpp
+++ b/kernel/kernel/filesystem/rootfs/filesystem.cpp
@@ -5,6 +5,7 @@
#include <kernel/filesystem/inode.hpp>
#include <kernel/filesystem/rootfs/inode.hpp>
#include <kernel/filesystem/type.hpp>
+#include <kernel/filesystem/vfs_types.hpp>
#include <kstd/memory.hpp>
#include <kstd/result.hpp>
@@ -14,7 +15,6 @@
namespace kernel::filesystem::rootfs
{
-
struct type final : kernel::filesystem::type
{
[[nodiscard]] auto name() const noexcept -> std::string_view override
@@ -47,4 +47,11 @@ namespace kernel::filesystem::rootfs
{
return kstd::failure(vfs_errc::no_such_file_or_directory);
}
+
+ auto filesystem::create_inode(kstd::shared_ptr<kernel::filesystem::inode> const &, std::string_view,
+ kernel::filesystem::vfs_types::inode_type)
+ -> kstd::result<kstd::shared_ptr<kernel::filesystem::inode>>
+ {
+ return kstd::failure(vfs_errc::read_only_file_system);
+ }
} // namespace kernel::filesystem::rootfs
diff --git a/kernel/kernel/filesystem/rootfs/filesystem.hpp b/kernel/kernel/filesystem/rootfs/filesystem.hpp
index 8507d8b8..498503c5 100644
--- a/kernel/kernel/filesystem/rootfs/filesystem.hpp
+++ b/kernel/kernel/filesystem/rootfs/filesystem.hpp
@@ -3,6 +3,7 @@
#include <kernel/filesystem/filesystem.hpp>
#include <kernel/filesystem/inode.hpp>
+#include <kernel/filesystem/vfs_types.hpp>
#include <kstd/memory.hpp>
#include <kstd/result.hpp>
@@ -37,6 +38,17 @@ namespace kernel::filesystem::rootfs
*/
[[nodiscard]] auto lookup(kstd::shared_ptr<kernel::filesystem::inode> const & parent, std::string_view name) const
-> kstd::result<kstd::shared_ptr<kernel::filesystem::inode>> override;
+
+ /**
+ @brief Creates a new inode with @p name within a @p parent directory.
+ @param parent The parent directory inode.
+ @param name The name of the inode to create.
+ @param inode_type The type of inode to be created.
+ @return Always returns nullptr, as creating new inodes is not supported in devfs.
+ */
+ [[nodiscard]] auto create_inode(kstd::shared_ptr<kernel::filesystem::inode> const & parent, std::string_view name,
+ kernel::filesystem::vfs_types::inode_type inode_type)
+ -> kstd::result<kstd::shared_ptr<kernel::filesystem::inode>> override;
};
} // namespace kernel::filesystem::rootfs
diff --git a/kernel/kernel/filesystem/rootfs/filesystem.tests.cpp b/kernel/kernel/filesystem/rootfs/filesystem.tests.cpp
index 347502ca..95fa57db 100644
--- a/kernel/kernel/filesystem/rootfs/filesystem.tests.cpp
+++ b/kernel/kernel/filesystem/rootfs/filesystem.tests.cpp
@@ -1,6 +1,7 @@
#include <kernel/filesystem/rootfs/filesystem.hpp>
#include <kernel/filesystem/filesystem.hpp>
+#include <kernel/filesystem/vfs_types.hpp>
#include <kstd/memory.hpp>
#include <kstd/print.hpp>
@@ -34,5 +35,12 @@ SCENARIO("Rootfs filesystem mount and lookup", "[filesystem][rootfs][filesystem]
auto result = fs.lookup(nullptr, "dev");
REQUIRE(!result);
}
+
+ THEN("create_inode always returns an error")
+ {
+ auto result =
+ fs.create_inode(fs.root_inode(), "new_device", kernel::filesystem::vfs_types::inode_type::directory);
+ REQUIRE(!result);
+ }
}
}
diff --git a/kernel/kernel/filesystem/vfs.cpp b/kernel/kernel/filesystem/vfs.cpp
index d128bbf5..9a78b6f8 100644
--- a/kernel/kernel/filesystem/vfs.cpp
+++ b/kernel/kernel/filesystem/vfs.cpp
@@ -9,6 +9,7 @@
#include <kernel/filesystem/mount_table.hpp>
#include <kernel/filesystem/path.hpp>
#include <kernel/filesystem/rootfs/filesystem.hpp>
+#include <kernel/filesystem/vfs_types.hpp>
#include <kapi/system.hpp>
@@ -171,6 +172,56 @@ namespace kernel::filesystem
return kstd::failure(vfs_errc::unmount_failed);
}
+ auto vfs::mkdir(std::string_view path) -> kstd::result<void>
+ {
+ return create_inode(path, vfs_types::inode_type::directory);
+ }
+
+ auto vfs::create(std::string_view path) -> kstd::result<void>
+ {
+ return create_inode(path, vfs_types::inode_type::regular);
+ }
+
+ auto vfs::create_inode(std::string_view path, vfs_types::inode_type type) -> kstd::result<void>
+ {
+ if (!path::is_valid_path(path))
+ {
+ return kstd::failure(vfs_errc::invalid_path);
+ }
+
+ if (resolve_path(path))
+ {
+ return kstd::failure(vfs_errc::file_exists);
+ }
+
+ auto [parent_path, name] = path::split_into_path_and_filename(path);
+ if (name.empty())
+ {
+ return kstd::failure(vfs_errc::invalid_path);
+ }
+
+ auto resolved_parent = resolve_path_internal(parent_path);
+ if (!resolved_parent)
+ {
+ return kstd::failure(resolved_parent.error());
+ }
+
+ auto [parent_dentry, mount_context] = *resolved_parent;
+
+ if (auto fs = mount_context->get_filesystem())
+ {
+ if (auto new_inode = fs->create_inode(parent_dentry->get_inode(), name, type))
+ {
+ auto new_dentry = kstd::make_shared<dentry>(parent_dentry, *new_inode, name);
+ parent_dentry->add_child(new_dentry);
+
+ return kstd::success();
+ }
+ }
+
+ return kstd::failure(vfs_errc::no_such_file_or_directory);
+ }
+
auto vfs::do_mount_internal(dentry_ptr const & mount_point_dentry, mount_ptr const & parent_mount, fs_ptr const & fs,
mount_ptr const & source_mount) -> void
{
diff --git a/kernel/kernel/filesystem/vfs.hpp b/kernel/kernel/filesystem/vfs.hpp
index 9dfd3637..beb336a4 100644
--- a/kernel/kernel/filesystem/vfs.hpp
+++ b/kernel/kernel/filesystem/vfs.hpp
@@ -6,6 +6,7 @@
#include <kernel/filesystem/filesystem.hpp>
#include <kernel/filesystem/mount.hpp>
#include <kernel/filesystem/mount_table.hpp>
+#include <kernel/filesystem/vfs_types.hpp>
#include <kstd/memory.hpp>
#include <kstd/result.hpp>
@@ -77,6 +78,21 @@ namespace kernel::filesystem
*/
auto unmount(std::string_view path) -> kstd::result<void>;
+ /**
+ @brief Create a new directory at the specified @p path.
+ @param path The path where the new directory should be created.
+ @return The result of the mkdir operation.
+ */
+ auto mkdir(std::string_view path) -> kstd::result<void>;
+
+ /**
+ @brief Create a new file at the specified @p path.
+ @param path The path where the new file should be created.
+ @return The result of the create operation.
+ */
+ // TODO remove again after the open method supports an optional create flag
+ auto create(std::string_view path) -> kstd::result<void>;
+
private:
/**
* Note: Resolving a dentry requires traversing mount points; since the
@@ -100,6 +116,8 @@ namespace kernel::filesystem
auto graft_persistent_device_fs(kstd::shared_ptr<devfs::filesystem> const & device_fs) -> void;
+ auto create_inode(std::string_view path, vfs_types::inode_type type) -> kstd::result<void>;
+
mount_table m_mount_table{};
};
} // namespace kernel::filesystem
diff --git a/kernel/kernel/filesystem/vfs.tests.cpp b/kernel/kernel/filesystem/vfs.tests.cpp
index c99cea90..ace1bd1f 100644
--- a/kernel/kernel/filesystem/vfs.tests.cpp
+++ b/kernel/kernel/filesystem/vfs.tests.cpp
@@ -331,6 +331,53 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, "VFS
}
}
+ GIVEN("A real image file within which new files and directories can be created")
+ {
+ REQUIRE(std::filesystem::exists(image_path_1));
+ REQUIRE_NOTHROW(setup_modules_from_img_and_init_vfs({"test_img_module_1"}, {image_path_1}));
+
+ THEN("a new file can be created and read again")
+ {
+ auto & vfs = kernel::filesystem::vfs::get();
+ REQUIRE(vfs.create("/information/new_file.txt"));
+
+ auto new_file = vfs.open("/information/new_file.txt");
+ REQUIRE(new_file != nullptr);
+ }
+
+ THEN("a new directory can be created and read again")
+ {
+ auto & vfs = kernel::filesystem::vfs::get();
+ REQUIRE(vfs.mkdir("/information/new_directory"));
+ }
+
+ THEN("a new file cannot be created if it already exists")
+ {
+ auto & vfs = kernel::filesystem::vfs::get();
+ REQUIRE(!vfs.create("/information/info_1.txt"));
+ }
+
+ THEN("a new directory cannot be created if it already exists")
+ {
+ auto & vfs = kernel::filesystem::vfs::get();
+ REQUIRE(!vfs.mkdir("/information"));
+ }
+
+ THEN("a new file cannot be created if path does not exist or is invalid")
+ {
+ auto & vfs = kernel::filesystem::vfs::get();
+ REQUIRE(!vfs.create(""));
+ REQUIRE(!vfs.create("invalid_path"));
+ }
+
+ THEN("a new directory cannot be created if path does not exist or is invalid")
+ {
+ auto & vfs = kernel::filesystem::vfs::get();
+ REQUIRE(!vfs.mkdir(""));
+ REQUIRE(!vfs.mkdir("invalid_path"));
+ }
+ }
+
GIVEN("A real image file containing as filesystem formatted files")
{
REQUIRE(std::filesystem::exists(image_path_1));
diff --git a/kernel/kernel/filesystem/vfs_types.hpp b/kernel/kernel/filesystem/vfs_types.hpp
new file mode 100644
index 00000000..4c2ed72e
--- /dev/null
+++ b/kernel/kernel/filesystem/vfs_types.hpp
@@ -0,0 +1,16 @@
+#ifndef TEACH_OS_KERNEL_FILESYSTEM_VFS_TYPES_HPP
+#define TEACH_OS_KERNEL_FILESYSTEM_VFS_TYPES_HPP
+
+#include <cstddef>
+
+namespace kernel::filesystem::vfs_types
+{
+ enum class inode_type : size_t
+ {
+ directory = 0,
+ regular = 1,
+ symbolic_link = 2,
+ device = 3
+ };
+} // namespace kernel::filesystem::vfs_types
+#endif \ No newline at end of file
diff --git a/kernel/kernel/main.cpp b/kernel/kernel/main.cpp
index 8dce5143..fef35ed9 100644
--- a/kernel/kernel/main.cpp
+++ b/kernel/kernel/main.cpp
@@ -107,6 +107,39 @@ auto run_demo() -> void
kstd::println("--> written {} bytes to /dev/ram32: {::#04x}", bytes_written, write_buffer);
// 10) do memory dump to show that the write to the device file had an effect
+
+ // 11) dmp the module before create new directory and file
+ // -exec monitor memsave 0xffffffff8025b000 0xA00000 dump_before.bin
+
+ // 12) create new folder and file
+ auto creation_result = kapi::filesystem::mkdir("/test_files").and_then([] {
+ return kapi::filesystem::create("/test_files/test_file.txt");
+ });
+
+ if (!creation_result)
+ {
+ kapi::system::panic("demo failed", creation_result.error());
+ }
+
+ // 13) write to the new file
+ auto fd_4 = kapi::filesystem::open("/test_files/test_file.txt");
+ if (fd_4)
+ {
+ kstd::println("--> successfully opened /test_files/test_file.txt as a file with file descriptor {}", fd_4.value());
+ }
+ else
+ {
+ kapi::system::panic("demo failed");
+ }
+
+ kstd::vector<std::byte> test_write_buffer{
+ std::byte{'H'}, std::byte{'e'}, std::byte{'l'}, std::byte{'l'}, std::byte{'o'}, std::byte{' '}, std::byte{'T'},
+ std::byte{'e'}, std::byte{'a'}, std::byte{'c'}, std::byte{'h'}, std::byte{'O'}, std::byte{'S'}};
+ bytes_written = *kapi::filesystem::write(*fd_4, test_write_buffer.data(), test_write_buffer.size());
+ kstd::println("--> written {} bytes to /test_files/test_file.txt: {::#04x}", bytes_written, test_write_buffer);
+
+ // 14) dmp the module after create new directory and file
+ // -exec monitor memsave 0xffffffff8025b000 0xA00000 dump_after.bin
}
auto main() -> int
diff --git a/kernel/kernel/test_support/filesystem/ext2.cpp b/kernel/kernel/test_support/filesystem/ext2.cpp
index 52b6efe7..3a7449ed 100644
--- a/kernel/kernel/test_support/filesystem/ext2.cpp
+++ b/kernel/kernel/test_support/filesystem/ext2.cpp
@@ -8,6 +8,7 @@
#include <cstdint>
#include <cstring>
+#include <vector>
namespace kernel::tests::filesystem::ext2
{
@@ -49,10 +50,17 @@ namespace kernel::tests::filesystem::ext2
auto setup_mock_ext2_layout(kernel::tests::devices::block_device & device,
kernel::filesystem::ext2::superblock const & superblock) -> void
{
- write_bytes(device, kernel::filesystem::ext2::constants::superblock_offset, &superblock, sizeof(superblock));
-
auto group_descriptor = kernel::filesystem::ext2::block_group_descriptor{};
group_descriptor.inode_table = 5;
+
+ setup_mock_ext2_layout(device, superblock, group_descriptor);
+ }
+
+ auto setup_mock_ext2_layout(kernel::tests::devices::block_device & device,
+ kernel::filesystem::ext2::superblock const & superblock,
+ kernel::filesystem::ext2::block_group_descriptor const & group_descriptor) -> void
+ {
+ write_bytes(device, kernel::filesystem::ext2::constants::superblock_offset, &superblock, sizeof(superblock));
write_bytes(device, 2048, &group_descriptor, sizeof(group_descriptor));
auto root_inode_data = kernel::filesystem::ext2::inode_data{};
diff --git a/kernel/kernel/test_support/filesystem/ext2.hpp b/kernel/kernel/test_support/filesystem/ext2.hpp
index 18cef1c2..3dd80ea3 100644
--- a/kernel/kernel/test_support/filesystem/ext2.hpp
+++ b/kernel/kernel/test_support/filesystem/ext2.hpp
@@ -1,6 +1,7 @@
#ifndef TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_EXT2_HPP
#define TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_EXT2_HPP
+#include <kernel/filesystem/ext2/block_group_descriptor.hpp>
#include <kernel/filesystem/ext2/superblock.hpp>
#include <kernel/test_support/devices/block_device.hpp>
@@ -15,6 +16,9 @@ namespace kernel::tests::filesystem::ext2
auto setup_mock_ext2_layout(kernel::tests::devices::block_device & device) -> void;
auto setup_mock_ext2_layout(kernel::tests::devices::block_device & device,
kernel::filesystem::ext2::superblock const & superblock) -> void;
+ auto setup_mock_ext2_layout(kernel::tests::devices::block_device & device,
+ kernel::filesystem::ext2::superblock const & superblock,
+ kernel::filesystem::ext2::block_group_descriptor const & group_descriptor) -> void;
} // namespace kernel::tests::filesystem::ext2
diff --git a/kernel/kernel/test_support/filesystem/filesystem.cpp b/kernel/kernel/test_support/filesystem/filesystem.cpp
index 38cea2f1..b48c7935 100644
--- a/kernel/kernel/test_support/filesystem/filesystem.cpp
+++ b/kernel/kernel/test_support/filesystem/filesystem.cpp
@@ -1,18 +1,26 @@
#include <kernel/test_support/filesystem/filesystem.hpp>
#include <kernel/filesystem/inode.hpp>
+#include <kernel/filesystem/vfs_types.hpp>
#include <kernel/test_support/filesystem/inode.hpp>
#include <kstd/memory.hpp>
+#include <kstd/result.hpp>
#include <kstd/system_error.hpp>
-#include <expected>
#include <string_view>
namespace kernel::tests::filesystem
{
auto filesystem::lookup(kstd::shared_ptr<kernel::filesystem::inode> const &, std::string_view) const
- -> std::expected<kstd::shared_ptr<kernel::filesystem::inode>, kstd::error_code>
+ -> kstd::result<kstd::shared_ptr<kernel::filesystem::inode>>
+ {
+ return kstd::make_shared<inode>();
+ }
+
+ auto filesystem::create_inode(kstd::shared_ptr<kernel::filesystem::inode> const &, std::string_view,
+ kernel::filesystem::vfs_types::inode_type)
+ -> kstd::result<kstd::shared_ptr<kernel::filesystem::inode>>
{
return kstd::make_shared<inode>();
}
diff --git a/kernel/kernel/test_support/filesystem/filesystem.hpp b/kernel/kernel/test_support/filesystem/filesystem.hpp
index e6e0f416..17edd079 100644
--- a/kernel/kernel/test_support/filesystem/filesystem.hpp
+++ b/kernel/kernel/test_support/filesystem/filesystem.hpp
@@ -3,11 +3,12 @@
#include <kernel/filesystem/filesystem.hpp>
#include <kernel/filesystem/inode.hpp>
+#include <kernel/filesystem/vfs_types.hpp>
#include <kstd/memory.hpp>
+#include <kstd/result.hpp>
#include <kstd/system_error.hpp>
-#include <expected>
#include <string_view>
namespace kernel::tests::filesystem
@@ -17,7 +18,11 @@ namespace kernel::tests::filesystem
filesystem() = default;
[[nodiscard]] auto lookup(kstd::shared_ptr<kernel::filesystem::inode> const & parent, std::string_view name) const
- -> std::expected<kstd::shared_ptr<kernel::filesystem::inode>, kstd::error_code> override;
+ -> kstd::result<kstd::shared_ptr<kernel::filesystem::inode>> override;
+
+ [[nodiscard]] auto create_inode(kstd::shared_ptr<kernel::filesystem::inode> const & parent, std::string_view name,
+ kernel::filesystem::vfs_types::inode_type inode_type)
+ -> kstd::result<kstd::shared_ptr<kernel::filesystem::inode>> override;
};
} // namespace kernel::tests::filesystem