diff --git a/bootstrap/src/initfs.rs b/bootstrap/src/initfs.rs index 209e771f37..c33918da06 100644 --- a/bootstrap/src/initfs.rs +++ b/bootstrap/src/initfs.rs @@ -135,7 +135,7 @@ impl SchemeSync for InitFsScheme { // filter out double slashes (e.g. /usr//bin/...) .filter(|c| !c.is_empty()); - let mut current_inode = InitFs::ROOT_INODE; + let mut current_inode = self.fs.root_inode(); while let Some(component) = components.next() { match component { diff --git a/initfs/src/lib.rs b/initfs/src/lib.rs index 16e3bc3d23..db7ed8b008 100644 --- a/initfs/src/lib.rs +++ b/initfs/src/lib.rs @@ -268,8 +268,9 @@ impl<'initfs> InitFs<'initfs> { plain::slice_from_bytes::(inode_table_bytes) .expect("expected inode struct alignment to be 1") } - pub const ROOT_INODE: Inode = Inode(0); - + pub fn root_inode(&self) -> Inode { + Inode(self.get_header_assume_valid().root_inode.get()) + } pub fn all_inodes(&self) -> impl Iterator { (0..self.inode_count()).map(Inode) } diff --git a/initfs/src/types.rs b/initfs/src/types.rs index 8ef4a87833..8dce929151 100644 --- a/initfs/src/types.rs +++ b/initfs/src/types.rs @@ -70,6 +70,7 @@ pub struct Header { pub bootstrap_entry: U64, pub initfs_size: U64, pub page_size: U16, + pub root_inode: U16, } const _: () = { diff --git a/initfs/tools/data/foo/bootstrap.elf b/initfs/tools/data/foo/bootstrap.elf new file mode 100644 index 0000000000..0d77a7513a Binary files /dev/null and b/initfs/tools/data/foo/bootstrap.elf differ diff --git a/initfs/tools/src/bin/archive.rs b/initfs/tools/src/bin/archive.rs index 128e2ab6a9..11b54313b5 100644 --- a/initfs/tools/src/bin/archive.rs +++ b/initfs/tools/src/bin/archive.rs @@ -1,6 +1,6 @@ use std::path::Path; -use anyhow::{Context, Result}; +use anyhow::Result; use clap::{Arg, Command}; use redox_initfs_tools::{self as archive, Args, DEFAULT_MAX_SIZE}; @@ -10,13 +10,6 @@ fn main() -> Result<()> { .about("create an initfs image from a directory") .version(clap::crate_version!()) .author(clap::crate_authors!()) - .arg( - Arg::new("MAX_SIZE") - .long("max-size") - .short('m') - .required(false) - .help("Set the upper limit for how large the image can become (default 64 MiB)."), - ) // TODO: support non-utf8 paths (applies to other paths as well) .arg( Arg::new("SOURCE") @@ -25,7 +18,7 @@ fn main() -> Result<()> { ) .arg( Arg::new("BOOTSTRAP_CODE") - .required(false) + .required(true) .help("Specify the bootstrap ELF file to include in the image."), ) .arg( @@ -39,19 +32,13 @@ fn main() -> Result<()> { env_logger::init(); - let max_size = if let Some(max_size_str) = matches.get_one::("MAX_SIZE") { - max_size_str - .parse::() - .context("expected an integer for MAX_SIZE")? - } else { - DEFAULT_MAX_SIZE - }; - let source = matches .get_one::("SOURCE") .expect("expected the required arg SOURCE to exist"); - let bootstrap_code = matches.get_one::("BOOTSTRAP_CODE"); + let bootstrap_code = matches + .get_one::("BOOTSTRAP_CODE") + .expect("expected the required arg BOOTSTRAP_CODE to exist"); let destination = matches .get_one::("OUTPUT") @@ -59,9 +46,9 @@ fn main() -> Result<()> { let args = Args { source: Path::new(source), - bootstrap_code: bootstrap_code.map(Path::new), + bootstrap_code: Path::new(bootstrap_code), destination_path: Path::new(destination), - max_size, + max_size: DEFAULT_MAX_SIZE, }; archive::archive(&args) } diff --git a/initfs/tools/src/lib.rs b/initfs/tools/src/lib.rs index 00d9791168..38bc941e96 100644 --- a/initfs/tools/src/lib.rs +++ b/initfs/tools/src/lib.rs @@ -8,7 +8,7 @@ use std::os::unix::fs::{FileExt, FileTypeExt, PermissionsExt}; use anyhow::{anyhow, bail, Context, Result}; -use redox_initfs::types as initfs; +use redox_initfs::types::{self as initfs, Offset}; pub const KIBIBYTE: u64 = 1024; pub const MEBIBYTE: u64 = KIBIBYTE * 1024; @@ -41,9 +41,8 @@ struct State<'path> { file: OutputImageGuard<'path>, offset: u64, max_size: u64, - inode_count: u16, buffer: Box<[u8]>, - inode_table_offset: u32, + inode_table: InodeTable, } fn write_all_at(file: &File, buf: &[u8], offset: u64, r#where: &str) -> Result<()> { @@ -57,7 +56,7 @@ fn write_all_at(file: &File, buf: &[u8], offset: u64, r#where: &str) -> Result<( Ok(()) } -fn read_directory(state: &mut State, path: &Path, root_path: &Path) -> Result { +fn read_directory(path: &Path, root_path: &Path) -> Result { let read_dir = path .read_dir() .with_context(|| anyhow!("failed to read directory `{}`", path.to_string_lossy(),))?; @@ -106,7 +105,7 @@ fn read_directory(state: &mut State, path: &Path, root_path: &Path) -> Result Result Result Result<()> { - let inode_size: u32 = std::mem::size_of::() - .try_into() - .expect("inode header length cannot fit within u32"); - - // TODO: Use main buffer and write in bulk. - let mut inode_buf = [0_u8; std::mem::size_of::()]; - - let inode_hdr = plain::from_mut_bytes::(&mut inode_buf) - .expect("expected inode struct to have alignment 1, and buffer size to match"); - - *inode_hdr = initfs::InodeHeader { - type_: (ty as u32).into(), - length: initfs::Length(write_result.size.into()), - offset: initfs::Offset(write_result.offset.into()), - }; - - log::debug!( - "Writing inode index {} from offset {}", - inode, - state.inode_table_offset - ); - write_all_at( - &state.file, - &inode_buf, - u64::from(state.inode_table_offset + u32::from(inode) * inode_size), - "write_inode", - ) - .context("failed to write inode struct to disk image") -} -fn allocate_and_write_dir( - state: &mut State, - dir: &Dir, - current_inode: &mut u16, -) -> Result { +fn allocate_and_write_dir(state: &mut State, dir: &Dir) -> Result { let entry_size = u16::try_from(std::mem::size_of::()).context("entry size too large")?; let entry_count = u16::try_from(dir.entries.len()).context("too many subdirectories")?; @@ -292,13 +246,12 @@ fn allocate_and_write_dir( for (index, entry) in dir.entries.iter().enumerate() { let (write_result, ty) = match entry.kind { EntryKind::Dir(ref subdir) => { - let write_result = allocate_and_write_dir(state, subdir, current_inode) - .with_context(|| { - anyhow!( - "failed to copy directory entries from `{}` into image", - String::from_utf8_lossy(&entry.name) - ) - })?; + let write_result = allocate_and_write_dir(state, subdir).with_context(|| { + anyhow!( + "failed to copy directory entries from `{}` into image", + String::from_utf8_lossy(&entry.name) + ) + })?; (write_result, initfs::InodeType::Dir) } @@ -327,8 +280,7 @@ fn allocate_and_write_dir( .try_into() .expect("expected dir entry count not to exceed u32"); - *current_inode += 1; - write_inode(state, ty, write_result, *current_inode)?; + let inode = state.inode_table.allocate(ty, write_result); let (name_offset, name_len) = { let name_len: u16 = entry.name.len().try_into().context("file name too long")?; @@ -351,13 +303,13 @@ fn allocate_and_write_dir( log::debug!( "Linking inode {} into dir entry index {}, file name `{}`", - current_inode, + inode, index, String::from_utf8_lossy(&entry.name) ); *direntry = initfs::DirEntry { - inode: (*current_inode).into(), + inode: inode.into(), name_len: name_len.into(), name_offset: initfs::Offset(name_offset.into()), }; @@ -377,14 +329,87 @@ fn allocate_and_write_dir( offset: entry_table_offset, }) } -fn allocate_contents_and_write_inodes(state: &mut State, dir: &Dir) -> Result<()> { - let start_inode = 0; - let mut current_inode = start_inode; - - let write_result = allocate_and_write_dir(state, dir, &mut current_inode) +fn allocate_contents(state: &mut State, dir: &Dir) -> Result { + let write_result = allocate_and_write_dir(state, dir) .context("failed to allocate and write all directories and files")?; - write_inode(state, initfs::InodeType::Dir, write_result, start_inode) + let root_inode = state + .inode_table + .allocate(initfs::InodeType::Dir, write_result); + + Ok(root_inode.into()) +} + +struct InodeTable { + entries: Vec, +} + +impl InodeTable { + fn new() -> Self { + Self { entries: vec![] } + } + + fn count(&self) -> u16 { + self.entries + .len() + .try_into() + .expect("inode count too large") + } + + fn allocate(&mut self, ty: initfs::InodeType, write_result: WriteResult) -> u16 { + let inode = self.entries.len(); + self.entries.push(initfs::InodeHeader { + type_: (ty as u32).into(), + length: initfs::Length(write_result.size.into()), + offset: initfs::Offset(write_result.offset.into()), + }); + inode.try_into().expect("inode count too large") + } +} + +fn write_inode_table(state: &mut State) -> Result { + log::debug!("there are {} inodes", state.inode_table.count()); + + let inode_size: u32 = std::mem::size_of::() + .try_into() + .expect("inode header length cannot fit within u32"); + + let inode_table_length = { + u64::from(inode_size) + .checked_mul(u64::from(state.inode_table.count())) + .ok_or_else(|| anyhow!("inode table too large"))? + }; + + let inode_table_offset = bump_alloc(state, inode_table_length, "allocate inode table")?; + let inode_table_offset = + u32::try_from(inode_table_offset).with_context(|| "inode table located too far away")?; + + for (i, inode) in state.inode_table.entries.iter().enumerate() { + // TODO: Use main buffer and write in bulk. + let mut inode_buf = [0_u8; std::mem::size_of::()]; + + let inode_hdr = plain::from_mut_bytes::(&mut inode_buf) + .expect("expected inode struct to have alignment 1, and buffer size to match"); + + *inode_hdr = *inode; + + log::debug!( + "Writing inode index {} from offset {}", + i, + inode_table_offset + ); + write_all_at( + &state.file, + &inode_buf, + u64::from(inode_table_offset + u32::try_from(i).unwrap() * inode_size), + "write_inode", + ) + .context("failed to write inode struct to disk image")?; + } + + let inode_table_offset = initfs::Offset(inode_table_offset.into()); + + Ok(inode_table_offset) } struct OutputImageGuard<'a> { @@ -418,7 +443,7 @@ pub struct Args<'a> { pub destination_path: &'a Path, pub max_size: u64, pub source: &'a Path, - pub bootstrap_code: Option<&'a Path>, + pub bootstrap_code: &'a Path, } pub fn archive( &Args { @@ -428,6 +453,9 @@ pub fn archive( bootstrap_code, }: &Args, ) -> Result<()> { + let root_path = source; + let root = read_directory(root_path, root_path).context("failed to read root")?; + let previous_extension = destination_path.extension().map_or("", |ext| { ext.to_str() .expect("expected destination path to be valid UTF-8") @@ -464,65 +492,34 @@ pub fn archive( file: guard, offset: 0, max_size, - // Include root directory. - inode_count: 1, buffer: vec![0_u8; BUFFER_SIZE].into_boxed_slice(), - inode_table_offset: 0, + inode_table: InodeTable::new(), }; - let root_path = source; - let root = read_directory(&mut state, root_path, root_path).context("failed to read root")?; - - log::debug!("there are {} inodes", state.inode_count); - // NOTE: The header is always stored at offset zero. let header_offset = bump_alloc(&mut state, 4096, "allocate header")?; assert_eq!(header_offset, 0); - let bootstrap_entry = if let Some(bootstrap_code) = bootstrap_code { - allocate_and_write_file( - &mut state, - &File::open(bootstrap_code).with_context(|| { - anyhow!( - "failed to open bootstrap code file `{}`", - bootstrap_code.to_string_lossy(), - ) - })?, - )?; - let bootstrap_data = std::fs::read(bootstrap_code).with_context(|| { + allocate_and_write_file( + &mut state, + &File::open(bootstrap_code).with_context(|| { anyhow!( - "failed to read bootstrap code file `{}`", + "failed to open bootstrap code file `{}`", bootstrap_code.to_string_lossy(), ) - })?; - elf_entry(&bootstrap_data) - } else { - u64::MAX - }; + })?, + )?; + let bootstrap_data = std::fs::read(bootstrap_code).with_context(|| { + anyhow!( + "failed to read bootstrap code file `{}`", + bootstrap_code.to_string_lossy(), + ) + })?; + let bootstrap_entry = elf_entry(&bootstrap_data); - let inode_table_length = { - let inode_entry_size: u64 = std::mem::size_of::() - .try_into() - .expect("expected table entry size to fit"); + let root_inode = allocate_contents(&mut state, &root)?; - inode_entry_size - .checked_mul(u64::from(state.inode_count)) - .ok_or_else(|| anyhow!("inode table too large"))? - }; - - let inode_table_offset = bump_alloc(&mut state, inode_table_length, "allocate inode table")?; - - // Finally, write the header to the disk image. - - let inode_table_offset = initfs::Offset( - u32::try_from(inode_table_offset) - .with_context(|| "inode table located too far away")? - .into(), - ); - - state.inode_table_offset = inode_table_offset.0.get(); - - allocate_contents_and_write_inodes(&mut state, &root)?; + let inode_table_offset = write_inode_table(&mut state)?; let current_system_time = std::time::SystemTime::now(); @@ -541,7 +538,7 @@ pub fn archive( sec: time_since_epoch.as_secs().into(), nsec: time_since_epoch.subsec_nanos().into(), }, - inode_count: state.inode_count.into(), + inode_count: state.inode_table.count().into(), inode_table_offset, bootstrap_entry: bootstrap_entry.into(), initfs_size: state @@ -551,6 +548,7 @@ pub fn archive( .len() .into(), page_size: PAGE_SIZE.into(), + root_inode, }; write_all_at(&state.file, &header_bytes, header_offset, "writing header") .context("failed to write header")?; diff --git a/initfs/tools/tests/archive_and_read.rs b/initfs/tools/tests/archive_and_read.rs index 42ec0079b9..f32ea0ad3d 100644 --- a/initfs/tools/tests/archive_and_read.rs +++ b/initfs/tools/tests/archive_and_read.rs @@ -77,7 +77,7 @@ fn archive_and_read() -> Result<()> { let args = redox_initfs_tools::Args { destination_path: &Path::new(env!("CARGO_TARGET_TMPDIR")).join("out.img"), source: Path::new("data"), - bootstrap_code: None, + bootstrap_code: Path::new("data/foo/bootstrap.elf"), max_size: redox_initfs_tools::DEFAULT_MAX_SIZE, }; redox_initfs_tools::archive(&args).context("failed to archive")?; @@ -86,7 +86,7 @@ fn archive_and_read() -> Result<()> { let filesystem = redox_initfs::InitFs::new(&data, None).context("failed to parse archive header")?; let inode = filesystem - .get_inode(redox_initfs::InitFs::ROOT_INODE) + .get_inode(filesystem.root_inode()) .ok_or_else(|| anyhow!("Failed to get root inode"))?; let tree = build_tree(filesystem, inode)?; @@ -94,11 +94,15 @@ fn archive_and_read() -> Result<()> { let reference_tree = Node::dir([( b"foo", Node::dir([ + ( + b"bootstrap.elf".as_slice(), + Node::file("\x7FELF\x01\x01\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"), + ), + (b"file-link.txt".as_slice(), Node::link(b"file.txt")), ( b"file.txt".as_slice(), Node::file(b"This is a file meant to be used in a redox-initfs test.\n"), ), - (b"file-link.txt".as_slice(), Node::link(b"foo/file.txt")), ]), )]);