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:
@@ -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
@@ -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| {
|
||||
|
||||
Reference in New Issue
Block a user