Skip to main content
Version: 3.0.0-rc.2

Read and create CPIO archives

Hadris reads the newc (070701), newc CRC (070702), odc (070707) and old binary formats, and writes newc, newc CRC and odc. Linux initramfs images use newc.

[dependencies.hadris]
version = "3.0.0-rc.2"
default-features = false
features = ["std", "sync", "cpio"]

Stream archive entries​

use hadris::cpio::sync::CpioReader;
use hadris::io::StdIo;
use std::{fs::File, io::BufReader};

fn main() -> Result<(), Box<dyn std::error::Error>> {
let input = StdIo::new(BufReader::new(File::open("archive.cpio")?));
let mut archive = CpioReader::new(input);

while let Some(entry) = archive.next_entry()? {
let name = entry.path_str().unwrap_or("<non-UTF-8>");
println!("{} ({} bytes)", name, entry.len());
}

Ok(())
}

next_entry returns an entry that borrows the reader. Data the caller does not read is skipped by the next call, and CRC archives are checked either way. The reader needs no allocator, so the same code runs in a bootloader. An archive may end at an entry boundary without a trailer, as initramfs allows; ReaderOptions::with_strict_trailer requires one, and next_segment() reads archives concatenated after a trailer.

CpioReader::with_buffer(input, &mut storage[..], options) uses caller-owned name storage; capacity includes the terminating NUL, including for the TRAILER!!! name (11 bytes). Oversized encoded names return LimitExceeded. Entry offset() and data_offset() count from the supplied stream's start. Call next_segment() after next_entry() returns None; it returns false at EOF. A true result means bytes follow; next_entry() validates the header. into_parts() preserves the optional peeked byte for replay before reading the returned stream. into_inner() discards it.

Read a file payload​

# use hadris::cpio::sync::CpioReader;
# use hadris::io::StdIo;
# use hadris::io::sync::Read;
# use std::{fs::File, io::BufReader};
# fn run() -> Result<(), Box<dyn std::error::Error>> {
let mut archive = CpioReader::new(StdIo::new(BufReader::new(File::open("archive.cpio")?)));
while let Some(mut entry) = archive.next_entry()? {
if entry.path() == b"etc/hostname" {
let mut bytes = vec![0; entry.len() as usize];
entry.read_exact(&mut bytes)?;
println!("{}", String::from_utf8_lossy(&bytes));
break;
}
}
# Ok(())
# }

Create an archive​

use hadris::cpio::{CpioOptions, Format};
use hadris::fs::host::{self, TreeOptions};
use hadris::io::StdIo;
use std::{fs::File, io::BufWriter};

fn main() -> Result<(), Box<dyn std::error::Error>> {
let (tree, _) = host::read_tree("./root", &TreeOptions::new())?;
let mut output = StdIo::new(BufWriter::new(File::create("archive.cpio")?));
let options = CpioOptions::default().with_format(Format::Crc);
let report = hadris::cpio::sync::write(&mut output, &tree, &options)?;
for warning in report.warnings() {
eprintln!("{warning}");
}
Ok(())
}

The report's warnings list metadata cpio cannot store, such as access times, once per field with a count; hadris::cpio::plan returns the same report without writing. Entries without a modification time get the options' time (CpioOptions::with_time, 1980-01-01 by default). To write entries one at a time, use hadris::cpio::sync::Writer: append takes a Node, append_hard_links a group of names, and append_file streams data of a known length through an EntryWriter; finish writes the trailer. hadris::cpio::sync::read_tree reads an archive back into a Tree.

For initramfs-specific guidance, see Build a CPIO initramfs.