Ext2FS: Implement writing into inodes with arbitrary offset and length.

Okay, this is pretty cool. :^) There are some issues and limitations for
sure but the basic functionality is there.
This commit is contained in:
Andreas Kling 2019-01-23 04:29:56 +01:00
parent 29dfb4ae13
commit 906685e238
Notes: sideshowbarker 2024-07-19 15:58:49 +09:00
8 changed files with 147 additions and 19 deletions

View file

@ -15,10 +15,10 @@ DiskBackedFS::~DiskBackedFS()
bool DiskBackedFS::writeBlock(unsigned index, const ByteBuffer& data)
{
ASSERT(data.size() == blockSize());
#ifdef DBFS_DEBUG
kprintf("DiskBackedFileSystem::writeBlock %u\n", index);
kprintf("DiskBackedFileSystem::writeBlock %u, size=%u\n", index, data.size());
#endif
ASSERT(data.size() == blockSize());
DiskOffset baseOffset = static_cast<DiskOffset>(index) * static_cast<DiskOffset>(blockSize());
return device().write(baseOffset, blockSize(), data.pointer());
}

View file

@ -178,7 +178,7 @@ Ext2FS::BlockListShape Ext2FS::compute_block_list_shape(unsigned blocks)
return shape;
}
bool Ext2FS::write_block_list_for_inode(InodeIndex inode_index, ext2_inode& e2inode, Vector<BlockIndex>&& blocks)
bool Ext2FS::write_block_list_for_inode(InodeIndex inode_index, ext2_inode& e2inode, const Vector<BlockIndex>& blocks)
{
dbgprintf("Ext2FS: writing %u block(s) to i_block array\n", min((size_t)EXT2_NDIR_BLOCKS, blocks.size()));
@ -188,6 +188,8 @@ bool Ext2FS::write_block_list_for_inode(InodeIndex inode_index, ext2_inode& e2in
Vector<BlockIndex> new_meta_blocks;
if (new_shape.meta_blocks > old_shape.meta_blocks) {
new_meta_blocks = allocate_blocks(group_index_from_inode(inode_index), new_shape.meta_blocks - old_shape.meta_blocks);
for (auto bi : new_meta_blocks)
set_block_allocation_state(group_index_from_inode(inode_index), bi, true);
}
unsigned output_block_index = 0;
@ -207,6 +209,7 @@ bool Ext2FS::write_block_list_for_inode(InodeIndex inode_index, ext2_inode& e2in
}
{
dbgprintf("Ext2FS: Writing out indirect blockptr block for inode %u\n", inode_index);
auto block_contents = ByteBuffer::create_uninitialized(blockSize());
BufferStream stream(block_contents);
ASSERT(new_shape.indirect_blocks <= EXT2_ADDR_PER_BLOCK(&super_block()));
@ -459,30 +462,135 @@ ssize_t Ext2FSInode::read_bytes(Unix::off_t offset, size_t count, byte* buffer,
return nread;
}
bool Ext2FSInode::write(const ByteBuffer& data)
ssize_t Ext2FSInode::write_bytes(Unix::off_t offset, size_t count, const byte* data, FileDescriptor*)
{
LOCKER(m_lock);
// FIXME: Support writing to symlink inodes.
ASSERT(!is_symlink());
unsigned blocksNeededBefore = ceilDiv(size(), fs().blockSize());
unsigned blocksNeededAfter = ceilDiv((unsigned)data.size(), fs().blockSize());
ASSERT(offset >= 0);
// FIXME: Support growing or shrinking the block list.
ASSERT(blocksNeededBefore == blocksNeededAfter);
const size_t block_size = fs().blockSize();
size_t new_size = max(static_cast<size_t>(offset) + count, size());
auto list = fs().block_list_for_inode(m_raw_inode);
if (list.is_empty()) {
kprintf("ext2fs: writeInode: empty block list for inode %u\n", index());
return false;
unsigned blocks_needed_before = ceilDiv(size(), block_size);
unsigned blocks_needed_after = ceilDiv(new_size, block_size);
auto block_list = fs().block_list_for_inode(m_raw_inode);
if (blocks_needed_after > blocks_needed_before) {
auto new_blocks = fs().allocate_blocks(fs().group_index_from_inode(index()), blocks_needed_after - blocks_needed_before);
for (auto new_block_index : new_blocks)
fs().set_block_allocation_state(fs().group_index_from_inode(index()), new_block_index, true);
block_list.append(move(new_blocks));
} else if (blocks_needed_after < blocks_needed_before) {
// FIXME: Implement block list shrinking!
ASSERT_NOT_REACHED();
}
for (unsigned i = 0; i < list.size(); ++i) {
dword first_block_logical_index = offset / block_size;
dword last_block_logical_index = (offset + count) / block_size;
if (last_block_logical_index >= block_list.size())
last_block_logical_index = block_list.size() - 1;
dword offset_into_first_block = offset % block_size;
ssize_t nwritten = 0;
size_t remaining_count = min((Unix::off_t)count, (Unix::off_t)new_size - offset);
const byte* in = data;
#ifdef EXT2_DEBUG
dbgprintf("Ext2FS::write_bytes: Writing %u bytes %d bytes into inode %u:%u from %p\n", count, offset, fsid(), index(), data);
#endif
auto buffer_block = ByteBuffer::create_uninitialized(block_size);
for (dword bi = first_block_logical_index; remaining_count && bi <= last_block_logical_index; ++bi) {
dword offset_into_block = (bi == first_block_logical_index) ? offset_into_first_block : 0;
dword num_bytes_to_copy = min(block_size - offset_into_block, remaining_count);
ByteBuffer block;
if (offset_into_block != 0) {
block = fs().readBlock(block_list[bi]);
if (!block) {
kprintf("Ext2FSInode::write_bytes: readBlock(%u) failed (lbi: %u)\n", block_list[bi], bi);
return -EIO;
}
} else
block = buffer_block;
memcpy(block.pointer() + offset_into_block, in, num_bytes_to_copy);
if (offset_into_block == 0 && !num_bytes_to_copy)
memset(block.pointer() + num_bytes_to_copy, 0, block_size - num_bytes_to_copy);
#ifdef EXT2_DEBUG
dbgprintf("Ext2FSInode::write_bytes: writing block %u (offset_into_block: %u)\n", block_list[bi], offset_into_block);
#endif
bool success = fs().writeBlock(block_list[bi], block);
if (!success) {
kprintf("Ext2FSInode::write_bytes: writeBlock(%u) failed (lbi: %u)\n", block_list[bi], bi);
return -EIO;
}
remaining_count -= num_bytes_to_copy;
nwritten += num_bytes_to_copy;
in += num_bytes_to_copy;
}
bool success = fs().write_block_list_for_inode(index(), m_raw_inode, block_list);
ASSERT(success);
m_raw_inode.i_size = new_size;
m_raw_inode.i_blocks = block_list.size() * (block_size / 512);
#ifdef EXT2_DEBUG
dbgprintf("Ext2FSInode::write_bytes: after write, i_size=%u, i_blocks=%u (%u blocks in list)\n", m_raw_inode.i_size, m_raw_inode.i_blocks, block_list.size());
#endif
flush_metadata();
// NOTE: Make sure the cached block list is up to date!
m_block_list = move(block_list);
return nwritten;
}
bool Ext2FSInode::write(const ByteBuffer& data)
{
// FIXME: Lock this Inode during write. Inode should have a common locking mechanism.
// FIXME: Support writing to symlink inodes.
ASSERT(!is_symlink());
unsigned blocks_needed_before = ceilDiv(size(), fs().blockSize());
unsigned blocks_needed_after = ceilDiv((unsigned)data.size(), fs().blockSize());
auto block_list = fs().block_list_for_inode(m_raw_inode);
if (blocks_needed_after > blocks_needed_before) {
auto new_blocks = fs().allocate_blocks(fs().group_index_from_inode(index()), blocks_needed_after - blocks_needed_before);
for (auto new_block_index : new_blocks)
fs().set_block_allocation_state(fs().group_index_from_inode(index()), new_block_index, true);
block_list.append(move(new_blocks));
} else if (blocks_needed_after < blocks_needed_before) {
// FIXME: Implement block list shrinking!
ASSERT_NOT_REACHED();
}
auto padded_block = ByteBuffer::create_uninitialized(fs().blockSize());
for (unsigned i = 0; i < block_list.size(); ++i) {
auto section = data.slice(i * fs().blockSize(), fs().blockSize());
//kprintf("section = %p (%u)\n", section.pointer(), section.size());
bool success = fs().writeBlock(list[i], section);
bool success;
dbgprintf("Ext2FS: write section = %p (%u)\n", section.pointer(), section.size());
if (section.size() == fs().blockSize())
success = fs().writeBlock(block_list[i], section);
else {
memcpy(padded_block.pointer(), section.pointer(), section.size());
memset(padded_block.pointer() + section.size(), 0, fs().blockSize() - section.size());
success = fs().writeBlock(block_list[i], padded_block);
}
ASSERT(success);
}
bool success = fs().write_block_list_for_inode(index(), m_raw_inode, block_list);
ASSERT(success);
m_raw_inode.i_size = data.size();
set_metadata_dirty(true);
return true;
}
@ -1043,7 +1151,7 @@ RetainPtr<Inode> Ext2FS::create_inode(InodeIdentifier parent_id, const String& n
else
initialLinksCount = 1;
auto timestamp = ktime(nullptr);
auto timestamp = RTC::now();
auto e2inode = make<ext2_inode>();
memset(e2inode.ptr(), 0, sizeof(ext2_inode));
e2inode->i_mode = mode;
@ -1060,7 +1168,7 @@ RetainPtr<Inode> Ext2FS::create_inode(InodeIdentifier parent_id, const String& n
// FIXME: Implement writing out indirect blocks!
ASSERT(blocks.size() < EXT2_NDIR_BLOCKS);
success = write_block_list_for_inode(inode_id, *e2inode, move(blocks));
success = write_block_list_for_inode(inode_id, *e2inode, blocks);
ASSERT(success);
dbgprintf("Ext2FS: writing initial metadata for inode %u\n", inode_id);

View file

@ -33,6 +33,7 @@ private:
virtual String reverse_lookup(InodeIdentifier) override;
virtual void flush_metadata() override;
virtual bool write(const ByteBuffer&) override;
virtual ssize_t write_bytes(Unix::off_t, size_t, const byte* data, FileDescriptor*) override;
virtual bool add_child(InodeIdentifier child_id, const String& name, byte file_type, int& error) override;
virtual bool remove_child(const String& name, int& error) override;
virtual RetainPtr<Inode> parent() const override;
@ -93,7 +94,7 @@ private:
unsigned group_index_from_inode(unsigned) const;
Vector<unsigned> block_list_for_inode(const ext2_inode&, bool include_block_list_blocks = false) const;
bool write_block_list_for_inode(InodeIndex, ext2_inode&, Vector<BlockIndex>&&);
bool write_block_list_for_inode(InodeIndex, ext2_inode&, const Vector<BlockIndex>&);
void dump_block_bitmap(unsigned groupIndex) const;
void dump_inode_bitmap(unsigned groupIndex) const;

View file

@ -168,7 +168,7 @@ ssize_t FileDescriptor::write(Process& process, const byte* data, size_t size)
return m_device->write(process, data, size);
}
ASSERT(m_inode);
ssize_t nwritten = m_inode->write(ByteBuffer::wrap((byte*)data, size));
ssize_t nwritten = m_inode->write_bytes(m_current_offset, size, data, this);
m_current_offset += nwritten;
return nwritten;
}

View file

@ -84,6 +84,7 @@ public:
virtual InodeIdentifier lookup(const String& name) = 0;
virtual String reverse_lookup(InodeIdentifier) = 0;
virtual bool write(const ByteBuffer&) = 0;
virtual ssize_t write_bytes(Unix::off_t, size_t, const byte* data, FileDescriptor*) = 0;
virtual bool add_child(InodeIdentifier child_id, const String& name, byte file_type, int& error) = 0;
virtual bool remove_child(const String& name, int& error) = 0;
virtual RetainPtr<Inode> parent() const = 0;

View file

@ -280,6 +280,17 @@ bool SynthFSInode::write(const ByteBuffer& data)
return m_write_callback(*this, data);
}
ssize_t SynthFSInode::write_bytes(Unix::off_t offset, size_t size, const byte* buffer, FileDescriptor*)
{
if (!m_write_callback)
return -EPERM;
// FIXME: Being able to write into SynthFS at a non-zero offset seems like something we should support..
ASSERT(offset == 0);
bool success = m_write_callback(*this, ByteBuffer::wrap((byte*)buffer, size));
ASSERT(success);
return 0;
}
bool SynthFSInode::add_child(InodeIdentifier child_id, const String& name, byte file_type, int& error)
{
(void) child_id;

View file

@ -61,6 +61,7 @@ private:
virtual String reverse_lookup(InodeIdentifier) override;
virtual void flush_metadata() override;
virtual bool write(const ByteBuffer&) override;
virtual ssize_t write_bytes(Unix::off_t, size_t, const byte* buffer, FileDescriptor*) override;
virtual bool add_child(InodeIdentifier child_id, const String& name, byte file_type, int& error) override;
virtual bool remove_child(const String& name, int& error) override;
virtual RetainPtr<Inode> parent() const override;

View file

@ -161,6 +161,12 @@ RetainPtr<FileDescriptor> VFS::create(const String& path, int& error, int option
{
(void) options;
error = -EWHYTHO;
if (!isSocket(mode) && !isFIFO(mode) && !isBlockDevice(mode) && !isCharacterDevice(mode)) {
// Turn it into a regular file. (This feels rather hackish.)
mode |= 0100000;
}
// FIXME: This won't work nicely across mount boundaries.
FileSystemPath p(path);
if (!p.is_valid()) {