From 233348171e988b698e9be94c1deee8bf8dd8f513 Mon Sep 17 00:00:00 2001 From: Rat Cornu <98173832+RatCornu@users.noreply.github.com> Date: Wed, 24 Jan 2024 18:46:24 +0100 Subject: [PATCH] Ext2 better block allocation (#18) * fix(ext2): lots of issues linked to block bitmaps management * feat(ext2): add reserved blocks for files owned by superuser --- src/dev/mod.rs | 7 +- src/error.rs | 4 +- src/fs/ext2/block.rs | 129 ++++++------- src/fs/ext2/block_group.rs | 9 +- src/fs/ext2/file.rs | 31 ++- src/fs/ext2/inode.rs | 9 +- src/fs/ext2/mod.rs | 380 +++++++++++++++++++++++++++++++++---- src/io.rs | 19 +- src/lib.rs | 2 +- 9 files changed, 446 insertions(+), 144 deletions(-) diff --git a/src/dev/mod.rs b/src/dev/mod.rs index b732b36..7b847d3 100644 --- a/src/dev/mod.rs +++ b/src/dev/mod.rs @@ -4,15 +4,14 @@ use alloc::borrow::{Cow, ToOwned}; use alloc::boxed::Box; use alloc::slice; use alloc::vec::Vec; -#[cfg(feature = "std")] use core::cell::RefCell; use core::iter::Step; use core::mem::{size_of, transmute_copy}; use core::ops::{Deref, DerefMut, Range}; use core::ptr::{addr_of, slice_from_raw_parts}; -#[cfg(feature = "std")] +#[cfg(any(feature = "std", test))] use std::fs::File; -#[cfg(feature = "std")] +#[cfg(any(feature = "std", test))] use std::io::ErrorKind; use self::sector::Address; @@ -375,7 +374,7 @@ impl + Read + Write + Seek> Device Device for RefCell { #[inline] fn size(&self) -> Size { diff --git a/src/error.rs b/src/error.rs index 9fac586..f101b70 100644 --- a/src/error.rs +++ b/src/error.rs @@ -21,7 +21,7 @@ pub enum Error { Path(PathError), /// Standard I/O error - #[cfg(feature = "std")] + #[cfg(any(feature = "std", test))] IO(std::io::Error), } @@ -32,7 +32,7 @@ impl Display for Error { Self::Device(device_error) => write!(formatter, "Device Error: {device_error}"), Self::Fs(fs_error) => write!(formatter, "Filesystem Error: {fs_error}"), Self::Path(path_error) => write!(formatter, "Path Error: {path_error}"), - #[cfg(feature = "std")] + #[cfg(any(feature = "std", test))] Self::IO(io_error) => write!(formatter, "I/O Error: {io_error}"), } } diff --git a/src/fs/ext2/block.rs b/src/fs/ext2/block.rs index ceb97ca..360f295 100644 --- a/src/fs/ext2/block.rs +++ b/src/fs/ext2/block.rs @@ -9,13 +9,13 @@ use alloc::vec; use alloc::vec::Vec; use super::error::Ext2Error; +use super::superblock::Superblock; use super::Ext2; use crate::dev::celled::Celled; use crate::dev::sector::Address; use crate::dev::Device; use crate::error::Error; use crate::fs::error::FsError; -use crate::fs::ext2::block_group::BlockGroupDescriptor; use crate::io::{Base, Read, Seek, SeekFrom, Write}; /// An ext2 block. @@ -46,15 +46,15 @@ impl> Block { /// Returns the containing block group of this block. #[inline] #[must_use] - pub fn block_group(&self) -> u32 { - self.number / self.filesystem.borrow().superblock().base().blocks_per_group + pub const fn block_group(&self, superblock: &Superblock) -> u32 { + self.number / superblock.base().blocks_per_group } /// Returns the offset of this block in its containing block group. #[inline] #[must_use] - pub fn group_index(&self) -> u32 { - self.number % self.filesystem.borrow().superblock().base().blocks_per_group + pub const fn group_index(&self, superblock: &Superblock) -> u32 { + self.number % superblock.base().blocks_per_group } /// Reads all the content from this block and returns it in a vector. @@ -74,42 +74,26 @@ impl> Block { Ok(buffer) } - /// Returns whether this block is currently free or not. + /// Returns whether this block is currently free or not from the block bitmap in which the block resides. /// - /// As this operation needs to read directly from the given device, it is quite costly in computational time. - /// - /// # Errors - /// - /// Returns a [`Error`] if the device cannot be read. + /// The `bitmap` argument is usually the result of the method [`get_block_bitmap`](../struct.Ext2.html#method.get_block_bitmap). + #[allow(clippy::indexing_slicing)] #[inline] - pub fn is_free(&self) -> Result> { - let fs = self.filesystem.borrow(); - - let block_group = self.block_group(); - let block_group_descriptor = BlockGroupDescriptor::parse(&fs.device, fs.superblock(), block_group)?; - let mut block_group_descriptor_bitmap_block = Self::new(self.filesystem.clone(), block_group_descriptor.block_bitmap); - - let bitmap_index = u64::from(self.group_index()); - let byte_index = bitmap_index / 8; - let byte_offset = bitmap_index % 8; - - let mut buffer = [0_u8]; - block_group_descriptor_bitmap_block.seek(SeekFrom::Start(byte_index))?; - block_group_descriptor_bitmap_block.read(&mut buffer)?; - - Ok((buffer[0] >> byte_offset) & 1 == 0) + #[must_use] + pub const fn is_free(&self, superblock: &Superblock, bitmap: &[u8]) -> bool { + let index = self.group_index(superblock) / 8; + let offset = self.number % 8; + bitmap[index as usize] >> offset & 1 == 0 } - /// Returns whether this block is currently used or not. - /// - /// As this operation needs to read directly from the given device, it is quite costly in computational time. + /// Returns whether this block is currently used or not from the block bitmap in which the block resides. /// - /// # Errors - /// - /// Returns a [`Error`] if the device cannot be read. + /// The `bitmap` argument is usually the result of the method [`get_block_bitmap`](../struct.Ext2.html#method.get_block_bitmap). + #[allow(clippy::indexing_slicing)] #[inline] - pub fn is_used(&self) -> Result> { - self.is_free().map(|is_free| !is_free) + #[must_use] + pub const fn is_used(&self, superblock: &Superblock, bitmap: &[u8]) -> bool { + !self.is_free(superblock, bitmap) } /// Sets the current block usage in the block bitmap, and updates the superblock accordingly. @@ -121,32 +105,8 @@ impl> Block { /// Returns an [`BlockAlreadyFree`](Ext2Error::BlockAlreadyFree) error if the given block was already free. /// /// Otherwise, returns an [`Error`] if the device cannot be written. - fn set_usage(&mut self, used: bool) -> Result<(), Error> { - let fs = self.filesystem.borrow(); - - let block_group = self.block_group(); - let block_group_descriptor = BlockGroupDescriptor::parse(&fs.device, fs.superblock(), block_group)?; - let mut block_group_descriptor_bitmap_block = Self::new(self.filesystem.clone(), block_group_descriptor.block_bitmap); - let bitmap_index = self.group_index(); - - let byte_index = bitmap_index / 8; - let byte_offset = bitmap_index % 8; - - let mut buffer = [0_u8]; - - block_group_descriptor_bitmap_block.seek(SeekFrom::Start(u64::from(byte_index)))?; - block_group_descriptor_bitmap_block.read(&mut buffer)?; - - if (buffer[0] >> byte_offset) & 1 == 1 && used { - Err(Ext2Error::BlockAlreadyInUse(self.number).into()) - } else if (buffer[0] >> byte_offset) & 1 == 0 && !used { - Err(Ext2Error::BlockAlreadyFree(self.number).into()) - } else { - buffer[0] ^= 1 << byte_offset; - block_group_descriptor_bitmap_block.seek(SeekFrom::Current(-1_i64))?; - block_group_descriptor_bitmap_block.write(&buffer)?; - Ok(()) - } + fn set_usage(&mut self, usage: bool) -> Result<(), Error> { + self.filesystem.borrow_mut().locate_blocs(&[self.number], usage) } /// Sets the current block as free in the block bitmap, and updates the superblock accordingly. @@ -283,6 +243,7 @@ mod test { use crate::dev::sector::Address; use crate::dev::Device; use crate::fs::ext2::block::Block; + use crate::fs::ext2::block_group::BlockGroupDescriptor; use crate::fs::ext2::error::Ext2Error; use crate::fs::ext2::superblock::Superblock; use crate::fs::ext2::Ext2; @@ -357,14 +318,30 @@ mod test { .unwrap(), ); let ext2 = Celled::new(Ext2::new(file, 0).unwrap()); + let superblock = ext2.borrow().superblock().clone(); - let mut block = Block::new(ext2, BLOCK_NUMBER); + let mut block = Block::new(ext2.clone(), BLOCK_NUMBER); + let block_group = block.block_group(&superblock); - assert!(block.is_used().unwrap()); + let fs = ext2.borrow(); + let block_group_descriptor = BlockGroupDescriptor::parse(&fs.device, fs.superblock(), block_group).unwrap(); + let free_block_count = block_group_descriptor.free_blocks_count; + + let bitmap = fs.get_block_bitmap(block_group).unwrap(); + + drop(fs); + + assert!(block.is_used(&superblock, &bitmap)); block.set_free().unwrap(); - assert!(block.is_free().unwrap()); + let fs = ext2.borrow(); + let new_free_block_count = BlockGroupDescriptor::parse(&fs.device, fs.superblock(), block.block_group(&superblock)) + .unwrap() + .free_blocks_count; + + assert!(block.is_free(&superblock, &fs.get_block_bitmap(block_group).unwrap())); + assert_eq!(free_block_count + 1, new_free_block_count); fs::remove_file("./tests/fs/ext2/io_operations_copy_block_set_free.ext2").unwrap(); } @@ -372,7 +349,7 @@ mod test { #[test] fn block_set_used() { // This block should not be used - const BLOCK_NUMBER: u32 = 1234; + const BLOCK_NUMBER: u32 = 1920; fs::copy("./tests/fs/ext2/io_operations.ext2", "./tests/fs/ext2/io_operations_copy_block_set_used.ext2").unwrap(); @@ -384,14 +361,30 @@ mod test { .unwrap(), ); let ext2 = Celled::new(Ext2::new(file, 0).unwrap()); + let superblock = ext2.borrow().superblock().clone(); - let mut block = Block::new(ext2, BLOCK_NUMBER); + let mut block = Block::new(ext2.clone(), BLOCK_NUMBER); + let block_group = block.block_group(&superblock); + + let fs = ext2.borrow(); + let block_group_descriptor = BlockGroupDescriptor::parse(&fs.device, fs.superblock(), block_group).unwrap(); + let free_block_count = block_group_descriptor.free_blocks_count; - assert!(block.is_free().unwrap()); + let bitmap = fs.get_block_bitmap(block_group).unwrap(); + + drop(fs); + + assert!(block.is_free(&superblock, &bitmap)); block.set_used().unwrap(); - assert!(block.is_used().unwrap()); + let fs = ext2.borrow(); + let new_free_block_count = BlockGroupDescriptor::parse(&fs.device, fs.superblock(), block.block_group(&superblock)) + .unwrap() + .free_blocks_count; + + assert!(block.is_used(&superblock, &fs.get_block_bitmap(block_group).unwrap())); + assert_eq!(free_block_count - 1, new_free_block_count); fs::remove_file("./tests/fs/ext2/io_operations_copy_block_set_used.ext2").unwrap(); } diff --git a/src/fs/ext2/block_group.rs b/src/fs/ext2/block_group.rs index c5dc262..e03c6b4 100644 --- a/src/fs/ext2/block_group.rs +++ b/src/fs/ext2/block_group.rs @@ -30,7 +30,7 @@ pub struct BlockGroupDescriptor { /// Starting block address of inode table. pub inode_table: u32, - /// Number of unallocated blocks in groupĀµ. + /// Number of unallocated blocks in group. pub free_blocks_count: u16, /// Number of unallocated inodes in group. @@ -63,9 +63,7 @@ impl BlockGroupDescriptor { }; let superblock_end_address = SUPERBLOCK_START_BYTE + SUPERBLOCK_SIZE; - Ok(Address::new( - superblock_end_address + if superblock_end_address % (superblock.block_size() as usize) == 0 { 0 } else { 1 }, - )) + Ok(Address::new(superblock_end_address + BLOCK_GROUP_DESCRIPTOR_SIZE * n as usize)) } /// Parse the `n`th block group descriptor from the given device (starting at 0). @@ -84,9 +82,8 @@ impl BlockGroupDescriptor { ) -> Result> { let device = celled_device.borrow(); - let table_start_address = Self::starting_addr(superblock, n)?; + let block_group_descriptor_address = Self::starting_addr(superblock, n)?; - let block_group_descriptor_address = table_start_address + (n as usize * BLOCK_GROUP_DESCRIPTOR_SIZE); // SAFETY: all the possible failures are catched in the resulting error unsafe { device.read_at::(block_group_descriptor_address) } } diff --git a/src/fs/ext2/file.rs b/src/fs/ext2/file.rs index 86efdcd..1db2768 100644 --- a/src/fs/ext2/file.rs +++ b/src/fs/ext2/file.rs @@ -25,6 +25,10 @@ use crate::fs::PATH_MAX; use crate::io::{Base, Read, Seek, SeekFrom, Write}; use crate::types::{Blkcnt, Blksize, Dev, Gid, Ino, Mode, Nlink, Off, Time, Timespec, Uid}; +/// Arbitrary number of supplementary reserved blocks for each file owned by the UID or the GID declared in [the +/// superblock]((struct.Base.html#structfield.def_resuid)). +pub const SUPPLEMENTARY_RESERVED_BLOCKS_PER_WRITE: u64 = 8; + /// Limit in bytes for the length of a pointed path of a symbolic link to be store in an inode and not in a separate data block. pub const SYMBOLIC_LINK_INODE_STORE_LIMIT: usize = 60; @@ -255,10 +259,18 @@ impl> Write for File { return Err(Error::Fs(FsError::Implementation(Ext2Error::OutOfBounds(buf.len() as i128)))); } + let reserved_blocks = + if self.inode.uid == fs.superblock().base().def_resuid || self.inode.gid == fs.superblock().base().def_resgid { + SUPPLEMENTARY_RESERVED_BLOCKS_PER_WRITE + } else { + 0 + }; + // Calcul of the number of needed data blocks let bytes_to_write = buf.len() as u64; - let blocks_needed = - (bytes_to_write + self.io_offset) / block_size + u64::from((bytes_to_write + self.io_offset) % block_size != 0); + let blocks_needed = (bytes_to_write + self.io_offset) / block_size + + u64::from((bytes_to_write + self.io_offset) % block_size != 0) + + reserved_blocks; let ( initial_direct_block_pointers, (initial_singly_indirect_block_pointer, initial_singly_indirect_blocks), @@ -437,14 +449,14 @@ impl> Write for File { } } - // Add the free blocks where it's necessary. - let free_block_numbers = &mut self + let free_blocks = self .filesystem + .borrow() // SAFETY: `blocks_to_request <= blocks_needed < u32::MAX` - .free_blocks(unsafe { u32::try_from(total_blocks_to_request).unwrap_unchecked() })? - .into_iter(); + .free_blocks(unsafe { u32::try_from(total_blocks_to_request).unwrap_unchecked() })?; - let mut free_block_copied = free_block_numbers.clone(); + // Add the free blocks where it's necessary. + let free_block_numbers = &mut free_blocks.clone().into_iter(); // Direct block pointers direct_block_pointers.append(&mut free_block_numbers.take(12 - direct_block_pointers.len()).collect_vec()); @@ -655,13 +667,12 @@ impl> Write for File { updated_inode.size = unsafe { u32::try_from(new_size & u64::from(u32::MAX)).unwrap_unchecked() }; // TODO: update `updated_inode.blocks` - assert!(u32::try_from(new_size).is_ok(), "Search how to deal with bigger files"); + assert!(u32::try_from(new_size).is_ok(), "TODO: Search how to deal with bigger files"); // SAFETY: the updated inode contains the right inode created in this function unsafe { self.set_inode(&updated_inode) }?; - // TODO: be smarter to avoid make 1000000 calls to device's `write` - free_block_copied.try_for_each(|block| Block::new(self.filesystem.clone(), block).set_used())?; + self.filesystem.as_ref().borrow_mut().allocate_blocs(&free_blocks)?; Ok(written_bytes) } diff --git a/src/fs/ext2/inode.rs b/src/fs/ext2/inode.rs index a2a05a2..b02724a 100644 --- a/src/fs/ext2/inode.rs +++ b/src/fs/ext2/inode.rs @@ -70,8 +70,12 @@ pub struct Inode { /// Count of hard links (directory entries) to this inode. When this reaches 0, the data blocks are marked as unallocated. pub links_count: u16, - /// Count of disk sectors (not Ext2 blocks) in use by this inode, not counting the actual inode structure nor directory entries - /// linking to the inode. + /// Indicates the amount of blocks reserved for the associated file data. This includes both currently in used + /// and currently reserved blocks in case the file grows in size. + /// + /// Since this value represents 512-byte blocks and not file system blocks, this value should not be directly used as an index + /// to the i_block array. Rather, the maximum index of the i_block array should be computed from `i_blocks / + /// ((1024<> Ext2 { self.set_superblock(&superblock) } -} -impl> Celled> { - /// Returns a [`File`](crate::file::File) directly read on this filesystem. + /// Returns the block bitmap for the given block group. /// /// # Errors /// - /// Returns an [`BadFileType`](Ext2Error::BadFileType) if the type of the file pointed by the given inode is ill-formed. - /// - /// Otherwise, returns the same errors as [`Inode::parse`]. + /// Returns the same errors as [`BlockGroupDescriptor::parse`](block_group/struct.BlockGroupDescriptor.html#method.parse). #[inline] - pub fn file(&self, inode_number: u32) -> Result, Error> { - let filesystem = self.borrow(); - let inode = filesystem.inode(inode_number)?; - match inode.file_type()? { - Type::Regular => Ok(TypeWithFile::Regular(Regular::new(&self.clone(), inode_number)?)), - Type::Directory => Ok(TypeWithFile::Directory(Directory::new(&self.clone(), inode_number)?)), - Type::SymbolicLink => Ok(TypeWithFile::SymbolicLink(SymbolicLink::new(&self.clone(), inode_number)?)), - Type::Fifo | Type::CharacterDevice | Type::BlockDevice | Type::Socket | Type::Other => unreachable!( - "The only type of files in ext2's filesystems that are written on the device are the regular files, the directories and the symbolic links" - ), - } + pub fn get_block_bitmap(&self, block_group_number: u32) -> Result, Error> { + let superblock = self.superblock(); + + let block_group_descriptor = BlockGroupDescriptor::parse(&self.device, superblock, block_group_number)?; + let starting_addr = Address::new((block_group_descriptor.block_bitmap * superblock.block_size()) as usize); + + Ok(self + .device + .borrow() + .slice(starting_addr..starting_addr + (superblock.base().blocks_per_group / 8) as usize)? + .as_ref() + .to_vec()) + } + + /// Sets the block bitmap for the given block group as the given bitmap. + /// + /// # Errors + /// + /// Returns the same errors as [`BlockGroupDescriptor::parse`](block_group/struct.BlockGroupDescriptor.html#method.parse). + /// + /// # Panics + /// + /// This will panic if `block_bitmap.len() == superblock.blocks_per_group` is false. + /// + /// # Safety + /// + /// Must ensure that the given `block_bitmap` is coherent with the current filesystem's state. + unsafe fn set_block_bitmap(&self, block_group_number: u32, block_bitmap: &[u8]) -> Result<(), Error> { + let superblock = self.superblock(); + + let block_group_descriptor = BlockGroupDescriptor::parse(&self.device, superblock, block_group_number)?; + let starting_addr = Address::new((block_group_descriptor.block_bitmap * superblock.block_size()) as usize); + + let mut device = self.device.borrow_mut(); + let mut slice = device.slice(starting_addr..starting_addr + (superblock.base().blocks_per_group / 8) as usize)?; + slice.clone_from_slice(block_bitmap); + let commit = slice.commit(); + device.commit(commit)?; + + Ok(()) } /// Returns a [`Vec`] containing the block numbers of `n` free blocks. /// + /// Looks for free blocks starting at the block group `start_block_group`. + /// /// # Errors /// /// Returns an [`NotEnoughFreeBlocks`](Ext2Error::NotEnoughFreeBlocks) error if requested more free blocks than available. /// /// Returns an [`Error`] if the device cannot be read. #[inline] - pub fn free_blocks(&self, n: u32) -> Result, Error> { + pub fn free_blocks_offset(&self, n: u32, start_block_group: u32) -> Result, Error> { if n == 0 { return Ok(vec![]); } - let fs = self.borrow(); - - if n > fs.superblock().base().free_blocks_count { + if n > self.superblock().base().free_blocks_count { return Err(Error::Fs(FsError::Implementation(Ext2Error::NotEnoughFreeBlocks( n, - fs.superblock().base().free_blocks_count, + self.superblock().base().free_blocks_count, )))); } + let total_block_group_count = self.superblock().base().block_group_count(); + let mut free_blocks = Vec::new(); - for block_group_count in 0_u32..fs.superblock().base().block_group_count() { - let block_group_descriptor = BlockGroupDescriptor::parse(&fs.device, fs.superblock(), block_group_count)?; - let mut block_group_bitmap_block = Block::new(self.clone(), block_group_descriptor.block_bitmap); - let block_group_bitmap = block_group_bitmap_block.read_all()?; - for (index, eight_blocks_bitmap) in block_group_bitmap.iter().enumerate() { - // SAFETY: a block size is usually at most thousands of bytes, which is smaller than `u32::MAX` - let index = unsafe { u32::try_from(index).unwrap_unchecked() }; - for bit in 0_u32..8_u32 { - if (eight_blocks_bitmap >> bit) & 0x01 == 0x00 { - free_blocks.push(block_group_count * fs.superblock().base().blocks_per_group + index * 8 + bit); - // SAFETY: free_blocks.len() is smaller than n which is a u32 (not equal to 0xFFFF) - if unsafe { u32::try_from(free_blocks.len()).unwrap_unchecked() } == n { - return Ok(free_blocks); + for mut block_group_count in 0_u32..total_block_group_count { + block_group_count = (block_group_count + start_block_group) % total_block_group_count; + + let block_group_descriptor = BlockGroupDescriptor::parse(&self.device, self.superblock(), block_group_count)?; + if block_group_descriptor.free_blocks_count > 0 { + let bitmap = self.get_block_bitmap(block_group_count)?; + for (index, byte) in bitmap.into_iter().enumerate() { + // SAFETY: a block size is usually at most thousands of bytes, which is smaller than `u32::MAX` + let index = unsafe { u32::try_from(index).unwrap_unchecked() }; + + if byte != u8::MAX { + for bit in 0_u32..8 { + if (byte >> bit) & 1 == 0 { + free_blocks.push(block_group_count * self.superblock().base().blocks_per_group + index * 8 + bit); + + if free_blocks.len() as u64 == u64::from(n) { + return Ok(free_blocks); + } + } } } } } } - // SAFETY: free_blocks.len() is smaller than n which is a u32 (not equal to 0xFFFF) + // SAFETY: free_blocks.len() is smaller than n which is a u32 Err(Error::Fs(FsError::Implementation(Ext2Error::NotEnoughFreeBlocks(n, unsafe { free_blocks.len().try_into().unwrap_unchecked() })))) } + + /// Returns a [`Vec`] containing the block numbers of `n` free blocks. + /// + /// # Errors + /// + /// Returns the same errors as [`free_blocks_offset`](struct.Celled.html#method.free_blocks_offset). + #[inline] + pub fn free_blocks(&self, n: u32) -> Result, Error> { + self.free_blocks_offset(n, 0) + } + + /// Sets all the given `blocs` as `usage` in their bitmap, and updates the block group descriptors and the superblock + /// accordingly. + /// + /// # Errors + /// + /// Returns an [`BlockAlreadyInUse`](Ext2Error::BlockAlreadyInUse) error if it tries to allocate a block that was already in + /// use. + /// + /// Returns an [`BlockAlreadyFree`](Ext2Error::BlockAlreadyFree) error if it tries to deallocate a block that was already free. + /// + /// Returns an [`NonExistingBlock`](Ext2Error::NonExistingBlock) error if a given block does not exist. + /// + /// Otherwise, returns the same errors as [`get_block_bitmap`](struct.Ext2.html#method.get_block_bitmap). + #[inline] + fn locate_blocs(&mut self, blocks: &[u32], usage: bool) -> Result<(), Error> { + /// Updates the block group bitmap and the free block count in the descriptor. + /// + /// # Errors + /// + /// Returns an [`Error`] if the device cannot be written. + /// + /// # Safety + /// + /// Must ensure that the `number_blocks_changed_in_group` is coherent with the given `bitmap`. + unsafe fn update_block_group>( + ext2: &Ext2, + block_group_number: u32, + number_blocks_changed_in_group: u16, + bitmap: &[u8], + usage: bool, + ) -> Result<(), Error> { + ext2.set_block_bitmap(block_group_number, bitmap)?; + + let mut new_block_group_descriptor = BlockGroupDescriptor::parse(&ext2.device, ext2.superblock(), block_group_number)?; + + if usage { + new_block_group_descriptor.free_blocks_count -= number_blocks_changed_in_group; + ext2.device.borrow_mut().write_at( + BlockGroupDescriptor::starting_addr(ext2.superblock(), block_group_number)?, + new_block_group_descriptor, + ) + } else { + new_block_group_descriptor.free_blocks_count += number_blocks_changed_in_group; + ext2.device.borrow_mut().write_at( + BlockGroupDescriptor::starting_addr(ext2.superblock(), block_group_number)?, + new_block_group_descriptor, + ) + } + } + + let block_opt = blocks.first(); + + let mut number_blocks_changed_in_group = 0_u16; + // SAFETY: the total number of blocks in the filesystem is a u32 + let total_number_blocks_changed = unsafe { u32::try_from(blocks.len()).unwrap_unchecked() }; + + if let Some(block) = block_opt { + let mut block_group_number = block / self.superblock().base().blocks_per_group; + let mut bitmap = self.get_block_bitmap(block / self.superblock().base().blocks_per_group)?; + + for block in blocks { + if block / self.superblock().base().blocks_per_group != block_group_number { + // SAFETY: the state of the filesystem stays coherent within this function + unsafe { + update_block_group(self, block_group_number, number_blocks_changed_in_group, &bitmap, usage)?; + }; + + number_blocks_changed_in_group = 0; + + block_group_number = block / self.superblock().base().blocks_per_group; + bitmap = self.get_block_bitmap(block_group_number)?; + } + + number_blocks_changed_in_group += 1; + + let group_index = block % self.superblock().base().blocks_per_group; + let bitmap_index = group_index / 8; + let bitmap_offset = group_index % 8; + + let Some(byte) = bitmap.get_mut(bitmap_index as usize) else { + return Err(Error::Fs(FsError::Implementation(Ext2Error::NonExistingBlock(*block)))); + }; + + if usage && *byte >> bitmap_offset & 1 == 1 { + return Err(Error::Fs(FsError::Implementation(Ext2Error::BlockAlreadyInUse(*block)))); + } else if !usage && *byte >> bitmap_offset & 1 == 0 { + return Err(Error::Fs(FsError::Implementation(Ext2Error::BlockAlreadyFree(*block)))); + } + + *byte ^= 1 << bitmap_offset; + } + + // SAFETY: the state of the filesystem stays coherent within this function + unsafe { + update_block_group(self, block_group_number, number_blocks_changed_in_group, &bitmap, usage)?; + }; + } + + if usage { + // SAFETY: the total number of free blocks is the one before minus the total number of allocated blocks + unsafe { + self.set_free_blocks(self.superblock().base().free_blocks_count - total_number_blocks_changed)?; + }; + } else { + // SAFETY: the total number of free blocks is the one before plus the total number of deallocated blocks + unsafe { + self.set_free_blocks(self.superblock().base().free_blocks_count + total_number_blocks_changed)?; + }; + } + + Ok(()) + } + + /// Sets all the given `blocs` as "used". + /// + /// # Errors + /// + /// Returns an [`BlockAlreadyInUse`](Ext2Error::BlockAlreadyInUse) error if the given block was already in use. + /// + /// Otherwise, returns the same errors as [`get_block_bitmap`](struct.Ext2.html#method.get_block_bitmap). + #[inline] + pub fn allocate_blocs(&mut self, blocks: &[u32]) -> Result<(), Error> { + self.locate_blocs(blocks, true) + } + + /// Sets all the given `blocs` as "free". + /// + /// # Errors + /// + /// Returns an [`BlockAlreadyFree`](Ext2Error::BlockAlreadyFree) error if the given block was already in use. + /// + /// Otherwise, returns the same errors as [`get_block_bitmap`](struct.Ext2.html#method.get_block_bitmap). + #[inline] + pub fn deallocate_blocs(&mut self, blocks: &[u32]) -> Result<(), Error> { + self.locate_blocs(blocks, false) + } +} + +impl> Celled> { + /// Returns a [`File`](crate::file::File) directly read on this filesystem. + /// + /// # Errors + /// + /// Returns an [`BadFileType`](Ext2Error::BadFileType) if the type of the file pointed by the given inode is ill-formed. + /// + /// Otherwise, returns the same errors as [`Inode::parse`]. + #[inline] + pub fn file(&self, inode_number: u32) -> Result, Error> { + let filesystem = self.borrow(); + let inode = filesystem.inode(inode_number)?; + match inode.file_type()? { + Type::Regular => Ok(TypeWithFile::Regular(Regular::new(&self.clone(), inode_number)?)), + Type::Directory => Ok(TypeWithFile::Directory(Directory::new(&self.clone(), inode_number)?)), + Type::SymbolicLink => Ok(TypeWithFile::SymbolicLink(SymbolicLink::new(&self.clone(), inode_number)?)), + Type::Fifo | Type::CharacterDevice | Type::BlockDevice | Type::Socket | Type::Other => unreachable!( + "The only type of files in ext2's filesystems that are written on the device are the regular files, the directories and the symbolic links" + ), + } + } } impl> FileSystem> for Celled> { @@ -235,7 +437,7 @@ impl> FileSystem> for Celled #[cfg(test)] mod test { use core::cell::RefCell; - use std::fs::File; + use std::fs::{self, File}; use itertools::Itertools; @@ -243,6 +445,7 @@ mod test { use super::Ext2; use crate::dev::celled::Celled; use crate::file::{Directory, Type, TypeWithFile}; + use crate::fs::ext2::block::Block; use crate::io::Read; use crate::path::UnixStr; @@ -267,24 +470,117 @@ mod test { assert_eq!(buf.into_iter().all_equal_value(), Ok(1)); } + #[test] + fn get_bitmap() { + let device = RefCell::new(File::options().read(true).write(true).open("./tests/fs/ext2/base.ext2").unwrap()); + let ext2 = Ext2::new(device, 0).unwrap(); + + assert_eq!(ext2.get_block_bitmap(0).unwrap().len() * 8, ext2.superblock().base().blocks_per_group as usize); + } + #[test] fn free_block_numbers() { let device = RefCell::new(File::options().read(true).write(true).open("./tests/fs/ext2/base.ext2").unwrap()); let ext2 = Ext2::new(device, 0).unwrap(); + let free_blocks = ext2.free_blocks(1_024).unwrap(); + let superblock = ext2.superblock().clone(); + let bitmap = ext2.get_block_bitmap(0).unwrap(); let fs = Celled::new(ext2); - let free_blocks = fs.free_blocks(1_024).unwrap(); assert!(free_blocks.iter().all_unique()); + + for block in free_blocks { + assert!(Block::new(fs.clone(), block).is_free(&superblock, &bitmap), "{block}"); + } } #[test] fn free_block_amount() { let device = RefCell::new(File::options().read(true).write(true).open("./tests/fs/ext2/base.ext2").unwrap()); let ext2 = Ext2::new(device, 0).unwrap(); - let fs = Celled::new(ext2); for i in 1_u32..1_024 { - assert_eq!(fs.free_blocks(i).unwrap().len(), i as usize); + assert_eq!(ext2.free_blocks(i).unwrap().len(), i as usize, "{i}"); } } + + #[test] + fn free_block_small_allocation_deallocation() { + fs::copy( + "./tests/fs/ext2/io_operations.ext2", + "./tests/fs/ext2/io_operations_copy_free_block_small_allocation_deallocation.ext2", + ) + .unwrap(); + + let device = RefCell::new( + File::options() + .read(true) + .write(true) + .open("./tests/fs/ext2/io_operations_copy_free_block_small_allocation_deallocation.ext2") + .unwrap(), + ); + let mut ext2 = Ext2::new(device, 0).unwrap(); + + let free_blocks = ext2.free_blocks(1_024).unwrap(); + ext2.allocate_blocs(&free_blocks).unwrap(); + + let fs = Celled::new(ext2); + + let superblock = fs.borrow().superblock().clone(); + for block in &free_blocks { + let bitmap = fs.borrow().get_block_bitmap(block / superblock.base().blocks_per_group).unwrap(); + assert!(Block::new(fs.clone(), *block).is_used(&superblock, &bitmap), "Allocation: {block}"); + } + + fs.borrow_mut().deallocate_blocs(&free_blocks).unwrap(); + + for block in &free_blocks { + let bitmap = fs.borrow().get_block_bitmap(block / superblock.base().blocks_per_group).unwrap(); + assert!(Block::new(fs.clone(), *block).is_free(&superblock, &bitmap), "Deallocation: {block}"); + } + + fs::remove_file("./tests/fs/ext2/io_operations_copy_free_block_small_allocation_deallocation.ext2").unwrap(); + } + + #[test] + fn free_block_big_allocation_deallocation() { + fs::copy( + "./tests/fs/ext2/io_operations.ext2", + "./tests/fs/ext2/io_operations_copy_free_block_big_allocation_deallocation.ext2", + ) + .unwrap(); + + let device = RefCell::new( + File::options() + .read(true) + .write(true) + .open("./tests/fs/ext2/io_operations_copy_free_block_big_allocation_deallocation.ext2") + .unwrap(), + ); + let mut ext2 = Ext2::new(device, 0).unwrap(); + + let free_blocks = ext2.free_blocks(20_000).unwrap(); + ext2.allocate_blocs(&free_blocks).unwrap(); + + let fs = Celled::new(ext2); + + let superblock = fs.borrow().superblock().clone(); + for block in &free_blocks { + let bitmap = fs.borrow().get_block_bitmap(block / superblock.base().blocks_per_group).unwrap(); + assert!( + Block::new(fs.clone(), *block).is_used(&superblock, &bitmap), + "Allocation: {block} ({})", + block / superblock.base().blocks_per_group + ); + } + + fs.borrow_mut().deallocate_blocs(&free_blocks).unwrap(); + + for block in &free_blocks { + let bitmap = fs.borrow().get_block_bitmap(block / superblock.base().blocks_per_group).unwrap(); + assert!(Block::new(fs.clone(), *block).is_free(&superblock, &bitmap), "Deallocation: {block}"); + } + + fs::remove_file("./tests/fs/ext2/io_operations_copy_free_block_big_allocation_deallocation.ext2").unwrap(); + } } diff --git a/src/io.rs b/src/io.rs index 11d3d3f..be9d8cd 100644 --- a/src/io.rs +++ b/src/io.rs @@ -1,5 +1,6 @@ //! General traits for I/O interfaces. +#[cfg(any(feature = "std", test))] use derive_more::{Deref, DerefMut}; use crate::dev::error::DevError; @@ -124,7 +125,7 @@ pub enum SeekFrom { Current(i64), } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", test))] impl From for SeekFrom { #[inline] fn from(value: std::io::SeekFrom) -> Self { @@ -136,7 +137,7 @@ impl From for SeekFrom { } } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", test))] impl From for std::io::SeekFrom { #[inline] fn from(value: SeekFrom) -> Self { @@ -165,14 +166,14 @@ pub trait Seek: Base { /// A wrapper struct for types that have implementations for [`std::io`] traits. /// /// [`Read`], [`Write`] and [`Seek`] are implemented for this type if the corresponding [`std::io`] trait is implemented for `T`. -#[cfg(feature = "std")] +#[cfg(any(feature = "std", test))] #[derive(Deref, DerefMut)] pub struct StdIOWrapper { /// Inner object, supposedly implementing at least one [`std::io`] trait. inner: S, } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", test))] impl StdIOWrapper { /// Creates an [`StdIOWrapper`] from the object it wraps. #[inline] @@ -182,12 +183,12 @@ impl StdIOWrapper { } } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", test))] impl Base for StdIOWrapper { type Error = std::io::Error; } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", test))] impl Read for StdIOWrapper { #[inline] fn read(&mut self, buf: &mut [u8]) -> Result> { @@ -195,7 +196,7 @@ impl Read for StdIOWrapper { } } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", test))] impl Write for StdIOWrapper { #[inline] fn write(&mut self, buf: &[u8]) -> Result> { @@ -208,7 +209,7 @@ impl Write for StdIOWrapper { } } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", test))] impl Seek for StdIOWrapper { #[inline] fn seek(&mut self, pos: SeekFrom) -> Result> { @@ -216,7 +217,7 @@ impl Seek for StdIOWrapper { } } -#[cfg(feature = "std")] +#[cfg(any(feature = "std", test))] impl From for StdIOWrapper { #[inline] fn from(value: S) -> Self { diff --git a/src/lib.rs b/src/lib.rs index 401c954..90a892a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -69,7 +69,7 @@ extern crate alloc; extern crate core; -#[cfg(feature = "std")] +#[cfg(any(feature = "std", test))] extern crate std; pub mod dev;