Merge branch 'refactor_initfs' into 'main'

Refactor initfs building

See merge request redox-os/base!244
This commit is contained in:
Jeremy Soller
2026-04-25 09:48:12 -06:00
7 changed files with 134 additions and 143 deletions
+1 -1
View File
@@ -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 {
+3 -2
View File
@@ -268,8 +268,9 @@ impl<'initfs> InitFs<'initfs> {
plain::slice_from_bytes::<InodeHeader>(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<Item = Inode> {
(0..self.inode_count()).map(Inode)
}
+1
View File
@@ -70,6 +70,7 @@ pub struct Header {
pub bootstrap_entry: U64,
pub initfs_size: U64,
pub page_size: U16,
pub root_inode: U16,
}
const _: () = {
Binary file not shown.
+7 -20
View File
@@ -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::<String>("MAX_SIZE") {
max_size_str
.parse::<u64>()
.context("expected an integer for MAX_SIZE")?
} else {
DEFAULT_MAX_SIZE
};
let source = matches
.get_one::<String>("SOURCE")
.expect("expected the required arg SOURCE to exist");
let bootstrap_code = matches.get_one::<String>("BOOTSTRAP_CODE");
let bootstrap_code = matches
.get_one::<String>("BOOTSTRAP_CODE")
.expect("expected the required arg BOOTSTRAP_CODE to exist");
let destination = matches
.get_one::<String>("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)
}
+115 -117
View File
@@ -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<Dir> {
fn read_directory(path: &Path, root_path: &Path) -> Result<Dir> {
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<Di
anyhow!("failed to open file `{}`", entry.path().to_string_lossy(),)
})?)
} else if file_type.is_dir() {
EntryKind::Dir(read_directory(state, &entry.path(), root_path)?)
EntryKind::Dir(read_directory(&entry.path(), root_path)?)
} else if file_type.is_symlink() {
let link_file_path = entry.path();
@@ -138,12 +137,6 @@ fn read_directory(state: &mut State, path: &Path, root_path: &Path) -> Result<Di
));
};
// TODO: Allow the user to specify a lower limit than u16::MAX.
state.inode_count = state
.inode_count
.checked_add(1)
.ok_or_else(|| anyhow!("exceeded the maximum inode limit"))?;
Ok(Entry {
kind: entry_kind,
metadata,
@@ -235,46 +228,7 @@ fn allocate_and_write_link(state: &mut State, link: &Path) -> Result<WriteResult
Ok(WriteResult { size, offset })
}
fn write_inode(
state: &mut State,
ty: initfs::InodeType,
write_result: WriteResult,
inode: u16,
) -> Result<()> {
let inode_size: u32 = std::mem::size_of::<initfs::InodeHeader>()
.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::<initfs::InodeHeader>()];
let inode_hdr = plain::from_mut_bytes::<initfs::InodeHeader>(&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<WriteResult> {
fn allocate_and_write_dir(state: &mut State, dir: &Dir) -> Result<WriteResult> {
let entry_size =
u16::try_from(std::mem::size_of::<initfs::DirEntry>()).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<initfs::U16> {
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<initfs::InodeHeader>,
}
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<Offset> {
log::debug!("there are {} inodes", state.inode_table.count());
let inode_size: u32 = std::mem::size_of::<initfs::InodeHeader>()
.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::<initfs::InodeHeader>()];
let inode_hdr = plain::from_mut_bytes::<initfs::InodeHeader>(&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::<initfs::InodeHeader>()
.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")?;
+7 -3
View File
@@ -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")),
]),
)]);