diff --git a/src/lib.rs b/src/lib.rs index ecc1cc9980..a250ebb6d7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ #![no_std] +#![feature(offset_of)] //! A super simple initfs, only meant to be loaded into RAM by the bootloader, and then directly be //! read. diff --git a/src/types.rs b/src/types.rs index 24508fcfbc..6faf321db5 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,3 +1,5 @@ +use core::mem::offset_of; + pub const MAGIC_LEN: usize = 8; pub const MAGIC: [u8; 8] = *b"RedoxFtw"; @@ -69,8 +71,15 @@ pub struct Header { pub inode_table_offset: Offset, pub creation_time: Timespec, pub inode_count: U16, + pub bootstrap_entry: U64, + pub initfs_size: U64, } +const _: () = { + // Ensure the offsets of field used by the bootloader stay stable. + assert!(offset_of!(Header, bootstrap_entry) == 0x1a); +}; + #[repr(packed)] #[derive(Clone, Copy, Debug)] pub struct InodeHeader { diff --git a/tests/archive_and_read.rs b/tests/archive_and_read.rs index d26df6c71a..ac4f87caaf 100644 --- a/tests/archive_and_read.rs +++ b/tests/archive_and_read.rs @@ -14,6 +14,7 @@ fn archive_and_read() -> Result<()> { let args = self::archive::Args { destination_path: Path::new("out.img"), source: Path::new("data"), + bootstrap_code: None, max_size: self::archive::DEFAULT_MAX_SIZE, }; self::archive::archive(&args).context("failed to archive")?; diff --git a/tools/src/archive_common.rs b/tools/src/archive_common.rs index a340aeecad..b7a0b250b9 100644 --- a/tools/src/archive_common.rs +++ b/tools/src/archive_common.rs @@ -355,8 +355,16 @@ pub struct Args<'a> { pub destination_path: &'a Path, pub max_size: u64, pub source: &'a Path, + pub bootstrap_code: Option<&'a Path>, } -pub fn archive(&Args { destination_path, max_size, source }: &Args) -> Result<()> { +pub fn archive( + &Args { + destination_path, + max_size, + source, + bootstrap_code, + }: &Args, +) -> Result<()> { let previous_extension = destination_path.extension().map_or("", |ext| { ext.to_str() .expect("expected destination path to be valid UTF-8") @@ -407,15 +415,30 @@ pub fn archive(&Args { destination_path, max_size, source }: &Args) -> Result<() log::debug!("there are {} inodes", state.inode_count); // NOTE: The header is always stored at offset zero. - let header_offset = bump_alloc( - &mut state, - std::mem::size_of::() - .try_into() - .expect("expected header size to fit"), - "allocate header", - )?; + 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(|| { + anyhow!( + "failed to read bootstrap code file `{}`", + bootstrap_code.to_string_lossy(), + ) + })?; + elf_entry(&bootstrap_data) + } else { + u64::MAX + }; + let inode_table_length = { let inode_entry_size: u64 = std::mem::size_of::() .try_into() @@ -463,6 +486,8 @@ pub fn archive(&Args { destination_path, max_size, source }: &Args) -> Result<() }, inode_count: state.inode_count.into(), inode_table_offset, + bootstrap_entry: bootstrap_entry.into(), + initfs_size: state.file.metadata().context("failed to get initfs size")?.len().into(), }; write_all_at(&*state.file, &header_bytes, header_offset, "writing header") .context("failed to write header")?; @@ -475,3 +500,28 @@ pub fn archive(&Args { destination_path, max_size, source }: &Args) -> Result<() Ok(()) } + +fn elf_entry(data: &[u8]) -> u64 { + assert!(&data[..4] == b"\x7FELF"); + match (data[4], data[5]) { + // 32-bit, little endian + (1, 1) => u32::from_le_bytes( + <[u8; 4]>::try_from(&data[0x18..0x18 + 4]).expect("conversion cannot fail"), + ) as u64, + // 32-bit, big endian + (1, 2) => u32::from_be_bytes( + <[u8; 4]>::try_from(&data[0x18..0x18 + 4]).expect("conversion cannot fail"), + ) as u64, + // 64-bit, little endian + (2, 1) => u64::from_le_bytes( + <[u8; 8]>::try_from(&data[0x18..0x18 + 8]).expect("conversion cannot fail"), + ), + // 64-bit, big endian + (2, 2) => u64::from_be_bytes( + <[u8; 8]>::try_from(&data[0x18..0x18 + 8]).expect("conversion cannot fail"), + ), + (ei_class, ei_data) => { + panic!("Unsupported ELF EI_CLASS {} EI_DATA {}", ei_class, ei_data); + } + } +} diff --git a/tools/src/bin/archive.rs b/tools/src/bin/archive.rs index 6fc48c66b4..5258f75ee3 100644 --- a/tools/src/bin/archive.rs +++ b/tools/src/bin/archive.rs @@ -24,6 +24,11 @@ fn main() -> Result<()> { .required(true) .help("Specify the source directory to build the image from."), ) + .arg( + Arg::new("BOOTSTRAP_CODE") + .required(false) + .help("Specify the bootstrap ELF file to include in the image.") + ) .arg( Arg::new("OUTPUT") .required(true) @@ -47,12 +52,15 @@ fn main() -> Result<()> { .get_one::("SOURCE") .expect("expected the required arg SOURCE to exist"); + let bootstrap_code = matches.get_one::("BOOTSTRAP_CODE"); + let destination = matches .get_one::("OUTPUT") .expect("expected the required arg OUTPUT to exist"); let args = Args { source: Path::new(source), + bootstrap_code: bootstrap_code.map(|bootstrap_code| Path::new(bootstrap_code)), destination_path: Path::new(destination), max_size, };