Skip to content

Commit

Permalink
Kernel/Ext2FS: Support writing holes
Browse files Browse the repository at this point in the history
With this change, we no longer preallocate blocks when an inode's size
is updated, and instead only allocate the minimum amount of blocks when
the inode is actually written to.
  • Loading branch information
implicitfield committed Apr 28, 2024
1 parent ab0aa9f commit eb56b07
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 63 deletions.
108 changes: 46 additions & 62 deletions Kernel/FileSystem/Ext2FS/Inode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -485,77 +485,41 @@ ErrorOr<size_t> Ext2FSInode::read_bytes_locked(off_t offset, size_t count, UserO
ErrorOr<void> Ext2FSInode::resize(u64 new_size)
{
VERIFY(m_inode_lock.is_locked());
auto old_size = size();
if (old_size == new_size)
if (size() == new_size)
return {};

if (!((u32)fs().get_features_readonly() & (u32)Ext2FS::FeaturesReadOnly::FileSize64bits) && (new_size >= static_cast<u32>(-1)))
return ENOSPC;

u64 block_size = fs().logical_block_size();
auto blocks_needed_before = ceil_div(old_size, block_size);
auto blocks_needed_after = ceil_div(new_size, block_size);
if (new_size < size()) {
auto block_size = fs().logical_block_size();
BlockBasedFileSystem::BlockIndex first_block_logical_index = ceil_div(new_size, block_size);
BlockBasedFileSystem::BlockIndex last_block_logical_index = size() / block_size;

if constexpr (EXT2_DEBUG) {
dbgln("Ext2FSInode[{}]::resize(): Blocks needed before (size was {}): {}", identifier(), old_size, blocks_needed_before);
dbgln("Ext2FSInode[{}]::resize(): Blocks needed after (size is {}): {}", identifier(), new_size, blocks_needed_after);
}

if (blocks_needed_after > blocks_needed_before) {
auto additional_blocks_needed = blocks_needed_after - blocks_needed_before;
if (additional_blocks_needed > fs().super_block().s_free_blocks_count)
return ENOSPC;
}

if (m_block_list.is_empty())
m_block_list = TRY(compute_block_list());

auto old_block_list = TRY(m_block_list.clone());
if (m_block_list.is_empty())
m_block_list = TRY(compute_block_list());
auto old_block_list = TRY(m_block_list.clone());

if (blocks_needed_after > blocks_needed_before) {
auto blocks = TRY(fs().allocate_blocks(fs().group_index_from_inode(index()), blocks_needed_after - blocks_needed_before));
for (auto const& block : blocks)
TRY(m_block_list.try_set(m_block_list.size(), block));
} else if (blocks_needed_after < blocks_needed_before) {
if constexpr (EXT2_VERY_DEBUG) {
dbgln("Ext2FSInode[{}]::resize(): Shrinking inode, old block list is {} entries:", identifier(), m_block_list.size());
for (auto block_index : m_block_list) {
dbgln(" # {}", block_index);
for (auto bi = first_block_logical_index; bi <= last_block_logical_index; bi = bi.value() + 1) {
auto block = get_block(bi);
if (block == 0) {
// This is a hole, skip it.
continue;
}
}
auto blocks = TRY(get_blocks(0, m_block_list.size()));
while (m_block_list.size() != blocks_needed_after) {
m_block_list.remove(blocks.size() - 1);
auto block = blocks.take_last();
if (auto result = fs().set_block_allocation_state(block, false); result.is_error()) {
dbgln("Ext2FSInode[{}]::resize(): Failed to free block {}: {}", identifier(), block, result.error());
return result;
}
m_block_list.remove(bi);
}
TRY(flush_block_list(old_block_list));
}

TRY(flush_block_list(old_block_list));

m_raw_inode.i_size = new_size;
if (Kernel::is_regular_file(m_raw_inode.i_mode))
m_raw_inode.i_dir_acl = new_size >> 32;

set_metadata_dirty(true);

if (new_size > old_size) {
// If we're growing the inode, make sure we zero out all the new space.
// FIXME: There are definitely more efficient ways to achieve this.
auto bytes_to_clear = new_size - old_size;
auto clear_from = old_size;
u8 zero_buffer[PAGE_SIZE] {};
while (bytes_to_clear) {
auto nwritten = TRY(prepare_and_write_bytes_locked(clear_from, min(static_cast<u64>(sizeof(zero_buffer)), bytes_to_clear), UserOrKernelBuffer::for_kernel_buffer(zero_buffer), nullptr));
VERIFY(nwritten != 0);
bytes_to_clear -= nwritten;
clear_from += nwritten;
}
}

return {};
}

Expand Down Expand Up @@ -598,11 +562,13 @@ ErrorOr<size_t> Ext2FSInode::write_bytes_locked(off_t offset, size_t count, User
auto current_block_logical_index = first_block_logical_index;

dbgln_if(EXT2_VERY_DEBUG, "Ext2FSInode[{}]::write_bytes_locked(): Writing {} bytes, {} bytes into inode from {}", identifier(), count, offset, data.user_or_kernel_ptr());
auto old_block_list = TRY(m_block_list.clone());

while (remaining_count) {
auto block_index = get_block(current_block_logical_index);
size_t offset_into_block = (current_block_logical_index == first_block_logical_index) ? offset_into_first_block : 0;
size_t num_bytes_to_copy = min((size_t)block_size - offset_into_block, (size_t)remaining_count);
auto block_index = TRY(get_or_allocate_block(current_block_logical_index, num_bytes_to_copy != block_size, allow_cache));

dbgln_if(EXT2_DEBUG, "Ext2FSInode[{}]::write_bytes_locked(): Writing block {} (offset_into_block: {})", identifier(), block_index, offset_into_block);
if (auto result = fs().write_block(block_index, data.offset(nwritten), num_bytes_to_copy, offset_into_block, allow_cache); result.is_error()) {
dbgln("Ext2FSInode[{}]::write_bytes_locked(): Failed to write block {} (index {})", identifier(), block_index, current_block_logical_index);
Expand All @@ -613,6 +579,7 @@ ErrorOr<size_t> Ext2FSInode::write_bytes_locked(off_t offset, size_t count, User
nwritten += num_bytes_to_copy;
}

TRY(flush_block_list(old_block_list));
did_modify_contents();

dbgln_if(EXT2_VERY_DEBUG, "Ext2FSInode[{}]::write_bytes_locked(): After write, i_size={}, i_blocks={} ({} blocks in list)", identifier(), size(), m_raw_inode.i_blocks, m_block_list.size());
Expand Down Expand Up @@ -712,23 +679,40 @@ ErrorOr<void> Ext2FSInode::write_directory(Vector<Ext2FSDirectoryEntry>& entries
return {};
}

BlockBasedFileSystem::BlockIndex Ext2FSInode::get_block(BlockBasedFileSystem::BlockIndex block_index) const
ErrorOr<BlockBasedFileSystem::BlockIndex> Ext2FSInode::get_or_allocate_block(BlockBasedFileSystem::BlockIndex block_index, bool zero_newly_allocated_block, bool allow_cache)
{
auto it = m_block_list.find(block_index);
if (it == m_block_list.end())
return 0;
if (it != m_block_list.end()) {
auto block = (*it).value;
VERIFY(block != 0);
return block;
}

return (*it).value;
// FIXME: Preallocate some extra blocks here.
auto blocks = TRY(fs().allocate_blocks(fs().group_index_from_inode(index()), 1));

VERIFY(blocks.size() == 1);
auto block = blocks.first();
TRY(m_block_list.try_set(block_index, block));

if (zero_newly_allocated_block) {
u8 zero_buffer[PAGE_SIZE] {};
if (auto result = fs().write_block(block, UserOrKernelBuffer::for_kernel_buffer(zero_buffer), fs().logical_block_size(), 0, allow_cache); result.is_error()) {
dbgln("Ext2FSInode[{}]::get_or_allocate_block(): Failed to zero block {} (index {})", identifier(), block, block_index);
return result.release_error();
}
}

return block;
}

ErrorOr<Vector<BlockBasedFileSystem::BlockIndex>> Ext2FSInode::get_blocks(BlockBasedFileSystem::BlockIndex first_block_index, u64 count) const
BlockBasedFileSystem::BlockIndex Ext2FSInode::get_block(BlockBasedFileSystem::BlockIndex block_index) const
{
Vector<BlockBasedFileSystem::BlockIndex> blocks;
TRY(blocks.try_ensure_capacity(count));
for (auto block = first_block_index; block < first_block_index.value() + count; block = block.value() + 1)
blocks.unchecked_append(get_block(block));
auto it = m_block_list.find(block_index);
if (it == m_block_list.end())
return 0;

return blocks;
return (*it).value;
}

ErrorOr<NonnullRefPtr<Inode>> Ext2FSInode::create_child(StringView name, mode_t mode, dev_t dev, UserID uid, GroupID gid)
Expand Down
2 changes: 1 addition & 1 deletion Kernel/FileSystem/Ext2FS/Inode.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ class Ext2FSInode final : public Inode {
virtual ErrorOr<void> truncate_locked(u64) override;
virtual ErrorOr<int> get_block_address(int) override;

ErrorOr<BlockBasedFileSystem::BlockIndex> get_or_allocate_block(BlockBasedFileSystem::BlockIndex, bool zero_newly_allocated_block, bool allow_cache);
BlockBasedFileSystem::BlockIndex get_block(BlockBasedFileSystem::BlockIndex) const;
ErrorOr<Vector<BlockBasedFileSystem::BlockIndex>> get_blocks(BlockBasedFileSystem::BlockIndex first_block_index, u64 count) const;
ErrorOr<u32> allocate_and_zero_block();

ErrorOr<void> write_directory(Vector<Ext2FSDirectoryEntry>&);
Expand Down

0 comments on commit eb56b07

Please sign in to comment.