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(-) (limited to 'kernel') 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