From 18d80c9159424014f204fb1b2545405eeea9221f Mon Sep 17 00:00:00 2001 From: Marcel Braun Date: Sat, 30 May 2026 10:10:15 +0200 Subject: Implement ext2 inode write --- kernel/kernel/filesystem/ext2/inode.cpp | 41 +++++++++++++++++++++++++-- kernel/kernel/filesystem/ext2/inode.tests.cpp | 15 ---------- 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/kernel/kernel/filesystem/ext2/inode.cpp b/kernel/kernel/filesystem/ext2/inode.cpp index 6dadbec5..e873d446 100644 --- a/kernel/kernel/filesystem/ext2/inode.cpp +++ b/kernel/kernel/filesystem/ext2/inode.cpp @@ -76,10 +76,45 @@ namespace kernel::filesystem::ext2 return bytes_read; } - auto inode::write(void const *, size_t, size_t) -> kstd::result + auto inode::write(void const * buffer, size_t offset, size_t size) -> kstd::result { - kapi::system::panic("[EXT2] inode::write is not implemented yet"); - return 0; + auto const max_writable = this->size() - offset; + auto const requested_size = std::min(size, max_writable); + + auto block_index = offset / m_filesystem->block_size(); + auto in_block_offset = offset % m_filesystem->block_size(); + + auto bytes_written = 0uz; + + while (bytes_written < requested_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(requested_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(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; // After the first block, we always start at the beginning of the block + } + + return bytes_written; } [[nodiscard]] auto inode::data() const -> inode_data const & diff --git a/kernel/kernel/filesystem/ext2/inode.tests.cpp b/kernel/kernel/filesystem/ext2/inode.tests.cpp index 496e7c4f..7041f886 100644 --- a/kernel/kernel/filesystem/ext2/inode.tests.cpp +++ b/kernel/kernel/filesystem/ext2/inode.tests.cpp @@ -277,21 +277,6 @@ SCENARIO("Ext2 inode read across block boundaries", "[filesystem][ext2][inode]") } } -SCENARIO("Ext2 inode write is not implemented", "[filesystem][ext2][inode]") -{ - GIVEN("an ext2 inode") - { - auto fs = kernel::filesystem::ext2::filesystem{}; - auto inode = kernel::filesystem::ext2::inode{&fs, kernel::filesystem::ext2::inode_data{}}; - - THEN("writing to the inode panics") - { - auto buffer = kstd::vector(32, std::byte{0x00}); - REQUIRE_THROWS_AS(inode.write(buffer.data(), 0, buffer.size()), kernel::tests::cpu::halt); - } - } -} - SCENARIO("Ext2 inode get_size() correctly returns size depending on revision level", "[filesystem][ext2][inode]") { auto const block_size = 1024uz; -- cgit v1.2.3 From f664f45f92af96bc0f4fd7561047d9fc532c9ddc Mon Sep 17 00:00:00 2001 From: Marcel Braun Date: Sat, 30 May 2026 10:10:38 +0200 Subject: Add ext2 inode write tests --- kernel/kernel/filesystem/ext2/inode.tests.cpp | 79 +++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/kernel/kernel/filesystem/ext2/inode.tests.cpp b/kernel/kernel/filesystem/ext2/inode.tests.cpp index 7041f886..7448181c 100644 --- a/kernel/kernel/filesystem/ext2/inode.tests.cpp +++ b/kernel/kernel/filesystem/ext2/inode.tests.cpp @@ -277,6 +277,85 @@ SCENARIO("Ext2 inode read across block boundaries", "[filesystem][ext2][inode]") } } +SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, "Ext2 inode writes to real image", + "[filesystem][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(boot_device); + + auto fs = kernel::filesystem::ext2::filesystem{}; + 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") + { + auto write_buffer = kstd::vector{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 == 7); + + auto read_buffer = kstd::vector(42, std::byte{0x00}); + auto const bytes_read = file.value()->read(read_buffer.data(), 0, read_buffer.size()); + REQUIRE(bytes_read == 7); + + auto const text = std::string_view{reinterpret_cast(read_buffer.data()), *bytes_read}; + REQUIRE(text == "Hello W"); + } + } +} + +SCENARIO("Ext2 inode write across block boundaries", "[filesystem][ext2][inode]") +{ + auto const block_size = 1024uz; + GIVEN("an ext2 inode with two direct blocks") + { + auto device = kstd::make_shared(0, 0, "mock", block_size, 64 * block_size); + REQUIRE(device != nullptr); + kernel::tests::filesystem::ext2::setup_mock_ext2_layout(*device); + + auto dev_inode = kstd::make_shared(device); + + auto fs = kernel::filesystem::ext2::filesystem{}; + REQUIRE(fs.mount(dev_inode)); + + auto inode_data = kernel::filesystem::ext2::inode_data{}; + inode_data.size = block_size * 2; + inode_data.block[0] = 20; + inode_data.block[1] = 21; + + auto inode = kernel::filesystem::ext2::inode{&fs, inode_data}; + + THEN("writing over block boundaries updates backing device") + { + auto buffer = kstd::vector{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 = inode.write(buffer.data(), block_size - 4, buffer.size()); + REQUIRE(bytes_written == buffer.size()); + + auto const text = + std::string_view{reinterpret_cast(device->data.data() + 21 * block_size - 4), *bytes_written}; + REQUIRE(text == "Hello World!"); + } + } +} + SCENARIO("Ext2 inode get_size() correctly returns size depending on revision level", "[filesystem][ext2][inode]") { auto const block_size = 1024uz; -- cgit v1.2.3 From 63874f63ac91267ed6c9011306b47689f6edbbbd Mon Sep 17 00:00:00 2001 From: Marcel Braun Date: Sat, 30 May 2026 10:34:12 +0200 Subject: Add check for inode type to ext2 inode write --- kernel/kernel/filesystem/ext2/inode.cpp | 5 +++++ kernel/kernel/filesystem/ext2/inode.tests.cpp | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/kernel/kernel/filesystem/ext2/inode.cpp b/kernel/kernel/filesystem/ext2/inode.cpp index e873d446..136f37a4 100644 --- a/kernel/kernel/filesystem/ext2/inode.cpp +++ b/kernel/kernel/filesystem/ext2/inode.cpp @@ -78,6 +78,11 @@ namespace kernel::filesystem::ext2 auto inode::write(void const * buffer, size_t offset, size_t size) -> kstd::result { + if (!is_regular()) + { + kapi::system::panic("[EXT2] ext2::inode write called on inode that is not of type regular"); + } + auto const max_writable = this->size() - offset; auto const requested_size = std::min(size, max_writable); diff --git a/kernel/kernel/filesystem/ext2/inode.tests.cpp b/kernel/kernel/filesystem/ext2/inode.tests.cpp index 7448181c..549e2032 100644 --- a/kernel/kernel/filesystem/ext2/inode.tests.cpp +++ b/kernel/kernel/filesystem/ext2/inode.tests.cpp @@ -316,6 +316,12 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, "Ext2 in auto const text = std::string_view{reinterpret_cast(read_buffer.data()), *bytes_read}; REQUIRE(text == "Hello W"); } + + THEN("writing to a an inode of type directory panics") + { + auto write_buffer = kstd::vector(12, std::byte{0xAA}); + REQUIRE_THROWS_AS(information->write(write_buffer.data(), 0, write_buffer.size()), kernel::tests::cpu::halt); + } } } @@ -337,6 +343,7 @@ SCENARIO("Ext2 inode write across block boundaries", "[filesystem][ext2][inode]" inode_data.size = block_size * 2; inode_data.block[0] = 20; inode_data.block[1] = 21; + inode_data.mode = kernel::filesystem::ext2::constants::mode_regular; auto inode = kernel::filesystem::ext2::inode{&fs, inode_data}; -- cgit v1.2.3 From 1b24bb01dae10e226a3daaa08e9b47d4724e47a2 Mon Sep 17 00:00:00 2001 From: Marcel Braun Date: Sat, 30 May 2026 10:58:59 +0200 Subject: Add tests --- kernel/kapi/filesystem.tests.cpp | 24 ++++++++++++++++++++++ .../filesystem/open_file_descriptor.tests.cpp | 22 +++++++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/kernel/kapi/filesystem.tests.cpp b/kernel/kapi/filesystem.tests.cpp index d1e01db6..2c2a8d1e 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 >= 0); + + auto write_buffer = std::vector{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 >= 0); + + auto read_buffer = std::vector(6); + auto const bytes_read = kapi::filesystem::read(read_fd, read_buffer.data(), read_buffer.size()); + REQUIRE(bytes_read >= 0); + + std::string_view buffer_as_str{reinterpret_cast(read_buffer.data()), static_cast(bytes_read)}; + REQUIRE(buffer_as_str == "Hefo_1"); + + REQUIRE(kapi::filesystem::close(write_fd) == 0); + REQUIRE(kapi::filesystem::close(read_fd) == 0); + } + 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(); diff --git a/kernel/kernel/filesystem/open_file_descriptor.tests.cpp b/kernel/kernel/filesystem/open_file_descriptor.tests.cpp index 06488ba0..daf9327f 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 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(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(12, std::byte{0xAA}); + auto const bytes_written = ofd->write(write_buffer.data(), write_buffer.size()); + REQUIRE(bytes_written == 7); + REQUIRE(ofd->offset() == 7); + } + + THEN("the file can be written to multiple times and the offset is updated") + { + auto write_buffer = kstd::vector(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 == 3); + REQUIRE(ofd->offset() == 7); + } } } -- cgit v1.2.3 From 35499c5b9eaf1fd3e0f6b5e9e1a07025a85be9c4 Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Sat, 30 May 2026 13:01:57 +0200 Subject: fix comment, basic write is implemented yet --- kernel/kernel/filesystem/ext2/inode.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/kernel/kernel/filesystem/ext2/inode.hpp b/kernel/kernel/filesystem/ext2/inode.hpp index 16cb04e0..f04fb1e1 100644 --- a/kernel/kernel/filesystem/ext2/inode.hpp +++ b/kernel/kernel/filesystem/ext2/inode.hpp @@ -59,7 +59,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. -- cgit v1.2.3 From 290b94fd629d74d09659e486593a43db03ab066f Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Sat, 30 May 2026 18:41:29 +0200 Subject: first draft to allocate new blocks while writing to an inode --- kernel/kernel/filesystem/ext2/error.hpp | 9 ++ kernel/kernel/filesystem/ext2/filesystem.cpp | 121 ++++++++++++++++++++++++++- kernel/kernel/filesystem/ext2/filesystem.hpp | 18 +++- kernel/kernel/filesystem/ext2/inode.cpp | 52 ++++++++++-- kernel/kernel/filesystem/ext2/inode.hpp | 8 +- 5 files changed, 195 insertions(+), 13 deletions(-) diff --git a/kernel/kernel/filesystem/ext2/error.hpp b/kernel/kernel/filesystem/ext2/error.hpp index 79f40517..986ab167 100644 --- a/kernel/kernel/filesystem/ext2/error.hpp +++ b/kernel/kernel/filesystem/ext2/error.hpp @@ -20,6 +20,7 @@ namespace kernel::filesystem::ext2 invalid_block_number, failed_to_read_superblock, failed_to_read_block_group_descriptors, + not_enough_free_blocks, }; namespace detail @@ -49,6 +50,8 @@ 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"; default: return "unknown ext2 error"; }; @@ -80,6 +83,12 @@ namespace kernel::filesystem::ext2 return condition.value() == static_cast(kstd::errc::io_error); } break; + case ext2_errc::not_enough_free_blocks: + if (condition.category() == kstd::generic_category()) + { + return condition.value() == static_cast(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..af693230 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.cpp +++ b/kernel/kernel/filesystem/ext2/filesystem.cpp @@ -24,6 +24,22 @@ namespace kernel::filesystem::ext2 { + namespace + { + [[nodiscard]] auto bitmap_is_set(kstd::vector 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(1u << bit_index)) != 0; + } + + auto bitmap_set(kstd::vector & bitmap, size_t index) -> void + { + auto const byte_index = index / 8; + auto const bit_index = index % 8; + bitmap.at(byte_index) |= static_cast(1u << bit_index); + } + } // namespace struct type final : kernel::filesystem::type { @@ -107,14 +123,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()); } @@ -160,7 +174,101 @@ namespace kernel::filesystem::ext2 return kstd::failure(read_result.error()); } - return kstd::make_shared(this, new_inode_data); + return kstd::make_shared(const_cast(this), inode_number, new_inode_data); + } + + auto filesystem::write_inode(uint32_t inode_number, inode_data const & data) -> kstd::result + { + // TODO BA-FS26 refactoring + 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; + + if (block_group_index >= m_block_group_descriptors.size()) + { + return kstd::failure(ext2_errc::invalid_block_group_index); + } + + auto const & block_group_descriptor = m_block_group_descriptors.at(block_group_index); + auto const inode_table_start_block = block_group_descriptor.inode_table; + auto const inode_table_offset = static_cast(inode_table_start_block) * block_size(); + auto const inode_offset = inode_table_offset + inode_index_within_group * inode_size(); + + return m_backing_inode->write(&data, inode_offset, sizeof(inode_data)).transform([](auto) {}); + } + + auto filesystem::allocate_blocks(size_t count) -> kstd::result> + { + if (m_block_group_descriptors.empty() || count > m_superblock.free_blocks_count) + { + return kstd::failure(ext2_errc::not_enough_free_blocks); + } + + kstd::vector 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 block_bitmap{}; + block_bitmap.resize(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 < block_bitmap.size() * 8 && 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 @@ -282,6 +390,11 @@ 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(); diff --git a/kernel/kernel/filesystem/ext2/filesystem.hpp b/kernel/kernel/filesystem/ext2/filesystem.hpp index 5e5913bf..7361943a 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.hpp +++ b/kernel/kernel/filesystem/ext2/filesystem.hpp @@ -78,6 +78,18 @@ namespace kernel::filesystem::ext2 */ [[nodiscard]] auto revision_level() const -> uint32_t; + auto read_block(uint32_t block_number, void * buffer) const -> kstd::result; + + auto write_block(uint32_t block_number, void const * buffer) -> kstd::result; + + auto allocate_blocks(size_t count) -> kstd::result>; + + [[nodiscard]] auto inode_block_count(inode_data const & data) const -> uint32_t; + + auto update_inode_block_count(inode_data & data, uint32_t delta) -> void; + + auto write_inode(uint32_t inode_number, inode_data const & data) -> kstd::result; + /** @brief Maps an inode block index to a global block number. @param inode_block_index The index of the block within the inode. @@ -87,6 +99,9 @@ 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; + auto write_global_block_number_to_inode_block_index(size_t block_index, inode_data & data, + uint32_t global_block_number) -> void; + private: struct indirect_level { @@ -99,8 +114,9 @@ namespace kernel::filesystem::ext2 [[nodiscard]] auto read_inode(uint32_t inode_number) const -> kstd::result>; [[nodiscard]] auto read_block_number_at_index(uint32_t block_number, size_t index) const -> kstd::result; + auto ensure_and_get_block(uint32_t & block_number) -> bool; + [[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; diff --git a/kernel/kernel/filesystem/ext2/inode.cpp b/kernel/kernel/filesystem/ext2/inode.cpp index 136f37a4..2484d2f5 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) @@ -83,15 +84,35 @@ namespace kernel::filesystem::ext2 kapi::system::panic("[EXT2] ext2::inode write called on inode that is not of type regular"); } - auto const max_writable = this->size() - offset; - auto const requested_size = std::min(size, max_writable); + // 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"); + } + } + + // TODO update inode size, ... auto block_index = offset / m_filesystem->block_size(); auto in_block_offset = offset % m_filesystem->block_size(); auto bytes_written = 0uz; - while (bytes_written < requested_size) + 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 @@ -100,8 +121,7 @@ namespace kernel::filesystem::ext2 kstd::failure(block_number.error()); } - auto const bytes_to_write = - std::min(requested_size - bytes_written, m_filesystem->block_size() - in_block_offset); + 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; @@ -122,6 +142,26 @@ namespace kernel::filesystem::ext2 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); + + m_filesystem->write_global_block_number_to_inode_block_index(block_index, m_data, global_block_number); + } + + m_filesystem->update_inode_block_count(m_data, count); + return m_filesystem->write_inode(m_inode_number, m_data).has_value(); + } + [[nodiscard]] auto inode::data() const -> inode_data const & { return m_data; diff --git a/kernel/kernel/filesystem/ext2/inode.hpp b/kernel/kernel/filesystem/ext2/inode.hpp index f04fb1e1..175264b3 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. @@ -97,7 +98,10 @@ namespace kernel::filesystem::ext2 [[nodiscard]] auto size() const -> uint64_t; private: - filesystem const * m_filesystem; + auto append_blocks(size_t count) -> bool; + + filesystem * m_filesystem; + uint32_t m_inode_number{}; inode_data m_data{}; }; } // namespace kernel::filesystem::ext2 -- cgit v1.2.3 From 23fba472fa2d85aff45328727287bacaea2180f8 Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Sat, 30 May 2026 18:41:46 +0200 Subject: fix bht build --- kernel/kernel/filesystem/ext2/inode.tests.cpp | 28 +++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/kernel/kernel/filesystem/ext2/inode.tests.cpp b/kernel/kernel/filesystem/ext2/inode.tests.cpp index 549e2032..1998af28 100644 --- a/kernel/kernel/filesystem/ext2/inode.tests.cpp +++ b/kernel/kernel/filesystem/ext2/inode.tests.cpp @@ -32,7 +32,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 +43,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 +54,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 +65,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 +78,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 +152,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(data.size, std::byte{0xAB}); @@ -193,7 +193,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(data.size, std::byte{0xAB}); @@ -229,7 +229,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(block_size * 15, std::byte{0xAB}); @@ -262,7 +262,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(12, std::byte{0x00}); @@ -345,7 +345,7 @@ SCENARIO("Ext2 inode write across block boundaries", "[filesystem][ext2][inode]" inode_data.block[1] = 21; inode_data.mode = kernel::filesystem::ext2::constants::mode_regular; - auto inode = kernel::filesystem::ext2::inode{&fs, inode_data}; + auto inode = kernel::filesystem::ext2::inode{&fs, 42, inode_data}; THEN("writing over block boundaries updates backing device") { @@ -396,7 +396,7 @@ SCENARIO("Ext2 inode get_size() correctly returns size depending on revision lev { 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.size() == 256); } @@ -405,7 +405,7 @@ SCENARIO("Ext2 inode get_size() correctly returns size depending on revision lev { 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(inode.size() == 256); } @@ -432,7 +432,7 @@ SCENARIO("Ext2 inode get_size() correctly returns size depending on revision lev { 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.size() == 0x0000'0020'0000'0100); } @@ -441,7 +441,7 @@ SCENARIO("Ext2 inode get_size() correctly returns size depending on revision lev { 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(inode.size() == 256); } -- cgit v1.2.3 From 4c519c8e8852fd719bdf1cabf3fb7699e2cf6525 Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Sat, 30 May 2026 19:31:34 +0200 Subject: update inode size correctly, --- kernel/kapi/filesystem.tests.cpp | 16 ++++++++-------- kernel/kernel/filesystem/ext2/inode.cpp | 21 +++++++++++++++++---- kernel/kernel/filesystem/ext2/inode.hpp | 1 + kernel/kernel/filesystem/ext2/inode.tests.cpp | 3 ++- 4 files changed, 28 insertions(+), 13 deletions(-) diff --git a/kernel/kapi/filesystem.tests.cpp b/kernel/kapi/filesystem.tests.cpp index 2c2a8d1e..b339f19f 100644 --- a/kernel/kapi/filesystem.tests.cpp +++ b/kernel/kapi/filesystem.tests.cpp @@ -42,25 +42,25 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, "Kap 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 >= 0); + REQUIRE(write_fd); auto write_buffer = std::vector{std::byte{'H'}, std::byte{'e'}}; - auto const bytes_written = kapi::filesystem::write(write_fd, write_buffer.data(), write_buffer.size()); + 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 >= 0); + REQUIRE(read_fd); auto read_buffer = std::vector(6); - auto const bytes_read = kapi::filesystem::read(read_fd, read_buffer.data(), read_buffer.size()); - REQUIRE(bytes_read >= 0); + 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(read_buffer.data()), static_cast(bytes_read)}; + std::string_view buffer_as_str{reinterpret_cast(read_buffer.data()), static_cast(*bytes_read)}; REQUIRE(buffer_as_str == "Hefo_1"); - REQUIRE(kapi::filesystem::close(write_fd) == 0); - REQUIRE(kapi::filesystem::close(read_fd) == 0); + 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") diff --git a/kernel/kernel/filesystem/ext2/inode.cpp b/kernel/kernel/filesystem/ext2/inode.cpp index 2484d2f5..83db6ec6 100644 --- a/kernel/kernel/filesystem/ext2/inode.cpp +++ b/kernel/kernel/filesystem/ext2/inode.cpp @@ -105,8 +105,6 @@ namespace kernel::filesystem::ext2 } } - // TODO update inode size, ... - auto block_index = offset / m_filesystem->block_size(); auto in_block_offset = offset % m_filesystem->block_size(); @@ -136,7 +134,13 @@ namespace kernel::filesystem::ext2 bytes_written += write_result.value(); block_index++; - in_block_offset = 0; // After the first block, we always start at the beginning of the block + 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; @@ -159,7 +163,7 @@ namespace kernel::filesystem::ext2 } m_filesystem->update_inode_block_count(m_data, count); - return m_filesystem->write_inode(m_inode_number, m_data).has_value(); + return true; } [[nodiscard]] auto inode::data() const -> inode_data const & @@ -194,4 +198,13 @@ 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(new_size >> 32); + } + m_data.size = static_cast(new_size); + } + } // namespace kernel::filesystem::ext2 diff --git a/kernel/kernel/filesystem/ext2/inode.hpp b/kernel/kernel/filesystem/ext2/inode.hpp index 175264b3..6001c813 100644 --- a/kernel/kernel/filesystem/ext2/inode.hpp +++ b/kernel/kernel/filesystem/ext2/inode.hpp @@ -99,6 +99,7 @@ namespace kernel::filesystem::ext2 private: auto append_blocks(size_t count) -> bool; + auto set_size(uint64_t new_size) -> void; filesystem * m_filesystem; uint32_t m_inode_number{}; diff --git a/kernel/kernel/filesystem/ext2/inode.tests.cpp b/kernel/kernel/filesystem/ext2/inode.tests.cpp index 1998af28..65370bf5 100644 --- a/kernel/kernel/filesystem/ext2/inode.tests.cpp +++ b/kernel/kernel/filesystem/ext2/inode.tests.cpp @@ -320,7 +320,8 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, "Ext2 in THEN("writing to a an inode of type directory panics") { auto write_buffer = kstd::vector(12, std::byte{0xAA}); - REQUIRE_THROWS_AS(information->write(write_buffer.data(), 0, write_buffer.size()), kernel::tests::cpu::halt); + REQUIRE_THROWS_AS(information.value()->write(write_buffer.data(), 0, write_buffer.size()), + kernel::tests::cpu::halt); } } } -- cgit v1.2.3 From 64586bb24023d04e39476d007e9121e0102c1b47 Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Sat, 30 May 2026 19:31:50 +0200 Subject: fix (or comment) tests --- kernel/kernel/filesystem/ext2/inode.tests.cpp | 75 +++++++++++----------- .../filesystem/open_file_descriptor.tests.cpp | 8 +-- 2 files changed, 42 insertions(+), 41 deletions(-) diff --git a/kernel/kernel/filesystem/ext2/inode.tests.cpp b/kernel/kernel/filesystem/ext2/inode.tests.cpp index 65370bf5..6aba8b77 100644 --- a/kernel/kernel/filesystem/ext2/inode.tests.cpp +++ b/kernel/kernel/filesystem/ext2/inode.tests.cpp @@ -307,14 +307,14 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, "Ext2 in 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 == 7); + REQUIRE(bytes_written == 12); auto read_buffer = kstd::vector(42, std::byte{0x00}); auto const bytes_read = file.value()->read(read_buffer.data(), 0, read_buffer.size()); - REQUIRE(bytes_read == 7); + REQUIRE(bytes_read == 12); auto const text = std::string_view{reinterpret_cast(read_buffer.data()), *bytes_read}; - REQUIRE(text == "Hello W"); + REQUIRE(text == "Hello World!"); } THEN("writing to a an inode of type directory panics") @@ -328,40 +328,41 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, "Ext2 in SCENARIO("Ext2 inode write across block boundaries", "[filesystem][ext2][inode]") { - auto const block_size = 1024uz; - GIVEN("an ext2 inode with two direct blocks") - { - auto device = kstd::make_shared(0, 0, "mock", block_size, 64 * block_size); - REQUIRE(device != nullptr); - kernel::tests::filesystem::ext2::setup_mock_ext2_layout(*device); - - auto dev_inode = kstd::make_shared(device); - - auto fs = kernel::filesystem::ext2::filesystem{}; - REQUIRE(fs.mount(dev_inode)); - - auto inode_data = kernel::filesystem::ext2::inode_data{}; - inode_data.size = block_size * 2; - inode_data.block[0] = 20; - inode_data.block[1] = 21; - inode_data.mode = kernel::filesystem::ext2::constants::mode_regular; - - auto inode = kernel::filesystem::ext2::inode{&fs, 42, inode_data}; - - THEN("writing over block boundaries updates backing device") - { - auto buffer = kstd::vector{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 = inode.write(buffer.data(), block_size - 4, buffer.size()); - REQUIRE(bytes_written == buffer.size()); - - auto const text = - std::string_view{reinterpret_cast(device->data.data() + 21 * block_size - 4), *bytes_written}; - REQUIRE(text == "Hello World!"); - } - } + // auto const block_size = 1024uz; + // GIVEN("an ext2 inode with two direct blocks") + // { + // auto device = kstd::make_shared(0, 0, "mock", block_size, 64 * block_size); + // REQUIRE(device != nullptr); + // kernel::tests::filesystem::ext2::setup_mock_ext2_layout(*device); + + // auto dev_inode = kstd::make_shared(device); + + // auto fs = kernel::filesystem::ext2::filesystem{}; + // REQUIRE(fs.mount(dev_inode) == kernel::filesystem::filesystem::operation_result::success); + + // auto inode_data = kernel::filesystem::ext2::inode_data{}; + // inode_data.size = block_size * 2; + // 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; + + // auto inode = kernel::filesystem::ext2::inode{&fs, 42, inode_data}; + + // THEN("writing over block boundaries updates backing device") + // { + // auto buffer = kstd::vector{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 = inode.write(buffer.data(), block_size - 4, buffer.size()); + // REQUIRE(bytes_written == buffer.size()); + + // auto const text = + // std::string_view{reinterpret_cast(device->data.data() + 21 * block_size - 4), bytes_written}; + // REQUIRE(text == "Hello World!"); + // } + // } } SCENARIO("Ext2 inode get_size() correctly returns size depending on revision level", "[filesystem][ext2][inode]") diff --git a/kernel/kernel/filesystem/open_file_descriptor.tests.cpp b/kernel/kernel/filesystem/open_file_descriptor.tests.cpp index daf9327f..67883efd 100644 --- a/kernel/kernel/filesystem/open_file_descriptor.tests.cpp +++ b/kernel/kernel/filesystem/open_file_descriptor.tests.cpp @@ -114,8 +114,8 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, "Ope { auto write_buffer = kstd::vector(12, std::byte{0xAA}); auto const bytes_written = ofd->write(write_buffer.data(), write_buffer.size()); - REQUIRE(bytes_written == 7); - REQUIRE(ofd->offset() == 7); + REQUIRE(bytes_written == 12); + REQUIRE(ofd->offset() == 12); } THEN("the file can be written to multiple times and the offset is updated") @@ -126,8 +126,8 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, "Ope 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 == 3); - REQUIRE(ofd->offset() == 7); + REQUIRE(bytes_written_2 == write_buffer.size() / 2); + REQUIRE(ofd->offset() == write_buffer.size()); } } } -- cgit v1.2.3 From 0fb3f139253a94bc4ab203729eddffd6d97d4a15 Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Sat, 30 May 2026 20:17:17 +0200 Subject: fix, improve and extend tests --- kernel/kernel/filesystem/ext2/inode.hpp | 3 +- kernel/kernel/filesystem/ext2/inode.tests.cpp | 165 +++++++++++++++++-------- kernel/kernel/test_support/filesystem/ext2.cpp | 12 +- kernel/kernel/test_support/filesystem/ext2.hpp | 4 + 4 files changed, 127 insertions(+), 57 deletions(-) diff --git a/kernel/kernel/filesystem/ext2/inode.hpp b/kernel/kernel/filesystem/ext2/inode.hpp index 6001c813..9c5f9beb 100644 --- a/kernel/kernel/filesystem/ext2/inode.hpp +++ b/kernel/kernel/filesystem/ext2/inode.hpp @@ -97,9 +97,10 @@ namespace kernel::filesystem::ext2 */ [[nodiscard]] auto size() const -> uint64_t; + auto set_size(uint64_t new_size) -> void; + private: auto append_blocks(size_t count) -> bool; - auto set_size(uint64_t new_size) -> void; filesystem * m_filesystem; uint32_t m_inode_number{}; diff --git a/kernel/kernel/filesystem/ext2/inode.tests.cpp b/kernel/kernel/filesystem/ext2/inode.tests.cpp index 6aba8b77..752d17d7 100644 --- a/kernel/kernel/filesystem/ext2/inode.tests.cpp +++ b/kernel/kernel/filesystem/ext2/inode.tests.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -17,8 +18,10 @@ #include #include +#include #include #include +#include // NOLINTBEGIN(readability-magic-numbers) @@ -328,44 +331,84 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, "Ext2 in SCENARIO("Ext2 inode write across block boundaries", "[filesystem][ext2][inode]") { - // auto const block_size = 1024uz; - // GIVEN("an ext2 inode with two direct blocks") - // { - // auto device = kstd::make_shared(0, 0, "mock", block_size, 64 * block_size); - // REQUIRE(device != nullptr); - // kernel::tests::filesystem::ext2::setup_mock_ext2_layout(*device); - - // auto dev_inode = kstd::make_shared(device); - - // auto fs = kernel::filesystem::ext2::filesystem{}; - // REQUIRE(fs.mount(dev_inode) == kernel::filesystem::filesystem::operation_result::success); - - // auto inode_data = kernel::filesystem::ext2::inode_data{}; - // inode_data.size = block_size * 2; - // 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; - - // auto inode = kernel::filesystem::ext2::inode{&fs, 42, inode_data}; - - // THEN("writing over block boundaries updates backing device") - // { - // auto buffer = kstd::vector{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 = inode.write(buffer.data(), block_size - 4, buffer.size()); - // REQUIRE(bytes_written == buffer.size()); - - // auto const text = - // std::string_view{reinterpret_cast(device->data.data() + 21 * block_size - 4), bytes_written}; - // REQUIRE(text == "Hello World!"); - // } - // } + auto const block_size = 1024uz; + GIVEN("an ext2 inode with two direct blocks") + { + auto device = kstd::make_shared(0, 0, "mock", block_size, 64 * 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(device); + + auto block_bitmap = std::vector(block_size, std::byte{0x00}); + block_bitmap[0] = std::byte{0b1111'1111}; + block_bitmap[1] = std::byte{0b1111'1111}; + block_bitmap[2] = std::byte{0b0001'1000}; + + 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) == kernel::filesystem::filesystem::operation_result::success); + + 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 inode = kernel::filesystem::ext2::inode{&fs, inode_number, inode_data}; + dev_inode->write(&inode, inode_number * block_size, sizeof(kernel::filesystem::ext2::inode_data)); + + THEN("writing over block boundaries updates backing device no additional blocks allocated") + { + auto buffer = kstd::vector{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 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(device->data.data() + 21 * block_size - 4), bytes_written}; + REQUIRE(text == "Hello World!"); + + auto read_block_bitmap = std::vector(block_size, std::byte{0x00}); + 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 = kernel::filesystem::ext2::inode{&fs, inode_number, {}}; + dev_inode->read(&read_inode, inode_number * block_size, sizeof(kernel::filesystem::ext2::inode)); + REQUIRE(offset + 12 == 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); + } + } } -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; @@ -394,22 +437,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() == 256); + REQUIRE(inode.size() == expected_initial_size); + + inode.set_size(12); + + 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, 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, 42, data}; - - REQUIRE(inode.size() == 256); + verify_size_and_set_size(kernel::filesystem::ext2::constants::mode_directory, 256, 32); } } @@ -430,22 +480,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() == 0x0000'0020'0000'0100); + REQUIRE(inode.size() == expected_initial_size); + + inode.set_size(12); + + 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, 42, data}; - - REQUIRE(inode.size() == 256); + verify_size_and_set_size(kernel::filesystem::ext2::constants::mode_directory, 256, 32); } } } 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 #include +#include 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 #include #include @@ -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 -- cgit v1.2.3 From 071a6ba4643eccc1ac466ae1e96e8fed6f3e9f9e Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Sat, 30 May 2026 21:48:26 +0200 Subject: fix update block count --- kernel/kernel/filesystem/ext2/filesystem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/kernel/filesystem/ext2/filesystem.cpp b/kernel/kernel/filesystem/ext2/filesystem.cpp index af693230..982505df 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.cpp +++ b/kernel/kernel/filesystem/ext2/filesystem.cpp @@ -392,7 +392,7 @@ namespace kernel::filesystem::ext2 auto filesystem::update_inode_block_count(inode_data & data, uint32_t delta) -> void { - data.blocks = delta * (2 << m_superblock.log_block_size); + data.blocks += delta * (2 << m_superblock.log_block_size); } auto filesystem::block_group_descriptor_table_offset() const -> size_t -- cgit v1.2.3 From c63737704b8e9b275d9f2c09c9c8a2904230846d Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Sat, 30 May 2026 21:48:42 +0200 Subject: add inode tests --- kernel/kernel/filesystem/ext2/filesystem.hpp | 2 +- kernel/kernel/filesystem/ext2/inode.tests.cpp | 77 +++++++++++++++++++++++---- 2 files changed, 69 insertions(+), 10 deletions(-) diff --git a/kernel/kernel/filesystem/ext2/filesystem.hpp b/kernel/kernel/filesystem/ext2/filesystem.hpp index 7361943a..f440d4dc 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.hpp +++ b/kernel/kernel/filesystem/ext2/filesystem.hpp @@ -90,6 +90,7 @@ namespace kernel::filesystem::ext2 auto write_inode(uint32_t inode_number, inode_data const & data) -> kstd::result; + [[nodiscard]] auto inode_size() const -> uint16_t; /** @brief Maps an inode block index to a global block number. @param inode_block_index The index of the block within the inode. @@ -116,7 +117,6 @@ namespace kernel::filesystem::ext2 auto ensure_and_get_block(uint32_t & block_number) -> bool; - [[nodiscard]] auto inode_size() const -> uint16_t; [[nodiscard]] auto block_group_descriptor_table_offset() const -> size_t; [[nodiscard]] auto block_numbers_per_block() const -> size_t; diff --git a/kernel/kernel/filesystem/ext2/inode.tests.cpp b/kernel/kernel/filesystem/ext2/inode.tests.cpp index 752d17d7..90274f6f 100644 --- a/kernel/kernel/filesystem/ext2/inode.tests.cpp +++ b/kernel/kernel/filesystem/ext2/inode.tests.cpp @@ -359,7 +359,7 @@ SCENARIO("Ext2 inode write across block boundaries", "[filesystem][ext2][inode]" auto block_bitmap = std::vector(block_size, std::byte{0x00}); block_bitmap[0] = std::byte{0b1111'1111}; block_bitmap[1] = std::byte{0b1111'1111}; - block_bitmap[2] = std::byte{0b0001'1000}; + block_bitmap[2] = std::byte{0b0011'0000}; dev_inode->write(block_bitmap.data(), block_group_descriptor.block_bitmap * block_size, block_size); @@ -374,15 +374,47 @@ SCENARIO("Ext2 inode write across block boundaries", "[filesystem][ext2][inode]" 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}; - dev_inode->write(&inode, inode_number * block_size, sizeof(kernel::filesystem::ext2::inode_data)); + dev_inode->write(&inode, inode_data_offset, sizeof(kernel::filesystem::ext2::inode_data)); - THEN("writing over block boundaries updates backing device no additional blocks allocated") + auto buffer = kstd::vector{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 do_read_inode = [&]() { + auto inode_data = kernel::filesystem::ext2::inode_data{}; + 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 buffer = kstd::vector{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 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(device->data.data() + 20 * block_size), 3}; + REQUIRE(text == "Hel"); + + auto read_block_bitmap = std::vector(block_size, std::byte{0x00}); + 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 block boundaries updates backing device no additional blocks allocated") + { auto const offset = block_size - 4; auto const bytes_written = inode.write(buffer.data(), offset, buffer.size()); REQUIRE(bytes_written == buffer.size()); @@ -395,15 +427,42 @@ SCENARIO("Ext2 inode write across block boundaries", "[filesystem][ext2][inode]" 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 = kernel::filesystem::ext2::inode{&fs, inode_number, {}}; - dev_inode->read(&read_inode, inode_number * block_size, sizeof(kernel::filesystem::ext2::inode)); - REQUIRE(offset + 12 == inode.size()); + 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 backing device 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(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(read_buffer.data()), bytes_read}; + REQUIRE(text == "Hello World!"); + + auto read_block_bitmap = std::vector(block_size, std::byte{0x00}); + 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); + } } } -- cgit v1.2.3 From c26d681e88905726d6521b4fbfaeb63603db8faf Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Sat, 30 May 2026 22:04:08 +0200 Subject: add inode write_global_block_number_to_inode_block_index tests --- kernel/kernel/filesystem/ext2/filesystem.cpp | 108 +++++++++++++++++++++ kernel/kernel/filesystem/ext2/filesystem.hpp | 5 +- kernel/kernel/filesystem/ext2/filesystem.tests.cpp | 90 +++++++++++++++++ kernel/kernel/filesystem/ext2/inode.cpp | 7 +- kernel/kernel/filesystem/ext2/inode.tests.cpp | 18 ++-- 5 files changed, 216 insertions(+), 12 deletions(-) diff --git a/kernel/kernel/filesystem/ext2/filesystem.cpp b/kernel/kernel/filesystem/ext2/filesystem.cpp index 982505df..3aa07945 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.cpp +++ b/kernel/kernel/filesystem/ext2/filesystem.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -350,6 +351,113 @@ 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 + { + 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; + + auto const levels = indirect_levels(); + for (auto const & level : 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::ensure_and_get_block(uint32_t block_number) -> std::optional + { + if (block_number != 0) + { + return block_number; + } + + if (auto allocated = allocate_blocks(1)) + { + return allocated->front(); + } + + return std::nullopt; + } + + auto filesystem::read_block(uint32_t block_number, void * buffer) const -> kstd::result + { + auto const block_offset = static_cast(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 + { + auto const block_offset = static_cast(block_number) * block_size(); + return m_backing_inode->write(buffer, block_offset, block_size()); + } + auto filesystem::block_numbers_per_block() const -> size_t { return block_size() / sizeof(uint32_t); diff --git a/kernel/kernel/filesystem/ext2/filesystem.hpp b/kernel/kernel/filesystem/ext2/filesystem.hpp index f440d4dc..c6320ee2 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.hpp +++ b/kernel/kernel/filesystem/ext2/filesystem.hpp @@ -15,6 +15,7 @@ #include #include #include +#include #include namespace kernel::filesystem::ext2 @@ -101,7 +102,7 @@ namespace kernel::filesystem::ext2 -> kstd::result; auto write_global_block_number_to_inode_block_index(size_t block_index, inode_data & data, - uint32_t global_block_number) -> void; + uint32_t global_block_number) -> kstd::result; private: struct indirect_level @@ -115,7 +116,7 @@ namespace kernel::filesystem::ext2 [[nodiscard]] auto read_inode(uint32_t inode_number) const -> kstd::result>; [[nodiscard]] auto read_block_number_at_index(uint32_t block_number, size_t index) const -> kstd::result; - auto ensure_and_get_block(uint32_t & block_number) -> bool; + auto ensure_and_get_block(uint32_t block_number) -> std::optional; [[nodiscard]] auto block_group_descriptor_table_offset() const -> size_t; diff --git a/kernel/kernel/filesystem/ext2/filesystem.tests.cpp b/kernel/kernel/filesystem/ext2/filesystem.tests.cpp index 1702526e..62fc14c1 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.tests.cpp +++ b/kernel/kernel/filesystem/ext2/filesystem.tests.cpp @@ -2,8 +2,10 @@ #include #include +#include #include #include +#include #include #include #include @@ -15,8 +17,11 @@ #include #include +#include #include +#include #include +#include SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, "Ext2 filesystem mount and lookup with real image", "[filesystem][ext2][filesystem][img]") @@ -138,3 +143,88 @@ 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(0, 0, "mock", block_size, 256 * 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 = 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(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(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(block_size / sizeof(uint32_t)); + auto const singly_start = static_cast(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(inode_data.block[12]) * block_size) == singly_global_block_number); + + 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(inode_data.block[13]) * block_size); + REQUIRE(doubly_leaf_table == 18); + REQUIRE(read_u32(static_cast(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(inode_data.block[14]) * block_size); + REQUIRE(triply_middle_table == 20); + auto const triply_leaf_table = read_u32(static_cast(triply_middle_table) * block_size); + REQUIRE(triply_leaf_table == 21); + REQUIRE(read_u32(static_cast(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 83db6ec6..f95ddaad 100644 --- a/kernel/kernel/filesystem/ext2/inode.cpp +++ b/kernel/kernel/filesystem/ext2/inode.cpp @@ -159,7 +159,12 @@ namespace kernel::filesystem::ext2 auto const block_index = m_filesystem->inode_block_count(m_data) + i; auto const global_block_number = new_blocks->at(i); - m_filesystem->write_global_block_number_to_inode_block_index(block_index, m_data, global_block_number); + 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); diff --git a/kernel/kernel/filesystem/ext2/inode.tests.cpp b/kernel/kernel/filesystem/ext2/inode.tests.cpp index 90274f6f..35f54fea 100644 --- a/kernel/kernel/filesystem/ext2/inode.tests.cpp +++ b/kernel/kernel/filesystem/ext2/inode.tests.cpp @@ -361,10 +361,10 @@ SCENARIO("Ext2 inode write across block boundaries", "[filesystem][ext2][inode]" block_bitmap[1] = std::byte{0b1111'1111}; block_bitmap[2] = std::byte{0b0011'0000}; - dev_inode->write(block_bitmap.data(), block_group_descriptor.block_bitmap * block_size, block_size); + 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) == kernel::filesystem::filesystem::operation_result::success); + REQUIRE(fs.mount(dev_inode)); auto inode_data = kernel::filesystem::ext2::inode_data{}; inode_data.size = 5; @@ -378,7 +378,7 @@ SCENARIO("Ext2 inode write across block boundaries", "[filesystem][ext2][inode]" block_group_descriptor.inode_table * block_size + (inode_number - 1) * fs.inode_size(); auto inode = kernel::filesystem::ext2::inode{&fs, inode_number, inode_data}; - dev_inode->write(&inode, inode_data_offset, sizeof(kernel::filesystem::ext2::inode_data)); + REQUIRE(dev_inode->write(&inode, inode_data_offset, sizeof(kernel::filesystem::ext2::inode_data))); auto buffer = kstd::vector{std::byte{'H'}, std::byte{'e'}, std::byte{'l'}, std::byte{'l'}, std::byte{'o'}, std::byte{' '}, std::byte{'W'}, std::byte{'o'}, @@ -386,7 +386,7 @@ SCENARIO("Ext2 inode write across block boundaries", "[filesystem][ext2][inode]" auto do_read_inode = [&]() { auto inode_data = kernel::filesystem::ext2::inode_data{}; - dev_inode->read(&inode_data, inode_data_offset, sizeof(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; }; @@ -401,7 +401,7 @@ SCENARIO("Ext2 inode write across block boundaries", "[filesystem][ext2][inode]" REQUIRE(text == "Hel"); auto read_block_bitmap = std::vector(block_size, std::byte{0x00}); - dev_inode->read(read_block_bitmap.data(), block_group_descriptor.block_bitmap * block_size, block_size); + 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(); @@ -420,11 +420,11 @@ SCENARIO("Ext2 inode write across block boundaries", "[filesystem][ext2][inode]" REQUIRE(bytes_written == buffer.size()); auto const text = - std::string_view{reinterpret_cast(device->data.data() + 21 * block_size - 4), bytes_written}; + std::string_view{reinterpret_cast(device->data.data() + 21 * block_size - 4), *bytes_written}; REQUIRE(text == "Hello World!"); auto read_block_bitmap = std::vector(block_size, std::byte{0x00}); - dev_inode->read(read_block_bitmap.data(), block_group_descriptor.block_bitmap * block_size, block_size); + 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(); @@ -444,11 +444,11 @@ SCENARIO("Ext2 inode write across block boundaries", "[filesystem][ext2][inode]" auto read_buffer = std::vector(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(read_buffer.data()), bytes_read}; + auto const text = std::string_view{reinterpret_cast(read_buffer.data()), *bytes_read}; REQUIRE(text == "Hello World!"); auto read_block_bitmap = std::vector(block_size, std::byte{0x00}); - dev_inode->read(read_block_bitmap.data(), block_group_descriptor.block_bitmap * block_size, block_size); + 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}; -- cgit v1.2.3 From 08e84e373004e46ed00173b63ba16be123faf4b8 Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Sat, 30 May 2026 22:34:09 +0200 Subject: add another ext2 inode tests, it panics if not enough free blocks available --- kernel/kernel/filesystem/ext2/inode.tests.cpp | 86 ++++++++++++++------------- 1 file changed, 46 insertions(+), 40 deletions(-) diff --git a/kernel/kernel/filesystem/ext2/inode.tests.cpp b/kernel/kernel/filesystem/ext2/inode.tests.cpp index 35f54fea..7b00dae1 100644 --- a/kernel/kernel/filesystem/ext2/inode.tests.cpp +++ b/kernel/kernel/filesystem/ext2/inode.tests.cpp @@ -332,58 +332,58 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, "Ext2 in SCENARIO("Ext2 inode write across block boundaries", "[filesystem][ext2][inode]") { auto const block_size = 1024uz; - GIVEN("an ext2 inode with two direct blocks") - { - auto device = kstd::make_shared(0, 0, "mock", block_size, 64 * 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 device = kstd::make_shared(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; + 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); + kernel::tests::filesystem::ext2::setup_mock_ext2_layout(*device, superblock, block_group_descriptor); - auto dev_inode = kstd::make_shared(device); + auto dev_inode = kstd::make_shared(device); - auto block_bitmap = std::vector(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}; + auto block_bitmap = std::vector(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)); + 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 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; + 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(); + 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 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 buffer = kstd::vector{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 buffer = kstd::vector{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))); @@ -463,6 +463,12 @@ SCENARIO("Ext2 inode write across block boundaries", "[filesystem][ext2][inode]" 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 const offset = 65 * block_size; + REQUIRE_THROWS_AS(inode.write(buffer.data(), offset, buffer.size()), kernel::tests::cpu::halt); + } } } -- cgit v1.2.3 From efdb44b9ca98ccea925a4fbf51b0b1a9960570b2 Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Sat, 30 May 2026 22:47:16 +0200 Subject: add inode write tests with real filesystem image --- kernel/kernel/filesystem/ext2/inode.tests.cpp | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/kernel/kernel/filesystem/ext2/inode.tests.cpp b/kernel/kernel/filesystem/ext2/inode.tests.cpp index 7b00dae1..8341dbbf 100644 --- a/kernel/kernel/filesystem/ext2/inode.tests.cpp +++ b/kernel/kernel/filesystem/ext2/inode.tests.cpp @@ -303,7 +303,7 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, "Ext2 in REQUIRE(file); REQUIRE(file.value()->is_regular()); - THEN("writing to the inode updates backing device") + THEN("writing to the inode updates backing device and the inode size") { auto write_buffer = kstd::vector{std::byte{'H'}, std::byte{'e'}, std::byte{'l'}, std::byte{'l'}, std::byte{'o'}, std::byte{' '}, std::byte{'W'}, std::byte{'o'}, @@ -318,6 +318,31 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, "Ext2 in auto const text = std::string_view{reinterpret_cast(read_buffer.data()), *bytes_read}; REQUIRE(text == "Hello World!"); + + auto inode_data = static_cast(file.get())->data(); + REQUIRE(inode_data.size == bytes_read); + } + + THEN("writing allocates new blocks and updates the inode size") + { + auto inode_data = static_cast(file.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(block_size * expected_allocated_blocks, std::byte{'A'}); + + auto const bytes_written = file->write(write_buffer.data(), 0, write_buffer.size()); + REQUIRE(bytes_written == block_size * expected_allocated_blocks); + + auto read_buffer = kstd::vector(block_size * expected_allocated_blocks, std::byte{0x00}); + auto const bytes_read = file->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(file.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") -- cgit v1.2.3 From 34079d19ab57a38dad67bcd001ff2467e7bd9e99 Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Sun, 31 May 2026 13:49:35 +0200 Subject: refactoring --- kernel/kernel/filesystem/ext2/filesystem.cpp | 1 + kernel/kernel/filesystem/ext2/inode.tests.cpp | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/kernel/kernel/filesystem/ext2/filesystem.cpp b/kernel/kernel/filesystem/ext2/filesystem.cpp index 3aa07945..f29b3868 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.cpp +++ b/kernel/kernel/filesystem/ext2/filesystem.cpp @@ -507,4 +507,5 @@ namespace kernel::filesystem::ext2 { return block_size() == 1024 ? 2 * block_size() : block_size(); } + } // namespace kernel::filesystem::ext2 diff --git a/kernel/kernel/filesystem/ext2/inode.tests.cpp b/kernel/kernel/filesystem/ext2/inode.tests.cpp index 8341dbbf..4d5038f7 100644 --- a/kernel/kernel/filesystem/ext2/inode.tests.cpp +++ b/kernel/kernel/filesystem/ext2/inode.tests.cpp @@ -319,28 +319,28 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, "Ext2 in auto const text = std::string_view{reinterpret_cast(read_buffer.data()), *bytes_read}; REQUIRE(text == "Hello World!"); - auto inode_data = static_cast(file.get())->data(); + auto inode_data = static_cast(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(file.get())->data(); + auto inode_data = static_cast(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(block_size * expected_allocated_blocks, std::byte{'A'}); - auto const bytes_written = file->write(write_buffer.data(), 0, write_buffer.size()); + 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(block_size * expected_allocated_blocks, std::byte{0x00}); - auto const bytes_read = file->read(read_buffer.data(), 0, read_buffer.size()); + 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(file.get())->data(); + auto new_inode_data = static_cast(file.value().get())->data(); REQUIRE(new_inode_data.size == write_buffer.size()); REQUIRE(fs.inode_block_count(new_inode_data) == expected_allocated_blocks); } -- cgit v1.2.3 From 6d3f75685368c62037006e426c89a932072fc0e9 Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Sun, 31 May 2026 14:31:44 +0200 Subject: improve tests --- kernel/kernel/filesystem/ext2/filesystem.tests.cpp | 5 +++++ kernel/kernel/filesystem/ext2/inode.tests.cpp | 5 +++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/kernel/kernel/filesystem/ext2/filesystem.tests.cpp b/kernel/kernel/filesystem/ext2/filesystem.tests.cpp index 62fc14c1..2528db4b 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.tests.cpp +++ b/kernel/kernel/filesystem/ext2/filesystem.tests.cpp @@ -207,6 +207,11 @@ SCENARIO("Ext2 block writing includes direct and all indirect levels", "[filesys REQUIRE(inode_data.block[12] == 16); REQUIRE(read_u32(static_cast(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(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(inode_data.block[13]) * block_size); diff --git a/kernel/kernel/filesystem/ext2/inode.tests.cpp b/kernel/kernel/filesystem/ext2/inode.tests.cpp index 4d5038f7..7e293d14 100644 --- a/kernel/kernel/filesystem/ext2/inode.tests.cpp +++ b/kernel/kernel/filesystem/ext2/inode.tests.cpp @@ -438,7 +438,8 @@ SCENARIO("Ext2 inode write across block boundaries", "[filesystem][ext2][inode]" REQUIRE(current_blocks == read_blocks); } - THEN("writing over block boundaries updates backing device no additional blocks allocated") + 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()); @@ -461,7 +462,7 @@ SCENARIO("Ext2 inode write across block boundaries", "[filesystem][ext2][inode]" REQUIRE(current_blocks == read_blocks); } - THEN("writing over the current size and over allocated blocks updates backing device and allocates a new block") + 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()); -- cgit v1.2.3 From 10792616c5087fb781ad907c7edab9bfd6eb3d1a Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Sun, 31 May 2026 14:37:20 +0200 Subject: add comments --- kernel/kernel/filesystem/ext2/filesystem.hpp | 45 +++++++++++++++++++++++++++- kernel/kernel/filesystem/ext2/inode.hpp | 4 +++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/kernel/kernel/filesystem/ext2/filesystem.hpp b/kernel/kernel/filesystem/ext2/filesystem.hpp index c6320ee2..b514938c 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.hpp +++ b/kernel/kernel/filesystem/ext2/filesystem.hpp @@ -79,19 +79,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; + /** + @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; + /** + @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>; + /** + @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; - [[nodiscard]] auto inode_size() const -> uint16_t; /** @brief Maps an inode block index to a global block number. @param inode_block_index The index of the block within the inode. @@ -101,6 +138,12 @@ 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; + /** + @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; diff --git a/kernel/kernel/filesystem/ext2/inode.hpp b/kernel/kernel/filesystem/ext2/inode.hpp index 9c5f9beb..f01b9f1e 100644 --- a/kernel/kernel/filesystem/ext2/inode.hpp +++ b/kernel/kernel/filesystem/ext2/inode.hpp @@ -97,6 +97,10 @@ 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; private: -- cgit v1.2.3 From b388d3187da121304a9e9d4a2b398117b8bfd5e9 Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Sun, 31 May 2026 15:06:51 +0200 Subject: refactoring --- kernel/kernel/filesystem/ext2/filesystem.cpp | 43 ++++++++++++++++------------ kernel/kernel/filesystem/ext2/filesystem.hpp | 1 + 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/kernel/kernel/filesystem/ext2/filesystem.cpp b/kernel/kernel/filesystem/ext2/filesystem.cpp index f29b3868..0f0d0eb3 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.cpp +++ b/kernel/kernel/filesystem/ext2/filesystem.cpp @@ -155,32 +155,39 @@ namespace kernel::filesystem::ext2 auto filesystem::read_inode(uint32_t inode_number) const -> kstd::result> { - 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; + if (auto inode_offset = calculate_inode_offset(inode_number)) + { + auto new_inode_data = inode_data{}; - if (block_group_index >= m_block_group_descriptors.size()) + 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(const_cast(this), inode_number, new_inode_data); + } + } + else { - return kstd::failure(ext2_errc::invalid_block_group_index); + return kstd::failure(inode_offset.error()); } + } - auto const & block_group_descriptor = m_block_group_descriptors.at(block_group_index); - auto const inode_table_start_block = block_group_descriptor.inode_table; - auto const inode_table_offset = static_cast(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) + auto filesystem::write_inode(uint32_t inode_number, inode_data const & data) -> kstd::result + { + if (auto inode_offset = calculate_inode_offset(inode_number)) { - return kstd::failure(read_result.error()); + return m_backing_inode->write(&data, *inode_offset, sizeof(inode_data)).transform([](auto) {}); + } + else + { + return kstd::failure(inode_offset.error()); } - - return kstd::make_shared(const_cast(this), inode_number, new_inode_data); } - auto filesystem::write_inode(uint32_t inode_number, inode_data const & data) -> kstd::result + auto filesystem::calculate_inode_offset(uint32_t inode_number) const -> kstd::result { - // TODO BA-FS26 refactoring 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; @@ -195,7 +202,7 @@ namespace kernel::filesystem::ext2 auto const inode_table_offset = static_cast(inode_table_start_block) * block_size(); auto const inode_offset = inode_table_offset + inode_index_within_group * inode_size(); - return m_backing_inode->write(&data, inode_offset, sizeof(inode_data)).transform([](auto) {}); + return inode_offset; } auto filesystem::allocate_blocks(size_t count) -> kstd::result> diff --git a/kernel/kernel/filesystem/ext2/filesystem.hpp b/kernel/kernel/filesystem/ext2/filesystem.hpp index b514938c..6af49473 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.hpp +++ b/kernel/kernel/filesystem/ext2/filesystem.hpp @@ -156,6 +156,7 @@ namespace kernel::filesystem::ext2 [[nodiscard]] auto indirect_levels() const -> std::array; + [[nodiscard]] auto calculate_inode_offset(uint32_t inode_number) const -> kstd::result; [[nodiscard]] auto read_inode(uint32_t inode_number) const -> kstd::result>; [[nodiscard]] auto read_block_number_at_index(uint32_t block_number, size_t index) const -> kstd::result; -- cgit v1.2.3 From 7fcd38e81a5dd6abe32a3329ce8f8e475eb51142 Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Sun, 31 May 2026 16:07:28 +0200 Subject: small refactoring --- kernel/kernel/filesystem/ext2/filesystem.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/kernel/kernel/filesystem/ext2/filesystem.cpp b/kernel/kernel/filesystem/ext2/filesystem.cpp index 0f0d0eb3..a68f7930 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.cpp +++ b/kernel/kernel/filesystem/ext2/filesystem.cpp @@ -368,8 +368,7 @@ namespace kernel::filesystem::ext2 } inode_block_index -= constants::direct_block_count; - auto const levels = indirect_levels(); - for (auto const & level : levels) + for (auto const & level : indirect_levels()) { if (inode_block_index >= level.capacity) { -- cgit v1.2.3 From 2574721fa34f34d0d4f0909cedf006f77d82734c Mon Sep 17 00:00:00 2001 From: Marcel Braun Date: Sun, 7 Jun 2026 14:12:56 +0200 Subject: Implement inode allocation in ext2 --- kernel/kernel/filesystem/ext2/error.hpp | 4 ++ kernel/kernel/filesystem/ext2/filesystem.cpp | 78 ++++++++++++++++++++++++++-- kernel/kernel/filesystem/ext2/filesystem.hpp | 10 ++++ 3 files changed, 89 insertions(+), 3 deletions(-) diff --git a/kernel/kernel/filesystem/ext2/error.hpp b/kernel/kernel/filesystem/ext2/error.hpp index 986ab167..7de3b24b 100644 --- a/kernel/kernel/filesystem/ext2/error.hpp +++ b/kernel/kernel/filesystem/ext2/error.hpp @@ -21,6 +21,7 @@ namespace kernel::filesystem::ext2 failed_to_read_superblock, failed_to_read_block_group_descriptors, not_enough_free_blocks, + not_enough_inodes }; namespace detail @@ -52,6 +53,8 @@ namespace kernel::filesystem::ext2 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"; }; @@ -84,6 +87,7 @@ namespace kernel::filesystem::ext2 } 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(kstd::errc::no_space_on_device); diff --git a/kernel/kernel/filesystem/ext2/filesystem.cpp b/kernel/kernel/filesystem/ext2/filesystem.cpp index a68f7930..8860f213 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.cpp +++ b/kernel/kernel/filesystem/ext2/filesystem.cpp @@ -223,15 +223,14 @@ namespace kernel::filesystem::ext2 continue; } - kstd::vector block_bitmap{}; - block_bitmap.resize(block_size(), 0); + kstd::vector 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 < block_bitmap.size() * 8 && allocated_blocks.size() < count; ++i) + for (auto i = 0uz; i < static_cast(m_superblock.blocks_per_group) && allocated_blocks.size() < count; ++i) { if (!bitmap_is_set(block_bitmap, i)) { @@ -279,6 +278,61 @@ namespace kernel::filesystem::ext2 return allocated_blocks; } + auto filesystem::allocate_inode() -> kstd::result + { + 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 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(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::indirect_levels() const -> std::array { return { @@ -464,6 +518,24 @@ namespace kernel::filesystem::ext2 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 + { + // 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 + { + // TODO update all superblocks + return m_backing_inode->write(&m_superblock, constants::superblock_offset, sizeof(m_superblock)) + .transform([](auto) {}); + } + auto filesystem::block_numbers_per_block() const -> size_t { return block_size() / sizeof(uint32_t); diff --git a/kernel/kernel/filesystem/ext2/filesystem.hpp b/kernel/kernel/filesystem/ext2/filesystem.hpp index 6af49473..33b7b95c 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.hpp +++ b/kernel/kernel/filesystem/ext2/filesystem.hpp @@ -102,6 +102,12 @@ namespace kernel::filesystem::ext2 */ auto allocate_blocks(size_t count) -> kstd::result>; + /** + @brief Allocates a new inode. + @return The inode number within the filesystem. + */ + auto allocate_inode() -> kstd::result; + /** @brief Gets the size of an inode in the filesystem. @return The size of an inode in bytes. @@ -169,6 +175,10 @@ 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; + [[nodiscard]] auto write_superblock() const -> kstd::result; + superblock m_superblock{}; kstd::vector m_block_group_descriptors; }; -- cgit v1.2.3 From 1db4c39c28c58c01ae13e15be4849c1fc9e9efff Mon Sep 17 00:00:00 2001 From: Marcel Braun Date: Sun, 7 Jun 2026 18:47:00 +0200 Subject: Save wip of create_inode --- kernel/kernel/filesystem/ext2/filesystem.cpp | 87 ++++++++++++++++++++++++++++ kernel/kernel/filesystem/ext2/filesystem.hpp | 19 ++++-- 2 files changed, 100 insertions(+), 6 deletions(-) diff --git a/kernel/kernel/filesystem/ext2/filesystem.cpp b/kernel/kernel/filesystem/ext2/filesystem.cpp index 8860f213..7b0bce0e 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.cpp +++ b/kernel/kernel/filesystem/ext2/filesystem.cpp @@ -153,6 +153,93 @@ namespace kernel::filesystem::ext2 return kstd::failure(vfs_errc::no_such_file_or_directory); } + auto filesystem::create_inode(kstd::shared_ptr const & parent, std::string_view name) + -> kstd::result> + { + if (!parent) + { + return kstd::failure(vfs_errc::invalid_inode); + } + + if (!parent->is_directory()) + { + return kstd::failure(vfs_errc::not_a_directory); + } + + 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{}; + + // TODO set mode depending on type/mode + new_inode_data.mode = 0x8000; + 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); + + if (auto write_result = write_inode(inode_number, new_inode_data); !write_result) + { + return kstd::failure(write_result.error()); + } + + // add directory entry to parent + if (auto * ext2_parent = static_cast(parent.get())) + { + if (auto result = add_directory_entry(ext2_parent, name, inode_number, new_inode_data.mode); + !result) // TODO handle mode correctly + { + return kstd::failure(result.error()); + } + // TODO + } + } + + auto filesystem::add_directory_entry(inode * ext2_parent, std::string_view name, uint32_t inode_number, uint16_t mode) + -> kstd::result + { + auto block_count = inode_block_count(ext2_parent->data()); + auto const last_block_index = block_count - 1; + auto const global_block_number = + map_inode_block_index_to_global_block_number(last_block_index, ext2_parent->data()); + + if (!global_block_number) + { + return kstd::failure(global_block_number.error()); + } + + kstd::vector buffer(block_size()); + if (auto read_result = read_block(*global_block_number, buffer.data()); !read_result) + { + return kstd::failure(read_result.error()); + } + + auto offset = 0uz; + while (true) + { + auto const * entry = reinterpret_cast(buffer.data() + offset); + + if (offset + entry->rec_len >= block_size()) + { + break; + } + + offset += entry->rec_len; + } + + auto const * last_entry = reinterpret_cast(buffer.data() + offset); + + auto const last_entry_actual_length = 0; // TODO + + return kstd::success(); + } + auto filesystem::read_inode(uint32_t inode_number) const -> kstd::result> { if (auto inode_offset = calculate_inode_offset(inode_number)) diff --git a/kernel/kernel/filesystem/ext2/filesystem.hpp b/kernel/kernel/filesystem/ext2/filesystem.hpp index 33b7b95c..eaa42d01 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.hpp +++ b/kernel/kernel/filesystem/ext2/filesystem.hpp @@ -67,6 +67,15 @@ namespace kernel::filesystem::ext2 [[nodiscard]] auto lookup(kstd::shared_ptr const & parent, std::string_view name) const -> kstd::result> 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. + @return A pointer to the created inode, or a null pointer if creation failed. + */ + [[nodiscard]] auto create_inode(kstd::shared_ptr const & parent, std::string_view name) + -> kstd::result>; // TODO move into base class and override + /** @brief Gets the size of a block in the filesystem. @return The size of a block in bytes. @@ -102,12 +111,6 @@ namespace kernel::filesystem::ext2 */ auto allocate_blocks(size_t count) -> kstd::result>; - /** - @brief Allocates a new inode. - @return The inode number within the filesystem. - */ - auto allocate_inode() -> kstd::result; - /** @brief Gets the size of an inode in the filesystem. @return The size of an inode in bytes. @@ -162,6 +165,10 @@ namespace kernel::filesystem::ext2 [[nodiscard]] auto indirect_levels() const -> std::array; + auto allocate_inode() -> kstd::result; + auto add_directory_entry(inode * ext2_parent, std::string_view name, uint32_t inode_number, uint16_t mode) + -> kstd::result; + [[nodiscard]] auto calculate_inode_offset(uint32_t inode_number) const -> kstd::result; [[nodiscard]] auto read_inode(uint32_t inode_number) const -> kstd::result>; [[nodiscard]] auto read_block_number_at_index(uint32_t block_number, size_t index) const -> kstd::result; -- cgit v1.2.3 From 660e23151774411378799afb1a519f420ccda567 Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Sun, 7 Jun 2026 19:14:07 +0200 Subject: finish implementation of create_inode as first draft --- kernel/kernel/filesystem/ext2/filesystem.cpp | 62 ++++++++++++++++++++++++---- kernel/kernel/filesystem/ext2/filesystem.hpp | 2 +- kernel/kernel/filesystem/ext2/inode.cpp | 5 +++ kernel/kernel/filesystem/ext2/inode.hpp | 15 ++++++- 4 files changed, 74 insertions(+), 10 deletions(-) diff --git a/kernel/kernel/filesystem/ext2/filesystem.cpp b/kernel/kernel/filesystem/ext2/filesystem.cpp index 7b0bce0e..89f25d42 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.cpp +++ b/kernel/kernel/filesystem/ext2/filesystem.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -201,11 +202,10 @@ namespace kernel::filesystem::ext2 } } - auto filesystem::add_directory_entry(inode * ext2_parent, std::string_view name, uint32_t inode_number, uint16_t mode) - -> kstd::result + auto filesystem::add_directory_entry(inode * ext2_parent, std::string_view name, uint32_t child_inode_number, + [[maybe_unused]] uint16_t mode) -> kstd::result { - auto block_count = inode_block_count(ext2_parent->data()); - auto const last_block_index = block_count - 1; + auto const last_block_index = inode_block_count(ext2_parent->data()) - 1; auto const global_block_number = map_inode_block_index_to_global_block_number(last_block_index, ext2_parent->data()); @@ -233,11 +233,59 @@ namespace kernel::filesystem::ext2 offset += entry->rec_len; } - auto const * last_entry = reinterpret_cast(buffer.data() + offset); + // auto const * last_entry = reinterpret_cast(buffer.data() + offset); - auto const last_entry_actual_length = 0; // TODO + // auto const last_entry_actual_len = static_cast((8u + last_entry->name_len + 3u) & ~3u); + // auto const free_space = last_entry->rec_len - last_entry_actual_len; - return kstd::success(); + // auto const last_entry_actual_length = 0; // TODO + auto const name_len = static_cast(name.size()); + auto const needed_rec_len = static_cast((8u + name_len + 3u) & ~3u); + + auto * last_entry = reinterpret_cast(buffer.data() + offset); + auto const last_entry_actual_len = static_cast((8u + last_entry->name_len + 3u) & ~3u); + auto const free_space = last_entry->rec_len - last_entry_actual_len; + + if (free_space >= needed_rec_len) + { + last_entry->rec_len = last_entry_actual_len; + offset += last_entry_actual_len; + + auto * new_entry = reinterpret_cast(buffer.data() + offset); + new_entry->inode = child_inode_number; + new_entry->rec_len = static_cast(block_size() - offset); + new_entry->name_len = name_len; + new_entry->file_type = 0x01; // TODO handle mode correctly + kstd::libc::memcpy(new_entry->name.data(), name.data(), name_len); + + return write_block(*global_block_number, buffer.data()).transform([](auto) {}); + } + + if (!ext2_parent->append_blocks(1)) + { + return kstd::failure(ext2_errc::not_enough_free_blocks); + } + + auto new_last_block_index = inode_block_count(ext2_parent->data()) - 1; + + auto new_global_block = map_inode_block_index_to_global_block_number(new_last_block_index, ext2_parent->data()); + if (!new_global_block) + { + return kstd::failure(new_global_block.error()); + } + + kstd::libc::memset(buffer.data(), 0, block_size()); + + auto * entry = reinterpret_cast(buffer.data()); + entry->inode = child_inode_number; + entry->rec_len = static_cast(block_size()); + entry->name_len = name_len; + entry->file_type = 0x01; // TODO handle mode correctly + kstd::libc::memcpy(entry->name.data(), name.data(), name_len); + + return write_block(static_cast(*new_global_block), buffer.data()).and_then([&](auto) { + return write_inode(ext2_parent->number(), ext2_parent->data()); + }); } auto filesystem::read_inode(uint32_t inode_number) const -> kstd::result> diff --git a/kernel/kernel/filesystem/ext2/filesystem.hpp b/kernel/kernel/filesystem/ext2/filesystem.hpp index eaa42d01..b3279e09 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.hpp +++ b/kernel/kernel/filesystem/ext2/filesystem.hpp @@ -166,7 +166,7 @@ namespace kernel::filesystem::ext2 [[nodiscard]] auto indirect_levels() const -> std::array; auto allocate_inode() -> kstd::result; - auto add_directory_entry(inode * ext2_parent, std::string_view name, uint32_t inode_number, uint16_t mode) + auto add_directory_entry(inode * ext2_parent, std::string_view name, uint32_t child_inode_number, uint16_t mode) -> kstd::result; [[nodiscard]] auto calculate_inode_offset(uint32_t inode_number) const -> kstd::result; diff --git a/kernel/kernel/filesystem/ext2/inode.cpp b/kernel/kernel/filesystem/ext2/inode.cpp index f95ddaad..0c8da9d0 100644 --- a/kernel/kernel/filesystem/ext2/inode.cpp +++ b/kernel/kernel/filesystem/ext2/inode.cpp @@ -212,4 +212,9 @@ namespace kernel::filesystem::ext2 m_data.size = static_cast(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 f01b9f1e..ac17e6ab 100644 --- a/kernel/kernel/filesystem/ext2/inode.hpp +++ b/kernel/kernel/filesystem/ext2/inode.hpp @@ -67,6 +67,13 @@ namespace kernel::filesystem::ext2 */ auto write(void const * buffer, size_t offset, size_t size) -> kstd::result 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. @@ -103,9 +110,13 @@ namespace kernel::filesystem::ext2 */ auto set_size(uint64_t new_size) -> void; - private: - auto append_blocks(size_t count) -> bool; + /** + @brief Get the inode number of this inode. + @return The inode number. + */ + [[nodiscard]] auto number() const -> uint32_t; + private: filesystem * m_filesystem; uint32_t m_inode_number{}; inode_data m_data{}; -- cgit v1.2.3 From b37c7a9203a669feb7eec3b694e1e6285a68b6ea Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Sun, 7 Jun 2026 21:11:00 +0200 Subject: first draft implementing init_directory --- kernel/kernel/filesystem/ext2/filesystem.cpp | 134 +++++++++++++++++++-------- kernel/kernel/filesystem/ext2/filesystem.hpp | 6 ++ kernel/kernel/filesystem/ext2/inode.cpp | 5 + kernel/kernel/filesystem/ext2/inode.hpp | 8 +- 4 files changed, 113 insertions(+), 40 deletions(-) diff --git a/kernel/kernel/filesystem/ext2/filesystem.cpp b/kernel/kernel/filesystem/ext2/filesystem.cpp index 89f25d42..bd1b99a2 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.cpp +++ b/kernel/kernel/filesystem/ext2/filesystem.cpp @@ -167,6 +167,12 @@ namespace kernel::filesystem::ext2 return kstd::failure(vfs_errc::not_a_directory); } + auto * ext2_parent = static_cast(parent.get()); + if (!ext2_parent) + { + return nullptr; + } + auto maybe_inode_number = allocate_inode(); if (!maybe_inode_number) { @@ -185,80 +191,85 @@ namespace kernel::filesystem::ext2 new_inode_data.blocks = 0; new_inode_data.block.fill(0); - if (auto write_result = write_inode(inode_number, new_inode_data); !write_result) + // 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(write_result.error()); + return kstd::failure(result.error()); } - // add directory entry to parent - if (auto * ext2_parent = static_cast(parent.get())) + auto new_inode = kstd::make_shared(this, inode_number, new_inode_data); + + bool is_directory = false; // TODO refactor + if (is_directory) { - if (auto result = add_directory_entry(ext2_parent, name, inode_number, new_inode_data.mode); - !result) // TODO handle mode correctly + if (auto result = init_directory(new_inode, ext2_parent->number()); !result) { return kstd::failure(result.error()); } - // TODO + + // '..' inside the new dir counts as an extra hard link to the parent + auto & parent_inode_data = ext2_parent->data_mutable(); + parent_inode_data.links_count++; + + if (auto write_result = write_inode(ext2_parent->number(), parent_inode_data); !write_result) + { + return kstd::failure(write_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 { auto const last_block_index = inode_block_count(ext2_parent->data()) - 1; - auto const global_block_number = - map_inode_block_index_to_global_block_number(last_block_index, ext2_parent->data()); + auto const global_block = map_inode_block_index_to_global_block_number(last_block_index, ext2_parent->data()); - if (!global_block_number) + if (!global_block) { - return kstd::failure(global_block_number.error()); + return kstd::failure(global_block.error()); } kstd::vector buffer(block_size()); - if (auto read_result = read_block(*global_block_number, buffer.data()); !read_result) + 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(buffer.data() + offset); - if (offset + entry->rec_len >= block_size()) { break; } - offset += entry->rec_len; } - // auto const * last_entry = reinterpret_cast(buffer.data() + offset); - - // auto const last_entry_actual_len = static_cast((8u + last_entry->name_len + 3u) & ~3u); - // auto const free_space = last_entry->rec_len - last_entry_actual_len; - - // auto const last_entry_actual_length = 0; // TODO + auto const file_type = map_inode_mode_to_file_type(mode); auto const name_len = static_cast(name.size()); auto const needed_rec_len = static_cast((8u + name_len + 3u) & ~3u); auto * last_entry = reinterpret_cast(buffer.data() + offset); auto const last_entry_actual_len = static_cast((8u + last_entry->name_len + 3u) & ~3u); - auto const free_space = last_entry->rec_len - last_entry_actual_len; - if (free_space >= needed_rec_len) + if (last_entry->rec_len - last_entry_actual_len >= needed_rec_len) { last_entry->rec_len = last_entry_actual_len; - offset += last_entry_actual_len; - - auto * new_entry = reinterpret_cast(buffer.data() + offset); - new_entry->inode = child_inode_number; - new_entry->rec_len = static_cast(block_size() - offset); - new_entry->name_len = name_len; - new_entry->file_type = 0x01; // TODO handle mode correctly - kstd::libc::memcpy(new_entry->name.data(), name.data(), name_len); - - return write_block(*global_block_number, buffer.data()).transform([](auto) {}); + 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) {}); } if (!ext2_parent->append_blocks(1)) @@ -275,19 +286,47 @@ namespace kernel::filesystem::ext2 } kstd::libc::memset(buffer.data(), 0, block_size()); - - auto * entry = reinterpret_cast(buffer.data()); - entry->inode = child_inode_number; - entry->rec_len = static_cast(block_size()); - entry->name_len = name_len; - entry->file_type = 0x01; // TODO handle mode correctly - kstd::libc::memcpy(entry->name.data(), name.data(), name_len); + write_directory_entry_to_buffer(buffer.data(), 0, child_inode_number, name, file_type); return write_block(static_cast(*new_global_block), buffer.data()).and_then([&](auto) { return write_inode(ext2_parent->number(), ext2_parent->data()); }); } + auto filesystem::init_directory(kstd::shared_ptr const & new_inode, + uint32_t parent_inode_number) -> kstd::result + { + 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 buffer(block_size(), 0); + write_directory_entry_to_buffer(buffer.data(), 0, new_inode->number(), ".", 2); + auto const dot_actual_len = static_cast((8u + 1 + 3u) & ~3u); + write_directory_entry_to_buffer(buffer.data(), dot_actual_len, parent_inode_number, "..", 2); + 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(name.size()); + auto * entry = reinterpret_cast(block_buffer + offset); + entry->inode = child_inode_number; + entry->rec_len = static_cast(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> { if (auto inode_offset = calculate_inode_offset(inode_number)) @@ -671,6 +710,23 @@ namespace kernel::filesystem::ext2 .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::block_numbers_per_block() const -> size_t { return block_size() / sizeof(uint32_t); diff --git a/kernel/kernel/filesystem/ext2/filesystem.hpp b/kernel/kernel/filesystem/ext2/filesystem.hpp index b3279e09..f45bda97 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.hpp +++ b/kernel/kernel/filesystem/ext2/filesystem.hpp @@ -168,6 +168,10 @@ namespace kernel::filesystem::ext2 auto allocate_inode() -> kstd::result; auto add_directory_entry(inode * ext2_parent, std::string_view name, uint32_t child_inode_number, uint16_t mode) -> kstd::result; + auto init_directory(kstd::shared_ptr const & new_inode, + uint32_t parent_inode_number) -> kstd::result; + 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; [[nodiscard]] auto read_inode(uint32_t inode_number) const -> kstd::result>; @@ -186,6 +190,8 @@ namespace kernel::filesystem::ext2 size_t block_group_descriptor_index) const -> kstd::result; [[nodiscard]] auto write_superblock() const -> kstd::result; + auto map_inode_mode_to_file_type(uint16_t mode) -> uint8_t; + superblock m_superblock{}; kstd::vector m_block_group_descriptors; }; diff --git a/kernel/kernel/filesystem/ext2/inode.cpp b/kernel/kernel/filesystem/ext2/inode.cpp index 0c8da9d0..8af168b9 100644 --- a/kernel/kernel/filesystem/ext2/inode.cpp +++ b/kernel/kernel/filesystem/ext2/inode.cpp @@ -176,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; diff --git a/kernel/kernel/filesystem/ext2/inode.hpp b/kernel/kernel/filesystem/ext2/inode.hpp index ac17e6ab..61542ef8 100644 --- a/kernel/kernel/filesystem/ext2/inode.hpp +++ b/kernel/kernel/filesystem/ext2/inode.hpp @@ -76,10 +76,16 @@ namespace kernel::filesystem::ext2 /** @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. -- cgit v1.2.3 From c70d704ec06233a0222fe9a8559839736ffbe37b Mon Sep 17 00:00:00 2001 From: Marcel Braun Date: Mon, 8 Jun 2026 20:20:14 +0200 Subject: Refactor and init directory correctly --- kernel/kernel/filesystem/ext2/filesystem.cpp | 52 ++++++++++++---------------- kernel/kernel/filesystem/ext2/filesystem.hpp | 2 +- 2 files changed, 24 insertions(+), 30 deletions(-) diff --git a/kernel/kernel/filesystem/ext2/filesystem.cpp b/kernel/kernel/filesystem/ext2/filesystem.cpp index bd1b99a2..fb314d0d 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.cpp +++ b/kernel/kernel/filesystem/ext2/filesystem.cpp @@ -203,24 +203,10 @@ namespace kernel::filesystem::ext2 bool is_directory = false; // TODO refactor if (is_directory) { - if (auto result = init_directory(new_inode, ext2_parent->number()); !result) + if (auto result = init_directory(new_inode, ext2_parent); !result) { return kstd::failure(result.error()); } - - // '..' inside the new dir counts as an extra hard link to the parent - auto & parent_inode_data = ext2_parent->data_mutable(); - parent_inode_data.links_count++; - - if (auto write_result = write_inode(ext2_parent->number(), parent_inode_data); !write_result) - { - return kstd::failure(write_result.error()); - } - } - - if (auto write_result = write_inode(inode_number, new_inode_data); !write_result) - { - return kstd::failure(write_result.error()); } return new_inode; @@ -294,7 +280,7 @@ namespace kernel::filesystem::ext2 } auto filesystem::init_directory(kstd::shared_ptr const & new_inode, - uint32_t parent_inode_number) -> kstd::result + kernel::filesystem::ext2::inode * parent_inode) -> kstd::result { if (!new_inode->append_blocks(1)) { @@ -310,8 +296,18 @@ namespace kernel::filesystem::ext2 kstd::vector buffer(block_size(), 0); write_directory_entry_to_buffer(buffer.data(), 0, new_inode->number(), ".", 2); auto const dot_actual_len = static_cast((8u + 1 + 3u) & ~3u); - write_directory_entry_to_buffer(buffer.data(), dot_actual_len, parent_inode_number, "..", 2); - return global_block.and_then([&](auto block_number) { return write_block(block_number, buffer.data()); }) + auto * entry = reinterpret_cast(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) {}); } @@ -712,18 +708,16 @@ namespace kernel::filesystem::ext2 auto filesystem::map_inode_mode_to_file_type(uint16_t mode) -> uint8_t { + switch (mode & constants::mode_mask) { - 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; - } + case constants::mode_regular: + return 1; + case constants::mode_directory: + return 2; + case constants::mode_symbolic_link: + return 7; + default: + return 0; } } diff --git a/kernel/kernel/filesystem/ext2/filesystem.hpp b/kernel/kernel/filesystem/ext2/filesystem.hpp index f45bda97..d997f508 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.hpp +++ b/kernel/kernel/filesystem/ext2/filesystem.hpp @@ -169,7 +169,7 @@ namespace kernel::filesystem::ext2 auto add_directory_entry(inode * ext2_parent, std::string_view name, uint32_t child_inode_number, uint16_t mode) -> kstd::result; auto init_directory(kstd::shared_ptr const & new_inode, - uint32_t parent_inode_number) -> kstd::result; + kernel::filesystem::ext2::inode * parent_inode) -> kstd::result; 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; -- cgit v1.2.3 From 0b28a0df3406f011cd618cd5e7b2f15f38dbc516 Mon Sep 17 00:00:00 2001 From: Marcel Braun Date: Mon, 8 Jun 2026 21:47:25 +0200 Subject: Implement inode type handling for directory and regular in ext2 --- kernel/kernel/filesystem/ext2/filesystem.cpp | 45 +++++++++++++++++++--------- kernel/kernel/filesystem/ext2/filesystem.hpp | 6 +++- kernel/kernel/filesystem/vfs_types.hpp | 15 ++++++++++ 3 files changed, 51 insertions(+), 15 deletions(-) create mode 100644 kernel/kernel/filesystem/vfs_types.hpp diff --git a/kernel/kernel/filesystem/ext2/filesystem.cpp b/kernel/kernel/filesystem/ext2/filesystem.cpp index fb314d0d..a5bc5f4d 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.cpp +++ b/kernel/kernel/filesystem/ext2/filesystem.cpp @@ -9,6 +9,9 @@ #include #include #include +#include + +#include #include #include @@ -64,6 +67,15 @@ namespace kernel::filesystem::ext2 [[gnu::used]] constexpr auto registration = type_registration{}; + auto filesystem::indirect_levels() const -> std::array + { + 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 const & backing_inode) -> kstd::result { std::ignore = kernel::filesystem::filesystem::mount(backing_inode); @@ -154,7 +166,8 @@ namespace kernel::filesystem::ext2 return kstd::failure(vfs_errc::no_such_file_or_directory); } - auto filesystem::create_inode(kstd::shared_ptr const & parent, std::string_view name) + auto filesystem::create_inode(kstd::shared_ptr const & parent, std::string_view name, + kernel::filesystem::vfs_types::inode_type inode_type) -> kstd::result> { if (!parent) @@ -183,8 +196,7 @@ namespace kernel::filesystem::ext2 inode_data new_inode_data{}; - // TODO set mode depending on type/mode - new_inode_data.mode = 0x8000; + new_inode_data.mode = map_vfs_inode_type_to_inode_mode(inode_type); new_inode_data.size = 0; new_inode_data.dir_acl = 0; new_inode_data.links_count = 1; @@ -200,8 +212,7 @@ namespace kernel::filesystem::ext2 auto new_inode = kstd::make_shared(this, inode_number, new_inode_data); - bool is_directory = false; // TODO refactor - if (is_directory) + if (new_inode->is_directory()) { if (auto result = init_directory(new_inode, ext2_parent); !result) { @@ -503,15 +514,6 @@ namespace kernel::filesystem::ext2 return kstd::failure(ext2_errc::not_enough_inodes); } - auto filesystem::indirect_levels() const -> std::array - { - 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::map_inode_block_index_to_global_block_number(size_t inode_block_index, inode_data data) const -> std::expected { @@ -721,6 +723,21 @@ namespace kernel::filesystem::ext2 } } + auto filesystem::map_vfs_inode_type_to_inode_mode(kernel::filesystem::vfs_types::inode_type vfs_type) -> uint16_t + { + switch (vfs_type) + { + case kernel::filesystem::vfs_types::inode_type::directory: + return constants::mode_directory; + case kernel::filesystem::vfs_types::inode_type::regular: + return constants::mode_regular; + case kernel::filesystem::vfs_types::inode_type::symbolic_link: + // return 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); diff --git a/kernel/kernel/filesystem/ext2/filesystem.hpp b/kernel/kernel/filesystem/ext2/filesystem.hpp index d997f508..e8f72727 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.hpp +++ b/kernel/kernel/filesystem/ext2/filesystem.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -71,9 +72,11 @@ namespace kernel::filesystem::ext2 @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 const & parent, std::string_view name) + [[nodiscard]] auto create_inode(kstd::shared_ptr const & parent, std::string_view name, + kernel::filesystem::vfs_types::inode_type inode_type) -> kstd::result>; // TODO move into base class and override /** @@ -191,6 +194,7 @@ namespace kernel::filesystem::ext2 [[nodiscard]] auto write_superblock() const -> kstd::result; auto map_inode_mode_to_file_type(uint16_t mode) -> uint8_t; + auto map_vfs_inode_type_to_inode_mode(kernel::filesystem::vfs_types::inode_type vfs_type) -> uint16_t; superblock m_superblock{}; kstd::vector m_block_group_descriptors; diff --git a/kernel/kernel/filesystem/vfs_types.hpp b/kernel/kernel/filesystem/vfs_types.hpp new file mode 100644 index 00000000..4bedaddc --- /dev/null +++ b/kernel/kernel/filesystem/vfs_types.hpp @@ -0,0 +1,15 @@ +#ifndef TEACH_OS_KERNEL_FILESYSTEM_VFS_TYPES_HPP +#define TEACH_OS_KERNEL_FILESYSTEM_VFS_TYPES_HPP + +#include + +namespace kernel::filesystem::vfs_types +{ + enum class inode_type : size_t + { + directory = 0, + regular = 1, + symbolic_link = 2, + }; +} // namespace kernel::filesystem::vfs_types +#endif \ No newline at end of file -- cgit v1.2.3 From b0236a47309a5ceb2336acbf03460331ce0d0455 Mon Sep 17 00:00:00 2001 From: Marcel Braun Date: Mon, 8 Jun 2026 21:48:30 +0200 Subject: Add tests for inode creation --- kernel/kernel/filesystem/ext2/filesystem.tests.cpp | 82 ++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/kernel/kernel/filesystem/ext2/filesystem.tests.cpp b/kernel/kernel/filesystem/ext2/filesystem.tests.cpp index 2528db4b..277ff385 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.tests.cpp +++ b/kernel/kernel/filesystem/ext2/filesystem.tests.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -21,6 +22,7 @@ #include #include #include +#include #include SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, @@ -73,6 +75,86 @@ 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 != nullptr); + + auto dev_inode = kstd::make_shared(boot_device); + + auto fs = kernel::filesystem::ext2::filesystem{}; + REQUIRE(fs.mount(dev_inode) == kernel::filesystem::filesystem::operation_result::success); + + THEN("a file can be created") + { + auto lookup_result = fs.lookup(fs.root_inode(), "blub"); + REQUIRE(lookup_result == nullptr); + + auto new_inode = fs.create_inode(fs.root_inode(), "blub", kernel::filesystem::vfs_types::inode_type::regular); + REQUIRE(new_inode != nullptr); + REQUIRE(new_inode->is_regular()); + + lookup_result = fs.lookup(fs.root_inode(), "blub"); + REQUIRE(lookup_result != nullptr); + } + + THEN("a directory can be created") + { + auto lookup_result = fs.lookup(fs.root_inode(), "blub"); + REQUIRE(lookup_result == nullptr); + + auto new_directory = + fs.create_inode(fs.root_inode(), "blub", kernel::filesystem::vfs_types::inode_type::directory); + REQUIRE(new_directory != nullptr); + REQUIRE(new_directory->is_directory()); + + lookup_result = fs.lookup(fs.root_inode(), "blub"); + REQUIRE(lookup_result != nullptr); + } + + 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 != nullptr); + + auto new_file = fs.create_inode(new_directory, "blub_file", kernel::filesystem::vfs_types::inode_type::regular); + REQUIRE(new_file != nullptr); + + auto lookup_result = fs.lookup(new_directory, "blub_file"); + REQUIRE(lookup_result != nullptr); + REQUIRE(lookup_result->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 != nullptr); + + auto buffer = std::vector{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->write(buffer.data(), 0, buffer.size()); + REQUIRE(bytes_written == buffer.size()); + + auto read_buffer = std::vector(5, std::byte{0xAA}); + auto bytes_read = new_inode->read(read_buffer.data(), 0, read_buffer.size()); + REQUIRE(bytes_read == read_buffer.size()); + + std::string_view result{reinterpret_cast(read_buffer.data()), static_cast(bytes_read)}; + REQUIRE(result == "Hello"); + } + } +} + SCENARIO("Ext2 filesystem rejects invalid magic", "[filesystem][ext2][filesystem]") { auto const block_size = 1024; -- cgit v1.2.3 From 423f9e64a315a91a0f87b32cf2f6463b5ae412bb Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Tue, 9 Jun 2026 10:20:13 +0200 Subject: remove unneeded function --- kernel/kernel/filesystem/ext2/filesystem.cpp | 21 +++------ kernel/kernel/filesystem/ext2/filesystem.hpp | 2 - kernel/kernel/filesystem/ext2/filesystem.tests.cpp | 51 +++++++++++----------- 3 files changed, 31 insertions(+), 43 deletions(-) diff --git a/kernel/kernel/filesystem/ext2/filesystem.cpp b/kernel/kernel/filesystem/ext2/filesystem.cpp index a5bc5f4d..97f13b93 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.cpp +++ b/kernel/kernel/filesystem/ext2/filesystem.cpp @@ -23,7 +23,6 @@ #include #include #include -#include #include #include @@ -220,6 +219,11 @@ namespace kernel::filesystem::ext2 } } + if (auto write_result = write_inode(inode_number, new_inode_data); !write_result) + { + return kstd::failure(write_result.error()); + } + return new_inode; } @@ -663,21 +667,6 @@ namespace kernel::filesystem::ext2 return kstd::success(); } - auto filesystem::ensure_and_get_block(uint32_t block_number) -> std::optional - { - if (block_number != 0) - { - return block_number; - } - - if (auto allocated = allocate_blocks(1)) - { - return allocated->front(); - } - - return std::nullopt; - } - auto filesystem::read_block(uint32_t block_number, void * buffer) const -> kstd::result { auto const block_offset = static_cast(block_number) * block_size(); diff --git a/kernel/kernel/filesystem/ext2/filesystem.hpp b/kernel/kernel/filesystem/ext2/filesystem.hpp index e8f72727..03398f00 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.hpp +++ b/kernel/kernel/filesystem/ext2/filesystem.hpp @@ -180,8 +180,6 @@ namespace kernel::filesystem::ext2 [[nodiscard]] auto read_inode(uint32_t inode_number) const -> kstd::result>; [[nodiscard]] auto read_block_number_at_index(uint32_t block_number, size_t index) const -> kstd::result; - auto ensure_and_get_block(uint32_t block_number) -> std::optional; - [[nodiscard]] auto block_group_descriptor_table_offset() const -> size_t; [[nodiscard]] auto block_numbers_per_block() const -> size_t; diff --git a/kernel/kernel/filesystem/ext2/filesystem.tests.cpp b/kernel/kernel/filesystem/ext2/filesystem.tests.cpp index 277ff385..74e99c26 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.tests.cpp +++ b/kernel/kernel/filesystem/ext2/filesystem.tests.cpp @@ -36,7 +36,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(boot_device); @@ -45,7 +45,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()); } @@ -86,70 +86,71 @@ 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(boot_device); auto fs = kernel::filesystem::ext2::filesystem{}; - REQUIRE(fs.mount(dev_inode) == kernel::filesystem::filesystem::operation_result::success); + REQUIRE(fs.mount(dev_inode)); THEN("a file can be created") { auto lookup_result = fs.lookup(fs.root_inode(), "blub"); - REQUIRE(lookup_result == nullptr); + REQUIRE(!lookup_result); auto new_inode = fs.create_inode(fs.root_inode(), "blub", kernel::filesystem::vfs_types::inode_type::regular); - REQUIRE(new_inode != nullptr); - REQUIRE(new_inode->is_regular()); + REQUIRE(new_inode); + REQUIRE(new_inode.value()->is_regular()); lookup_result = fs.lookup(fs.root_inode(), "blub"); - REQUIRE(lookup_result != nullptr); + REQUIRE(lookup_result); } THEN("a directory can be created") { auto lookup_result = fs.lookup(fs.root_inode(), "blub"); - REQUIRE(lookup_result == nullptr); + REQUIRE(!lookup_result); auto new_directory = fs.create_inode(fs.root_inode(), "blub", kernel::filesystem::vfs_types::inode_type::directory); - REQUIRE(new_directory != nullptr); - REQUIRE(new_directory->is_directory()); + REQUIRE(new_directory); + REQUIRE(new_directory.value()->is_directory()); lookup_result = fs.lookup(fs.root_inode(), "blub"); - REQUIRE(lookup_result != nullptr); + 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 != nullptr); + REQUIRE(new_directory); - auto new_file = fs.create_inode(new_directory, "blub_file", kernel::filesystem::vfs_types::inode_type::regular); - REQUIRE(new_file != nullptr); + 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, "blub_file"); - REQUIRE(lookup_result != nullptr); - REQUIRE(lookup_result->is_regular()); + 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 != nullptr); + REQUIRE(new_inode); auto buffer = std::vector{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->write(buffer.data(), 0, buffer.size()); + auto bytes_written = new_inode.value()->write(buffer.data(), 0, buffer.size()); REQUIRE(bytes_written == buffer.size()); auto read_buffer = std::vector(5, std::byte{0xAA}); - auto bytes_read = new_inode->read(read_buffer.data(), 0, read_buffer.size()); + 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(read_buffer.data()), static_cast(bytes_read)}; + std::string_view result{reinterpret_cast(read_buffer.data()), static_cast(*bytes_read)}; REQUIRE(result == "Hello"); } } @@ -161,7 +162,7 @@ SCENARIO("Ext2 filesystem rejects invalid magic", "[filesystem][ext2][filesystem GIVEN("a block device that does not contain an ext2 superblock") { auto device = kstd::make_shared(0, 0, "mock", block_size, 2 * block_size); - REQUIRE(device != nullptr); + REQUIRE(device); auto dev_inode = kstd::make_shared(device); @@ -181,7 +182,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(0, 0, "mock", block_size, 128 * block_size); - REQUIRE(device != nullptr); + REQUIRE(device); kernel::tests::filesystem::ext2::setup_mock_ext2_layout(*device); @@ -233,7 +234,7 @@ SCENARIO("Ext2 block writing includes direct and all indirect levels", "[filesys GIVEN("a mounted ext2 filesystem with free blocks available for indirect table allocation") { auto device = kstd::make_shared(0, 0, "mock", block_size, 256 * block_size); - REQUIRE(device != nullptr); + REQUIRE(device); auto superblock = kernel::filesystem::ext2::superblock{}; superblock.magic = kernel::filesystem::ext2::constants::magic_number; -- cgit v1.2.3 From 8f4719cb75bc1be5198156d80c7be54f124a192c Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Tue, 9 Jun 2026 10:20:34 +0200 Subject: add test that symbolic link creation isn't implemented yet --- kernel/kernel/filesystem/ext2/filesystem.tests.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/kernel/kernel/filesystem/ext2/filesystem.tests.cpp b/kernel/kernel/filesystem/ext2/filesystem.tests.cpp index 74e99c26..8aecc847 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.tests.cpp +++ b/kernel/kernel/filesystem/ext2/filesystem.tests.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -153,6 +154,13 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, std::string_view result{reinterpret_cast(read_buffer.data()), static_cast(*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); + } } } -- cgit v1.2.3 From c09ef501e1baf293d92b166e5e38e29a41cf64c2 Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Tue, 9 Jun 2026 11:06:25 +0200 Subject: implement mkdir and create function in kapi::filesystem that calls the vfs and then the underlying filesystem --- kapi/kapi/filesystem.hpp | 18 +++++++++ kernel/kapi/filesystem.cpp | 12 ++++++ kernel/kernel/filesystem/devfs/filesystem.cpp | 10 ++++- kernel/kernel/filesystem/devfs/filesystem.hpp | 12 ++++++ kernel/kernel/filesystem/error.hpp | 5 +++ kernel/kernel/filesystem/ext2/filesystem.hpp | 3 +- kernel/kernel/filesystem/filesystem.hpp | 12 ++++++ kernel/kernel/filesystem/path.hpp | 32 +++++++++++++++ kernel/kernel/filesystem/rootfs/filesystem.cpp | 9 ++++- kernel/kernel/filesystem/rootfs/filesystem.hpp | 12 ++++++ kernel/kernel/filesystem/vfs.cpp | 46 ++++++++++++++++++++++ kernel/kernel/filesystem/vfs.hpp | 18 +++++++++ .../kernel/test_support/filesystem/filesystem.cpp | 12 +++++- .../kernel/test_support/filesystem/filesystem.hpp | 9 ++++- 14 files changed, 202 insertions(+), 8 deletions(-) diff --git a/kapi/kapi/filesystem.hpp b/kapi/kapi/filesystem.hpp index 5346a96d..1656a17f 100644 --- a/kapi/kapi/filesystem.hpp +++ b/kapi/kapi/filesystem.hpp @@ -1,6 +1,7 @@ #ifndef TEACHOS_KAPI_FILESYSTEM_HPP #define TEACHOS_KAPI_FILESYSTEM_HPP +#include #include #include @@ -69,6 +70,23 @@ namespace kapi::filesystem @qualifier kernel-defined */ auto write(size_t file_descriptor, void const * buffer, size_t size) -> std::expected; + + /** + @brief Creates a new directory at the specified @p path. + @param path The path where the new directory should be created. + @return 0 on success, -1 on failure. + @qualifier kernel-defined + */ + auto mkdir(std::string_view path) -> kstd::result; + + /** + @brief Creates a new file at the specified @p path. + @param path The path where the new file should be created. + @return 0 on success, -1 on failure. + @qualifier kernel-defined + */ + // TODO remove again after the open method supports an optional create flag + auto create(std::string_view path) -> kstd::result; } // namespace kapi::filesystem #endif // TEACHOS_KAPI_FILESYSTEM_HPP \ No newline at end of file diff --git a/kernel/kapi/filesystem.cpp b/kernel/kapi/filesystem.cpp index efde1336..822095e6 100644 --- a/kernel/kapi/filesystem.cpp +++ b/kernel/kapi/filesystem.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -53,4 +54,15 @@ namespace kapi::filesystem return descriptor->write(buffer, size); }); } + + auto mkdir(std::string_view path) -> kstd::result + { + return kernel::filesystem::vfs::get().mkdir(path); + } + + auto create(std::string_view path) -> kstd::result + { + return kernel::filesystem::vfs::get().create(path); + } + } // namespace kapi::filesystem \ No newline at end of file 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 -#include "kernel/filesystem/filesystem.hpp" #include #include #include #include +#include #include #include +#include #include @@ -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 const &, std::string_view, + kernel::filesystem::vfs_types::inode_type) + -> kstd::result> + { + 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 #include #include +#include #include #include @@ -38,6 +39,17 @@ namespace kernel::filesystem::devfs [[nodiscard]] auto lookup(kstd::shared_ptr const & parent, std::string_view name) const -> kstd::result> 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 const & parent, std::string_view name, + kernel::filesystem::vfs_types::inode_type inode_type) + -> kstd::result> override; + private: auto build_device_inode_table() -> void; diff --git a/kernel/kernel/filesystem/error.hpp b/kernel/kernel/filesystem/error.hpp index 56f56ce4..82ba33b3 100644 --- a/kernel/kernel/filesystem/error.hpp +++ b/kernel/kernel/filesystem/error.hpp @@ -24,6 +24,7 @@ namespace kernel::filesystem invalid_inode, not_a_directory, is_a_directory, + read_only_file_system, }; namespace detail @@ -65,6 +66,8 @@ 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"; default: return "unknown VFS error"; } @@ -95,6 +98,8 @@ 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); default: return kstd::error_condition{value, *this}; } diff --git a/kernel/kernel/filesystem/ext2/filesystem.hpp b/kernel/kernel/filesystem/ext2/filesystem.hpp index 03398f00..9af9aeb1 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.hpp +++ b/kernel/kernel/filesystem/ext2/filesystem.hpp @@ -16,7 +16,6 @@ #include #include #include -#include #include namespace kernel::filesystem::ext2 @@ -77,7 +76,7 @@ namespace kernel::filesystem::ext2 */ [[nodiscard]] auto create_inode(kstd::shared_ptr const & parent, std::string_view name, kernel::filesystem::vfs_types::inode_type inode_type) - -> kstd::result>; // TODO move into base class and override + -> kstd::result> override; /** @brief Gets the size of a block in the filesystem. 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 +#include #include #include @@ -53,6 +54,17 @@ namespace kernel::filesystem [[nodiscard]] virtual auto lookup(kstd::shared_ptr const & parent, std::string_view name) const -> kstd::result> = 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 const & parent, std::string_view name, + vfs_types::inode_type inode_type) + -> kstd::result> = 0; + /** @brief Returns a reference to the root inode of the filesystem. */ diff --git a/kernel/kernel/filesystem/path.hpp b/kernel/kernel/filesystem/path.hpp index 438c22f3..c2d8e588 100644 --- a/kernel/kernel/filesystem/path.hpp +++ b/kernel/kernel/filesystem/path.hpp @@ -7,6 +7,7 @@ #include #include +#include namespace kernel::filesystem::path { @@ -66,6 +67,37 @@ 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 + { + if (path.empty()) + { + return {"", ""}; + } + + auto search_from = path.length() > 1 ? path.length() - 2 : 0; + auto last_separator_index = path.rfind('/', search_from); + + 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/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 #include #include +#include #include #include @@ -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 const &, std::string_view, + kernel::filesystem::vfs_types::inode_type) + -> kstd::result> + { + 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 #include +#include #include #include @@ -37,6 +38,17 @@ namespace kernel::filesystem::rootfs */ [[nodiscard]] auto lookup(kstd::shared_ptr const & parent, std::string_view name) const -> kstd::result> 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 const & parent, std::string_view name, + kernel::filesystem::vfs_types::inode_type inode_type) + -> kstd::result> override; }; } // namespace kernel::filesystem::rootfs diff --git a/kernel/kernel/filesystem/vfs.cpp b/kernel/kernel/filesystem/vfs.cpp index d128bbf5..2e4145db 100644 --- a/kernel/kernel/filesystem/vfs.cpp +++ b/kernel/kernel/filesystem/vfs.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include @@ -171,6 +172,51 @@ namespace kernel::filesystem return kstd::failure(vfs_errc::unmount_failed); } + auto vfs::mkdir(std::string_view path) -> kstd::result + { + return create_inode(path, vfs_types::inode_type::directory); + } + + auto vfs::create(std::string_view path) -> kstd::result + { + return create_inode(path, vfs_types::inode_type::regular); + } + + auto vfs::create_inode(std::string_view path, vfs_types::inode_type type) -> kstd::result + { + if (!path::is_valid_path(path)) + { + return kstd::failure(vfs_errc::invalid_path); + } + + 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(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 #include #include +#include #include #include @@ -77,6 +78,21 @@ namespace kernel::filesystem */ auto unmount(std::string_view path) -> kstd::result; + /** + @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; + + /** + @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; + 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 const & device_fs) -> void; + auto create_inode(std::string_view path, vfs_types::inode_type type) -> kstd::result; + mount_table m_mount_table{}; }; } // namespace kernel::filesystem 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 #include +#include #include #include +#include #include -#include #include namespace kernel::tests::filesystem { auto filesystem::lookup(kstd::shared_ptr const &, std::string_view) const - -> std::expected, kstd::error_code> + -> kstd::result> + { + return kstd::make_shared(); + } + + auto filesystem::create_inode(kstd::shared_ptr const &, std::string_view, + kernel::filesystem::vfs_types::inode_type) + -> kstd::result> { return kstd::make_shared(); } 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 #include +#include #include +#include #include -#include #include namespace kernel::tests::filesystem @@ -17,7 +18,11 @@ namespace kernel::tests::filesystem filesystem() = default; [[nodiscard]] auto lookup(kstd::shared_ptr const & parent, std::string_view name) const - -> std::expected, kstd::error_code> override; + -> kstd::result> override; + + [[nodiscard]] auto create_inode(kstd::shared_ptr const & parent, std::string_view name, + kernel::filesystem::vfs_types::inode_type inode_type) + -> kstd::result> override; }; } // namespace kernel::tests::filesystem -- cgit v1.2.3 From fcc3e7cef934f9a1532d4e2f6f011d60d41574ce Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Tue, 9 Jun 2026 11:40:29 +0200 Subject: fix split_into_path_and_filename logic, add todo --- kernel/kernel/filesystem/path.hpp | 7 +++++-- kernel/kernel/filesystem/vfs.cpp | 2 ++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/kernel/kernel/filesystem/path.hpp b/kernel/kernel/filesystem/path.hpp index c2d8e588..a0dbd1f1 100644 --- a/kernel/kernel/filesystem/path.hpp +++ b/kernel/kernel/filesystem/path.hpp @@ -79,9 +79,12 @@ namespace kernel::filesystem::path return {"", ""}; } - auto search_from = path.length() > 1 ? path.length() - 2 : 0; - auto last_separator_index = path.rfind('/', search_from); + 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}; diff --git a/kernel/kernel/filesystem/vfs.cpp b/kernel/kernel/filesystem/vfs.cpp index 2e4145db..4691dac3 100644 --- a/kernel/kernel/filesystem/vfs.cpp +++ b/kernel/kernel/filesystem/vfs.cpp @@ -189,6 +189,8 @@ namespace kernel::filesystem return kstd::failure(vfs_errc::invalid_path); } + // TODO check whether the target already exists + auto [parent_path, name] = path::split_into_path_and_filename(path); if (name.empty()) { -- cgit v1.2.3 From 918e6eecfaefd94ccb2995cc526b2673263733b4 Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Tue, 9 Jun 2026 11:40:45 +0200 Subject: add tests --- kernel/kapi/filesystem.tests.cpp | 58 +++++++++++++++++++++ .../kernel/filesystem/devfs/filesystem.tests.cpp | 7 +++ kernel/kernel/filesystem/path.tests.cpp | 59 ++++++++++++++++++++++ .../kernel/filesystem/rootfs/filesystem.tests.cpp | 8 +++ kernel/kernel/filesystem/vfs_types.hpp | 1 + 5 files changed, 133 insertions(+) diff --git a/kernel/kapi/filesystem.tests.cpp b/kernel/kapi/filesystem.tests.cpp index b339f19f..595d73e8 100644 --- a/kernel/kapi/filesystem.tests.cpp +++ b/kernel/kapi/filesystem.tests.cpp @@ -211,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) < 0); + + REQUIRE(kapi::filesystem::create(new_file_path) == 0); + + auto fd = kapi::filesystem::open(new_file_path); + REQUIRE(fd >= 0); + + auto write_buffer = std::vector{std::byte{'T'}, std::byte{'e'}, std::byte{'s'}, std::byte{'t'}}; + kapi::filesystem::write(fd, write_buffer.data(), write_buffer.size()); + + REQUIRE(kapi::filesystem::close(fd) == 0); + + fd = kapi::filesystem::open(new_file_path); + REQUIRE(fd >= 0); + + auto read_buffer = std::vector(4); + auto bytes_read = kapi::filesystem::read(fd, read_buffer.data(), read_buffer.size()); + + std::string_view buffer_as_str{reinterpret_cast(read_buffer.data()), static_cast(bytes_read)}; + REQUIRE(buffer_as_str == "Test"); + + REQUIRE(kapi::filesystem::close(fd) == 0); + } + + 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) < 0); + + REQUIRE(kapi::filesystem::mkdir(new_directory_path) == 0); + REQUIRE(kapi::filesystem::create(new_file_path) == 0); + + auto fd = kapi::filesystem::open(new_file_path); + REQUIRE(fd >= 0); + REQUIRE(kapi::filesystem::close(fd) == 0); + } + + THEN("a file can be created in a mounted filesystem, opened, and closed again") + { + kapi::filesystem::mount("/dev/ram16", "/information"); + + auto new_file_path = "/information/monkey_house/monkey_4.txt"; + REQUIRE(kapi::filesystem::open(new_file_path) < 0); + REQUIRE(kapi::filesystem::create(new_file_path) == 0); + + auto fd = kapi::filesystem::open(new_file_path); + REQUIRE(fd >= 0); + REQUIRE(kapi::filesystem::close(fd) == 0); + + REQUIRE(kapi::filesystem::umount("/information") == 0); + } } } diff --git a/kernel/kernel/filesystem/devfs/filesystem.tests.cpp b/kernel/kernel/filesystem/devfs/filesystem.tests.cpp index d5028231..bbfddee3 100644 --- a/kernel/kernel/filesystem/devfs/filesystem.tests.cpp +++ b/kernel/kernel/filesystem/devfs/filesystem.tests.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include @@ -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 null") + { + auto result = fs.create_inode(fs.root_inode(), "new_device", kernel::filesystem::vfs_types::inode_type::device); + REQUIRE(result == nullptr); + } } GIVEN("a boot module registry with three modules") 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.tests.cpp b/kernel/kernel/filesystem/rootfs/filesystem.tests.cpp index 347502ca..ecd20707 100644 --- a/kernel/kernel/filesystem/rootfs/filesystem.tests.cpp +++ b/kernel/kernel/filesystem/rootfs/filesystem.tests.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include @@ -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 null") + { + auto result = + fs.create_inode(fs.root_inode(), "new_device", kernel::filesystem::vfs_types::inode_type::directory); + REQUIRE(result == nullptr); + } } } diff --git a/kernel/kernel/filesystem/vfs_types.hpp b/kernel/kernel/filesystem/vfs_types.hpp index 4bedaddc..4c2ed72e 100644 --- a/kernel/kernel/filesystem/vfs_types.hpp +++ b/kernel/kernel/filesystem/vfs_types.hpp @@ -10,6 +10,7 @@ namespace kernel::filesystem::vfs_types directory = 0, regular = 1, symbolic_link = 2, + device = 3 }; } // namespace kernel::filesystem::vfs_types #endif \ No newline at end of file -- cgit v1.2.3 From 1b17e5de9d5a949dc4da6b312af19b06c96e0f2f Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Tue, 9 Jun 2026 22:58:49 +0200 Subject: check whether the target already exists when creating an inode --- kernel/kapi/filesystem.tests.cpp | 40 +++++++++++----------- .../kernel/filesystem/devfs/filesystem.tests.cpp | 4 +-- kernel/kernel/filesystem/error.hpp | 5 +++ .../kernel/filesystem/rootfs/filesystem.tests.cpp | 4 +-- kernel/kernel/filesystem/vfs.cpp | 5 ++- 5 files changed, 33 insertions(+), 25 deletions(-) diff --git a/kernel/kapi/filesystem.tests.cpp b/kernel/kapi/filesystem.tests.cpp index 595d73e8..f2a6a988 100644 --- a/kernel/kapi/filesystem.tests.cpp +++ b/kernel/kapi/filesystem.tests.cpp @@ -216,28 +216,28 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, "Kap { auto new_file_path = "/information/new_information.txt"; - REQUIRE(kapi::filesystem::open(new_file_path) < 0); + REQUIRE(!kapi::filesystem::open(new_file_path)); - REQUIRE(kapi::filesystem::create(new_file_path) == 0); + REQUIRE(kapi::filesystem::create(new_file_path)); auto fd = kapi::filesystem::open(new_file_path); - REQUIRE(fd >= 0); + REQUIRE(fd); auto write_buffer = std::vector{std::byte{'T'}, std::byte{'e'}, std::byte{'s'}, std::byte{'t'}}; - kapi::filesystem::write(fd, write_buffer.data(), write_buffer.size()); + CHECK(kapi::filesystem::write(*fd, write_buffer.data(), write_buffer.size())); - REQUIRE(kapi::filesystem::close(fd) == 0); + REQUIRE(kapi::filesystem::close(*fd)); fd = kapi::filesystem::open(new_file_path); - REQUIRE(fd >= 0); + REQUIRE(fd); auto read_buffer = std::vector(4); - auto bytes_read = kapi::filesystem::read(fd, read_buffer.data(), read_buffer.size()); + auto bytes_read = kapi::filesystem::read(*fd, read_buffer.data(), read_buffer.size()); - std::string_view buffer_as_str{reinterpret_cast(read_buffer.data()), static_cast(bytes_read)}; + std::string_view buffer_as_str{reinterpret_cast(read_buffer.data()), static_cast(*bytes_read)}; REQUIRE(buffer_as_str == "Test"); - REQUIRE(kapi::filesystem::close(fd) == 0); + REQUIRE(kapi::filesystem::close(*fd)); } THEN("a new directory can be created and a file within can be created, opened, and closed again") @@ -245,29 +245,29 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, "Kap auto new_directory_path = "/information/new_directory"; auto new_file_path = "/information/new_directory/new_information.txt"; - REQUIRE(kapi::filesystem::open(new_file_path) < 0); + REQUIRE(!kapi::filesystem::open(new_file_path)); - REQUIRE(kapi::filesystem::mkdir(new_directory_path) == 0); - REQUIRE(kapi::filesystem::create(new_file_path) == 0); + REQUIRE(kapi::filesystem::mkdir(new_directory_path)); + REQUIRE(kapi::filesystem::create(new_file_path)); auto fd = kapi::filesystem::open(new_file_path); - REQUIRE(fd >= 0); - REQUIRE(kapi::filesystem::close(fd) == 0); + REQUIRE(fd); + REQUIRE(kapi::filesystem::close(*fd)); } THEN("a file can be created in a mounted filesystem, opened, and closed again") { - kapi::filesystem::mount("/dev/ram16", "/information"); + CHECK(kapi::filesystem::mount("/dev/ram16", "/information")); auto new_file_path = "/information/monkey_house/monkey_4.txt"; - REQUIRE(kapi::filesystem::open(new_file_path) < 0); - REQUIRE(kapi::filesystem::create(new_file_path) == 0); + REQUIRE(!kapi::filesystem::open(new_file_path)); + REQUIRE(kapi::filesystem::create(new_file_path)); auto fd = kapi::filesystem::open(new_file_path); - REQUIRE(fd >= 0); - REQUIRE(kapi::filesystem::close(fd) == 0); + REQUIRE(fd); + REQUIRE(kapi::filesystem::close(*fd)); - REQUIRE(kapi::filesystem::umount("/information") == 0); + REQUIRE(kapi::filesystem::umount("/information")); } } } diff --git a/kernel/kernel/filesystem/devfs/filesystem.tests.cpp b/kernel/kernel/filesystem/devfs/filesystem.tests.cpp index bbfddee3..c1aad37c 100644 --- a/kernel/kernel/filesystem/devfs/filesystem.tests.cpp +++ b/kernel/kernel/filesystem/devfs/filesystem.tests.cpp @@ -54,10 +54,10 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, REQUIRE(!result); } - THEN("create_inode always returns null") + 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 == nullptr); + REQUIRE(!result); } } diff --git a/kernel/kernel/filesystem/error.hpp b/kernel/kernel/filesystem/error.hpp index 82ba33b3..584bf261 100644 --- a/kernel/kernel/filesystem/error.hpp +++ b/kernel/kernel/filesystem/error.hpp @@ -25,6 +25,7 @@ namespace kernel::filesystem not_a_directory, is_a_directory, read_only_file_system, + file_exists, }; namespace detail @@ -68,6 +69,8 @@ namespace kernel::filesystem 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"; } @@ -100,6 +103,8 @@ namespace kernel::filesystem 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/rootfs/filesystem.tests.cpp b/kernel/kernel/filesystem/rootfs/filesystem.tests.cpp index ecd20707..95fa57db 100644 --- a/kernel/kernel/filesystem/rootfs/filesystem.tests.cpp +++ b/kernel/kernel/filesystem/rootfs/filesystem.tests.cpp @@ -36,11 +36,11 @@ SCENARIO("Rootfs filesystem mount and lookup", "[filesystem][rootfs][filesystem] REQUIRE(!result); } - THEN("create_inode always returns null") + 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 == nullptr); + REQUIRE(!result); } } } diff --git a/kernel/kernel/filesystem/vfs.cpp b/kernel/kernel/filesystem/vfs.cpp index 4691dac3..9a78b6f8 100644 --- a/kernel/kernel/filesystem/vfs.cpp +++ b/kernel/kernel/filesystem/vfs.cpp @@ -189,7 +189,10 @@ namespace kernel::filesystem return kstd::failure(vfs_errc::invalid_path); } - // TODO check whether the target already exists + 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()) -- cgit v1.2.3 From 760d5f5e7147e90198d144dd366aee7ace5de77b Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Tue, 9 Jun 2026 23:09:32 +0200 Subject: always set all access rights, update parent's inode size --- kernel/kernel/filesystem/ext2/filesystem.cpp | 51 ++++++++++++++++------------ kernel/kernel/filesystem/ext2/filesystem.hpp | 3 +- kernel/kernel/main.cpp | 33 ++++++++++++++++++ 3 files changed, 65 insertions(+), 22 deletions(-) diff --git a/kernel/kernel/filesystem/ext2/filesystem.cpp b/kernel/kernel/filesystem/ext2/filesystem.cpp index 97f13b93..091b1399 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.cpp +++ b/kernel/kernel/filesystem/ext2/filesystem.cpp @@ -195,7 +195,8 @@ namespace kernel::filesystem::ext2 inode_data new_inode_data{}; - new_inode_data.mode = map_vfs_inode_type_to_inode_mode(inode_type); + 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; @@ -213,13 +214,14 @@ namespace kernel::filesystem::ext2 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) + if (auto write_result = write_inode(inode_number, new_inode->data()); !write_result) { return kstd::failure(write_result.error()); } @@ -272,26 +274,32 @@ namespace kernel::filesystem::ext2 return global_block.and_then([&](auto block_number) { return write_block(block_number, buffer.data()); }) .transform([](auto) {}); } - - if (!ext2_parent->append_blocks(1)) + else { - return kstd::failure(ext2_errc::not_enough_free_blocks); - } + if (!ext2_parent->append_blocks(1)) + { + return kstd::failure(ext2_errc::not_enough_free_blocks); + } - auto new_last_block_index = inode_block_count(ext2_parent->data()) - 1; + 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()); + } - auto new_global_block = map_inode_block_index_to_global_block_number(new_last_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()); + } } - kstd::libc::memset(buffer.data(), 0, block_size()); - write_directory_entry_to_buffer(buffer.data(), 0, child_inode_number, name, file_type); - - return write_block(static_cast(*new_global_block), buffer.data()).and_then([&](auto) { - return write_inode(ext2_parent->number(), ext2_parent->data()); - }); + 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 const & new_inode, @@ -712,16 +720,17 @@ namespace kernel::filesystem::ext2 } } - auto filesystem::map_vfs_inode_type_to_inode_mode(kernel::filesystem::vfs_types::inode_type vfs_type) -> uint16_t + 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 constants::mode_directory; + return mode | constants::mode_directory; case kernel::filesystem::vfs_types::inode_type::regular: - return constants::mode_regular; + return mode | constants::mode_regular; case kernel::filesystem::vfs_types::inode_type::symbolic_link: - // return constants::mode_symbolic_link; // TODO implement + // return mode | constants::mode_symbolic_link; // TODO implement default: kapi::system::panic("[EXT2] Not implemented."); } diff --git a/kernel/kernel/filesystem/ext2/filesystem.hpp b/kernel/kernel/filesystem/ext2/filesystem.hpp index 9af9aeb1..e5192ec0 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.hpp +++ b/kernel/kernel/filesystem/ext2/filesystem.hpp @@ -191,7 +191,8 @@ namespace kernel::filesystem::ext2 [[nodiscard]] auto write_superblock() const -> kstd::result; auto map_inode_mode_to_file_type(uint16_t mode) -> uint8_t; - auto map_vfs_inode_type_to_inode_mode(kernel::filesystem::vfs_types::inode_type vfs_type) -> uint16_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 m_block_group_descriptors; diff --git a/kernel/kernel/main.cpp b/kernel/kernel/main.cpp index 8dce5143..2da18e0b 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 0xffffffff80259000 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 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 0xffffffff80259000 0xA00000 dump_after.bin } auto main() -> int -- cgit v1.2.3 From c992070065e1811905a351d319decd92dc0db37e Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Tue, 9 Jun 2026 23:33:42 +0200 Subject: add VFS tests --- kernel/kernel/filesystem/vfs.tests.cpp | 47 ++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/kernel/kernel/filesystem/vfs.tests.cpp b/kernel/kernel/filesystem/vfs.tests.cpp index c99cea90..c733d071 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") == kernel::filesystem::vfs::operation_result::success); + + 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") == kernel::filesystem::vfs::operation_result::success); + } + + THEN("a new file cannot be created if it already exists") + { + auto & vfs = kernel::filesystem::vfs::get(); + REQUIRE(vfs.create("/information/info_1.txt") == kernel::filesystem::vfs::operation_result::inode_already_exists); + } + + THEN("a new directory cannot be created if it already exists") + { + auto & vfs = kernel::filesystem::vfs::get(); + REQUIRE(vfs.mkdir("/information") == kernel::filesystem::vfs::operation_result::inode_already_exists); + } + + THEN("a new file cannot be created if path does not exist or is invalid") + { + auto & vfs = kernel::filesystem::vfs::get(); + REQUIRE(vfs.create("") == kernel::filesystem::vfs::operation_result::invalid_path); + REQUIRE(vfs.create("invalid_path") == kernel::filesystem::vfs::operation_result::non_existent_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("") == kernel::filesystem::vfs::operation_result::invalid_path); + REQUIRE(vfs.mkdir("invalid_path") == kernel::filesystem::vfs::operation_result::non_existent_path); + } + } + GIVEN("A real image file containing as filesystem formatted files") { REQUIRE(std::filesystem::exists(image_path_1)); -- cgit v1.2.3 From 981b17c0a46ad26de684de29709810e3f6494886 Mon Sep 17 00:00:00 2001 From: Lukas Oesch Date: Wed, 10 Jun 2026 12:28:56 +0200 Subject: new memory address after rebase --- kernel/kernel/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kernel/kernel/main.cpp b/kernel/kernel/main.cpp index 2da18e0b..fef35ed9 100644 --- a/kernel/kernel/main.cpp +++ b/kernel/kernel/main.cpp @@ -109,7 +109,7 @@ auto run_demo() -> void // 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 0xffffffff80259000 0xA00000 dump_before.bin + // -exec monitor memsave 0xffffffff8025b000 0xA00000 dump_before.bin // 12) create new folder and file auto creation_result = kapi::filesystem::mkdir("/test_files").and_then([] { @@ -139,7 +139,7 @@ auto run_demo() -> void 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 0xffffffff80259000 0xA00000 dump_after.bin + // -exec monitor memsave 0xffffffff8025b000 0xA00000 dump_after.bin } auto main() -> int -- cgit v1.2.3 From 8170017c8fcea4b9047c7a1d586bb82473c1eed5 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Tue, 14 Jul 2026 22:28:12 +0200 Subject: kernel/vfs: fix test compilation --- kernel/kernel/filesystem/vfs.tests.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/kernel/kernel/filesystem/vfs.tests.cpp b/kernel/kernel/filesystem/vfs.tests.cpp index c733d071..ace1bd1f 100644 --- a/kernel/kernel/filesystem/vfs.tests.cpp +++ b/kernel/kernel/filesystem/vfs.tests.cpp @@ -339,7 +339,7 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, "VFS THEN("a new file can be created and read again") { auto & vfs = kernel::filesystem::vfs::get(); - REQUIRE(vfs.create("/information/new_file.txt") == kernel::filesystem::vfs::operation_result::success); + REQUIRE(vfs.create("/information/new_file.txt")); auto new_file = vfs.open("/information/new_file.txt"); REQUIRE(new_file != nullptr); @@ -348,33 +348,33 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, "VFS THEN("a new directory can be created and read again") { auto & vfs = kernel::filesystem::vfs::get(); - REQUIRE(vfs.mkdir("/information/new_directory") == kernel::filesystem::vfs::operation_result::success); + 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") == kernel::filesystem::vfs::operation_result::inode_already_exists); + 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") == kernel::filesystem::vfs::operation_result::inode_already_exists); + 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("") == kernel::filesystem::vfs::operation_result::invalid_path); - REQUIRE(vfs.create("invalid_path") == kernel::filesystem::vfs::operation_result::non_existent_path); + 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("") == kernel::filesystem::vfs::operation_result::invalid_path); - REQUIRE(vfs.mkdir("invalid_path") == kernel::filesystem::vfs::operation_result::non_existent_path); + REQUIRE(!vfs.mkdir("")); + REQUIRE(!vfs.mkdir("invalid_path")); } } -- cgit v1.2.3