Kernel/FATFS: Implement file modification

This is the first part of write support, it allows for full file
modification, but no creating or removing files yet.

Co-Authored-By: implicitfield <114500360+implicitfield@users.noreply.github.com>
This commit is contained in:
Undefine 2024-01-18 15:37:48 +01:00 committed by Tim Schumacher
parent fde7bd9190
commit 098518cc57
Notes: sideshowbarker 2024-07-16 20:08:14 +09:00
4 changed files with 198 additions and 3 deletions

View File

@ -371,6 +371,36 @@ u32 FATFS::end_of_chain_marker() const
}
}
ErrorOr<u32> 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<u32> FATFS::fat_read(u32 cluster)
{
dbgln_if(FAT_DEBUG, "FATFS: Reading FAT entry for cluster {}", cluster);

View File

@ -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<u32> allocate_cluster();
size_t fat_offset_for_cluster(u32 cluster) const;

View File

@ -240,6 +240,67 @@ u32 FATInode::first_cluster(FATVersion const version) const
return m_entry.first_cluster_low;
}
ErrorOr<void> 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<void> 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<size_t> 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<NonnullRefPtr<Inode>> FATInode::lookup(StringView name)
return inode.release_nonnull();
}
ErrorOr<size_t> FATInode::write_bytes_locked(off_t, size_t, UserOrKernelBuffer const&, OpenFileDescription*)
ErrorOr<size_t> 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<NonnullRefPtr<Inode>> FATInode::create_child(StringView, mode_t, dev_t, UserID, GroupID)
@ -345,9 +433,77 @@ ErrorOr<void> FATInode::chown(UserID, GroupID)
return Error::from_errno(ENOTSUP);
}
ErrorOr<void> 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<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 {};
}
ErrorOr<void> 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<void> 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<u8*>(&m_entry)), sizeof(FATEntry), m_inode_metadata_location.entry * sizeof(FATEntry)));
set_metadata_dirty(false);
return {};
}
ErrorOr<void> FATInode::update_timestamps(Optional<UnixDateTime>, Optional<UnixDateTime>, Optional<UnixDateTime>)
{
// FIXME: Implement FATInode::update_timestamps
return {};
}
}

View File

@ -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<void> allocate_and_add_cluster_to_chain();
ErrorOr<void> remove_last_cluster_from_chain();
ErrorOr<void> resize(u64 size);
// ^Inode
virtual ErrorOr<size_t> write_bytes_locked(off_t, size_t, UserOrKernelBuffer const& data, OpenFileDescription*) override;
@ -70,7 +74,9 @@ private:
virtual ErrorOr<void> replace_child(StringView name, Inode& child) override;
virtual ErrorOr<void> chmod(mode_t) override;
virtual ErrorOr<void> chown(UserID, GroupID) override;
virtual ErrorOr<void> truncate_locked(u64) override;
virtual ErrorOr<void> flush_metadata() override;
virtual ErrorOr<void> update_timestamps(Optional<UnixDateTime> atime, Optional<UnixDateTime> ctime, Optional<UnixDateTime> mtime) override;
Vector<u32> m_cluster_list;
FATEntry m_entry;