diff --git a/kernel/aster-nix/src/fs/ext2/block_group.rs b/kernel/aster-nix/src/fs/ext2/block_group.rs index 77be1c84f0..c74366413d 100644 --- a/kernel/aster-nix/src/fs/ext2/block_group.rs +++ b/kernel/aster-nix/src/fs/ext2/block_group.rs @@ -3,6 +3,7 @@ use aster_util::id_allocator::IdAlloc; use super::{ + block_ptr::Ext2Bid, fs::Ext2, inode::{Inode, InodeDesc, RawInode}, prelude::*, @@ -18,7 +19,7 @@ pub(super) struct BlockGroup { } struct BlockGroupImpl { - inode_table_bid: Bid, + inode_table_bid: Ext2Bid, raw_inodes_size: usize, inner: RwMutex, fs: Weak, @@ -47,12 +48,12 @@ impl BlockGroup { GroupDescriptor::from(raw_descriptor) }; - let get_bitmap = |bid: Bid, capacity: usize| -> Result { + let get_bitmap = |bid: Ext2Bid, capacity: usize| -> Result { if capacity > BLOCK_SIZE * 8 { return_errno_with_message!(Errno::EINVAL, "bad bitmap"); } let mut buf = vec![0u8; BLOCK_SIZE]; - block_device.read_bytes(bid.to_offset(), &mut buf)?; + block_device.read_bytes(bid as usize * BLOCK_SIZE, &mut buf)?; Ok(IdAlloc::from_bytes_with_capacity(&buf, capacity)) }; @@ -173,27 +174,38 @@ impl BlockGroup { inner.inode_cache.remove(&inode_idx); } - /// Allocates and returns a block index. - pub fn alloc_block(&self) -> Option { + /// Allocates and returns a consecutive range of block indices. + /// + /// Returns `None` if the allocation fails. + /// + /// The actual allocated range size may be smaller than the requested `count` if + /// insufficient consecutive blocks are available. + pub fn alloc_blocks(&self, count: Ext2Bid) -> Option> { // The fast path if self.bg_impl.inner.read().metadata.free_blocks_count() == 0 { return None; } // The slow path - self.bg_impl.inner.write().metadata.alloc_block() + self.bg_impl.inner.write().metadata.alloc_blocks(count) } - /// Frees the allocated block idx. + /// Frees the consecutive range of allocated block indices. /// /// # Panic /// - /// If `block_idx` has not been allocated before, then the method panics. - pub fn free_block(&self, block_idx: u32) { - let mut inner = self.bg_impl.inner.write(); - assert!(inner.metadata.is_block_allocated(block_idx)); + /// If the `range` is out of bounds, this method will panic. + /// If one of the `idx` in `range` has not been allocated before, then the method panics. + pub fn free_blocks(&self, range: Range) { + if range.is_empty() { + return; + } - inner.metadata.free_block(block_idx); + let mut inner = self.bg_impl.inner.write(); + for idx in range.clone() { + assert!(inner.metadata.is_block_allocated(idx)); + } + inner.metadata.free_blocks(range); } /// Writes back the raw inode metadata to the raw inode metadata cache. @@ -206,7 +218,7 @@ impl BlockGroup { } /// Writes back the metadata of this group. - pub fn sync_metadata(&self, super_block: &SuperBlock) -> Result<()> { + pub fn sync_metadata(&self) -> Result<()> { if !self.bg_impl.inner.read().metadata.is_dirty() { return Ok(()); } @@ -219,14 +231,14 @@ impl BlockGroup { let mut bio_waiter = BioWaiter::new(); // Writes back the inode bitmap. - let inode_bitmap_bid = inner.metadata.descriptor.inode_bitmap_bid; + let inode_bitmap_bid = Bid::new(inner.metadata.descriptor.inode_bitmap_bid as u64); bio_waiter.concat(fs.block_device().write_bytes_async( inode_bitmap_bid.to_offset(), inner.metadata.inode_bitmap.as_bytes(), )?); // Writes back the block bitmap. - let block_bitmap_bid = inner.metadata.descriptor.block_bitmap_bid; + let block_bitmap_bid = Bid::new(inner.metadata.descriptor.block_bitmap_bid as u64); bio_waiter.concat(fs.block_device().write_bytes_async( block_bitmap_bid.to_offset(), inner.metadata.block_bitmap.as_bytes(), @@ -307,12 +319,12 @@ impl Debug for BlockGroup { impl PageCacheBackend for BlockGroupImpl { fn read_page(&self, idx: usize, frame: &VmFrame) -> Result { - let bid = self.inode_table_bid + idx as u64; + let bid = self.inode_table_bid + idx as Ext2Bid; self.fs.upgrade().unwrap().read_block_async(bid, frame) } fn write_page(&self, idx: usize, frame: &VmFrame) -> Result { - let bid = self.inode_table_bid + idx as u64; + let bid = self.inode_table_bid + idx as Ext2Bid; self.fs.upgrade().unwrap().write_block_async(bid, frame) } @@ -356,19 +368,28 @@ impl GroupMetadata { } } - pub fn is_block_allocated(&self, block_idx: u32) -> bool { + pub fn is_block_allocated(&self, block_idx: Ext2Bid) -> bool { self.block_bitmap.is_allocated(block_idx as usize) } - pub fn alloc_block(&mut self) -> Option { - let block_idx = self.block_bitmap.alloc()?; - self.dec_free_blocks(); - Some(block_idx as u32) + pub fn alloc_blocks(&mut self, count: Ext2Bid) -> Option> { + let mut current_count = count.min(self.free_blocks_count() as Ext2Bid) as usize; + while current_count > 0 { + let Some(range) = self.block_bitmap.alloc_consecutive(current_count) else { + // It is efficient to halve the value + current_count /= 2; + continue; + }; + self.dec_free_blocks(current_count as u16); + return Some((range.start as Ext2Bid)..(range.end as Ext2Bid)); + } + None } - pub fn free_block(&mut self, block_idx: u32) { - self.block_bitmap.free(block_idx as usize); - self.inc_free_blocks(); + pub fn free_blocks(&mut self, range: Range) { + self.block_bitmap + .free_consecutive((range.start as usize)..(range.end as usize)); + self.inc_free_blocks(range.len() as u16); } pub fn free_inodes_count(&self) -> u16 { @@ -388,13 +409,20 @@ impl GroupMetadata { self.descriptor.free_inodes_count -= 1; } - pub fn inc_free_blocks(&mut self) { - self.descriptor.free_blocks_count += 1; + pub fn inc_free_blocks(&mut self, count: u16) { + self.descriptor.free_blocks_count = self + .descriptor + .free_blocks_count + .checked_add(count) + .unwrap(); } - pub fn dec_free_blocks(&mut self) { - debug_assert!(self.descriptor.free_blocks_count > 0); - self.descriptor.free_blocks_count -= 1; + pub fn dec_free_blocks(&mut self, count: u16) { + self.descriptor.free_blocks_count = self + .descriptor + .free_blocks_count + .checked_sub(count) + .unwrap(); } pub fn inc_dirs(&mut self) { @@ -414,11 +442,11 @@ impl GroupMetadata { #[derive(Clone, Copy, Debug)] struct GroupDescriptor { /// Blocks usage bitmap block - block_bitmap_bid: Bid, + block_bitmap_bid: Ext2Bid, /// Inodes usage bitmap block - inode_bitmap_bid: Bid, + inode_bitmap_bid: Ext2Bid, /// Starting block of inode table - inode_table_bid: Bid, + inode_table_bid: Ext2Bid, /// Number of free blocks in group free_blocks_count: u16, /// Number of free inodes in group @@ -430,9 +458,9 @@ struct GroupDescriptor { impl From for GroupDescriptor { fn from(desc: RawGroupDescriptor) -> Self { Self { - block_bitmap_bid: Bid::new(desc.block_bitmap as _), - inode_bitmap_bid: Bid::new(desc.inode_bitmap as _), - inode_table_bid: Bid::new(desc.inode_table as _), + block_bitmap_bid: desc.block_bitmap, + inode_bitmap_bid: desc.inode_bitmap, + inode_table_bid: desc.inode_table, free_blocks_count: desc.free_blocks_count, free_inodes_count: desc.free_inodes_count, dirs_count: desc.dirs_count, @@ -461,9 +489,9 @@ pub(super) struct RawGroupDescriptor { impl From<&GroupDescriptor> for RawGroupDescriptor { fn from(desc: &GroupDescriptor) -> Self { Self { - block_bitmap: desc.block_bitmap_bid.to_raw() as _, - inode_bitmap: desc.inode_bitmap_bid.to_raw() as _, - inode_table: desc.inode_table_bid.to_raw() as _, + block_bitmap: desc.block_bitmap_bid, + inode_bitmap: desc.inode_bitmap_bid, + inode_table: desc.inode_table_bid, free_blocks_count: desc.free_blocks_count, free_inodes_count: desc.free_inodes_count, dirs_count: desc.dirs_count, diff --git a/kernel/aster-nix/src/fs/ext2/block_ptr.rs b/kernel/aster-nix/src/fs/ext2/block_ptr.rs new file mode 100644 index 0000000000..4deba76dd9 --- /dev/null +++ b/kernel/aster-nix/src/fs/ext2/block_ptr.rs @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: MPL-2.0 + +use super::prelude::*; + +pub type Ext2Bid = u32; + +/// The pointers to blocks for an inode. +#[repr(C)] +#[derive(Clone, Copy, Default, Debug, Pod)] +pub struct BlockPtrs { + inner: [Ext2Bid; MAX_BLOCK_PTRS], +} + +impl BlockPtrs { + /// Returns the direct block ID. + /// + /// # Panic + /// + /// If the `idx` is out of bounds, this method will panic. + pub fn direct(&self, idx: usize) -> Ext2Bid { + assert!(DIRECT_RANGE.contains(&idx)); + self.inner[idx] + } + + /// Sets the direct block ID. + /// + /// # Panic + /// + /// If the `idx` is out of bounds, this method will panic. + pub fn set_direct(&mut self, idx: usize, bid: Ext2Bid) { + assert!(DIRECT_RANGE.contains(&idx)); + self.inner[idx] = bid; + } + + /// Returns the block ID of single indirect block pointer. + pub fn indirect(&self) -> Ext2Bid { + self.inner[INDIRECT] + } + + /// Sets the block ID of single indirect block pointer. + pub fn set_indirect(&mut self, bid: Ext2Bid) { + self.inner[INDIRECT] = bid; + } + + /// Returns the block ID of double indirect block pointer. + pub fn db_indirect(&self) -> Ext2Bid { + self.inner[DB_INDIRECT] + } + + /// Sets the block ID of double indirect block pointer. + pub fn set_db_indirect(&mut self, bid: Ext2Bid) { + self.inner[DB_INDIRECT] = bid; + } + + /// Returns the block ID of treble indirect block pointer. + pub fn tb_indirect(&self) -> Ext2Bid { + self.inner[TB_INDIRECT] + } + + /// Sets the block ID of treble indirect block pointer. + pub fn set_tb_indirect(&mut self, bid: Ext2Bid) { + self.inner[TB_INDIRECT] = bid; + } + + /// Views it as a slice of `u8` bytes. + pub fn as_bytes(&self) -> &[u8] { + self.inner.as_bytes() + } + + /// Views it as a mutable slice of `u8` bytes. + pub fn as_bytes_mut(&mut self) -> &mut [u8] { + self.inner.as_bytes_mut() + } +} + +/// Represents the various ways in which a block ID can be located in Ext2. +/// It is an enum with different variants corresponding to the level of indirection +/// used to locate the block. +/// +/// We choose `u16` because it is reasonably large to represent the index. +#[derive(Debug)] +pub enum BidPath { + /// Direct reference to a block. The block can be accessed directly through the given + /// index with no levels of indirection. + Direct(u16), + /// Single level of indirection. The block ID can be found at the specified index + /// within an indirect block. + Indirect(u16), + /// Double level of indirection. The first item is the index of the first-level + /// indirect block, and the second item is the index within the second-level + /// indirect block where the block ID can be found. + DbIndirect(u16, u16), + /// Treble level of indirection. The three values represent the index within the + /// first-level, second-level, and third-level indirect blocks, respectively. + /// The block ID can be found at the third-level indirect block. + TbIndirect(u16, u16, u16), +} + +impl From for BidPath { + fn from(bid: Ext2Bid) -> Self { + if bid < MAX_DIRECT_BLOCKS { + Self::Direct(bid as u16) + } else if bid < MAX_DIRECT_BLOCKS + MAX_INDIRECT_BLOCKS { + let indirect_bid = bid - MAX_DIRECT_BLOCKS; + Self::Indirect(indirect_bid as u16) + } else if bid < MAX_DIRECT_BLOCKS + MAX_INDIRECT_BLOCKS + MAX_DB_INDIRECT_BLOCKS { + let db_indirect_bid = bid - (MAX_DIRECT_BLOCKS + MAX_INDIRECT_BLOCKS); + let lvl1_idx = (db_indirect_bid / MAX_INDIRECT_BLOCKS) as u16; + let lvl2_idx = (db_indirect_bid % MAX_INDIRECT_BLOCKS) as u16; + Self::DbIndirect(lvl1_idx, lvl2_idx) + } else if bid + < MAX_DIRECT_BLOCKS + + MAX_INDIRECT_BLOCKS + + MAX_DB_INDIRECT_BLOCKS + + MAX_TB_INDIRECT_BLOCKS + { + let tb_indirect_bid = + bid - (MAX_DIRECT_BLOCKS + MAX_INDIRECT_BLOCKS + MAX_DB_INDIRECT_BLOCKS); + let lvl1_idx = (tb_indirect_bid / MAX_DB_INDIRECT_BLOCKS) as u16; + let lvl2_idx = ((tb_indirect_bid / MAX_INDIRECT_BLOCKS) % MAX_INDIRECT_BLOCKS) as u16; + let lvl3_idx = (tb_indirect_bid % MAX_INDIRECT_BLOCKS) as u16; + Self::TbIndirect(lvl1_idx, lvl2_idx, lvl3_idx) + } else { + // The bid value in Ext2 must not surpass the representation of BidPath. + unreachable!(); + } + } +} + +impl BidPath { + /// Returns the number of blocks remaining before the next indirect block is required. + pub fn cnt_to_next_indirect(&self) -> Ext2Bid { + match self { + Self::Direct(idx) => MAX_DIRECT_BLOCKS - (*idx as Ext2Bid), + Self::Indirect(idx) | Self::DbIndirect(_, idx) | Self::TbIndirect(_, _, idx) => { + MAX_INDIRECT_BLOCKS - (*idx as Ext2Bid) + } + } + } + + /// Returns the last level index. + /// + /// This index corresponds to the position of a block within the most deeply nested + /// indirect block (if any), or the direct block index if no indirection is involved. + pub fn last_lvl_idx(&self) -> usize { + match self { + Self::Direct(idx) + | Self::Indirect(idx) + | Self::DbIndirect(_, idx) + | Self::TbIndirect(_, _, idx) => *idx as _, + } + } +} + +/// Direct pointers to blocks. +pub const DIRECT_RANGE: core::ops::Range = 0..12; +/// The number of direct blocks. +pub const MAX_DIRECT_BLOCKS: Ext2Bid = DIRECT_RANGE.end as Ext2Bid; + +/// Indirect pointer to blocks. +pub const INDIRECT: usize = DIRECT_RANGE.end; +/// The number of indirect blocks. +pub const MAX_INDIRECT_BLOCKS: Ext2Bid = (BLOCK_SIZE / BID_SIZE) as Ext2Bid; + +/// Doubly indirect pointer to blocks. +pub const DB_INDIRECT: usize = INDIRECT + 1; +/// The number of doubly indirect blocks. +pub const MAX_DB_INDIRECT_BLOCKS: Ext2Bid = MAX_INDIRECT_BLOCKS * MAX_INDIRECT_BLOCKS; + +/// Treble indirect pointer to blocks. +pub const TB_INDIRECT: usize = DB_INDIRECT + 1; +/// The number of trebly indirect blocks. +pub const MAX_TB_INDIRECT_BLOCKS: Ext2Bid = MAX_INDIRECT_BLOCKS * MAX_DB_INDIRECT_BLOCKS; + +/// The number of block pointers. +pub const MAX_BLOCK_PTRS: usize = TB_INDIRECT + 1; + +/// The size of of the block id. +pub const BID_SIZE: usize = core::mem::size_of::(); diff --git a/kernel/aster-nix/src/fs/ext2/fs.rs b/kernel/aster-nix/src/fs/ext2/fs.rs index cce3f78b2a..1f44528a29 100644 --- a/kernel/aster-nix/src/fs/ext2/fs.rs +++ b/kernel/aster-nix/src/fs/ext2/fs.rs @@ -2,6 +2,7 @@ use super::{ block_group::{BlockGroup, RawGroupDescriptor}, + block_ptr::Ext2Bid, inode::{FilePerm, FileType, Inode, InodeDesc, RawInode}, prelude::*, super_block::{RawSuperBlock, SuperBlock, SUPER_BLOCK_OFFSET}, @@ -17,7 +18,7 @@ pub struct Ext2 { super_block: RwMutex>, block_groups: Vec, inodes_per_group: u32, - blocks_per_group: u32, + blocks_per_group: Ext2Bid, inode_size: usize, block_size: usize, group_descriptors_segment: VmSegment, @@ -112,7 +113,7 @@ impl Ext2 { } /// Returns the number of blocks in each block group. - pub fn blocks_per_group(&self) -> u32 { + pub fn blocks_per_group(&self) -> Ext2Bid { self.blocks_per_group } @@ -208,46 +209,93 @@ impl Ext2 { Ok(()) } - /// Allocates a new block. + /// Allocates a consecutive range of blocks. /// - /// Attempts to allocate from the `block_group_idx` group first. + /// The returned allocated range size may be smaller than the requested `count` if + /// insufficient consecutive blocks are available. + /// + /// Attempts to allocate blocks from the `block_group_idx` group first. /// If allocation is not possible from this group, then search the remaining groups. - pub(super) fn alloc_block(&self, block_group_idx: usize) -> Result { - let mut block_group_idx = block_group_idx; - if block_group_idx >= self.block_groups.len() { - return_errno_with_message!(Errno::EINVAL, "invalid block group idx"); + pub(super) fn alloc_blocks( + &self, + mut block_group_idx: usize, + count: Ext2Bid, + ) -> Option> { + if count > self.super_block.read().free_blocks_count() { + return None; } + let mut remaining_count = count; + let mut allocated_range: Option> = None; for _ in 0..self.block_groups.len() { + if remaining_count == 0 { + break; + } + if block_group_idx >= self.block_groups.len() { block_group_idx = 0; } let block_group = &self.block_groups[block_group_idx]; - if let Some(block_idx) = block_group.alloc_block() { - let bid = block_group_idx as u32 * self.blocks_per_group + block_idx; - self.super_block.write().dec_free_blocks(); - return Ok(Bid::new(bid as _)); + if let Some(range_in_group) = block_group.alloc_blocks(remaining_count) { + let device_range = { + let start = + (block_group_idx as Ext2Bid) * self.blocks_per_group + range_in_group.start; + start..start + (range_in_group.len() as Ext2Bid) + }; + match allocated_range { + Some(ref mut range) => { + if range.end == device_range.start { + // Accumulate consecutive bids + range.end = device_range.end; + remaining_count -= range_in_group.len() as Ext2Bid; + } else { + block_group.free_blocks(range_in_group); + break; + } + } + None => { + allocated_range = Some(device_range); + } + } } block_group_idx += 1; } - return_errno_with_message!(Errno::ENOSPC, "no space on device"); + if let Some(range) = allocated_range.as_ref() { + self.super_block + .write() + .dec_free_blocks(range.len() as Ext2Bid); + } + allocated_range } - /// Frees a block. - pub(super) fn free_block(&self, bid: Bid) -> Result<()> { - let (_, block_group) = self.block_group_of_bid(bid)?; - let block_idx = self.block_idx(bid); - // In order to prevent value underflow, it is necessary to increment - // the free block counter prior to freeing the block. - self.super_block.write().inc_free_blocks(); - block_group.free_block(block_idx); + /// Frees a range of blocks. + pub(super) fn free_blocks(&self, range: Range) -> Result<()> { + let mut current_range = range.clone(); + while !current_range.is_empty() { + let (_, block_group) = self.block_group_of_bid(current_range.start)?; + let range_in_group = { + let start = self.block_idx(current_range.start); + let len = (current_range.len() as Ext2Bid).min(self.blocks_per_group - start); + start..start + len + }; + // In order to prevent value underflow, it is necessary to increment + // the free block counter prior to freeing the block. + self.super_block + .write() + .inc_free_blocks(range_in_group.len() as Ext2Bid); + block_group.free_blocks(range_in_group.clone()); + current_range.start += range_in_group.len() as Ext2Bid + } + Ok(()) } /// Reads contiguous blocks starting from the `bid` synchronously. - pub(super) fn read_blocks(&self, bid: Bid, segment: &VmSegment) -> Result<()> { - let status = self.block_device.read_blocks_sync(bid, segment)?; + pub(super) fn read_blocks(&self, bid: Ext2Bid, segment: &VmSegment) -> Result<()> { + let status = self + .block_device + .read_blocks_sync(Bid::new(bid as u64), segment)?; match status { BioStatus::Complete => Ok(()), err_status => Err(Error::from(err_status)), @@ -255,8 +303,10 @@ impl Ext2 { } /// Reads one block indicated by the `bid` synchronously. - pub(super) fn read_block(&self, bid: Bid, frame: &VmFrame) -> Result<()> { - let status = self.block_device.read_block_sync(bid, frame)?; + pub(super) fn read_block(&self, bid: Ext2Bid, frame: &VmFrame) -> Result<()> { + let status = self + .block_device + .read_block_sync(Bid::new(bid as u64), frame)?; match status { BioStatus::Complete => Ok(()), err_status => Err(Error::from(err_status)), @@ -264,14 +314,16 @@ impl Ext2 { } /// Reads one block indicated by the `bid` asynchronously. - pub(super) fn read_block_async(&self, bid: Bid, frame: &VmFrame) -> Result { - let waiter = self.block_device.read_block(bid, frame)?; + pub(super) fn read_block_async(&self, bid: Ext2Bid, frame: &VmFrame) -> Result { + let waiter = self.block_device.read_block(Bid::new(bid as u64), frame)?; Ok(waiter) } /// Writes contiguous blocks starting from the `bid` synchronously. - pub(super) fn write_blocks(&self, bid: Bid, segment: &VmSegment) -> Result<()> { - let status = self.block_device.write_blocks_sync(bid, segment)?; + pub(super) fn write_blocks(&self, bid: Ext2Bid, segment: &VmSegment) -> Result<()> { + let status = self + .block_device + .write_blocks_sync(Bid::new(bid as u64), segment)?; match status { BioStatus::Complete => Ok(()), err_status => Err(Error::from(err_status)), @@ -279,8 +331,10 @@ impl Ext2 { } /// Writes one block indicated by the `bid` synchronously. - pub(super) fn write_block(&self, bid: Bid, frame: &VmFrame) -> Result<()> { - let status = self.block_device.write_block_sync(bid, frame)?; + pub(super) fn write_block(&self, bid: Ext2Bid, frame: &VmFrame) -> Result<()> { + let status = self + .block_device + .write_block_sync(Bid::new(bid as u64), frame)?; match status { BioStatus::Complete => Ok(()), err_status => Err(Error::from(err_status)), @@ -288,8 +342,8 @@ impl Ext2 { } /// Writes one block indicated by the `bid` asynchronously. - pub(super) fn write_block_async(&self, bid: Bid, frame: &VmFrame) -> Result { - let waiter = self.block_device.write_block(bid, frame)?; + pub(super) fn write_block_async(&self, bid: Ext2Bid, frame: &VmFrame) -> Result { + let waiter = self.block_device.write_block(Bid::new(bid as u64), frame)?; Ok(waiter) } @@ -303,7 +357,7 @@ impl Ext2 { let mut super_block = self.super_block.write(); // Writes back the metadata of block groups for block_group in &self.block_groups { - block_group.sync_metadata(&super_block)?; + block_group.sync_metadata()?; } let mut bio_waiter = BioWaiter::new(); @@ -353,10 +407,10 @@ impl Ext2 { } #[inline] - fn block_group_of_bid(&self, bid: Bid) -> Result<(usize, &BlockGroup)> { - let block_group_idx = (bid.to_raw() / (self.blocks_per_group as u64)) as usize; + fn block_group_of_bid(&self, bid: Ext2Bid) -> Result<(usize, &BlockGroup)> { + let block_group_idx = (bid / self.blocks_per_group) as usize; if block_group_idx >= self.block_groups.len() { - return_errno!(Errno::ENOENT); + return_errno_with_message!(Errno::EINVAL, "invalid bid"); } Ok((block_group_idx, &self.block_groups[block_group_idx])) } @@ -376,7 +430,7 @@ impl Ext2 { } #[inline] - fn block_idx(&self, bid: Bid) -> u32 { - (bid.to_raw() as u32) % self.blocks_per_group + fn block_idx(&self, bid: Ext2Bid) -> Ext2Bid { + bid % self.blocks_per_group } } diff --git a/kernel/aster-nix/src/fs/ext2/impl_for_vfs/fs.rs b/kernel/aster-nix/src/fs/ext2/impl_for_vfs/fs.rs index 7fc4c11419..bf2626a9da 100644 --- a/kernel/aster-nix/src/fs/ext2/impl_for_vfs/fs.rs +++ b/kernel/aster-nix/src/fs/ext2/impl_for_vfs/fs.rs @@ -36,10 +36,10 @@ impl From>> for SuperBlock { magic: EXT2_MAGIC as _, bsize: ext2_sb.block_size(), blocks: ext2_sb.total_blocks() as _, - bfree: ext2_sb.free_blocks() as _, - bavail: ext2_sb.free_blocks() as _, + bfree: ext2_sb.free_blocks_count() as _, + bavail: ext2_sb.free_blocks_count() as _, files: ext2_sb.total_inodes() as _, - ffree: ext2_sb.free_inodes() as _, + ffree: ext2_sb.free_inodes_count() as _, fsid: 0, // TODO namelen: NAME_MAX, frsize: ext2_sb.fragment_size(), diff --git a/kernel/aster-nix/src/fs/ext2/indirect_block_cache.rs b/kernel/aster-nix/src/fs/ext2/indirect_block_cache.rs new file mode 100644 index 0000000000..0dfcaf0927 --- /dev/null +++ b/kernel/aster-nix/src/fs/ext2/indirect_block_cache.rs @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: MPL-2.0 + +use lru::LruCache; + +use super::{block_ptr::BID_SIZE, fs::Ext2, prelude::*}; + +/// `IndirectBlockCache` is a caching structure that stores `IndirectBlock` objects for Ext2. +/// +/// This cache uses an `LruCache` to manage the indirect blocks, ensuring that frequently accessed +/// blocks remain in memory for quick retrieval, while less used blocks can be evicted to make room +/// for new blocks. +#[derive(Debug)] +pub struct IndirectBlockCache { + cache: LruCache, + fs: Weak, +} + +impl IndirectBlockCache { + /// The upper bound on the size of the cache. + /// + /// Use the same value as `BH_LRU_SIZE`. + const MAX_SIZE: usize = 16; + + /// Creates a new cache. + pub fn new(fs: Weak) -> Self { + Self { + cache: LruCache::unbounded(), + fs, + } + } + + /// Retrieves a reference to an `IndirectBlock` by its `bid`. + /// + /// If the block is not present in the cache, it will be loaded from the disk. + pub fn find(&mut self, bid: u32) -> Result<&IndirectBlock> { + self.try_shrink()?; + + let fs = self.fs(); + let load_block = || -> Result { + let mut block = IndirectBlock::alloc_uninit()?; + fs.read_block(bid, &block.frame)?; + block.state = State::UpToDate; + Ok(block) + }; + + self.cache.try_get_or_insert(bid, load_block) + } + + /// Retrieves a mutable reference to an `IndirectBlock` by its `bid`. + /// + /// If the block is not present in the cache, it will be loaded from the disk. + pub fn find_mut(&mut self, bid: u32) -> Result<&mut IndirectBlock> { + self.try_shrink()?; + + let fs = self.fs(); + let load_block = || -> Result { + let mut block = IndirectBlock::alloc_uninit()?; + fs.read_block(bid, &block.frame)?; + block.state = State::UpToDate; + Ok(block) + }; + + self.cache.try_get_or_insert_mut(bid, load_block) + } + + /// Inserts or updates an `IndirectBlock` in the cache with the specified `bid`. + pub fn insert(&mut self, bid: u32, block: IndirectBlock) -> Result<()> { + self.try_shrink()?; + self.cache.put(bid, block); + Ok(()) + } + + /// Removes and returns the `IndirectBlock` corresponding to the `bid` + /// from the cache or `None` if does not exist. + pub fn remove(&mut self, bid: u32) -> Option { + self.cache.pop(&bid) + } + + /// Evicts all blocks from the cache, persisting any with a 'Dirty' state to the disk. + pub fn evict_all(&mut self) -> Result<()> { + let mut bio_waiter = BioWaiter::new(); + loop { + let Some((bid, block)) = self.cache.pop_lru() else { + break; + }; + + if block.is_dirty() { + bio_waiter.concat( + self.fs() + .block_device() + .write_block(Bid::new(bid as _), &block.frame)?, + ); + } + } + + bio_waiter.wait().ok_or_else(|| { + Error::with_message(Errno::EIO, "failed to evict_all the indirect blocks") + })?; + + Ok(()) + } + + /// Attempts to shrink the cache size if it exceeds the maximum allowed cache size. + fn try_shrink(&mut self) -> Result<()> { + if self.cache.len() < Self::MAX_SIZE { + return Ok(()); + } + + let mut bio_waiter = BioWaiter::new(); + for _ in 0..(Self::MAX_SIZE / 2) { + let (bid, block) = self.cache.pop_lru().unwrap(); + if block.is_dirty() { + bio_waiter.concat( + self.fs() + .block_device() + .write_block(Bid::new(bid as _), &block.frame)?, + ); + } + } + + bio_waiter.wait().ok_or_else(|| { + Error::with_message(Errno::EIO, "failed to write back the indirect block") + })?; + + Ok(()) + } + + #[inline] + fn fs(&self) -> Arc { + self.fs.upgrade().unwrap() + } +} + +/// Represents a single indirect block buffer cached by the `IndirectCache`. +#[derive(Clone, Debug)] +pub struct IndirectBlock { + frame: VmFrame, + state: State, +} + +impl IndirectBlock { + /// Allocates an uninitialized block whose bytes are to be populated with + /// data loaded from the disk. + fn alloc_uninit() -> Result { + let frame = VmAllocOptions::new(1).uninit(true).alloc_single()?; + Ok(Self { + frame, + state: State::Uninit, + }) + } + + /// Allocates a new block with its bytes initialized to zero. + pub fn alloc() -> Result { + let frame = VmAllocOptions::new(1).alloc_single()?; + Ok(Self { + frame, + state: State::Dirty, + }) + } + + /// Returns `true` if it is in dirty state. + pub fn is_dirty(&self) -> bool { + self.state == State::Dirty + } + + /// Reads a bid at a specified `idx`. + pub fn read_bid(&self, idx: usize) -> Result { + assert!(self.state != State::Uninit); + let bid: u32 = self.frame.read_val(idx * BID_SIZE)?; + Ok(bid) + } + + /// Writes a value of bid at a specified `idx`. + /// + /// After a successful write operation, the block's state will be marked as dirty. + pub fn write_bid(&mut self, idx: usize, bid: &u32) -> Result<()> { + assert!(self.state != State::Uninit); + self.frame.write_val(idx * BID_SIZE, bid)?; + self.state = State::Dirty; + Ok(()) + } +} + +#[derive(Clone, Eq, PartialEq, Debug)] +enum State { + /// Indicates a new allocated block which content has not been initialized. + Uninit, + /// Indicates a block which content is consistent with corresponding disk content. + UpToDate, + /// indicates a block which content has been updated and not written back to underlying disk. + Dirty, +} diff --git a/kernel/aster-nix/src/fs/ext2/inode.rs b/kernel/aster-nix/src/fs/ext2/inode.rs index fc719d83d1..9100d76554 100644 --- a/kernel/aster-nix/src/fs/ext2/inode.rs +++ b/kernel/aster-nix/src/fs/ext2/inode.rs @@ -1,35 +1,21 @@ // SPDX-License-Identifier: MPL-2.0 -use core::cmp::Ordering; - use inherit_methods_macro::inherit_methods; use super::{ + block_ptr::{BidPath, BlockPtrs, Ext2Bid, BID_SIZE, MAX_BLOCK_PTRS}, blocks_hole::BlocksHoleDesc, dir::{DirEntry, DirEntryReader, DirEntryWriter}, fs::Ext2, + indirect_block_cache::{IndirectBlock, IndirectBlockCache}, prelude::*, }; -mod field { - pub type Field = core::ops::Range; - - /// Direct pointer to blocks. - pub const DIRECT: Field = 0..12; - /// Indirect pointer to blocks. - pub const INDIRECT: Field = 12..13; - /// Doubly indirect pointer to blocks. - pub const DB_INDIRECT: Field = 13..14; - /// Trebly indirect pointer to blocks. - pub const TB_INDIRECT: Field = 14..15; -} - -/// The number of block pointers. -pub const BLOCK_PTR_CNT: usize = field::TB_INDIRECT.end; /// Max length of file name. pub const MAX_FNAME_LEN: usize = 255; + /// Max path length of the fast symlink. -pub const FAST_SYMLINK_MAX_LEN: usize = BLOCK_PTR_CNT * core::mem::size_of::(); +pub const MAX_FAST_SYMLINK_LEN: usize = MAX_BLOCK_PTRS * BID_SIZE; /// The Ext2 inode. pub struct Inode { @@ -49,7 +35,7 @@ impl Inode { Arc::new_cyclic(|weak_self| Self { ino, block_group_idx, - inner: RwMutex::new(Inner::new(desc, weak_self.clone())), + inner: RwMutex::new(Inner::new(desc, weak_self.clone(), fs.clone())), fs, }) } @@ -567,7 +553,7 @@ impl Inode { pub fn gid(&self) -> u32; pub fn file_flags(&self) -> FileFlags; pub fn hard_links(&self) -> u16; - pub fn blocks_count(&self) -> u32; + pub fn blocks_count(&self) -> Ext2Bid; pub fn acl(&self) -> Option; pub fn atime(&self) -> Duration; pub fn mtime(&self) -> Duration; @@ -613,7 +599,7 @@ impl Inner { pub fn hard_links(&self) -> u16; pub fn inc_hard_links(&mut self); pub fn dec_hard_links(&mut self); - pub fn blocks_count(&self) -> u32; + pub fn blocks_count(&self) -> Ext2Bid; pub fn acl(&self) -> Option; pub fn atime(&self) -> Duration; pub fn set_atime(&mut self, time: Duration); @@ -626,9 +612,9 @@ impl Inner { } impl Inner { - pub fn new(desc: Dirty, weak_self: Weak) -> Self { + pub fn new(desc: Dirty, weak_self: Weak, fs: Weak) -> Self { let num_page_bytes = desc.num_page_bytes(); - let inode_impl = InodeImpl::new(desc, weak_self); + let inode_impl = InodeImpl::new(desc, weak_self, fs); Self { page_cache: PageCache::with_capacity(num_page_bytes, Arc::downgrade(&inode_impl) as _) .unwrap(), @@ -670,7 +656,8 @@ impl Inner { let mut buf_offset = 0; for bid in Bid::from_offset(offset)..Bid::from_offset(offset + read_len) { let frame = VmAllocOptions::new(1).uninit(true).alloc_single().unwrap(); - self.inode_impl.read_block_sync(bid, &frame)?; + self.inode_impl + .read_block_sync(bid.to_raw() as Ext2Bid, &frame)?; frame.read_bytes(0, &mut buf[buf_offset..buf_offset + BLOCK_SIZE])?; buf_offset += BLOCK_SIZE; } @@ -710,7 +697,8 @@ impl Inner { frame.write_bytes(0, &buf[buf_offset..buf_offset + BLOCK_SIZE])?; frame }; - self.inode_impl.write_block_sync(bid, &frame)?; + self.inode_impl + .write_block_sync(bid.to_raw() as Ext2Bid, &frame)?; buf_offset += BLOCK_SIZE; } @@ -718,7 +706,7 @@ impl Inner { } pub fn write_link(&mut self, target: &str) -> Result<()> { - if target.len() <= FAST_SYMLINK_MAX_LEN { + if target.len() <= MAX_FAST_SYMLINK_LEN { return self.inode_impl.write_link(target); } @@ -733,7 +721,7 @@ impl Inner { pub fn read_link(&self) -> Result { let file_size = self.inode_impl.file_size(); - if file_size <= FAST_SYMLINK_MAX_LEN { + if file_size <= MAX_FAST_SYMLINK_LEN { return self.inode_impl.read_link(); } @@ -825,17 +813,21 @@ struct InodeImpl(RwMutex); struct InodeImpl_ { desc: Dirty, - blocks_hole_desc: BlocksHoleDesc, + blocks_hole_desc: RwLock, + indirect_blocks: RwMutex, is_freed: bool, + last_alloc_device_bid: Option, weak_self: Weak, } impl InodeImpl_ { - pub fn new(desc: Dirty, weak_self: Weak) -> Self { + pub fn new(desc: Dirty, weak_self: Weak, fs: Weak) -> Self { Self { - blocks_hole_desc: BlocksHoleDesc::new(desc.blocks_count() as usize), + blocks_hole_desc: RwLock::new(BlocksHoleDesc::new(desc.blocks_count() as usize)), desc, + indirect_blocks: RwMutex::new(IndirectBlockCache::new(fs)), is_freed: false, + last_alloc_device_bid: None, weak_self, } } @@ -844,40 +836,45 @@ impl InodeImpl_ { self.weak_self.upgrade().unwrap() } - pub fn read_block_async(&self, bid: Bid, block: &VmFrame) -> Result { - let bid = bid.to_raw() as u32; + pub fn fs(&self) -> Arc { + self.inode().fs() + } + + pub fn read_block_async(&self, bid: Ext2Bid, block: &VmFrame) -> Result { if bid >= self.desc.blocks_count() { return_errno!(Errno::EINVAL); } - debug_assert!(field::DIRECT.contains(&(bid as usize))); - if self.blocks_hole_desc.is_hole(bid as usize) { + if self.blocks_hole_desc.read().is_hole(bid as usize) { block.writer().fill(0); return Ok(BioWaiter::new()); } - let device_bid = Bid::new(self.desc.data[bid as usize] as _); - self.inode().fs().read_block_async(device_bid, block) + + let device_range = DeviceRangeReader::new(self, bid..bid + 1)?.read()?; + self.fs().read_block_async(device_range.start, block) } - pub fn read_block_sync(&self, bid: Bid, block: &VmFrame) -> Result<()> { + pub fn read_block_sync(&self, bid: Ext2Bid, block: &VmFrame) -> Result<()> { match self.read_block_async(bid, block)?.wait() { Some(BioStatus::Complete) => Ok(()), _ => return_errno!(Errno::EIO), } } - pub fn write_block_async(&self, bid: Bid, block: &VmFrame) -> Result { - let bid = bid.to_raw() as u32; + pub fn write_block_async(&self, bid: Ext2Bid, block: &VmFrame) -> Result { if bid >= self.desc.blocks_count() { return_errno!(Errno::EINVAL); } - debug_assert!(field::DIRECT.contains(&(bid as usize))); - let device_bid = Bid::new(self.desc.data[bid as usize] as _); - self.inode().fs().write_block_async(device_bid, block) + let device_range = DeviceRangeReader::new(self, bid..bid + 1)?.read()?; + let waiter = self.fs().write_block_async(device_range.start, block)?; + + // FIXME: Unset the block hole in the callback function of bio. + self.blocks_hole_desc.write().unset(bid as usize); + Ok(waiter) } - pub fn write_block_sync(&self, bid: Bid, block: &VmFrame) -> Result<()> { + pub fn write_block_sync(&self, bid: Ext2Bid, block: &VmFrame) -> Result<()> { match self.write_block_async(bid, block)?.wait() { Some(BioStatus::Complete) => Ok(()), _ => return_errno!(Errno::EIO), @@ -885,48 +882,560 @@ impl InodeImpl_ { } pub fn resize(&mut self, new_size: usize) -> Result<()> { - let new_blocks = if self.desc.type_ == FileType::Symlink && new_size <= FAST_SYMLINK_MAX_LEN - { - 0 + let old_size = self.desc.size; + if new_size > old_size { + self.expand(new_size)?; } else { - new_size.div_ceil(BLOCK_SIZE) as u32 - }; + self.shrink(new_size); + } + Ok(()) + } + + /// Expands inode size. + /// + /// After a successful expansion, the size will be enlarged to `new_size`, + /// which may result in an increased block count. + fn expand(&mut self, new_size: usize) -> Result<()> { + let new_blocks = self.desc.size_to_blocks(new_size); let old_blocks = self.desc.blocks_count(); - match new_blocks.cmp(&old_blocks) { - Ordering::Greater => { - // Allocate blocks - for file_bid in old_blocks..new_blocks { - debug_assert!(field::DIRECT.contains(&(file_bid as usize))); - let device_bid = self - .inode() - .fs() - .alloc_block(self.inode().block_group_idx)?; - self.desc.data[file_bid as usize] = device_bid.to_raw() as u32; + // Expands block count if necessary + if new_blocks > old_blocks { + if new_blocks - old_blocks > self.fs().super_block().free_blocks_count() { + return_errno_with_message!(Errno::ENOSPC, "not enough free blocks"); + } + self.expand_blocks(old_blocks..new_blocks)?; + self.blocks_hole_desc.write().resize(new_blocks as usize); + } + + // Expands the size + self.desc.size = new_size; + Ok(()) + } + + /// Expands inode blocks. + /// + /// After a successful expansion, the block count will be enlarged to `range.end`. + fn expand_blocks(&mut self, range: Range) -> Result<()> { + let mut current_range = range.clone(); + while !current_range.is_empty() { + let Ok(expand_cnt) = self.try_expand_blocks(current_range.clone()) else { + self.shrink_blocks(range.start..current_range.start); + return_errno_with_message!(Errno::ENOSPC, "can not allocate blocks"); + }; + current_range.start += expand_cnt; + } + + Ok(()) + } + + /// Attempts to expand a range of blocks and returns the number of consecutive + /// blocks successfully allocated. + /// + /// Note that the returned number may be less than the requested range if there + /// isn't enough consecutive space available or if there is a necessity to allocate + /// indirect blocks. + fn try_expand_blocks(&mut self, range: Range) -> Result { + // Calculates the maximum number of consecutive blocks that can be allocated in + // this round, as well as the number of additional indirect blocks required for + // the allocation. + let (max_cnt, indirect_cnt) = { + let bid_path = BidPath::from(range.start); + let max_cnt = (range.len() as Ext2Bid).min(bid_path.cnt_to_next_indirect()); + let indirect_cnt = match bid_path { + BidPath::Direct(_) => 0, + BidPath::Indirect(0) => 1, + BidPath::Indirect(_) => 0, + BidPath::DbIndirect(0, 0) => 2, + BidPath::DbIndirect(_, 0) => 1, + BidPath::DbIndirect(_, _) => 0, + BidPath::TbIndirect(0, 0, 0) => 3, + BidPath::TbIndirect(_, 0, 0) => 2, + BidPath::TbIndirect(_, _, 0) => 1, + BidPath::TbIndirect(_, _, _) => 0, + }; + (max_cnt, indirect_cnt) + }; + + // Calculates the block_group_idx to advise the filesystem on which group + // to prioritize for allocation. + let block_group_idx = self + .last_alloc_device_bid + .map_or(self.inode().block_group_idx, |id| { + ((id + 1) / self.fs().blocks_per_group()) as usize + }); + + // Allocates the blocks only, no indirect blocks are required. + if indirect_cnt == 0 { + let device_range = self + .fs() + .alloc_blocks(block_group_idx, max_cnt) + .ok_or_else(|| Error::new(Errno::ENOSPC))?; + if let Err(e) = self.set_device_range(range.start, device_range.clone()) { + self.fs().free_blocks(device_range).unwrap(); + return Err(e); + } + self.desc.blocks_count = range.start + device_range.len() as Ext2Bid; + self.last_alloc_device_bid = Some(device_range.end - 1); + return Ok(device_range.len() as Ext2Bid); + } + + // Allocates the required additional indirect blocks and at least one block. + let (indirect_bids, device_range) = { + let mut indirect_bids: Vec = Vec::with_capacity(indirect_cnt as usize); + let mut total_cnt = max_cnt + indirect_cnt; + let mut device_range: Option> = None; + while device_range.is_none() { + let Some(mut range) = self.fs().alloc_blocks(block_group_idx, total_cnt) else { + for indirect_bid in indirect_bids.iter() { + self.fs() + .free_blocks(*indirect_bid..*indirect_bid + 1) + .unwrap(); + } + return_errno!(Errno::ENOSPC); + }; + total_cnt -= range.len() as Ext2Bid; + + // Stores the bids for indirect blocks. + while (indirect_bids.len() as Ext2Bid) < indirect_cnt && !range.is_empty() { + indirect_bids.push(range.start); + range.start += 1; + } + + if !range.is_empty() { + device_range = Some(range); + } + } + + (indirect_bids, device_range.unwrap()) + }; + + if let Err(e) = self.set_indirect_bids(range.start, &indirect_bids) { + self.free_indirect_blocks_required_by(range.start).unwrap(); + return Err(e); + } + + if let Err(e) = self.set_device_range(range.start, device_range.clone()) { + self.fs().free_blocks(device_range).unwrap(); + self.free_indirect_blocks_required_by(range.start).unwrap(); + return Err(e); + } + + self.desc.blocks_count = range.start + device_range.len() as Ext2Bid; + self.last_alloc_device_bid = Some(device_range.end - 1); + Ok(device_range.len() as Ext2Bid) + } + + /// Sets the device block IDs for a specified range. + /// + /// It updates the mapping between the file's block IDs and the device's block IDs + /// starting from `start_bid`. It maps each block ID in the file to the corresponding + /// block ID on the device based on the provided `device_range`. + fn set_device_range(&mut self, start_bid: Ext2Bid, device_range: Range) -> Result<()> { + match BidPath::from(start_bid) { + BidPath::Direct(idx) => { + for (i, bid) in device_range.enumerate() { + self.desc.block_ptrs.set_direct(idx as usize + i, bid); } - self.desc.blocks_count = new_blocks; } - Ordering::Equal => (), - Ordering::Less => { - // Free blocks - for file_bid in new_blocks..old_blocks { - debug_assert!(field::DIRECT.contains(&(file_bid as usize))); - let device_bid = Bid::new(self.desc.data[file_bid as usize] as _); - self.inode().fs().free_block(device_bid)?; + BidPath::Indirect(idx) => { + let indirect_bid = self.desc.block_ptrs.indirect(); + assert!(indirect_bid != 0); + let mut indirect_blocks = self.indirect_blocks.write(); + let indirect_block = indirect_blocks.find_mut(indirect_bid)?; + for (i, bid) in device_range.enumerate() { + indirect_block.write_bid(idx as usize + i, &bid)?; + } + } + BidPath::DbIndirect(lvl1_idx, lvl2_idx) => { + let mut indirect_blocks = self.indirect_blocks.write(); + let lvl1_indirect_bid = { + let db_indirect_bid = self.desc.block_ptrs.db_indirect(); + assert!(db_indirect_bid != 0); + let db_indirect_block = indirect_blocks.find(db_indirect_bid)?; + db_indirect_block.read_bid(lvl1_idx as usize)? + }; + assert!(lvl1_indirect_bid != 0); + + let lvl1_indirect_block = indirect_blocks.find_mut(lvl1_indirect_bid)?; + for (i, bid) in device_range.enumerate() { + lvl1_indirect_block.write_bid(lvl2_idx as usize + i, &bid)?; + } + } + BidPath::TbIndirect(lvl1_idx, lvl2_idx, lvl3_idx) => { + let mut indirect_blocks = self.indirect_blocks.write(); + let lvl2_indirect_bid = { + let lvl1_indirect_bid = { + let tb_indirect_bid = self.desc.block_ptrs.tb_indirect(); + assert!(tb_indirect_bid != 0); + let tb_indirect_block = indirect_blocks.find(tb_indirect_bid)?; + tb_indirect_block.read_bid(lvl1_idx as usize)? + }; + assert!(lvl1_indirect_bid != 0); + let lvl1_indirect_block = indirect_blocks.find(lvl1_indirect_bid)?; + lvl1_indirect_block.read_bid(lvl2_idx as usize)? + }; + assert!(lvl2_indirect_bid != 0); + + let lvl2_indirect_block = indirect_blocks.find_mut(lvl2_indirect_bid)?; + for (i, bid) in device_range.enumerate() { + lvl2_indirect_block.write_bid(lvl3_idx as usize + i, &bid)?; } - self.desc.blocks_count = new_blocks; } } + Ok(()) + } + + /// Sets the device block IDs for indirect blocks required by a specific block ID. + /// + /// It assigns a sequence of block IDs (`indirect_bids`) on the device to be used + /// as indirect blocks for a given file block ID (`bid`). + fn set_indirect_bids(&mut self, bid: Ext2Bid, indirect_bids: &[Ext2Bid]) -> Result<()> { + assert!((1..=3).contains(&indirect_bids.len())); + let mut indirect_blocks = self.indirect_blocks.write(); + let bid_path = BidPath::from(bid); + for indirect_bid in indirect_bids.iter() { + let indirect_block = IndirectBlock::alloc()?; + indirect_blocks.insert(*indirect_bid, indirect_block)?; + + match bid_path { + BidPath::Indirect(idx) => { + assert_eq!(idx, 0); + self.desc.block_ptrs.set_indirect(*indirect_bid); + } + BidPath::DbIndirect(lvl1_idx, lvl2_idx) => { + assert_eq!(lvl2_idx, 0); + if self.desc.block_ptrs.db_indirect() == 0 { + self.desc.block_ptrs.set_db_indirect(*indirect_bid); + } else { + let db_indirect_block = + indirect_blocks.find_mut(self.desc.block_ptrs.db_indirect())?; + db_indirect_block.write_bid(lvl1_idx as usize, indirect_bid)?; + } + } + BidPath::TbIndirect(lvl1_idx, lvl2_idx, lvl3_idx) => { + assert_eq!(lvl3_idx, 0); + if self.desc.block_ptrs.tb_indirect() == 0 { + self.desc.block_ptrs.set_tb_indirect(*indirect_bid); + } else { + let lvl1_indirect_bid = { + let tb_indirect_block = + indirect_blocks.find(self.desc.block_ptrs.tb_indirect())?; + tb_indirect_block.read_bid(lvl1_idx as usize)? + }; + + if lvl1_indirect_bid == 0 { + let tb_indirect_block = + indirect_blocks.find_mut(self.desc.block_ptrs.tb_indirect())?; + tb_indirect_block.write_bid(lvl1_idx as usize, indirect_bid)?; + } else { + let lvl1_indirect_block = + indirect_blocks.find_mut(lvl1_indirect_bid)?; + lvl1_indirect_block.write_bid(lvl2_idx as usize, indirect_bid)?; + } + } + } + BidPath::Direct(_) => panic!(), + } + } + + Ok(()) + } + + /// Shrinks inode size. + /// + /// After the reduction, the size will be shrinked to `new_size`, + /// which may result in an decreased block count. + fn shrink(&mut self, new_size: usize) { + let new_blocks = self.desc.size_to_blocks(new_size); + let old_blocks = self.desc.blocks_count(); + + // Shrinks block count if necessary + if new_blocks < old_blocks { + self.shrink_blocks(new_blocks..old_blocks); + self.blocks_hole_desc.write().resize(new_blocks as usize); + } + + // Shrinks the size self.desc.size = new_size; - self.blocks_hole_desc.resize(new_blocks as usize); + } + + /// Shrinks inode blocks. + /// + /// After the reduction, the block count will be decreased to `range.start`. + fn shrink_blocks(&mut self, range: Range) { + let mut current_range = range.clone(); + while !current_range.is_empty() { + let free_cnt = self.try_shrink_blocks(current_range.clone()); + current_range.end -= free_cnt; + } + + self.desc.blocks_count = range.start; + self.last_alloc_device_bid = if range.start == 0 { + None + } else { + Some( + DeviceRangeReader::new(self, (range.start - 1)..range.start) + .unwrap() + .read() + .unwrap() + .start, + ) + }; + } + + /// Attempts to shrink a range of blocks and returns the number of blocks + /// successfully freed. + /// + /// Note that the returned number may be less than the requested range if needs + /// to free the indirect blocks that are no longer required. + fn try_shrink_blocks(&mut self, range: Range) -> Ext2Bid { + // Calculates the maximum range of blocks that can be freed in this round. + let range = { + let max_cnt = (range.len() as Ext2Bid) + .min(BidPath::from(range.end - 1).last_lvl_idx() as Ext2Bid + 1); + (range.end - max_cnt)..range.end + }; + + let fs = self.fs(); + let device_range_reader = DeviceRangeReader::new(self, range.clone()).unwrap(); + for device_range in device_range_reader { + fs.free_blocks(device_range.clone()).unwrap(); + } + + self.free_indirect_blocks_required_by(range.start).unwrap(); + range.len() as Ext2Bid + } + + /// Frees the indirect blocks required by the specified block ID. + /// + /// It ensures that the indirect blocks that are required by the block ID + /// are properly released. + fn free_indirect_blocks_required_by(&mut self, bid: Ext2Bid) -> Result<()> { + let bid_path = BidPath::from(bid); + if bid_path.last_lvl_idx() != 0 { + return Ok(()); + } + if bid == 0 { + return Ok(()); + } + + match bid_path { + BidPath::Indirect(_) => { + let indirect_bid = self.desc.block_ptrs.indirect(); + if indirect_bid == 0 { + return Ok(()); + } + + self.desc.block_ptrs.set_indirect(0); + self.indirect_blocks.write().remove(indirect_bid); + self.fs() + .free_blocks(indirect_bid..indirect_bid + 1) + .unwrap(); + } + BidPath::DbIndirect(lvl1_idx, _) => { + let db_indirect_bid = self.desc.block_ptrs.db_indirect(); + if db_indirect_bid == 0 { + return Ok(()); + } + + let mut indirect_blocks = self.indirect_blocks.write(); + let lvl1_indirect_bid = { + let db_indirect_block = indirect_blocks.find(db_indirect_bid)?; + db_indirect_block.read_bid(lvl1_idx as usize)? + }; + if lvl1_indirect_bid != 0 { + indirect_blocks.remove(lvl1_indirect_bid); + self.fs() + .free_blocks(lvl1_indirect_bid..lvl1_indirect_bid + 1) + .unwrap(); + } + if lvl1_idx == 0 { + self.desc.block_ptrs.set_db_indirect(0); + indirect_blocks.remove(db_indirect_bid); + self.fs() + .free_blocks(db_indirect_bid..db_indirect_bid + 1) + .unwrap(); + } + } + BidPath::TbIndirect(lvl1_idx, lvl2_idx, _) => { + let tb_indirect_bid = self.desc.block_ptrs.tb_indirect(); + if tb_indirect_bid == 0 { + return Ok(()); + } + + let mut indirect_blocks = self.indirect_blocks.write(); + let lvl1_indirect_bid = { + let tb_indirect_block = indirect_blocks.find(tb_indirect_bid)?; + tb_indirect_block.read_bid(lvl1_idx as usize)? + }; + if lvl1_indirect_bid != 0 { + let lvl2_indirect_bid = { + let lvl1_indirect_block = indirect_blocks.find(lvl1_indirect_bid)?; + lvl1_indirect_block.read_bid(lvl2_idx as usize)? + }; + if lvl2_indirect_bid != 0 { + indirect_blocks.remove(lvl2_indirect_bid); + self.fs() + .free_blocks(lvl2_indirect_bid..lvl2_indirect_bid + 1) + .unwrap(); + } + if lvl2_idx == 0 { + indirect_blocks.remove(lvl1_indirect_bid); + self.fs() + .free_blocks(lvl1_indirect_bid..lvl1_indirect_bid + 1) + .unwrap(); + } + } + + if lvl2_idx == 0 && lvl1_idx == 0 { + self.desc.block_ptrs.set_tb_indirect(0); + indirect_blocks.remove(tb_indirect_bid); + self.fs() + .free_blocks(tb_indirect_bid..tb_indirect_bid + 1) + .unwrap(); + } + } + BidPath::Direct(_) => panic!(), + } + Ok(()) } } +/// A reader to get the corresponding device block IDs for a specified range. +/// +/// It calculates and returns the range of block IDs on the device that would map to +/// the file's block range. This is useful for translating file-level block addresses +/// to their locations on the physical storage device. +struct DeviceRangeReader<'a> { + inode: &'a InodeImpl_, + indirect_blocks: RwMutexWriteGuard<'a, IndirectBlockCache>, + range: Range, + indirect_block: Option, +} + +impl<'a> DeviceRangeReader<'a> { + /// Creates a new reader. + /// + /// # Panic + /// + /// If the 'range' is empty, this method will panic. + pub fn new(inode: &'a InodeImpl_, range: Range) -> Result { + assert!(!range.is_empty()); + + let mut reader = Self { + indirect_blocks: inode.indirect_blocks.write(), + inode, + range, + indirect_block: None, + }; + reader.update_indirect_block()?; + Ok(reader) + } + + /// Reads the corresponding device block IDs for a specified range. + /// + /// Note that the returned device range size may be smaller than the requested range + /// due to possible inconsecutive block allocation. + pub fn read(&mut self) -> Result> { + let bid_path = BidPath::from(self.range.start); + let max_cnt = self + .range + .len() + .min(bid_path.cnt_to_next_indirect() as usize); + let start_idx = bid_path.last_lvl_idx(); + + // Reads the device block ID range + let mut device_range: Option> = None; + for i in start_idx..start_idx + max_cnt { + let device_bid = match &self.indirect_block { + None => self.inode.desc.block_ptrs.direct(i), + Some(indirect_block) => indirect_block.read_bid(i)?, + }; + match device_range { + Some(ref mut range) => { + if device_bid == range.end { + range.end += 1; + } else { + break; + } + } + None => { + device_range = Some(device_bid..device_bid + 1); + } + } + } + let device_range = device_range.unwrap(); + + // Updates the range + self.range.start += device_range.len() as Ext2Bid; + if device_range.len() == max_cnt { + // Updates the indirect block + self.update_indirect_block()?; + } + + Ok(device_range) + } + + fn update_indirect_block(&mut self) -> Result<()> { + let bid_path = BidPath::from(self.range.start); + match bid_path { + BidPath::Direct(_) => { + self.indirect_block = None; + } + BidPath::Indirect(_) => { + let indirect_bid = self.inode.desc.block_ptrs.indirect(); + let indirect_block = self.indirect_blocks.find(indirect_bid)?; + self.indirect_block = Some(indirect_block.clone()); + } + BidPath::DbIndirect(lvl1_idx, _) => { + let lvl1_indirect_bid = { + let db_indirect_block = self + .indirect_blocks + .find(self.inode.desc.block_ptrs.db_indirect())?; + db_indirect_block.read_bid(lvl1_idx as usize)? + }; + let lvl1_indirect_block = self.indirect_blocks.find(lvl1_indirect_bid)?; + self.indirect_block = Some(lvl1_indirect_block.clone()) + } + BidPath::TbIndirect(lvl1_idx, lvl2_idx, _) => { + let lvl2_indirect_bid = { + let lvl1_indirect_bid = { + let tb_indirect_block = self + .indirect_blocks + .find(self.inode.desc.block_ptrs.tb_indirect())?; + tb_indirect_block.read_bid(lvl1_idx as usize)? + }; + let lvl1_indirect_block = self.indirect_blocks.find(lvl1_indirect_bid)?; + lvl1_indirect_block.read_bid(lvl2_idx as usize)? + }; + let lvl2_indirect_block = self.indirect_blocks.find(lvl2_indirect_bid)?; + self.indirect_block = Some(lvl2_indirect_block.clone()) + } + } + + Ok(()) + } +} + +impl<'a> Iterator for DeviceRangeReader<'a> { + type Item = Range; + + fn next(&mut self) -> Option { + if self.range.is_empty() { + return None; + } + + let range = self.read().unwrap(); + Some(range) + } +} + impl InodeImpl { - pub fn new(desc: Dirty, weak_self: Weak) -> Arc { - let inner = InodeImpl_::new(desc, weak_self); + pub fn new(desc: Dirty, weak_self: Weak, fs: Weak) -> Arc { + let inner = InodeImpl_::new(desc, weak_self, fs); Arc::new(Self(RwMutex::new(inner))) } @@ -988,7 +1497,7 @@ impl InodeImpl { inner.desc.hard_links -= 1; } - pub fn blocks_count(&self) -> u32 { + pub fn blocks_count(&self) -> Ext2Bid { self.0.read().desc.blocks_count() } @@ -1018,53 +1527,38 @@ impl InodeImpl { self.0.read().desc.ctime } - pub fn read_block_sync(&self, bid: Bid, block: &VmFrame) -> Result<()> { + pub fn read_block_sync(&self, bid: Ext2Bid, block: &VmFrame) -> Result<()> { self.0.read().read_block_sync(bid, block) } - pub fn read_block_async(&self, bid: Bid, block: &VmFrame) -> Result { + pub fn read_block_async(&self, bid: Ext2Bid, block: &VmFrame) -> Result { self.0.read().read_block_async(bid, block) } - pub fn write_block_sync(&self, bid: Bid, block: &VmFrame) -> Result<()> { - let waiter = self.write_block_async(bid, block)?; - match waiter.wait() { - Some(BioStatus::Complete) => Ok(()), - _ => return_errno!(Errno::EIO), - } + pub fn write_block_sync(&self, bid: Ext2Bid, block: &VmFrame) -> Result<()> { + self.0.read().write_block_sync(bid, block) } - pub fn write_block_async(&self, bid: Bid, block: &VmFrame) -> Result { - let inner = self.0.read(); - let waiter = inner.write_block_async(bid, block)?; - - let bid = bid.to_raw() as usize; - if inner.blocks_hole_desc.is_hole(bid) { - drop(inner); - let mut inner = self.0.write(); - if bid < inner.blocks_hole_desc.size() && inner.blocks_hole_desc.is_hole(bid) { - inner.blocks_hole_desc.unset(bid); - } - } - Ok(waiter) + pub fn write_block_async(&self, bid: Ext2Bid, block: &VmFrame) -> Result { + self.0.read().write_block_async(bid, block) } pub fn set_device_id(&self, device_id: u64) { - self.0.write().desc.data.as_bytes_mut()[..core::mem::size_of::()] + self.0.write().desc.block_ptrs.as_bytes_mut()[..core::mem::size_of::()] .copy_from_slice(device_id.as_bytes()); } pub fn device_id(&self) -> u64 { let mut device_id: u64 = 0; - device_id - .as_bytes_mut() - .copy_from_slice(&self.0.read().desc.data.as_bytes()[..core::mem::size_of::()]); + device_id.as_bytes_mut().copy_from_slice( + &self.0.read().desc.block_ptrs.as_bytes()[..core::mem::size_of::()], + ); device_id } pub fn write_link(&self, target: &str) -> Result<()> { let mut inner = self.0.write(); - inner.desc.data.as_bytes_mut()[..target.len()].copy_from_slice(target.as_bytes()); + inner.desc.block_ptrs.as_bytes_mut()[..target.len()].copy_from_slice(target.as_bytes()); if inner.desc.size != target.len() { inner.resize(target.len())?; } @@ -1074,17 +1568,17 @@ impl InodeImpl { pub fn read_link(&self) -> Result { let inner = self.0.read(); let mut symlink = vec![0u8; inner.desc.size]; - symlink.copy_from_slice(&inner.desc.data.as_bytes()[..inner.desc.size]); + symlink.copy_from_slice(&inner.desc.block_ptrs.as_bytes()[..inner.desc.size]); Ok(String::from_utf8(symlink)?) } pub fn sync_data_holes(&self) -> Result<()> { - let mut inner = self.0.write(); + let inner = self.0.read(); let zero_frame = VmAllocOptions::new(1).alloc_single().unwrap(); for bid in 0..inner.desc.blocks_count() { - if inner.blocks_hole_desc.is_hole(bid as usize) { - inner.write_block_sync(Bid::new(bid as _), &zero_frame)?; - inner.blocks_hole_desc.unset(bid as usize); + let is_data_hole = inner.blocks_hole_desc.read().is_hole(bid as usize); + if is_data_hole { + inner.write_block_sync(bid, &zero_frame)?; } } Ok(()) @@ -1112,6 +1606,7 @@ impl InodeImpl { } } + inner.indirect_blocks.write().evict_all()?; inode.fs().sync_inode(inode.ino(), &inner.desc)?; inner.desc.clear_dirty(); Ok(()) @@ -1120,12 +1615,12 @@ impl InodeImpl { impl PageCacheBackend for InodeImpl { fn read_page(&self, idx: usize, frame: &VmFrame) -> Result { - let bid = Bid::new(idx as _); + let bid = idx as Ext2Bid; self.read_block_async(bid, frame) } fn write_page(&self, idx: usize, frame: &VmFrame) -> Result { - let bid = Bid::new(idx as _); + let bid = idx as Ext2Bid; self.write_block_async(bid, frame) } @@ -1164,11 +1659,11 @@ pub(super) struct InodeDesc { /// Hard links count. hard_links: u16, /// Number of blocks. - blocks_count: u32, + blocks_count: Ext2Bid, /// File flags. flags: FileFlags, /// Pointers to blocks. - data: [u32; BLOCK_PTR_CNT], + block_ptrs: BlockPtrs, /// File or directory acl block. acl: Option, } @@ -1196,7 +1691,7 @@ impl TryFrom for InodeDesc { blocks_count: inode.blocks_count, flags: FileFlags::from_bits(inode.flags) .ok_or(Error::with_message(Errno::EINVAL, "invalid file flags"))?, - data: inode.data, + block_ptrs: inode.block_ptrs, acl: match file_type { FileType::File => Some(Bid::new(inode.file_acl as _)), FileType::Dir => Some(Bid::new(inode.size_high as _)), @@ -1221,7 +1716,7 @@ impl InodeDesc { hard_links: 1, blocks_count: 0, flags: FileFlags::empty(), - data: [0; BLOCK_PTR_CNT], + block_ptrs: BlockPtrs::default(), acl: match type_ { FileType::File | FileType::Dir => Some(Bid::new(0)), _ => None, @@ -1233,13 +1728,21 @@ impl InodeDesc { (self.blocks_count() as usize) * BLOCK_SIZE } - pub fn blocks_count(&self) -> u32 { - if self.type_ == FileType::Dir { - let real_blocks = (self.size / BLOCK_SIZE) as u32; - assert!(real_blocks <= self.blocks_count); - return real_blocks; + /// Returns the actual number of blocks utilized. + /// + /// Ext2 allows the `block_count` to exceed the actual number of blocks utilized. + pub fn blocks_count(&self) -> Ext2Bid { + let blocks = self.size_to_blocks(self.size); + assert!(blocks <= self.blocks_count); + blocks + } + + #[inline] + fn size_to_blocks(&self, size: usize) -> Ext2Bid { + if self.type_ == FileType::Symlink && size <= MAX_FAST_SYMLINK_LEN { + return 0; } - self.blocks_count + size.div_ceil(BLOCK_SIZE) as Ext2Bid } } @@ -1378,7 +1881,8 @@ pub(super) struct RawInode { pub flags: u32, /// OS dependent Value 1. reserved1: u32, - pub data: [u32; BLOCK_PTR_CNT], + /// Pointers to blocks. + pub block_ptrs: BlockPtrs, /// File version (for NFS). pub generation: u32, /// In revision 0, this field is reserved. @@ -1408,7 +1912,7 @@ impl From<&InodeDesc> for RawInode { hard_links: inode.hard_links, blocks_count: inode.blocks_count, flags: inode.flags.bits(), - data: inode.data, + block_ptrs: inode.block_ptrs, file_acl: match inode.acl { Some(acl) if inode.type_ == FileType::File => acl.to_raw() as u32, _ => Default::default(), diff --git a/kernel/aster-nix/src/fs/ext2/mod.rs b/kernel/aster-nix/src/fs/ext2/mod.rs index b1c107c249..6c4b00efbd 100644 --- a/kernel/aster-nix/src/fs/ext2/mod.rs +++ b/kernel/aster-nix/src/fs/ext2/mod.rs @@ -33,19 +33,20 @@ //! # Limitation //! //! Here we summarizes the features that need to be implemented in the future. -//! 1. Supports large file. -//! 2. Supports merging small read/write operations. -//! 3. Handles the intermediate failure status correctly. +//! 1. Supports merging small read/write operations. +//! 2. Handles the intermediate failure status correctly. pub use fs::Ext2; pub use inode::{FilePerm, FileType, Inode}; pub use super_block::{SuperBlock, MAGIC_NUM}; mod block_group; +mod block_ptr; mod blocks_hole; mod dir; mod fs; mod impl_for_vfs; +mod indirect_block_cache; mod inode; mod prelude; mod super_block; diff --git a/kernel/aster-nix/src/fs/ext2/prelude.rs b/kernel/aster-nix/src/fs/ext2/prelude.rs index ff29b99997..fe5e50975c 100644 --- a/kernel/aster-nix/src/fs/ext2/prelude.rs +++ b/kernel/aster-nix/src/fs/ext2/prelude.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MPL-2.0 pub(super) use core::{ - ops::{Deref, DerefMut}, + ops::{Deref, DerefMut, Range}, time::Duration, }; @@ -12,7 +12,7 @@ pub(super) use aster_block::{ BlockDevice, BLOCK_SIZE, }; pub(super) use aster_frame::{ - sync::{RwMutex, RwMutexReadGuard}, + sync::{RwMutex, RwMutexReadGuard, RwMutexWriteGuard}, vm::{VmAllocOptions, VmFrame, VmIo, VmSegment}, }; pub(super) use aster_rights::Full; diff --git a/kernel/aster-nix/src/fs/ext2/super_block.rs b/kernel/aster-nix/src/fs/ext2/super_block.rs index 38b8a2441f..cbdfe145ae 100644 --- a/kernel/aster-nix/src/fs/ext2/super_block.rs +++ b/kernel/aster-nix/src/fs/ext2/super_block.rs @@ -243,23 +243,22 @@ impl SuperBlock { } /// Returns the number of free blocks. - pub fn free_blocks(&self) -> u32 { + pub fn free_blocks_count(&self) -> u32 { self.free_blocks_count } /// Increase the number of free blocks. - pub(super) fn inc_free_blocks(&mut self) { - self.free_blocks_count += 1; + pub(super) fn inc_free_blocks(&mut self, count: u32) { + self.free_blocks_count = self.free_blocks_count.checked_add(count).unwrap(); } /// Decrease the number of free blocks. - pub(super) fn dec_free_blocks(&mut self) { - debug_assert!(self.free_blocks_count > 0); - self.free_blocks_count -= 1; + pub(super) fn dec_free_blocks(&mut self, count: u32) { + self.free_blocks_count = self.free_blocks_count.checked_sub(count).unwrap(); } /// Returns the number of free inodes. - pub fn free_inodes(&self) -> u32 { + pub fn free_inodes_count(&self) -> u32 { self.free_inodes_count } diff --git a/kernel/libs/aster-util/src/id_allocator.rs b/kernel/libs/aster-util/src/id_allocator.rs index b02cabce92..71033fd1d9 100644 --- a/kernel/libs/aster-util/src/id_allocator.rs +++ b/kernel/libs/aster-util/src/id_allocator.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MPL-2.0 -use core::fmt::Debug; +use core::{fmt::Debug, ops::Range}; use bitvec::prelude::BitVec; @@ -63,6 +63,74 @@ impl IdAlloc { } } + /// Allocates a consecutive range of new `id`s. + /// + /// The `count` is the number of consecutive `id`s to allocate. If it is 0, return `None`. + /// + /// If allocation is not possible, it returns `None`. + /// + /// TODO: Choose a more efficient strategy. + pub fn alloc_consecutive(&mut self, count: usize) -> Option> { + if count == 0 { + return None; + } + + // Scan the bitmap from the position `first_available_id` + // for the first `count` number of consecutive 0's. + let allocated_range = { + // Invariance: all bits within `curr_range` are 0's + let mut curr_range = self.first_available_id..self.first_available_id + 1; + while curr_range.len() < count && curr_range.end < self.bitset.len() { + if !self.is_allocated(curr_range.end) { + curr_range.end += 1; + } else { + curr_range = curr_range.end + 1..curr_range.end + 1; + } + } + + if curr_range.len() < count { + return None; + } + + curr_range + }; + + // Set every bit to 1 within the allocated range + for id in allocated_range.clone() { + self.bitset.set(id, true); + } + + // In case we need to update first_available_id + if self.is_allocated(self.first_available_id) { + self.first_available_id = (allocated_range.end..self.bitset.len()) + .find(|&i| !self.bitset[i]) + .map_or(self.bitset.len(), |i| i); + } + + Some(allocated_range) + } + + /// Releases the consecutive range of allocated `id`s. + /// + /// # Panic + /// + /// If the `range` is out of bounds, this method will panic. + pub fn free_consecutive(&mut self, range: Range) { + if range.is_empty() { + return; + } + + let range_start = range.start; + for id in range { + debug_assert!(self.is_allocated(id)); + self.bitset.set(id, false); + } + + if range_start < self.first_available_id { + self.first_available_id = range_start + } + } + /// Releases the allocated `id`. /// /// # Panic diff --git a/regression/apps/scripts/ext2.sh b/regression/apps/scripts/ext2.sh new file mode 100755 index 0000000000..2495568409 --- /dev/null +++ b/regression/apps/scripts/ext2.sh @@ -0,0 +1,38 @@ +#!/bin/sh + +# SPDX-License-Identifier: MPL-2.0 + +set -e + +check_file_size() { + local file_name="$1" + local expected_size="$2" + + if [ ! -f "$file_name" ]; then + echo "Error: File does not exist." + return 1 + fi + + actual_size=$(du -b "$file_name" | cut -f1) + + if [ "$actual_size" -eq "$expected_size" ]; then + return 0 + else + echo "Error: File size is incorrect: expected ${expected_size}, but got ${actual_size}." + return 1 + fi +} + +EXT2_DIR=/ext2 +cd ${EXT2_DIR} + +echo "Start ext2 fs test......" + +# Test case for the big file feature +truncate -s 500M test_file.txt +check_file_size test_file.txt $((500 * 1024 * 1024)) +truncate -s 2K test_file.txt +check_file_size test_file.txt $((2 * 1024)) +sync + +echo "All ext2 fs test passed." \ No newline at end of file diff --git a/regression/apps/scripts/run_regression_test.sh b/regression/apps/scripts/run_regression_test.sh index 59d515bbd6..1382119b77 100755 --- a/regression/apps/scripts/run_regression_test.sh +++ b/regression/apps/scripts/run_regression_test.sh @@ -8,6 +8,7 @@ SCRIPT_DIR=/regression cd ${SCRIPT_DIR} ./shell_cmd.sh +./ext2.sh ./process.sh ./network.sh