diff --git a/Kernel/FileSystem/FATFS/FileSystem.cpp b/Kernel/FileSystem/FATFS/FileSystem.cpp index ff0617a1c3f..974166b2c85 100644 --- a/Kernel/FileSystem/FATFS/FileSystem.cpp +++ b/Kernel/FileSystem/FATFS/FileSystem.cpp @@ -371,6 +371,36 @@ u32 FATFS::end_of_chain_marker() const } } +ErrorOr FATFS::allocate_cluster() +{ + u32 start_cluster; + if (m_fat_version == FATVersion::FAT32) { + // If we have a hint, start there. + if (m_fs_info.next_free_cluster_hint != fs_info_data_unknown) { + start_cluster = m_fs_info.next_free_cluster_hint; + } else { + // Otherwise, start at the beginning of the data area. + start_cluster = first_data_cluster; + } + } else { + // For FAT12/16, start at the beginning of the data area, as there is no + // FSInfo struct to store the hint. + start_cluster = first_data_cluster; + } + + MutexLocker locker(m_lock); + + for (u32 i = start_cluster; i < m_parameter_block->sector_count() / m_parameter_block->common_bpb()->sectors_per_cluster; i++) { + if (TRY(fat_read(i)) == 0) { + dbgln_if(FAT_DEBUG, "FATFS: Allocating cluster {}", i); + TRY(fat_write(i, end_of_chain_marker())); + return i; + } + } + + return Error::from_errno(ENOSPC); +} + ErrorOr FATFS::fat_read(u32 cluster) { dbgln_if(FAT_DEBUG, "FATFS: Reading FAT entry for cluster {}", cluster); diff --git a/Kernel/FileSystem/FATFS/FileSystem.h b/Kernel/FileSystem/FATFS/FileSystem.h index 8c028979a0a..736ca4f3726 100644 --- a/Kernel/FileSystem/FATFS/FileSystem.h +++ b/Kernel/FileSystem/FATFS/FileSystem.h @@ -82,9 +82,12 @@ private: static constexpr u32 fs_info_signature_2 = 0x61417272; static constexpr u32 fs_info_signature_3 = 0xAA550000; + static constexpr u32 fs_info_data_unknown = 0xFFFFFFFF; + static constexpr u32 first_data_cluster = 2; FatBlockSpan first_block_of_cluster(u32 cluster) const; + ErrorOr allocate_cluster(); size_t fat_offset_for_cluster(u32 cluster) const; diff --git a/Kernel/FileSystem/FATFS/Inode.cpp b/Kernel/FileSystem/FATFS/Inode.cpp index 7f0862e03d6..a6a38991c69 100644 --- a/Kernel/FileSystem/FATFS/Inode.cpp +++ b/Kernel/FileSystem/FATFS/Inode.cpp @@ -240,6 +240,67 @@ u32 FATInode::first_cluster(FATVersion const version) const return m_entry.first_cluster_low; } +ErrorOr FATInode::allocate_and_add_cluster_to_chain() +{ + VERIFY(m_inode_lock.is_locked()); + + u32 allocated_cluster = TRY(fs().allocate_cluster()); + dbgln_if(FAT_DEBUG, "FATInode[{}]::allocate_and_add_cluster_to_chain(): allocated cluster {}", identifier(), allocated_cluster); + + if (m_cluster_list.is_empty() || (m_cluster_list.size() == 1 && first_cluster() <= 1)) { + // This is the first cluster in the chain, so update the inode metadata. + if (fs().m_fat_version == FATVersion::FAT32) { + // Only FAT32 uses the `first_cluster_high` field. + m_entry.first_cluster_high = allocated_cluster >> 16; + } + + m_entry.first_cluster_low = allocated_cluster & 0xFFFF; + + set_metadata_dirty(true); + } else { + // This is not the first cluster in the chain, so we need to update the + // FAT entry for the last cluster in the chain to point to the newly + // allocated cluster. + TRY(fs().fat_write(m_cluster_list[m_cluster_list.size() - 1], allocated_cluster)); + } + + m_cluster_list.append(allocated_cluster); + + return {}; +} + +ErrorOr FATInode::remove_last_cluster_from_chain() +{ + VERIFY(m_inode_lock.is_locked()); + VERIFY(m_cluster_list.size() > 0); + + u32 last_cluster = m_cluster_list[m_cluster_list.size() - 1]; + TRY(fs().fat_write(last_cluster, 0x0)); + + dbgln_if(FAT_DEBUG, "FATInode[{}]::remove_last_cluster_from_chain(): freeing cluster {}", identifier(), last_cluster); + + m_cluster_list.remove(m_cluster_list.size() - 1); + + if (m_cluster_list.is_empty() || (m_cluster_list.size() == 1 && first_cluster() <= 1)) { + // We have removed the last cluster in the chain, so update the inode metadata. + if (fs().m_fat_version == FATVersion::FAT32) { + // Only FAT32 uses the `first_cluster_high` field. + m_entry.first_cluster_high = 0; + } + + m_entry.first_cluster_low = 0; + + set_metadata_dirty(true); + } else { + // We have removed a cluster from the chain, so update the FAT entry for + // the last cluster in the chain mark it as the end of the chain. + last_cluster = m_cluster_list[m_cluster_list.size() - 1]; + TRY(fs().fat_write(last_cluster, fs().end_of_chain_marker())); + } + + return {}; +} + ErrorOr FATInode::read_bytes_locked(off_t offset, size_t size, UserOrKernelBuffer& buffer, OpenFileDescription*) const { dbgln_if(FAT_DEBUG, "FATInode[{}]::read_bytes_locked(): Reading {} bytes at offset {}", identifier(), size, offset); @@ -314,9 +375,36 @@ ErrorOr> FATInode::lookup(StringView name) return inode.release_nonnull(); } -ErrorOr FATInode::write_bytes_locked(off_t, size_t, UserOrKernelBuffer const&, OpenFileDescription*) +ErrorOr FATInode::write_bytes_locked(off_t offset, size_t size, UserOrKernelBuffer const& buffer, OpenFileDescription*) { - return EROFS; + dbgln_if(FAT_DEBUG, "FATInode[{}]::write_bytes_locked(): Writing size: {} offset: {}", identifier(), size, offset); + + u32 new_size = max(m_entry.file_size, offset + size); + if (new_size != m_entry.file_size) + TRY(resize(new_size)); + + auto block_list = TRY(get_block_list()); + + u32 first_block_index = offset / fs().m_device_block_size; + u32 last_block_index = (offset + size - 1) / fs().m_device_block_size; + + size_t offset_into_first_block = offset - first_block_index * fs().m_device_block_size; + + size_t nwritten = 0; + size_t remaining_count = size; + for (u32 block_index = first_block_index; block_index <= last_block_index; ++block_index) { + size_t offset_into_block = block_index == first_block_index ? offset_into_first_block : 0; + + size_t to_write = min(fs().m_device_block_size - offset_into_block, remaining_count); + dbgln_if(FAT_DEBUG, "FATInode[{}]::write_bytes_locked(): Writing {} byte(s) to block {} at offset {}", identifier(), to_write, block_list[block_index], offset_into_block); + + TRY(fs().write_block(block_list[block_index], buffer.offset(nwritten), to_write, offset_into_block)); + + nwritten += to_write; + remaining_count -= to_write; + } + + return size; } ErrorOr> FATInode::create_child(StringView, mode_t, dev_t, UserID, GroupID) @@ -345,9 +433,77 @@ ErrorOr FATInode::chown(UserID, GroupID) return Error::from_errno(ENOTSUP); } +ErrorOr FATInode::resize(u64 size) +{ + VERIFY(m_inode_lock.is_locked()); + VERIFY(size != m_entry.file_size); + + u32 old_size = m_entry.file_size; + u64 bytes_per_cluster = fs().m_device_block_size * fs().m_parameter_block->common_bpb()->sectors_per_cluster; + + u64 size_rounded_up_to_bytes_per_cluster = size; + if (size == 0) + size_rounded_up_to_bytes_per_cluster = bytes_per_cluster; + else if (size % bytes_per_cluster != 0) + size_rounded_up_to_bytes_per_cluster = (size + bytes_per_cluster) - (size % bytes_per_cluster); + + if (size > m_entry.file_size) { + while (m_cluster_list.size() * bytes_per_cluster < size_rounded_up_to_bytes_per_cluster) + TRY(allocate_and_add_cluster_to_chain()); + } else { + while (m_cluster_list.size() * bytes_per_cluster > size_rounded_up_to_bytes_per_cluster) + TRY(remove_last_cluster_from_chain()); + } + + m_entry.file_size = size; + set_metadata_dirty(true); + + if (size > old_size) { + // There will likely be stale data following the old end of the file, + // so make sure to zero out all of the newly-allocated data. + u64 bytes_to_clear = size - old_size; + u64 clear_from = old_size; + u8 zero_buffer[PAGE_SIZE] {}; + while (bytes_to_clear) { + auto nwritten = TRY(write_bytes_locked(clear_from, min(static_cast(sizeof(zero_buffer)), bytes_to_clear), UserOrKernelBuffer::for_kernel_buffer(zero_buffer), nullptr)); + VERIFY(nwritten != 0); + bytes_to_clear -= nwritten; + clear_from += nwritten; + } + } + + return {}; +} + +ErrorOr FATInode::truncate_locked(u64 size) +{ + VERIFY(m_inode_lock.is_locked()); + if (m_entry.file_size == size) + return {}; + + dbgln_if(FAT_DEBUG, "FATInode[{}]::truncate_locked(): truncating to {}", identifier(), size); + TRY(resize(size)); + + return {}; +} + ErrorOr FATInode::flush_metadata() { - return EROFS; + if (m_inode_metadata_location.block == 0) + return {}; + + dbgln_if(FAT_DEBUG, "FATInode[{}]::flush_metadata(): Writing entry at block {}, entry {} (size: {}, cluster_low: {}, cluster_high: {})", identifier().index(), m_inode_metadata_location.block, m_inode_metadata_location.entry, m_entry.file_size, m_entry.first_cluster_low, m_entry.first_cluster_high); + + TRY(fs().write_block(m_inode_metadata_location.block, UserOrKernelBuffer::for_kernel_buffer(bit_cast(&m_entry)), sizeof(FATEntry), m_inode_metadata_location.entry * sizeof(FATEntry))); + + set_metadata_dirty(false); + return {}; +} + +ErrorOr FATInode::update_timestamps(Optional, Optional, Optional) +{ + // FIXME: Implement FATInode::update_timestamps + return {}; } } diff --git a/Kernel/FileSystem/FATFS/Inode.h b/Kernel/FileSystem/FATFS/Inode.h index 335a7d3463d..49afd736af8 100644 --- a/Kernel/FileSystem/FATFS/Inode.h +++ b/Kernel/FileSystem/FATFS/Inode.h @@ -56,6 +56,10 @@ private: // already being created to determine the FAT version. It is used // during FATInode creation (create()). u32 first_cluster(FATVersion const version) const; + ErrorOr allocate_and_add_cluster_to_chain(); + ErrorOr remove_last_cluster_from_chain(); + + ErrorOr resize(u64 size); // ^Inode virtual ErrorOr write_bytes_locked(off_t, size_t, UserOrKernelBuffer const& data, OpenFileDescription*) override; @@ -70,7 +74,9 @@ private: virtual ErrorOr replace_child(StringView name, Inode& child) override; virtual ErrorOr chmod(mode_t) override; virtual ErrorOr chown(UserID, GroupID) override; + virtual ErrorOr truncate_locked(u64) override; virtual ErrorOr flush_metadata() override; + virtual ErrorOr update_timestamps(Optional atime, Optional ctime, Optional mtime) override; Vector m_cluster_list; FATEntry m_entry;