diff options
Diffstat (limited to 'kernel')
| -rw-r--r-- | kernel/kernel/filesystem/ext2/directory_iterator.cpp | 6 | ||||
| -rw-r--r-- | kernel/kernel/filesystem/ext2/filesystem.cpp | 971 | ||||
| -rw-r--r-- | kernel/kernel/filesystem/ext2/filesystem.hpp | 282 | ||||
| -rw-r--r-- | kernel/kernel/filesystem/ext2/filesystem.tests.cpp | 40 | ||||
| -rw-r--r-- | kernel/kernel/filesystem/ext2/inode.cpp | 78 | ||||
| -rw-r--r-- | kernel/kernel/filesystem/ext2/inode.hpp | 10 | ||||
| -rw-r--r-- | kernel/kernel/filesystem/ext2/inode.tests.cpp | 10 | ||||
| -rw-r--r-- | kernel/kernel/filesystem/ext2/superblock.hpp | 14 | ||||
| -rw-r--r-- | kernel/kernel/filesystem/ext2/write_batch.cpp | 11 | ||||
| -rw-r--r-- | kernel/kernel/filesystem/ext2/write_batch.hpp | 4 |
10 files changed, 678 insertions, 748 deletions
diff --git a/kernel/kernel/filesystem/ext2/directory_iterator.cpp b/kernel/kernel/filesystem/ext2/directory_iterator.cpp index 3e614f26..cfb520d6 100644 --- a/kernel/kernel/filesystem/ext2/directory_iterator.cpp +++ b/kernel/kernel/filesystem/ext2/directory_iterator.cpp @@ -37,7 +37,7 @@ namespace kernel::filesystem::ext2 auto directory_iterator::operator++() -> directory_iterator & { - if (!m_inode || m_file_offset >= m_filesystem->data_size(*m_inode, *m_state)) + if (!m_inode || m_file_offset >= data_size(*m_inode, *m_state)) { m_inode = nullptr; return *this; @@ -64,7 +64,7 @@ namespace kernel::filesystem::ext2 auto directory_iterator::read() -> void { - if (auto result = m_filesystem->do_read(*m_inode, m_buffer, m_file_offset, *m_state); !result) + if (auto result = do_read(*m_inode, m_buffer, m_file_offset, *m_state); !result) { kapi::system::panic("[FS:ext2] failed to read directory entry", result.error()); } @@ -77,7 +77,7 @@ namespace kernel::filesystem::ext2 { m_buffer.resize(m_buffer.size() + remainder); - if (auto result = m_filesystem->do_read(*m_inode, m_buffer, m_file_offset, *m_state); !result) + if (auto result = do_read(*m_inode, m_buffer, m_file_offset, *m_state); !result) { kapi::system::panic("[FS:ext2] failed to read directory entry", result.error()); } diff --git a/kernel/kernel/filesystem/ext2/filesystem.cpp b/kernel/kernel/filesystem/ext2/filesystem.cpp index 4f5ff686..10b5cd66 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.cpp +++ b/kernel/kernel/filesystem/ext2/filesystem.cpp @@ -42,6 +42,7 @@ namespace kernel::filesystem::ext2 //! Check that this driver class is stateless. static_assert(sizeof(kernel::filesystem::filesystem) == sizeof(kernel::filesystem::ext2::filesystem)); + //! Hidden implementation details. namespace { struct indirect_level @@ -56,7 +57,7 @@ namespace kernel::filesystem::ext2 //! //! @param bitmap The bitmap to check. //! @param index The index of the bit to check. - [[nodiscard]] auto bitmap_is_set(kstd::vector<std::byte> const & bitmap, size_t index) -> bool + [[nodiscard]] constexpr auto bitmap_is_set(kstd::vector<std::byte> const & bitmap, size_t index) -> bool { auto const byte_index = index / 8; auto const bit_index = index % 8; @@ -69,7 +70,7 @@ namespace kernel::filesystem::ext2 //! //! @param bitmap The bitmap to manipulate. //! @param index The index of the bit to set. - auto bitmap_set(kstd::vector<std::byte> & bitmap, size_t index) -> void + constexpr auto bitmap_set(kstd::vector<std::byte> & bitmap, size_t index) -> void { auto const byte_index = index / 8; auto const bit_index = index % 8; @@ -97,6 +98,9 @@ namespace kernel::filesystem::ext2 return result; } + //! Get mapping of block indirection levels to possible number of block on that level. + //! + //! @param block_size The size of a single block. [[nodiscard]] constexpr auto indirect_levels(kstd::bytes block_size) -> std::array<indirect_level, 3> { return { @@ -106,6 +110,466 @@ namespace kernel::filesystem::ext2 }; } + //! Extract the file type from the an ext2 file mode. + //! + //! @param mode The mode to extract the type from. + //! @return The file type encoded in the mode. + constexpr auto to_filetype(uint16_t mode) -> kapi::filesystem::file_type + { + switch (mode & constants::mode_mask) + { + case constants::mode_regular: + return kapi::filesystem::file_type::regular; + case constants::mode_directory: + return kapi::filesystem::file_type::directory; + case constants::mode_block_device: + return kapi::filesystem::file_type::block_device; + case constants::mode_character_device: + return kapi::filesystem::file_type::character_device; + case constants::mode_symbolic_link: + return kapi::filesystem::file_type::symbolic_link; + case constants::mode_fifo: + return kapi::filesystem::file_type::fifo; + case constants::mode_socket: + return kapi::filesystem::file_type::socket; + default: + return kapi::filesystem::file_type{0}; + } + } + + //! Combine a file type and an ext2 mode. + //! + //! @param type The type to encode. + //! @param mode The original ext2 file mode. + constexpr auto combine(kapi::filesystem::file_type type, uint16_t mode) -> uint16_t + { + if (mode & constants::mode_mask) + { + return mode; + } + + return mode | [=] { + switch (type) + { + case kapi::filesystem::file_type::directory: + return constants::mode_directory; + case kapi::filesystem::file_type::regular: + return constants::mode_regular; + case kapi::filesystem::file_type::block_device: + return constants::mode_block_device; + case kapi::filesystem::file_type::character_device: + return constants::mode_character_device; + case kapi::filesystem::file_type::symbolic_link: + return constants::mode_symbolic_link; + case kapi::filesystem::file_type::fifo: + return constants::mode_fifo; + case kapi::filesystem::file_type::socket: + return constants::mode_socket; + default: + kapi::system::panic("[EXT2] Not implemented."); + } + }(); + } + + //! Convert a file type to a directory entry file type. + //! + //! @param type The file type to convert. + //! @return The directory file type. + constexpr auto to_directory_file_type(kapi::filesystem::file_type type) -> std::uint8_t + { + switch (type) + { + case kapi::filesystem::file_type::regular: + return 1; + case kapi::filesystem::file_type::directory: + return 2; + case kapi::filesystem::file_type::character_device: + return 3; + case kapi::filesystem::file_type::block_device: + return 4; + case kapi::filesystem::file_type::fifo: + return 5; + case kapi::filesystem::file_type::socket: + return 6; + case kapi::filesystem::file_type::symbolic_link: + return 7; + default: + return 0; + } + } + + //! Write a new directory entry into the given buffer. + //! + //! @param buffer The buffer to write to. + //! @param offset The offset inside to the buffer. + //! @param target The inode the entry targets. + //! @param name The name for the entry. + //! @param type The filet type of the entry. + //! @return A pointer to the written entry on success, an error otherwise. + auto write_directory_entry_to_buffer(std::span<std::byte> buffer, kstd::bytes offset, inode & target, + std::string_view name, kapi::filesystem::file_type type) + -> kstd::result<linked_directory_entry *> + { + auto const name_length = static_cast<uint8_t>(name.size()); + auto const record_size = (sizeof(linked_directory_entry) - 1 + name_length + 3u) & ~3u; + if (buffer.size() - offset.value < record_size) + { + // TODO: improve error code. + return kstd::failure(vfs_errc::invalid_argument); + } + + auto * entry = reinterpret_cast<linked_directory_entry *>(buffer.data() + offset); + entry->inode = target.number(); + entry->rec_len = record_size; + entry->name_len = name_length; + entry->file_type = to_directory_file_type(type); + kstd::libc::memcpy(&entry->name_start, name.data(), name_length); + return kstd::success(entry); + } + + //! Get the revision level of the filesystem. + //! + //! @param state The driver state to operate on. + //! @return The revision level. + [[nodiscard]] constexpr auto revision_level(mount_state const & state) -> uint32_t + { + return state.superblock.rev_level; + } + + //! Get the size of an inode in the filesystem. + //! + //! @param state The driver state to operate on. + //! @return The size of an inode in bytes. + [[nodiscard]] auto inode_size(mount_state const & state) -> kstd::bytes + { + return kstd::bytes(revision_level(state) == constants::good_old_revision ? 128 : state.superblock.inode_size); + } + + //! Read a block from the backing inode into the provided buffer. + //! + //! @param block_number The number of the block to read. + //! @param buffer The buffer to read the block data into. + //! @param state The state to operate on. + //! @return The number of bytes read. + [[nodiscard]] auto read_block(uint32_t block_number, std::span<std::byte> buffer, mount_state const & state) + -> kstd::result<kstd::bytes> + { + if (buffer.size() < block_size(state).value) + { + return kstd::failure(vfs_errc::invalid_argument); + } + + auto const block_offset = static_cast<size_t>(block_number) * block_size(state); + return state.backing_inode->read(buffer, block_offset); + } + + //! Write a block of data from the provided buffer to the backing inode. + //! + //! @param block_number The number of the block to write. + //! @param buffer The buffer containing the data to write. + //! @param state The state to operate on. + //! @return The number of bytes written. + [[nodiscard]] auto write_block(uint32_t block_number, std::span<std::byte const> buffer, mount_state & state) + -> kstd::result<kstd::bytes> + { + if (buffer.size() < block_size(state).value) + { + return kstd::failure(vfs_errc::invalid_argument); + } + + auto const block_offset = static_cast<size_t>(block_number) * block_size(state); + return state.backing_inode->write(buffer, block_offset); + } + + //! Allocate a single inode in the file system. + //! + //! @param state The driver state to operate on. + //! @param batch The active write batch. + //! @return An inode number on success, an error otherwise. + auto allocate_inode(mount_state & state, write_batch & batch) -> kstd::result<uint32_t> + { + auto & [superblock, block_group_descriptors, _, __] = state; + + if (block_group_descriptors.empty() || superblock.free_inodes_count == 0) + { + return kstd::failure(ext2_errc::not_enough_inodes); + } + + for (auto block_group_descriptor_index = 0uz; block_group_descriptor_index < block_group_descriptors.size(); + ++block_group_descriptor_index) + { + auto & block_group_descriptor = block_group_descriptors.at(block_group_descriptor_index); + if (block_group_descriptor.free_inodes_count == 0) + { + continue; + } + + auto inode_bitmap = kstd::vector<std::byte>{block_size(state).value}; + if (auto read_result = read_block(block_group_descriptor.inode_bitmap, inode_bitmap, state); !read_result) + { + return kstd::failure(read_result.error()); + } + + for (auto i = 0uz; i < static_cast<size_t>(superblock.inodes_per_group); ++i) + { + if (!bitmap_is_set(inode_bitmap, i)) + { + bitmap_set(inode_bitmap, i); + block_group_descriptor.free_inodes_count--; + superblock.free_inodes_count--; + + if (auto write_result = write_block(block_group_descriptor.inode_bitmap, inode_bitmap, state); + !write_result) + { + return kstd::failure(write_result.error()); + } + + batch.mark_group(block_group_descriptor_index); + + return block_group_descriptor_index * superblock.inodes_per_group + i + 1; + } + } + } + + return kstd::failure(ext2_errc::not_enough_inodes); + } + + //! Add a new directory entry to a given directory. + //! + //! @param directory The directory to add the entry to. + //! @param name The name for the entry. + //! @param child The inode number of the child. + //! @param mode The file mode of the entry. + //! @param state The driver state to operate on. + //! @param batch The active write batch. + //! @return Nothing on success, an error code otherwise. + auto add_directory_entry(inode & directory, std::string_view name, inode & child, std::uint16_t mode, + mount_state & state, write_batch & batch) -> kstd::result<void> + { + auto const last_block_index = block_count(directory, state) - 1; + auto const global_block = inode_block_number(last_block_index, directory, state); + + if (!global_block) + { + return kstd::failure(global_block.error()); + } + + auto buffer = kstd::vector<std::byte>{block_size(state).value}; + if (auto result = read_block(*global_block, buffer, state); !result) + { + return kstd::failure(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 = 0_B; + while (true) + { + auto const * entry = reinterpret_cast<linked_directory_entry const *>(buffer.data() + offset); + if (offset + kstd::bytes{entry->rec_len} >= block_size(state)) + { + break; + } + offset += kstd::bytes{entry->rec_len}; + } + + auto const file_type = to_filetype(mode); + auto const name_len = static_cast<uint8_t>(name.size()); + auto const needed_rec_len = static_cast<kstd::bytes>((8u + name_len + 3u) & ~3u); + + auto * last_entry = reinterpret_cast<linked_directory_entry *>(buffer.data() + offset); + auto const last_entry_actual_len = static_cast<kstd::bytes>((8u + last_entry->name_len + 3u) & ~3u); + + if (kstd::bytes{last_entry->rec_len} - last_entry_actual_len >= needed_rec_len) + { + last_entry->rec_len = last_entry_actual_len.value; + auto entry_offset = offset + last_entry_actual_len; + auto entry = write_directory_entry_to_buffer(buffer, entry_offset, child, name, file_type); + if (!entry) + { + return kstd::failure(entry.error()); + } + + auto remainder = block_size(state) - entry_offset; + (*entry)->rec_len = remainder.value; + + return global_block.and_then([&](auto block_number) { return write_block(block_number, buffer, state); }) + .transform([](auto) {}); + } + else + { + if (!directory.append_blocks(1, batch)) + { + return kstd::failure(ext2_errc::not_enough_free_blocks); + } + + auto const new_block_index = block_count(directory, state) - 1; + auto const new_global_block = inode_block_number(new_block_index, directory, state); + if (!new_global_block) + { + return kstd::failure(new_global_block.error()); + } + + kstd::libc::memset(buffer.data(), 0, block_size(state).value); + auto entry = write_directory_entry_to_buffer(buffer, 0_B, child, name, file_type); + if (!entry) + { + return kstd::failure(entry.error()); + } + + auto remainder = block_size(state); + (*entry)->rec_len = remainder.value; + + if (auto result = + new_global_block.and_then([&](auto block_number) { return write_block(block_number, buffer, state); }); + !result) + { + return kstd::failure(result.error()); + } + } + + auto size = block_count(directory, state) * block_size(state); + directory.data().size = size.value; + batch.mark_inode(directory); + return kstd::success(); + } + + //! Initialize a new directory inode. + //! + //! This function initializes an inode to be an empty directory. On success, the inode will be a directory with only + //! two children, "." and "..". + //! + //! @param directory The inode to initialize. + //! @param parent The parent of this newly initialized directory. + //! @param state The driver state to operate on. + //! @param batch The active write batch. + //! @return Nothing on success, an error otherwise. + auto init_directory(inode & directory, inode & parent, mount_state & state, write_batch & batch) + -> kstd::result<void> + { + if (!directory.append_blocks(1, batch)) + { + return kstd::failure(ext2_errc::not_enough_free_blocks); + } + + auto const global_block = inode_block_number(0, directory, state); + if (!global_block) + { + return kstd::failure(global_block.error()); + } + + auto buffer = kstd::vector<std::byte>{block_size(state).value}; + auto dot = write_directory_entry_to_buffer(buffer, 0_B, directory, ".", kapi::filesystem::file_type::directory); + + if (!dot) + { + return kstd::failure(dot.error()); + } + + auto dot_dot_offset = kstd::bytes{(*dot)->rec_len}; + auto dot_dot = + write_directory_entry_to_buffer(buffer, dot_dot_offset, parent, "..", kapi::filesystem::file_type::directory); + if (!dot_dot) + { + return kstd::failure(dot_dot.error()); + } + auto remainder = block_size(state) - dot_dot_offset; + (*dot_dot)->rec_len = remainder.value; + + // '..' inside the new dir counts as an extra hard link to the parent + auto & parent_inode_data = parent.data(); + parent_inode_data.links_count++; + + batch.mark_inode(parent); + + return global_block.and_then([&](auto number) { return write_block(number, buffer, state); }).transform([](auto) { + }); + } + + //! Calculate the offset of the inode with a given number in the filesystem. + //! + //! @param number The number of the inode. + //! @param state The driver state to operate on. + //! @return The offset to the filesystem start of the inode. + [[nodiscard]] auto calculate_inode_offset(std::uint32_t number, mount_state const & state) + -> kstd::result<kstd::bytes> + { + auto const inodes_per_group = state.superblock.inodes_per_group; + auto const block_group_index = (number - 1) / inodes_per_group; + auto const inode_index_within_group = (number - 1) % inodes_per_group; + + if (block_group_index >= state.block_group_descriptors.size()) + { + return kstd::failure(ext2_errc::invalid_block_group_index); + } + + auto const & block_group_descriptor = state.block_group_descriptors.at(block_group_index); + auto const inode_table_start_block = block_group_descriptor.inode_table; + auto const inode_table_offset = inode_table_start_block * block_size(state); + auto const inode_offset = inode_table_offset + inode_index_within_group * inode_size(state); + + return inode_offset; + } + + //! Read the inode with the given number from the filesystem. + //! + //! @param number The number of the inode to read. + //! @param state The driver state to operate on. + //! @return The read inode on success, an error otherwise. + [[nodiscard]] auto read_inode(std::uint32_t number, mount_state const & state) + -> kstd::result<kstd::shared_ptr<inode>> + { + if (auto inode_offset = calculate_inode_offset(number, state)) + { + auto new_inode_data = inode_data{}; + + if (auto read_result = state.backing_inode->read(kstd::raw_bytes(new_inode_data), *inode_offset); !read_result) + { + return kstd::failure(read_result.error()); + } + else + { + return kstd::make_shared<struct inode>(number, new_inode_data); + } + } + else + { + return kstd::failure(inode_offset.error()); + } + } + + //! Read the indirect block number from the specified block at the specified index. + //! + //! @param block The number of the indirect block to read from. + //! @param index The index into the indirect block to read from. + //! @param state The driver state to operate on. + //! @return The block number at the given index in the given indirect block on success, an error otherwise. + [[nodiscard]] auto read_block_number_at_index(std::uint32_t block, std::size_t index, mount_state const & state) + -> kstd::result<uint32_t> + { + uint32_t block_number_buffer = 0; + + auto const block_offset = block * block_size(state); + auto const number_offset = block_offset + index * kstd::size_of<uint32_t>(); + + if (auto read_result = state.backing_inode->read(kstd::raw_bytes(block_number_buffer), number_offset); + !read_result) + { + return kstd::failure(read_result.error()); + } + + return block_number_buffer; + } + + //! Get the offset of the Block Group Descriptor table. + //! + //! @param state The driver state to operate on. + //! @return The filesystem offset of the block group descriptor table. + [[nodiscard]] auto block_group_descriptor_table_offset(mount_state const & state) -> kstd::bytes + { + return block_size(state) == 1024_B ? 2 * block_size(state) : block_size(state); + } + //! The set of "incompatible" Extended Filesystem features supported by this driver. constexpr auto supported_incompatible_features = incompatible_features::file_types_in_directory_entries; @@ -229,12 +693,13 @@ namespace kernel::filesystem::ext2 driver_data_ptr driver_data, std::optional<kapi::filesystem::device_number> raw_device) -> kstd::result<kstd::shared_ptr<kernel::filesystem::inode>> { - if (!parent) + auto const ext2_parent = static_pointer_cast<inode>(parent); + if (!ext2_parent) { - return kstd::failure(vfs_errc::invalid_inode); + return kstd::failure(vfs_errc::invalid_argument); } - if (!parent->is_directory()) + if (!ext2_parent->is_directory()) { return kstd::failure(vfs_errc::not_a_directory); } @@ -245,12 +710,6 @@ namespace kernel::filesystem::ext2 return kstd::failure(vfs_errc::invalid_argument); } - auto const ext2_parent = static_pointer_cast<inode>(parent); - if (!ext2_parent) - { - return kstd::failure(vfs_errc::invalid_argument); - } - auto const mount_state = static_pointer_cast<struct mount_state>(driver_data); if (!mount_state) { @@ -258,7 +717,7 @@ namespace kernel::filesystem::ext2 } auto guard = kstd::lock_guard{mount_state->lock}; - auto batch = write_batch{*this, *mount_state}; + auto batch = write_batch{*mount_state}; auto maybe_inode_number = allocate_inode(*mount_state, batch); if (!maybe_inode_number) @@ -271,7 +730,7 @@ namespace kernel::filesystem::ext2 auto data = inode_data{}; // TODO: use correct access rights - data.mode = map_vfs_inode_type_into_inode_mode(type, no_access_restrictions); + data.mode = combine(type, no_access_restrictions); data.size = 0; data.dir_acl = 0; data.links_count = 1; @@ -308,281 +767,89 @@ namespace kernel::filesystem::ext2 return created; } - auto filesystem::block_size(mount_state const & state) const -> kstd::bytes - { - return kstd::bytes{constants::base_block_size.value << state.superblock.log_block_size}; - } - - auto filesystem::revision_level(mount_state const & state) const -> uint32_t - { - return state.superblock.rev_level; - } + //! @name Free Functions + //! @{ - auto filesystem::inode_size(mount_state const & state) const -> kstd::bytes + auto block_size(mount_state const & state) -> kstd::bytes { - return kstd::bytes(revision_level(state) == constants::good_old_revision ? 128 : state.superblock.inode_size); + return kstd::bytes{constants::base_block_size.value << state.superblock.log_block_size}; } - auto filesystem::block_count(inode const & inode, mount_state const & state) const -> uint32_t + auto block_count(inode const & inode, mount_state const & state) -> uint32_t { return inode.data().blocks / (2 << state.superblock.log_block_size); } - auto filesystem::read_block(uint32_t block_number, std::span<std::byte> buffer, mount_state const & state) const - -> kstd::result<kstd::bytes> + auto data_size(inode const & inode, mount_state const & state) -> kstd::bytes { - if (buffer.size() < block_size(state).value) - { - return kstd::failure(vfs_errc::invalid_argument); - } - - auto const block_offset = static_cast<size_t>(block_number) * block_size(state); - return state.backing_inode->read(buffer, block_offset); - } + uint64_t size = inode.data().size; - auto filesystem::write_block(uint32_t block_number, std::span<std::byte const> buffer, mount_state & state) - -> kstd::result<kstd::bytes> - { - if (buffer.size() < block_size(state).value) + if (revision_level(state) > constants::good_old_revision && inode.is_regular()) { - return kstd::failure(vfs_errc::invalid_argument); + size |= static_cast<uint64_t>(inode.data().dir_acl) << 32; } - auto const block_offset = static_cast<size_t>(block_number) * block_size(state); - return state.backing_inode->write(buffer, block_offset); + return kstd::bytes{size}; } - auto filesystem::add_directory_entry(inode & directory, std::string_view name, inode & child, std::uint16_t mode, - mount_state & state, write_batch & batch) -> kstd::result<void> + auto set_data_size(inode & inode, kstd::bytes new_size, mount_state const & state) -> void { - auto const last_block_index = block_count(directory, state) - 1; - auto const global_block = inode_block_number(last_block_index, directory, state); - - if (!global_block) - { - return kstd::failure(global_block.error()); - } - - auto buffer = kstd::vector<std::byte>{block_size(state).value}; - if (auto result = read_block(*global_block, buffer, state); !result) - { - return kstd::failure(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 = 0_B; - while (true) - { - auto const * entry = reinterpret_cast<linked_directory_entry const *>(buffer.data() + offset); - if (offset + kstd::bytes{entry->rec_len} >= block_size(state)) - { - break; - } - offset += kstd::bytes{entry->rec_len}; - } - - auto const file_type = map_inode_mode_to_file_type(mode); - auto const name_len = static_cast<uint8_t>(name.size()); - auto const needed_rec_len = static_cast<kstd::bytes>((8u + name_len + 3u) & ~3u); - - auto * last_entry = reinterpret_cast<linked_directory_entry *>(buffer.data() + offset); - auto const last_entry_actual_len = static_cast<kstd::bytes>((8u + last_entry->name_len + 3u) & ~3u); - - if (kstd::bytes{last_entry->rec_len} - last_entry_actual_len >= needed_rec_len) - { - last_entry->rec_len = last_entry_actual_len.value; - auto entry_offset = offset + last_entry_actual_len; - auto entry = write_directory_entry_to_buffer(buffer, entry_offset, child, name, file_type); - if (!entry) - { - return kstd::failure(entry.error()); - } - - auto remainder = block_size(state) - entry_offset; - (*entry)->rec_len = remainder.value; - - return global_block.and_then([&](auto block_number) { return write_block(block_number, buffer, state); }) - .transform([](auto) {}); - } - else + if (revision_level(state) > constants::good_old_revision && inode.is_regular()) { - if (!directory.append_blocks(1, batch)) - { - return kstd::failure(ext2_errc::not_enough_free_blocks); - } - - auto const new_block_index = block_count(directory, state) - 1; - auto const new_global_block = inode_block_number(new_block_index, directory, state); - if (!new_global_block) - { - return kstd::failure(new_global_block.error()); - } - - kstd::libc::memset(buffer.data(), 0, block_size(state).value); - auto entry = write_directory_entry_to_buffer(buffer, 0_B, child, name, file_type); - if (!entry) - { - return kstd::failure(entry.error()); - } - - auto remainder = block_size(state); - (*entry)->rec_len = remainder.value; - - if (auto result = - new_global_block.and_then([&](auto block_number) { return write_block(block_number, buffer, state); }); - !result) - { - return kstd::failure(result.error()); - } + inode.data().dir_acl = static_cast<uint32_t>(new_size.value >> 32); } - - auto size = block_count(directory, state) * block_size(state); - directory.data().size = size.value; - batch.mark_inode(directory); - return kstd::success(); + inode.data().size = static_cast<uint32_t>(new_size.value); } - auto filesystem::init_directory(inode & directory, inode & parent, mount_state & state, write_batch & batch) - -> kstd::result<void> + auto do_read(inode const & inode, std::span<std::byte> buffer, kstd::bytes offset, mount_state const & state) + -> kstd::result<kstd::bytes> { - if (!directory.append_blocks(1, batch)) - { - return kstd::failure(ext2_errc::not_enough_free_blocks); - } - - auto const global_block = inode_block_number(0, directory, state); - if (!global_block) - { - return kstd::failure(global_block.error()); - } - - auto buffer = kstd::vector<std::byte>{block_size(state).value}; - auto dot = write_directory_entry_to_buffer(buffer, 0_B, directory, ".", kapi::filesystem::file_type::directory); - - if (!dot) - { - return kstd::failure(dot.error()); - } - - auto dot_dot_offset = kstd::bytes{(*dot)->rec_len}; - auto dot_dot = - write_directory_entry_to_buffer(buffer, dot_dot_offset, parent, "..", kapi::filesystem::file_type::directory); - if (!dot_dot) - { - return kstd::failure(dot_dot.error()); - } - auto remainder = block_size(state) - dot_dot_offset; - (*dot_dot)->rec_len = remainder.value; - - // '..' inside the new dir counts as an extra hard link to the parent - auto & parent_inode_data = parent.data(); - parent_inode_data.links_count++; + auto const max_readable = data_size(inode, state) - offset; + auto const requested_size = std::min(kstd::bytes{buffer.size()}, max_readable); - batch.mark_inode(parent); + auto const block_size = ext2::block_size(state); + auto block_index = offset / block_size; + auto in_block_offset = offset % block_size; - return global_block.and_then([&](auto number) { return write_block(number, buffer, state); }).transform([](auto) { - }); - } + auto bytes_read = 0_B; - auto filesystem::write_directory_entry_to_buffer(std::span<std::byte> buffer, kstd::bytes offset, inode & target, - std::string_view name, kapi::filesystem::file_type type) - -> kstd::result<linked_directory_entry *> - { - auto const name_length = static_cast<uint8_t>(name.size()); - auto const record_size = (sizeof(linked_directory_entry) - 1 + name_length + 3u) & ~3u; - if (buffer.size() - offset.value < record_size) + while (bytes_read < requested_size) { - // TODO: improve error code. - return kstd::failure(vfs_errc::invalid_argument); - } - - auto * entry = reinterpret_cast<linked_directory_entry *>(buffer.data() + offset); - entry->inode = target.number(); - entry->rec_len = record_size; - entry->name_len = name_length; - entry->file_type = [=] { - switch (type) + auto const block_number = inode_block_number(block_index, inode, state); + if (!block_number) { - case kapi::filesystem::file_type::regular: - return 1; - case kapi::filesystem::file_type::directory: - return 2; - case kapi::filesystem::file_type::character_device: - return 3; - case kapi::filesystem::file_type::block_device: - return 4; - case kapi::filesystem::file_type::fifo: - return 5; - case kapi::filesystem::file_type::socket: - return 6; - case kapi::filesystem::file_type::symbolic_link: - return 7; - default: - return 0; + break; } - }(); - kstd::libc::memcpy(&entry->name_start, name.data(), name_length); - return kstd::success(entry); - } - - auto filesystem::read_inode(uint32_t inode_number, mount_state const & state) const - -> kstd::result<kstd::shared_ptr<inode>> - { - if (auto inode_offset = calculate_inode_offset(inode_number, state)) - { - auto new_inode_data = inode_data{}; - if (auto read_result = state.backing_inode->read(kstd::raw_bytes(new_inode_data), *inode_offset); !read_result) + auto const bytes_to_read = std::min(requested_size - bytes_read, block_size - in_block_offset); + if (block_number == 0) { - return kstd::failure(read_result.error()); + kstd::libc::memset(buffer.data() + bytes_read, 0, bytes_to_read.value); + bytes_read += bytes_to_read; } else { - return kstd::make_shared<struct inode>(inode_number, new_inode_data); - } - } - else - { - return kstd::failure(inode_offset.error()); - } - } + auto const block_start_offset = block_number.value() * block_size; + auto const read_offset = block_start_offset + in_block_offset; - auto filesystem::write_inode(uint32_t inode_number, inode_data const & data, mount_state & state) - -> kstd::result<void> - { - if (auto inode_offset = calculate_inode_offset(inode_number, state)) - { - return state.backing_inode->write(kstd::raw_bytes(data), *inode_offset).transform([](auto) {}); - } - else - { - return kstd::failure(inode_offset.error()); - } - } + auto const read_result = + state.backing_inode->read(buffer.subspan(bytes_read.value, bytes_to_read.value), read_offset); + if (!read_result) + { + return kstd::failure(read_result.error()); + } - auto filesystem::calculate_inode_offset(uint32_t inode_number, mount_state const & state) const - -> kstd::result<kstd::bytes> - { - auto const inodes_per_group = state.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; + bytes_read += read_result.value(); + } - if (block_group_index >= state.block_group_descriptors.size()) - { - return kstd::failure(ext2_errc::invalid_block_group_index); + block_index++; + in_block_offset = 0_B; } - auto const & block_group_descriptor = state.block_group_descriptors.at(block_group_index); - auto const inode_table_start_block = block_group_descriptor.inode_table; - auto const inode_table_offset = inode_table_start_block * block_size(state); - auto const inode_offset = inode_table_offset + inode_index_within_group * inode_size(state); - - return inode_offset; + return bytes_read; } - auto filesystem::allocate_blocks(size_t count, mount_state & state, write_batch & batch) - -> kstd::result<kstd::vector<uint32_t>> + auto allocate_blocks(size_t count, mount_state & state, write_batch & batch) -> kstd::result<kstd::vector<uint32_t>> { auto & [superblock, block_group_descriptors, backing_inode, _] = state; @@ -654,124 +921,24 @@ namespace kernel::filesystem::ext2 return allocated_blocks; } - auto filesystem::data_size(inode const & inode, mount_state const & state) const -> kstd::bytes - { - uint64_t size = inode.data().size; - - if (revision_level(state) > constants::good_old_revision && inode.is_regular()) - { - size |= static_cast<uint64_t>(inode.data().dir_acl) << 32; - } - - return kstd::bytes{size}; - } - - auto filesystem::set_data_size(inode & inode, kstd::bytes new_size, mount_state const & state) const -> void + auto update_inode_block_count(inode_data & data, uint32_t delta, mount_state & state) -> void { - if (revision_level(state) > constants::good_old_revision && inode.is_regular()) - { - inode.data().dir_acl = static_cast<uint32_t>(new_size.value >> 32); - } - inode.data().size = static_cast<uint32_t>(new_size.value); - } - - auto filesystem::do_read(inode const & inode, std::span<std::byte> buffer, kstd::bytes offset, - mount_state const & state) const -> kstd::result<kstd::bytes> - { - auto const max_readable = data_size(inode, state) - offset; - auto const requested_size = std::min(kstd::bytes{buffer.size()}, max_readable); - - auto const block_size = this->block_size(state); - auto block_index = offset / block_size; - auto in_block_offset = offset % block_size; - - auto bytes_read = 0_B; - - while (bytes_read < requested_size) - { - auto const block_number = inode_block_number(block_index, inode, state); - if (!block_number) - { - break; - } - - auto const bytes_to_read = std::min(requested_size - bytes_read, block_size - in_block_offset); - if (block_number == 0) - { - kstd::libc::memset(buffer.data() + bytes_read, 0, bytes_to_read.value); - bytes_read += bytes_to_read; - } - else - { - auto const block_start_offset = block_number.value() * block_size; - auto const read_offset = block_start_offset + in_block_offset; - - auto const read_result = - state.backing_inode->read(buffer.subspan(bytes_read.value, bytes_to_read.value), read_offset); - if (!read_result) - { - return kstd::failure(read_result.error()); - } - - bytes_read += read_result.value(); - } - - block_index++; - in_block_offset = 0_B; - } - - return bytes_read; + data.blocks += delta * (2 << state.superblock.log_block_size); } - auto filesystem::allocate_inode(mount_state & state, write_batch & batch) -> kstd::result<uint32_t> + auto write_inode(uint32_t inode_number, inode_data const & data, mount_state & state) -> kstd::result<void> { - auto & [superblock, block_group_descriptors, _, __] = state; - - if (block_group_descriptors.empty() || superblock.free_inodes_count == 0) + if (auto inode_offset = calculate_inode_offset(inode_number, state)) { - return kstd::failure(ext2_errc::not_enough_inodes); + return state.backing_inode->write(kstd::raw_bytes(data), *inode_offset).transform([](auto) {}); } - - for (auto block_group_descriptor_index = 0uz; block_group_descriptor_index < block_group_descriptors.size(); - ++block_group_descriptor_index) + else { - auto & block_group_descriptor = block_group_descriptors.at(block_group_descriptor_index); - if (block_group_descriptor.free_inodes_count == 0) - { - continue; - } - - auto inode_bitmap = kstd::vector<std::byte>{block_size(state).value}; - if (auto read_result = read_block(block_group_descriptor.inode_bitmap, inode_bitmap, state); !read_result) - { - return kstd::failure(read_result.error()); - } - - for (auto i = 0uz; i < static_cast<size_t>(superblock.inodes_per_group); ++i) - { - if (!bitmap_is_set(inode_bitmap, i)) - { - bitmap_set(inode_bitmap, i); - block_group_descriptor.free_inodes_count--; - superblock.free_inodes_count--; - - if (auto write_result = write_block(block_group_descriptor.inode_bitmap, inode_bitmap, state); !write_result) - { - return kstd::failure(write_result.error()); - } - - batch.mark_group(block_group_descriptor_index); - - return block_group_descriptor_index * superblock.inodes_per_group + i + 1; - } - } + return kstd::failure(inode_offset.error()); } - - return kstd::failure(ext2_errc::not_enough_inodes); } - auto filesystem::inode_block_number(size_t index, inode const & inode, mount_state const & state) const - -> kstd::result<std::size_t> + auto inode_block_number(size_t index, inode const & inode, mount_state const & state) -> kstd::result<std::size_t> { auto const & block_array = inode.data().block; @@ -781,7 +948,7 @@ namespace kernel::filesystem::ext2 } index -= constants::direct_block_count; - auto const block_size = this->block_size(state); + auto const block_size = ext2::block_size(state); auto const numbers_per_block = block_numbers(1, block_size); for (auto const & level : indirect_levels(block_size)) { @@ -829,25 +996,9 @@ namespace kernel::filesystem::ext2 return kstd::failure(ext2_errc::invalid_block_index); } - auto filesystem::read_block_number_at_index(uint32_t block_number, size_t index, mount_state const & state) const - -> kstd::result<uint32_t> - { - uint32_t block_number_buffer = 0; - - auto const block_start_offset = block_number * block_size(state); - auto const number_start_address = block_start_offset + index * kstd::size_of<uint32_t>(); - if (auto read_result = state.backing_inode->read(kstd::raw_bytes(block_number_buffer), number_start_address); - !read_result) - { - return kstd::failure(read_result.error()); - } - - 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, mount_state & state, - write_batch & batch) -> kstd::result<void> + auto write_global_block_number_to_inode_block_index(size_t inode_block_index, inode_data & data, + uint32_t global_block_number, mount_state & state, + write_batch & batch) -> kstd::result<void> { if (inode_block_index < constants::direct_block_count) { @@ -856,7 +1007,7 @@ namespace kernel::filesystem::ext2 } inode_block_index -= constants::direct_block_count; - auto const block_size = this->block_size(state); + auto const block_size = ext2::block_size(state); auto const numbers_per_block = block_numbers(1, block_size); for (auto const & level : indirect_levels(block_size)) { @@ -928,8 +1079,8 @@ namespace kernel::filesystem::ext2 return kstd::success(); } - auto filesystem::write_block_group_descriptor(block_group_descriptor const & block_group_descriptor, - size_t block_group_descriptor_index, mount_state const & state) const + auto write_block_group_descriptor(block_group_descriptor const & block_group_descriptor, + size_t block_group_descriptor_index, mount_state const & state) -> kstd::result<void> { // TODO update all block group descriptors @@ -940,69 +1091,13 @@ namespace kernel::filesystem::ext2 .transform([](auto) {}); } - auto filesystem::write_superblock(mount_state & state) const -> kstd::result<void> + auto write_superblock(mount_state & state) -> kstd::result<void> { // TODO update all superblock copies return state.backing_inode->write(kstd::raw_bytes(state.superblock), constants::superblock_offset) .transform([](auto) {}); } - auto filesystem::map_inode_mode_to_file_type(uint16_t mode) -> kapi::filesystem::file_type - { - switch (mode & constants::mode_mask) - { - case constants::mode_regular: - return kapi::filesystem::file_type::regular; - case constants::mode_directory: - return kapi::filesystem::file_type::directory; - case constants::mode_block_device: - return kapi::filesystem::file_type::block_device; - case constants::mode_character_device: - return kapi::filesystem::file_type::character_device; - case constants::mode_symbolic_link: - return kapi::filesystem::file_type::symbolic_link; - case constants::mode_fifo: - return kapi::filesystem::file_type::fifo; - case constants::mode_socket: - return kapi::filesystem::file_type::socket; - default: - return kapi::filesystem::file_type{0}; - } - } - - auto filesystem::map_vfs_inode_type_into_inode_mode(kapi::filesystem::file_type type, uint16_t mode) -> uint16_t - { - return mode | [=] { - switch (type) - { - case kapi::filesystem::file_type::directory: - return constants::mode_directory; - case kapi::filesystem::file_type::regular: - return constants::mode_regular; - case kapi::filesystem::file_type::block_device: - return constants::mode_block_device; - case kapi::filesystem::file_type::character_device: - return constants::mode_character_device; - case kapi::filesystem::file_type::symbolic_link: - return constants::mode_symbolic_link; // TODO implement - case kapi::filesystem::file_type::fifo: - return constants::mode_fifo; - case kapi::filesystem::file_type::socket: - return constants::mode_socket; - default: - kapi::system::panic("[EXT2] Not implemented."); - } - }(); - } - - auto filesystem::update_inode_block_count(inode_data & data, uint32_t delta, mount_state & state) -> void - { - data.blocks += delta * (2 << state.superblock.log_block_size); - } - - auto filesystem::block_group_descriptor_table_offset(mount_state const & state) const -> kstd::bytes - { - return block_size(state) == 1024_B ? 2 * block_size(state) : block_size(state); - } + //! @} } // namespace kernel::filesystem::ext2 diff --git a/kernel/kernel/filesystem/ext2/filesystem.hpp b/kernel/kernel/filesystem/ext2/filesystem.hpp index 66db7589..85ce4624 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.hpp +++ b/kernel/kernel/filesystem/ext2/filesystem.hpp @@ -13,7 +13,6 @@ #include <kapi/filesystem.hpp> -#include <kstd/bitfield_enum.hpp> #include <kstd/memory.hpp> #include <kstd/result.hpp> #include <kstd/system_error.hpp> @@ -25,7 +24,6 @@ #include <optional> #include <span> #include <string_view> -#include <type_traits> namespace kernel::filesystem::ext2 { @@ -76,203 +74,91 @@ namespace kernel::filesystem::ext2 -> kstd::result<inode_ptr> override; //! @} - - //! @name Filesystem Information Queries - //! @{ - - //! Get the size of a block in the filesystem. - //! - //! @param state The driver state to operate on. - //! @return The size of a block in bytes. - [[nodiscard]] auto block_size(mount_state const & state) const -> kstd::bytes; - - //! Get the revision level of the filesystem. - //! - //! @param state The driver state to operate on. - //! @return The revision level. - [[nodiscard]] auto revision_level(mount_state const & state) const -> uint32_t; - - //! Get the size of an inode in the filesystem. - //! - //! @param state The driver state to operate on. - //! @return The size of an inode in bytes. - [[nodiscard]] auto inode_size(mount_state const & state) const -> kstd::bytes; - - //! Get the number of blocks allocated to an inode. - //! - //! @param inode The inode whose block count to query. - //! @param state The driver state to operate on. - //! @return The number of blocks allocated to the inode. - [[nodiscard]] auto block_count(inode const & inode, mount_state const & state) const -> uint32_t; - - //! @} - - //! @name Block I/O - //! @{ - - //! Read a block from the backing inode into the provided buffer. - //! - //! @param block_number The number of the block to read. - //! @param buffer The buffer to read the block data into. - //! @param state The state to operate on. - //! @return The number of bytes read. - [[nodiscard]] auto read_block(uint32_t block_number, std::span<std::byte> buffer, mount_state const & state) const - -> kstd::result<kstd::bytes>; - - //! Write a block of data from the provided buffer to the backing inode. - //! - //! @param block_number The number of the block to write. - //! @param buffer The buffer containing the data to write. - //! @param state The state to operate on. - //! @return The number of bytes written. - [[nodiscard]] auto write_block(uint32_t block_number, std::span<std::byte const> buffer, mount_state & state) - -> kstd::result<kstd::bytes>; - - //! @} - - //! Allocate a specified number of blocks. - //! - //! @param count The number of blocks to allocate. - //! @param state The state to operate on. - //! @param batch The active write batch. - //! @return A vector of the allocated block numbers. - auto allocate_blocks(size_t count, mount_state & state, write_batch & batch) - -> kstd::result<kstd::vector<uint32_t>>; - - //! Update the number of blocks allocated to an inode. - //! - //! @param data The inode data. - //! @param state The state to operate on. - //! @param delta The change in the number of blocks. - auto update_inode_block_count(inode_data & data, uint32_t delta, mount_state & state) -> void; - - //! Write an inode to the backing inode. - //! - //! @param inode_number The number of the inode to write. - //! @param state The state to operate on. - //! @param data The inode data to write. - auto write_inode(uint32_t inode_number, inode_data const & data, mount_state & state) -> kstd::result<void>; - - //! Get the block number associated with the nth block of an inode. - //! - //! @param index The index of the block within the inode. - //! @param inode The inode. - //! @param state The state to operate on. - //! @return The global block number on success, an error otherwise. - [[nodiscard]] auto inode_block_number(size_t index, inode const & inode, mount_state const & state) const - -> kstd::result<std::size_t>; - - //! Write 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. - //! @param state The state to operate on. - //! @param batch The active write batch. - auto write_global_block_number_to_inode_block_index(size_t block_index, inode_data & data, - uint32_t global_block_number, mount_state & state, - write_batch & batch) -> kstd::result<void>; - - private: - friend write_batch; - friend inode; - friend directory_iterator; - - //! Perform the actual read of the requested data. - //! - //! @param inode The inode whose data to read. - //! @param buffer The buffer to read into. - //! @param offset The offset inside the inode's data to start at. - //! @param state The driver state to operate on. - //! @return The number of bytes read on success, an error otherwise. - [[nodiscard]] auto do_read(inode const & inode, std::span<std::byte> buffer, kstd::bytes offset, - mount_state const & state) const -> kstd::result<kstd::bytes>; - - //! Determine the size of an inode's data on disk. - //! - //! @param inode The inode whose data size to determine. - //! @param state The driver state to operate on. - //! @return The size of the data referenced by the inode. - [[nodiscard]] auto data_size(inode const & inode, mount_state const & state) const -> kstd::bytes; - - //! Set the size of an inode's data on disk. - //! - //! @param inode The inode whose data size to set. - //! @param new_size The size to set. - //! @param state The driver state to operate on. - auto set_data_size(inode & inode, kstd::bytes new_size, mount_state const & state) const -> void; - - //! Allocate a single inode in the file system. - //! - //! @param state The driver state to operate on. - //! @param batch The active write batch. - //! @return An inode number on success, an error otherwise. - auto allocate_inode(mount_state & state, write_batch & batch) -> kstd::result<uint32_t>; - - //! Add a new directory entry to a given directory. - //! - //! @param directory The directory to add the entry to. - //! @param name The name for the entry. - //! @param child The inode number of the child. - //! @param mode The file mode of the entry. - //! @param state The driver state to operate on. - //! @param batch The active write batch. - //! @return Nothing on success, an error code otherwise. - auto add_directory_entry(inode & directory, std::string_view name, inode & child, std::uint16_t mode, - mount_state & state, write_batch & batch) -> kstd::result<void>; - - //! Initialize a new directory inode. - //! - //! This function initializes an inode to be an empty directory. On success, the inode will be a directory with only - //! two children, "." and "..". - //! - //! @param directory The inode to initialize. - //! @param parent The parent of this newly initialized directory. - //! @param state The driver state to operate on. - //! @param batch The active write batch. - //! @return Nothing on success, an error otherwise. - auto init_directory(inode & directory, inode & parent, mount_state & state, write_batch & batch) - -> kstd::result<void>; - - //! Write a new directory entry into the given buffer. - //! - //! @param buffer The buffer to write to. - //! @param offset The offset inside to the buffer. - //! @param target The inode the entry targets. - //! @param name The name for the entry. - //! @param type The filet type of the entry. - //! @return A pointer to the written entry on success, an error otherwise. - auto write_directory_entry_to_buffer(std::span<std::byte> buffer, kstd::bytes offset, inode & target, - std::string_view name, kapi::filesystem::file_type type) - -> kstd::result<linked_directory_entry *>; - - [[nodiscard]] auto calculate_inode_offset(uint32_t inode_number, mount_state const & state) const - -> kstd::result<kstd::bytes>; - [[nodiscard]] auto read_inode(uint32_t inode_number, mount_state const & state) const - -> kstd::result<kstd::shared_ptr<inode>>; - [[nodiscard]] auto read_block_number_at_index(uint32_t block_number, size_t index, mount_state const & state) const - -> kstd::result<uint32_t>; - - [[nodiscard]] auto block_group_descriptor_table_offset(mount_state const & state) const -> kstd::bytes; - - [[nodiscard]] auto write_block_group_descriptor(block_group_descriptor const & block_group_descriptor, - size_t block_group_descriptor_index, - mount_state const & state) const -> kstd::result<void>; - [[nodiscard]] auto write_superblock(mount_state & state) const -> kstd::result<void>; - - auto map_inode_mode_to_file_type(uint16_t mode) -> kapi::filesystem::file_type; - auto map_vfs_inode_type_into_inode_mode(kapi::filesystem::file_type type, uint16_t mode) -> uint16_t; }; -} // namespace kernel::filesystem::ext2 -template<> -struct kstd::is_bitfield_enum<kernel::filesystem::ext2::incompatible_features> : std::true_type -{ -}; - -template<> -struct kstd::is_bitfield_enum<kernel::filesystem::ext2::read_only_compatible_features> : std::true_type -{ -}; + //! Get the size of a block in the filesystem. + //! + //! @param state The driver state to operate on. + //! @return The size of a block in bytes. + [[nodiscard]] auto block_size(mount_state const & state) -> kstd::bytes; + + //! Get the number of blocks allocated to an inode. + //! + //! @param inode The inode whose block count to query. + //! @param state The driver state to operate on. + //! @return The number of blocks allocated to the inode. + [[nodiscard]] auto block_count(inode const & inode, mount_state const & state) -> uint32_t; + + //! Determine the size of an inode's data on disk. + //! + //! @param inode The inode whose data size to determine. + //! @param state The driver state to operate on. + //! @return The size of the data referenced by the inode. + [[nodiscard]] auto data_size(inode const & inode, mount_state const & state) -> kstd::bytes; + + //! Set the size of an inode's data on disk. + //! + //! @param inode The inode whose data size to set. + //! @param new_size The size to set. + //! @param state The driver state to operate on. + auto set_data_size(inode & inode, kstd::bytes new_size, mount_state const & state) -> void; + + //! Perform the actual read of the requested data. + //! + //! @param inode The inode whose data to read. + //! @param buffer The buffer to read into. + //! @param offset The offset inside the inode's data to start at. + //! @param state The driver state to operate on. + //! @return The number of bytes read on success, an error otherwise. + [[nodiscard]] auto do_read(inode const & inode, std::span<std::byte> buffer, kstd::bytes offset, + mount_state const & state) -> kstd::result<kstd::bytes>; + + //! Allocate a specified number of blocks. + //! + //! @param count The number of blocks to allocate. + //! @param state The state to operate on. + //! @param batch The active write batch. + //! @return A vector of the allocated block numbers. + auto allocate_blocks(size_t count, mount_state & state, write_batch & batch) -> kstd::result<kstd::vector<uint32_t>>; + + //! Update the number of blocks allocated to an inode. + //! + //! @param data The inode data. + //! @param state The state to operate on. + //! @param delta The change in the number of blocks. + auto update_inode_block_count(inode_data & data, uint32_t delta, mount_state & state) -> void; + + //! Write an inode to the backing inode. + //! + //! @param inode_number The number of the inode to write. + //! @param state The state to operate on. + //! @param data The inode data to write. + auto write_inode(uint32_t inode_number, inode_data const & data, mount_state & state) -> kstd::result<void>; + + //! Get the block number associated with the nth block of an inode. + //! + //! @param index The index of the block within the inode. + //! @param inode The inode. + //! @param state The state to operate on. + //! @return The global block number on success, an error otherwise. + [[nodiscard]] auto inode_block_number(size_t index, inode const & inode, mount_state const & state) + -> kstd::result<std::size_t>; + + //! Write 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. + //! @param state The state to operate on. + //! @param batch The active write batch. + auto write_global_block_number_to_inode_block_index(size_t block_index, inode_data & data, + uint32_t global_block_number, mount_state & state, + write_batch & batch) -> kstd::result<void>; + + [[nodiscard]] auto write_block_group_descriptor(block_group_descriptor const & block_group_descriptor, + size_t block_group_descriptor_index, mount_state const & state) + -> kstd::result<void>; + [[nodiscard]] auto write_superblock(mount_state & state) -> kstd::result<void>; +} // namespace kernel::filesystem::ext2 #endif diff --git a/kernel/kernel/filesystem/ext2/filesystem.tests.cpp b/kernel/kernel/filesystem/ext2/filesystem.tests.cpp index 28aabed7..8565db4d 100644 --- a/kernel/kernel/filesystem/ext2/filesystem.tests.cpp +++ b/kernel/kernel/filesystem/ext2/filesystem.tests.cpp @@ -241,16 +241,16 @@ SCENARIO("Ext2 block mapping includes direct and all indirect levels", "[filesys THEN("mapping resolves direct, singly, doubly and triply indirect indexes") { - REQUIRE(fs->inode_block_number(0, inode, *mount_state) == 7); - REQUIRE(fs->inode_block_number(singly_start, inode, *mount_state) == 31); - REQUIRE(fs->inode_block_number(doubly_start, inode, *mount_state) == 42); - REQUIRE(fs->inode_block_number(triply_start, inode, *mount_state) == 53); + REQUIRE(inode_block_number(0, inode, *mount_state) == 7); + REQUIRE(inode_block_number(singly_start, inode, *mount_state) == 31); + REQUIRE(inode_block_number(doubly_start, inode, *mount_state) == 42); + REQUIRE(inode_block_number(triply_start, inode, *mount_state) == 53); } THEN("mapping returns error for out-of-range indexes") { auto const beyond_triply = triply_start + numbers_per_block * numbers_per_block * numbers_per_block; - REQUIRE(!fs->inode_block_number(beyond_triply, inode, *mount_state)); + REQUIRE(!inode_block_number(beyond_triply, inode, *mount_state)); } } } @@ -314,32 +314,32 @@ SCENARIO("Ext2 block writing includes direct and all indirect levels", "[filesys constexpr auto doubly_global_block_number = 303; constexpr auto triply_global_block_number = 404; - auto batch = kernel::filesystem::ext2::write_batch{*fs, *mount_state}; + auto batch = kernel::filesystem::ext2::write_batch{*mount_state}; - REQUIRE(fs->write_global_block_number_to_inode_block_index(0, inode_data, direct_global_block_number, - *mount_state, batch)); + REQUIRE(write_global_block_number_to_inode_block_index(0, inode_data, direct_global_block_number, *mount_state, + batch)); 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, - *mount_state, batch)); + REQUIRE(write_global_block_number_to_inode_block_index(singly_start, inode_data, singly_global_block_number, + *mount_state, batch)); REQUIRE(inode_data.block[12] == 16); REQUIRE(read_u32(static_cast<kstd::bytes>(inode_data.block[12]) * block_size.value) == singly_global_block_number); - REQUIRE(fs->write_global_block_number_to_inode_block_index(singly_start + 1, inode_data, - singly_global_block_number + 1, *mount_state, batch)); + REQUIRE(write_global_block_number_to_inode_block_index(singly_start + 1, inode_data, + singly_global_block_number + 1, *mount_state, batch)); REQUIRE(read_u32(static_cast<kstd::bytes>(inode_data.block[12]) * block_size.value + 1 * kstd::size_of<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, - *mount_state, batch)); + REQUIRE(write_global_block_number_to_inode_block_index(doubly_start, inode_data, doubly_global_block_number, + *mount_state, batch)); REQUIRE(inode_data.block[13] == 17); auto const doubly_leaf_table = read_u32(static_cast<size_t>(inode_data.block[13]) * block_size); REQUIRE(doubly_leaf_table == 18); REQUIRE(read_u32(static_cast<size_t>(doubly_leaf_table) * block_size) == doubly_global_block_number); - REQUIRE(fs->write_global_block_number_to_inode_block_index(triply_start, inode_data, triply_global_block_number, - *mount_state, batch)); + REQUIRE(write_global_block_number_to_inode_block_index(triply_start, inode_data, triply_global_block_number, + *mount_state, batch)); REQUIRE(inode_data.block[14] == 19); auto const triply_middle_table = read_u32(static_cast<size_t>(inode_data.block[14]) * block_size); REQUIRE(triply_middle_table == 20); @@ -349,10 +349,10 @@ SCENARIO("Ext2 block writing includes direct and all indirect levels", "[filesys auto inode = kernel::filesystem::ext2::inode{42, inode_data}; - REQUIRE(fs->inode_block_number(0, inode, *mount_state) == direct_global_block_number); - REQUIRE(fs->inode_block_number(singly_start, inode, *mount_state) == singly_global_block_number); - REQUIRE(fs->inode_block_number(doubly_start, inode, *mount_state) == doubly_global_block_number); - REQUIRE(fs->inode_block_number(triply_start, inode, *mount_state) == triply_global_block_number); + REQUIRE(inode_block_number(0, inode, *mount_state) == direct_global_block_number); + REQUIRE(inode_block_number(singly_start, inode, *mount_state) == singly_global_block_number); + REQUIRE(inode_block_number(doubly_start, inode, *mount_state) == doubly_global_block_number); + REQUIRE(inode_block_number(triply_start, inode, *mount_state) == triply_global_block_number); } } } diff --git a/kernel/kernel/filesystem/ext2/inode.cpp b/kernel/kernel/filesystem/ext2/inode.cpp index 075d6c5f..aa0c6368 100644 --- a/kernel/kernel/filesystem/ext2/inode.cpp +++ b/kernel/kernel/filesystem/ext2/inode.cpp @@ -43,12 +43,6 @@ namespace kernel::filesystem::ext2 return requested_size; } - auto filesystem = get_filesystem(); - if (!filesystem) - { - return kstd::failure(filesystem.error()); - } - auto state = get_driver_data(); if (!state) { @@ -62,7 +56,7 @@ namespace kernel::filesystem::ext2 } auto guard = kstd::lock_guard{(*state)->lock}; - return (*filesystem)->do_read(*this, buffer, offset, **state); + return ext2::do_read(*this, buffer, offset, **state); } auto inode::write(std::span<std::byte const> buffer, kstd::bytes offset) -> kstd::result<kstd::bytes> @@ -75,12 +69,6 @@ namespace kernel::filesystem::ext2 // TODO check maximum file size of filesystem // TODO handle sparse files - auto filesystem = get_filesystem(); - if (!filesystem) - { - return kstd::failure(filesystem.error()); - } - auto state = get_driver_data(); if (!state) { @@ -94,12 +82,12 @@ namespace kernel::filesystem::ext2 } auto guard = kstd::lock_guard{(*state)->lock}; - auto batch = write_batch{**filesystem, **state}; + auto batch = write_batch{**state}; - auto const new_inode_size = std::max((*filesystem)->data_size(*this, **state), offset + kstd::bytes{buffer.size()}); + auto const new_inode_size = std::max(ext2::data_size(*this, **state), offset + kstd::bytes{buffer.size()}); - auto block_size = (*filesystem)->block_size(**state); - auto const current_block_count = (*filesystem)->block_count(*this, **state); + auto block_size = ext2::block_size(**state); + auto const current_block_count = ext2::block_count(*this, **state); auto const max_new_inode_size_without_new_blocks = current_block_count * block_size; if (new_inode_size > max_new_inode_size_without_new_blocks) @@ -120,7 +108,7 @@ namespace kernel::filesystem::ext2 while (bytes_written < kstd::bytes{buffer.size()}) { - auto const block_number = (*filesystem)->inode_block_number(block_index, *this, **state); + auto const block_number = ext2::inode_block_number(block_index, *this, **state); // TODO BA-FS26 if blocknumber == 0 --> handle sparse file if (!block_number) { @@ -145,8 +133,7 @@ namespace kernel::filesystem::ext2 in_block_offset = 0_B; } - (*filesystem) - ->set_data_size(*this, std::max((*filesystem)->data_size(*this, **state), offset + bytes_written), **state); + ext2::set_data_size(*this, std::max(ext2::data_size(*this, **state), offset + bytes_written), **state); batch.mark_inode(*this); return bytes_written; } @@ -209,19 +196,13 @@ namespace kernel::filesystem::ext2 auto inode::append_blocks(size_t count, write_batch & batch) -> bool { - auto filesystem = get_filesystem(); - if (!filesystem) - { - return false; - } - auto state = get_driver_data(); if (!state) { return false; } - auto new_blocks = (*filesystem)->allocate_blocks(count, **state, batch); + auto new_blocks = ext2::allocate_blocks(count, **state, batch); if (!new_blocks) { return false; @@ -229,11 +210,10 @@ namespace kernel::filesystem::ext2 for (auto i = 0uz; i < new_blocks->size(); ++i) { - auto const block_index = (*filesystem)->block_count(*this, **state) + i; + auto const block_index = ext2::block_count(*this, **state) + i; auto const global_block_number = new_blocks->at(i); - if (auto write_result = (*filesystem) - ->write_global_block_number_to_inode_block_index(block_index, m_data, + if (auto write_result = ext2::write_global_block_number_to_inode_block_index(block_index, m_data, global_block_number, **state, batch); !write_result) { @@ -241,18 +221,12 @@ namespace kernel::filesystem::ext2 } } - (*filesystem)->update_inode_block_count(m_data, count, **state); + ext2::update_inode_block_count(m_data, count, **state); return true; } auto inode::set_size(kstd::bytes new_size) -> void { - auto filesystem = get_filesystem(); - if (!filesystem) - { - return; - } - auto driver_data = get_driver_data(); if (!driver_data) { @@ -261,7 +235,7 @@ namespace kernel::filesystem::ext2 auto state = static_pointer_cast<mount_state>(*driver_data); auto guard = kstd::lock_guard{state->lock}; - return (*filesystem)->set_data_size(*this, new_size, *state); + return ext2::set_data_size(*this, new_size, *state); } [[nodiscard]] auto inode::data() -> inode_data & @@ -276,12 +250,6 @@ namespace kernel::filesystem::ext2 auto inode::size() const -> kstd::bytes { - auto filesystem = get_filesystem(); - if (!filesystem) - { - return 0_B; - } - auto state = get_driver_data(); if (!state) { @@ -289,7 +257,7 @@ namespace kernel::filesystem::ext2 } auto guard = kstd::lock_guard{(*state)->lock}; - return (*filesystem)->data_size(*this, **state); + return ext2::data_size(*this, **state); } [[nodiscard]] auto inode::number() const -> uint32_t @@ -297,26 +265,6 @@ namespace kernel::filesystem::ext2 return m_inode_number; } - auto inode::get_filesystem() -> kstd::result<kstd::shared_ptr<filesystem>> - { - if (auto mount = owning_mount().lock()) - { - return static_pointer_cast<filesystem>(mount->filesystem()); - } - - return kstd::failure(vfs_errc::not_mounted); - } - - auto inode::get_filesystem() const -> kstd::result<kstd::shared_ptr<filesystem const>> - { - if (auto mount = owning_mount().lock()) - { - return static_pointer_cast<filesystem>(mount->filesystem()); - } - - return kstd::failure(vfs_errc::not_mounted); - } - auto inode::get_driver_data() -> kstd::result<kstd::shared_ptr<mount_state>> { if (auto mount = owning_mount().lock()) diff --git a/kernel/kernel/filesystem/ext2/inode.hpp b/kernel/kernel/filesystem/ext2/inode.hpp index 5393a853..b506be55 100644 --- a/kernel/kernel/filesystem/ext2/inode.hpp +++ b/kernel/kernel/filesystem/ext2/inode.hpp @@ -134,16 +134,6 @@ namespace kernel::filesystem::ext2 //! @} private: - //! Get the filesystem driver associated with this inode. - //! - //! @return The filesystem driver on success, and error otherwise. - [[nodiscard]] auto get_filesystem() -> kstd::result<kstd::shared_ptr<filesystem>>; - - //! Get the filesystem driver associated with this inode. - //! - //! @return The filesystem driver on success, and error otherwise. - [[nodiscard]] auto get_filesystem() const -> kstd::result<kstd::shared_ptr<filesystem const>>; - //! Get the filesystem driver data associated with this inode. //! //! @return The filesystem driver data on success, and error otherwise. diff --git a/kernel/kernel/filesystem/ext2/inode.tests.cpp b/kernel/kernel/filesystem/ext2/inode.tests.cpp index eab0aabd..bf5d9ab7 100644 --- a/kernel/kernel/filesystem/ext2/inode.tests.cpp +++ b/kernel/kernel/filesystem/ext2/inode.tests.cpp @@ -358,8 +358,8 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, "Ext2 in { auto inode = static_pointer_cast<kernel::filesystem::ext2::inode>(file.value()); - auto const block_size = fs->block_size(*mount_state); - auto const expected_allocated_blocks = 32 * fs->block_count(*inode, *mount_state); + auto const block_size = kernel::filesystem::ext2::block_size(*mount_state); + auto const expected_allocated_blocks = 32 * block_count(*inode, *mount_state); auto write_buffer = kstd::vector<std::byte>(block_size.value * expected_allocated_blocks, std::byte{'A'}); @@ -372,7 +372,7 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, "Ext2 in REQUIRE(std::ranges::all_of(read_buffer, [](std::byte c) { return c == std::byte{'A'}; })); REQUIRE(inode->size().value == write_buffer.size()); - REQUIRE(fs->block_count(*inode, *mount_state) == expected_allocated_blocks); + REQUIRE(block_count(*inode, *mount_state) == expected_allocated_blocks); } THEN("writing to a an inode of type directory panics") @@ -431,8 +431,8 @@ 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(*mount_state); + auto const inode_data_offset = block_group_descriptor.inode_table * block_size + + kstd::bytes{(inode_number - 1) * mount_state->superblock.inode_size}; auto inode = kernel::filesystem::ext2::inode{inode_number, inode_data}; inode.set_owning_mount(*mount); diff --git a/kernel/kernel/filesystem/ext2/superblock.hpp b/kernel/kernel/filesystem/ext2/superblock.hpp index e5cd845b..dce3cb29 100644 --- a/kernel/kernel/filesystem/ext2/superblock.hpp +++ b/kernel/kernel/filesystem/ext2/superblock.hpp @@ -1,8 +1,11 @@ #ifndef TEACHOS_KERNEL_FILESYSTEM_EXT2_SUPERBLOCK_HPP #define TEACHOS_KERNEL_FILESYSTEM_EXT2_SUPERBLOCK_HPP +#include <kstd/bitfield_enum.hpp> + #include <array> #include <cstdint> +#include <type_traits> namespace kernel::filesystem::ext2 { @@ -105,4 +108,15 @@ namespace kernel::filesystem::ext2 std::array<uint8_t, 760> unused; // NOLINT(readability-magic-numbers) }; } // namespace kernel::filesystem::ext2 + +template<> +struct kstd::is_bitfield_enum<kernel::filesystem::ext2::incompatible_features> : std::true_type +{ +}; + +template<> +struct kstd::is_bitfield_enum<kernel::filesystem::ext2::read_only_compatible_features> : std::true_type +{ +}; + #endif
\ No newline at end of file diff --git a/kernel/kernel/filesystem/ext2/write_batch.cpp b/kernel/kernel/filesystem/ext2/write_batch.cpp index e8663d21..3ea6ef19 100644 --- a/kernel/kernel/filesystem/ext2/write_batch.cpp +++ b/kernel/kernel/filesystem/ext2/write_batch.cpp @@ -13,16 +13,15 @@ namespace kernel::filesystem::ext2 { - write_batch::write_batch(filesystem & filesystem, mount_state & state) - : m_filesystem{filesystem} - , m_state{state} + write_batch::write_batch(mount_state & state) + : m_state{state} {} write_batch::~write_batch() { std::ranges::for_each(m_dirty_groups, [this](auto index) { auto & descriptor = m_state.block_group_descriptors.at(index); - auto result = m_filesystem.write_block_group_descriptor(descriptor, index, m_state); + auto result = ext2::write_block_group_descriptor(descriptor, index, m_state); if (!result) { @@ -32,7 +31,7 @@ namespace kernel::filesystem::ext2 if (!m_dirty_groups.empty()) { - auto result = m_filesystem.write_superblock(m_state); + auto result = ext2::write_superblock(m_state); if (!result) { kapi::system::panic("[FS:ext2] failed to write superblock", result.error()); @@ -40,7 +39,7 @@ namespace kernel::filesystem::ext2 } std::ranges::for_each(m_dirty_inodes, [this](auto inode) { - auto result = m_filesystem.write_inode(inode->number(), inode->data(), m_state); + auto result = ext2::write_inode(inode->number(), inode->data(), m_state); if (!result) { kapi::system::panic("[FS:ext2] failed to write inode", result.error()); diff --git a/kernel/kernel/filesystem/ext2/write_batch.hpp b/kernel/kernel/filesystem/ext2/write_batch.hpp index f4000391..110d089c 100644 --- a/kernel/kernel/filesystem/ext2/write_batch.hpp +++ b/kernel/kernel/filesystem/ext2/write_batch.hpp @@ -19,9 +19,8 @@ namespace kernel::filesystem::ext2 { //! Construct a new write batch for the given filesystem and mount state. //! - //! @param filesystem The filesystem driver to use for writes. //! @param state The driver state to operate on. - write_batch(filesystem & filesystem, mount_state & state); + explicit write_batch(mount_state & state); write_batch(write_batch const &) = delete; write_batch(write_batch &&) = delete; @@ -42,7 +41,6 @@ namespace kernel::filesystem::ext2 auto mark_inode(inode & inode) -> void; private: - filesystem & m_filesystem; mount_state & m_state; kstd::vector<std::size_t> m_dirty_groups{}; kstd::vector<inode *> m_dirty_inodes{}; |
