mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-09-21 10:19:03 +03:00
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:
parent
fde7bd9190
commit
098518cc57
Notes:
sideshowbarker
2024-07-16 20:08:14 +09:00
Author: https://github.com/cqundefine Commit: https://github.com/SerenityOS/serenity/commit/098518cc57 Pull-request: https://github.com/SerenityOS/serenity/pull/23907 Reviewed-by: https://github.com/Hendiadyoin1 Reviewed-by: https://github.com/implicitfield Reviewed-by: https://github.com/nico Reviewed-by: https://github.com/timschumi ✅
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 {};
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user