refactor(verbose): replace the global flag and veprintln! with log
Diagnostics now go through the log facade: the library no longer decides where output goes, and consumers plug in their own sink (the CLI installs a stderr logger printing warnings always and ecr debug diagnostics under -v; dependency records stay filtered out). The kernel 'quiet' flag moves from the global verbosity flag to an explicit QemuConfig field.
This commit is contained in:
Generated
+2
@@ -307,6 +307,7 @@ dependencies = [
|
||||
"flate2",
|
||||
"futures-util",
|
||||
"libc",
|
||||
"log",
|
||||
"nix",
|
||||
"reqwest",
|
||||
"serde",
|
||||
@@ -329,6 +330,7 @@ dependencies = [
|
||||
"clap",
|
||||
"ecr",
|
||||
"indicatif",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -22,5 +22,8 @@ clap = { version = "4", features = ["derive", "env"] }
|
||||
# Error handling
|
||||
anyhow = "1"
|
||||
|
||||
# Logging facade; the stderr sink lives in src/logger.rs
|
||||
log = { version = "0.4", features = ["std"] }
|
||||
|
||||
# Progress bar rendering of library progress events
|
||||
indicatif = "0.18"
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
//! stderr logging sink: `Warn`/`Error` records always, `Debug`
|
||||
//! diagnostics only with -v.
|
||||
|
||||
use log::{Level, LevelFilter, Log, Metadata, Record};
|
||||
|
||||
struct CliLogger {
|
||||
max: LevelFilter,
|
||||
}
|
||||
|
||||
impl Log for CliLogger {
|
||||
fn enabled(&self, metadata: &Metadata) -> bool {
|
||||
if metadata.level() > self.max {
|
||||
return false;
|
||||
}
|
||||
match metadata.level() {
|
||||
Level::Warn | Level::Error => true,
|
||||
// Dependencies (hyper, rustls, …) share the log facade and are
|
||||
// far noisier than we are; -v follows only our own diagnostics.
|
||||
_ => metadata.target().starts_with("ecr"),
|
||||
}
|
||||
}
|
||||
|
||||
fn log(&self, record: &Record) {
|
||||
if !self.enabled(record.metadata()) {
|
||||
return;
|
||||
}
|
||||
match record.level() {
|
||||
Level::Error => eprintln!("Error: {}", record.args()),
|
||||
Level::Warn => eprintln!("Warning: {}", record.args()),
|
||||
_ => eprintln!("{}", record.args()),
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&self) {}
|
||||
}
|
||||
|
||||
/// Install the CLI logger: warnings and errors always, `ecr` debug
|
||||
/// diagnostics only when `verbose` (-v).
|
||||
pub fn init(verbose: bool) {
|
||||
let max = if verbose {
|
||||
LevelFilter::Debug
|
||||
} else {
|
||||
LevelFilter::Warn
|
||||
};
|
||||
log::set_boxed_logger(Box::new(CliLogger { max })).ok();
|
||||
log::set_max_level(max);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
mod cli;
|
||||
mod logger;
|
||||
mod progress;
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -11,14 +12,14 @@ use ecr::chroot;
|
||||
use ecr::config::Config;
|
||||
use ecr::exec::ExecOptions;
|
||||
use ecr::mount::BindTarget;
|
||||
use ecr::{kernel, qemu, qemu_vm, utils, veprintln, verbose, PrepareRequest, RootfsCache};
|
||||
use ecr::{kernel, qemu, qemu_vm, utils, PrepareRequest, RootfsCache};
|
||||
use progress::TermProgress;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let args = Args::parse();
|
||||
|
||||
// Initialise verbosity before anything else so all downstream code can use veprintln!.
|
||||
verbose::set(args.verbose);
|
||||
// Install the logger before anything else so all downstream code can log.
|
||||
logger::init(args.verbose);
|
||||
|
||||
// Render library progress (bars/spinners) on the terminal
|
||||
let progress = TermProgress::new();
|
||||
@@ -63,11 +64,11 @@ fn main() -> Result<()> {
|
||||
// QEMU system mode
|
||||
let kernel_path = match kernel_opt {
|
||||
Some(path) => {
|
||||
veprintln!("QEMU mode: using provided kernel {}", path.display());
|
||||
log::debug!("QEMU mode: using provided kernel {}", path.display());
|
||||
path.clone()
|
||||
}
|
||||
None => {
|
||||
veprintln!("QEMU mode: downloading default kernel...");
|
||||
log::debug!("QEMU mode: downloading default kernel...");
|
||||
kernel::get_default_kernel(cache.dir(), &arch, &progress)?
|
||||
}
|
||||
};
|
||||
@@ -83,6 +84,7 @@ fn main() -> Result<()> {
|
||||
kernel_path,
|
||||
rootfs_path: rootfs.dir().to_path_buf(),
|
||||
memory: args.memory.clone(),
|
||||
quiet: !args.verbose,
|
||||
arch: arch.clone(),
|
||||
command,
|
||||
},
|
||||
@@ -91,7 +93,7 @@ fn main() -> Result<()> {
|
||||
|
||||
// Cleanup happens automatically via tempfile
|
||||
if result.is_ok() {
|
||||
veprintln!("Cleanup complete.");
|
||||
log::debug!("Cleanup complete.");
|
||||
}
|
||||
|
||||
result
|
||||
@@ -123,7 +125,7 @@ fn namespace_mode(args: &Args, rootfs: &Path, config: &Config, arch: &str) -> Re
|
||||
|
||||
// Cleanup happens automatically via tempfile
|
||||
if exit_code.is_ok() {
|
||||
veprintln!("Cleanup complete.");
|
||||
log::debug!("Cleanup complete.");
|
||||
}
|
||||
|
||||
exit_code
|
||||
|
||||
@@ -21,6 +21,9 @@ flate2 = "1"
|
||||
xz2 = "0.1"
|
||||
zstd = "0.13"
|
||||
|
||||
# Logging facade (the CLI installs the sink)
|
||||
log = "0.4"
|
||||
|
||||
# Unix syscall bindings
|
||||
nix = { version = "0.31", features = ["fs", "mount", "sched", "signal", "user", "process", "hostname"] }
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::veprintln;
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use nix::unistd::{chroot, execve};
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
@@ -39,7 +38,7 @@ pub fn run_chroot(
|
||||
) -> Result<()> {
|
||||
// Set hostname in UTS namespace
|
||||
if let Err(e) = crate::namespace::set_hostname("chroot") {
|
||||
eprintln!("Warning: Failed to set hostname: {}", e);
|
||||
log::warn!("Failed to set hostname: {}", e);
|
||||
}
|
||||
|
||||
// Detect shell before chroot (we're still outside)
|
||||
@@ -90,8 +89,8 @@ pub fn run_chroot(
|
||||
})?;
|
||||
|
||||
// Print welcome message
|
||||
veprintln!("Entering chroot at {}", rootfs.display());
|
||||
veprintln!("Working directory: {}", working_dir.display());
|
||||
log::debug!("Entering chroot at {}", rootfs.display());
|
||||
log::debug!("Working directory: {}", working_dir.display());
|
||||
|
||||
// execve(2) does not search PATH: resolve the program through the
|
||||
// caller-composed environment like execvp(3) would.
|
||||
|
||||
+13
-14
@@ -1,5 +1,4 @@
|
||||
use crate::progress::{Progress, Unit};
|
||||
use crate::veprintln;
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use futures_util::StreamExt;
|
||||
use reqwest::Client;
|
||||
@@ -88,7 +87,7 @@ pub fn download_image(
|
||||
match source {
|
||||
ImageSource::DirectTarball { distro, version } => {
|
||||
let url = crate::distro::resolve_distro_url(distro, version.as_deref(), arch)?;
|
||||
veprintln!("Resolved URL: {}", url);
|
||||
log::debug!("Resolved URL: {}", url);
|
||||
if !url.starts_with("http://") && !url.starts_with("https://") {
|
||||
return Err(anyhow!("Unsupported URL scheme: {}", url));
|
||||
}
|
||||
@@ -100,7 +99,7 @@ pub fn download_image(
|
||||
tag,
|
||||
architecture,
|
||||
} => {
|
||||
veprintln!("Pulling OCI image: {}/{}:{}", registry, repository, tag);
|
||||
log::debug!("Pulling OCI image: {}/{}:{}", registry, repository, tag);
|
||||
rt.block_on(download_oci_image_async(
|
||||
registry,
|
||||
repository,
|
||||
@@ -114,7 +113,7 @@ pub fn download_image(
|
||||
}
|
||||
|
||||
async fn download_file_async(url: &str, dest: &Path, progress: &dyn Progress) -> Result<()> {
|
||||
veprintln!("Downloading: {}", url);
|
||||
log::debug!("Downloading: {}", url);
|
||||
|
||||
let client = Client::builder()
|
||||
.timeout(Duration::from_secs(300))
|
||||
@@ -168,7 +167,7 @@ async fn download_file_async(url: &str, dest: &Path, progress: &dyn Progress) ->
|
||||
tokio::fs::rename(&temp_dest, dest)
|
||||
.await
|
||||
.context("Failed to move partial download to final destination")?;
|
||||
veprintln!("Download complete: {}", dest.display());
|
||||
log::debug!("Download complete: {}", dest.display());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -230,7 +229,7 @@ async fn try_download_oci_image(
|
||||
format!("https://{}/v2/{}/manifests/{}", registry, repository, tag)
|
||||
};
|
||||
|
||||
veprintln!("Fetching manifest from: {}", manifest_url);
|
||||
log::debug!("Fetching manifest from: {}", manifest_url);
|
||||
|
||||
// Request both manifest list and single manifest types (including OCI formats)
|
||||
let response = client
|
||||
@@ -265,7 +264,7 @@ async fn try_download_oci_image(
|
||||
// Check if this is a manifest list (multi-arch)
|
||||
let layers = if let Some(manifests) = manifest["manifests"].as_array() {
|
||||
// This is a manifest list - find the right architecture
|
||||
veprintln!("Got manifest list with {} manifests", manifests.len());
|
||||
log::debug!("Got manifest list with {} manifests", manifests.len());
|
||||
|
||||
let manifest_entry = manifests
|
||||
.iter()
|
||||
@@ -291,7 +290,7 @@ async fn try_download_oci_image(
|
||||
.as_str()
|
||||
.ok_or_else(|| anyhow!("No digest in manifest entry"))?;
|
||||
|
||||
veprintln!("Found manifest digest: {}", manifest_digest);
|
||||
log::debug!("Found manifest digest: {}", manifest_digest);
|
||||
|
||||
// Now get the actual manifest for this architecture
|
||||
let arch_manifest_url = if registry == "docker.io" {
|
||||
@@ -338,7 +337,7 @@ async fn try_download_oci_image(
|
||||
.clone()
|
||||
};
|
||||
|
||||
veprintln!("Found {} layers to download", layers.len());
|
||||
log::debug!("Found {} layers to download", layers.len());
|
||||
|
||||
// Download all layer blobs into a temp directory
|
||||
let temp_dir = tempfile::tempdir()?;
|
||||
@@ -358,7 +357,7 @@ async fn try_download_oci_image(
|
||||
let ext = media_type_to_extension(media_type);
|
||||
let layer_name = format!("layer_{}.{}", i, ext);
|
||||
|
||||
veprintln!(
|
||||
log::debug!(
|
||||
"Fetching layer {}/{}: {}",
|
||||
i + 1,
|
||||
layers.len(),
|
||||
@@ -440,7 +439,7 @@ async fn get_auth_token(client: &Client, registry: &str, repository: &str) -> Re
|
||||
"https://auth.docker.io/token?service=registry.docker.io&scope=repository:{}:pull",
|
||||
repository
|
||||
);
|
||||
veprintln!("Getting auth token from: {}", url);
|
||||
log::debug!("Getting auth token from: {}", url);
|
||||
return fetch_bearer_token(client, &url).await;
|
||||
}
|
||||
|
||||
@@ -464,8 +463,8 @@ async fn get_auth_token(client: &Client, registry: &str, repository: &str) -> Re
|
||||
other => {
|
||||
// Unexpected status; proceed without a token and let the manifest
|
||||
// request fail with a more descriptive error.
|
||||
eprintln!(
|
||||
"Warning: registry probe returned HTTP {}; trying without auth",
|
||||
log::warn!(
|
||||
"registry probe returned HTTP {}; trying without auth",
|
||||
other
|
||||
);
|
||||
return Ok(String::new());
|
||||
@@ -492,7 +491,7 @@ async fn get_auth_token(client: &Client, registry: &str, repository: &str) -> Re
|
||||
"{}?service={}&scope=repository:{}:pull",
|
||||
realm, service, repository
|
||||
);
|
||||
veprintln!("Getting auth token from: {}", token_url);
|
||||
log::debug!("Getting auth token from: {}", token_url);
|
||||
fetch_bearer_token(client, &token_url).await
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ use crate::mount::{self, BindTarget};
|
||||
use crate::namespace;
|
||||
use crate::qemu;
|
||||
use crate::utils::{self, Arch};
|
||||
use crate::veprintln;
|
||||
|
||||
/// Options for [`exec`].
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -99,7 +98,7 @@ pub fn exec(rootfs: &Path, opts: &ExecOptions) -> Result<i32> {
|
||||
|
||||
for bind in &opts.binds {
|
||||
if !bind.read_only {
|
||||
veprintln!("Read-write mount: {}", bind.target.display());
|
||||
log::debug!("Read-write mount: {}", bind.target.display());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::progress::{Progress, Unit};
|
||||
use crate::veprintln;
|
||||
use anyhow::{Context, Result};
|
||||
use std::fs::File;
|
||||
use std::io::{BufReader, Read};
|
||||
@@ -15,7 +14,7 @@ pub fn extract_tarball(tarball: &Path, dest: &Path, progress: &dyn Progress) ->
|
||||
// scanning the archive for a layers.manifest entry (which required
|
||||
// reading the entire compressed archive twice for large OCI images).
|
||||
if filename.starts_with("oci-") {
|
||||
veprintln!("Detected multi-layer OCI image, extracting layers...");
|
||||
log::debug!("Detected multi-layer OCI image, extracting layers...");
|
||||
return extract_multi_layer_oci(tarball, dest, progress);
|
||||
}
|
||||
|
||||
@@ -115,7 +114,7 @@ fn extract_multi_layer_oci(tarball: &Path, dest: &Path, progress: &dyn Progress)
|
||||
// Not our layer-bundle layout: a provisioned rootfs persisted over
|
||||
// an OCI cache entry (the dispatch above keys on the "oci-" filename
|
||||
// prefix). The outer archive is a plain rootfs tarball.
|
||||
veprintln!("No layers.manifest in bundle; extracting as plain rootfs tarball");
|
||||
log::debug!("No layers.manifest in bundle; extracting as plain rootfs tarball");
|
||||
return extract_single_archive(tarball, dest, progress);
|
||||
}
|
||||
|
||||
@@ -134,7 +133,7 @@ fn extract_multi_layer_oci(tarball: &Path, dest: &Path, progress: &dyn Progress)
|
||||
if !layer_path.exists() {
|
||||
continue;
|
||||
}
|
||||
veprintln!("Extracting layer: {}", layer_name);
|
||||
log::debug!("Extracting layer: {}", layer_name);
|
||||
extract_oci_layer(&layer_path, dest, &mut fixups, progress)?;
|
||||
}
|
||||
|
||||
@@ -324,7 +323,7 @@ fn apply_hard_link(dest: &Path, dst: &Path, target: &Path) -> Result<()> {
|
||||
}
|
||||
std::os::unix::fs::symlink(target, dst)
|
||||
.with_context(|| format!("Failed to link {} -> {}", dst.display(), target.display()))?;
|
||||
veprintln!(
|
||||
log::debug!(
|
||||
"Warning: hard link target {} absent; extracted {} as a symlink",
|
||||
target.display(),
|
||||
dst.display()
|
||||
@@ -421,7 +420,7 @@ fn extract_archive<R: std::io::Read>(
|
||||
}
|
||||
|
||||
progress.finish();
|
||||
veprintln!("Extracted {} files", count);
|
||||
log::debug!("Extracted {} files", count);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
+15
-16
@@ -18,7 +18,6 @@
|
||||
//! only ELF, uImage and raw images.
|
||||
|
||||
use crate::progress::{Progress, Unit};
|
||||
use crate::veprintln;
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use std::io::{Read, Seek};
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -112,7 +111,7 @@ fn find_kernel_package(branch: &str, arch: &str) -> Result<(&'static str, String
|
||||
branch, arch
|
||||
);
|
||||
|
||||
veprintln!("Fetching package index: {}", url);
|
||||
log::debug!("Fetching package index: {}", url);
|
||||
|
||||
let client = reqwest::blocking::Client::builder()
|
||||
.timeout(Duration::from_secs(30))
|
||||
@@ -136,7 +135,7 @@ fn find_kernel_package(branch: &str, arch: &str) -> Result<(&'static str, String
|
||||
// Extract APKINDEX from the tar.gz
|
||||
// We need to own the bytes to avoid lifetime issues
|
||||
let bytes_owned = bytes.to_vec();
|
||||
veprintln!(" Downloaded {} bytes", bytes_owned.len());
|
||||
log::debug!(" Downloaded {} bytes", bytes_owned.len());
|
||||
|
||||
// First decompress gzip to memory, then parse tar
|
||||
// Note: Alpine's APKINDEX.tar.gz uses concatenated gzip members (multi-member gzip)
|
||||
@@ -148,7 +147,7 @@ fn find_kernel_package(branch: &str, arch: &str) -> Result<(&'static str, String
|
||||
.read_to_end(&mut decompressed)
|
||||
.context("Failed to decompress gzip")?;
|
||||
|
||||
veprintln!(" Decompressed {} bytes", decompressed.len());
|
||||
log::debug!(" Decompressed {} bytes", decompressed.len());
|
||||
|
||||
let tar_cursor = std::io::Cursor::new(decompressed);
|
||||
let mut archive = tar::Archive::new(tar_cursor);
|
||||
@@ -164,7 +163,7 @@ fn find_kernel_package(branch: &str, arch: &str) -> Result<(&'static str, String
|
||||
let mut entry = match entry_result {
|
||||
Ok(e) => e,
|
||||
Err(e) => {
|
||||
veprintln!(
|
||||
log::debug!(
|
||||
" Warning: failed to read tar entry #{}: {}",
|
||||
entry_count,
|
||||
e
|
||||
@@ -176,7 +175,7 @@ fn find_kernel_package(branch: &str, arch: &str) -> Result<(&'static str, String
|
||||
let path = entry.path().context("Failed to get entry path")?;
|
||||
let path_str = path.to_string_lossy();
|
||||
|
||||
veprintln!(" Entry #{}: {}", entry_count, path_str);
|
||||
log::debug!(" Entry #{}: {}", entry_count, path_str);
|
||||
|
||||
if path_str == "APKINDEX" {
|
||||
let mut contents = String::new();
|
||||
@@ -184,7 +183,7 @@ fn find_kernel_package(branch: &str, arch: &str) -> Result<(&'static str, String
|
||||
.read_to_string(&mut contents)
|
||||
.context("Failed to read APKINDEX contents")?;
|
||||
|
||||
veprintln!(" APKINDEX size: {} bytes", contents.len());
|
||||
log::debug!(" APKINDEX size: {} bytes", contents.len());
|
||||
|
||||
// Parse the APKINDEX to find the best kernel package
|
||||
// Format:
|
||||
@@ -228,7 +227,7 @@ fn find_kernel_package(branch: &str, arch: &str) -> Result<(&'static str, String
|
||||
}
|
||||
}
|
||||
|
||||
veprintln!(" Total entries processed: {}", entry_count);
|
||||
log::debug!(" Total entries processed: {}", entry_count);
|
||||
|
||||
Err(anyhow!(
|
||||
"APKINDEX file not found in tar.gz archive (processed {} entries)",
|
||||
@@ -244,7 +243,7 @@ fn download_alpine_kernel(
|
||||
progress: &dyn Progress,
|
||||
) -> Result<()> {
|
||||
let (package, version) = find_kernel_package(branch, arch)?;
|
||||
veprintln!("Found {} version: {}", package, version);
|
||||
log::debug!("Found {} version: {}", package, version);
|
||||
|
||||
// Construct the download URL for the kernel .apk
|
||||
// Format: https://dl-cdn.alpinelinux.org/alpine/v3.23/main/x86_64/linux-virt-6.12.8-r0.apk
|
||||
@@ -253,7 +252,7 @@ fn download_alpine_kernel(
|
||||
branch, arch, package, version
|
||||
);
|
||||
|
||||
veprintln!("Downloading kernel: {}", url);
|
||||
log::debug!("Downloading kernel: {}", url);
|
||||
|
||||
// Use async download via the existing download module pattern
|
||||
let rt = tokio::runtime::Runtime::new().context("Failed to create Tokio runtime")?;
|
||||
@@ -336,7 +335,7 @@ fn extract_kernel_from_apk(apk_path: &Path, dest: &Path, package: &str) -> Resul
|
||||
.ok_or_else(|| anyhow!("unexpected kernel package name: {}", package))?;
|
||||
let kernel_name = format!("boot/vmlinuz-{}", flavor);
|
||||
|
||||
veprintln!("Extracting kernel from APK...");
|
||||
log::debug!("Extracting kernel from APK...");
|
||||
|
||||
let file = std::fs::File::open(apk_path).context("Failed to open APK file")?;
|
||||
// Use MultiGzDecoder because Alpine APKs have concatenated gzip members
|
||||
@@ -348,12 +347,12 @@ fn extract_kernel_from_apk(apk_path: &Path, dest: &Path, package: &str) -> Resul
|
||||
let path = entry.path().context("Failed to get entry path")?;
|
||||
let path_str = path.to_string_lossy();
|
||||
|
||||
veprintln!(" APK entry: {}", path_str);
|
||||
log::debug!(" APK entry: {}", path_str);
|
||||
|
||||
if path_str == kernel_name || path_str == format!("./{}", kernel_name) {
|
||||
// Extract to destination
|
||||
entry.unpack(dest).context("Failed to extract kernel")?;
|
||||
veprintln!(" Extracted kernel to: {}", dest.display());
|
||||
log::debug!(" Extracted kernel to: {}", dest.display());
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
@@ -379,7 +378,7 @@ fn decompress_kernel_if_gzipped(path: &Path) -> Result<()> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
veprintln!("Kernel is gzip-compressed, decompressing...");
|
||||
log::debug!("Kernel is gzip-compressed, decompressing...");
|
||||
file.rewind().context("Failed to rewind kernel file")?;
|
||||
let mut decompressed = Vec::new();
|
||||
flate2::read::MultiGzDecoder::new(file)
|
||||
@@ -407,7 +406,7 @@ pub fn get_default_kernel(
|
||||
|
||||
// Check if already cached
|
||||
if kernel_path.exists() {
|
||||
veprintln!("Using cached default kernel: {}", kernel_path.display());
|
||||
log::debug!("Using cached default kernel: {}", kernel_path.display());
|
||||
return Ok(kernel_path);
|
||||
}
|
||||
|
||||
@@ -416,7 +415,7 @@ pub fn get_default_kernel(
|
||||
|
||||
// Determine Alpine branch
|
||||
let branch = get_alpine_branch()?;
|
||||
veprintln!("Using Alpine branch: {}", branch);
|
||||
log::debug!("Using Alpine branch: {}", branch);
|
||||
|
||||
// Download and extract the kernel
|
||||
download_alpine_kernel(&branch, alpine_arch, &kernel_path, progress)?;
|
||||
|
||||
@@ -25,17 +25,6 @@ pub mod qemu;
|
||||
pub mod qemu_vm;
|
||||
pub mod rootfs;
|
||||
pub mod utils;
|
||||
pub mod verbose;
|
||||
|
||||
/// Print to stderr only when verbose mode is active (see [`verbose::set`]).
|
||||
#[macro_export]
|
||||
macro_rules! veprintln {
|
||||
($($arg:tt)*) => {
|
||||
if $crate::verbose::is_verbose() {
|
||||
eprintln!($($arg)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub use exec::{exec, ExecOptions};
|
||||
pub use mount::BindTarget;
|
||||
|
||||
@@ -67,7 +67,7 @@ pub fn setup_mounts(rootfs: &Path, binds: &[BindTarget]) -> Result<Vec<TempDir>>
|
||||
MsFlags::MS_PRIVATE | MsFlags::MS_REC,
|
||||
None::<&str>,
|
||||
) {
|
||||
eprintln!("Warning: Failed to make mounts private: {}", e);
|
||||
log::warn!("Failed to make mounts private: {}", e);
|
||||
}
|
||||
|
||||
// Mount /proc
|
||||
@@ -81,7 +81,7 @@ pub fn setup_mounts(rootfs: &Path, binds: &[BindTarget]) -> Result<Vec<TempDir>>
|
||||
|
||||
// Try to mount /sys (may fail in some environments)
|
||||
if let Err(e) = mount_sys(rootfs) {
|
||||
eprintln!("Warning: Could not mount /sys: {}", e);
|
||||
log::warn!("Could not mount /sys: {}", e);
|
||||
}
|
||||
|
||||
for bind in binds {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::veprintln;
|
||||
use anyhow::{anyhow, Result};
|
||||
use std::path::Path;
|
||||
|
||||
@@ -19,6 +18,6 @@ pub fn check_binfmt(arch: &str) -> Result<()> {
|
||||
));
|
||||
}
|
||||
|
||||
veprintln!("QEMU binfmt_misc registered for {}", arch);
|
||||
log::debug!("QEMU binfmt_misc registered for {}", arch);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+20
-22
@@ -1,5 +1,4 @@
|
||||
use crate::progress::{Progress, Unit};
|
||||
use crate::veprintln;
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use cpio::{newc, NewcBuilder};
|
||||
use std::io::Write;
|
||||
@@ -15,6 +14,9 @@ pub struct QemuConfig {
|
||||
pub rootfs_path: PathBuf,
|
||||
/// Memory size for VM (e.g., "2G", "512M")
|
||||
pub memory: String,
|
||||
/// Append `quiet` to the kernel command line to suppress kernel log
|
||||
/// messages (the CLI leaves this off under -v).
|
||||
pub quiet: bool,
|
||||
/// Target architecture
|
||||
pub arch: String,
|
||||
/// Optional command to run instead of default init
|
||||
@@ -65,9 +67,9 @@ pub fn launch_qemu(config: QemuConfig, progress: &dyn Progress) -> Result<()> {
|
||||
// Check if we can use KVM acceleration
|
||||
let use_kvm = can_use_kvm(&config.arch);
|
||||
if use_kvm {
|
||||
veprintln!(" KVM: enabled (native acceleration)");
|
||||
log::debug!(" KVM: enabled (native acceleration)");
|
||||
} else {
|
||||
veprintln!(" KVM: disabled (using software emulation)");
|
||||
log::debug!(" KVM: disabled (using software emulation)");
|
||||
}
|
||||
|
||||
// Detect the best available shell in the rootfs
|
||||
@@ -89,13 +91,9 @@ pub fn launch_qemu(config: QemuConfig, progress: &dyn Progress) -> Result<()> {
|
||||
// Build kernel command line
|
||||
// For initramfs boot, use rdinit= instead of init=
|
||||
// No root= needed as initramfs becomes the rootfs
|
||||
// 'quiet' suppresses kernel log messages for a cleaner console (removed with -v)
|
||||
// 'quiet' suppresses kernel log messages for a cleaner console (see QemuConfig::quiet)
|
||||
// The init script (added to initramfs) handles hostname, shell, and poweroff
|
||||
let quiet_flag = if crate::verbose::is_verbose() {
|
||||
""
|
||||
} else {
|
||||
" quiet"
|
||||
};
|
||||
let quiet_flag = if config.quiet { " quiet" } else { "" };
|
||||
|
||||
let kernel_append = if let Some(ref cmd) = config.command {
|
||||
// The argv travels through the kernel cmdline, where quotes and
|
||||
@@ -119,11 +117,11 @@ pub fn launch_qemu(config: QemuConfig, progress: &dyn Progress) -> Result<()> {
|
||||
)
|
||||
};
|
||||
|
||||
veprintln!("Launching QEMU: {}", qemu_bin);
|
||||
veprintln!(" Kernel: {}", config.kernel_path.display());
|
||||
veprintln!(" Initramfs: {}", initramfs.display());
|
||||
veprintln!(" Memory: {}", config.memory);
|
||||
veprintln!(" Kernel append: {}", kernel_append);
|
||||
log::debug!("Launching QEMU: {}", qemu_bin);
|
||||
log::debug!(" Kernel: {}", config.kernel_path.display());
|
||||
log::debug!(" Initramfs: {}", initramfs.display());
|
||||
log::debug!(" Memory: {}", config.memory);
|
||||
log::debug!(" Kernel append: {}", kernel_append);
|
||||
|
||||
// Build QEMU arguments
|
||||
// -machine virt is selected explicitly on riscv64 (see machine_for_arch)
|
||||
@@ -173,7 +171,7 @@ pub fn launch_qemu(config: QemuConfig, progress: &dyn Progress) -> Result<()> {
|
||||
|
||||
// Cleanup initramfs
|
||||
if let Err(e) = std::fs::remove_file(&initramfs) {
|
||||
veprintln!("Warning: failed to cleanup initramfs: {}", e);
|
||||
log::debug!("Warning: failed to cleanup initramfs: {}", e);
|
||||
}
|
||||
|
||||
if !status.success() {
|
||||
@@ -308,7 +306,7 @@ fn create_initramfs(rootfs: &Path, progress: &dyn Progress) -> Result<PathBuf> {
|
||||
let total_bytes = std::fs::metadata(&initramfs_path)
|
||||
.map(|m| m.len())
|
||||
.unwrap_or(0);
|
||||
veprintln!(
|
||||
log::debug!(
|
||||
"Initramfs created: {} bytes, {} files",
|
||||
total_bytes,
|
||||
file_count
|
||||
@@ -345,7 +343,7 @@ fn write_cpio_archive<W: Write>(
|
||||
&mut total_data,
|
||||
)?;
|
||||
|
||||
veprintln!(
|
||||
log::debug!(
|
||||
"Collected {} entries, {} bytes total data",
|
||||
written_names.len(),
|
||||
total_data
|
||||
@@ -508,7 +506,7 @@ fn write_dir_entries(
|
||||
let dir_entries: Vec<_> = match std::fs::read_dir(current) {
|
||||
Ok(entries) => entries.collect::<std::result::Result<_, _>>()?,
|
||||
Err(e) => {
|
||||
veprintln!(
|
||||
log::debug!(
|
||||
"Warning: cannot read directory {}: {}",
|
||||
current.display(),
|
||||
e
|
||||
@@ -526,7 +524,7 @@ fn write_dir_entries(
|
||||
let metadata = match std::fs::symlink_metadata(&path) {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
veprintln!(
|
||||
log::debug!(
|
||||
"Warning: skipping {} due to metadata error: {}",
|
||||
path.display(),
|
||||
e
|
||||
@@ -577,7 +575,7 @@ fn write_dir_entries(
|
||||
let data = match std::fs::read(&path) {
|
||||
Ok(data) => data,
|
||||
Err(e) => {
|
||||
veprintln!("Warning: cannot read file {}: {}", path.display(), e);
|
||||
log::debug!("Warning: cannot read file {}: {}", path.display(), e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
@@ -589,7 +587,7 @@ fn write_dir_entries(
|
||||
let data = match std::fs::read(&path) {
|
||||
Ok(data) => data,
|
||||
Err(e) => {
|
||||
veprintln!("Warning: cannot read file {}: {}", path.display(), e);
|
||||
log::debug!("Warning: cannot read file {}: {}", path.display(), e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
@@ -598,7 +596,7 @@ fn write_dir_entries(
|
||||
match std::fs::read_link(&path) {
|
||||
Ok(target) => (target.to_string_lossy().into_owned().into_bytes(), 1, 0),
|
||||
Err(e) => {
|
||||
veprintln!("Warning: cannot read symlink {}: {}", path.display(), e);
|
||||
log::debug!("Warning: cannot read symlink {}: {}", path.display(), e);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,6 @@ use crate::download::{digest_sidecar, download_image, fetch_oci_digest};
|
||||
use crate::exec::ExecOptions;
|
||||
use crate::extract::extract_tarball;
|
||||
use crate::progress::Progress;
|
||||
use crate::veprintln;
|
||||
|
||||
/// Sidecar suffix marking a cache entry that holds a provisioned rootfs.
|
||||
const PROVISIONED_SUFFIX: &str = "provisioned";
|
||||
@@ -117,10 +116,7 @@ impl RootfsCache {
|
||||
stored.as_deref() != Some(current.trim())
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
"Warning: could not check image freshness ({}); using cache",
|
||||
e
|
||||
);
|
||||
log::warn!("could not check image freshness ({}); using cache", e);
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -142,7 +138,7 @@ impl RootfsCache {
|
||||
})?;
|
||||
download_image(&source, &cache_path, &arch, progress)?;
|
||||
} else {
|
||||
veprintln!("Using cached tarball: {}", cache_path.display());
|
||||
log::debug!("Using cached tarball: {}", cache_path.display());
|
||||
}
|
||||
|
||||
extract_prepared(cache_path, arch, progress)
|
||||
@@ -175,7 +171,7 @@ impl RootfsCache {
|
||||
let marker = sidecar(&cache_path, PROVISIONED_SUFFIX);
|
||||
|
||||
if !req.no_cache && cache_path.exists() && marker.exists() {
|
||||
veprintln!("Using provisioned cache entry: {}", cache_path.display());
|
||||
log::debug!("Using provisioned cache entry: {}", cache_path.display());
|
||||
return extract_prepared(cache_path, arch, progress);
|
||||
}
|
||||
|
||||
@@ -278,7 +274,7 @@ impl PreparedRootfs {
|
||||
self.cache_path.display()
|
||||
)
|
||||
})?;
|
||||
veprintln!("Persisted rootfs to cache: {}", self.cache_path.display());
|
||||
log::debug!("Persisted rootfs to cache: {}", self.cache_path.display());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -309,7 +305,7 @@ fn extract_prepared(
|
||||
let temp = tempfile::tempdir().context("Failed to create temporary directory")?;
|
||||
let rootfs = temp.path().to_path_buf();
|
||||
|
||||
veprintln!("Extracting to: {}", rootfs.display());
|
||||
log::debug!("Extracting to: {}", rootfs.display());
|
||||
extract_tarball(&cache_path, &rootfs, progress)?;
|
||||
|
||||
Ok(PreparedRootfs {
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
static VERBOSE: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
pub fn set(v: bool) {
|
||||
VERBOSE.store(v, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn is_verbose() -> bool {
|
||||
VERBOSE.load(Ordering::Relaxed)
|
||||
}
|
||||
Reference in New Issue
Block a user