From 1cfcf0e433ec2f38b9dafba74edcd5b78c65d60a Mon Sep 17 00:00:00 2001 From: Valentin Haudiquet Date: Sat, 3 Oct 2026 00:05:47 +0200 Subject: [PATCH] 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. --- crates/ecr-cli/src/main.rs | 17 ++++---- crates/ecr/src/qemu_vm.rs | 79 ++++++++++++++++++++------------------ 2 files changed, 51 insertions(+), 45 deletions(-) diff --git a/crates/ecr-cli/src/main.rs b/crates/ecr-cli/src/main.rs index f37e925..df1aea6 100644 --- a/crates/ecr-cli/src/main.rs +++ b/crates/ecr-cli/src/main.rs @@ -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() { diff --git a/crates/ecr/src/qemu_vm.rs b/crates/ecr/src/qemu_vm.rs index 66a39bf..101020d 100644 --- a/crates/ecr/src/qemu_vm.rs +++ b/crates/ecr/src/qemu_vm.rs @@ -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>, } -/// 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 { +fn create_initramfs(rootfs: &Path, progress: &dyn Progress) -> Result { // 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 { .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 { Ok(initramfs_path) } -/// Write the newc-format cpio archive for a directory tree, streaming to `writer` -fn write_cpio_archive(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( + rootfs: &Path, + writer: &mut W, + progress: &dyn Progress, +) -> Result { // 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(rootfs: &Path, writer: &mut W, pb: &ProgressBar) let mut written_names: std::collections::HashSet = 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( /// 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, total_data: &mut u64, -) -> Result<()> { +) -> Result { // Read directory entries let dir_entries: Vec<_> = match std::fs::read_dir(current) { Ok(entries) => entries.collect::>()?, @@ -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| {