diff options
Diffstat (limited to 'kernel')
29 files changed, 1686 insertions, 72 deletions
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 |
