use std::{path::PathBuf, process};
use anyhow::Result;
use clap::{Args as ClapArgs, Parser, Subcommand};
#[cfg(feature = "bsd")]
use vmrunner_sysroot::bsd::extract_bsd_sysroot;
use vmrunner_sysroot::{
GuestfsCopyOptions, GuestfsDiskFormat, GuestfsMountMode, SysrootOptions, copy_paths_from_guest,
linux::extract_linux_sysroot,
};
const DEFAULT_SYSROOT_OUTPUT_PARENT: &str = "target/vmrunner-sysroot";
#[derive(Debug, Parser)]
#[command(
name = "vmrunner-sysroot",
about = "Extract vmrunner compiler sysroots from VM disk images",
after_help = "Requires libguestfs at runtime."
)]
struct Args {
#[command(subcommand)]
command: Command,
}
#[derive(Debug, Subcommand)]
enum Command {
#[cfg(feature = "bsd")]
#[command(about = "Extract a BSD sysroot from a QEMU/libguestfs-readable root image")]
Bsd(SysrootArgs),
#[command(about = "Extract a Linux sysroot from a QEMU/libguestfs-readable root image")]
Linux(SysrootArgs),
#[command(about = "Copy arbitrary paths out of a libguestfs-readable QEMU disk image")]
CopyOut(CopyOutArgs),
}
#[derive(Debug, ClapArgs)]
struct SysrootArgs {
#[arg(long = "target", value_name = "TARGET")]
target: String,
#[arg(long = "image", value_name = "PATH")]
image: PathBuf,
#[arg(
long = "image-format",
value_name = "FORMAT",
default_value = "qcow2",
help = "Disk image format passed to libguestfs, for example qcow2 or raw; use auto for libguestfs autodetection."
)]
image_format: GuestfsDiskFormat,
#[arg(
long = "output-parent",
value_name = "DIR",
default_value = DEFAULT_SYSROOT_OUTPUT_PARENT
)]
output_parent: PathBuf,
#[arg(
long = "mount",
value_name = "MOUNT",
help = "Manual libguestfs mount, for example /dev/sda3:/; repeatable. Defaults to libguestfs inspection."
)]
mounts: Vec<String>,
#[arg(long, help = "Replace an existing invalid or complete sysroot")]
force: bool,
}
#[derive(Debug, ClapArgs)]
struct CopyOutArgs {
#[arg(long = "image", value_name = "PATH")]
image: PathBuf,
#[arg(
long = "image-format",
value_name = "FORMAT",
default_value = "qcow2",
help = "Disk image format passed to libguestfs, for example qcow2 or raw; use auto for libguestfs autodetection."
)]
image_format: GuestfsDiskFormat,
#[arg(
long = "path",
value_name = "GUEST_PATH",
required = true,
help = "Absolute guest path to copy out; repeatable."
)]
guest_paths: Vec<String>,
#[arg(long = "output", value_name = "DIR")]
output: PathBuf,
#[arg(
long = "mount",
value_name = "MOUNT",
help = "Manual libguestfs mount, for example /dev/sda3:/; repeatable. Defaults to libguestfs inspection."
)]
mounts: Vec<String>,
}
impl SysrootArgs {
fn sysroot_options(self) -> SysrootOptions {
SysrootOptions::new(self.image, self.target, self.output_parent)
.with_disk_format(self.image_format)
.with_mount_mode(mount_mode_from_cli(self.mounts))
.with_force(self.force)
}
}
impl CopyOutArgs {
fn copy_options(self) -> (GuestfsCopyOptions, PathBuf) {
let options = GuestfsCopyOptions::new(self.image, self.guest_paths)
.with_disk_format(self.image_format)
.with_mount_mode(mount_mode_from_cli(self.mounts));
(options, self.output)
}
}
fn mount_mode_from_cli(mounts: Vec<String>) -> GuestfsMountMode {
if mounts.is_empty() {
GuestfsMountMode::Inspect
} else {
GuestfsMountMode::Manual(mounts)
}
}
fn main() {
if let Err(error) = run() {
eprintln!("{error:#}");
process::exit(1);
}
}
fn run() -> Result<()> {
match Args::parse().command {
#[cfg(feature = "bsd")]
Command::Bsd(args) => {
let sysroot = extract_bsd_sysroot(args.sysroot_options())?;
println!("extracted BSD sysroot: {}", sysroot.display());
}
Command::Linux(args) => {
let sysroot = extract_linux_sysroot(args.sysroot_options())?;
println!("extracted Linux sysroot: {}", sysroot.display());
}
Command::CopyOut(args) => {
let (options, output) = args.copy_options();
copy_paths_from_guest(options, &output)?;
println!("copied guest paths to: {}", output.display());
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::Args;
use clap::CommandFactory;
#[test]
fn clap_definition_is_valid() {
Args::command().debug_assert();
}
}