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