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