refactor(qemu): report initramfs progress through the sink

create_initramfs emits Progress events instead of drawing a spinner
inside the library; the cpio walker counts the entries it visits so
the verbose summary no longer reads the count off a progress bar.
This commit is contained in:
2026-10-03 00:05:47 +02:00
parent 71768f71d7
commit 1cfcf0e433
2 changed files with 51 additions and 45 deletions
+10 -7
View File
@@ -73,13 +73,16 @@ fn main() -> Result<()> {
Some(args.command.clone())
};
let result = qemu_vm::launch_qemu(qemu_vm::QemuConfig {
kernel_path,
rootfs_path: rootfs.dir().to_path_buf(),
memory: args.memory.clone(),
arch: arch.clone(),
command,
});
let result = qemu_vm::launch_qemu(
qemu_vm::QemuConfig {
kernel_path,
rootfs_path: rootfs.dir().to_path_buf(),
memory: args.memory.clone(),
arch: arch.clone(),
command,
},
&ecr::NoProgress,
);
// Cleanup happens automatically via tempfile
if result.is_ok() {
+41 -38
View File
@@ -1,7 +1,7 @@
use crate::progress::{Progress, Unit};
use crate::veprintln;
use anyhow::{anyhow, Context, Result};
use cpio::{newc, NewcBuilder};
use indicatif::{ProgressBar, ProgressStyle};
use std::io::Write;
use std::os::unix::fs::MetadataExt;
use std::path::{Path, PathBuf};
@@ -21,8 +21,9 @@ pub struct QemuConfig {
pub command: Option<Vec<String>>,
}
/// Launch QEMU with the given configuration
pub fn launch_qemu(config: QemuConfig) -> Result<()> {
/// Launch QEMU with the given configuration, reporting initramfs-creation
/// progress through `progress`
pub fn launch_qemu(config: QemuConfig, progress: &dyn Progress) -> Result<()> {
// Check that kernel exists
if !config.kernel_path.exists() {
return Err(anyhow!(
@@ -44,7 +45,7 @@ pub fn launch_qemu(config: QemuConfig) -> Result<()> {
.with_context(|| format!("Invalid memory size: {}", config.memory))?;
// Create an uncompressed cpio initramfs from the rootfs
let initramfs = create_initramfs(&config.rootfs_path)?;
let initramfs = create_initramfs(&config.rootfs_path, progress)?;
// Get QEMU binary for architecture
let qemu_bin = qemu_binary_for_arch(&config.arch);
@@ -276,7 +277,7 @@ fn check_kvm_capabilities() -> bool {
/// Create an uncompressed cpio initramfs from a directory.
/// Entries are streamed straight to disk so large rootfs images never have
/// to fit in memory as a whole archive.
fn create_initramfs(rootfs: &Path) -> Result<PathBuf> {
fn create_initramfs(rootfs: &Path, progress: &dyn Progress) -> Result<PathBuf> {
// Create a temporary file for the initramfs (uncompressed cpio)
// Use a temp file in the same directory as rootfs, or fall back to /tmp
let initramfs_path = rootfs
@@ -284,31 +285,25 @@ fn create_initramfs(rootfs: &Path) -> Result<PathBuf> {
.map(|p| p.join("initramfs.cpio"))
.unwrap_or_else(|| std::env::temp_dir().join("initramfs.cpio"));
// Create progress bar
let pb = ProgressBar::new_spinner();
pb.set_style(
ProgressStyle::default_spinner()
.template("{spinner:.green} {msg} ({pos} files)")
.unwrap(),
);
pb.set_message("Creating initramfs...");
progress.begin("Creating initramfs...", Unit::Files, None);
let file = std::fs::File::create(&initramfs_path)
.with_context(|| format!("Failed to create {}", initramfs_path.display()))?;
let mut writer = std::io::BufWriter::new(file);
let result = write_cpio_archive(rootfs, &mut writer, &pb);
let written = write_cpio_archive(rootfs, &mut writer, progress);
let flushed = writer.flush().context("Failed to flush initramfs");
// On any error, don't leave a partial archive behind
if let Err(e) = result.or(flushed) {
std::fs::remove_file(&initramfs_path).ok();
return Err(e);
}
let file_count = match (written, flushed) {
(Ok(count), Ok(())) => count,
(Err(e), _) | (_, Err(e)) => {
std::fs::remove_file(&initramfs_path).ok();
return Err(e);
}
};
// Finish progress bar
let file_count = pb.position();
pb.finish_and_clear();
progress.finish();
let total_bytes = std::fs::metadata(&initramfs_path)
.map(|m| m.len())
@@ -322,8 +317,13 @@ fn create_initramfs(rootfs: &Path) -> Result<PathBuf> {
Ok(initramfs_path)
}
/// Write the newc-format cpio archive for a directory tree, streaming to `writer`
fn write_cpio_archive<W: Write>(rootfs: &Path, writer: &mut W, pb: &ProgressBar) -> Result<()> {
/// Write the newc-format cpio archive for a directory tree, streaming to
/// `writer`; returns the number of entries visited.
fn write_cpio_archive<W: Write>(
rootfs: &Path,
writer: &mut W,
progress: &dyn Progress,
) -> Result<u64> {
// Track hard links by (device, inode): the value is the synthetic cpio
// inode assigned to the first occurrence. The Linux initramfs loader
// turns later zero-size entries sharing that inode into hard links.
@@ -334,11 +334,11 @@ fn write_cpio_archive<W: Write>(rootfs: &Path, writer: &mut W, pb: &ProgressBar)
let mut written_names: std::collections::HashSet<String> = std::collections::HashSet::new();
let mut total_data: u64 = 0;
write_dir_entries(
let entry_count = write_dir_entries(
rootfs,
rootfs,
writer,
pb,
progress,
&mut seen_inodes,
&mut next_ino,
&mut written_names,
@@ -457,7 +457,7 @@ fi
// Write the trailer
newc::trailer(&mut *writer).context("Failed to write cpio trailer")?;
Ok(())
Ok(entry_count)
}
/// Write a single newc-format cpio entry
@@ -491,18 +491,19 @@ fn write_cpio_entry<W: Write>(
/// Walk a directory tree, writing every entry to the cpio archive as it goes
/// Hard links are handled by assigning a synthetic inode to the first
/// occurrence of a (device, inode) pair; subsequent occurrences are written
/// as zero-size entries reusing that inode.
/// as zero-size entries reusing that inode. Returns the number of entries
/// visited.
#[allow(clippy::too_many_arguments)]
fn write_dir_entries(
base: &Path,
current: &Path,
writer: &mut impl Write,
pb: &ProgressBar,
progress: &dyn Progress,
seen_inodes: &mut std::collections::HashMap<(u64, u64), u32>,
next_ino: &mut u32,
written_names: &mut std::collections::HashSet<String>,
total_data: &mut u64,
) -> Result<()> {
) -> Result<u64> {
// Read directory entries
let dir_entries: Vec<_> = match std::fs::read_dir(current) {
Ok(entries) => entries.collect::<std::result::Result<_, _>>()?,
@@ -512,10 +513,12 @@ fn write_dir_entries(
current.display(),
e
);
return Ok(());
return Ok(0);
}
};
let mut count: u64 = 0;
for entry in dir_entries {
let path = entry.path();
@@ -533,7 +536,8 @@ fn write_dir_entries(
};
// Increment progress counter
pb.inc(1);
progress.advance(1);
count += 1;
let file_type = metadata.file_type();
@@ -618,11 +622,11 @@ fn write_dir_entries(
// Recurse into directories
if file_type.is_dir() {
write_dir_entries(
count += write_dir_entries(
base,
&path,
writer,
pb,
progress,
seen_inodes,
next_ino,
written_names,
@@ -631,12 +635,13 @@ fn write_dir_entries(
}
}
Ok(())
Ok(count)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::progress::NoProgress;
use std::io::Read as _;
#[test]
@@ -708,9 +713,8 @@ mod tests {
)
.unwrap();
let pb = ProgressBar::hidden();
let mut archive = Vec::new();
write_cpio_archive(dir.path(), &mut archive, &pb).unwrap();
write_cpio_archive(dir.path(), &mut archive, &NoProgress).unwrap();
let entries = parse_cpio(archive)
.into_iter()
@@ -763,9 +767,8 @@ mod tests {
std::fs::create_dir(dir.path().join("sub")).unwrap();
std::fs::write(dir.path().join("sub/plain.txt"), b"plain").unwrap();
let pb = ProgressBar::hidden();
let mut archive = Vec::new();
write_cpio_archive(dir.path(), &mut archive, &pb).unwrap();
write_cpio_archive(dir.path(), &mut archive, &NoProgress).unwrap();
let entries = parse_cpio(archive);
let find = |name: &str| {