mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2024-11-28 17:44:10 +00:00
ext2: Support indirect/double-indirect blocks
To allow files larger than (4*12)KiB, support indirect blocks and double-indirect blocks so it now supports ~4GB files. BUG=b:342937441 TEST=cargo test in /ext2 Change-Id: I0b8fd421e85d0a5e203876aa260077448f740737 Reviewed-on: https://chromium-review.googlesource.com/c/crosvm/crosvm/+/5569436 Reviewed-by: Junichi Uekawa <uekawa@chromium.org> Commit-Queue: Keiichi Watanabe <keiichiw@chromium.org> Reviewed-by: Takaya Saeki <takayas@chromium.org>
This commit is contained in:
parent
c9f4ad0da9
commit
80dc747e0d
3 changed files with 167 additions and 23 deletions
117
ext2/src/fs.rs
117
ext2/src/fs.rs
|
@ -133,8 +133,10 @@ struct FileMappingInfo {
|
|||
start_block: BlockId,
|
||||
/// The file to be mmap'd.
|
||||
file: File,
|
||||
/// The size of the file to be mmap'd.
|
||||
file_size: usize,
|
||||
/// The length of the mapping.
|
||||
length: usize,
|
||||
/// Offset in the file to start the mapping.
|
||||
file_offset: usize,
|
||||
}
|
||||
|
||||
/// A struct to represent an ext2 filesystem.
|
||||
|
@ -273,7 +275,7 @@ impl<'a> Ext2<'a> {
|
|||
if !self.dentries.contains_key(&parent) {
|
||||
let block_id = self.allocate_block()?;
|
||||
let inode = self.get_inode_mut(parent)?;
|
||||
inode.block.set_block_id(0, &block_id);
|
||||
inode.block.set_direct_blocks(&[block_id])?;
|
||||
inode.blocks = block_size as u32 / 512;
|
||||
self.dentries.insert(
|
||||
parent,
|
||||
|
@ -410,6 +412,38 @@ impl<'a> Ext2<'a> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn fill_indirect_block(
|
||||
&mut self,
|
||||
arena: &'a Arena<'a>,
|
||||
indirect_table: BlockId,
|
||||
file: &File,
|
||||
file_size: usize,
|
||||
file_offset: usize,
|
||||
) -> Result<usize> {
|
||||
let block_size = self.block_size() as usize;
|
||||
// We use a block as a table of indirect blocks.
|
||||
// So, the maximum number of blocks supported by single indirect blocks is limited by the
|
||||
// maximum number of entries in one block, which is (block_size / 4) where 4 is the size of
|
||||
// int.
|
||||
let max_num_blocks = block_size / 4;
|
||||
let max_data_len = max_num_blocks * block_size;
|
||||
|
||||
let length = std::cmp::min(file_size - file_offset, max_data_len);
|
||||
let block_num = length.div_ceil(block_size);
|
||||
let blocks = self.allocate_contiguous_blocks(block_num as u16)?;
|
||||
|
||||
let slice = arena.allocate_slice(indirect_table, 0, 4 * block_num)?;
|
||||
slice.copy_from_slice(blocks.as_bytes());
|
||||
|
||||
self.fd_mappings.push(FileMappingInfo {
|
||||
start_block: blocks[0],
|
||||
length,
|
||||
file: file.try_clone()?,
|
||||
file_offset,
|
||||
});
|
||||
Ok(length)
|
||||
}
|
||||
|
||||
fn add_file(
|
||||
&mut self,
|
||||
arena: &'a Arena<'a>,
|
||||
|
@ -426,26 +460,74 @@ impl<'a> Ext2<'a> {
|
|||
let block_size = self.block_size() as usize;
|
||||
let mut block = InodeBlock::default();
|
||||
|
||||
let block_num = file_size.div_ceil(block_size);
|
||||
if block_num > 12 {
|
||||
// TODO(b/342937441): Support indirect blocks.
|
||||
bail!("indirect data block are not yet supported");
|
||||
}
|
||||
|
||||
if block_num > 0 {
|
||||
let mut written = 0;
|
||||
let mut used_blocks = 0;
|
||||
if file_size > 0 {
|
||||
let block_num = std::cmp::min(
|
||||
file_size.div_ceil(block_size),
|
||||
InodeBlock::NUM_DIRECT_BLOCKS,
|
||||
);
|
||||
let blocks = self.allocate_contiguous_blocks(block_num as u16)?;
|
||||
let length = std::cmp::min(file_size, block_size * InodeBlock::NUM_DIRECT_BLOCKS);
|
||||
self.fd_mappings.push(FileMappingInfo {
|
||||
start_block: blocks[0],
|
||||
file_size,
|
||||
file,
|
||||
length,
|
||||
file: file.try_clone()?,
|
||||
file_offset: 0,
|
||||
});
|
||||
block.copy_from_slice(0, blocks.as_bytes());
|
||||
block.set_direct_blocks(&blocks)?;
|
||||
written = length;
|
||||
used_blocks = block_num;
|
||||
}
|
||||
|
||||
// Indirect data block
|
||||
if written < file_size {
|
||||
let indirect_table = self.allocate_block()?;
|
||||
block.set_indirect_block_table(&indirect_table)?;
|
||||
used_blocks += 1;
|
||||
|
||||
let length =
|
||||
self.fill_indirect_block(arena, indirect_table, &file, file_size, written)?;
|
||||
written += length;
|
||||
used_blocks += length.div_ceil(block_size);
|
||||
}
|
||||
|
||||
// Double-indirect data block
|
||||
// Supporting double-indirect data block allows storing ~4GB files if 4GB block size is
|
||||
// used.
|
||||
if written < file_size {
|
||||
let d_indirect_table = self.allocate_block()?;
|
||||
block.set_double_indirect_block_table(&d_indirect_table)?;
|
||||
used_blocks += 1;
|
||||
|
||||
let mut indirect_blocks: Vec<BlockId> = vec![];
|
||||
// Iterate (block_size / 4) times, as each block id is 4-byte.
|
||||
for _ in 0..block_size / 4 {
|
||||
if written >= file_size {
|
||||
break;
|
||||
}
|
||||
let indirect_table = self.allocate_block()?;
|
||||
indirect_blocks.push(indirect_table);
|
||||
used_blocks += 1;
|
||||
|
||||
let length =
|
||||
self.fill_indirect_block(arena, indirect_table, &file, file_size, written)?;
|
||||
written += length;
|
||||
used_blocks += length.div_ceil(block_size);
|
||||
}
|
||||
|
||||
let d_table = arena.allocate_slice(d_indirect_table, 0, indirect_blocks.len() * 4)?;
|
||||
d_table.copy_from_slice(indirect_blocks.as_bytes());
|
||||
}
|
||||
|
||||
if written < file_size {
|
||||
unimplemented!("Triple-indirect block is not supported");
|
||||
}
|
||||
|
||||
// The spec says that the `blocks` field is a "32-bit value representing the total number
|
||||
// of 512-bytes blocks". This `512` is a fixed number regardless of the actual block size,
|
||||
// which is usuaully 4KB.
|
||||
let blocks = block_num as u32 * (block_size as u32 / 512);
|
||||
let blocks = used_blocks as u32 * (block_size as u32 / 512);
|
||||
let size = file_size as u32;
|
||||
let inode = Inode::from_metadata(
|
||||
arena,
|
||||
|
@ -533,15 +615,16 @@ pub fn create_ext2_region(cfg: &Config, src_dir: Option<&Path>) -> Result<Memory
|
|||
let mut mmap_arena = MemoryMappingArena::from(mem);
|
||||
for FileMappingInfo {
|
||||
start_block,
|
||||
file_size,
|
||||
file,
|
||||
length,
|
||||
file_offset,
|
||||
} in file_mappings
|
||||
{
|
||||
mmap_arena.add_fd_mapping(
|
||||
u32::from(start_block) as usize * BLOCK_SIZE,
|
||||
file_size,
|
||||
length,
|
||||
&file,
|
||||
0, /* fd_offset */
|
||||
file_offset as u64, /* fd_offset */
|
||||
Protection::read(),
|
||||
)?;
|
||||
}
|
||||
|
|
|
@ -93,14 +93,45 @@ impl Default for InodeBlock {
|
|||
}
|
||||
|
||||
impl InodeBlock {
|
||||
/// Copies the given slice to `self.0.[offset..]`.
|
||||
pub fn copy_from_slice(&mut self, offset: usize, src: &[u8]) {
|
||||
self.0[offset..offset + src.len()].copy_from_slice(src)
|
||||
}
|
||||
// Each inode contains 12 direct pointers (0-11), one singly indirect pointer (12), one
|
||||
// doubly indirect block pointer (13), and one triply indirect pointer (14).
|
||||
pub const NUM_DIRECT_BLOCKS: usize = 12;
|
||||
const INDIRECT_BLOCK_TABLE_ID: usize = Self::NUM_DIRECT_BLOCKS;
|
||||
const DOUBLE_INDIRECT_BLOCK_TABLE_ID: usize = 13;
|
||||
|
||||
/// Set a block id at the given index.
|
||||
pub fn set_block_id(&mut self, index: usize, block_id: &BlockId) {
|
||||
self.copy_from_slice(index * 4, block_id.as_bytes())
|
||||
fn set_block_id(&mut self, index: usize, block_id: &BlockId) -> Result<()> {
|
||||
let offset = index * std::mem::size_of::<BlockId>();
|
||||
let bytes = block_id.as_bytes();
|
||||
if self.0.len() < offset + bytes.len() {
|
||||
bail!("index out of bounds when setting block_id to InodeBlock: index={index}, block_id: {:?}", block_id);
|
||||
}
|
||||
self.0[offset..offset + bytes.len()].copy_from_slice(bytes);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set an array of direct block IDs.
|
||||
pub fn set_direct_blocks(&mut self, block_ids: &[BlockId]) -> Result<()> {
|
||||
let bytes = block_ids.as_bytes();
|
||||
if bytes.len() > self.0.len() {
|
||||
bail!(
|
||||
"length of direct blocks is {} bytes, but it must not exceed {}",
|
||||
bytes.len(),
|
||||
self.0.len()
|
||||
);
|
||||
}
|
||||
self.0[..bytes.len()].copy_from_slice(bytes);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set a block id to be used as the indirect block table.
|
||||
pub fn set_indirect_block_table(&mut self, block_id: &BlockId) -> Result<()> {
|
||||
self.set_block_id(Self::INDIRECT_BLOCK_TABLE_ID, block_id)
|
||||
}
|
||||
|
||||
/// Set a block id to be used as the double indirect block table.
|
||||
pub fn set_double_indirect_block_table(&mut self, block_id: &BlockId) -> Result<()> {
|
||||
self.set_block_id(Self::DOUBLE_INDIRECT_BLOCK_TABLE_ID, block_id)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,8 @@ use std::fs::create_dir;
|
|||
use std::fs::File;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::BufWriter;
|
||||
use std::io::Seek;
|
||||
use std::io::SeekFrom;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
@ -289,3 +291,31 @@ fn test_max_file_name() {
|
|||
|
||||
assert_eq_dirs(&td, &dir, &disk);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mkfs_indirect_block() {
|
||||
// testdata
|
||||
// ├── big.txt (80KiB), which requires indirect blocks
|
||||
// └── huge.txt (8MiB), which requires doubly indirect blocks
|
||||
let td = tempdir().unwrap();
|
||||
let dir = td.path().join("testdata");
|
||||
std::fs::create_dir(&dir).unwrap();
|
||||
let mut big = std::fs::File::create(dir.join("big.txt")).unwrap();
|
||||
big.seek(SeekFrom::Start(80 * 1024)).unwrap();
|
||||
big.write_all(&[0]).unwrap();
|
||||
|
||||
let mut huge = std::fs::File::create(dir.join("huge.txt")).unwrap();
|
||||
huge.seek(SeekFrom::Start(8 * 1024 * 1024)).unwrap();
|
||||
huge.write_all(&[0]).unwrap();
|
||||
|
||||
let disk = mkfs(
|
||||
&td,
|
||||
&Config {
|
||||
blocks_per_group: 4096,
|
||||
inodes_per_group: 4096,
|
||||
},
|
||||
Some(&dir),
|
||||
);
|
||||
|
||||
assert_eq_dirs(&td, &dir, &disk);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue