From 202d282fb3e381f31769f4f34860b2170fb2df05 Mon Sep 17 00:00:00 2001 From: tsatke Date: Sat, 22 Jun 2024 19:39:13 +0200 Subject: [PATCH] add support for extending the size of a regular file through a write as long as no new blocks need to be allocated for it --- ext2/src/inode.rs | 12 +++++++++++ ext2/src/read.rs | 8 ++++++-- ext2/src/write.rs | 51 +++++++++++++++++++++++++++++++++++++---------- 3 files changed, 59 insertions(+), 12 deletions(-) diff --git a/ext2/src/inode.rs b/ext2/src/inode.rs index 2e44aea..ffd3906 100644 --- a/ext2/src/inode.rs +++ b/ext2/src/inode.rs @@ -21,6 +21,10 @@ macro_rules! inode_type { &self.1 } + pub fn inode_mut(&mut self) -> &mut Inode { + &mut self.1 + } + pub fn into_inner(self) -> (InodeAddress, Inode) { (self.0, self.1) } @@ -147,6 +151,14 @@ impl Inode { Flags::from_bits_truncate(self.flags) } + pub fn set_file_size_lower(&mut self, size: u32) { + self.byte_size_lower = size; + } + + pub fn set_file_size_upper(&mut self, size: u32) { + self.byte_size_upper_or_dir_acl = size; + } + pub fn direct_ptr(&self, index: usize) -> Option { BlockAddress::new(match index { 0 => self.direct_block_ptr_0, diff --git a/ext2/src/read.rs b/ext2/src/read.rs index 3e563e1..8bc874c 100644 --- a/ext2/src/read.rs +++ b/ext2/src/read.rs @@ -41,7 +41,7 @@ where Ok(total_read) } - fn read_blocks_from_inode(&self, inode: &Inode, start_block: usize, end_block: usize, buf: &mut [u8]) -> Result { + pub(crate) fn read_blocks_from_inode(&self, inode: &Inode, start_block: usize, end_block: usize, buf: &mut [u8]) -> Result { let block_size = self.superblock.block_size() as usize; assert_eq!(buf.len(), (end_block - start_block + 1) * block_size, "buf.len() must be equal to the number of blocks you want to read"); @@ -79,6 +79,11 @@ where } pub fn is_block_allocated(&self, inode: &Inode, block_index: u32) -> Result { + self.resolve_block_index(inode, block_index) + .map(|block| block.is_some()) + } + + pub fn resolve_block_index(&self, inode: &Inode, block_index: u32) -> Result, Error> { let (direct_limit, indirect_limit, double_indirect_limit) = self.indirect_pointer_limits(); Ok( @@ -91,7 +96,6 @@ where } else { self.resolve_triple_indirect_ptr(inode.triple_indirect_ptr(), block_index - double_indirect_limit)? } - .is_some() ) } diff --git a/ext2/src/write.rs b/ext2/src/write.rs index 9c65e72..59d9363 100644 --- a/ext2/src/write.rs +++ b/ext2/src/write.rs @@ -1,3 +1,5 @@ +use alloc::vec; + use filesystem::BlockDevice; use crate::{Error, Ext2Fs, RegularFile}; @@ -9,7 +11,7 @@ where #[allow(unused_variables)] pub fn write_to_file( &mut self, - file: &RegularFile, + file: &mut RegularFile, offset: usize, buf: &[u8], ) -> Result { @@ -21,22 +23,51 @@ where let relative_offset = (offset % block_size) as usize; let block_count = (end_block - start_block + 1) as usize; - assert_eq!(buf.len() % block_size as usize, 0, "buf.len() must be a multiple of block_size for now"); // TODO: make this more flexible + // This is the data that we want to write. We pad the data with data from the disk + // to align it with the block boundaries (start and length). This data can be written + // back to disk (block aligned) as is. + let data = { + let mut data = vec![0_u8; block_count * block_size as usize]; + self.read_blocks_from_inode(&file, start_block as usize, end_block as usize, &mut data)?; // TODO: we don't need to read what will be overwritten anyways + // overwrite the part that should be written + data[relative_offset..relative_offset + buf.len()].copy_from_slice(buf); + data + }; for (i, block) in (start_block..=end_block).enumerate() { if !self.is_block_allocated(file, block)? { - // TODO: we don't need to allocate if the full content of this block would be zero if the fs allows sparse files - let free_block_address = self.allocate_block()?; - if free_block_address.is_none() { - return Err(Error::NoSpace); - } - let free_block_address = free_block_address.unwrap(); - // TODO: write the block address to the inode + todo!("allocate block") + // // TODO: we don't need to allocate if the full content of this block would be zero if the fs allows sparse files + // let free_block_address = self.allocate_block()?; + // if free_block_address.is_none() { + // return Err(Error::NoSpace); + // } + // let free_block_address = free_block_address.unwrap(); + // // TODO: write the block address to the inode } } // we can now be certain that all blocks that we want to write into are allocated - todo!() + let mut chunks = data.chunks_exact(block_size as usize); + for (block, data) in (start_block..=end_block).zip(&mut chunks) { + let block_address = self.resolve_block_index(&file, block)?.expect("we should have just allocated this block, it should be present"); + self.write_block(block_address, data)?; + } + debug_assert_eq!(chunks.remainder().len(), 0, "data to write was not block aligned"); + + if file.len() < offset as usize + buf.len() { + let new_size = offset as usize + buf.len(); + let new_size_lower = new_size as u32; + let new_size_upper = (new_size >> 32) as u32; + + let inode = file.inode_mut(); + inode.set_file_size_lower(new_size_lower); + inode.set_file_size_upper(new_size_upper); + + self.write_inode(file.inode_address(), file)?; + } + + Ok(buf.len()) } }