From 34572a1935937a40cb92ea43b231a91c8c1d6b81 Mon Sep 17 00:00:00 2001 From: Shion Tanaka Date: Wed, 6 May 2026 22:56:17 +0900 Subject: [PATCH 1/6] feat(macOS): add vfkit backend for ephemeral and persistent VMs macOS has no KVM/QEMU, so this adds vfkit as the VM backend. Ephemeral VMs use direct kernel boot with SquashFS, persistent VMs use EFI boot. The vfkit/ module mirrors the libvirt/ directory structure, and CLI options match Linux where applicable. Build and run on macOS: cargo build --release codesign -fs - target/release/bcvk Tested on macOS (Apple Silicon) with rootful and rootless podman machine. Assisted-by: Claude Code (Claude Opus 4.6) Signed-off-by: Shion Tanaka --- Cargo.lock | 41 + crates/kit/Cargo.toml | 4 + crates/kit/src/ephemeral_macos.rs | 195 +++++ crates/kit/src/lib.rs | 10 + crates/kit/src/main.rs | 45 +- crates/kit/src/run_ephemeral_macos.rs | 1094 +++++++++++++++++++++++++ crates/kit/src/ssh_options.rs | 136 +++ crates/kit/src/vfkit/inspect.rs | 62 ++ crates/kit/src/vfkit/list.rs | 29 + crates/kit/src/vfkit/mod.rs | 271 ++++++ crates/kit/src/vfkit/rm.rs | 59 ++ crates/kit/src/vfkit/rm_all.rs | 44 + crates/kit/src/vfkit/run.rs | 188 +++++ crates/kit/src/vfkit/ssh.rs | 24 + crates/kit/src/vfkit/start.rs | 115 +++ crates/kit/src/vfkit/stop.rs | 63 ++ 16 files changed, 2370 insertions(+), 10 deletions(-) create mode 100644 crates/kit/src/ephemeral_macos.rs create mode 100644 crates/kit/src/run_ephemeral_macos.rs create mode 100644 crates/kit/src/ssh_options.rs create mode 100644 crates/kit/src/vfkit/inspect.rs create mode 100644 crates/kit/src/vfkit/list.rs create mode 100644 crates/kit/src/vfkit/mod.rs create mode 100644 crates/kit/src/vfkit/rm.rs create mode 100644 crates/kit/src/vfkit/rm_all.rs create mode 100644 crates/kit/src/vfkit/run.rs create mode 100644 crates/kit/src/vfkit/ssh.rs create mode 100644 crates/kit/src/vfkit/start.rs create mode 100644 crates/kit/src/vfkit/stop.rs diff --git a/Cargo.lock b/Cargo.lock index b651d1a79..f18fec2bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -268,6 +268,7 @@ dependencies = [ "xshell", "yaml-rust2", "zlink", + "zstd", ] [[package]] @@ -433,6 +434,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1354349954c6fc9cb0deab020f27f783cf0b604e8bb754dc4658ecf0d29c35f" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] @@ -1598,6 +1601,16 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + [[package]] name = "js-sys" version = "0.3.81" @@ -3878,3 +3891,31 @@ dependencies = [ "tokio-stream", "zlink-core", ] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/crates/kit/Cargo.toml b/crates/kit/Cargo.toml index a5ef6afdc..399f0764f 100644 --- a/crates/kit/Cargo.toml +++ b/crates/kit/Cargo.toml @@ -58,6 +58,10 @@ zlink = "0.4" futures-util = "0.3" libsystemd = "0.7" +# macOS-only dependencies (vfkit backend) +[target.'cfg(target_os = "macos")'.dependencies] +zstd = "0.13" + [dev-dependencies] similar-asserts = "1.5" diff --git a/crates/kit/src/ephemeral_macos.rs b/crates/kit/src/ephemeral_macos.rs new file mode 100644 index 000000000..ca3255247 --- /dev/null +++ b/crates/kit/src/ephemeral_macos.rs @@ -0,0 +1,195 @@ +//! Ephemeral VM management commands for macOS (vfkit backend). + +use std::io::Write; +use std::process::{Command, Stdio}; + +use clap::Subcommand; +use color_eyre::eyre::bail; +use color_eyre::Result; + +use crate::run_ephemeral_macos::{self, EphemeralVmMetadata}; + +/// Options for `ephemeral run-ssh`, combining run options with optional SSH arguments. +#[derive(Debug, clap::Parser)] +pub struct RunSshOpts { + #[command(flatten)] + pub run_opts: run_ephemeral_macos::RunEphemeralOpts, + + /// SSH command to execute (optional, defaults to interactive shell) + #[arg(trailing_var_arg = true)] + pub ssh_args: Vec, +} + +#[derive(Debug, Subcommand)] +pub enum EphemeralCommands { + /// Run bootc containers as ephemeral VMs + #[clap(name = "run")] + Run(run_ephemeral_macos::RunEphemeralOpts), + + /// Run ephemeral VM and SSH into it + #[clap(name = "run-ssh")] + RunSsh(RunSshOpts), + + /// Connect to a running ephemeral VM via SSH + #[clap(name = "ssh")] + Ssh { + /// VM name + name: String, + + /// Additional SSH arguments (e.g. -v, -L, commands to execute) + #[clap(allow_hyphen_values = true)] + args: Vec, + }, + + /// List ephemeral VM containers + #[clap(name = "ps")] + Ps { + /// Output as JSON + #[clap(long)] + json: bool, + }, + + /// Remove all ephemeral VM containers + #[clap(name = "rm-all")] + RmAll { + /// Force removal without confirmation + #[clap(short, long)] + force: bool, + }, +} + +impl EphemeralCommands { + /// Execute the ephemeral subcommand. + pub fn run(self) -> Result<()> { + match self { + EphemeralCommands::Run(opts) => run_ephemeral_macos::run(opts), + EphemeralCommands::RunSsh(mut opts) => { + opts.run_opts.ssh_keygen = true; + if !opts.ssh_args.is_empty() { + let combined = shlex::try_join(opts.ssh_args.iter().map(|s| s.as_str())) + .map_err(|e| color_eyre::eyre::eyre!("failed to escape SSH args: {}", e))?; + opts.run_opts.execute.push(combined); + } + run_ephemeral_macos::run(opts.run_opts) + } + EphemeralCommands::Ssh { name, args } => cmd_ssh(&name, &args), + EphemeralCommands::Ps { json } => cmd_ps(json), + EphemeralCommands::RmAll { force } => cmd_rm_all(force), + } + } +} + +fn cmd_ps(json: bool) -> Result<()> { + let vms = EphemeralVmMetadata::list_all()?; + for vm in &vms { + if !vm.is_alive() { + EphemeralVmMetadata::remove(&vm.name); + } + } + let live: Vec<_> = vms.into_iter().filter(|vm| vm.is_alive()).collect(); + + if json { + println!("{}", serde_json::to_string_pretty(&live)?); + return Ok(()); + } + + if live.is_empty() { + println!("No running ephemeral VMs."); + return Ok(()); + } + + println!("{:<24} {:<50} SSH", "NAME", "IMAGE"); + for vm in &live { + println!( + "{:<24} {:<50} ssh -p {} -i {} root@localhost", + vm.name, vm.image, vm.ssh_port, vm.ssh_key + ); + } + Ok(()) +} + +fn cmd_rm_all(force: bool) -> Result<()> { + let vms = EphemeralVmMetadata::list_all()?; + if vms.is_empty() { + println!("No ephemeral VMs found."); + return Ok(()); + } + + if !force { + println!("Found {} ephemeral VM(s):", vms.len()); + for vm in &vms { + println!( + " {} ({})", + vm.name, + if vm.is_alive() { "running" } else { "stopped" } + ); + } + print!("Remove all ephemeral VMs? [y/N]: "); + std::io::stdout().flush()?; + let mut input = String::new(); + std::io::stdin().read_line(&mut input)?; + let input = input.trim().to_lowercase(); + if input != "y" && input != "yes" { + println!("Aborted."); + return Ok(()); + } + } + + for vm in &vms { + if vm.is_alive() { + if let Err(e) = Command::new("kill") + .args([&vm.pid.to_string()]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + { + tracing::warn!("failed to kill VM process {}: {}", vm.pid, e); + } + if vm.gvproxy_pid > 0 { + if let Err(e) = Command::new("kill") + .args([&vm.gvproxy_pid.to_string()]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + { + tracing::warn!("failed to kill gvproxy {}: {}", vm.gvproxy_pid, e); + } + } + } + EphemeralVmMetadata::remove(&vm.name); + println!("Removed {}", vm.name); + } + Ok(()) +} + +fn cmd_ssh(name: &str, args: &[String]) -> Result<()> { + let vm = EphemeralVmMetadata::load(name)?; + if !vm.is_alive() { + EphemeralVmMetadata::remove(name); + bail!("VM '{}' is not running", name); + } + + // Try to set up SSH port forwarding via VM-specific gvproxy socket + let svc_sock = format!("/private/tmp/bcvk/{}-gvproxy-svc.sock", name); + if std::path::Path::new(&svc_sock).exists() { + if let Err(e) = + run_ephemeral_macos::expose_ssh_port(&svc_sock, "192.168.127.2", vm.ssh_port) + { + tracing::debug!("SSH port forward re-expose: {}", e); + } + } + + let key_path = std::path::Path::new(&vm.ssh_key); + if args.is_empty() { + run_ephemeral_macos::run_ssh_interactive(vm.ssh_port, key_path, "root")?; + } else { + let combined = shlex::try_join(args.iter().map(|s| s.as_str())) + .map_err(|e| color_eyre::eyre::eyre!("failed to escape SSH command: {}", e))?; + let status = + run_ephemeral_macos::run_ssh_command(vm.ssh_port, key_path, "root", &combined)?; + if !status.success() { + std::process::exit(status.code().unwrap_or(1)); + } + } + Ok(()) +} diff --git a/crates/kit/src/lib.rs b/crates/kit/src/lib.rs index 279e5caa5..a3aa51578 100644 --- a/crates/kit/src/lib.rs +++ b/crates/kit/src/lib.rs @@ -4,6 +4,16 @@ pub mod cpio; pub mod qemu_img; pub mod xml_utils; +// Cross-platform modules +pub mod ssh_options; + // Linux-only modules #[cfg(target_os = "linux")] pub mod kernel; + +// macOS-only modules (vfkit backend) +#[cfg(target_os = "macos")] +pub mod run_ephemeral_macos; + +#[cfg(target_os = "macos")] +pub mod vfkit; diff --git a/crates/kit/src/main.rs b/crates/kit/src/main.rs index de0a3107e..cc4969312 100644 --- a/crates/kit/src/main.rs +++ b/crates/kit/src/main.rs @@ -11,6 +11,7 @@ mod cpio; mod install_options; mod instancetypes; mod qemu_img; +mod ssh_options; mod xml_utils; // Linux-only modules @@ -60,6 +61,14 @@ mod utils; #[cfg(target_os = "linux")] mod varlink_ipc; +// macOS-only modules (vfkit backend) +#[cfg(target_os = "macos")] +mod ephemeral_macos; +#[cfg(target_os = "macos")] +mod run_ephemeral_macos; +#[cfg(target_os = "macos")] +mod vfkit; + /// Default state directory for bcvk container data #[cfg(target_os = "linux")] pub const CONTAINER_STATEDIR: &str = "/var/lib/bcvk"; @@ -104,8 +113,8 @@ enum InternalsCmds { DumpCliJson, } -/// Stub subcommands for macOS (shows error message when run) -#[cfg(not(target_os = "linux"))] +/// Stub subcommands for unsupported platforms +#[cfg(not(any(target_os = "linux", target_os = "macos")))] #[derive(Debug, Subcommand)] pub enum StubEphemeralCommands { /// Run bootc containers as ephemeral VMs @@ -139,9 +148,21 @@ enum Commands { #[clap(subcommand)] Ephemeral(ephemeral::EphemeralCommands), - // macOS stub: ephemeral command exists but errors out - #[cfg(not(target_os = "linux"))] - /// Run bootc images as stateless VMs via QEMU+Podman (not available on this platform) + // macOS: vfkit-based ephemeral VMs + #[cfg(target_os = "macos")] + /// Manage ephemeral VMs for bootc containers (vfkit backend) + #[clap(subcommand)] + Ephemeral(ephemeral_macos::EphemeralCommands), + + // macOS: vfkit-based persistent VMs + #[cfg(target_os = "macos")] + /// Manage persistent VMs (vfkit backend) + #[clap(subcommand)] + Vm(vfkit::VmCommands), + + // Other platforms: stub + #[cfg(not(any(target_os = "linux", target_os = "macos")))] + /// Manage ephemeral VMs for bootc containers (not available on this platform) #[clap(subcommand)] Ephemeral(StubEphemeralCommands), @@ -284,13 +305,17 @@ fn main() -> Result<(), Report> { #[cfg(target_os = "linux")] Commands::Ephemeral(cmd) => cmd.run()?, - // macOS stub: ephemeral command exists but errors out - #[cfg(not(target_os = "linux"))] + #[cfg(target_os = "macos")] + Commands::Ephemeral(cmd) => cmd.run()?, + + #[cfg(target_os = "macos")] + Commands::Vm(cmd) => cmd.run()?, + + #[cfg(not(any(target_os = "linux", target_os = "macos")))] Commands::Ephemeral(_) => { return Err(color_eyre::eyre::eyre!( - "The 'ephemeral' command is not available on macOS.\n\ - bcvk requires Linux with KVM/QEMU for VM operations.\n\ - See https://github.com/bootc-dev/bcvk/issues/21 for more information." + "The 'ephemeral' command is not available on this platform.\n\ + bcvk requires Linux with KVM/QEMU or macOS with vfkit for VM operations." )); } diff --git a/crates/kit/src/run_ephemeral_macos.rs b/crates/kit/src/run_ephemeral_macos.rs new file mode 100644 index 000000000..d7fe9257f --- /dev/null +++ b/crates/kit/src/run_ephemeral_macos.rs @@ -0,0 +1,1094 @@ +//! Ephemeral VM launch flow for macOS using vfkit + SquashFS. +//! +//! Boot flow: +//! 1. Extract kernel + initramfs from container image +//! 2. Create SquashFS rootfs (lz4, cached by digest) +//! 3. Decompress vmlinuz PE+zstd → uncompressed ARM64 Image +//! 4. Append bcvk units CPIO to initramfs (/etc overlay + /var tmpfs + SSH) +//! 5. Launch vfkit with virtio-blk (SquashFS) + virtio-net (gvproxy) +//! +//! Common helpers (gvproxy, SSH, vfkit detection) are pub for reuse by vfkit/ module. + +use std::fs::{self, OpenOptions}; +use std::io::{Seek, SeekFrom, Write}; +use std::os::unix::net::UnixStream; +use std::path::Path; +use std::process::{Command, Stdio}; +use std::time::Duration; + +use color_eyre::{ + eyre::{bail, eyre, Context}, + Result, +}; +use tracing::{debug, info}; + +// --- Data structures --- + +/// Metadata for a running ephemeral VM, persisted as JSON for `ps` and `ssh`. +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] +#[allow(dead_code)] +pub struct EphemeralVmMetadata { + /// VM name used as identifier for resource isolation. + pub name: String, + /// Container image reference used to boot the VM. + pub image: String, + /// PID of the vfkit process. + pub pid: u32, + /// PID of the gvproxy network proxy process. + pub gvproxy_pid: u32, + /// Host-side SSH port forwarded to the VM. + pub ssh_port: u16, + /// Path to the SSH private key for this VM. + pub ssh_key: String, + /// Path to the serial console log file. + pub serial_log: String, + /// Path to the vfkit process log file. + pub log_path: Option, + /// ISO 8601 timestamp when the VM was created. + pub created: String, +} + +#[allow(dead_code)] +impl EphemeralVmMetadata { + /// Return the directory path for ephemeral VM metadata files. + pub fn vms_dir() -> std::path::PathBuf { + std::path::PathBuf::from("/private/tmp/bcvk/vms") + } + + /// Save metadata to a JSON file in the VMs directory. + pub fn save(&self) -> Result<()> { + let dir = Self::vms_dir(); + fs::create_dir_all(&dir)?; + let path = dir.join(format!("{}.json", self.name)); + fs::write(&path, serde_json::to_string_pretty(self)?)?; + Ok(()) + } + + /// Remove metadata file for the named VM. + pub fn remove(name: &str) { + let path = Self::vms_dir().join(format!("{}.json", name)); + let _ = fs::remove_file(path); + } + + /// Load metadata for the named VM from its JSON file. + pub fn load(name: &str) -> Result { + let path = Self::vms_dir().join(format!("{}.json", name)); + let data = fs::read_to_string(&path)?; + Ok(serde_json::from_str(&data)?) + } + + /// List all ephemeral VM metadata from the VMs directory. + pub fn list_all() -> Result> { + let dir = Self::vms_dir(); + if !dir.exists() { + return Ok(Vec::new()); + } + let mut vms = Vec::new(); + for entry in fs::read_dir(&dir)? { + let path = entry?.path(); + if path.extension().and_then(|e| e.to_str()) != Some("json") { + continue; + } + if let Ok(data) = fs::read_to_string(&path) { + if let Ok(meta) = serde_json::from_str::(&data) { + vms.push(meta); + } + } + } + Ok(vms) + } + + /// Check if the VM process is still alive via kill -0. + pub fn is_alive(&self) -> bool { + Command::new("kill") + .args(["-0", &self.pid.to_string()]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .map(|s| s.success()) + .unwrap_or(false) + } +} + +/// Options for launching an ephemeral VM via vfkit. +#[derive(clap::Parser, Debug)] +pub struct RunEphemeralOpts { + /// Container image to boot + pub image: String, + /// Number of vCPUs + #[clap(long)] + pub vcpus: Option, + /// Memory size (e.g. "4G", "2048M", or plain number for MB) + #[clap(long, default_value = "4G")] + pub memory: String, + /// Generate a temporary SSH key pair for VM access + #[clap(long = "ssh-keygen", short = 'K')] + pub ssh_keygen: bool, + /// Command(s) to execute via SSH after boot + #[clap(long)] + pub execute: Vec, + /// VM name for identification and resource isolation + #[clap(long)] + pub name: Option, + /// Additional kernel command line arguments + #[clap(long = "karg")] + pub kernel_args: Vec, + /// Display VM console in GUI window + #[clap(long)] + pub gui: bool, + /// Run in background + #[clap(long, short = 'd')] + pub detach: bool, + /// Enable debug mode (reserved for future use) + #[clap(long)] + pub debug: bool, +} + +fn default_vcpus() -> u32 { + std::thread::available_parallelism() + .map(|n| n.get() as u32) + .unwrap_or(2) +} + +/// Parse memory specification string (e.g. "4G", "2048M") to megabytes. +pub fn parse_memory_to_mb(s: &str) -> Result { + let s = s.trim(); + if let Some(n) = s.strip_suffix('G').or_else(|| s.strip_suffix('g')) { + Ok((n.parse::()? * 1024.0) as u32) + } else if let Some(n) = s.strip_suffix('M').or_else(|| s.strip_suffix('m')) { + Ok(n.parse::()? as u32) + } else { + Ok(s.parse::()?) + } +} + +// --- RAII cleanup guard --- + +struct VmCleanup { + vfkit_pid: u32, + gvproxy_pid: u32, + vm_name: String, +} + +impl Drop for VmCleanup { + fn drop(&mut self) { + tracing::debug!("cleaning up VM processes..."); + if let Err(e) = Command::new("kill") + .arg(self.vfkit_pid.to_string()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + { + tracing::warn!("failed to kill vfkit (PID {}): {}", self.vfkit_pid, e); + } + if let Err(e) = Command::new("kill") + .arg(self.gvproxy_pid.to_string()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + { + tracing::warn!("failed to kill gvproxy (PID {}): {}", self.gvproxy_pid, e); + } + EphemeralVmMetadata::remove(&self.vm_name); + } +} + +// --- Main entry point --- + +/// Run an ephemeral VM from a container image using vfkit + SquashFS. +pub fn run(opts: RunEphemeralOpts) -> Result<()> { + if opts.gui && opts.detach { + bail!("--gui and --detach cannot be used together (GUI requires foreground process)"); + } + + if opts.detach { + return run_detached(&opts); + } + + let vfkit_bin = find_vfkit()?; + info!(image = %opts.image, "starting ephemeral VM on macOS (vfkit + SquashFS)"); + + let cache_base = std::path::PathBuf::from("/private/tmp/bcvk"); + fs::create_dir_all(&cache_base)?; + + let machine = detect_machine_name()?; + let rootful = is_machine_rootful(&machine); + debug!( + "podman machine '{}' ({})", + machine, + if rootful { "rootful" } else { "rootless" } + ); + let digest = ensure_image_and_get_digest(&opts.image)?; + let digest_short = &digest[..16.min(digest.len())]; + info!("image digest: {}...", digest_short); + + let vm_name = opts + .name + .clone() + .unwrap_or_else(|| format!("ephemeral-{}", &digest_short[..8])); + let ssh_key_path = cache_base.join(format!("{}-key", vm_name)); + + let boot_dir = cache_base.join(format!("boot-{}", digest_short)); + fs::create_dir_all(&boot_dir)?; + let squashfs_cache = format!("/private/tmp/bcvk/rootfs-{}.squashfs", digest_short); + let squashfs_path = format!("/private/tmp/bcvk/{}-rootfs.squashfs", vm_name); + let vmlinuz_path = boot_dir.join("vmlinuz"); + let image_path = boot_dir.join("Image"); + let initramfs_orig = boot_dir.join("initramfs-orig.img"); + let initramfs_path = cache_base.join(format!("{}-initramfs.img", vm_name)); + + // Step 1+2: kernel extract + SquashFS creation (parallel) + let step2_handle = if !Path::new(&squashfs_cache).exists() { + let mc = machine.clone(); + let rf = rootful; + let img = opts.image.clone(); + let sc = squashfs_cache.clone(); + Some(std::thread::spawn(move || -> Result<()> { + info!("creating SquashFS image (lz4)..."); + create_squashfs_image(&mc, rf, &img, &sc) + })) + } else { + info!("using cached SquashFS: {}", squashfs_cache); + None + }; + + if !vmlinuz_path.exists() || !initramfs_orig.exists() { + info!("extracting kernel and initramfs..."); + extract_kernel(&machine, &opts.image, &boot_dir)?; + fs::rename(boot_dir.join("initramfs.img"), &initramfs_orig)?; + } + + // Step 3+4: kernel decompress + CPIO append (parallel after Step 1) + let step3_handle = if !image_path.exists() { + let vp = vmlinuz_path.clone(); + let ip = image_path.clone(); + Some(std::thread::spawn(move || -> Result<()> { + info!("decompressing kernel (vmlinuz → Image)..."); + extract_uncompressed_kernel(&vp, &ip) + })) + } else { + None + }; + + fs::copy(&initramfs_orig, &initramfs_path)?; + { + let cpio_data = crate::cpio::create_initramfs_units_cpio() + .map_err(|e| eyre!("failed to create CPIO: {e}"))?; + let mut f = OpenOptions::new().append(true).open(&initramfs_path)?; + let sz = f.seek(SeekFrom::End(0))?; + let pad = sz.next_multiple_of(4) - sz; + if pad > 0 { + f.write_all(&vec![0u8; pad as usize])?; + } + f.write_all(&cpio_data)?; + + if opts.ssh_keygen || !opts.execute.is_empty() { + info!("generating SSH keypair..."); + let _ = fs::remove_file(&ssh_key_path); + let _ = fs::remove_file(ssh_key_path.with_extension("pub")); + let status = Command::new("ssh-keygen") + .args([ + "-t", + "ed25519", + "-f", + &ssh_key_path.to_string_lossy(), + "-N", + "", + "-q", + ]) + .status()?; + if !status.success() { + bail!("ssh-keygen failed (exit code: {:?})", status.code()); + } + let pubkey = fs::read_to_string(ssh_key_path.with_extension("pub"))?; + let ssh_cpio = create_ssh_setup_cpio(pubkey.trim())?; + let pos = f.seek(SeekFrom::End(0))?; + let pad = pos.next_multiple_of(4) - pos; + if pad > 0 { + f.write_all(&vec![0u8; pad as usize])?; + } + f.write_all(&ssh_cpio)?; + } + info!("initramfs prepared"); + } + + if let Some(h) = step3_handle { + h.join() + .map_err(|_| eyre!("kernel decompression thread panicked"))??; + } + if let Some(h) = step2_handle { + h.join() + .map_err(|_| eyre!("squashfs creation thread panicked"))??; + } + + // CoW clone SquashFS for this VM (allows concurrent use of same image) + let _ = fs::remove_file(&squashfs_path); + let clone_status = Command::new("cp") + .args(["-c", &squashfs_cache, &squashfs_path]) + .status() + .context("cloning SquashFS")?; + if !clone_status.success() { + fs::copy(&squashfs_cache, &squashfs_path).context("copying SquashFS")?; + } + + // 5. gvproxy + vfkit + let gvproxy_sock = cache_base.join(format!("{}-gvproxy.sock", vm_name)); + let services_sock = cache_base.join(format!("{}-gvproxy-svc.sock", vm_name)); + let gvproxy_sock_str = gvproxy_sock.to_string_lossy().to_string(); + let services_sock_str = services_sock.to_string_lossy().to_string(); + info!("starting gvproxy..."); + let mut gvproxy_child = start_gvproxy(&gvproxy_sock_str, &services_sock_str)?; + + let mut cmdline_parts: Vec<&str> = vec![ + "root=/dev/vda", + "ro", + "rootfstype=squashfs", + "console=tty0", + "console=hvc0", + "loglevel=4", + "selinux=0", + "net.ifnames=0", + "systemd.journald.storage=volatile", + ]; + let user_args: Vec<&str> = opts.kernel_args.iter().map(|s| s.as_str()).collect(); + cmdline_parts.extend(&user_args); + let cmdline = cmdline_parts.join(" "); + + let mac = generate_mac(); + let mac_str = format!( + "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] + ); + + let bootloader_arg = format!( + "linux,kernel={},initrd={},cmdline=\"{}\"", + image_path.display(), + initramfs_path.display(), + cmdline + ); + + let vcpus = opts.vcpus.unwrap_or_else(default_vcpus); + let memory_mb = parse_memory_to_mb(&opts.memory)?; + + let mut vfkit_args = vec![ + "--cpus".to_string(), + vcpus.to_string(), + "--memory".to_string(), + memory_mb.to_string(), + "--bootloader".to_string(), + bootloader_arg, + "--device".to_string(), + format!("virtio-blk,path={}", squashfs_path), + "--device".to_string(), + format!( + "virtio-net,unixSocketPath={},mac={}", + gvproxy_sock_str, mac_str + ), + "--device".to_string(), + "virtio-rng".to_string(), + ]; + if opts.gui { + vfkit_args.push("--gui".to_string()); + } + + info!("launching vfkit..."); + let vfkit_log = cache_base.join(format!("{}-vfkit.log", vm_name)); + let vfkit_log_file = fs::File::create(&vfkit_log)?; + let mut vfkit_child = Command::new(&vfkit_bin) + .args(&vfkit_args) + .stdout(vfkit_log_file.try_clone()?) + .stderr(vfkit_log_file) + .spawn() + .context("failed to start vfkit")?; + + let ssh_port = find_available_ssh_port(); + debug!("allocated SSH port: {}", ssh_port); + + let metadata = EphemeralVmMetadata { + name: vm_name.clone(), + image: opts.image.clone(), + pid: vfkit_child.id(), + gvproxy_pid: gvproxy_child.id(), + ssh_port, + ssh_key: ssh_key_path.to_string_lossy().to_string(), + serial_log: String::new(), + log_path: None, + created: chrono::Utc::now().to_rfc3339(), + }; + metadata.save()?; + + let _cleanup = VmCleanup { + vfkit_pid: vfkit_child.id(), + gvproxy_pid: gvproxy_child.id(), + vm_name: vm_name.clone(), + }; + + if opts.ssh_keygen || !opts.execute.is_empty() { + info!("setting up SSH port forwarding..."); + for attempt in 0..15u32 { + match expose_ssh_port(&services_sock_str, "192.168.127.2", ssh_port) { + Ok(_) => { + info!("SSH port {} forwarded", ssh_port); + break; + } + Err(e) if attempt < 14 => { + debug!("SSH port forward attempt {}: {}", attempt, e); + let backoff = 200 * 2u64.pow(attempt.min(4)); + std::thread::sleep(Duration::from_millis(backoff)); + } + Err(e) => bail!("SSH port forward failed: {}", e), + } + } + + wait_for_ssh(ssh_port, &ssh_key_path, "root")?; + + if !opts.execute.is_empty() { + for cmd_str in &opts.execute { + info!("executing: {}", cmd_str); + let status = run_ssh_command(ssh_port, &ssh_key_path, "root", cmd_str)?; + if !status.success() { + bail!("command failed: {}", status); + } + } + return Ok(()); + } + + info!( + "SSH ready: ssh -p {} -i {} root@localhost", + ssh_port, + ssh_key_path.display() + ); + + use std::io::IsTerminal; + if std::io::stdin().is_terminal() { + let status = run_ssh_interactive(ssh_port, &ssh_key_path, "root")?; + let exit_code = status.code().unwrap_or(1); + drop(_cleanup); + std::process::exit(exit_code); + } + } + + // No SSH: wait for vfkit to exit (GUI window closed or VM shutdown) + std::mem::forget(_cleanup); + let status = vfkit_child.wait()?; + info!("vfkit exited: {}", status); + if let Err(e) = gvproxy_child.kill() { + tracing::debug!("failed to kill gvproxy: {}", e); + } + EphemeralVmMetadata::remove(&vm_name); + Ok(()) +} + +fn run_detached(opts: &RunEphemeralOpts) -> Result<()> { + let cache_base = std::path::PathBuf::from("/private/tmp/bcvk"); + fs::create_dir_all(&cache_base)?; + let digest = ensure_image_and_get_digest(&opts.image)?; + let digest_short = &digest[..16.min(digest.len())]; + let vm_name = opts + .name + .clone() + .unwrap_or_else(|| format!("ephemeral-{}", &digest_short[..8])); + let log_path = cache_base.join(format!("bcvk-{}.log", vm_name)); + let log_file = fs::File::create(&log_path)?; + + let exe = std::env::current_exe()?; + let mut args: Vec = std::env::args() + .skip(1) + .filter(|a| a != "--detach" && a != "-d") + .collect(); + if !args.contains(&"-K".to_string()) && !args.contains(&"--ssh-keygen".to_string()) { + args.insert(args.len() - 1, "-K".to_string()); + } + if opts.name.is_none() { + args.insert(args.len() - 1, "--name".to_string()); + args.insert(args.len() - 1, vm_name.clone()); + } + + let child = Command::new(exe) + .args(&args) + .stdin(Stdio::null()) + .stdout(log_file.try_clone()?) + .stderr(log_file) + .spawn()?; + + let metadata = EphemeralVmMetadata { + name: vm_name.clone(), + image: opts.image.clone(), + pid: child.id(), + gvproxy_pid: 0, + ssh_port: 0, + ssh_key: cache_base + .join(format!("{}-key", vm_name)) + .to_string_lossy() + .to_string(), + serial_log: String::new(), + log_path: Some(log_path.to_string_lossy().to_string()), + created: chrono::Utc::now().to_rfc3339(), + }; + metadata.save()?; + println!("{}", vm_name); + Ok(()) +} + +// --- SSH setup CPIO --- + +fn create_ssh_setup_cpio(pubkey: &str) -> Result> { + use cpio::newc::Builder as NewcBuilder; + let mut buf = Vec::new(); + + let script = format!( + "#!/bin/bash\n\ + mkdir -p /sysroot/var/roothome/.ssh\n\ + chmod 700 /sysroot/var/roothome/.ssh\n\ + echo '{}' > /sysroot/var/roothome/.ssh/authorized_keys\n\ + chmod 600 /sysroot/var/roothome/.ssh/authorized_keys\n\ + chown -R 0:0 /sysroot/var/roothome/.ssh\n", + pubkey + ); + + let service = "[Unit]\n\ + Description=Setup SSH authorized_keys for root\n\ + DefaultDependencies=no\n\ + ConditionPathExists=/etc/initrd-release\n\ + Before=initrd-fs.target\n\ + After=bcvk-var-ephemeral.service\n\ + Requires=bcvk-var-ephemeral.service\n\ + \n\ + [Service]\n\ + Type=oneshot\n\ + RemainAfterExit=yes\n\ + ExecStart=/usr/bin/bash /usr/lib/bcvk/setup-ssh.sh\n"; + + let dropin = "[Unit]\nWants=bcvk-ssh-setup.service\n"; + + let write_entry = + |buf: &mut Vec, path: &str, data: &[u8], executable: bool| -> std::io::Result<()> { + let mode = if executable { 0o100755 } else { 0o100644 }; + let builder = NewcBuilder::new(path).mode(mode).uid(0).gid(0); + let mut writer = builder.write(buf, data.len() as u32); + writer.write_all(data)?; + writer.finish()?; + Ok(()) + }; + + let write_dir = |buf: &mut Vec, path: &str| -> std::io::Result<()> { + NewcBuilder::new(path) + .mode(0o040755) + .uid(0) + .gid(0) + .write(buf, 0) + .finish()?; + Ok(()) + }; + + write_dir(&mut buf, "usr/lib/bcvk")?; + write_entry( + &mut buf, + "usr/lib/bcvk/setup-ssh.sh", + script.as_bytes(), + true, + )?; + write_entry( + &mut buf, + "usr/lib/systemd/system/bcvk-ssh-setup.service", + service.as_bytes(), + false, + )?; + write_entry( + &mut buf, + "usr/lib/systemd/system/initrd-fs.target.d/bcvk-ssh-setup.conf", + dropin.as_bytes(), + false, + )?; + cpio::newc::trailer(&mut buf).map_err(|e| eyre!("cpio trailer: {e}"))?; + Ok(buf) +} + +// --- vfkit kernel decompression --- + +fn extract_uncompressed_kernel(vmlinuz_path: &Path, output_path: &Path) -> Result<()> { + let data = fs::read(vmlinuz_path)?; + + // Parse zboot header: offset 0x08 = payload_offset (le32), 0x0c = payload_size (le32) + let (pos, payload_end) = if data.len() >= 16 && &data[4..8] == b"zimg" { + let payload_offset = u32::from_le_bytes(data[8..12].try_into().unwrap()) as usize; + let payload_size = u32::from_le_bytes(data[12..16].try_into().unwrap()) as usize; + if payload_offset + payload_size > data.len() { + bail!("zboot payload extends beyond file"); + } + info!( + "zboot header: payload at 0x{:x}, size 0x{:x}", + payload_offset, payload_size + ); + (payload_offset, payload_offset + payload_size) + } else { + let magic = [0x28u8, 0xb5, 0x2f, 0xfd]; + let p = data + .windows(4) + .position(|w| w == magic) + .ok_or_else(|| eyre!("zstd magic not found in vmlinuz"))?; + info!("zstd payload at offset 0x{:x} (no zboot header)", p); + (p, data.len()) + }; + + let mut kernel = Vec::new(); + zstd::stream::copy_decode(&data[pos..payload_end], &mut kernel) + .context("decompressing zstd payload from vmlinuz")?; + + if kernel.len() < 0x3c || &kernel[0x38..0x3c] != b"ARMd" { + bail!("decompressed kernel is not a valid ARM64 Image"); + } + fs::write(output_path, &kernel)?; + info!("decompressed kernel: {} bytes (ARM64 Image)", kernel.len()); + Ok(()) +} + +// --- Shared helpers (pub for vfkit/ module) --- + +fn detect_machine_name() -> Result { + let output = Command::new("podman") + .args(["machine", "info", "--format", "{{.Host.CurrentMachine}}"]) + .output()?; + let name = String::from_utf8_lossy(&output.stdout).trim().to_string(); + if name.is_empty() { + bail!("no podman machine is running"); + } + Ok(name) +} + +fn ensure_image_and_get_digest(image: &str) -> Result { + let status = Command::new("podman") + .args(["image", "exists", image]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status()?; + if !status.success() { + info!("pulling image {}...", image); + if !Command::new("podman") + .args(["pull", image]) + .status()? + .success() + { + bail!("failed to pull image: {}", image); + } + } + let output = Command::new("podman") + .args(["image", "inspect", "--format", "{{.Digest}}", image]) + .output()?; + let digest = String::from_utf8_lossy(&output.stdout).trim().to_string(); + Ok(digest.trim_start_matches("sha256:").to_string()) +} + +fn extract_kernel(machine: &str, image: &str, boot_dir: &Path) -> Result<()> { + let boot_dir_str = boot_dir.to_string_lossy(); + let script = format!( + "KVER=$(podman run --rm {image} ls /usr/lib/modules/ | head -1) && \ + [ -n \"$KVER\" ] && \ + podman run --rm {image} cat /usr/lib/modules/$KVER/vmlinuz > {boot}/vmlinuz && \ + podman run --rm {image} cat /usr/lib/modules/$KVER/initramfs.img > {boot}/initramfs.img", + image = image, + boot = boot_dir_str + ); + let output = Command::new("podman") + .args(["machine", "ssh", machine, &script]) + .output() + .context("extracting kernel from container image")?; + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + bail!( + "No kernel found in image '{}'.\n\ + Checked: /usr/lib/modules//vmlinuz + initramfs.img\n\ + This image may not be a bootable container (bootc) image.\n\ + {}", + image, + stderr.trim() + ); + } + Ok(()) +} + +fn is_machine_rootful(machine: &str) -> bool { + Command::new("podman") + .args(["machine", "ssh", machine, "id", "-u"]) + .output() + .map(|o| String::from_utf8_lossy(&o.stdout).trim() == "0") + .unwrap_or(false) +} + +fn create_squashfs_image( + machine: &str, + rootful: bool, + image: &str, + output_path: &str, +) -> Result<()> { + let script = if rootful { + format!( + "MERGED=$(podman image mount {}) && \ + mksquashfs $MERGED {} -noappend -comp lz4 -b 1M -quiet", + image, output_path + ) + } else { + info!("rootless mode: using podman unshare for SquashFS creation"); + format!( + "podman unshare sh -c 'MERGED=$(podman image mount {}) && \ + mksquashfs $MERGED {} -noappend -comp lz4 -b 1M -quiet'", + image, output_path + ) + }; + + let output = Command::new("podman") + .args(["machine", "ssh", machine, &script]) + .output() + .context("running mksquashfs")?; + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + bail!("mksquashfs failed: {}", stderr.trim()); + } + Ok(()) +} + +/// Clear extended attributes from a file. +/// +/// Apple Virtualization.framework rejects disk images with xattrs like +/// `security.selinux` or `user.containers.override_stat` that are added +/// by podman/buildah when creating images inside containers. +pub fn clear_xattr(path: &Path) { + let _ = Command::new("xattr") + .args(["-c", &path.to_string_lossy()]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status(); +} + +/// Find the vfkit binary, checking PATH and Podman PKG location. +pub fn find_vfkit() -> Result { + if let Ok(path) = which::which("vfkit") { + return Ok(path.to_string_lossy().to_string()); + } + let podman_path = "/opt/podman/bin/vfkit"; + if Path::new(podman_path).exists() { + return Ok(podman_path.to_string()); + } + bail!("vfkit not found. Install: brew install vfkit") +} + +/// Fixed MAC address matching gvproxy's DHCP static lease for 192.168.127.2. +const GVPROXY_STATIC_MAC: [u8; 6] = [0x5a, 0x94, 0xef, 0xe4, 0x0c, 0xee]; + +/// Generate the fixed MAC address for gvproxy DHCP static lease. +pub fn generate_mac() -> [u8; 6] { + GVPROXY_STATIC_MAC +} + +/// Find the gvproxy binary, checking PATH and Podman installation paths. +fn find_gvproxy() -> Result { + if let Ok(path) = which::which("gvproxy") { + return Ok(path.to_string_lossy().to_string()); + } + for candidate in [ + "/opt/homebrew/opt/podman/libexec/podman/gvproxy", + "/opt/podman/bin/gvproxy", + ] { + if Path::new(candidate).exists() { + return Ok(candidate.to_string()); + } + } + bail!("gvproxy not found. Ensure Podman is installed (brew install podman)") +} + +/// Start a gvproxy instance with the given socket paths. +pub fn start_gvproxy(gvproxy_sock: &str, services_sock: &str) -> Result { + let gvproxy_bin = find_gvproxy()?; + let _ = fs::remove_file(gvproxy_sock); + let _ = fs::remove_file(services_sock); + let child = Command::new(&gvproxy_bin) + .args([ + "-listen-vfkit", + &format!("unixgram://{}", gvproxy_sock), + "-ssh-port", + "-1", + "-services", + &format!("unix://{}", services_sock), + ]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .context("failed to start gvproxy. Ensure gvproxy is installed (included in Podman)")?; + for _ in 0..50 { + if Path::new(gvproxy_sock).exists() { + break; + } + std::thread::sleep(Duration::from_millis(100)); + } + if !Path::new(gvproxy_sock).exists() { + bail!("gvproxy socket did not appear"); + } + Ok(child) +} + +/// Expose SSH port forwarding via gvproxy's HTTP API. +pub fn expose_ssh_port(services_sock: &str, vm_ip: &str, host_port: u16) -> Result<()> { + let body = format!( + r#"{{"local":":{}","remote":"{}:22","protocol":"tcp"}}"#, + host_port, vm_ip + ); + let mut stream = UnixStream::connect(services_sock)?; + let request = format!( + "POST /services/forwarder/expose HTTP/1.1\r\nHost: unix\r\n\ + Content-Type: application/json\r\nContent-Length: {}\r\n\r\n{}", + body.len(), + body + ); + std::io::Write::write_all(&mut stream, request.as_bytes())?; + std::io::Write::flush(&mut stream)?; + let mut response = vec![0u8; 1024]; + let _ = std::io::Read::read(&mut stream, &mut response); + let response_str = String::from_utf8_lossy(&response); + if !response_str.contains("200") { + bail!( + "gvproxy expose failed: {}", + response_str.trim_end_matches('\0') + ); + } + Ok(()) +} + +const SSH_TIMEOUT: Duration = Duration::from_secs(240); + +/// Find an available TCP port for SSH forwarding in range 2222-3000. +pub fn find_available_ssh_port() -> u16 { + use rand::Rng; + let mut rng = rand::rng(); + const PORT_RANGE_START: u16 = 2222; + const PORT_RANGE_END: u16 = 3000; + for _ in 0..100 { + let port = rng.random_range(PORT_RANGE_START..PORT_RANGE_END); + if std::net::TcpListener::bind(("127.0.0.1", port)).is_ok() { + return port; + } + } + for port in PORT_RANGE_START..PORT_RANGE_END { + if std::net::TcpListener::bind(("127.0.0.1", port)).is_ok() { + return port; + } + } + PORT_RANGE_START +} + +/// Wait for SSH connectivity with exponential backoff (240s timeout). +pub fn wait_for_ssh(port: u16, key_path: &Path, user: &str) -> Result<()> { + use crate::ssh_options::CommonSshOptions; + let ssh_opts = CommonSshOptions::default(); + let user_host = format!("{}@localhost", user); + info!("waiting for SSH on port {} ({}@localhost)...", port, user); + let start = std::time::Instant::now(); + let mut attempt = 0u32; + loop { + if start.elapsed() > SSH_TIMEOUT { + bail!("SSH connection timeout ({}s)", SSH_TIMEOUT.as_secs()); + } + let mut cmd = Command::new("ssh"); + cmd.args(["-p", &port.to_string(), "-i", &key_path.to_string_lossy()]); + ssh_opts.apply_to_command(&mut cmd); + cmd.args(["-o", "BatchMode=yes", &user_host, "true"]); + let status = cmd.stdout(Stdio::null()).stderr(Stdio::null()).status(); + if let Ok(s) = status { + if s.success() { + info!("SSH connected after {}s", start.elapsed().as_secs()); + return Ok(()); + } + } + let backoff = if attempt < 2 { + 500 + } else if attempt < 4 { + 1000 + } else { + 2000 + }; + std::thread::sleep(Duration::from_millis(backoff)); + attempt += 1; + } +} + +/// Execute a command via SSH and return the exit status. +pub fn run_ssh_command( + port: u16, + key_path: &Path, + user: &str, + command: &str, +) -> Result { + use crate::ssh_options::CommonSshOptions; + let ssh_opts = CommonSshOptions::default(); + let user_host = format!("{}@localhost", user); + let mut cmd = Command::new("ssh"); + cmd.args(["-p", &port.to_string(), "-i", &key_path.to_string_lossy()]); + ssh_opts.apply_to_command(&mut cmd); + cmd.args(["-o", "BatchMode=yes", &user_host, command]); + cmd.stdin(Stdio::inherit()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .status() + .map_err(|e| eyre!("ssh failed: {}", e)) +} + +/// Start an interactive SSH session with TTY allocation. +pub fn run_ssh_interactive( + port: u16, + key_path: &Path, + user: &str, +) -> Result { + use crate::ssh_options::CommonSshOptions; + let ssh_opts = CommonSshOptions::default(); + let user_host = format!("{}@localhost", user); + let mut cmd = Command::new("ssh"); + cmd.args(["-p", &port.to_string(), "-i", &key_path.to_string_lossy()]); + ssh_opts.apply_to_command(&mut cmd); + cmd.args(["-t", &user_host]); + cmd.stdin(Stdio::inherit()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .status() + .map_err(|e| eyre!("ssh failed: {}", e)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_memory_to_mb() { + let cases = [ + ("4G", 4096), + ("4g", 4096), + ("2048M", 2048), + ("2048m", 2048), + ("512", 512), + ("1G", 1024), + ]; + for (input, expected) in &cases { + assert_eq!( + parse_memory_to_mb(input).unwrap(), + *expected, + "parse_memory_to_mb({:?})", + input + ); + } + } + + #[test] + fn test_parse_memory_to_mb_errors() { + assert!(parse_memory_to_mb("").is_err()); + assert!(parse_memory_to_mb("abc").is_err()); + } + + #[test] + fn test_generate_mac() { + let mac = generate_mac(); + assert_eq!(mac, GVPROXY_STATIC_MAC); + } + + #[test] + fn test_default_vcpus() { + let vcpus = default_vcpus(); + assert!(vcpus >= 1); + assert_eq!( + vcpus, + std::thread::available_parallelism() + .map(|n| n.get() as u32) + .unwrap_or(2) + ); + } + + #[test] + fn test_find_available_ssh_port() { + let port = find_available_ssh_port(); + assert!((2222..3000).contains(&port)); + assert!(std::net::TcpListener::bind(("127.0.0.1", port)).is_ok()); + } + + #[test] + fn test_ephemeral_vm_metadata_roundtrip() { + let meta = EphemeralVmMetadata { + name: "test-vm".to_string(), + image: "quay.io/fedora/fedora-bootc:42".to_string(), + pid: 12345, + gvproxy_pid: 12346, + ssh_port: 2222, + ssh_key: "/tmp/test-key".to_string(), + serial_log: "/tmp/test-serial.log".to_string(), + log_path: Some("/tmp/test-vfkit.log".to_string()), + created: "2026-01-01T00:00:00Z".to_string(), + }; + let json = serde_json::to_string_pretty(&meta).unwrap(); + let loaded: EphemeralVmMetadata = serde_json::from_str(&json).unwrap(); + assert_eq!(loaded.name, "test-vm"); + assert_eq!(loaded.image, "quay.io/fedora/fedora-bootc:42"); + assert_eq!(loaded.pid, 12345); + assert_eq!(loaded.ssh_port, 2222); + assert_eq!(loaded.log_path.as_deref(), Some("/tmp/test-vfkit.log")); + } + + #[test] + fn test_ephemeral_vm_metadata_save_load_remove() { + let dir = tempfile::tempdir().unwrap(); + let json_path = dir.path().join("roundtrip-vm.json"); + let meta = EphemeralVmMetadata { + name: "roundtrip-vm".to_string(), + image: "localhost/test:latest".to_string(), + pid: 999, + gvproxy_pid: 1000, + ssh_port: 2250, + ssh_key: "/tmp/key".to_string(), + serial_log: "/tmp/serial.log".to_string(), + log_path: None, + created: "2026-05-04T00:00:00Z".to_string(), + }; + fs::write(&json_path, serde_json::to_string_pretty(&meta).unwrap()).unwrap(); + let data = fs::read_to_string(&json_path).unwrap(); + let loaded: EphemeralVmMetadata = serde_json::from_str(&data).unwrap(); + assert_eq!(loaded.name, "roundtrip-vm"); + assert_eq!(loaded.ssh_port, 2250); + assert!(loaded.log_path.is_none()); + fs::remove_file(&json_path).unwrap(); + assert!(!json_path.exists()); + } + + #[test] + fn test_ephemeral_vm_metadata_list_all_from_dir() { + let dir = tempfile::tempdir().unwrap(); + for i in 0..3 { + let meta = EphemeralVmMetadata { + name: format!("vm-{i}"), + image: "test:latest".to_string(), + pid: 100 + i, + gvproxy_pid: 200 + i, + ssh_port: 2222 + (i as u16), + ssh_key: "/tmp/key".to_string(), + serial_log: "/tmp/serial.log".to_string(), + log_path: None, + created: "2026-01-01T00:00:00Z".to_string(), + }; + let path = dir.path().join(format!("vm-{i}.json")); + fs::write(&path, serde_json::to_string(&meta).unwrap()).unwrap(); + } + // Also write a non-json file that should be skipped + fs::write(dir.path().join("README.txt"), "not json").unwrap(); + + let mut vms = Vec::new(); + for entry in fs::read_dir(dir.path()).unwrap() { + let path = entry.unwrap().path(); + if path.extension().and_then(|e| e.to_str()) != Some("json") { + continue; + } + if let Ok(data) = fs::read_to_string(&path) { + if let Ok(meta) = serde_json::from_str::(&data) { + vms.push(meta); + } + } + } + assert_eq!(vms.len(), 3); + let mut names: Vec<_> = vms.iter().map(|v| v.name.clone()).collect(); + names.sort(); + assert_eq!(names, vec!["vm-0", "vm-1", "vm-2"]); + } +} diff --git a/crates/kit/src/ssh_options.rs b/crates/kit/src/ssh_options.rs new file mode 100644 index 000000000..8e26be324 --- /dev/null +++ b/crates/kit/src/ssh_options.rs @@ -0,0 +1,136 @@ +//! Cross-platform SSH option types shared between Linux and macOS backends. +//! +//! Extracted from ssh.rs to avoid pulling in Linux-only dependencies on macOS. + +/// Common SSH options that can be shared between different SSH implementations +#[derive(Debug, Clone)] +#[allow(dead_code)] +pub struct CommonSshOptions { + /// Use strict host key checking + pub strict_host_keys: bool, + /// SSH connection timeout in seconds + pub connect_timeout: u32, + /// Server alive interval in seconds + pub server_alive_interval: u32, + /// SSH log level + pub log_level: String, + /// Additional SSH options as key-value pairs + pub extra_options: Vec<(String, String)>, +} + +impl Default for CommonSshOptions { + fn default() -> Self { + Self { + strict_host_keys: false, + connect_timeout: 1, + server_alive_interval: 60, + log_level: "ERROR".to_string(), + extra_options: vec![], + } + } +} + +impl CommonSshOptions { + /// Apply these options to an SSH command + #[allow(dead_code)] + pub fn apply_to_command(&self, cmd: &mut std::process::Command) { + cmd.args(["-o", "IdentitiesOnly=yes"]); + cmd.args(["-o", "PasswordAuthentication=no"]); + cmd.args(["-o", "KbdInteractiveAuthentication=no"]); + cmd.args(["-o", "GSSAPIAuthentication=no"]); + + cmd.args(["-o", &format!("ConnectTimeout={}", self.connect_timeout)]); + cmd.args([ + "-o", + &format!("ServerAliveInterval={}", self.server_alive_interval), + ]); + cmd.args(["-o", &format!("LogLevel={}", self.log_level)]); + + if !self.strict_host_keys { + cmd.args(["-o", "StrictHostKeyChecking=no"]); + cmd.args(["-o", "UserKnownHostsFile=/dev/null"]); + } + + for (key, value) in &self.extra_options { + cmd.args(["-o", &format!("{}={}", key, value)]); + } + } +} + +/// SSH connection configuration options +#[derive(Debug, Clone)] +#[allow(dead_code)] +pub struct SshConnectionOptions { + /// Common SSH options shared across implementations + pub common: CommonSshOptions, + /// Enable/disable TTY allocation (default: true) + pub allocate_tty: bool, + /// Suppress output to stdout/stderr (default: false) + pub suppress_output: bool, +} + +impl Default for SshConnectionOptions { + fn default() -> Self { + Self { + common: CommonSshOptions::default(), + allocate_tty: true, + suppress_output: false, + } + } +} + +impl SshConnectionOptions { + /// Create options suitable for quick connectivity tests (short timeout, no TTY) + #[allow(dead_code)] + pub fn for_connectivity_test() -> Self { + Self { + common: CommonSshOptions { + strict_host_keys: false, + connect_timeout: 2, + server_alive_interval: 60, + log_level: "ERROR".to_string(), + extra_options: vec![], + }, + allocate_tty: false, + suppress_output: true, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_common_ssh_options_default() { + let opts = CommonSshOptions::default(); + assert!(!opts.strict_host_keys); + assert_eq!(opts.connect_timeout, 1); + assert_eq!(opts.server_alive_interval, 60); + assert_eq!(opts.log_level, "ERROR"); + assert!(opts.extra_options.is_empty()); + } + + #[test] + fn test_connectivity_test_options() { + let opts = SshConnectionOptions::for_connectivity_test(); + assert_eq!(opts.common.connect_timeout, 2); + assert!(!opts.allocate_tty); + assert!(opts.suppress_output); + } + + #[test] + fn test_apply_to_command() { + let opts = CommonSshOptions::default(); + let mut cmd = std::process::Command::new("ssh"); + opts.apply_to_command(&mut cmd); + let args: Vec<_> = cmd + .get_args() + .map(|a| a.to_string_lossy().to_string()) + .collect(); + assert!(args.contains(&"IdentitiesOnly=yes".to_string())); + assert!(args.contains(&"PasswordAuthentication=no".to_string())); + assert!(args.contains(&"StrictHostKeyChecking=no".to_string())); + assert!(args.contains(&"ConnectTimeout=1".to_string())); + } +} diff --git a/crates/kit/src/vfkit/inspect.rs b/crates/kit/src/vfkit/inspect.rs new file mode 100644 index 000000000..67a506d7c --- /dev/null +++ b/crates/kit/src/vfkit/inspect.rs @@ -0,0 +1,62 @@ +//! vm inspect — Show detailed VM information. + +use super::VmMetadata; +use color_eyre::Result; + +/// Display detailed metadata for the named VM. +pub fn run(name: &str, json: bool) -> Result<()> { + let meta = VmMetadata::load(name)?; + + if json { + println!("{}", serde_json::to_string_pretty(&meta)?); + return Ok(()); + } + + let state = if meta.is_alive() { + "running" + } else { + "stopped" + }; + + println!("Name: {}", meta.name); + println!("State: {}", state); + println!("Disk: {}", meta.disk_image); + println!("CPUs: {}", meta.cpus); + println!("Memory: {} MiB", meta.memory); + println!("GUI: {}", meta.gui); + println!("Created: {}", meta.created); + println!(); + println!("Processes:"); + if meta.vfkit_pid > 0 { + println!( + " vfkit: PID {} ({})", + meta.vfkit_pid, + if meta.is_alive() { + "running" + } else { + "stopped" + } + ); + } + if meta.gvproxy_pid > 0 { + println!(" gvproxy: PID {}", meta.gvproxy_pid); + } + println!(); + println!("SSH:"); + println!(" Port: {}", meta.ssh_port); + println!(" User: {}", meta.ssh_user); + println!(" Key: {}", meta.ssh_key); + if state == "running" { + println!(); + println!( + " ssh -p {} -i {} {}@localhost", + meta.ssh_port, meta.ssh_key, meta.ssh_user + ); + } + println!(); + println!("Files:"); + println!(" EFI store: {}", meta.efi_store); + println!(" Serial log: {}", meta.serial_log); + + Ok(()) +} diff --git a/crates/kit/src/vfkit/list.rs b/crates/kit/src/vfkit/list.rs new file mode 100644 index 000000000..bdda3f295 --- /dev/null +++ b/crates/kit/src/vfkit/list.rs @@ -0,0 +1,29 @@ +//! vm list — List all persistent VMs. + +use super::VmMetadata; +use color_eyre::Result; + +/// List all persistent VMs, optionally as JSON. +pub fn run(json: bool) -> Result<()> { + let vms = VmMetadata::list_all()?; + + if json { + println!("{}", serde_json::to_string_pretty(&vms)?); + return Ok(()); + } + + if vms.is_empty() { + println!("No VMs found."); + return Ok(()); + } + + println!("{:<20} {:<10} {:<30} SSH", "NAME", "STATE", "DISK"); + for vm in &vms { + let state = if vm.is_alive() { "running" } else { "stopped" }; + println!( + "{:<20} {:<10} {:<30} ssh -p {} -i {} {}@localhost", + vm.name, state, vm.disk_image, vm.ssh_port, vm.ssh_key, vm.ssh_user + ); + } + Ok(()) +} diff --git a/crates/kit/src/vfkit/mod.rs b/crates/kit/src/vfkit/mod.rs new file mode 100644 index 000000000..62939254a --- /dev/null +++ b/crates/kit/src/vfkit/mod.rs @@ -0,0 +1,271 @@ +//! Persistent VM management for macOS using vfkit + EFI boot. +//! +//! Subcommands mirror the Linux libvirt/ module structure: +//! run, list, ssh, stop, start, rm, rm-all, inspect + +use std::fs; +use std::path::PathBuf; +use std::process::{Command, Stdio}; + +use clap::Subcommand; +use color_eyre::Result; + +pub mod inspect; +pub mod list; +pub mod rm; +pub mod rm_all; +pub mod run; +pub mod ssh; +pub mod start; +pub mod stop; + +/// Subcommands for persistent VM management via vfkit. +#[derive(Debug, Subcommand)] +pub enum VmCommands { + /// Run a persistent VM from a disk image + Run(run::VmRunOpts), + + /// List all persistent VMs + #[clap(name = "list", alias = "ls")] + List { + /// Output in JSON format + #[clap(long)] + json: bool, + }, + + /// SSH into a running VM + Ssh(ssh::VmSshOpts), + + /// Stop a running VM + Stop { + /// VM name + name: String, + }, + + /// Start a stopped VM + Start(start::VmStartOpts), + + /// Remove a VM and its metadata + #[clap(name = "rm")] + Remove(rm::VmRmOpts), + + /// Remove all VMs + #[clap(name = "rm-all")] + RemoveAll { + /// Force removal without confirmation + #[clap(short, long)] + force: bool, + }, + + /// Show detailed VM information + Inspect { + /// VM name + name: String, + /// Output in JSON format + #[clap(long)] + json: bool, + }, +} + +impl VmCommands { + /// Dispatch to the appropriate subcommand handler. + pub fn run(self) -> Result<()> { + match self { + VmCommands::Run(opts) => run::run(opts), + VmCommands::List { json } => list::run(json), + VmCommands::Ssh(opts) => ssh::run(opts), + VmCommands::Stop { name } => stop::run(&name), + VmCommands::Start(opts) => start::run(opts), + VmCommands::Remove(opts) => rm::run(opts), + VmCommands::RemoveAll { force } => rm_all::run(force), + VmCommands::Inspect { name, json } => inspect::run(&name, json), + } + } +} + +// --- VM Metadata --- + +/// Persistent VM metadata, stored as JSON in `~/.local/share/bcvk/vms/`. +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] +pub struct VmMetadata { + /// VM name used as identifier. + pub name: String, + /// Path to the disk image file. + pub disk_image: String, + /// PID of the vfkit process. + pub vfkit_pid: u32, + /// PID of the gvproxy network proxy process. + pub gvproxy_pid: u32, + /// Host-side SSH port forwarded to the VM. + pub ssh_port: u16, + /// Path to the SSH private key. + pub ssh_key: String, + /// SSH username for connecting to the VM. + pub ssh_user: String, + /// Number of vCPUs allocated. + pub cpus: u32, + /// Memory in megabytes. + pub memory: u32, + /// Path to the EFI variable store file. + pub efi_store: String, + /// Path to the serial console log file. + pub serial_log: String, + /// Whether GUI mode is enabled. + pub gui: bool, + /// ISO 8601 timestamp when the VM was created. + pub created: String, + /// Current VM state (running, stopped). + pub state: String, +} + +impl VmMetadata { + /// Return the directory path for persistent VM metadata files. + pub fn vms_dir() -> PathBuf { + dirs::home_dir() + .expect("cannot determine home directory") + .join(".local/share/bcvk/vms") + } + + /// Save metadata to a JSON file in the VMs directory. + pub fn save(&self) -> Result<()> { + let dir = Self::vms_dir(); + fs::create_dir_all(&dir)?; + let path = dir.join(format!("{}.json", self.name)); + fs::write(&path, serde_json::to_string_pretty(self)?)?; + Ok(()) + } + + /// Load metadata for the named VM from its JSON file. + pub fn load(name: &str) -> Result { + let path = Self::vms_dir().join(format!("{}.json", name)); + let data = fs::read_to_string(&path)?; + Ok(serde_json::from_str(&data)?) + } + + /// Remove metadata file for the named VM. + pub fn remove(name: &str) { + let path = Self::vms_dir().join(format!("{}.json", name)); + let _ = fs::remove_file(path); + } + + /// List all persistent VM metadata from the VMs directory. + pub fn list_all() -> Result> { + let dir = Self::vms_dir(); + if !dir.exists() { + return Ok(Vec::new()); + } + let mut vms = Vec::new(); + for entry in fs::read_dir(&dir)? { + let path = entry?.path(); + if path.extension().and_then(|e| e.to_str()) != Some("json") { + continue; + } + if let Ok(data) = fs::read_to_string(&path) { + if let Ok(meta) = serde_json::from_str::(&data) { + vms.push(meta); + } + } + } + Ok(vms) + } + + /// Check if the VM process is still alive via kill -0. + pub fn is_alive(&self) -> bool { + if self.vfkit_pid == 0 { + return false; + } + Command::new("kill") + .args(["-0", &self.vfkit_pid.to_string()]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .map(|s| s.success()) + .unwrap_or(false) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn sample_vm_metadata(name: &str) -> VmMetadata { + VmMetadata { + name: name.to_string(), + disk_image: "/tmp/disk.raw".to_string(), + vfkit_pid: 0, + gvproxy_pid: 0, + ssh_port: 2222, + ssh_key: "/tmp/key".to_string(), + ssh_user: "root".to_string(), + cpus: 2, + memory: 4096, + efi_store: "/tmp/efi.fd".to_string(), + serial_log: "/tmp/serial.log".to_string(), + gui: false, + created: "2026-01-01T00:00:00Z".to_string(), + state: "running".to_string(), + } + } + + #[test] + fn test_vm_metadata_roundtrip() { + let meta = sample_vm_metadata("test-vm"); + let json = serde_json::to_string_pretty(&meta).unwrap(); + let loaded: VmMetadata = serde_json::from_str(&json).unwrap(); + assert_eq!(loaded.name, "test-vm"); + assert_eq!(loaded.disk_image, "/tmp/disk.raw"); + assert_eq!(loaded.cpus, 2); + assert_eq!(loaded.memory, 4096); + assert_eq!(loaded.ssh_user, "root"); + assert_eq!(loaded.state, "running"); + assert!(!loaded.gui); + } + + #[test] + fn test_vm_metadata_save_load_remove() { + let dir = tempfile::tempdir().unwrap(); + let json_path = dir.path().join("myvm.json"); + let meta = sample_vm_metadata("myvm"); + fs::write(&json_path, serde_json::to_string_pretty(&meta).unwrap()).unwrap(); + let data = fs::read_to_string(&json_path).unwrap(); + let loaded: VmMetadata = serde_json::from_str(&data).unwrap(); + assert_eq!(loaded.name, "myvm"); + assert_eq!(loaded.ssh_port, 2222); + fs::remove_file(&json_path).unwrap(); + assert!(!json_path.exists()); + } + + #[test] + fn test_vm_metadata_list_from_dir() { + let dir = tempfile::tempdir().unwrap(); + for i in 0..3 { + let meta = sample_vm_metadata(&format!("vm-{i}")); + let path = dir.path().join(format!("vm-{i}.json")); + fs::write(&path, serde_json::to_string(&meta).unwrap()).unwrap(); + } + fs::write(dir.path().join("notes.txt"), "ignored").unwrap(); + + let mut vms = Vec::new(); + for entry in fs::read_dir(dir.path()).unwrap() { + let path = entry.unwrap().path(); + if path.extension().and_then(|e| e.to_str()) != Some("json") { + continue; + } + if let Ok(data) = fs::read_to_string(&path) { + if let Ok(meta) = serde_json::from_str::(&data) { + vms.push(meta); + } + } + } + assert_eq!(vms.len(), 3); + let mut names: Vec<_> = vms.iter().map(|v| v.name.clone()).collect(); + names.sort(); + assert_eq!(names, vec!["vm-0", "vm-1", "vm-2"]); + } + + #[test] + fn test_vm_metadata_is_alive_zero_pid() { + let meta = sample_vm_metadata("dead-vm"); + assert!(!meta.is_alive()); + } +} diff --git a/crates/kit/src/vfkit/rm.rs b/crates/kit/src/vfkit/rm.rs new file mode 100644 index 000000000..ec48044e8 --- /dev/null +++ b/crates/kit/src/vfkit/rm.rs @@ -0,0 +1,59 @@ +//! vm rm — Remove a persistent VM and its metadata. + +use std::fs; + +use clap::Parser; +use color_eyre::Result; +use tracing::info; + +use super::VmMetadata; + +/// Options for `vm rm`. +#[derive(Parser, Debug)] +pub struct VmRmOpts { + /// VM name + pub name: String, + /// Force removal even if running + #[clap(short, long)] + pub force: bool, +} + +/// Remove a persistent VM, optionally force-killing it. +pub fn run(opts: VmRmOpts) -> Result<()> { + let meta = VmMetadata::load(&opts.name)?; + + if meta.is_alive() { + if !opts.force { + color_eyre::eyre::bail!( + "VM '{}' is running. Stop it first or use --force", + opts.name + ); + } + info!("force stopping VM '{}'...", opts.name); + crate::vfkit::stop::run(&opts.name)?; + } + + for path in [&meta.efi_store, &meta.serial_log] { + if !path.is_empty() { + if let Err(e) = fs::remove_file(path) { + if e.kind() != std::io::ErrorKind::NotFound { + tracing::debug!("failed to remove {}: {}", path, e); + } + } + } + } + + let vms_dir = VmMetadata::vms_dir(); + for suffix in ["-gvproxy.sock", "-gvproxy-svc.sock"] { + let p = vms_dir.join(format!("{}{}", meta.name, suffix)); + if let Err(e) = fs::remove_file(&p) { + if e.kind() != std::io::ErrorKind::NotFound { + tracing::debug!("failed to remove {}: {}", p.display(), e); + } + } + } + + VmMetadata::remove(&opts.name); + println!("Removed '{}'", opts.name); + Ok(()) +} diff --git a/crates/kit/src/vfkit/rm_all.rs b/crates/kit/src/vfkit/rm_all.rs new file mode 100644 index 000000000..2ed80df66 --- /dev/null +++ b/crates/kit/src/vfkit/rm_all.rs @@ -0,0 +1,44 @@ +//! vm rm-all — Remove all persistent VMs. + +use std::io::Write; + +use super::VmMetadata; +use color_eyre::Result; + +/// Remove all persistent VMs, prompting unless `force` is set. +pub fn run(force: bool) -> Result<()> { + let vms = VmMetadata::list_all()?; + if vms.is_empty() { + println!("No VMs found."); + return Ok(()); + } + + if !force { + println!("Found {} VM(s):", vms.len()); + for vm in &vms { + println!( + " {} ({})", + vm.name, + if vm.is_alive() { "running" } else { "stopped" } + ); + } + print!("Remove all VMs? [y/N]: "); + std::io::stdout().flush()?; + let mut input = String::new(); + std::io::stdin().read_line(&mut input)?; + let input = input.trim().to_lowercase(); + if input != "y" && input != "yes" { + println!("Aborted."); + return Ok(()); + } + } + + for vm in &vms { + let opts = super::rm::VmRmOpts { + name: vm.name.clone(), + force: true, + }; + super::rm::run(opts)?; + } + Ok(()) +} diff --git a/crates/kit/src/vfkit/run.rs b/crates/kit/src/vfkit/run.rs new file mode 100644 index 000000000..389aa0ca7 --- /dev/null +++ b/crates/kit/src/vfkit/run.rs @@ -0,0 +1,188 @@ +//! vm run — Start a persistent VM from a disk image using vfkit + EFI boot. + +use std::fs; +use std::path::Path; +use std::process::{Command, Stdio}; + +use clap::Parser; +use color_eyre::{eyre::bail, Result}; +use tracing::info; + +use super::VmMetadata; +use crate::run_ephemeral_macos::{ + clear_xattr, expose_ssh_port, find_available_ssh_port, find_vfkit, generate_mac, start_gvproxy, + wait_for_ssh, +}; + +/// Options for `vm run`. +#[derive(Parser, Debug)] +pub struct VmRunOpts { + /// Disk image path (.raw) + pub disk: String, + /// VM name for identification + #[clap(long)] + pub name: Option, + /// Number of vCPUs + #[clap(long)] + pub vcpus: Option, + /// Memory size (e.g. "4G", "2048M", or plain number for MB) + #[clap(long, default_value = "4G")] + pub memory: String, + /// Path to an existing SSH private key + #[clap(long)] + pub ssh_key: Option, + /// SSH username (default: root) + #[clap(long, default_value = "root")] + pub ssh_user: String, + /// SSH port (default: auto-allocate) + #[clap(long)] + pub ssh_port: Option, + /// Display VM console in GUI window + #[clap(long)] + pub gui: bool, +} + +/// Create and launch a persistent VM from a disk image via vfkit + EFI. +pub fn run(opts: VmRunOpts) -> Result<()> { + let vfkit_bin = find_vfkit()?; + + if !Path::new(&opts.disk).exists() { + bail!("disk image not found: {}", opts.disk); + } + clear_xattr(Path::new(&opts.disk)); + + let ssh_key_path = match &opts.ssh_key { + Some(p) => p.clone(), + None => find_ssh_key()?, + }; + if !Path::new(&ssh_key_path).exists() { + bail!( + "SSH key not found: {}. Specify with --ssh-key", + ssh_key_path + ); + } + + let vm_name = opts.name.clone().unwrap_or_else(|| { + Path::new(&opts.disk) + .file_stem() + .and_then(|s| s.to_str()) + .unwrap_or("vm") + .to_string() + }); + + let vms_dir = VmMetadata::vms_dir(); + fs::create_dir_all(&vms_dir)?; + + let efi_store = vms_dir.join(format!("{}-efi-vars", vm_name)); + let serial_log = vms_dir.join(format!("{}-serial.log", vm_name)); + let gvproxy_sock = vms_dir.join(format!("{}-gvproxy.sock", vm_name)); + let services_sock = vms_dir.join(format!("{}-gvproxy-svc.sock", vm_name)); + + let gvproxy_sock_str = gvproxy_sock.to_string_lossy().to_string(); + let services_sock_str = services_sock.to_string_lossy().to_string(); + + info!("starting gvproxy..."); + let gvproxy_child = start_gvproxy(&gvproxy_sock_str, &services_sock_str)?; + + let mac = generate_mac(); + let mac_str = format!( + "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] + ); + + let vcpus = opts.vcpus.unwrap_or(2); + let memory_mb = crate::run_ephemeral_macos::parse_memory_to_mb(&opts.memory)?; + + let mut vfkit_args = vec![ + "--cpus".to_string(), + vcpus.to_string(), + "--memory".to_string(), + memory_mb.to_string(), + "--bootloader".to_string(), + format!("efi,variable-store={},create", efi_store.display()), + "--device".to_string(), + format!("virtio-blk,path={}", opts.disk), + "--device".to_string(), + format!( + "virtio-net,unixSocketPath={},mac={}", + gvproxy_sock_str, mac_str + ), + "--device".to_string(), + format!("virtio-serial,logFilePath={}", serial_log.display()), + "--device".to_string(), + "virtio-rng".to_string(), + ]; + if opts.gui { + vfkit_args.push("--gui".to_string()); + } + + info!("launching vfkit (EFI boot)..."); + let vfkit_child = Command::new(&vfkit_bin) + .args(&vfkit_args) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn()?; + + let ssh_port = opts.ssh_port.unwrap_or_else(find_available_ssh_port); + info!("SSH port: {}", ssh_port); + + info!("setting up SSH port forwarding..."); + for attempt in 0..15u32 { + match expose_ssh_port(&services_sock_str, "192.168.127.2", ssh_port) { + Ok(_) => { + info!("SSH port {} forwarded", ssh_port); + break; + } + Err(e) if attempt < 14 => { + tracing::debug!("SSH port forward attempt {}: {}", attempt, e); + let backoff = 200 * 2u64.pow(attempt.min(4)); + std::thread::sleep(std::time::Duration::from_millis(backoff)); + } + Err(e) => bail!("SSH port forward failed: {}", e), + } + } + + let key_path = std::path::Path::new(&ssh_key_path); + wait_for_ssh(ssh_port, key_path, &opts.ssh_user)?; + + let metadata = VmMetadata { + name: vm_name.clone(), + disk_image: opts.disk.clone(), + vfkit_pid: vfkit_child.id(), + gvproxy_pid: gvproxy_child.id(), + ssh_port, + ssh_key: ssh_key_path.clone(), + ssh_user: opts.ssh_user.clone(), + cpus: vcpus, + memory: memory_mb, + efi_store: efi_store.to_string_lossy().to_string(), + serial_log: serial_log.to_string_lossy().to_string(), + gui: opts.gui, + created: chrono::Utc::now().to_rfc3339(), + state: "running".to_string(), + }; + metadata.save()?; + + println!("VM '{}' is running", vm_name); + println!( + " ssh -p {} -i {} {}@localhost", + ssh_port, ssh_key_path, opts.ssh_user + ); + println!(); + println!("To connect: bcvk vm ssh {}", vm_name); + println!("To stop: bcvk vm stop {}", vm_name); + + Ok(()) +} + +fn find_ssh_key() -> Result { + let home = dirs::home_dir() + .ok_or_else(|| color_eyre::eyre::eyre!("cannot determine home directory"))?; + for name in &["id_ed25519", "id_rsa"] { + let path = home.join(".ssh").join(name); + if path.exists() { + return Ok(path.to_string_lossy().to_string()); + } + } + bail!("no SSH key found in ~/.ssh/. Generate with: ssh-keygen -t ed25519") +} diff --git a/crates/kit/src/vfkit/ssh.rs b/crates/kit/src/vfkit/ssh.rs new file mode 100644 index 000000000..74af46736 --- /dev/null +++ b/crates/kit/src/vfkit/ssh.rs @@ -0,0 +1,24 @@ +//! vm ssh — SSH into a running persistent VM. + +use super::VmMetadata; +use crate::run_ephemeral_macos::run_ssh_interactive; +use clap::Parser; +use color_eyre::{eyre::bail, Result}; + +/// Options for `vm ssh`. +#[derive(Parser, Debug)] +pub struct VmSshOpts { + /// VM name + pub name: String, +} + +/// Open an interactive SSH session to a running persistent VM. +pub fn run(opts: VmSshOpts) -> Result<()> { + let vm = VmMetadata::load(&opts.name)?; + if !vm.is_alive() { + bail!("VM '{}' is not running", opts.name); + } + let key_path = std::path::Path::new(&vm.ssh_key); + run_ssh_interactive(vm.ssh_port, key_path, &vm.ssh_user)?; + Ok(()) +} diff --git a/crates/kit/src/vfkit/start.rs b/crates/kit/src/vfkit/start.rs new file mode 100644 index 000000000..f2f2a48f3 --- /dev/null +++ b/crates/kit/src/vfkit/start.rs @@ -0,0 +1,115 @@ +//! vm start — Restart a stopped persistent VM. + +use std::process::{Command, Stdio}; + +use clap::Parser; +use color_eyre::{eyre::bail, Result}; +use tracing::info; + +use super::VmMetadata; +use crate::run_ephemeral_macos::{ + clear_xattr, expose_ssh_port, find_vfkit, generate_mac, start_gvproxy, wait_for_ssh, +}; + +/// Options for `vm start`. +#[derive(Parser, Debug)] +pub struct VmStartOpts { + /// VM name + pub name: String, + /// Display VM console in GUI window + #[clap(long)] + pub gui: bool, +} + +/// Restart a stopped persistent VM by re-launching vfkit. +pub fn run(opts: VmStartOpts) -> Result<()> { + let mut meta = VmMetadata::load(&opts.name)?; + if meta.is_alive() { + bail!("VM '{}' is already running", opts.name); + } + + if !std::path::Path::new(&meta.disk_image).exists() { + bail!("disk image not found: {}", meta.disk_image); + } + clear_xattr(std::path::Path::new(&meta.disk_image)); + + let vfkit_bin = find_vfkit()?; + let vms_dir = VmMetadata::vms_dir(); + + let gvproxy_sock = vms_dir.join(format!("{}-gvproxy.sock", meta.name)); + let services_sock = vms_dir.join(format!("{}-gvproxy-svc.sock", meta.name)); + let gvproxy_sock_str = gvproxy_sock.to_string_lossy().to_string(); + let services_sock_str = services_sock.to_string_lossy().to_string(); + + info!("starting gvproxy..."); + let gvproxy_child = start_gvproxy(&gvproxy_sock_str, &services_sock_str)?; + + let mac = generate_mac(); + let mac_str = format!( + "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] + ); + + let gui = opts.gui || meta.gui; + let mut vfkit_args = vec![ + "--cpus".to_string(), + meta.cpus.to_string(), + "--memory".to_string(), + meta.memory.to_string(), + "--bootloader".to_string(), + format!("efi,variable-store={},create", meta.efi_store), + "--device".to_string(), + format!("virtio-blk,path={}", meta.disk_image), + "--device".to_string(), + format!( + "virtio-net,unixSocketPath={},mac={}", + gvproxy_sock_str, mac_str + ), + "--device".to_string(), + format!("virtio-serial,logFilePath={}", meta.serial_log), + "--device".to_string(), + "virtio-rng".to_string(), + ]; + if gui { + vfkit_args.push("--gui".to_string()); + } + + info!("launching vfkit (EFI boot)..."); + let vfkit_child = Command::new(&vfkit_bin) + .args(&vfkit_args) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn()?; + + info!("setting up SSH port forwarding..."); + for attempt in 0..15u32 { + match expose_ssh_port(&services_sock_str, "192.168.127.2", meta.ssh_port) { + Ok(_) => { + info!("SSH port {} forwarded", meta.ssh_port); + break; + } + Err(e) if attempt < 14 => { + tracing::debug!("SSH port forward attempt {}: {}", attempt, e); + let backoff = 200 * 2u64.pow(attempt.min(4)); + std::thread::sleep(std::time::Duration::from_millis(backoff)); + } + Err(e) => bail!("SSH port forward failed: {}", e), + } + } + + let key_path = std::path::Path::new(&meta.ssh_key); + wait_for_ssh(meta.ssh_port, key_path, &meta.ssh_user)?; + + meta.vfkit_pid = vfkit_child.id(); + meta.gvproxy_pid = gvproxy_child.id(); + meta.state = "running".to_string(); + meta.gui = gui; + meta.save()?; + + println!("Started '{}'", meta.name); + println!( + " ssh -p {} -i {} {}@localhost", + meta.ssh_port, meta.ssh_key, meta.ssh_user + ); + Ok(()) +} diff --git a/crates/kit/src/vfkit/stop.rs b/crates/kit/src/vfkit/stop.rs new file mode 100644 index 000000000..24ea6ceba --- /dev/null +++ b/crates/kit/src/vfkit/stop.rs @@ -0,0 +1,63 @@ +//! vm stop — Stop a running persistent VM. + +use std::process::{Command, Stdio}; +use std::time::Duration; + +use super::VmMetadata; +use color_eyre::{eyre::bail, Result}; +use tracing::info; + +/// Stop a running persistent VM by sending SIGTERM to vfkit. +pub fn run(name: &str) -> Result<()> { + let mut meta = VmMetadata::load(name)?; + if !meta.is_alive() { + bail!("VM '{}' is not running", name); + } + + info!("stopping VM '{}'...", name); + + if meta.vfkit_pid > 0 { + if let Err(e) = Command::new("kill") + .args(["-TERM", &meta.vfkit_pid.to_string()]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + { + tracing::debug!("failed to SIGTERM vfkit (PID {}): {}", meta.vfkit_pid, e); + } + std::thread::sleep(Duration::from_secs(3)); + if meta.is_alive() { + if let Err(e) = Command::new("kill") + .args(["-KILL", &meta.vfkit_pid.to_string()]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + { + tracing::debug!("failed to SIGKILL vfkit (PID {}): {}", meta.vfkit_pid, e); + } + } + } + + if meta.gvproxy_pid > 0 { + if let Err(e) = Command::new("kill") + .args(["-KILL", &meta.gvproxy_pid.to_string()]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + { + tracing::debug!( + "failed to SIGKILL gvproxy (PID {}): {}", + meta.gvproxy_pid, + e + ); + } + } + + meta.state = "stopped".to_string(); + meta.vfkit_pid = 0; + meta.gvproxy_pid = 0; + meta.save()?; + + println!("Stopped '{}'", name); + Ok(()) +} From b1c257347abeacde5f3ffb6ca3cbe16df3f331fe Mon Sep 17 00:00:00 2001 From: Shion Tanaka Date: Tue, 12 May 2026 01:10:31 +0900 Subject: [PATCH 2/6] macOS: add vfkit backend for ephemeral and persistent VMs macOS has no KVM/QEMU, so this adds vfkit as the VM backend. Ephemeral VMs use a custom nbdkit EROFS plugin that dynamically generates rootfs, ESP, and GPT from the container overlay via NBD. Persistent VMs use EFI boot. The vfkit/ module mirrors the libvirt/ directory structure, and CLI options match Linux where applicable. Plugin distribution method is TBD. Build and run on macOS: cargo build --release codesign -fs - target/release/bcvk Tested on macOS (Apple Silicon) with rootful and rootless podman machine. Assisted-by: Claude Code (Opus 4.6) Signed-off-by: Shion Tanaka --- Cargo.lock | 17 + crates/kit/Cargo.toml | 1 + crates/kit/src/ephemeral_macos.rs | 54 +- crates/kit/src/lib.rs | 2 + crates/kit/src/main.rs | 2 + crates/kit/src/nbdkit_macos.rs | 186 +++++++ crates/kit/src/run_ephemeral_macos.rs | 478 ++++++----------- crates/kit/src/vfkit/mod.rs | 14 +- crates/kit/src/vfkit/stop.rs | 26 +- crates/nbdkit-erofs-plugin/Cargo.lock | 39 ++ crates/nbdkit-erofs-plugin/Cargo.toml | 13 + crates/nbdkit-erofs-plugin/src/dir_walk.rs | 138 +++++ crates/nbdkit-erofs-plugin/src/erofs.rs | 502 ++++++++++++++++++ crates/nbdkit-erofs-plugin/src/fat32.rs | 548 ++++++++++++++++++++ crates/nbdkit-erofs-plugin/src/gpt.rs | 290 +++++++++++ crates/nbdkit-erofs-plugin/src/initramfs.rs | 182 +++++++ crates/nbdkit-erofs-plugin/src/lib.rs | 389 ++++++++++++++ crates/nbdkit-erofs-plugin/src/regions.rs | 80 +++ 18 files changed, 2595 insertions(+), 366 deletions(-) create mode 100644 crates/kit/src/nbdkit_macos.rs create mode 100644 crates/nbdkit-erofs-plugin/Cargo.lock create mode 100644 crates/nbdkit-erofs-plugin/Cargo.toml create mode 100644 crates/nbdkit-erofs-plugin/src/dir_walk.rs create mode 100644 crates/nbdkit-erofs-plugin/src/erofs.rs create mode 100644 crates/nbdkit-erofs-plugin/src/fat32.rs create mode 100644 crates/nbdkit-erofs-plugin/src/gpt.rs create mode 100644 crates/nbdkit-erofs-plugin/src/initramfs.rs create mode 100644 crates/nbdkit-erofs-plugin/src/lib.rs create mode 100644 crates/nbdkit-erofs-plugin/src/regions.rs diff --git a/Cargo.lock b/Cargo.lock index f18fec2bf..27f2d278d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -632,6 +632,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-channel" version = "0.5.15" @@ -1833,6 +1842,14 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nbdkit-erofs-plugin" +version = "0.1.0" +dependencies = [ + "crc32fast", + "libc", +] + [[package]] name = "newtype-uuid" version = "1.3.2" diff --git a/crates/kit/Cargo.toml b/crates/kit/Cargo.toml index 399f0764f..034663be1 100644 --- a/crates/kit/Cargo.toml +++ b/crates/kit/Cargo.toml @@ -60,6 +60,7 @@ libsystemd = "0.7" # macOS-only dependencies (vfkit backend) [target.'cfg(target_os = "macos")'.dependencies] +rustix = { version = "1", features = ["process"] } zstd = "0.13" [dev-dependencies] diff --git a/crates/kit/src/ephemeral_macos.rs b/crates/kit/src/ephemeral_macos.rs index ca3255247..8d46075f4 100644 --- a/crates/kit/src/ephemeral_macos.rs +++ b/crates/kit/src/ephemeral_macos.rs @@ -137,28 +137,55 @@ fn cmd_rm_all(force: bool) -> Result<()> { for vm in &vms { if vm.is_alive() { - if let Err(e) = Command::new("kill") - .args([&vm.pid.to_string()]) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status() - { + if let Err(e) = rustix::process::kill_process( + rustix::process::Pid::from_raw(vm.pid as i32).unwrap(), + rustix::process::Signal::TERM, + ) { tracing::warn!("failed to kill VM process {}: {}", vm.pid, e); } if vm.gvproxy_pid > 0 { - if let Err(e) = Command::new("kill") - .args([&vm.gvproxy_pid.to_string()]) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status() - { + if let Err(e) = rustix::process::kill_process( + rustix::process::Pid::from_raw(vm.gvproxy_pid as i32).unwrap(), + rustix::process::Signal::TERM, + ) { tracing::warn!("failed to kill gvproxy {}: {}", vm.gvproxy_pid, e); } } } + if let Some(ref container) = vm.nbd_container { + crate::nbdkit_macos::stop_nbdkit_container(container); + } EphemeralVmMetadata::remove(&vm.name); println!("Removed {}", vm.name); } + + // Sweep orphaned resources inside podman machine + if let Ok(machine) = run_ephemeral_macos::detect_machine_name() { + // Remove orphaned nbdkit containers + let _ = Command::new("podman") + .args([ + "machine", + "ssh", + &machine, + "--", + "podman", + "rm", + "-f", + "--filter", + "name=bcvk-nbd-", + ]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status(); + // Unmount any remaining container image overlays + let _ = Command::new("podman") + .args([ + "machine", "ssh", &machine, "--", "podman", "image", "umount", "--all", + ]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status(); + } Ok(()) } @@ -170,7 +197,8 @@ fn cmd_ssh(name: &str, args: &[String]) -> Result<()> { } // Try to set up SSH port forwarding via VM-specific gvproxy socket - let svc_sock = format!("/private/tmp/bcvk/{}-gvproxy-svc.sock", name); + let base = run_ephemeral_macos::ephemeral_base_dir(); + let svc_sock = format!("{}/{}-gvproxy-svc.sock", base.display(), name); if std::path::Path::new(&svc_sock).exists() { if let Err(e) = run_ephemeral_macos::expose_ssh_port(&svc_sock, "192.168.127.2", vm.ssh_port) diff --git a/crates/kit/src/lib.rs b/crates/kit/src/lib.rs index a3aa51578..d7257cb8e 100644 --- a/crates/kit/src/lib.rs +++ b/crates/kit/src/lib.rs @@ -13,6 +13,8 @@ pub mod kernel; // macOS-only modules (vfkit backend) #[cfg(target_os = "macos")] +pub mod nbdkit_macos; +#[cfg(target_os = "macos")] pub mod run_ephemeral_macos; #[cfg(target_os = "macos")] diff --git a/crates/kit/src/main.rs b/crates/kit/src/main.rs index cc4969312..b92d35783 100644 --- a/crates/kit/src/main.rs +++ b/crates/kit/src/main.rs @@ -65,6 +65,8 @@ mod varlink_ipc; #[cfg(target_os = "macos")] mod ephemeral_macos; #[cfg(target_os = "macos")] +mod nbdkit_macos; +#[cfg(target_os = "macos")] mod run_ephemeral_macos; #[cfg(target_os = "macos")] mod vfkit; diff --git a/crates/kit/src/nbdkit_macos.rs b/crates/kit/src/nbdkit_macos.rs new file mode 100644 index 000000000..40c2cc20e --- /dev/null +++ b/crates/kit/src/nbdkit_macos.rs @@ -0,0 +1,186 @@ +//! nbdkit EROFS plugin management for macOS ephemeral VMs. + +use color_eyre::{ + eyre::{bail, Context}, + Result, +}; +use std::process::{Command, Stdio}; +use std::time::Duration; +use tracing::info; + +use crate::run_ephemeral_macos::detect_machine_name; + +/// Path to the nbdkit EROFS plugin shared library inside podman machine. +const NBDKIT_EROFS_PLUGIN_PATH: &str = "/var/tmp/bcvk/libnbdkit_erofs_plugin.so"; + +/// Get the merged overlay path from podman image mount. +pub(crate) fn get_merged_path(machine: &str, rootful: bool, image: &str) -> Result { + let output = if rootful { + Command::new("podman") + .args([ + "machine", "ssh", machine, "--", "podman", "image", "mount", image, + ]) + .output() + .context("podman image mount")? + } else { + Command::new("podman") + .args([ + "machine", "ssh", machine, "--", "podman", "unshare", "podman", "image", "mount", + image, + ]) + .output() + .context("podman image mount")? + }; + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + bail!("podman image mount failed: {}", stderr.trim()); + } + Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) +} + +/// Start nbdkit with the erofs plugin for dynamic EROFS + ESP + GPT generation. +pub(crate) fn start_nbdkit_erofs_plugin( + machine: &str, + merged_path: &str, + cmdline: &str, + ssh_pubkey: &str, + nbd_port: u16, + vm_name: &str, +) -> Result { + let container_name = format!("bcvk-nbd-{}", vm_name); + + let _ = Command::new("podman") + .args([ + "machine", + "ssh", + machine, + "--", + "podman", + "rm", + "-f", + &container_name, + ]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status(); + + fn shell_escape(s: &str) -> String { + format!("'{}'", s.replace('\'', "'\\''")) + } + + let cmdline_esc = shell_escape(&format!("cmdline={}", cmdline)); + let dir_esc = shell_escape(&format!("dir={}", merged_path)); + + let mut ssh_param = String::new(); + if !ssh_pubkey.is_empty() { + ssh_param = format!(" {}", shell_escape(&format!("ssh_pubkey={}", ssh_pubkey))); + } + + let podman_cmd = format!( + "podman run -d --name {name} --security-opt label=disable \ + -p {port}:10809 \ + -v {merged}:{merged}:ro \ + -v {plugin}:/plugin.so:ro \ + -v /usr/bin/nbdkit:/usr/bin/nbdkit:ro \ + -v /usr/lib64/nbdkit:/usr/lib64/nbdkit:ro \ + quay.io/fedora/fedora:latest \ + nbdkit -f -p 10809 -r /plugin.so \ + {dir} {cmdline}{ssh}", + name = container_name, + port = nbd_port, + merged = merged_path, + plugin = NBDKIT_EROFS_PLUGIN_PATH, + dir = dir_esc, + cmdline = cmdline_esc, + ssh = ssh_param, + ); + + let output = Command::new("podman") + .args(["machine", "ssh", machine, "--", &podman_cmd]) + .output() + .context("failed to start nbdkit erofs plugin")?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + bail!("failed to start nbdkit erofs plugin: {}", stderr.trim()); + } + + info!("waiting for nbdkit on port {}...", nbd_port); + let deadline = std::time::Instant::now() + Duration::from_secs(30); + loop { + if let Ok(mut stream) = std::net::TcpStream::connect_timeout( + &std::net::SocketAddr::from(([127, 0, 0, 1], nbd_port)), + Duration::from_millis(500), + ) { + use std::io::Read; + stream.set_read_timeout(Some(Duration::from_secs(2))).ok(); + let mut buf = [0u8; 8]; + if stream.read_exact(&mut buf).is_ok() && &buf == b"NBDMAGIC" { + break; + } + } + if std::time::Instant::now() > deadline { + let _ = Command::new("podman") + .args([ + "machine", + "ssh", + machine, + "--", + "podman", + "rm", + "-f", + &container_name, + ]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status(); + bail!( + "nbdkit erofs plugin did not become ready on port {}", + nbd_port + ); + } + std::thread::sleep(Duration::from_millis(500)); + } + + Ok(container_name) +} + +/// Find an available TCP port for NBD in range 10800-10900. +pub fn find_available_nbd_port() -> u16 { + use rand::Rng; + let mut rng = rand::rng(); + const PORT_RANGE_START: u16 = 10800; + const PORT_RANGE_END: u16 = 10900; + for _ in 0..100 { + let port = rng.random_range(PORT_RANGE_START..PORT_RANGE_END); + if std::net::TcpListener::bind(("127.0.0.1", port)).is_ok() { + return port; + } + } + for port in PORT_RANGE_START..PORT_RANGE_END { + if std::net::TcpListener::bind(("127.0.0.1", port)).is_ok() { + return port; + } + } + PORT_RANGE_START +} + +/// Stop and remove an nbdkit container (best-effort). +pub fn stop_nbdkit_container(container_name: &str) { + if let Ok(machine) = detect_machine_name() { + let _ = Command::new("podman") + .args([ + "machine", + "ssh", + &machine, + "--", + "podman", + "rm", + "-f", + container_name, + ]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status(); + } +} diff --git a/crates/kit/src/run_ephemeral_macos.rs b/crates/kit/src/run_ephemeral_macos.rs index d7fe9257f..2265aacb7 100644 --- a/crates/kit/src/run_ephemeral_macos.rs +++ b/crates/kit/src/run_ephemeral_macos.rs @@ -1,16 +1,14 @@ -//! Ephemeral VM launch flow for macOS using vfkit + SquashFS. +//! Ephemeral VM launch flow for macOS using vfkit + NBD EROFS plugin. //! -//! Boot flow: -//! 1. Extract kernel + initramfs from container image -//! 2. Create SquashFS rootfs (lz4, cached by digest) -//! 3. Decompress vmlinuz PE+zstd → uncompressed ARM64 Image -//! 4. Append bcvk units CPIO to initramfs (/etc overlay + /var tmpfs + SSH) -//! 5. Launch vfkit with virtio-blk (SquashFS) + virtio-net (gvproxy) +//! Boot flow (fully diskless): +//! 1. Mount container image overlay (`podman image mount`) +//! 2. Start nbdkit with erofs plugin (dynamically generates GPT + ESP + EROFS) +//! 3. Launch vfkit with EFI boot via NBD + virtio-net (gvproxy) +//! 4. Wait for SSH and execute commands //! //! Common helpers (gvproxy, SSH, vfkit detection) are pub for reuse by vfkit/ module. -use std::fs::{self, OpenOptions}; -use std::io::{Seek, SeekFrom, Write}; +use std::fs; use std::os::unix::net::UnixStream; use std::path::Path; use std::process::{Command, Stdio}; @@ -22,6 +20,13 @@ use color_eyre::{ }; use tracing::{debug, info}; +/// Base directory for ephemeral VM state on macOS host. +pub fn ephemeral_base_dir() -> std::path::PathBuf { + dirs::home_dir() + .unwrap_or_else(|| std::path::PathBuf::from("/tmp")) + .join(".local/share/bcvk/ephemeral") +} + // --- Data structures --- /// Metadata for a running ephemeral VM, persisted as JSON for `ps` and `ssh`. @@ -46,13 +51,19 @@ pub struct EphemeralVmMetadata { pub log_path: Option, /// ISO 8601 timestamp when the VM was created. pub created: String, + /// Name of the nbdkit podman container serving the rootfs. + #[serde(default)] + pub nbd_container: Option, + /// NBD port allocated for this VM's rootfs. + #[serde(default)] + pub nbd_port: Option, } #[allow(dead_code)] impl EphemeralVmMetadata { /// Return the directory path for ephemeral VM metadata files. pub fn vms_dir() -> std::path::PathBuf { - std::path::PathBuf::from("/private/tmp/bcvk/vms") + ephemeral_base_dir().join("vms") } /// Save metadata to a JSON file in the VMs directory. @@ -98,15 +109,10 @@ impl EphemeralVmMetadata { Ok(vms) } - /// Check if the VM process is still alive via kill -0. + /// Check if the VM process is still alive via kill(pid, 0). pub fn is_alive(&self) -> bool { - Command::new("kill") - .args(["-0", &self.pid.to_string()]) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status() - .map(|s| s.success()) - .unwrap_or(false) + rustix::process::test_kill_process(rustix::process::Pid::from_raw(self.pid as i32).unwrap()) + .is_ok() } } @@ -167,35 +173,53 @@ pub fn parse_memory_to_mb(s: &str) -> Result { struct VmCleanup { vfkit_pid: u32, gvproxy_pid: u32, + nbd_container: Option, + image: String, vm_name: String, } impl Drop for VmCleanup { fn drop(&mut self) { tracing::debug!("cleaning up VM processes..."); - if let Err(e) = Command::new("kill") - .arg(self.vfkit_pid.to_string()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status() - { + if let Some(ref name) = self.nbd_container { + crate::nbdkit_macos::stop_nbdkit_container(name); + } + if let Err(e) = rustix::process::kill_process( + rustix::process::Pid::from_raw(self.vfkit_pid as i32).unwrap(), + rustix::process::Signal::TERM, + ) { tracing::warn!("failed to kill vfkit (PID {}): {}", self.vfkit_pid, e); } - if let Err(e) = Command::new("kill") - .arg(self.gvproxy_pid.to_string()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status() - { + if let Err(e) = rustix::process::kill_process( + rustix::process::Pid::from_raw(self.gvproxy_pid as i32).unwrap(), + rustix::process::Signal::TERM, + ) { tracing::warn!("failed to kill gvproxy (PID {}): {}", self.gvproxy_pid, e); } + // Release container image overlay mount + if let Ok(machine) = detect_machine_name() { + let _ = Command::new("podman") + .args([ + "machine", + "ssh", + &machine, + "--", + "podman", + "image", + "umount", + &self.image, + ]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status(); + } EphemeralVmMetadata::remove(&self.vm_name); } } // --- Main entry point --- -/// Run an ephemeral VM from a container image using vfkit + SquashFS. +/// Run an ephemeral VM from a container image using vfkit + EROFS over NBD. pub fn run(opts: RunEphemeralOpts) -> Result<()> { if opts.gui && opts.detach { bail!("--gui and --detach cannot be used together (GUI requires foreground process)"); @@ -206,9 +230,9 @@ pub fn run(opts: RunEphemeralOpts) -> Result<()> { } let vfkit_bin = find_vfkit()?; - info!(image = %opts.image, "starting ephemeral VM on macOS (vfkit + SquashFS)"); + info!(image = %opts.image, "starting ephemeral VM on macOS (vfkit + EROFS)"); - let cache_base = std::path::PathBuf::from("/private/tmp/bcvk"); + let cache_base = ephemeral_base_dir(); fs::create_dir_all(&cache_base)?; let machine = detect_machine_name()?; @@ -228,121 +252,37 @@ pub fn run(opts: RunEphemeralOpts) -> Result<()> { .unwrap_or_else(|| format!("ephemeral-{}", &digest_short[..8])); let ssh_key_path = cache_base.join(format!("{}-key", vm_name)); - let boot_dir = cache_base.join(format!("boot-{}", digest_short)); - fs::create_dir_all(&boot_dir)?; - let squashfs_cache = format!("/private/tmp/bcvk/rootfs-{}.squashfs", digest_short); - let squashfs_path = format!("/private/tmp/bcvk/{}-rootfs.squashfs", vm_name); - let vmlinuz_path = boot_dir.join("vmlinuz"); - let image_path = boot_dir.join("Image"); - let initramfs_orig = boot_dir.join("initramfs-orig.img"); - let initramfs_path = cache_base.join(format!("{}-initramfs.img", vm_name)); - - // Step 1+2: kernel extract + SquashFS creation (parallel) - let step2_handle = if !Path::new(&squashfs_cache).exists() { - let mc = machine.clone(); - let rf = rootful; - let img = opts.image.clone(); - let sc = squashfs_cache.clone(); - Some(std::thread::spawn(move || -> Result<()> { - info!("creating SquashFS image (lz4)..."); - create_squashfs_image(&mc, rf, &img, &sc) - })) - } else { - info!("using cached SquashFS: {}", squashfs_cache); - None - }; - - if !vmlinuz_path.exists() || !initramfs_orig.exists() { - info!("extracting kernel and initramfs..."); - extract_kernel(&machine, &opts.image, &boot_dir)?; - fs::rename(boot_dir.join("initramfs.img"), &initramfs_orig)?; - } - - // Step 3+4: kernel decompress + CPIO append (parallel after Step 1) - let step3_handle = if !image_path.exists() { - let vp = vmlinuz_path.clone(); - let ip = image_path.clone(); - Some(std::thread::spawn(move || -> Result<()> { - info!("decompressing kernel (vmlinuz → Image)..."); - extract_uncompressed_kernel(&vp, &ip) - })) - } else { - None - }; - - fs::copy(&initramfs_orig, &initramfs_path)?; - { - let cpio_data = crate::cpio::create_initramfs_units_cpio() - .map_err(|e| eyre!("failed to create CPIO: {e}"))?; - let mut f = OpenOptions::new().append(true).open(&initramfs_path)?; - let sz = f.seek(SeekFrom::End(0))?; - let pad = sz.next_multiple_of(4) - sz; - if pad > 0 { - f.write_all(&vec![0u8; pad as usize])?; - } - f.write_all(&cpio_data)?; + fs::create_dir_all(&cache_base)?; - if opts.ssh_keygen || !opts.execute.is_empty() { - info!("generating SSH keypair..."); - let _ = fs::remove_file(&ssh_key_path); - let _ = fs::remove_file(ssh_key_path.with_extension("pub")); - let status = Command::new("ssh-keygen") - .args([ - "-t", - "ed25519", - "-f", - &ssh_key_path.to_string_lossy(), - "-N", - "", - "-q", - ]) - .status()?; - if !status.success() { - bail!("ssh-keygen failed (exit code: {:?})", status.code()); - } - let pubkey = fs::read_to_string(ssh_key_path.with_extension("pub"))?; - let ssh_cpio = create_ssh_setup_cpio(pubkey.trim())?; - let pos = f.seek(SeekFrom::End(0))?; - let pad = pos.next_multiple_of(4) - pos; - if pad > 0 { - f.write_all(&vec![0u8; pad as usize])?; - } - f.write_all(&ssh_cpio)?; + // Generate SSH keypair on macOS host + let mut ssh_pubkey = String::new(); + if opts.ssh_keygen || !opts.execute.is_empty() { + info!("generating SSH keypair..."); + let _ = fs::remove_file(&ssh_key_path); + let _ = fs::remove_file(ssh_key_path.with_extension("pub")); + let status = Command::new("ssh-keygen") + .args([ + "-t", + "ed25519", + "-f", + &ssh_key_path.to_string_lossy(), + "-N", + "", + "-q", + ]) + .status()?; + if !status.success() { + bail!("ssh-keygen failed"); } - info!("initramfs prepared"); - } - - if let Some(h) = step3_handle { - h.join() - .map_err(|_| eyre!("kernel decompression thread panicked"))??; - } - if let Some(h) = step2_handle { - h.join() - .map_err(|_| eyre!("squashfs creation thread panicked"))??; + ssh_pubkey = fs::read_to_string(ssh_key_path.with_extension("pub"))? + .trim() + .to_string(); } - // CoW clone SquashFS for this VM (allows concurrent use of same image) - let _ = fs::remove_file(&squashfs_path); - let clone_status = Command::new("cp") - .args(["-c", &squashfs_cache, &squashfs_path]) - .status() - .context("cloning SquashFS")?; - if !clone_status.success() { - fs::copy(&squashfs_cache, &squashfs_path).context("copying SquashFS")?; - } - - // 5. gvproxy + vfkit - let gvproxy_sock = cache_base.join(format!("{}-gvproxy.sock", vm_name)); - let services_sock = cache_base.join(format!("{}-gvproxy-svc.sock", vm_name)); - let gvproxy_sock_str = gvproxy_sock.to_string_lossy().to_string(); - let services_sock_str = services_sock.to_string_lossy().to_string(); - info!("starting gvproxy..."); - let mut gvproxy_child = start_gvproxy(&gvproxy_sock_str, &services_sock_str)?; - let mut cmdline_parts: Vec<&str> = vec![ - "root=/dev/vda", + "root=/dev/vda2", "ro", - "rootfstype=squashfs", + "rootfstype=erofs", "console=tty0", "console=hvc0", "loglevel=4", @@ -354,18 +294,39 @@ pub fn run(opts: RunEphemeralOpts) -> Result<()> { cmdline_parts.extend(&user_args); let cmdline = cmdline_parts.join(" "); + // Get container image merged overlay path + let merged_path = crate::nbdkit_macos::get_merged_path(&machine, rootful, &opts.image)?; + info!("overlay merged: {}", merged_path); + + // Start nbdkit with erofs plugin (dynamic EROFS + ESP + GPT from overlay dir) + let nbd_port = crate::nbdkit_macos::find_available_nbd_port(); + let nbd_container_name = crate::nbdkit_macos::start_nbdkit_erofs_plugin( + &machine, + &merged_path, + &cmdline, + &ssh_pubkey, + nbd_port, + &vm_name, + )?; + std::thread::sleep(Duration::from_millis(500)); + info!("nbdkit ready on port {}", nbd_port); + + // gvproxy + vfkit (EFI boot) + let gvproxy_sock = cache_base.join(format!("{}-gvproxy.sock", vm_name)); + let services_sock = cache_base.join(format!("{}-gvproxy-svc.sock", vm_name)); + let gvproxy_sock_str = gvproxy_sock.to_string_lossy().to_string(); + let services_sock_str = services_sock.to_string_lossy().to_string(); + info!("starting gvproxy..."); + let mut gvproxy_child = start_gvproxy(&gvproxy_sock_str, &services_sock_str)?; + let mac = generate_mac(); let mac_str = format!( "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] ); - let bootloader_arg = format!( - "linux,kernel={},initrd={},cmdline=\"{}\"", - image_path.display(), - initramfs_path.display(), - cmdline - ); + let efi_var_store = cache_base.join(format!("{}-efi-vars", vm_name)); + let bootloader_arg = format!("efi,variable-store={},create", efi_var_store.display()); let vcpus = opts.vcpus.unwrap_or_else(default_vcpus); let memory_mb = parse_memory_to_mb(&opts.memory)?; @@ -378,7 +339,10 @@ pub fn run(opts: RunEphemeralOpts) -> Result<()> { "--bootloader".to_string(), bootloader_arg, "--device".to_string(), - format!("virtio-blk,path={}", squashfs_path), + format!( + "nbd,uri=nbd://127.0.0.1:{}/,readonly,timeout=5000,deviceId=rootfs", + nbd_port + ), "--device".to_string(), format!( "virtio-net,unixSocketPath={},mac={}", @@ -387,6 +351,13 @@ pub fn run(opts: RunEphemeralOpts) -> Result<()> { "--device".to_string(), "virtio-rng".to_string(), ]; + + let serial_log = cache_base.join(format!("{}-serial.log", vm_name)); + vfkit_args.extend([ + "--device".to_string(), + format!("virtio-serial,logFilePath={}", serial_log.display()), + ]); + if opts.gui { vfkit_args.push("--gui".to_string()); } @@ -411,15 +382,19 @@ pub fn run(opts: RunEphemeralOpts) -> Result<()> { gvproxy_pid: gvproxy_child.id(), ssh_port, ssh_key: ssh_key_path.to_string_lossy().to_string(), - serial_log: String::new(), + serial_log: serial_log.to_string_lossy().to_string(), log_path: None, created: chrono::Utc::now().to_rfc3339(), + nbd_container: Some(nbd_container_name.clone()), + nbd_port: Some(nbd_port), }; metadata.save()?; let _cleanup = VmCleanup { vfkit_pid: vfkit_child.id(), gvproxy_pid: gvproxy_child.id(), + nbd_container: Some(nbd_container_name.clone()), + image: opts.image.clone(), vm_name: vm_name.clone(), }; @@ -472,15 +447,31 @@ pub fn run(opts: RunEphemeralOpts) -> Result<()> { std::mem::forget(_cleanup); let status = vfkit_child.wait()?; info!("vfkit exited: {}", status); + crate::nbdkit_macos::stop_nbdkit_container(&nbd_container_name); if let Err(e) = gvproxy_child.kill() { tracing::debug!("failed to kill gvproxy: {}", e); } + // Release container image overlay mount + let _ = Command::new("podman") + .args([ + "machine", + "ssh", + &machine, + "--", + "podman", + "image", + "umount", + &opts.image, + ]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status(); EphemeralVmMetadata::remove(&vm_name); Ok(()) } fn run_detached(opts: &RunEphemeralOpts) -> Result<()> { - let cache_base = std::path::PathBuf::from("/private/tmp/bcvk"); + let cache_base = ephemeral_base_dir(); fs::create_dir_all(&cache_base)?; let digest = ensure_image_and_get_digest(&opts.image)?; let digest_short = &digest[..16.min(digest.len())]; @@ -524,128 +515,18 @@ fn run_detached(opts: &RunEphemeralOpts) -> Result<()> { serial_log: String::new(), log_path: Some(log_path.to_string_lossy().to_string()), created: chrono::Utc::now().to_rfc3339(), + nbd_container: None, + nbd_port: None, }; metadata.save()?; println!("{}", vm_name); Ok(()) } -// --- SSH setup CPIO --- - -fn create_ssh_setup_cpio(pubkey: &str) -> Result> { - use cpio::newc::Builder as NewcBuilder; - let mut buf = Vec::new(); - - let script = format!( - "#!/bin/bash\n\ - mkdir -p /sysroot/var/roothome/.ssh\n\ - chmod 700 /sysroot/var/roothome/.ssh\n\ - echo '{}' > /sysroot/var/roothome/.ssh/authorized_keys\n\ - chmod 600 /sysroot/var/roothome/.ssh/authorized_keys\n\ - chown -R 0:0 /sysroot/var/roothome/.ssh\n", - pubkey - ); - - let service = "[Unit]\n\ - Description=Setup SSH authorized_keys for root\n\ - DefaultDependencies=no\n\ - ConditionPathExists=/etc/initrd-release\n\ - Before=initrd-fs.target\n\ - After=bcvk-var-ephemeral.service\n\ - Requires=bcvk-var-ephemeral.service\n\ - \n\ - [Service]\n\ - Type=oneshot\n\ - RemainAfterExit=yes\n\ - ExecStart=/usr/bin/bash /usr/lib/bcvk/setup-ssh.sh\n"; - - let dropin = "[Unit]\nWants=bcvk-ssh-setup.service\n"; - - let write_entry = - |buf: &mut Vec, path: &str, data: &[u8], executable: bool| -> std::io::Result<()> { - let mode = if executable { 0o100755 } else { 0o100644 }; - let builder = NewcBuilder::new(path).mode(mode).uid(0).gid(0); - let mut writer = builder.write(buf, data.len() as u32); - writer.write_all(data)?; - writer.finish()?; - Ok(()) - }; - - let write_dir = |buf: &mut Vec, path: &str| -> std::io::Result<()> { - NewcBuilder::new(path) - .mode(0o040755) - .uid(0) - .gid(0) - .write(buf, 0) - .finish()?; - Ok(()) - }; - - write_dir(&mut buf, "usr/lib/bcvk")?; - write_entry( - &mut buf, - "usr/lib/bcvk/setup-ssh.sh", - script.as_bytes(), - true, - )?; - write_entry( - &mut buf, - "usr/lib/systemd/system/bcvk-ssh-setup.service", - service.as_bytes(), - false, - )?; - write_entry( - &mut buf, - "usr/lib/systemd/system/initrd-fs.target.d/bcvk-ssh-setup.conf", - dropin.as_bytes(), - false, - )?; - cpio::newc::trailer(&mut buf).map_err(|e| eyre!("cpio trailer: {e}"))?; - Ok(buf) -} - -// --- vfkit kernel decompression --- - -fn extract_uncompressed_kernel(vmlinuz_path: &Path, output_path: &Path) -> Result<()> { - let data = fs::read(vmlinuz_path)?; - - // Parse zboot header: offset 0x08 = payload_offset (le32), 0x0c = payload_size (le32) - let (pos, payload_end) = if data.len() >= 16 && &data[4..8] == b"zimg" { - let payload_offset = u32::from_le_bytes(data[8..12].try_into().unwrap()) as usize; - let payload_size = u32::from_le_bytes(data[12..16].try_into().unwrap()) as usize; - if payload_offset + payload_size > data.len() { - bail!("zboot payload extends beyond file"); - } - info!( - "zboot header: payload at 0x{:x}, size 0x{:x}", - payload_offset, payload_size - ); - (payload_offset, payload_offset + payload_size) - } else { - let magic = [0x28u8, 0xb5, 0x2f, 0xfd]; - let p = data - .windows(4) - .position(|w| w == magic) - .ok_or_else(|| eyre!("zstd magic not found in vmlinuz"))?; - info!("zstd payload at offset 0x{:x} (no zboot header)", p); - (p, data.len()) - }; - - let mut kernel = Vec::new(); - zstd::stream::copy_decode(&data[pos..payload_end], &mut kernel) - .context("decompressing zstd payload from vmlinuz")?; - - if kernel.len() < 0x3c || &kernel[0x38..0x3c] != b"ARMd" { - bail!("decompressed kernel is not a valid ARM64 Image"); - } - fs::write(output_path, &kernel)?; - info!("decompressed kernel: {} bytes (ARM64 Image)", kernel.len()); - Ok(()) -} - // --- Shared helpers (pub for vfkit/ module) --- -fn detect_machine_name() -> Result { +/// Detect the name of the running podman machine. +pub fn detect_machine_name() -> Result { let output = Command::new("podman") .args(["machine", "info", "--format", "{{.Host.CurrentMachine}}"]) .output()?; @@ -679,34 +560,6 @@ fn ensure_image_and_get_digest(image: &str) -> Result { Ok(digest.trim_start_matches("sha256:").to_string()) } -fn extract_kernel(machine: &str, image: &str, boot_dir: &Path) -> Result<()> { - let boot_dir_str = boot_dir.to_string_lossy(); - let script = format!( - "KVER=$(podman run --rm {image} ls /usr/lib/modules/ | head -1) && \ - [ -n \"$KVER\" ] && \ - podman run --rm {image} cat /usr/lib/modules/$KVER/vmlinuz > {boot}/vmlinuz && \ - podman run --rm {image} cat /usr/lib/modules/$KVER/initramfs.img > {boot}/initramfs.img", - image = image, - boot = boot_dir_str - ); - let output = Command::new("podman") - .args(["machine", "ssh", machine, &script]) - .output() - .context("extracting kernel from container image")?; - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - bail!( - "No kernel found in image '{}'.\n\ - Checked: /usr/lib/modules//vmlinuz + initramfs.img\n\ - This image may not be a bootable container (bootc) image.\n\ - {}", - image, - stderr.trim() - ); - } - Ok(()) -} - fn is_machine_rootful(machine: &str) -> bool { Command::new("podman") .args(["machine", "ssh", machine, "id", "-u"]) @@ -715,38 +568,6 @@ fn is_machine_rootful(machine: &str) -> bool { .unwrap_or(false) } -fn create_squashfs_image( - machine: &str, - rootful: bool, - image: &str, - output_path: &str, -) -> Result<()> { - let script = if rootful { - format!( - "MERGED=$(podman image mount {}) && \ - mksquashfs $MERGED {} -noappend -comp lz4 -b 1M -quiet", - image, output_path - ) - } else { - info!("rootless mode: using podman unshare for SquashFS creation"); - format!( - "podman unshare sh -c 'MERGED=$(podman image mount {}) && \ - mksquashfs $MERGED {} -noappend -comp lz4 -b 1M -quiet'", - image, output_path - ) - }; - - let output = Command::new("podman") - .args(["machine", "ssh", machine, &script]) - .output() - .context("running mksquashfs")?; - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - bail!("mksquashfs failed: {}", stderr.trim()); - } - Ok(()) -} - /// Clear extended attributes from a file. /// /// Apple Virtualization.framework rejects disk images with xattrs like @@ -1018,12 +839,15 @@ mod tests { serial_log: "/tmp/test-serial.log".to_string(), log_path: Some("/tmp/test-vfkit.log".to_string()), created: "2026-01-01T00:00:00Z".to_string(), + nbd_container: Some("bcvk-nbd-test-vm".to_string()), + nbd_port: Some(10841), }; let json = serde_json::to_string_pretty(&meta).unwrap(); let loaded: EphemeralVmMetadata = serde_json::from_str(&json).unwrap(); assert_eq!(loaded.name, "test-vm"); assert_eq!(loaded.image, "quay.io/fedora/fedora-bootc:42"); assert_eq!(loaded.pid, 12345); + assert_eq!(loaded.nbd_container.as_deref(), Some("bcvk-nbd-test-vm")); assert_eq!(loaded.ssh_port, 2222); assert_eq!(loaded.log_path.as_deref(), Some("/tmp/test-vfkit.log")); } @@ -1042,6 +866,8 @@ mod tests { serial_log: "/tmp/serial.log".to_string(), log_path: None, created: "2026-05-04T00:00:00Z".to_string(), + nbd_container: None, + nbd_port: None, }; fs::write(&json_path, serde_json::to_string_pretty(&meta).unwrap()).unwrap(); let data = fs::read_to_string(&json_path).unwrap(); @@ -1067,6 +893,8 @@ mod tests { serial_log: "/tmp/serial.log".to_string(), log_path: None, created: "2026-01-01T00:00:00Z".to_string(), + nbd_container: Some(format!("bcvk-nbd-vm-{i}")), + nbd_port: Some(10800 + i as u16), }; let path = dir.path().join(format!("vm-{i}.json")); fs::write(&path, serde_json::to_string(&meta).unwrap()).unwrap(); diff --git a/crates/kit/src/vfkit/mod.rs b/crates/kit/src/vfkit/mod.rs index 62939254a..2062851d5 100644 --- a/crates/kit/src/vfkit/mod.rs +++ b/crates/kit/src/vfkit/mod.rs @@ -5,7 +5,6 @@ use std::fs; use std::path::PathBuf; -use std::process::{Command, Stdio}; use clap::Subcommand; use color_eyre::Result; @@ -169,18 +168,15 @@ impl VmMetadata { Ok(vms) } - /// Check if the VM process is still alive via kill -0. + /// Check if the VM process is still alive via kill(pid, 0). pub fn is_alive(&self) -> bool { if self.vfkit_pid == 0 { return false; } - Command::new("kill") - .args(["-0", &self.vfkit_pid.to_string()]) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status() - .map(|s| s.success()) - .unwrap_or(false) + rustix::process::test_kill_process( + rustix::process::Pid::from_raw(self.vfkit_pid as i32).unwrap(), + ) + .is_ok() } } diff --git a/crates/kit/src/vfkit/stop.rs b/crates/kit/src/vfkit/stop.rs index 24ea6ceba..52c69fb51 100644 --- a/crates/kit/src/vfkit/stop.rs +++ b/crates/kit/src/vfkit/stop.rs @@ -1,6 +1,5 @@ //! vm stop — Stop a running persistent VM. -use std::process::{Command, Stdio}; use std::time::Duration; use super::VmMetadata; @@ -17,34 +16,23 @@ pub fn run(name: &str) -> Result<()> { info!("stopping VM '{}'...", name); if meta.vfkit_pid > 0 { - if let Err(e) = Command::new("kill") - .args(["-TERM", &meta.vfkit_pid.to_string()]) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status() - { + let pid = rustix::process::Pid::from_raw(meta.vfkit_pid as i32).unwrap(); + if let Err(e) = rustix::process::kill_process(pid, rustix::process::Signal::TERM) { tracing::debug!("failed to SIGTERM vfkit (PID {}): {}", meta.vfkit_pid, e); } std::thread::sleep(Duration::from_secs(3)); if meta.is_alive() { - if let Err(e) = Command::new("kill") - .args(["-KILL", &meta.vfkit_pid.to_string()]) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status() - { + if let Err(e) = rustix::process::kill_process(pid, rustix::process::Signal::KILL) { tracing::debug!("failed to SIGKILL vfkit (PID {}): {}", meta.vfkit_pid, e); } } } if meta.gvproxy_pid > 0 { - if let Err(e) = Command::new("kill") - .args(["-KILL", &meta.gvproxy_pid.to_string()]) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status() - { + if let Err(e) = rustix::process::kill_process( + rustix::process::Pid::from_raw(meta.gvproxy_pid as i32).unwrap(), + rustix::process::Signal::KILL, + ) { tracing::debug!( "failed to SIGKILL gvproxy (PID {}): {}", meta.gvproxy_pid, diff --git a/crates/nbdkit-erofs-plugin/Cargo.lock b/crates/nbdkit-erofs-plugin/Cargo.lock new file mode 100644 index 000000000..b5064fd23 --- /dev/null +++ b/crates/nbdkit-erofs-plugin/Cargo.lock @@ -0,0 +1,39 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cpio" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "938e716cb1ade5d6c8f959c13a7248b889c07491fc7e41167c3afe20f8f0de1e" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "nbdkit-erofs-plugin" +version = "0.1.0" +dependencies = [ + "cpio", + "crc32fast", + "libc", +] diff --git a/crates/nbdkit-erofs-plugin/Cargo.toml b/crates/nbdkit-erofs-plugin/Cargo.toml new file mode 100644 index 000000000..0f645c08c --- /dev/null +++ b/crates/nbdkit-erofs-plugin/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "nbdkit-erofs-plugin" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +libc = "0.2" +cpio = "0.4" +crc32fast = "1.4" diff --git a/crates/nbdkit-erofs-plugin/src/dir_walk.rs b/crates/nbdkit-erofs-plugin/src/dir_walk.rs new file mode 100644 index 000000000..674556d4b --- /dev/null +++ b/crates/nbdkit-erofs-plugin/src/dir_walk.rs @@ -0,0 +1,138 @@ +use std::ffi::OsString; +use std::fs; +use std::os::unix::fs::MetadataExt; +use std::path::{Path, PathBuf}; + +#[derive(Debug)] +pub struct FileEntry { + pub host_path: PathBuf, + pub size: u64, + pub mode: u32, + pub uid: u32, + pub gid: u32, + pub mtime: u64, + pub nlink: u32, + pub inode_id: u64, +} + +#[derive(Debug)] +pub struct DirInfo { + pub name: OsString, + pub mode: u32, + pub uid: u32, + pub gid: u32, + pub mtime: u64, + pub inode_id: u64, + pub parent_inode_id: u64, + pub children: Vec, +} + +#[derive(Debug)] +pub struct SymlinkEntry { + pub name: Vec, + pub target: Vec, + pub mode: u32, + pub uid: u32, + pub gid: u32, + pub mtime: u64, + pub inode_id: u64, +} + +/// Child entry in a directory: either a file index, dir index, or symlink index +#[derive(Debug, Clone, Copy)] +pub enum ChildRef { + File(usize), + Dir(usize), + Symlink(usize), +} + +#[derive(Debug)] +pub struct WalkResult { + pub dirs: Vec, + pub files: Vec, + pub symlinks: Vec, +} + +pub fn walk_directory(root: &Path) -> std::io::Result { + let mut result = WalkResult { + dirs: Vec::new(), + files: Vec::new(), + symlinks: Vec::new(), + }; + let mut next_inode: u64 = 0; + + walk_recursive(root, root, &mut result, &mut next_inode, 0)?; + Ok(result) +} + +fn walk_recursive( + root: &Path, + dir: &Path, + result: &mut WalkResult, + next_inode: &mut u64, + parent_inode_id: u64, +) -> std::io::Result { + let meta = fs::symlink_metadata(dir)?; + let dir_inode = *next_inode; + *next_inode += 1; + + let di = result.dirs.len(); + result.dirs.push(DirInfo { + name: dir.file_name().unwrap_or_default().to_os_string(), + mode: meta.mode(), + uid: meta.uid(), + gid: meta.gid(), + mtime: meta.mtime() as u64, + inode_id: dir_inode, + parent_inode_id, + children: Vec::new(), + }); + + let mut entries: Vec<_> = fs::read_dir(dir)?.filter_map(|e| e.ok()).collect(); + entries.sort_by_key(|e| e.file_name()); + + for entry in entries { + let path = entry.path(); + let meta = fs::symlink_metadata(&path)?; + let ft = meta.file_type(); + + if ft.is_dir() { + let child_di = walk_recursive(root, &path, result, next_inode, dir_inode)?; + result.dirs[di].children.push(ChildRef::Dir(child_di)); + } else if ft.is_symlink() { + let target = fs::read_link(&path)?; + let target_bytes = target.as_os_str().as_encoded_bytes().to_vec(); + let name = entry.file_name().as_encoded_bytes().to_vec(); + let si = result.symlinks.len(); + let inode = *next_inode; + *next_inode += 1; + result.symlinks.push(SymlinkEntry { + name, + target: target_bytes, + mode: meta.mode(), + uid: meta.uid(), + gid: meta.gid(), + mtime: meta.mtime() as u64, + inode_id: inode, + }); + result.dirs[di].children.push(ChildRef::Symlink(si)); + } else if ft.is_file() { + let fi = result.files.len(); + let inode = *next_inode; + *next_inode += 1; + result.files.push(FileEntry { + host_path: path, + size: meta.len(), + mode: meta.mode(), + uid: meta.uid(), + gid: meta.gid(), + mtime: meta.mtime() as u64, + nlink: meta.nlink() as u32, + inode_id: inode, + }); + result.dirs[di].children.push(ChildRef::File(fi)); + } + } + + Ok(di) +} diff --git a/crates/nbdkit-erofs-plugin/src/erofs.rs b/crates/nbdkit-erofs-plugin/src/erofs.rs new file mode 100644 index 000000000..a795b076a --- /dev/null +++ b/crates/nbdkit-erofs-plugin/src/erofs.rs @@ -0,0 +1,502 @@ +use crate::dir_walk::{ChildRef, DirInfo, WalkResult}; +use crate::regions::{Region, RegionType}; +use std::sync::Arc; + +const EROFS_MAGIC: u32 = 0xE0F5E1E2; +const BLOCK_SIZE: u64 = 4096; +const BLOCK_BITS: u8 = 12; +const SUPERBLOCK_OFFSET: u64 = 1024; + +// EROFS inode formats +const EROFS_INODE_LAYOUT_COMPACT: u16 = 0; + +// EROFS data layouts +const EROFS_INODE_FLAT_PLAIN: u16 = 0; + +// EROFS file types (matching Linux DT_* values) +const EROFS_FT_REG_FILE: u8 = 1; +const EROFS_FT_DIR: u8 = 2; +const EROFS_FT_SYMLINK: u8 = 7; + +#[derive(Debug)] +pub struct FileRegion { + pub file_index: usize, + pub offset_in_erofs: u64, + pub size: u64, +} + +#[derive(Debug)] +pub struct ErofsLayout { + pub metadata: Vec, + pub file_regions: Vec, + pub total_size: u64, +} + +struct DirEntryOnDisk { + nid: u64, + file_type: u8, + name: Vec, +} + +pub fn build_erofs(walk: &WalkResult) -> std::io::Result { + let total_inodes = walk.dirs.len() + walk.files.len() + walk.symlinks.len(); + + // Phase 1: Assign inode positions + // Inodes start at block 1 (block 0 has superblock) + let inode_table_offset = BLOCK_SIZE; // block 1 + let inode_size: u64 = 32; // compact inode + let inode_table_size = align_up(total_inodes as u64 * inode_size, BLOCK_SIZE); + + // Phase 2: Build directory entry blocks + let dir_blocks_offset = inode_table_offset + inode_table_size; + let mut dir_data: Vec = Vec::new(); + let mut dir_block_offsets: Vec = Vec::new(); // per-directory offset in dir_data + + for dir in &walk.dirs { + let offset = align_up(dir_data.len() as u64, BLOCK_SIZE); + dir_data.resize(offset as usize, 0); + dir_block_offsets.push(dir_blocks_offset + offset); + + let mut entries = Vec::new(); + + // "." entry + entries.push(DirEntryOnDisk { + nid: dir.inode_id, + file_type: EROFS_FT_DIR, + name: b".".to_vec(), + }); + + // ".." entry (root points to self) + let parent_nid = dir.parent_inode_id; + entries.push(DirEntryOnDisk { + nid: parent_nid, + file_type: EROFS_FT_DIR, + name: b"..".to_vec(), + }); + + // children (sorted by name in walk) + for child in &dir.children { + match child { + ChildRef::Dir(di) => { + let child_dir = &walk.dirs[*di]; + entries.push(DirEntryOnDisk { + nid: child_dir.inode_id, + file_type: EROFS_FT_DIR, + name: child_dir.name.as_encoded_bytes().to_vec(), + }); + } + ChildRef::File(fi) => { + let file = &walk.files[*fi]; + entries.push(DirEntryOnDisk { + nid: file.inode_id, + file_type: EROFS_FT_REG_FILE, + name: file + .host_path + .file_name() + .unwrap_or_default() + .as_encoded_bytes() + .to_vec(), + }); + } + ChildRef::Symlink(si) => { + let symlink = &walk.symlinks[*si]; + entries.push(DirEntryOnDisk { + nid: symlink.inode_id, + file_type: EROFS_FT_SYMLINK, + name: symlink.name.clone(), + }); + } + } + } + + // Write EROFS directory blocks (splits at 4096-byte boundaries) + write_dir_blocks(&mut dir_data, &entries); + } + let dir_data_size = align_up(dir_data.len() as u64, BLOCK_SIZE); + dir_data.resize(dir_data_size as usize, 0); + + // Phase 3: Compute data region layout + let data_offset = dir_blocks_offset + dir_data_size; + let mut file_regions = Vec::new(); + let mut current_data_offset = data_offset; + + for (i, file) in walk.files.iter().enumerate() { + if file.size > 0 { + let aligned_offset = align_up(current_data_offset, BLOCK_SIZE); + file_regions.push(FileRegion { + file_index: i, + offset_in_erofs: aligned_offset, + size: file.size, + }); + current_data_offset = aligned_offset + align_up(file.size, BLOCK_SIZE); + } + } + + // Symlink targets also need data blocks + for (si, symlink) in walk.symlinks.iter().enumerate() { + if !symlink.target.is_empty() { + let aligned_offset = align_up(current_data_offset, BLOCK_SIZE); + file_regions.push(FileRegion { + file_index: walk.files.len() + si, // files.len() + symlink index + offset_in_erofs: aligned_offset, + size: symlink.target.len() as u64, + }); + current_data_offset = + aligned_offset + align_up(symlink.target.len() as u64, BLOCK_SIZE); + } + } + + let total_size = align_up(current_data_offset, BLOCK_SIZE); + let total_blocks = total_size / BLOCK_SIZE; + + // Phase 4: Build metadata blob + let mut metadata = vec![0u8; (dir_blocks_offset + dir_data_size) as usize]; + + // Write superblock at offset 1024 + write_superblock( + &mut metadata, + total_inodes as u32, + total_blocks as u32, + 0, // root nid + ); + + // Write inodes + // Directories + for (i, dir) in walk.dirs.iter().enumerate() { + let dir_size = compute_dir_size(dir, walk); + let dir_block = (dir_block_offsets[i] - dir_blocks_offset) / BLOCK_SIZE; + write_compact_inode( + &mut metadata, + inode_table_offset as usize + (dir.inode_id as usize * 32), + 0o040000 | (dir.mode & 0o7777), + dir.uid as u16, + dir.gid as u16, + dir_size as u32, + dir.mtime as u32, + 2 + dir + .children + .iter() + .filter(|c| matches!(c, ChildRef::Dir(_))) + .count() as u16, + EROFS_INODE_FLAT_PLAIN, + (dir_blocks_offset / BLOCK_SIZE + dir_block) as u32, + ); + } + + // Regular files + for (i, file) in walk.files.iter().enumerate() { + let data_block = if file.size > 0 { + let fr = file_regions.iter().find(|r| r.file_index == i); + fr.map(|r| (r.offset_in_erofs / BLOCK_SIZE) as u32) + .unwrap_or(0) + } else { + 0 + }; + write_compact_inode( + &mut metadata, + inode_table_offset as usize + (file.inode_id as usize * 32), + 0o100000 | (file.mode & 0o7777), + file.uid as u16, + file.gid as u16, + file.size as u32, + file.mtime as u32, + file.nlink as u16, + EROFS_INODE_FLAT_PLAIN, + data_block, + ); + } + + // Symlinks: FlatPlain with target in data region + // File regions for symlinks start after file regions + let file_region_count = walk.files.iter().filter(|f| f.size > 0).count(); + let mut sym_fr_idx = file_region_count; + for symlink in &walk.symlinks { + let data_block = if !symlink.target.is_empty() { + let fr = &file_regions[sym_fr_idx]; + sym_fr_idx += 1; + (fr.offset_in_erofs / BLOCK_SIZE) as u32 + } else { + 0 + }; + + write_compact_inode( + &mut metadata, + inode_table_offset as usize + (symlink.inode_id as usize * 32), + 0o120000 | (symlink.mode & 0o7777), + symlink.uid as u16, + symlink.gid as u16, + symlink.target.len() as u32, + symlink.mtime as u32, + 1, + EROFS_INODE_FLAT_PLAIN, + data_block, + ); + } + + // Write directory data + let dir_start = dir_blocks_offset as usize; + if dir_start + dir_data.len() <= metadata.len() { + metadata[dir_start..dir_start + dir_data.len()].copy_from_slice(&dir_data); + } + + Ok(ErofsLayout { + metadata, + file_regions, + total_size, + }) +} + +fn write_superblock(buf: &mut [u8], inodes: u32, blocks: u32, root_nid: u16) { + let off = SUPERBLOCK_OFFSET as usize; + // magic + buf[off..off + 4].copy_from_slice(&EROFS_MAGIC.to_le_bytes()); + // checksum (unused) + // feature_compat + buf[off + 8..off + 12].copy_from_slice(&0u32.to_le_bytes()); + // blkszbits + buf[off + 12] = BLOCK_BITS; + // sb_extslots + buf[off + 13] = 0; + // root_nid + buf[off + 14..off + 16].copy_from_slice(&root_nid.to_le_bytes()); + // inos + buf[off + 16..off + 24].copy_from_slice(&(inodes as u64).to_le_bytes()); + // build_time + buf[off + 24..off + 32].copy_from_slice(&0u64.to_le_bytes()); + // build_time_nsec + buf[off + 32..off + 36].copy_from_slice(&0u32.to_le_bytes()); + // blocks + buf[off + 36..off + 40].copy_from_slice(&blocks.to_le_bytes()); + // meta_blkaddr (inode table starts at block 1) + buf[off + 40..off + 44].copy_from_slice(&1u32.to_le_bytes()); + // xattr_blkaddr + buf[off + 44..off + 48].copy_from_slice(&0u32.to_le_bytes()); + // uuid (16 bytes) + // volume_name (16 bytes) + // feature_incompat + buf[off + 80..off + 84].copy_from_slice(&0u32.to_le_bytes()); + // available_compr_algs (union with checksum) + // lz4_max_distance +} + +fn write_compact_inode( + buf: &mut [u8], + offset: usize, + mode: u32, + uid: u16, + gid: u16, + size: u32, + _mtime: u32, + nlink: u16, + data_layout: u16, + u_field: u32, +) { + if offset + 32 > buf.len() { + return; + } + + // format: layout(compact=0) | data_layout << 1 + let format = (EROFS_INODE_LAYOUT_COMPACT) | (data_layout << 1); + buf[offset..offset + 2].copy_from_slice(&format.to_le_bytes()); + // xattr_icount + buf[offset + 2..offset + 4].copy_from_slice(&0u16.to_le_bytes()); + // mode + buf[offset + 4..offset + 6].copy_from_slice(&(mode as u16).to_le_bytes()); + // nlink + buf[offset + 6..offset + 8].copy_from_slice(&nlink.to_le_bytes()); + // size + buf[offset + 8..offset + 12].copy_from_slice(&size.to_le_bytes()); + // reserved + buf[offset + 12..offset + 16].copy_from_slice(&0u32.to_le_bytes()); + // u (union: raw_blkaddr for FlatPlain) + buf[offset + 16..offset + 20].copy_from_slice(&u_field.to_le_bytes()); + // ino (on-disk inode number, optional) + buf[offset + 20..offset + 24].copy_from_slice(&0u32.to_le_bytes()); + // uid + buf[offset + 24..offset + 26].copy_from_slice(&uid.to_le_bytes()); + // gid + buf[offset + 26..offset + 28].copy_from_slice(&gid.to_le_bytes()); + // reserved2 + buf[offset + 28..offset + 32].copy_from_slice(&0u32.to_le_bytes()); +} + +fn write_dir_blocks(buf: &mut Vec, entries: &[DirEntryOnDisk]) { + // EROFS directories are split into 4096-byte blocks. + // Each block contains: [headers...][names...] + // header = 12 bytes: nid(8) + nameoff(2) + file_type(1) + reserved(1) + // nameoff is relative to block start. + + let mut remaining = entries; + + while !remaining.is_empty() { + // Determine how many entries fit in this block + let mut count = 0; + let mut total_size: usize = 0; + for entry in remaining { + let entry_size = 12 + entry.name.len(); + if total_size + entry_size > BLOCK_SIZE as usize && count > 0 { + break; + } + total_size += entry_size; + count += 1; + } + + let block_entries = &remaining[..count]; + remaining = &remaining[count..]; + + // Write headers + let header_total = 12 * block_entries.len(); + let mut nameoff = header_total as u16; + for entry in block_entries { + buf.extend_from_slice(&(entry.nid as u64).to_le_bytes()); + buf.extend_from_slice(&nameoff.to_le_bytes()); + buf.push(entry.file_type); + buf.push(0); + nameoff += entry.name.len() as u16; + } + + // Write names + for entry in block_entries { + buf.extend_from_slice(&entry.name); + } + + // Pad to block boundary (except last block which is sized by inode.size) + if !remaining.is_empty() { + let written = total_size; + let pad = BLOCK_SIZE as usize - (written % BLOCK_SIZE as usize); + if pad < BLOCK_SIZE as usize { + buf.resize(buf.len() + pad, 0); + } + } + } +} + +fn compute_dir_size(dir: &DirInfo, walk: &WalkResult) -> u64 { + // Build entry list to accurately compute size including block splits + let mut entries = Vec::new(); + entries.push(DirEntryOnDisk { + nid: 0, + file_type: EROFS_FT_DIR, + name: b".".to_vec(), + }); + entries.push(DirEntryOnDisk { + nid: 0, + file_type: EROFS_FT_DIR, + name: b"..".to_vec(), + }); + for child in &dir.children { + let name_len = match child { + ChildRef::Dir(di) => walk.dirs[*di].name.len(), + ChildRef::File(fi) => walk.files[*fi] + .host_path + .file_name() + .unwrap_or_default() + .len(), + ChildRef::Symlink(si) => walk.symlinks[*si].name.len(), + }; + entries.push(DirEntryOnDisk { + nid: 0, + file_type: 0, + name: vec![0; name_len], + }); + } + + // Simulate block splitting to get total size + let mut total = 0u64; + let mut remaining = &entries[..]; + while !remaining.is_empty() { + let mut count = 0; + let mut block_size = 0usize; + for entry in remaining { + let entry_size = 12 + entry.name.len(); + if block_size + entry_size > BLOCK_SIZE as usize && count > 0 { + break; + } + block_size += entry_size; + count += 1; + } + remaining = &remaining[count..]; + if remaining.is_empty() { + total += block_size as u64; // last block: actual size + } else { + total += BLOCK_SIZE; // full block + } + } + total +} + +fn align_up(val: u64, align: u64) -> u64 { + (val + align - 1) & !(align - 1) +} + +pub fn build_erofs_regions(layout: &ErofsLayout, walk: &WalkResult) -> Vec { + let files = &walk.files; + let mut regions = Vec::new(); + + // Metadata region (superblock + inode table + dir blocks) + regions.push(Region { + start: 0, + len: layout.metadata.len() as u64, + region_type: RegionType::Data(Arc::new(layout.metadata.clone())), + }); + + // File and symlink data regions + for fr in &layout.file_regions { + // Padding gap + let current_end = regions.last().map(|r| r.start + r.len).unwrap_or(0); + if fr.offset_in_erofs > current_end { + regions.push(Region { + start: current_end, + len: fr.offset_in_erofs - current_end, + region_type: RegionType::Zero, + }); + } + + if fr.file_index < files.len() { + // Regular file: read from host + regions.push(Region { + start: fr.offset_in_erofs, + len: fr.size, + region_type: RegionType::File { + path: files[fr.file_index].host_path.clone(), + }, + }); + } else { + // Symlink target: inline data + let sym_idx = fr.file_index - files.len(); + if sym_idx < walk.symlinks.len() { + // Pad symlink target to fill the block + let mut data = walk.symlinks[sym_idx].target.clone(); + data.resize(fr.size as usize, 0); + regions.push(Region { + start: fr.offset_in_erofs, + len: fr.size, + region_type: RegionType::Data(Arc::new(data)), + }); + } + } + + // Padding to block boundary + let end = fr.offset_in_erofs + fr.size; + let aligned_end = align_up(end, BLOCK_SIZE); + if aligned_end > end { + regions.push(Region { + start: end, + len: aligned_end - end, + region_type: RegionType::Zero, + }); + } + } + + // Ensure total size + let last_end = regions.last().map(|r| r.start + r.len).unwrap_or(0); + if last_end < layout.total_size { + regions.push(Region { + start: last_end, + len: layout.total_size - last_end, + region_type: RegionType::Zero, + }); + } + + regions +} diff --git a/crates/nbdkit-erofs-plugin/src/fat32.rs b/crates/nbdkit-erofs-plugin/src/fat32.rs new file mode 100644 index 000000000..ecc6992d5 --- /dev/null +++ b/crates/nbdkit-erofs-plugin/src/fat32.rs @@ -0,0 +1,548 @@ +//! FAT32 ESP generation using the regions pattern. +//! +//! Generates a virtual FAT32 filesystem with boot files for EFI boot. +//! Metadata (BPB, FAT tables, directory entries) are in-memory Data regions. +//! File data uses File regions for lazy pread from source files. + +use crate::regions::{Region, RegionType}; +use std::path::PathBuf; +use std::sync::Arc; + +const SECTOR_SIZE: u64 = 512; +const CLUSTER_SIZE: u64 = 512; +const SECTORS_PER_CLUSTER: u64 = 1; +const RESERVED_SECTORS: u64 = 32; +const NUM_FATS: u64 = 2; +const DIR_ENTRY_SIZE: u64 = 32; + +const FAT32_EOC: u32 = 0x0FFF_FFFF; +const FAT32_MEDIA: u32 = 0x0FFF_FFF8; + +// Fixed cluster assignments for the ESP directory structure. +// Root directory is always cluster 2 per FAT32 spec. +const CLUSTER_ROOT: u32 = 2; +const CLUSTER_EFI: u32 = 3; +const CLUSTER_EFI_BOOT: u32 = 4; +const CLUSTER_BOOT: u32 = 5; + +struct FatFile { + name_8_3: [u8; 11], + size: u64, + regions: Vec, +} + +pub enum FileDataRegion { + FromFile { path: PathBuf, len: u64 }, + FromData(Vec), + Zero(u64), +} + +struct FatDir { + name_8_3: [u8; 11], + cluster: u32, + entries: Vec, +} + +enum FatDirChild { + Dir(usize), + File(usize), +} + +fn clusters_for(size: u64) -> u64 { + if size == 0 { + 1 + } else { + (size + CLUSTER_SIZE - 1) / CLUSTER_SIZE + } +} + +fn make_8_3(name: &str, ext: &str) -> [u8; 11] { + let mut r = [b' '; 11]; + for (i, b) in name.bytes().take(8).enumerate() { + r[i] = b; + } + for (i, b) in ext.bytes().take(3).enumerate() { + r[8 + i] = b; + } + r +} + +pub fn build_esp_regions( + grub_path: &std::path::Path, + grub_size: u64, + grub_cfg: &[u8], + kernel_path: &std::path::Path, + kernel_size: u64, + initrd_parts: Vec<(FileDataRegion, u64)>, + initrd_total_size: u64, +) -> (Vec, u64) { + // Files + let mut files: Vec = Vec::new(); + + // BOOTAA64.EFI + files.push(FatFile { + name_8_3: make_8_3("BOOTAA64", "EFI"), + size: grub_size, + regions: vec![FileDataRegion::FromFile { + path: grub_path.to_path_buf(), + len: grub_size, + }], + }); + + // GRUB.CFG + files.push(FatFile { + name_8_3: make_8_3("GRUB", "CFG"), + size: grub_cfg.len() as u64, + regions: vec![FileDataRegion::FromData(grub_cfg.to_vec())], + }); + + // VMLINUZ + files.push(FatFile { + name_8_3: make_8_3("VMLINUZ", ""), + size: kernel_size, + regions: vec![FileDataRegion::FromFile { + path: kernel_path.to_path_buf(), + len: kernel_size, + }], + }); + + // INITRD.IMG + files.push(FatFile { + name_8_3: make_8_3("INITRD", "IMG"), + size: initrd_total_size, + regions: initrd_parts.into_iter().map(|(r, _)| r).collect(), + }); + + // Directory structure: + // / (root, cluster 2) → EFI/, boot/ + // /EFI (cluster 3) → BOOT/ + // /EFI/BOOT (cluster 4) → BOOTAA64.EFI, GRUB.CFG + // /boot (cluster 5) → VMLINUZ, INITRD.IMG + // Note: /EFI/BOOT and /boot both use 8.3 name "BOOT" but are in different + // parent directories so there is no conflict in the FAT32 namespace. + let dirs = vec![ + FatDir { + name_8_3: make_8_3("", ""), + cluster: CLUSTER_ROOT, + entries: vec![FatDirChild::Dir(1), FatDirChild::Dir(3)], + }, + FatDir { + name_8_3: make_8_3("EFI", ""), + cluster: CLUSTER_EFI, + entries: vec![FatDirChild::Dir(2)], + }, + FatDir { + name_8_3: make_8_3("BOOT", ""), + cluster: CLUSTER_EFI_BOOT, + entries: vec![FatDirChild::File(0), FatDirChild::File(1)], + }, + FatDir { + name_8_3: make_8_3("BOOT", ""), + cluster: CLUSTER_BOOT, + entries: vec![FatDirChild::File(2), FatDirChild::File(3)], + }, + ]; + + let dir_clusters = dirs.len() as u32; + + // Assign file clusters (starting after directory clusters) + let mut file_start_clusters: Vec = Vec::new(); + let mut next_cluster = 2 + dir_clusters; + for f in &files { + file_start_clusters.push(next_cluster); + next_cluster += clusters_for(f.size) as u32; + } + let total_clusters = next_cluster; + let data_clusters = total_clusters - 2; + + // FAT table + let fat_entries = total_clusters as usize; + let fat_bytes = ((fat_entries * 4 + SECTOR_SIZE as usize - 1) / SECTOR_SIZE as usize) + * SECTOR_SIZE as usize; + let fat_sectors = fat_bytes as u64 / SECTOR_SIZE; + + let mut fat = vec![0u8; fat_bytes]; + // Entry 0: media descriptor + fat[0..4].copy_from_slice(&FAT32_MEDIA.to_le_bytes()); + // Entry 1: EOC + fat[4..8].copy_from_slice(&FAT32_EOC.to_le_bytes()); + + // Directory clusters (each is single-cluster, EOC) + for d in &dirs { + let off = d.cluster as usize * 4; + fat[off..off + 4].copy_from_slice(&FAT32_EOC.to_le_bytes()); + } + + // File cluster chains + for (fi, f) in files.iter().enumerate() { + let start = file_start_clusters[fi]; + let num = clusters_for(f.size) as u32; + for c in 0..num { + let cluster = start + c; + let off = cluster as usize * 4; + if c == num - 1 { + fat[off..off + 4].copy_from_slice(&FAT32_EOC.to_le_bytes()); + } else { + fat[off..off + 4].copy_from_slice(&(cluster + 1).to_le_bytes()); + } + } + } + + // Data region start (in sectors) + let data_start_sector = RESERVED_SECTORS + NUM_FATS * fat_sectors; + + // Build directory entry blocks + let mut dir_blocks: Vec> = Vec::new(); + for (di, d) in dirs.iter().enumerate() { + let mut block = vec![0u8; CLUSTER_SIZE as usize]; + let mut pos = 0usize; + + // "." and ".." entries for subdirectories + if di > 0 { + write_dir_entry(&mut block, pos, b". ", 0x10, d.cluster, 0); + pos += DIR_ENTRY_SIZE as usize; + // Parent cluster: dirs at index 1 (EFI) and 3 (boot) are children of root (0). + // Dir at index 2 (EFI/BOOT) is a child of EFI (dirs[1]). + debug_assert!(dirs.len() == 4, "directory structure changed"); + let parent_cluster = if di == 1 || di == 3 { + 0u32 + } else { + dirs[1].cluster + }; + write_dir_entry(&mut block, pos, b".. ", 0x10, parent_cluster, 0); + pos += DIR_ENTRY_SIZE as usize; + } + + for child in &d.entries { + match child { + FatDirChild::Dir(idx) => { + let cd = &dirs[*idx]; + write_dir_entry(&mut block, pos, &cd.name_8_3, 0x10, cd.cluster, 0); + } + FatDirChild::File(idx) => { + let cf = &files[*idx]; + write_dir_entry( + &mut block, + pos, + &cf.name_8_3, + 0x20, + file_start_clusters[*idx], + cf.size, + ); + } + } + pos += DIR_ENTRY_SIZE as usize; + } + dir_blocks.push(block); + } + + // Total size of ESP partition + let total_sectors = data_start_sector + data_clusters as u64 * SECTORS_PER_CLUSTER; + let total_size = total_sectors * SECTOR_SIZE; + + // BPB (Boot Parameter Block) + let bpb = build_bpb( + total_sectors as u32, + fat_sectors as u32, + data_clusters as u64, + ); + + // FSInfo + let fsinfo = build_fsinfo( + (data_clusters as u32).saturating_sub( + dir_clusters as u32 + + files + .iter() + .map(|f| clusters_for(f.size) as u32) + .sum::(), + ), + next_cluster, + ); + + // Assemble regions + let mut regions: Vec = Vec::new(); + let mut offset = 0u64; + + // Sector 0: BPB + regions.push(Region { + start: offset, + len: SECTOR_SIZE, + region_type: RegionType::Data(Arc::new(bpb.clone())), + }); + offset += SECTOR_SIZE; + + // Sector 1: FSInfo + regions.push(Region { + start: offset, + len: SECTOR_SIZE, + region_type: RegionType::Data(Arc::new(fsinfo.clone())), + }); + offset += SECTOR_SIZE; + + // Sectors 2-5: zero padding + let pad_to_backup = 4 * SECTOR_SIZE; + regions.push(Region { + start: offset, + len: pad_to_backup, + region_type: RegionType::Zero, + }); + offset += pad_to_backup; + + // Sector 6: Backup BPB + regions.push(Region { + start: offset, + len: SECTOR_SIZE, + region_type: RegionType::Data(Arc::new(bpb)), + }); + offset += SECTOR_SIZE; + + // Sector 7: Backup FSInfo + regions.push(Region { + start: offset, + len: SECTOR_SIZE, + region_type: RegionType::Data(Arc::new(fsinfo)), + }); + offset += SECTOR_SIZE; + + // Sectors 8-31: zero padding to reserved end + let remaining_reserved = (RESERVED_SECTORS * SECTOR_SIZE) - offset; + if remaining_reserved > 0 { + regions.push(Region { + start: offset, + len: remaining_reserved, + region_type: RegionType::Zero, + }); + offset += remaining_reserved; + } + + // FAT1 + let fat_data = Arc::new(fat.clone()); + regions.push(Region { + start: offset, + len: fat_bytes as u64, + region_type: RegionType::Data(fat_data.clone()), + }); + offset += fat_bytes as u64; + + // FAT2 (copy) + regions.push(Region { + start: offset, + len: fat_bytes as u64, + region_type: RegionType::Data(fat_data), + }); + offset += fat_bytes as u64; + + // Data area: directory clusters + for block in &dir_blocks { + regions.push(Region { + start: offset, + len: CLUSTER_SIZE, + region_type: RegionType::Data(Arc::new(block.clone())), + }); + offset += CLUSTER_SIZE; + } + + // Data area: file clusters + for (_fi, f) in files.iter().enumerate() { + let mut file_offset = 0u64; + + for part in &f.regions { + match part { + FileDataRegion::FromFile { path, len } => { + regions.push(Region { + start: offset, + len: *len, + region_type: RegionType::File { path: path.clone() }, + }); + offset += len; + file_offset += len; + } + FileDataRegion::FromData(data) => { + let len = data.len() as u64; + regions.push(Region { + start: offset, + len, + region_type: RegionType::Data(Arc::new(data.clone())), + }); + offset += len; + file_offset += len; + } + FileDataRegion::Zero(len) => { + if *len > 0 { + regions.push(Region { + start: offset, + len: *len, + region_type: RegionType::Zero, + }); + offset += len; + file_offset += len; + } + } + } + } + + // Pad to cluster boundary + let used_in_last = file_offset % CLUSTER_SIZE; + if used_in_last > 0 { + let pad = CLUSTER_SIZE - used_in_last; + regions.push(Region { + start: offset, + len: pad, + region_type: RegionType::Zero, + }); + offset += pad; + } + } + + // Ensure total_size is correct + debug_assert!( + offset <= total_size, + "regions exceeded total_size: {} > {}", + offset, + total_size + ); + if offset < total_size { + regions.push(Region { + start: offset, + len: total_size - offset, + region_type: RegionType::Zero, + }); + } + + (regions, total_size) +} + +/// Build initrd regions: original file + 4-byte alignment + CPIO data. +pub fn build_initrd_regions( + initrd_path: &std::path::Path, + initrd_size: u64, + units_cpio: &[u8], + ssh_cpio: Option<&[u8]>, +) -> (Vec<(FileDataRegion, u64)>, u64) { + let mut parts = Vec::new(); + let mut total = 0u64; + + // Original initramfs + parts.push(( + FileDataRegion::FromFile { + path: initrd_path.to_path_buf(), + len: initrd_size, + }, + initrd_size, + )); + total += initrd_size; + + // 4-byte alignment padding + let pad = ((4 - (initrd_size % 4)) % 4) as u64; + if pad > 0 { + parts.push((FileDataRegion::Zero(pad), pad)); + total += pad; + } + + // Units CPIO + let len = units_cpio.len() as u64; + parts.push((FileDataRegion::FromData(units_cpio.to_vec()), len)); + total += len; + + // SSH CPIO (if provided) + if let Some(ssh) = ssh_cpio { + let pad2 = ((4 - (total % 4)) % 4) as u64; + if pad2 > 0 { + parts.push((FileDataRegion::Zero(pad2), pad2)); + total += pad2; + } + let len = ssh.len() as u64; + parts.push((FileDataRegion::FromData(ssh.to_vec()), len)); + total += len; + } + + (parts, total) +} + +fn write_dir_entry(buf: &mut [u8], pos: usize, name: &[u8; 11], attr: u8, cluster: u32, size: u64) { + buf[pos..pos + 11].copy_from_slice(name); + buf[pos + 11] = attr; + // cluster high + buf[pos + 20..pos + 22].copy_from_slice(&((cluster >> 16) as u16).to_le_bytes()); + // cluster low + buf[pos + 26..pos + 28].copy_from_slice(&(cluster as u16).to_le_bytes()); + // file size (32-bit) + buf[pos + 28..pos + 32].copy_from_slice(&(size as u32).to_le_bytes()); +} + +fn build_bpb(total_sectors: u32, fat_sectors: u32, _data_clusters: u64) -> Vec { + let mut bpb = vec![0u8; SECTOR_SIZE as usize]; + // Jump instruction + bpb[0] = 0xEB; + bpb[1] = 0x58; + bpb[2] = 0x90; + // OEM name + bpb[3..11].copy_from_slice(b"MSWIN4.1"); + // Bytes per sector + bpb[11..13].copy_from_slice(&(SECTOR_SIZE as u16).to_le_bytes()); + // Sectors per cluster + bpb[13] = SECTORS_PER_CLUSTER as u8; + // Reserved sectors + bpb[14..16].copy_from_slice(&(RESERVED_SECTORS as u16).to_le_bytes()); + // Number of FATs + bpb[16] = NUM_FATS as u8; + // Root entry count (0 for FAT32) + bpb[17..19].copy_from_slice(&0u16.to_le_bytes()); + // Total sectors 16 (0 for FAT32) + bpb[19..21].copy_from_slice(&0u16.to_le_bytes()); + // Media type + bpb[21] = 0xF8; + // Sectors per FAT 16 (0 for FAT32) + bpb[22..24].copy_from_slice(&0u16.to_le_bytes()); + // Sectors per track + bpb[24..26].copy_from_slice(&32u16.to_le_bytes()); + // Number of heads + bpb[26..28].copy_from_slice(&64u16.to_le_bytes()); + // Hidden sectors + bpb[28..32].copy_from_slice(&0u32.to_le_bytes()); + // Total sectors 32 + bpb[32..36].copy_from_slice(&total_sectors.to_le_bytes()); + // --- FAT32 specific --- + // Sectors per FAT + bpb[36..40].copy_from_slice(&fat_sectors.to_le_bytes()); + // Extended flags + bpb[40..42].copy_from_slice(&0u16.to_le_bytes()); + // FS version + bpb[42..44].copy_from_slice(&0u16.to_le_bytes()); + // Root cluster + bpb[44..48].copy_from_slice(&2u32.to_le_bytes()); + // FSInfo sector + bpb[48..50].copy_from_slice(&1u16.to_le_bytes()); + // Backup boot sector + bpb[50..52].copy_from_slice(&6u16.to_le_bytes()); + // Reserved (12 bytes, already zero) + // Drive number + bpb[64] = 0x80; + // Boot signature + bpb[66] = 0x29; + // Volume serial number + bpb[67..71].copy_from_slice(&0x42424242u32.to_le_bytes()); + // Volume label + bpb[71..82].copy_from_slice(b"BCVK-ESP "); + // Filesystem type + bpb[82..90].copy_from_slice(b"FAT32 "); + // Boot signature + bpb[510] = 0x55; + bpb[511] = 0xAA; + bpb +} + +fn build_fsinfo(free_clusters: u32, next_free: u32) -> Vec { + let mut fs = vec![0u8; SECTOR_SIZE as usize]; + // Signature1 + fs[0..4].copy_from_slice(&0x41615252u32.to_le_bytes()); + // Signature2 + fs[484..488].copy_from_slice(&0x61417272u32.to_le_bytes()); + // Free cluster count + fs[488..492].copy_from_slice(&free_clusters.to_le_bytes()); + // Next free cluster + fs[492..496].copy_from_slice(&next_free.to_le_bytes()); + // Signature3 + fs[508..512].copy_from_slice(&0xAA550000u32.to_le_bytes()); + fs +} diff --git a/crates/nbdkit-erofs-plugin/src/gpt.rs b/crates/nbdkit-erofs-plugin/src/gpt.rs new file mode 100644 index 000000000..88e8bcf44 --- /dev/null +++ b/crates/nbdkit-erofs-plugin/src/gpt.rs @@ -0,0 +1,290 @@ +use crate::regions::{Region, RegionType}; +use std::sync::Arc; + +const SECTOR_SIZE: u64 = 512; +const GPT_HEADER_SIZE: u64 = 92; +const GPT_ENTRY_SIZE: u64 = 128; +const GPT_ENTRIES: u64 = 128; + +// EFI System Partition type GUID +const ESP_TYPE_GUID: [u8; 16] = [ + 0x28, 0x73, 0x2A, 0xC1, 0x1F, 0xF8, 0xD2, 0x11, 0xBA, 0x4B, 0x00, 0xA0, 0xC9, 0x3E, 0xC9, 0x3B, +]; + +// Linux filesystem type GUID +const LINUX_TYPE_GUID: [u8; 16] = [ + 0xAF, 0x3D, 0xC6, 0x0F, 0x83, 0x84, 0x72, 0x47, 0x8E, 0x79, 0x3D, 0x69, 0xD8, 0x47, 0x7D, 0xE4, +]; + +pub struct DiskLayout { + pub regions: Vec, + pub total_size: u64, +} + +pub fn build_gpt_disk( + esp_regions: Vec, + esp_size: u64, + erofs_regions: Vec, + erofs_size: u64, +) -> std::io::Result { + // GPT layout: + // LBA 0: Protective MBR + // LBA 1: GPT Header + // LBA 2-33: Partition Table (128 entries * 128 bytes = 16384 bytes = 32 sectors) + // LBA 34+: ESP partition (aligned to 2048 sectors / 1MB) + // After ESP: EROFS partition + // End: Backup GPT + + let partition_table_sectors = (GPT_ENTRIES * GPT_ENTRY_SIZE + SECTOR_SIZE - 1) / SECTOR_SIZE; + let first_usable_lba = 34u64; // standard + let esp_start_lba = 2048u64; // 1MB aligned + let esp_sectors = (esp_size + SECTOR_SIZE - 1) / SECTOR_SIZE; + let erofs_start_lba = esp_start_lba + esp_sectors; + // Align to 2048 sectors + let erofs_start_lba = (erofs_start_lba + 2047) & !2047; + let erofs_sectors = (erofs_size + SECTOR_SIZE - 1) / SECTOR_SIZE; + let last_usable_lba = erofs_start_lba + erofs_sectors - 1; + let backup_table_lba = last_usable_lba + 1; + let backup_header_lba = backup_table_lba + partition_table_sectors; + let total_sectors = backup_header_lba + 1; + let total_size = total_sectors * SECTOR_SIZE; + + // Build partition table entries + let mut partition_table = vec![0u8; (GPT_ENTRIES * GPT_ENTRY_SIZE) as usize]; + + // Entry 0: ESP + write_gpt_entry( + &mut partition_table, + 0, + &ESP_TYPE_GUID, + esp_start_lba, + esp_start_lba + esp_sectors - 1, + b"EFI System", + ); + + // Entry 1: EROFS rootfs + write_gpt_entry( + &mut partition_table, + 1, + &LINUX_TYPE_GUID, + erofs_start_lba, + erofs_start_lba + erofs_sectors - 1, + b"root", + ); + + let partition_table_crc = crc32fast::hash(&partition_table); + + // Build GPT header + let mut gpt_header = vec![0u8; SECTOR_SIZE as usize]; + write_gpt_header( + &mut gpt_header, + 1, // my LBA + backup_header_lba, + first_usable_lba, + last_usable_lba, + 2, // partition table LBA + 2, // num entries used + partition_table_crc, + ); + + // Build backup GPT header + let mut backup_header = vec![0u8; SECTOR_SIZE as usize]; + write_gpt_header( + &mut backup_header, + backup_header_lba, + 1, // alternate LBA + first_usable_lba, + last_usable_lba, + backup_table_lba, + 2, + partition_table_crc, + ); + + // Build protective MBR + let mut mbr = vec![0u8; SECTOR_SIZE as usize]; + write_protective_mbr(&mut mbr, total_sectors); + + // Assemble regions + let mut regions = Vec::new(); + + // MBR + regions.push(Region { + start: 0, + len: SECTOR_SIZE, + region_type: RegionType::Data(Arc::new(mbr)), + }); + + // GPT Header + regions.push(Region { + start: SECTOR_SIZE, + len: SECTOR_SIZE, + region_type: RegionType::Data(Arc::new(gpt_header)), + }); + + // Partition Table + regions.push(Region { + start: 2 * SECTOR_SIZE, + len: partition_table.len() as u64, + region_type: RegionType::Data(Arc::new(partition_table.clone())), + }); + + // Padding to ESP start + let pad_start = 2 * SECTOR_SIZE + partition_table.len() as u64; + let esp_byte_offset = esp_start_lba * SECTOR_SIZE; + if esp_byte_offset > pad_start { + regions.push(Region { + start: pad_start, + len: esp_byte_offset - pad_start, + region_type: RegionType::Zero, + }); + } + + // ESP partition (from provided regions, offset-adjusted) + for mut r in esp_regions { + r.start += esp_byte_offset; + regions.push(r); + } + + // Padding between ESP and EROFS + let esp_end = esp_byte_offset + esp_size; + let erofs_byte_offset = erofs_start_lba * SECTOR_SIZE; + if erofs_byte_offset > esp_end { + regions.push(Region { + start: esp_end, + len: erofs_byte_offset - esp_end, + region_type: RegionType::Zero, + }); + } + + // EROFS partition (offset all regions) + for mut r in erofs_regions { + r.start += erofs_byte_offset; + regions.push(r); + } + + // Padding to backup GPT + let erofs_end = erofs_byte_offset + erofs_size; + let backup_table_offset = backup_table_lba * SECTOR_SIZE; + if backup_table_offset > erofs_end { + regions.push(Region { + start: erofs_end, + len: backup_table_offset - erofs_end, + region_type: RegionType::Zero, + }); + } + + // Backup partition table + regions.push(Region { + start: backup_table_offset, + len: partition_table.len() as u64, + region_type: RegionType::Data(Arc::new(partition_table)), + }); + + // Backup GPT header + regions.push(Region { + start: backup_header_lba * SECTOR_SIZE, + len: SECTOR_SIZE, + region_type: RegionType::Data(Arc::new(backup_header)), + }); + + Ok(DiskLayout { + regions, + total_size, + }) +} + +fn write_gpt_entry( + table: &mut [u8], + index: usize, + type_guid: &[u8; 16], + first_lba: u64, + last_lba: u64, + name: &[u8], +) { + let off = index * GPT_ENTRY_SIZE as usize; + // Partition type GUID + table[off..off + 16].copy_from_slice(type_guid); + // Unique partition GUID (generate simple one from index) + let mut unique = [0u8; 16]; + unique[0] = index as u8 + 1; + unique[15] = 0x42; + table[off + 16..off + 32].copy_from_slice(&unique); + // First LBA + table[off + 32..off + 40].copy_from_slice(&first_lba.to_le_bytes()); + // Last LBA + table[off + 40..off + 48].copy_from_slice(&last_lba.to_le_bytes()); + // Attributes + table[off + 48..off + 56].copy_from_slice(&0u64.to_le_bytes()); + // Name (UTF-16LE) + for (i, &b) in name.iter().enumerate().take(36) { + table[off + 56 + i * 2] = b; + table[off + 56 + i * 2 + 1] = 0; + } +} + +fn write_gpt_header( + buf: &mut [u8], + my_lba: u64, + alternate_lba: u64, + first_usable: u64, + last_usable: u64, + partition_table_lba: u64, + _num_entries: u32, + partition_crc: u32, +) { + // Signature "EFI PART" + buf[0..8].copy_from_slice(b"EFI PART"); + // Revision 1.0 + buf[8..12].copy_from_slice(&0x00010000u32.to_le_bytes()); + // Header size + buf[12..16].copy_from_slice(&(GPT_HEADER_SIZE as u32).to_le_bytes()); + // Header CRC32 (computed after all fields set) + // My LBA + buf[24..32].copy_from_slice(&my_lba.to_le_bytes()); + // Alternate LBA + buf[32..40].copy_from_slice(&alternate_lba.to_le_bytes()); + // First usable LBA + buf[40..48].copy_from_slice(&first_usable.to_le_bytes()); + // Last usable LBA + buf[48..56].copy_from_slice(&last_usable.to_le_bytes()); + // Fixed disk GUID for reproducible builds (not security-sensitive) + const DISK_GUID: [u8; 16] = [ + 0xAA, 0xBB, 0xCC, 0xDD, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, + 0xCC, + ]; + let disk_guid = DISK_GUID; + buf[56..72].copy_from_slice(&disk_guid); + // Partition entry start LBA + buf[72..80].copy_from_slice(&partition_table_lba.to_le_bytes()); + // Number of partition entries + buf[80..84].copy_from_slice(&(GPT_ENTRIES as u32).to_le_bytes()); + // Size of partition entry + buf[84..88].copy_from_slice(&(GPT_ENTRY_SIZE as u32).to_le_bytes()); + // Partition table CRC32 + buf[88..92].copy_from_slice(&partition_crc.to_le_bytes()); + + // Compute header CRC32 + buf[16..20].copy_from_slice(&0u32.to_le_bytes()); // zero CRC field first + let crc = crc32fast::hash(&buf[0..GPT_HEADER_SIZE as usize]); + buf[16..20].copy_from_slice(&crc.to_le_bytes()); +} + +fn write_protective_mbr(buf: &mut [u8], total_sectors: u64) { + // Partition entry at offset 446 + buf[446] = 0x00; // not bootable + buf[447] = 0x00; // CHS start + buf[448] = 0x02; + buf[449] = 0x00; + buf[450] = 0xEE; // type: GPT protective + buf[451] = 0xFF; // CHS end + buf[452] = 0xFF; + buf[453] = 0xFF; + // LBA start + buf[454..458].copy_from_slice(&1u32.to_le_bytes()); + // LBA size + let size = std::cmp::min(total_sectors - 1, 0xFFFFFFFF) as u32; + buf[458..462].copy_from_slice(&size.to_le_bytes()); + // Boot signature + buf[510] = 0x55; + buf[511] = 0xAA; +} diff --git a/crates/nbdkit-erofs-plugin/src/initramfs.rs b/crates/nbdkit-erofs-plugin/src/initramfs.rs new file mode 100644 index 000000000..87d0d7732 --- /dev/null +++ b/crates/nbdkit-erofs-plugin/src/initramfs.rs @@ -0,0 +1,182 @@ +//! CPIO newc archive generation for initramfs append. + +use std::io::Write; + +use cpio::newc::Builder as NewcBuilder; +use cpio::newc::ModeFileType; + +fn write_dir(out: &mut Vec, path: &str) { + NewcBuilder::new(path) + .mode(0o755) + .set_mode_file_type(ModeFileType::Directory) + .write(out, 0) + .finish() + .unwrap(); +} + +fn write_file(out: &mut Vec, path: &str, data: &[u8]) { + let mut w = NewcBuilder::new(path) + .mode(0o644) + .set_mode_file_type(ModeFileType::Regular) + .write(out, data.len() as u32); + w.write_all(data).unwrap(); + w.finish().unwrap(); +} + +fn write_file_exec(out: &mut Vec, path: &str, data: &[u8]) { + let mut w = NewcBuilder::new(path) + .mode(0o755) + .set_mode_file_type(ModeFileType::Regular) + .write(out, data.len() as u32); + w.write_all(data).unwrap(); + w.finish().unwrap(); +} + +pub fn build_units_cpio() -> Vec { + let mut out = Vec::with_capacity(32768); + + write_dir(&mut out, "usr"); + write_dir(&mut out, "usr/lib"); + write_dir(&mut out, "usr/lib/systemd"); + write_dir(&mut out, "usr/lib/systemd/system"); + write_dir(&mut out, "usr/lib/systemd/system/initrd-fs.target.d"); + + write_file( + &mut out, + "usr/lib/systemd/system/bcvk-var-ephemeral.service", + b"[Unit]\n\ + Description=Setup ephemeral /var from image content\n\ + DefaultDependencies=no\n\ + ConditionPathExists=/etc/initrd-release\n\ + Before=initrd-fs.target\n\ + After=sysroot.mount initrd-parse-etc.service\n\ + Requires=sysroot.mount\n\ + \n\ + [Service]\n\ + Type=oneshot\n\ + RemainAfterExit=yes\n\ + TimeoutStartSec=60\n\ + ExecStart=/usr/bin/mkdir -p /run/var-ephemeral\n\ + ExecStart=/usr/bin/cp -a /sysroot/var/. /run/var-ephemeral/\n\ + ExecStart=/usr/bin/mount --bind /run/var-ephemeral /sysroot/var\n", + ); + + write_file( + &mut out, + "usr/lib/systemd/system/bcvk-etc-overlay.service", + b"[Unit]\n\ + Description=Setup ephemeral /etc overlay\n\ + DefaultDependencies=no\n\ + ConditionPathExists=/etc/initrd-release\n\ + Before=initrd-fs.target\n\ + After=sysroot.mount initrd-parse-etc.service\n\ + Requires=sysroot.mount\n\ + \n\ + [Service]\n\ + Type=oneshot\n\ + RemainAfterExit=yes\n\ + TimeoutStartSec=30\n\ + ExecStart=/usr/bin/mkdir -p /run/etc-lower /run/etc-upper /run/etc-work\n\ + ExecStart=/usr/bin/mount --bind /sysroot/etc /run/etc-lower\n\ + ExecStart=/usr/bin/mount -t overlay overlay -o lowerdir=/run/etc-lower,upperdir=/run/etc-upper,workdir=/run/etc-work,index=off,metacopy=off /sysroot/etc\n", + ); + + write_file( + &mut out, + "usr/lib/systemd/system/bcvk-copy-units.service", + b"[Unit]\n\ + Description=Copy bcvk units for post-switch-root on systemd <256\n\ + DefaultDependencies=no\n\ + ConditionPathExists=/etc/initrd-release\n\ + Before=initrd-fs.target\n\ + \n\ + [Service]\n\ + Type=oneshot\n\ + RemainAfterExit=yes\n\ + ExecStart=/bin/sh -c 'mkdir -p /run/systemd/system/sysinit.target.wants && cp /usr/lib/systemd/system/bcvk-journal-stream.service /run/systemd/system/ && ln -s ../bcvk-journal-stream.service /run/systemd/system/sysinit.target.wants/'\n", + ); + + write_file( + &mut out, + "usr/lib/systemd/system/bcvk-journal-stream.service", + b"[Unit]\n\ + Description=Stream journal to virtio-serial\n\ + DefaultDependencies=no\n\ + \n\ + [Service]\n\ + Type=simple\n\ + ExecStart=/bin/sh -c 'journalctl -f --no-hostname -o short-monotonic > /dev/hvc1 2>&1 || true'\n", + ); + + write_file( + &mut out, + "usr/lib/systemd/system/initrd-fs.target.d/bcvk-var-ephemeral.conf", + b"[Unit]\nWants=bcvk-var-ephemeral.service\n", + ); + write_file( + &mut out, + "usr/lib/systemd/system/initrd-fs.target.d/bcvk-etc-overlay.conf", + b"[Unit]\nWants=bcvk-etc-overlay.service\n", + ); + write_file( + &mut out, + "usr/lib/systemd/system/initrd-fs.target.d/bcvk-copy-units.conf", + b"[Unit]\nWants=bcvk-copy-units.service\n", + ); + + cpio::newc::trailer(out).unwrap() +} + +pub fn build_ssh_cpio(pubkey: &str) -> Vec { + let mut out = Vec::with_capacity(4096); + + write_dir(&mut out, "usr"); + write_dir(&mut out, "usr/lib"); + write_dir(&mut out, "usr/lib/bcvk"); + write_dir(&mut out, "usr/lib/systemd"); + write_dir(&mut out, "usr/lib/systemd/system"); + write_dir(&mut out, "usr/lib/systemd/system/initrd-fs.target.d"); + + let setup_script = format!( + "#!/bin/bash\n\ + mkdir -p /sysroot/var/roothome /sysroot/var/empty /sysroot/var/log /sysroot/var/tmp\n\ + chmod 700 /sysroot/var/roothome\n\ + chmod 711 /sysroot/var/empty\n\ + mkdir -p /sysroot/var/roothome/.ssh\n\ + chmod 700 /sysroot/var/roothome/.ssh\n\ + echo '{}' > /sysroot/var/roothome/.ssh/authorized_keys\n\ + chmod 600 /sysroot/var/roothome/.ssh/authorized_keys\n\ + chown -R 0:0 /sysroot/var/roothome/.ssh\n", + pubkey + ); + write_file_exec( + &mut out, + "usr/lib/bcvk/setup-ssh.sh", + setup_script.as_bytes(), + ); + + write_file( + &mut out, + "usr/lib/systemd/system/bcvk-ssh-setup.service", + b"[Unit]\n\ + Description=Setup SSH authorized_keys for root\n\ + DefaultDependencies=no\n\ + ConditionPathExists=/etc/initrd-release\n\ + Before=initrd-fs.target\n\ + After=bcvk-var-ephemeral.service\n\ + Requires=bcvk-var-ephemeral.service\n\ + \n\ + [Service]\n\ + Type=oneshot\n\ + RemainAfterExit=yes\n\ + ExecStart=/usr/bin/bash /usr/lib/bcvk/setup-ssh.sh\n", + ); + + write_file( + &mut out, + "usr/lib/systemd/system/initrd-fs.target.d/bcvk-ssh-setup.conf", + b"[Unit]\nWants=bcvk-ssh-setup.service\n", + ); + + cpio::newc::trailer(out).unwrap() +} diff --git a/crates/nbdkit-erofs-plugin/src/lib.rs b/crates/nbdkit-erofs-plugin/src/lib.rs new file mode 100644 index 000000000..b2cd4075c --- /dev/null +++ b/crates/nbdkit-erofs-plugin/src/lib.rs @@ -0,0 +1,389 @@ +mod dir_walk; +mod erofs; +mod fat32; +mod gpt; +mod initramfs; +mod regions; + +use std::ffi::{c_char, c_int, c_void, CStr, CString}; +use std::path::PathBuf; +use std::sync::Mutex; + +use regions::Region; + +static PLUGIN_STATE: Mutex> = Mutex::new(None); + +struct PluginState { + dir: PathBuf, + cmdline: Option, + ssh_pubkey: Option, + regions: Vec, + total_size: u64, +} + +// --- nbdkit C FFI --- + +extern "C" { + fn nbdkit_error(fmt: *const c_char, ...); +} + +fn log_error(msg: &str) { + let c = CString::new(msg).unwrap_or_default(); + unsafe { nbdkit_error(b"%s\0".as_ptr() as *const c_char, c.as_ptr()) }; +} + +// --- Plugin callbacks --- + +#[no_mangle] +pub extern "C" fn plugin_config(key: *const c_char, value: *const c_char) -> c_int { + let key = unsafe { CStr::from_ptr(key) }.to_str().unwrap_or(""); + let value = unsafe { CStr::from_ptr(value) }.to_str().unwrap_or(""); + + let mut state = PLUGIN_STATE.lock().unwrap(); + let state = state.get_or_insert_with(|| PluginState { + dir: PathBuf::new(), + cmdline: None, + ssh_pubkey: None, + regions: Vec::new(), + total_size: 0, + }); + + match key { + "dir" => state.dir = PathBuf::from(value), + "cmdline" => state.cmdline = Some(value.to_string()), + "ssh_pubkey" => state.ssh_pubkey = Some(value.to_string()), + _ => { + log_error(&format!("unknown parameter: {}", key)); + return -1; + } + } + 0 +} + +#[no_mangle] +pub extern "C" fn plugin_config_complete() -> c_int { + let state = PLUGIN_STATE.lock().unwrap(); + let state = match state.as_ref() { + Some(s) => s, + None => { + log_error("dir parameter is required"); + return -1; + } + }; + + if state.dir.as_os_str().is_empty() { + log_error("dir parameter is required"); + return -1; + } + + if state.cmdline.is_none() { + log_error("cmdline parameter is required"); + return -1; + } + + 0 +} + +fn find_kernel_dir(dir: &std::path::Path) -> Option<(PathBuf, PathBuf)> { + let modules = dir.join("usr/lib/modules"); + if let Ok(entries) = std::fs::read_dir(&modules) { + for entry in entries.flatten() { + let kdir = entry.path(); + let vmlinuz = kdir.join("vmlinuz"); + let initramfs = kdir.join("initramfs.img"); + if vmlinuz.exists() && initramfs.exists() { + return Some((vmlinuz, initramfs)); + } + } + } + None +} + +fn find_grub(dir: &std::path::Path) -> Option { + fn walk(path: &std::path::Path, target: &str) -> Option { + if let Ok(entries) = std::fs::read_dir(path) { + for entry in entries.flatten() { + let p = entry.path(); + if p.is_file() && p.file_name().map(|n| n == target).unwrap_or(false) { + return Some(p); + } + if p.is_dir() { + if let Some(found) = walk(&p, target) { + return Some(found); + } + } + } + } + None + } + walk(&dir.join("usr/lib"), "grubaa64.efi") +} + +#[no_mangle] +pub extern "C" fn plugin_get_ready() -> c_int { + let mut state_guard = PLUGIN_STATE.lock().unwrap(); + let state = match state_guard.as_mut() { + Some(s) => s, + None => return -1, + }; + + // Walk directory for EROFS + let walk = match dir_walk::walk_directory(&state.dir) { + Ok(w) => w, + Err(e) => { + log_error(&format!("failed to walk directory: {}", e)); + return -1; + } + }; + + let erofs_layout = match erofs::build_erofs(&walk) { + Ok(l) => l, + Err(e) => { + log_error(&format!("failed to build EROFS: {}", e)); + return -1; + } + }; + + let erofs_regions = erofs::build_erofs_regions(&erofs_layout, &walk); + + // Discover boot files from dir + let (kernel_path, initrd_path) = match find_kernel_dir(&state.dir) { + Some(paths) => paths, + None => { + log_error("kernel/initramfs not found in dir/usr/lib/modules/"); + return -1; + } + }; + + let grub_path = match find_grub(&state.dir) { + Some(p) => p, + None => { + log_error("grubaa64.efi not found in dir/usr/lib/"); + return -1; + } + }; + + fn file_size(path: &std::path::Path) -> Option { + match std::fs::metadata(path) { + Ok(m) => Some(m.len()), + Err(e) => { + log_error(&format!("cannot stat {:?}: {}", path, e)); + None + } + } + } + + let Some(kernel_size) = file_size(&kernel_path) else { + return -1; + }; + let Some(initrd_size) = file_size(&initrd_path) else { + return -1; + }; + let Some(grub_size) = file_size(&grub_path) else { + return -1; + }; + + let cmdline = state.cmdline.as_deref().unwrap_or(""); + + // Generate grub.cfg + let grub_cfg = format!( + "set timeout=0\nset default=0\nmenuentry \"bcvk\" {{\n linux /boot/vmlinuz {}\n initrd /boot/initrd.img\n}}\n", + cmdline + ); + + // Generate CPIO archives + let units_cpio = initramfs::build_units_cpio(); + let ssh_cpio = state.ssh_pubkey.as_deref().map(initramfs::build_ssh_cpio); + + // Build initrd regions (original file + padding + CPIO) + let (initrd_parts, initrd_total) = + fat32::build_initrd_regions(&initrd_path, initrd_size, &units_cpio, ssh_cpio.as_deref()); + + // Build ESP regions + let (esp_regions, esp_size) = fat32::build_esp_regions( + &grub_path, + grub_size, + grub_cfg.as_bytes(), + &kernel_path, + kernel_size, + initrd_parts, + initrd_total, + ); + + // Build GPT disk with ESP + EROFS + match gpt::build_gpt_disk( + esp_regions, + esp_size, + erofs_regions, + erofs_layout.total_size, + ) { + Ok(disk) => { + state.regions = disk.regions; + state.total_size = disk.total_size; + } + Err(e) => { + log_error(&format!("failed to build GPT disk: {}", e)); + return -1; + } + } + + 0 +} + +#[no_mangle] +pub extern "C" fn plugin_open(_readonly: c_int) -> *mut c_void { + 1 as *mut c_void +} + +#[no_mangle] +pub extern "C" fn plugin_close(_handle: *mut c_void) {} + +#[no_mangle] +pub extern "C" fn plugin_get_size(_handle: *mut c_void) -> i64 { + let state = PLUGIN_STATE.lock().unwrap(); + state.as_ref().map(|s| s.total_size as i64).unwrap_or(-1) +} + +#[no_mangle] +pub extern "C" fn plugin_can_multi_conn(_handle: *mut c_void) -> c_int { + 1 +} + +#[no_mangle] +pub extern "C" fn plugin_pread( + _handle: *mut c_void, + buf: *mut c_void, + count: u32, + offset: u64, + _flags: u32, +) -> c_int { + let state = PLUGIN_STATE.lock().unwrap(); + let state = match state.as_ref() { + Some(s) => s, + None => return -1, + }; + + let buf = unsafe { std::slice::from_raw_parts_mut(buf as *mut u8, count as usize) }; + + match regions::pread(&state.regions, buf, offset) { + Ok(()) => 0, + Err(e) => { + log_error(&format!("pread error at offset {}: {}", offset, e)); + -1 + } + } +} + +// --- Plugin registration --- + +#[repr(C)] +pub struct NbdkitPlugin { + _struct_size: u64, + _api_version: c_int, + _thread_model: c_int, + name: *const c_char, + longname: *const c_char, + version: *const c_char, + description: *const c_char, + load: Option, + unload: Option, + config: Option c_int>, + config_complete: Option c_int>, + config_help: *const c_char, + open: Option *mut c_void>, + close: Option, + get_size: Option i64>, + can_write: Option c_int>, + can_flush: Option c_int>, + is_rotational: Option c_int>, + can_trim: Option c_int>, + _pread_v1: Option c_int>, + _pwrite_v1: Option c_int>, + _flush_v1: Option c_int>, + _trim_v1: Option c_int>, + _zero_v1: Option c_int>, + errno_is_preserved: c_int, + dump_plugin: Option, + can_zero: Option c_int>, + can_fua: Option c_int>, + pread: Option c_int>, + pwrite: Option c_int>, + flush: Option c_int>, + trim: Option c_int>, + zero: Option c_int>, + magic_config_key: *const c_char, + can_multi_conn: Option c_int>, + can_extents: Option c_int>, + extents: Option c_int>, + can_cache: Option c_int>, + cache: Option c_int>, + thread_model: Option c_int>, + can_fast_zero: Option c_int>, + preconnect: Option c_int>, + get_ready: Option c_int>, + after_fork: Option c_int>, + // Fields after after_fork (list_exports, default_export, export_description, + // cleanup, block_size) are omitted. nbdkit uses _struct_size to determine + // which fields are present, so omitting trailing fields is safe. +} + +unsafe impl Sync for NbdkitPlugin {} + +static PLUGIN_NAME: &[u8] = b"erofs\0"; +static PLUGIN_LONGNAME: &[u8] = b"nbdkit EROFS plugin\0"; +static PLUGIN_VERSION: &[u8] = b"0.2.0\0"; +static PLUGIN_DESCRIPTION: &[u8] = b"Create virtual EROFS+ESP disk from directory\0"; +static PLUGIN_CONFIG_HELP: &[u8] = b"dir= (required) Container overlay merged directory\ncmdline= (required) Kernel command line for grub.cfg\nssh_pubkey= SSH public key for root access\0"; +static PLUGIN_MAGIC_KEY: &[u8] = b"dir\0"; + +static PLUGIN: NbdkitPlugin = NbdkitPlugin { + _struct_size: std::mem::size_of::() as u64, + _api_version: 2, + _thread_model: 0, + name: PLUGIN_NAME.as_ptr() as *const c_char, + longname: PLUGIN_LONGNAME.as_ptr() as *const c_char, + version: PLUGIN_VERSION.as_ptr() as *const c_char, + description: PLUGIN_DESCRIPTION.as_ptr() as *const c_char, + load: None, + unload: None, + config: Some(plugin_config), + config_complete: Some(plugin_config_complete), + config_help: PLUGIN_CONFIG_HELP.as_ptr() as *const c_char, + open: Some(plugin_open), + close: Some(plugin_close), + get_size: Some(plugin_get_size), + can_write: None, + can_flush: None, + is_rotational: None, + can_trim: None, + _pread_v1: None, + _pwrite_v1: None, + _flush_v1: None, + _trim_v1: None, + _zero_v1: None, + errno_is_preserved: 1, + dump_plugin: None, + can_zero: None, + can_fua: None, + pread: Some(plugin_pread), + pwrite: None, + flush: None, + trim: None, + zero: None, + magic_config_key: PLUGIN_MAGIC_KEY.as_ptr() as *const c_char, + can_multi_conn: Some(plugin_can_multi_conn), + can_extents: None, + extents: None, + can_cache: None, + cache: None, + thread_model: None, + can_fast_zero: None, + preconnect: None, + get_ready: Some(plugin_get_ready), + after_fork: None, +}; + +#[no_mangle] +pub extern "C" fn plugin_init() -> *const NbdkitPlugin { + &PLUGIN +} diff --git a/crates/nbdkit-erofs-plugin/src/regions.rs b/crates/nbdkit-erofs-plugin/src/regions.rs new file mode 100644 index 000000000..16268d623 --- /dev/null +++ b/crates/nbdkit-erofs-plugin/src/regions.rs @@ -0,0 +1,80 @@ +//! Region-based virtual block device composition. +//! Inspired by the regions pattern in nbdkit's floppy plugin (BSD-3-Clause). + +use std::path::PathBuf; +use std::sync::Arc; + +#[derive(Debug, Clone)] +pub enum RegionType { + Data(Arc>), + File { path: PathBuf }, + Zero, +} + +#[derive(Debug, Clone)] +pub struct Region { + pub start: u64, + pub len: u64, + pub region_type: RegionType, +} + +impl Region { + pub fn end(&self) -> u64 { + self.start + self.len + } +} + +pub fn find_region(regions: &[Region], offset: u64) -> Option<&Region> { + regions + .binary_search_by(|r| { + if offset < r.start { + std::cmp::Ordering::Greater + } else if offset >= r.end() { + std::cmp::Ordering::Less + } else { + std::cmp::Ordering::Equal + } + }) + .ok() + .map(|i| ®ions[i]) +} + +pub fn pread(regions: &[Region], buf: &mut [u8], offset: u64) -> std::io::Result<()> { + let mut remaining = buf.len(); + let mut buf_offset = 0; + let mut disk_offset = offset; + + while remaining > 0 { + let region = find_region(regions, disk_offset).ok_or_else(|| { + std::io::Error::new( + std::io::ErrorKind::InvalidInput, + format!("offset {} outside disk", disk_offset), + ) + })?; + + let region_offset = disk_offset - region.start; + let avail = (region.len - region_offset) as usize; + let len = remaining.min(avail); + + match ®ion.region_type { + RegionType::Data(data) => { + let start = region_offset as usize; + buf[buf_offset..buf_offset + len].copy_from_slice(&data[start..start + len]); + } + RegionType::File { path } => { + use std::os::unix::fs::FileExt; + let f = std::fs::File::open(path)?; + f.read_exact_at(&mut buf[buf_offset..buf_offset + len], region_offset)?; + } + RegionType::Zero => { + buf[buf_offset..buf_offset + len].fill(0); + } + } + + remaining -= len; + buf_offset += len; + disk_offset += len as u64; + } + + Ok(()) +} From 7acd9a451c558c129d5fe527647a07ca2cd68ac2 Mon Sep 17 00:00:00 2001 From: Shion Tanaka Date: Fri, 5 Jun 2026 04:53:34 +0900 Subject: [PATCH 3/6] macOS: add to-disk, vm_helpers, nbdkit auto-build, CLI unification - to-disk with APFS clonefile-based base disk caching - vm_helpers.rs shared with Windows (12 functions) - nbdkit .so plugin auto-build via include_bytes! embedding - CLI options unified with Linux/Windows (--ssh, --ssh-wait, --force, --stop, --install-log, --label, --format, --itype) Assisted-by: Claude Code (Claude Opus 4.6) Signed-off-by: Shion Tanaka --- Cargo.lock | 1 + crates/kit/src/ephemeral_macos.rs | 12 +- crates/kit/src/install_options.rs | 11 +- crates/kit/src/instancetypes.rs | 6 +- crates/kit/src/lib.rs | 6 + crates/kit/src/main.rs | 17 +- crates/kit/src/nbdkit_macos.rs | 112 ++++-- crates/kit/src/run_ephemeral_macos.rs | 254 ++++---------- crates/kit/src/to_disk_macos.rs | 402 ++++++++++++++++++++++ crates/kit/src/vfkit/inspect.rs | 46 ++- crates/kit/src/vfkit/list.rs | 92 ++++- crates/kit/src/vfkit/mod.rs | 71 ++-- crates/kit/src/vfkit/rm.rs | 30 +- crates/kit/src/vfkit/rm_all.rs | 47 ++- crates/kit/src/vfkit/run.rs | 281 +++++++++++++-- crates/kit/src/vfkit/ssh.rs | 16 +- crates/kit/src/vfkit/start.rs | 23 +- crates/kit/src/vfkit/stop.rs | 38 +- crates/kit/src/vm_helpers.rs | 340 ++++++++++++++++++ crates/nbdkit-erofs-plugin/src/erofs.rs | 5 +- crates/nbdkit-erofs-plugin/src/fat32.rs | 18 +- crates/nbdkit-erofs-plugin/src/gpt.rs | 2 +- crates/nbdkit-erofs-plugin/src/lib.rs | 21 +- crates/nbdkit-erofs-plugin/src/regions.rs | 79 ++++- 24 files changed, 1551 insertions(+), 379 deletions(-) create mode 100644 crates/kit/src/to_disk_macos.rs create mode 100644 crates/kit/src/vm_helpers.rs diff --git a/Cargo.lock b/Cargo.lock index 27f2d278d..20fdb4e67 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1846,6 +1846,7 @@ dependencies = [ name = "nbdkit-erofs-plugin" version = "0.1.0" dependencies = [ + "cpio", "crc32fast", "libc", ] diff --git a/crates/kit/src/ephemeral_macos.rs b/crates/kit/src/ephemeral_macos.rs index 8d46075f4..97d599ec7 100644 --- a/crates/kit/src/ephemeral_macos.rs +++ b/crates/kit/src/ephemeral_macos.rs @@ -11,7 +11,7 @@ use crate::run_ephemeral_macos::{self, EphemeralVmMetadata}; /// Options for `ephemeral run-ssh`, combining run options with optional SSH arguments. #[derive(Debug, clap::Parser)] -pub struct RunSshOpts { +pub struct RunEphemeralSshOpts { #[command(flatten)] pub run_opts: run_ephemeral_macos::RunEphemeralOpts, @@ -28,7 +28,7 @@ pub enum EphemeralCommands { /// Run ephemeral VM and SSH into it #[clap(name = "run-ssh")] - RunSsh(RunSshOpts), + RunSsh(RunEphemeralSshOpts), /// Connect to a running ephemeral VM via SSH #[clap(name = "ssh")] @@ -151,6 +151,12 @@ fn cmd_rm_all(force: bool) -> Result<()> { tracing::warn!("failed to kill gvproxy {}: {}", vm.gvproxy_pid, e); } } + // Wait for the VM process to exit so cleanup (VmCleanup::drop in + // the detached child) finishes before we proceed. + let deadline = std::time::Instant::now() + std::time::Duration::from_secs(5); + while vm.is_alive() && std::time::Instant::now() < deadline { + std::thread::sleep(std::time::Duration::from_millis(100)); + } } if let Some(ref container) = vm.nbd_container { crate::nbdkit_macos::stop_nbdkit_container(container); @@ -201,7 +207,7 @@ fn cmd_ssh(name: &str, args: &[String]) -> Result<()> { let svc_sock = format!("{}/{}-gvproxy-svc.sock", base.display(), name); if std::path::Path::new(&svc_sock).exists() { if let Err(e) = - run_ephemeral_macos::expose_ssh_port(&svc_sock, "192.168.127.2", vm.ssh_port) + run_ephemeral_macos::expose_port(&svc_sock, "192.168.127.2", vm.ssh_port, 22) { tracing::debug!("SSH port forward re-expose: {}", e); } diff --git a/crates/kit/src/install_options.rs b/crates/kit/src/install_options.rs index a284558cb..61908d275 100644 --- a/crates/kit/src/install_options.rs +++ b/crates/kit/src/install_options.rs @@ -5,7 +5,7 @@ //! and other installation-related commands. // On non-Linux, this module is unused as it's for installation operations -#![cfg_attr(not(target_os = "linux"), allow(dead_code))] +#![cfg_attr(not(any(target_os = "linux", target_os = "macos")), allow(dead_code))] use camino::Utf8PathBuf; use clap::Parser; @@ -52,6 +52,10 @@ pub struct InstallOptions { /// backend #[clap(long, requires = "composefs_backend")] pub allow_missing_fsverity: bool, + + /// Path to an authorized_keys file to inject into the root account + #[clap(long)] + pub root_ssh_authorized_keys: Option, } impl InstallOptions { @@ -91,6 +95,11 @@ impl InstallOptions { args.push("--allow-missing-fsverity".into()); } + if let Some(ref key_path) = self.root_ssh_authorized_keys { + args.push("--root-ssh-authorized-keys".to_string()); + args.push(key_path.to_string()); + } + args } } diff --git a/crates/kit/src/instancetypes.rs b/crates/kit/src/instancetypes.rs index 70a8c4ccb..aa43dfc0a 100644 --- a/crates/kit/src/instancetypes.rs +++ b/crates/kit/src/instancetypes.rs @@ -8,14 +8,14 @@ //! Instance types follow the format: u1.{size} //! Examples: u1.nano, u1.micro, u1.small, u1.medium, u1.large, etc. //! -//! Source: https://github.com/kubevirt/common-instancetypes +//! Source: // On non-Linux, this module is unused as it's for VM instance types -#![cfg_attr(not(target_os = "linux"), allow(dead_code))] +#![cfg_attr(not(any(target_os = "linux", target_os = "macos")), allow(dead_code))] /// Instance type variants with associated vCPU and memory specifications /// -/// Source: https://github.com/kubevirt/common-instancetypes/blob/main/instancetypes/u/1/sizes.yaml +/// Source: #[derive( Debug, Clone, diff --git a/crates/kit/src/lib.rs b/crates/kit/src/lib.rs index d7257cb8e..f98fce92e 100644 --- a/crates/kit/src/lib.rs +++ b/crates/kit/src/lib.rs @@ -5,6 +5,7 @@ pub mod qemu_img; pub mod xml_utils; // Cross-platform modules +pub mod install_options; pub mod ssh_options; // Linux-only modules @@ -17,5 +18,10 @@ pub mod nbdkit_macos; #[cfg(target_os = "macos")] pub mod run_ephemeral_macos; +#[cfg(target_os = "macos")] +pub mod instancetypes; +#[cfg(target_os = "macos")] +pub mod to_disk_macos; #[cfg(target_os = "macos")] pub mod vfkit; +pub mod vm_helpers; diff --git a/crates/kit/src/main.rs b/crates/kit/src/main.rs index b92d35783..efa91d614 100644 --- a/crates/kit/src/main.rs +++ b/crates/kit/src/main.rs @@ -69,7 +69,11 @@ mod nbdkit_macos; #[cfg(target_os = "macos")] mod run_ephemeral_macos; #[cfg(target_os = "macos")] +mod to_disk_macos; +#[cfg(target_os = "macos")] mod vfkit; +#[cfg(target_os = "macos")] +mod vm_helpers; /// Default state directory for bcvk container data #[cfg(target_os = "linux")] @@ -159,9 +163,15 @@ enum Commands { // macOS: vfkit-based persistent VMs #[cfg(target_os = "macos")] /// Manage persistent VMs (vfkit backend) - #[clap(subcommand)] + #[clap(subcommand, alias = "vfkit")] Vm(vfkit::VmCommands), + // macOS: to-disk + #[cfg(target_os = "macos")] + /// Install bootc images to persistent disk images + #[clap(name = "to-disk")] + ToDisk(to_disk_macos::ToDiskMacosOpts), + // Other platforms: stub #[cfg(not(any(target_os = "linux", target_os = "macos")))] /// Manage ephemeral VMs for bootc containers (not available on this platform) @@ -313,6 +323,11 @@ fn main() -> Result<(), Report> { #[cfg(target_os = "macos")] Commands::Vm(cmd) => cmd.run()?, + #[cfg(target_os = "macos")] + Commands::ToDisk(opts) => { + to_disk_macos::run(opts)?; + } + #[cfg(not(any(target_os = "linux", target_os = "macos")))] Commands::Ephemeral(_) => { return Err(color_eyre::eyre::eyre!( diff --git a/crates/kit/src/nbdkit_macos.rs b/crates/kit/src/nbdkit_macos.rs index 40c2cc20e..d8e13c91e 100644 --- a/crates/kit/src/nbdkit_macos.rs +++ b/crates/kit/src/nbdkit_macos.rs @@ -8,10 +8,14 @@ use std::process::{Command, Stdio}; use std::time::Duration; use tracing::info; -use crate::run_ephemeral_macos::detect_machine_name; +use crate::vm_helpers::detect_machine_name; -/// Path to the nbdkit EROFS plugin shared library inside podman machine. -const NBDKIT_EROFS_PLUGIN_PATH: &str = "/var/tmp/bcvk/libnbdkit_erofs_plugin.so"; +/// EROFS plugin shared library, embedded at compile time. +const EROFS_PLUGIN_SO: &[u8] = include_bytes!("../nbdkit-erofs-plugin.so"); + +fn shell_escape(s: &str) -> String { + format!("'{}'", s.replace('\'', "'\\''")) +} /// Get the merged overlay path from podman image mount. pub(crate) fn get_merged_path(machine: &str, rootful: bool, image: &str) -> Result { @@ -38,7 +42,33 @@ pub(crate) fn get_merged_path(machine: &str, rootful: bool, image: &str) -> Resu Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) } -/// Start nbdkit with the erofs plugin for dynamic EROFS + ESP + GPT generation. +/// Ensure the nbdkit container image exists in podman machine. +/// On first run, transfers embedded .so and builds container image. +pub(crate) fn ensure_nbdkit_ready(machine: &str) -> Result<()> { + let script = crate::vm_helpers::nbdkit_setup_script(EROFS_PLUGIN_SO); + info!("checking nbdkit container image..."); + let mut child = Command::new("podman") + .args(["machine", "ssh", machine, "--", "bash", "-s"]) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .context("nbdkit setup in podman machine")?; + if let Some(mut stdin) = child.stdin.take() { + use std::io::Write; + stdin.write_all(script.as_bytes())?; + } + let output = child.wait_with_output()?; + if !output.status.success() { + bail!( + "nbdkit setup failed: {}", + String::from_utf8_lossy(&output.stderr).trim() + ); + } + Ok(()) +} + +#[allow(dead_code)] pub(crate) fn start_nbdkit_erofs_plugin( machine: &str, merged_path: &str, @@ -64,10 +94,6 @@ pub(crate) fn start_nbdkit_erofs_plugin( .stderr(Stdio::null()) .status(); - fn shell_escape(s: &str) -> String { - format!("'{}'", s.replace('\'', "'\\''")) - } - let cmdline_esc = shell_escape(&format!("cmdline={}", cmdline)); let dir_esc = shell_escape(&format!("dir={}", merged_path)); @@ -80,16 +106,13 @@ pub(crate) fn start_nbdkit_erofs_plugin( "podman run -d --name {name} --security-opt label=disable \ -p {port}:10809 \ -v {merged}:{merged}:ro \ - -v {plugin}:/plugin.so:ro \ - -v /usr/bin/nbdkit:/usr/bin/nbdkit:ro \ - -v /usr/lib64/nbdkit:/usr/lib64/nbdkit:ro \ - quay.io/fedora/fedora:latest \ - nbdkit -f -p 10809 -r /plugin.so \ + {image} \ + nbdkit -f --threads 4 -p 10809 -r /plugin.so \ {dir} {cmdline}{ssh}", name = container_name, port = nbd_port, merged = merged_path, - plugin = NBDKIT_EROFS_PLUGIN_PATH, + image = crate::vm_helpers::NBDKIT_IMAGE, dir = dir_esc, cmdline = cmdline_esc, ssh = ssh_param, @@ -106,7 +129,6 @@ pub(crate) fn start_nbdkit_erofs_plugin( } info!("waiting for nbdkit on port {}...", nbd_port); - let deadline = std::time::Instant::now() + Duration::from_secs(30); loop { if let Ok(mut stream) = std::net::TcpStream::connect_timeout( &std::net::SocketAddr::from(([127, 0, 0, 1], nbd_port)), @@ -119,25 +141,47 @@ pub(crate) fn start_nbdkit_erofs_plugin( break; } } - if std::time::Instant::now() > deadline { - let _ = Command::new("podman") - .args([ - "machine", - "ssh", - machine, - "--", - "podman", - "rm", - "-f", - &container_name, - ]) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status(); - bail!( - "nbdkit erofs plugin did not become ready on port {}", - nbd_port - ); + // Check if container is still alive (no fixed timeout — wait as long + // as plugin_get_ready() is running, which scans the entire overlay + // directory and scales with image size) + let ps_output = Command::new("podman") + .args([ + "machine", + "ssh", + machine, + "--", + "podman", + "ps", + "-a", + "--filter", + &format!("name=^{}$", container_name), + "--format", + "{{.Status}}", + ]) + .output(); + if let Ok(out) = &ps_output { + let stdout = String::from_utf8_lossy(&out.stdout); + if stdout.contains("Exited") { + let _ = Command::new("podman") + .args([ + "machine", + "ssh", + machine, + "--", + "podman", + "rm", + "-f", + &container_name, + ]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status(); + bail!( + "nbdkit container '{}' exited before becoming ready on port {}", + container_name, + nbd_port + ); + } } std::thread::sleep(Duration::from_millis(500)); } diff --git a/crates/kit/src/run_ephemeral_macos.rs b/crates/kit/src/run_ephemeral_macos.rs index 2265aacb7..156de0517 100644 --- a/crates/kit/src/run_ephemeral_macos.rs +++ b/crates/kit/src/run_ephemeral_macos.rs @@ -1,9 +1,9 @@ -//! Ephemeral VM launch flow for macOS using vfkit + NBD EROFS plugin. +//! Ephemeral VM launch flow for macOS using vfkit + NBD EROFS over TCP. //! //! Boot flow (fully diskless): //! 1. Mount container image overlay (`podman image mount`) -//! 2. Start nbdkit with erofs plugin (dynamically generates GPT + ESP + EROFS) -//! 3. Launch vfkit with EFI boot via NBD + virtio-net (gvproxy) +//! 2. Start nbdkit with erofs plugin in TCP mode (port forwarded via gvproxy) +//! 3. Launch vfkit with EFI boot via NBD TCP + virtio-net (gvproxy) //! 4. Wait for SSH and execute commands //! //! Common helpers (gvproxy, SSH, vfkit detection) are pub for reuse by vfkit/ module. @@ -15,11 +15,16 @@ use std::process::{Command, Stdio}; use std::time::Duration; use color_eyre::{ - eyre::{bail, eyre, Context}, + eyre::{bail, Context}, Result, }; use tracing::{debug, info}; +pub use crate::vm_helpers::{ + default_vcpus, detect_machine_name, ensure_image_and_get_digest, is_machine_rootful, + parse_memory_to_mb, run_ssh_command, run_ssh_interactive, wait_for_ssh, +}; + /// Base directory for ephemeral VM state on macOS host. pub fn ephemeral_base_dir() -> std::path::PathBuf { dirs::home_dir() @@ -121,10 +126,13 @@ impl EphemeralVmMetadata { pub struct RunEphemeralOpts { /// Container image to boot pub image: String, - /// Number of vCPUs + /// Instance type (e.g., u1.nano, u1.small). Overrides vcpus/memory if specified. + #[clap(long)] + pub itype: Option, + /// Number of vCPUs (overridden by --itype if specified) #[clap(long)] pub vcpus: Option, - /// Memory size (e.g. "4G", "2048M", or plain number for MB) + /// Memory size (overridden by --itype if specified) #[clap(long, default_value = "4G")] pub memory: String, /// Generate a temporary SSH key pair for VM access @@ -150,24 +158,6 @@ pub struct RunEphemeralOpts { pub debug: bool, } -fn default_vcpus() -> u32 { - std::thread::available_parallelism() - .map(|n| n.get() as u32) - .unwrap_or(2) -} - -/// Parse memory specification string (e.g. "4G", "2048M") to megabytes. -pub fn parse_memory_to_mb(s: &str) -> Result { - let s = s.trim(); - if let Some(n) = s.strip_suffix('G').or_else(|| s.strip_suffix('g')) { - Ok((n.parse::()? * 1024.0) as u32) - } else if let Some(n) = s.strip_suffix('M').or_else(|| s.strip_suffix('m')) { - Ok(n.parse::()? as u32) - } else { - Ok(s.parse::()?) - } -} - // --- RAII cleanup guard --- struct VmCleanup { @@ -219,18 +209,23 @@ impl Drop for VmCleanup { // --- Main entry point --- -/// Run an ephemeral VM from a container image using vfkit + EROFS over NBD. +/// Run an ephemeral VM from a container image. +/// pub fn run(opts: RunEphemeralOpts) -> Result<()> { if opts.gui && opts.detach { bail!("--gui and --detach cannot be used together (GUI requires foreground process)"); } + run_vfkit(opts) +} +/// Run an ephemeral VM using vfkit + EROFS over NBD (TCP transport). +fn run_vfkit(opts: RunEphemeralOpts) -> Result<()> { if opts.detach { return run_detached(&opts); } let vfkit_bin = find_vfkit()?; - info!(image = %opts.image, "starting ephemeral VM on macOS (vfkit + EROFS)"); + info!(image = %opts.image, "starting ephemeral VM on macOS (vfkit + NBD TCP)"); let cache_base = ephemeral_base_dir(); fs::create_dir_all(&cache_base)?; @@ -294,12 +289,15 @@ pub fn run(opts: RunEphemeralOpts) -> Result<()> { cmdline_parts.extend(&user_args); let cmdline = cmdline_parts.join(" "); + // Ensure nbdkit container image is ready (auto-build on first run) + crate::nbdkit_macos::ensure_nbdkit_ready(&machine)?; + // Get container image merged overlay path let merged_path = crate::nbdkit_macos::get_merged_path(&machine, rootful, &opts.image)?; info!("overlay merged: {}", merged_path); - // Start nbdkit with erofs plugin (dynamic EROFS + ESP + GPT from overlay dir) let nbd_port = crate::nbdkit_macos::find_available_nbd_port(); + info!("NBD transport: TCP (port {})", nbd_port); let nbd_container_name = crate::nbdkit_macos::start_nbdkit_erofs_plugin( &machine, &merged_path, @@ -308,8 +306,6 @@ pub fn run(opts: RunEphemeralOpts) -> Result<()> { nbd_port, &vm_name, )?; - std::thread::sleep(Duration::from_millis(500)); - info!("nbdkit ready on port {}", nbd_port); // gvproxy + vfkit (EFI boot) let gvproxy_sock = cache_base.join(format!("{}-gvproxy.sock", vm_name)); @@ -328,8 +324,16 @@ pub fn run(opts: RunEphemeralOpts) -> Result<()> { let efi_var_store = cache_base.join(format!("{}-efi-vars", vm_name)); let bootloader_arg = format!("efi,variable-store={},create", efi_var_store.display()); - let vcpus = opts.vcpus.unwrap_or_else(default_vcpus); - let memory_mb = parse_memory_to_mb(&opts.memory)?; + let vcpus = opts + .itype + .map(|t| t.vcpus()) + .or(opts.vcpus) + .unwrap_or_else(default_vcpus); + let memory_mb = opts + .itype + .map(|t| t.memory_mb()) + .map(Ok) + .unwrap_or_else(|| parse_memory_to_mb(&opts.memory))?; let mut vfkit_args = vec![ "--cpus".to_string(), @@ -350,8 +354,20 @@ pub fn run(opts: RunEphemeralOpts) -> Result<()> { ), "--device".to_string(), "virtio-rng".to_string(), + "--device".to_string(), + format!( + "virtio-vsock,port=9000,socketURL={},connect", + cache_base.join(format!("{}-vsock.sock", vm_name)).display() + ), ]; + if let Ok(bench_nbd) = std::env::var("BCVK_BENCH_NBD") { + vfkit_args.extend([ + "--device".to_string(), + format!("nbd,uri={},readonly,timeout=5000,deviceId=bench", bench_nbd), + ]); + } + let serial_log = cache_base.join(format!("{}-serial.log", vm_name)); vfkit_args.extend([ "--device".to_string(), @@ -401,7 +417,7 @@ pub fn run(opts: RunEphemeralOpts) -> Result<()> { if opts.ssh_keygen || !opts.execute.is_empty() { info!("setting up SSH port forwarding..."); for attempt in 0..15u32 { - match expose_ssh_port(&services_sock_str, "192.168.127.2", ssh_port) { + match expose_port(&services_sock_str, "192.168.127.2", ssh_port, 22) { Ok(_) => { info!("SSH port {} forwarded", ssh_port); break; @@ -523,50 +539,7 @@ fn run_detached(opts: &RunEphemeralOpts) -> Result<()> { Ok(()) } -// --- Shared helpers (pub for vfkit/ module) --- - -/// Detect the name of the running podman machine. -pub fn detect_machine_name() -> Result { - let output = Command::new("podman") - .args(["machine", "info", "--format", "{{.Host.CurrentMachine}}"]) - .output()?; - let name = String::from_utf8_lossy(&output.stdout).trim().to_string(); - if name.is_empty() { - bail!("no podman machine is running"); - } - Ok(name) -} - -fn ensure_image_and_get_digest(image: &str) -> Result { - let status = Command::new("podman") - .args(["image", "exists", image]) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status()?; - if !status.success() { - info!("pulling image {}...", image); - if !Command::new("podman") - .args(["pull", image]) - .status()? - .success() - { - bail!("failed to pull image: {}", image); - } - } - let output = Command::new("podman") - .args(["image", "inspect", "--format", "{{.Digest}}", image]) - .output()?; - let digest = String::from_utf8_lossy(&output.stdout).trim().to_string(); - Ok(digest.trim_start_matches("sha256:").to_string()) -} - -fn is_machine_rootful(machine: &str) -> bool { - Command::new("podman") - .args(["machine", "ssh", machine, "id", "-u"]) - .output() - .map(|o| String::from_utf8_lossy(&o.stdout).trim() == "0") - .unwrap_or(false) -} +// --- macOS-specific helpers (pub for vfkit/ module) --- /// Clear extended attributes from a file. /// @@ -647,11 +620,16 @@ pub fn start_gvproxy(gvproxy_sock: &str, services_sock: &str) -> Result Result<()> { +/// Expose a TCP port forwarding rule via gvproxy's HTTP API. +pub fn expose_port( + services_sock: &str, + vm_ip: &str, + host_port: u16, + guest_port: u16, +) -> Result<()> { let body = format!( - r#"{{"local":":{}","remote":"{}:22","protocol":"tcp"}}"#, - host_port, vm_ip + r#"{{"local":":{}","remote":"{}:{}","protocol":"tcp"}}"#, + host_port, vm_ip, guest_port ); let mut stream = UnixStream::connect(services_sock)?; let request = format!( @@ -674,8 +652,6 @@ pub fn expose_ssh_port(services_sock: &str, vm_ip: &str, host_port: u16) -> Resu Ok(()) } -const SSH_TIMEOUT: Duration = Duration::from_secs(240); - /// Find an available TCP port for SSH forwarding in range 2222-3000. pub fn find_available_ssh_port() -> u16 { use rand::Rng; @@ -696,130 +672,16 @@ pub fn find_available_ssh_port() -> u16 { PORT_RANGE_START } -/// Wait for SSH connectivity with exponential backoff (240s timeout). -pub fn wait_for_ssh(port: u16, key_path: &Path, user: &str) -> Result<()> { - use crate::ssh_options::CommonSshOptions; - let ssh_opts = CommonSshOptions::default(); - let user_host = format!("{}@localhost", user); - info!("waiting for SSH on port {} ({}@localhost)...", port, user); - let start = std::time::Instant::now(); - let mut attempt = 0u32; - loop { - if start.elapsed() > SSH_TIMEOUT { - bail!("SSH connection timeout ({}s)", SSH_TIMEOUT.as_secs()); - } - let mut cmd = Command::new("ssh"); - cmd.args(["-p", &port.to_string(), "-i", &key_path.to_string_lossy()]); - ssh_opts.apply_to_command(&mut cmd); - cmd.args(["-o", "BatchMode=yes", &user_host, "true"]); - let status = cmd.stdout(Stdio::null()).stderr(Stdio::null()).status(); - if let Ok(s) = status { - if s.success() { - info!("SSH connected after {}s", start.elapsed().as_secs()); - return Ok(()); - } - } - let backoff = if attempt < 2 { - 500 - } else if attempt < 4 { - 1000 - } else { - 2000 - }; - std::thread::sleep(Duration::from_millis(backoff)); - attempt += 1; - } -} - -/// Execute a command via SSH and return the exit status. -pub fn run_ssh_command( - port: u16, - key_path: &Path, - user: &str, - command: &str, -) -> Result { - use crate::ssh_options::CommonSshOptions; - let ssh_opts = CommonSshOptions::default(); - let user_host = format!("{}@localhost", user); - let mut cmd = Command::new("ssh"); - cmd.args(["-p", &port.to_string(), "-i", &key_path.to_string_lossy()]); - ssh_opts.apply_to_command(&mut cmd); - cmd.args(["-o", "BatchMode=yes", &user_host, command]); - cmd.stdin(Stdio::inherit()) - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .status() - .map_err(|e| eyre!("ssh failed: {}", e)) -} - -/// Start an interactive SSH session with TTY allocation. -pub fn run_ssh_interactive( - port: u16, - key_path: &Path, - user: &str, -) -> Result { - use crate::ssh_options::CommonSshOptions; - let ssh_opts = CommonSshOptions::default(); - let user_host = format!("{}@localhost", user); - let mut cmd = Command::new("ssh"); - cmd.args(["-p", &port.to_string(), "-i", &key_path.to_string_lossy()]); - ssh_opts.apply_to_command(&mut cmd); - cmd.args(["-t", &user_host]); - cmd.stdin(Stdio::inherit()) - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .status() - .map_err(|e| eyre!("ssh failed: {}", e)) -} - #[cfg(test)] mod tests { use super::*; - #[test] - fn test_parse_memory_to_mb() { - let cases = [ - ("4G", 4096), - ("4g", 4096), - ("2048M", 2048), - ("2048m", 2048), - ("512", 512), - ("1G", 1024), - ]; - for (input, expected) in &cases { - assert_eq!( - parse_memory_to_mb(input).unwrap(), - *expected, - "parse_memory_to_mb({:?})", - input - ); - } - } - - #[test] - fn test_parse_memory_to_mb_errors() { - assert!(parse_memory_to_mb("").is_err()); - assert!(parse_memory_to_mb("abc").is_err()); - } - #[test] fn test_generate_mac() { let mac = generate_mac(); assert_eq!(mac, GVPROXY_STATIC_MAC); } - #[test] - fn test_default_vcpus() { - let vcpus = default_vcpus(); - assert!(vcpus >= 1); - assert_eq!( - vcpus, - std::thread::available_parallelism() - .map(|n| n.get() as u32) - .unwrap_or(2) - ); - } - #[test] fn test_find_available_ssh_port() { let port = find_available_ssh_port(); diff --git a/crates/kit/src/to_disk_macos.rs b/crates/kit/src/to_disk_macos.rs new file mode 100644 index 000000000..834ea1398 --- /dev/null +++ b/crates/kit/src/to_disk_macos.rs @@ -0,0 +1,402 @@ +//! Install bootc images to disk on macOS using loopback devices via podman machine. +//! +//! Uses losetup inside podman machine to create loop devices from raw disk files +//! accessible via virtiofs, then runs `bootc install to-disk` targeting the loop device. +//! Base disk caching with APFS clonefile (`cp -c`) provides fast VM creation. + +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::{Command, Stdio}; + +use clap::Parser; +use color_eyre::eyre::{bail, Context}; +use color_eyre::Result; +use tracing::{debug, info}; + +use crate::install_options::InstallOptions; +use crate::run_ephemeral_macos::clear_xattr; +use crate::vm_helpers::{ + detect_machine_name, ensure_image_and_get_digest, generate_ssh_keypair, is_machine_rootful, + parse_size, remove_file_if_exists, +}; +use sha2::{Digest, Sha256}; + +/// Options for `bcvk to-disk` on macOS. +#[derive(Parser, Debug)] +pub struct ToDiskMacosOpts { + /// Container image to install + pub source_image: String, + /// Target disk path (output .raw file) + pub target_disk: String, + /// Disk size (e.g. "10G", "5120M", or plain number for bytes) + #[clap(long, default_value = "20G")] + pub disk_size: String, + /// Installation options (filesystem, root-size, etc.) + #[clap(flatten)] + pub install: InstallOptions, + /// Configure logging for `bootc install` by setting the `RUST_LOG` environment variable + #[clap(long)] + pub install_log: Option, + /// Add metadata to the container in key=value form + #[clap(long = "label")] + pub label: Vec, + /// Check if the disk would be regenerated without actually creating it + #[clap(long)] + pub dry_run: bool, +} + +fn base_dir() -> PathBuf { + dirs::home_dir() + .expect("cannot determine home directory") + .join(".local/share/bcvk/base") +} + +/// Directory for persistent VM disk images. +pub fn vms_dir() -> PathBuf { + dirs::home_dir() + .expect("cannot determine home directory") + .join(".local/share/bcvk/vms") +} + +fn resolve_path_in_machine(host_path: &str) -> String { + let resolved = if let Ok(canonical) = std::fs::canonicalize(host_path) { + canonical.to_string_lossy().to_string() + } else { + host_path.to_string() + }; + // macOS /tmp is a symlink to /private/tmp; podman machine mounts + // /private/tmp via virtiofs, so we need the canonical path. + // canonicalize() normally resolves this, but handle it explicitly. + if resolved.starts_with("/tmp/") { + format!("/private{}", resolved) + } else { + resolved + } +} + +fn create_raw_disk(path: &str, size_bytes: u64) -> Result<()> { + let file = fs::File::create(path).with_context(|| format!("creating {}", path))?; + file.set_len(size_bytes) + .with_context(|| format!("setting size {} on {}", size_bytes, path))?; + drop(file); + clear_xattr(Path::new(path)); + Ok(()) +} + +fn generate_bootc_install_script( + disk_path_in_machine: &str, + image: &str, + install_opts: &InstallOptions, + ssh_pubkey: &str, + rootful: bool, + install_log: &Option, + labels: &[String], +) -> String { + let bootc_args = install_opts + .to_bootc_args() + .iter() + .map(|a| { + shlex::try_quote(a) + .unwrap_or(std::borrow::Cow::Borrowed(a)) + .to_string() + }) + .collect::>() + .join(" "); + + let image_quoted = shlex::try_quote(image) + .unwrap_or(std::borrow::Cow::Borrowed(image)) + .to_string(); + + use base64::Engine; + let pub_key_b64 = base64::engine::general_purpose::STANDARD.encode(ssh_pubkey); + + let sudo = if rootful { "" } else { "sudo " }; + + let rust_log_line = if let Some(ref level) = install_log { + format!( + "export RUST_LOG={}\n", + shlex::try_quote(level).unwrap_or(std::borrow::Cow::Borrowed(level)) + ) + } else { + String::new() + }; + + let label_args = labels + .iter() + .map(|l| { + format!( + "--label {}", + shlex::try_quote(l).unwrap_or(std::borrow::Cow::Borrowed(l)) + ) + }) + .collect::>() + .join(" \\\n "); + let label_line = if label_args.is_empty() { + String::new() + } else { + format!(" {} \\\n", label_args) + }; + + format!( + r#"set -euo pipefail +{rust_log} +LOOP=$({sudo}losetup -fP --show {disk_path}) +echo "Loop device: $LOOP" +trap '{sudo}losetup -d $LOOP 2>/dev/null' EXIT + +printf '%s' '{b64}' | base64 -d > /dev/shm/bcvk-ssh-key.pub + +echo "Running bootc install to-disk..." +podman run --rm --privileged --pid=host --net=none \ + -v /dev:/dev \ + -v /dev/shm:/dev/shm \ + -v /var/lib/containers:/var/lib/containers \ +{label_line} {image} bootc install to-disk \ + --generic-image --skip-fetch-check --wipe \ + --root-ssh-authorized-keys /dev/shm/bcvk-ssh-key.pub \ + {bootc_args} $LOOP + +rm -f /dev/shm/bcvk-ssh-key.pub + +echo "Installation complete!" +"#, + rust_log = rust_log_line, + sudo = sudo, + disk_path = disk_path_in_machine, + b64 = pub_key_b64, + image = image_quoted, + bootc_args = bootc_args, + label_line = label_line, + ) +} + +const CACHE_HASH_XATTR: &str = "user.bcvk.cache_hash"; + +fn compute_cache_hash( + image_digest: &str, + source_image: &str, + install_opts: &InstallOptions, +) -> String { + let bootc_args = install_opts.to_bootc_args().join(","); + let input = format!("{}|{}|{}", image_digest, source_image, bootc_args); + let hash = Sha256::digest(input.as_bytes()); + format!("sha256:{:x}", hash) +} + +fn read_xattr(path: &Path, name: &str) -> Option { + let output = Command::new("xattr") + .args(["-p", name, &path.to_string_lossy()]) + .stdout(Stdio::piped()) + .stderr(Stdio::null()) + .output() + .ok()?; + if output.status.success() { + Some(String::from_utf8_lossy(&output.stdout).trim().to_string()) + } else { + None + } +} + +fn write_xattr(path: &Path, name: &str, value: &str) -> Result<()> { + let status = Command::new("xattr") + .args(["-w", name, value, &path.to_string_lossy()]) + .status() + .with_context(|| format!("writing xattr {} on {}", name, path.display()))?; + if !status.success() { + bail!("xattr -w failed for {} on {}", name, path.display()); + } + Ok(()) +} + +/// Find or create a cached base disk for the given image + install options. +pub fn find_or_create_base_disk( + source_image: &str, + image_digest: &str, + install_options: &InstallOptions, + disk_size: &str, + machine: &str, + install_log: &Option, + labels: &[String], +) -> Result { + let cache_hash = compute_cache_hash(image_digest, source_image, install_options); + let short_hash = cache_hash + .strip_prefix("sha256:") + .unwrap_or(&cache_hash) + .chars() + .take(16) + .collect::(); + + let base_dir = base_dir(); + fs::create_dir_all(&base_dir)?; + let base_disk_name = format!("bootc-base-{}.raw", short_hash); + let base_disk_path = base_dir.join(&base_disk_name); + + if base_disk_path.exists() { + debug!("checking existing base disk: {:?}", base_disk_path); + if let Some(stored_hash) = read_xattr(&base_disk_path, CACHE_HASH_XATTR) { + if stored_hash == cache_hash { + info!("reusing cached base disk: {:?}", base_disk_path); + return Ok(base_disk_path); + } + info!("base disk cache hash mismatch, recreating"); + } else { + info!("base disk has no cache hash, recreating"); + } + fs::remove_file(&base_disk_path)?; + } + + info!("creating base disk: {:?}", base_disk_path); + let base_disk_str = base_disk_path.to_string_lossy().to_string(); + + let size_bytes = parse_size(disk_size)?; + create_raw_disk(&base_disk_str, size_bytes)?; + + let key_path = PathBuf::from(format!("{}.key", base_disk_path.display())); + let ssh_pubkey = generate_ssh_keypair(&key_path)?; + + let disk_in_machine = resolve_path_in_machine(&base_disk_str); + let rootful = is_machine_rootful(machine); + let script = generate_bootc_install_script( + &disk_in_machine, + source_image, + install_options, + &ssh_pubkey, + rootful, + install_log, + labels, + ); + + info!("running bootc install to-disk in podman machine..."); + let mut child = Command::new("podman") + .args(["machine", "ssh", machine, "--", "bash", "-s"]) + .stdin(Stdio::piped()) + .spawn() + .context("podman machine ssh")?; + if let Some(mut stdin) = child.stdin.take() { + use std::io::Write; + stdin.write_all(script.as_bytes())?; + } + let status = child.wait()?; + + if !status.success() { + remove_file_if_exists(&base_disk_path); + remove_file_if_exists(&key_path); + remove_file_if_exists(&PathBuf::from(format!("{}.pub", key_path.display()))); + bail!("bootc install to-disk failed"); + } + + write_xattr(&base_disk_path, CACHE_HASH_XATTR, &cache_hash)?; + + Ok(base_disk_path) +} + +/// Clone a base disk to create a VM-specific disk via APFS clonefile (`cp -c`). +pub fn clone_base_disk(base_path: &Path, vm_disk_path: &Path) -> Result<()> { + if let Some(parent) = vm_disk_path.parent() { + fs::create_dir_all(parent)?; + } + let status = Command::new("cp") + .args([ + "-c", + &base_path.to_string_lossy(), + &vm_disk_path.to_string_lossy(), + ]) + .status() + .context("cp -c (APFS clonefile)")?; + if !status.success() { + bail!( + "APFS clonefile failed: {} -> {}", + base_path.display(), + vm_disk_path.display() + ); + } + clear_xattr(vm_disk_path); + Ok(()) +} + +/// Execute `bcvk to-disk` on macOS. +pub fn run(opts: ToDiskMacosOpts) -> Result<()> { + let machine = detect_machine_name()?; + let digest = ensure_image_and_get_digest(&opts.source_image)?; + info!("image digest: {}...", &digest[..16.min(digest.len())]); + + let cache_hash = compute_cache_hash(&digest, &opts.source_image, &opts.install); + let short_hash: String = cache_hash + .strip_prefix("sha256:") + .unwrap_or(&cache_hash) + .chars() + .take(16) + .collect(); + let base_disk_path = base_dir().join(format!("bootc-base-{}.raw", short_hash)); + + if opts.dry_run { + if base_disk_path.exists() { + if let Some(stored) = read_xattr(&base_disk_path, CACHE_HASH_XATTR) { + if stored == cache_hash { + println!("Would reuse cached base disk: {}", base_disk_path.display()); + if Path::new(&opts.target_disk).exists() { + println!("Output already exists: {}", opts.target_disk); + } else { + println!("Would create disk: {} (from base)", opts.target_disk); + } + return Ok(()); + } + } + println!("Would regenerate base disk (hash mismatch)"); + } else { + println!( + "Would create new base disk and output: {}", + opts.target_disk + ); + } + return Ok(()); + } + + let base_disk_path = find_or_create_base_disk( + &opts.source_image, + &digest, + &opts.install, + &opts.disk_size, + &machine, + &opts.install_log, + &opts.label, + )?; + + // Copy base disk to target via APFS clonefile + let target = Path::new(&opts.target_disk); + clone_base_disk(&base_disk_path, target)?; + + // Copy SSH key ({base}.raw.key → {target}.key) + let base_key = PathBuf::from(format!("{}.key", base_disk_path.display())); + let target_key = PathBuf::from(format!("{}.key", target.display())); + if base_key.exists() { + fs::copy(&base_key, &target_key).context("copying SSH key")?; + let base_pub = PathBuf::from(format!("{}.pub", base_key.display())); + let target_pub = PathBuf::from(format!("{}.pub", target_key.display())); + if base_pub.exists() { + fs::copy(&base_pub, &target_pub).context("copying SSH pubkey")?; + } + } + + println!("Disk image created: {}", opts.target_disk); + println!("SSH key: {}", target_key.display()); + println!( + "\nTo boot: bcvk vm run --ssh-key {} {}", + target_key.display(), + opts.target_disk + ); + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_resolve_path_in_machine() { + assert_eq!( + resolve_path_in_machine("/tmp/test.raw"), + "/private/tmp/test.raw" + ); + } +} diff --git a/crates/kit/src/vfkit/inspect.rs b/crates/kit/src/vfkit/inspect.rs index 67a506d7c..a539c2422 100644 --- a/crates/kit/src/vfkit/inspect.rs +++ b/crates/kit/src/vfkit/inspect.rs @@ -1,15 +1,34 @@ //! vm inspect — Show detailed VM information. -use super::VmMetadata; +use super::{OutputFormat, VmMetadata}; +use clap::Parser; use color_eyre::Result; +/// Options for `vm inspect`. +#[derive(Parser, Debug)] +pub struct VmInspectOpts { + /// VM name + pub name: String, + /// Output format + #[clap(long, value_enum, default_value_t = OutputFormat::Yaml)] + pub format: OutputFormat, +} + /// Display detailed metadata for the named VM. -pub fn run(name: &str, json: bool) -> Result<()> { - let meta = VmMetadata::load(name)?; +pub fn run(opts: VmInspectOpts) -> Result<()> { + let meta = VmMetadata::load(&opts.name)?; - if json { - println!("{}", serde_json::to_string_pretty(&meta)?); - return Ok(()); + match opts.format { + OutputFormat::Json => { + println!("{}", serde_json::to_string_pretty(&meta)?); + return Ok(()); + } + OutputFormat::Yaml | OutputFormat::Table => {} + OutputFormat::Xml => { + return Err(color_eyre::eyre::eyre!( + "XML format is not supported for inspect command" + )); + } } let state = if meta.is_alive() { @@ -21,8 +40,8 @@ pub fn run(name: &str, json: bool) -> Result<()> { println!("Name: {}", meta.name); println!("State: {}", state); println!("Disk: {}", meta.disk_image); - println!("CPUs: {}", meta.cpus); - println!("Memory: {} MiB", meta.memory); + println!("CPUs: {}", meta.vcpus); + println!("Memory: {} MiB", meta.memory_mb); println!("GUI: {}", meta.gui); println!("Created: {}", meta.created); println!(); @@ -53,6 +72,17 @@ pub fn run(name: &str, json: bool) -> Result<()> { meta.ssh_port, meta.ssh_key, meta.ssh_user ); } + if !meta.labels.is_empty() { + println!(); + println!("Labels: {}", meta.labels.join(", ")); + } + if !meta.port_mappings.is_empty() { + println!(); + println!("Port mappings:"); + for (h, g) in &meta.port_mappings { + println!(" {}:{}", h, g); + } + } println!(); println!("Files:"); println!(" EFI store: {}", meta.efi_store); diff --git a/crates/kit/src/vfkit/list.rs b/crates/kit/src/vfkit/list.rs index bdda3f295..397856573 100644 --- a/crates/kit/src/vfkit/list.rs +++ b/crates/kit/src/vfkit/list.rs @@ -1,29 +1,85 @@ //! vm list — List all persistent VMs. -use super::VmMetadata; +use super::{OutputFormat, VmMetadata}; +use clap::Parser; use color_eyre::Result; -/// List all persistent VMs, optionally as JSON. -pub fn run(json: bool) -> Result<()> { - let vms = VmMetadata::list_all()?; +/// Options for `vm list`. +#[derive(Parser, Debug)] +pub struct VmListOpts { + /// VM name to query (returns only this VM) + pub domain_name: Option, + /// Output format + #[clap(long, value_enum, default_value_t = OutputFormat::Table)] + pub format: OutputFormat, + /// Show all VMs including stopped ones + #[clap(long, short = 'a')] + pub all: bool, + /// Filter VMs by label + #[clap(long)] + pub label: Option, +} - if json { - println!("{}", serde_json::to_string_pretty(&vms)?); - return Ok(()); - } +/// List persistent VMs with optional filtering and format selection. +pub fn run(opts: VmListOpts) -> Result<()> { + let all_vms = if let Some(ref name) = opts.domain_name { + match VmMetadata::load(name) { + Ok(meta) => vec![meta], + Err(e) => { + return Err(color_eyre::eyre::eyre!( + "Failed to get VM '{}': {}", + name, + e + )); + } + } + } else { + VmMetadata::list_all()? + }; + + let mut vms: Vec<_> = all_vms + .into_iter() + .filter(|vm| opts.all || opts.domain_name.is_some() || vm.is_alive()) + .collect(); - if vms.is_empty() { - println!("No VMs found."); - return Ok(()); + if let Some(ref filter_label) = opts.label { + vms.retain(|vm| vm.labels.contains(filter_label)); } - println!("{:<20} {:<10} {:<30} SSH", "NAME", "STATE", "DISK"); - for vm in &vms { - let state = if vm.is_alive() { "running" } else { "stopped" }; - println!( - "{:<20} {:<10} {:<30} ssh -p {} -i {} {}@localhost", - vm.name, state, vm.disk_image, vm.ssh_port, vm.ssh_key, vm.ssh_user - ); + match opts.format { + OutputFormat::Table => { + if vms.is_empty() { + println!("No VMs found."); + return Ok(()); + } + println!("{:<20} {:<10} {:<30} SSH", "NAME", "STATE", "DISK"); + for vm in &vms { + let state = if vm.is_alive() { "running" } else { "stopped" }; + println!( + "{:<20} {:<10} {:<30} ssh -p {} -i {} {}@localhost", + vm.name, state, vm.disk_image, vm.ssh_port, vm.ssh_key, vm.ssh_user + ); + } + } + OutputFormat::Json => { + println!("{}", serde_json::to_string_pretty(&vms)?); + } + OutputFormat::Yaml => { + for vm in &vms { + let state = if vm.is_alive() { "running" } else { "stopped" }; + println!("- name: {}", vm.name); + println!(" state: {}", state); + println!(" disk: {}", vm.disk_image); + println!(" vcpus: {}", vm.vcpus); + println!(" memory_mb: {}", vm.memory_mb); + println!(" ssh_port: {}", vm.ssh_port); + } + } + OutputFormat::Xml => { + return Err(color_eyre::eyre::eyre!( + "XML format is not supported for list command" + )); + } } Ok(()) } diff --git a/crates/kit/src/vfkit/mod.rs b/crates/kit/src/vfkit/mod.rs index 2062851d5..347f242d5 100644 --- a/crates/kit/src/vfkit/mod.rs +++ b/crates/kit/src/vfkit/mod.rs @@ -18,6 +18,20 @@ pub mod ssh; pub mod start; pub mod stop; +/// Output format for inspect and list commands. +#[derive(Debug, Clone, clap::ValueEnum)] +#[clap(rename_all = "kebab-case")] +pub enum OutputFormat { + /// Table format (default for list) + Table, + /// JSON format + Json, + /// YAML-like key-value format (default for inspect) + Yaml, + /// XML format (not yet implemented) + Xml, +} + /// Subcommands for persistent VM management via vfkit. #[derive(Debug, Subcommand)] pub enum VmCommands { @@ -26,20 +40,13 @@ pub enum VmCommands { /// List all persistent VMs #[clap(name = "list", alias = "ls")] - List { - /// Output in JSON format - #[clap(long)] - json: bool, - }, + List(list::VmListOpts), /// SSH into a running VM Ssh(ssh::VmSshOpts), /// Stop a running VM - Stop { - /// VM name - name: String, - }, + Stop(stop::VmStopOpts), /// Start a stopped VM Start(start::VmStartOpts), @@ -50,20 +57,10 @@ pub enum VmCommands { /// Remove all VMs #[clap(name = "rm-all")] - RemoveAll { - /// Force removal without confirmation - #[clap(short, long)] - force: bool, - }, + RemoveAll(rm_all::VmRmAllOpts), /// Show detailed VM information - Inspect { - /// VM name - name: String, - /// Output in JSON format - #[clap(long)] - json: bool, - }, + Inspect(inspect::VmInspectOpts), } impl VmCommands { @@ -71,13 +68,13 @@ impl VmCommands { pub fn run(self) -> Result<()> { match self { VmCommands::Run(opts) => run::run(opts), - VmCommands::List { json } => list::run(json), + VmCommands::List(opts) => list::run(opts), VmCommands::Ssh(opts) => ssh::run(opts), - VmCommands::Stop { name } => stop::run(&name), + VmCommands::Stop(opts) => stop::run(opts), VmCommands::Start(opts) => start::run(opts), VmCommands::Remove(opts) => rm::run(opts), - VmCommands::RemoveAll { force } => rm_all::run(force), - VmCommands::Inspect { name, json } => inspect::run(&name, json), + VmCommands::RemoveAll(opts) => rm_all::run(opts), + VmCommands::Inspect(opts) => inspect::run(opts), } } } @@ -89,6 +86,9 @@ impl VmCommands { pub struct VmMetadata { /// VM name used as identifier. pub name: String, + /// Container image used to create this VM (None if created from disk directly). + #[serde(default, skip_serializing_if = "Option::is_none")] + pub image: Option, /// Path to the disk image file. pub disk_image: String, /// PID of the vfkit process. @@ -102,9 +102,9 @@ pub struct VmMetadata { /// SSH username for connecting to the VM. pub ssh_user: String, /// Number of vCPUs allocated. - pub cpus: u32, + pub vcpus: u32, /// Memory in megabytes. - pub memory: u32, + pub memory_mb: u32, /// Path to the EFI variable store file. pub efi_store: String, /// Path to the serial console log file. @@ -115,6 +115,12 @@ pub struct VmMetadata { pub created: String, /// Current VM state (running, stopped). pub state: String, + /// User-defined labels for organizing VMs. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub labels: Vec, + /// Port mappings from host to VM (host_port, guest_port). + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub port_mappings: Vec<(u16, u16)>, } impl VmMetadata { @@ -187,19 +193,22 @@ mod tests { fn sample_vm_metadata(name: &str) -> VmMetadata { VmMetadata { name: name.to_string(), + image: None, disk_image: "/tmp/disk.raw".to_string(), vfkit_pid: 0, gvproxy_pid: 0, ssh_port: 2222, ssh_key: "/tmp/key".to_string(), ssh_user: "root".to_string(), - cpus: 2, - memory: 4096, + vcpus: 2, + memory_mb: 4096, efi_store: "/tmp/efi.fd".to_string(), serial_log: "/tmp/serial.log".to_string(), gui: false, created: "2026-01-01T00:00:00Z".to_string(), state: "running".to_string(), + labels: vec![], + port_mappings: vec![], } } @@ -210,8 +219,8 @@ mod tests { let loaded: VmMetadata = serde_json::from_str(&json).unwrap(); assert_eq!(loaded.name, "test-vm"); assert_eq!(loaded.disk_image, "/tmp/disk.raw"); - assert_eq!(loaded.cpus, 2); - assert_eq!(loaded.memory, 4096); + assert_eq!(loaded.vcpus, 2); + assert_eq!(loaded.memory_mb, 4096); assert_eq!(loaded.ssh_user, "root"); assert_eq!(loaded.state, "running"); assert!(!loaded.gui); diff --git a/crates/kit/src/vfkit/rm.rs b/crates/kit/src/vfkit/rm.rs index ec48044e8..e78f6212f 100644 --- a/crates/kit/src/vfkit/rm.rs +++ b/crates/kit/src/vfkit/rm.rs @@ -16,6 +16,9 @@ pub struct VmRmOpts { /// Force removal even if running #[clap(short, long)] pub force: bool, + /// Stop domain if it's running (implied by --force) + #[clap(long)] + pub stop: bool, } /// Remove a persistent VM, optionally force-killing it. @@ -23,14 +26,34 @@ pub fn run(opts: VmRmOpts) -> Result<()> { let meta = VmMetadata::load(&opts.name)?; if meta.is_alive() { - if !opts.force { + if !(opts.force || opts.stop) { color_eyre::eyre::bail!( - "VM '{}' is running. Stop it first or use --force", + "VM '{}' is running. Stop it first or use --force/--stop", opts.name ); } info!("force stopping VM '{}'...", opts.name); - crate::vfkit::stop::run(&opts.name)?; + crate::vfkit::stop::run(crate::vfkit::stop::VmStopOpts { + name: opts.name.clone(), + force: true, + })?; + } + + // Remove disk image and SSH keys if they are inside the bcvk vms directory + // (i.e., created by `bcvk run`). User-provided disks from `bcvk vm run` are left alone. + let vms_dir = VmMetadata::vms_dir(); + if std::path::Path::new(&meta.disk_image).starts_with(&vms_dir) { + for path in [ + meta.disk_image.clone(), + format!("{}.key", meta.disk_image), + format!("{}.key.pub", meta.disk_image), + ] { + if let Err(e) = fs::remove_file(&path) { + if e.kind() != std::io::ErrorKind::NotFound { + tracing::debug!("failed to remove {}: {}", path, e); + } + } + } } for path in [&meta.efi_store, &meta.serial_log] { @@ -43,7 +66,6 @@ pub fn run(opts: VmRmOpts) -> Result<()> { } } - let vms_dir = VmMetadata::vms_dir(); for suffix in ["-gvproxy.sock", "-gvproxy-svc.sock"] { let p = vms_dir.join(format!("{}{}", meta.name, suffix)); if let Err(e) = fs::remove_file(&p) { diff --git a/crates/kit/src/vfkit/rm_all.rs b/crates/kit/src/vfkit/rm_all.rs index 2ed80df66..570a906a3 100644 --- a/crates/kit/src/vfkit/rm_all.rs +++ b/crates/kit/src/vfkit/rm_all.rs @@ -3,17 +3,41 @@ use std::io::Write; use super::VmMetadata; +use clap::Parser; use color_eyre::Result; -/// Remove all persistent VMs, prompting unless `force` is set. -pub fn run(force: bool) -> Result<()> { - let vms = VmMetadata::list_all()?; +/// Options for `vm rm-all`. +#[derive(Parser, Debug)] +pub struct VmRmAllOpts { + /// Force removal without confirmation + #[clap(long, short = 'f')] + pub force: bool, + /// Stop running VMs before removal (gentler than --force kill) + #[clap(long)] + pub stop: bool, + /// Only remove VMs with this label + #[clap(long)] + pub label: Option, +} + +/// Remove all persistent VMs, with optional label filtering. +pub fn run(opts: VmRmAllOpts) -> Result<()> { + let mut vms = VmMetadata::list_all()?; + + if let Some(ref filter_label) = opts.label { + vms.retain(|v| v.labels.contains(filter_label)); + } + if vms.is_empty() { - println!("No VMs found."); + if let Some(ref label) = opts.label { + println!("No VMs found with label '{}'", label); + } else { + println!("No VMs found."); + } return Ok(()); } - if !force { + if !opts.force { println!("Found {} VM(s):", vms.len()); for vm in &vms { println!( @@ -34,11 +58,20 @@ pub fn run(force: bool) -> Result<()> { } for vm in &vms { - let opts = super::rm::VmRmOpts { + if vm.is_alive() && opts.stop { + if let Err(e) = super::stop::run(super::stop::VmStopOpts { + name: vm.name.clone(), + force: false, + }) { + tracing::warn!("failed to stop '{}': {}", vm.name, e); + } + } + let rm_opts = super::rm::VmRmOpts { name: vm.name.clone(), force: true, + stop: false, }; - super::rm::run(opts)?; + super::rm::run(rm_opts)?; } Ok(()) } diff --git a/crates/kit/src/vfkit/run.rs b/crates/kit/src/vfkit/run.rs index 389aa0ca7..5fadf89f9 100644 --- a/crates/kit/src/vfkit/run.rs +++ b/crates/kit/src/vfkit/run.rs @@ -1,7 +1,7 @@ -//! vm run — Start a persistent VM from a disk image using vfkit + EFI boot. +//! vm run — Start a persistent VM from a container image or disk image. use std::fs; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use clap::Parser; @@ -10,22 +10,62 @@ use tracing::info; use super::VmMetadata; use crate::run_ephemeral_macos::{ - clear_xattr, expose_ssh_port, find_available_ssh_port, find_vfkit, generate_mac, start_gvproxy, - wait_for_ssh, + clear_xattr, expose_port, find_available_ssh_port, find_vfkit, generate_mac, start_gvproxy, }; +use crate::vm_helpers::{ + detect_machine_name, ensure_image_and_get_digest, parse_memory_to_mb, remove_file_if_exists, + run_ssh_interactive, sanitize_vm_name, wait_for_ssh, +}; + +/// Port mapping from host to VM (format: host_port:guest_port). +#[derive(Debug, Clone)] +pub struct PortMapping { + /// Host-side port number. + pub host_port: u16, + /// Guest-side port number. + pub guest_port: u16, +} + +impl std::str::FromStr for PortMapping { + type Err = color_eyre::Report; + fn from_str(s: &str) -> Result { + let (host_part, guest_part) = s.split_once(':').ok_or_else(|| { + color_eyre::eyre::eyre!( + "Invalid port format '{}'. Expected format: host_port:guest_port", + s + ) + })?; + let host_port = host_part + .trim() + .parse::() + .map_err(|_| color_eyre::eyre::eyre!("Invalid host port '{}'", host_part))?; + let guest_port = guest_part + .trim() + .parse::() + .map_err(|_| color_eyre::eyre::eyre!("Invalid guest port '{}'", guest_part))?; + Ok(PortMapping { + host_port, + guest_port, + }) + } +} /// Options for `vm run`. #[derive(Parser, Debug)] pub struct VmRunOpts { - /// Disk image path (.raw) - pub disk: String, - /// VM name for identification - #[clap(long)] + /// Container image or disk image path (.raw) + #[clap(default_value = "")] + pub image_or_disk: String, + /// VM name (default: derived from image or disk filename) + #[clap(long, short)] pub name: Option, - /// Number of vCPUs + /// Instance type (e.g., u1.nano, u1.small). Overrides vcpus/memory if specified. + #[clap(long)] + pub itype: Option, + /// Number of vCPUs (overridden by --itype if specified) #[clap(long)] pub vcpus: Option, - /// Memory size (e.g. "4G", "2048M", or plain number for MB) + /// Memory size (overridden by --itype if specified) #[clap(long, default_value = "4G")] pub memory: String, /// Path to an existing SSH private key @@ -40,30 +80,159 @@ pub struct VmRunOpts { /// Display VM console in GUI window #[clap(long)] pub gui: bool, + /// Disk size for to-disk (e.g. "10G", "20G") + #[clap(long, default_value = "20G")] + pub disk_size: String, + /// Installation options (filesystem, root-size, etc.) + #[clap(flatten)] + pub install: crate::install_options::InstallOptions, + /// Replace existing VM with same name + #[clap(long, short = 'R')] + pub replace: bool, + /// Port mapping from host to VM (format: host_port:guest_port, e.g. 8080:80) + #[clap(long = "port", short = 'p', action = clap::ArgAction::Append)] + pub port_mappings: Vec, + /// User-defined labels for organizing VMs (comma not allowed in labels) + #[clap(long)] + pub label: Vec, + /// Automatically SSH into the VM after creation + #[clap(long)] + pub ssh: bool, + /// Wait for SSH to become available and verify connectivity (for testing) + #[clap(long, conflicts_with = "ssh")] + pub ssh_wait: bool, + /// Keep the VM running in background after creation (always true for vfkit) + #[clap(long, short = 'd')] + pub detach: bool, } -/// Create and launch a persistent VM from a disk image via vfkit + EFI. -pub fn run(opts: VmRunOpts) -> Result<()> { - let vfkit_bin = find_vfkit()?; +fn validate_labels(labels: &[String]) -> Result<()> { + for label in labels { + if label.contains(',') { + bail!("Label '{}' contains comma which is not allowed", label); + } + } + Ok(()) +} + +fn is_disk_path(input: &str) -> bool { + let p = Path::new(input); + p.extension() + .map(|e| e == "raw" || e == "img" || e == "qcow2") + .unwrap_or(false) + || p.exists() +} - if !Path::new(&opts.disk).exists() { - bail!("disk image not found: {}", opts.disk); +/// Create and launch a persistent VM. +pub fn run(opts: VmRunOpts) -> Result<()> { + if opts.image_or_disk.is_empty() { + bail!("container image or disk path required"); } - clear_xattr(Path::new(&opts.disk)); + validate_labels(&opts.label)?; + + let (disk_path_str, image_name) = if is_disk_path(&opts.image_or_disk) { + let p = Path::new(&opts.image_or_disk); + if !p.exists() { + bail!("disk image not found: {}", opts.image_or_disk); + } + (opts.image_or_disk.clone(), None) + } else { + let image = &opts.image_or_disk; + let vm_name = opts.name.clone().unwrap_or_else(|| sanitize_vm_name(image)); - let ssh_key_path = match &opts.ssh_key { - Some(p) => p.clone(), - None => find_ssh_key()?, + if vm_name.is_empty() { + bail!("could not derive VM name from image. Use --name to specify one."); + } + + // Check existing VM + if let Ok(existing) = VmMetadata::load(&vm_name) { + if opts.replace { + info!("replacing existing VM '{}'", vm_name); + if existing.is_alive() { + if let Some(pid) = rustix::process::Pid::from_raw(existing.vfkit_pid as i32) { + if let Err(e) = + rustix::process::kill_process(pid, rustix::process::Signal::KILL) + { + tracing::warn!( + "failed to kill vfkit (pid {}): {}", + existing.vfkit_pid, + e + ); + } + } + if let Some(pid) = rustix::process::Pid::from_raw(existing.gvproxy_pid as i32) { + if let Err(e) = + rustix::process::kill_process(pid, rustix::process::Signal::KILL) + { + tracing::warn!( + "failed to kill gvproxy (pid {}): {}", + existing.gvproxy_pid, + e + ); + } + } + std::thread::sleep(std::time::Duration::from_millis(500)); + } + VmMetadata::remove(&vm_name); + } else { + bail!( + "VM '{}' already exists. Use --replace to overwrite, or --name to choose a different name.", + vm_name + ); + } + } + + let vms_dir = crate::to_disk_macos::vms_dir(); + fs::create_dir_all(&vms_dir)?; + let disk_path = vms_dir.join(format!("{}.raw", vm_name)); + let key_path = PathBuf::from(format!("{}.key", disk_path.display())); + let key_pub_path = PathBuf::from(format!("{}.pub", key_path.display())); + + if opts.replace { + remove_file_if_exists(&disk_path); + remove_file_if_exists(&key_path); + remove_file_if_exists(&key_pub_path); + } + + if !disk_path.exists() { + info!("creating disk image for VM '{}'...", vm_name); + let machine = detect_machine_name()?; + let digest = ensure_image_and_get_digest(image)?; + + let base_disk_path = crate::to_disk_macos::find_or_create_base_disk( + image, + &digest, + &opts.install, + &opts.disk_size, + &machine, + &None, + &[], + )?; + + crate::to_disk_macos::clone_base_disk(&base_disk_path, &disk_path)?; + + let base_key = PathBuf::from(format!("{}.key", base_disk_path.display())); + if base_key.exists() { + fs::copy(&base_key, &key_path)?; + let base_pub = PathBuf::from(format!("{}.pub", base_key.display())); + if base_pub.exists() { + fs::copy(&base_pub, &key_pub_path)?; + } + } + } + + ( + disk_path.to_string_lossy().to_string(), + Some(image.to_string()), + ) }; - if !Path::new(&ssh_key_path).exists() { - bail!( - "SSH key not found: {}. Specify with --ssh-key", - ssh_key_path - ); - } + + clear_xattr(Path::new(&disk_path_str)); + + let ssh_key_path = find_ssh_key(&opts.ssh_key, &disk_path_str)?; let vm_name = opts.name.clone().unwrap_or_else(|| { - Path::new(&opts.disk) + Path::new(&disk_path_str) .file_stem() .and_then(|s| s.to_str()) .unwrap_or("vm") @@ -90,9 +259,14 @@ pub fn run(opts: VmRunOpts) -> Result<()> { mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] ); - let vcpus = opts.vcpus.unwrap_or(2); - let memory_mb = crate::run_ephemeral_macos::parse_memory_to_mb(&opts.memory)?; + let vcpus = opts.itype.map(|t| t.vcpus()).or(opts.vcpus).unwrap_or(2); + let memory_mb = opts + .itype + .map(|t| t.memory_mb()) + .map(Ok) + .unwrap_or_else(|| parse_memory_to_mb(&opts.memory))?; + let vfkit_bin = find_vfkit()?; let mut vfkit_args = vec![ "--cpus".to_string(), vcpus.to_string(), @@ -101,7 +275,7 @@ pub fn run(opts: VmRunOpts) -> Result<()> { "--bootloader".to_string(), format!("efi,variable-store={},create", efi_store.display()), "--device".to_string(), - format!("virtio-blk,path={}", opts.disk), + format!("virtio-blk,path={}", disk_path_str), "--device".to_string(), format!( "virtio-net,unixSocketPath={},mac={}", @@ -128,7 +302,7 @@ pub fn run(opts: VmRunOpts) -> Result<()> { info!("setting up SSH port forwarding..."); for attempt in 0..15u32 { - match expose_ssh_port(&services_sock_str, "192.168.127.2", ssh_port) { + match expose_port(&services_sock_str, "192.168.127.2", ssh_port, 22) { Ok(_) => { info!("SSH port {} forwarded", ssh_port); break; @@ -142,24 +316,41 @@ pub fn run(opts: VmRunOpts) -> Result<()> { } } + for pm in &opts.port_mappings { + expose_port( + &services_sock_str, + "192.168.127.2", + pm.host_port, + pm.guest_port, + )?; + info!("port {}:{} forwarded", pm.host_port, pm.guest_port); + } + let key_path = std::path::Path::new(&ssh_key_path); wait_for_ssh(ssh_port, key_path, &opts.ssh_user)?; let metadata = VmMetadata { name: vm_name.clone(), - disk_image: opts.disk.clone(), + image: image_name, + disk_image: disk_path_str.clone(), vfkit_pid: vfkit_child.id(), gvproxy_pid: gvproxy_child.id(), ssh_port, ssh_key: ssh_key_path.clone(), ssh_user: opts.ssh_user.clone(), - cpus: vcpus, - memory: memory_mb, + vcpus, + memory_mb, efi_store: efi_store.to_string_lossy().to_string(), serial_log: serial_log.to_string_lossy().to_string(), gui: opts.gui, created: chrono::Utc::now().to_rfc3339(), state: "running".to_string(), + labels: opts.label.clone(), + port_mappings: opts + .port_mappings + .iter() + .map(|pm| (pm.host_port, pm.guest_port)) + .collect(), }; metadata.save()?; @@ -172,10 +363,30 @@ pub fn run(opts: VmRunOpts) -> Result<()> { println!("To connect: bcvk vm ssh {}", vm_name); println!("To stop: bcvk vm stop {}", vm_name); + if opts.ssh_wait { + println!("Ready; use bcvk vm ssh to connect"); + return Ok(()); + } + if opts.ssh { + let status = run_ssh_interactive(ssh_port, key_path, &opts.ssh_user)?; + std::process::exit(status.code().unwrap_or(1)); + } + Ok(()) } -fn find_ssh_key() -> Result { +fn find_ssh_key(explicit: &Option, disk_path: &str) -> Result { + if let Some(p) = explicit { + if !Path::new(p).exists() { + bail!("SSH key not found: {}", p); + } + return Ok(p.clone()); + } + let auto_key = format!("{}.key", disk_path); + if Path::new(&auto_key).exists() { + info!("using auto-generated SSH key: {}", auto_key); + return Ok(auto_key); + } let home = dirs::home_dir() .ok_or_else(|| color_eyre::eyre::eyre!("cannot determine home directory"))?; for name in &["id_ed25519", "id_rsa"] { @@ -184,5 +395,5 @@ fn find_ssh_key() -> Result { return Ok(path.to_string_lossy().to_string()); } } - bail!("no SSH key found in ~/.ssh/. Generate with: ssh-keygen -t ed25519") + bail!("no SSH key found. Specify with --ssh-key") } diff --git a/crates/kit/src/vfkit/ssh.rs b/crates/kit/src/vfkit/ssh.rs index 74af46736..84527e8a8 100644 --- a/crates/kit/src/vfkit/ssh.rs +++ b/crates/kit/src/vfkit/ssh.rs @@ -1,7 +1,7 @@ //! vm ssh — SSH into a running persistent VM. use super::VmMetadata; -use crate::run_ephemeral_macos::run_ssh_interactive; +use crate::vm_helpers::{run_ssh_command, run_ssh_interactive}; use clap::Parser; use color_eyre::{eyre::bail, Result}; @@ -10,15 +10,25 @@ use color_eyre::{eyre::bail, Result}; pub struct VmSshOpts { /// VM name pub name: String, + /// Additional SSH arguments + #[clap(trailing_var_arg = true, allow_hyphen_values = true)] + pub args: Vec, } -/// Open an interactive SSH session to a running persistent VM. +/// Open an SSH session to a running persistent VM. pub fn run(opts: VmSshOpts) -> Result<()> { let vm = VmMetadata::load(&opts.name)?; if !vm.is_alive() { bail!("VM '{}' is not running", opts.name); } let key_path = std::path::Path::new(&vm.ssh_key); - run_ssh_interactive(vm.ssh_port, key_path, &vm.ssh_user)?; + if opts.args.is_empty() { + run_ssh_interactive(vm.ssh_port, key_path, &vm.ssh_user)?; + } else { + let cmd = shlex::try_join(opts.args.iter().map(|s| s.as_str())) + .map_err(|e| color_eyre::eyre::eyre!("failed to escape SSH args: {}", e))?; + let status = run_ssh_command(vm.ssh_port, key_path, &vm.ssh_user, &cmd)?; + std::process::exit(status.code().unwrap_or(1)); + } Ok(()) } diff --git a/crates/kit/src/vfkit/start.rs b/crates/kit/src/vfkit/start.rs index f2f2a48f3..4e97c2e30 100644 --- a/crates/kit/src/vfkit/start.rs +++ b/crates/kit/src/vfkit/start.rs @@ -8,8 +8,9 @@ use tracing::info; use super::VmMetadata; use crate::run_ephemeral_macos::{ - clear_xattr, expose_ssh_port, find_vfkit, generate_mac, start_gvproxy, wait_for_ssh, + clear_xattr, expose_port, find_vfkit, generate_mac, start_gvproxy, }; +use crate::vm_helpers::{run_ssh_interactive, wait_for_ssh}; /// Options for `vm start`. #[derive(Parser, Debug)] @@ -19,6 +20,9 @@ pub struct VmStartOpts { /// Display VM console in GUI window #[clap(long)] pub gui: bool, + /// Automatically SSH into the VM after starting + #[clap(long)] + pub ssh: bool, } /// Restart a stopped persistent VM by re-launching vfkit. @@ -53,9 +57,9 @@ pub fn run(opts: VmStartOpts) -> Result<()> { let gui = opts.gui || meta.gui; let mut vfkit_args = vec![ "--cpus".to_string(), - meta.cpus.to_string(), + meta.vcpus.to_string(), "--memory".to_string(), - meta.memory.to_string(), + meta.memory_mb.to_string(), "--bootloader".to_string(), format!("efi,variable-store={},create", meta.efi_store), "--device".to_string(), @@ -83,7 +87,7 @@ pub fn run(opts: VmStartOpts) -> Result<()> { info!("setting up SSH port forwarding..."); for attempt in 0..15u32 { - match expose_ssh_port(&services_sock_str, "192.168.127.2", meta.ssh_port) { + match expose_port(&services_sock_str, "192.168.127.2", meta.ssh_port, 22) { Ok(_) => { info!("SSH port {} forwarded", meta.ssh_port); break; @@ -97,6 +101,11 @@ pub fn run(opts: VmStartOpts) -> Result<()> { } } + for &(host_port, guest_port) in &meta.port_mappings { + expose_port(&services_sock_str, "192.168.127.2", host_port, guest_port)?; + info!("port {}:{} forwarded", host_port, guest_port); + } + let key_path = std::path::Path::new(&meta.ssh_key); wait_for_ssh(meta.ssh_port, key_path, &meta.ssh_user)?; @@ -111,5 +120,11 @@ pub fn run(opts: VmStartOpts) -> Result<()> { " ssh -p {} -i {} {}@localhost", meta.ssh_port, meta.ssh_key, meta.ssh_user ); + + if opts.ssh { + let status = run_ssh_interactive(meta.ssh_port, key_path, &meta.ssh_user)?; + std::process::exit(status.code().unwrap_or(1)); + } + Ok(()) } diff --git a/crates/kit/src/vfkit/stop.rs b/crates/kit/src/vfkit/stop.rs index 52c69fb51..acc8cd4de 100644 --- a/crates/kit/src/vfkit/stop.rs +++ b/crates/kit/src/vfkit/stop.rs @@ -6,25 +6,41 @@ use super::VmMetadata; use color_eyre::{eyre::bail, Result}; use tracing::info; -/// Stop a running persistent VM by sending SIGTERM to vfkit. -pub fn run(name: &str) -> Result<()> { - let mut meta = VmMetadata::load(name)?; +/// Options for `vm stop`. +#[derive(clap::Parser, Debug)] +pub struct VmStopOpts { + /// VM name + pub name: String, + /// Force immediate power-off (SIGKILL) instead of graceful shutdown + #[clap(long, short = 'f')] + pub force: bool, +} + +/// Stop a running persistent VM. +pub fn run(opts: VmStopOpts) -> Result<()> { + let mut meta = VmMetadata::load(&opts.name)?; if !meta.is_alive() { - bail!("VM '{}' is not running", name); + bail!("VM '{}' is not running", opts.name); } - info!("stopping VM '{}'...", name); + info!("stopping VM '{}'...", opts.name); if meta.vfkit_pid > 0 { let pid = rustix::process::Pid::from_raw(meta.vfkit_pid as i32).unwrap(); - if let Err(e) = rustix::process::kill_process(pid, rustix::process::Signal::TERM) { - tracing::debug!("failed to SIGTERM vfkit (PID {}): {}", meta.vfkit_pid, e); - } - std::thread::sleep(Duration::from_secs(3)); - if meta.is_alive() { + if opts.force { if let Err(e) = rustix::process::kill_process(pid, rustix::process::Signal::KILL) { tracing::debug!("failed to SIGKILL vfkit (PID {}): {}", meta.vfkit_pid, e); } + } else { + if let Err(e) = rustix::process::kill_process(pid, rustix::process::Signal::TERM) { + tracing::debug!("failed to SIGTERM vfkit (PID {}): {}", meta.vfkit_pid, e); + } + std::thread::sleep(Duration::from_secs(3)); + if meta.is_alive() { + if let Err(e) = rustix::process::kill_process(pid, rustix::process::Signal::KILL) { + tracing::debug!("failed to SIGKILL vfkit (PID {}): {}", meta.vfkit_pid, e); + } + } } } @@ -46,6 +62,6 @@ pub fn run(name: &str) -> Result<()> { meta.gvproxy_pid = 0; meta.save()?; - println!("Stopped '{}'", name); + println!("Stopped '{}'", opts.name); Ok(()) } diff --git a/crates/kit/src/vm_helpers.rs b/crates/kit/src/vm_helpers.rs new file mode 100644 index 000000000..0c03157d0 --- /dev/null +++ b/crates/kit/src/vm_helpers.rs @@ -0,0 +1,340 @@ +//! Shared helpers for macOS/Windows VM management. +//! +//! Functions in this module are OS-independent (use `podman` and `ssh` CLI). +//! Modelled after `ssh_options.rs` — designed for future cross-platform sharing. + +use std::path::Path; +use std::process::{Command, Stdio}; +use std::time::Duration; + +use color_eyre::{eyre::bail, eyre::eyre, eyre::Context, Result}; +use tracing::info; + +use crate::ssh_options::CommonSshOptions; + +/// SSH connection timeout (shared by wait_for_ssh). +pub const SSH_TIMEOUT: Duration = Duration::from_secs(240); + +/// Detect the currently active podman machine name. +pub fn detect_machine_name() -> Result { + let output = Command::new("podman") + .args(["machine", "info", "--format", "{{.Host.CurrentMachine}}"]) + .output()?; + let name = String::from_utf8_lossy(&output.stdout).trim().to_string(); + if name.is_empty() { + bail!("no podman machine is running"); + } + Ok(name) +} + +/// Detect the podman machine VM type (e.g. "hyperv", "wsl", "libkrun", "applehv"). +#[allow(dead_code)] +pub fn detect_podman_vmtype() -> Result { + let output = Command::new("podman") + .args(["machine", "info", "--format", "{{.Host.VMType}}"]) + .output()?; + let vmtype = String::from_utf8_lossy(&output.stdout) + .trim() + .to_lowercase(); + if vmtype.is_empty() { + bail!("could not detect podman machine VM type"); + } + Ok(vmtype) +} + +/// Check if the podman machine is running as root (UID 0). +pub fn is_machine_rootful(machine: &str) -> bool { + Command::new("podman") + .args(["machine", "ssh", machine, "id", "-u"]) + .output() + .map(|o| String::from_utf8_lossy(&o.stdout).trim() == "0") + .unwrap_or(false) +} + +/// Parse memory specification string (e.g. "4G", "2048M") to megabytes. +pub fn parse_memory_to_mb(s: &str) -> Result { + let s = s.trim(); + if let Some(n) = s.strip_suffix('G').or_else(|| s.strip_suffix('g')) { + Ok((n.parse::()? * 1024.0) as u32) + } else if let Some(n) = s.strip_suffix('M').or_else(|| s.strip_suffix('m')) { + Ok(n.parse::()? as u32) + } else { + Ok(s.parse::()?) + } +} + +/// Return sensible default vCPU count based on available host parallelism. +pub fn default_vcpus() -> u32 { + std::thread::available_parallelism() + .map(|n| n.get() as u32) + .unwrap_or(2) +} + +/// Ensure image exists locally (pulling if needed) and return its short digest. +pub fn ensure_image_and_get_digest(image: &str) -> Result { + let status = Command::new("podman") + .args(["image", "exists", image]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status()?; + if !status.success() { + info!("pulling image {}...", image); + if !Command::new("podman") + .args(["pull", image]) + .status()? + .success() + { + bail!("failed to pull image: {}", image); + } + } + let output = Command::new("podman") + .args(["image", "inspect", "--format", "{{.Digest}}", image]) + .output()?; + let digest = String::from_utf8_lossy(&output.stdout).trim().to_string(); + if digest.is_empty() { + bail!("failed to get image digest: {}", image); + } + Ok(digest + .trim_start_matches("sha256:") + .chars() + .take(16) + .collect()) +} + +/// Wait for SSH to become available on the given port. +pub fn wait_for_ssh(port: u16, key_path: &Path, user: &str) -> Result<()> { + let ssh_opts = CommonSshOptions::default(); + let user_host = format!("{}@localhost", user); + info!("waiting for SSH on port {}...", port); + let start = std::time::Instant::now(); + let mut attempt = 0u32; + loop { + if start.elapsed() > SSH_TIMEOUT { + bail!("SSH connection timeout ({}s)", SSH_TIMEOUT.as_secs()); + } + let mut cmd = Command::new("ssh"); + cmd.args(["-p", &port.to_string(), "-i", &key_path.to_string_lossy()]); + ssh_opts.apply_to_command(&mut cmd); + cmd.args(["-o", "BatchMode=yes", &user_host, "true"]); + if let Ok(s) = cmd.stdout(Stdio::null()).stderr(Stdio::null()).status() { + if s.success() { + info!("SSH connected after {}s", start.elapsed().as_secs()); + return Ok(()); + } + } + let backoff = if attempt < 2 { + 500 + } else if attempt < 4 { + 1000 + } else { + 2000 + }; + std::thread::sleep(Duration::from_millis(backoff)); + attempt += 1; + } +} + +/// Execute a command via SSH and return the exit status. +pub fn run_ssh_command( + port: u16, + key_path: &Path, + user: &str, + command: &str, +) -> Result { + let ssh_opts = CommonSshOptions::default(); + let user_host = format!("{}@localhost", user); + let mut cmd = Command::new("ssh"); + cmd.args(["-p", &port.to_string(), "-i", &key_path.to_string_lossy()]); + ssh_opts.apply_to_command(&mut cmd); + cmd.args(["-o", "BatchMode=yes", &user_host, command]); + cmd.stdin(Stdio::inherit()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .status() + .map_err(|e| eyre!("ssh failed: {}", e)) +} + +/// Start an interactive SSH session with TTY allocation. +pub fn run_ssh_interactive( + port: u16, + key_path: &Path, + user: &str, +) -> Result { + let ssh_opts = CommonSshOptions::default(); + let user_host = format!("{}@localhost", user); + let mut cmd = Command::new("ssh"); + cmd.args(["-p", &port.to_string(), "-i", &key_path.to_string_lossy()]); + ssh_opts.apply_to_command(&mut cmd); + cmd.args(["-t", &user_host]); + cmd.stdin(Stdio::inherit()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .status() + .map_err(|e| eyre!("ssh failed: {}", e)) +} + +/// Remove a file, ignoring NotFound errors. +pub fn remove_file_if_exists(path: &Path) { + if let Err(e) = std::fs::remove_file(path) { + if e.kind() != std::io::ErrorKind::NotFound { + tracing::debug!("failed to remove {}: {}", path.display(), e); + } + } +} + +/// Generate an SSH keypair and return the public key content. +pub fn generate_ssh_keypair(key_path: &Path) -> Result { + let pub_path = key_path.with_extension( + key_path + .extension() + .map(|e| format!("{}.pub", e.to_string_lossy())) + .unwrap_or_else(|| "pub".to_string()), + ); + remove_file_if_exists(key_path); + remove_file_if_exists(&pub_path); + let status = Command::new("ssh-keygen") + .args([ + "-t", + "ed25519", + "-N", + "", + "-q", + "-f", + &key_path.to_string_lossy(), + ]) + .status()?; + if !status.success() { + bail!("ssh-keygen failed"); + } + let pubkey = std::fs::read_to_string(&pub_path)?.trim().to_string(); + Ok(pubkey) +} + +/// Sanitize a container image name into a valid VM name. +pub fn sanitize_vm_name(image: &str) -> String { + image + .split('/') + .last() + .unwrap_or(image) + .replace(':', "-") + .replace('.', "-") + .chars() + .filter(|c| c.is_alphanumeric() || *c == '-' || *c == '_') + .collect::() + .trim_matches('-') + .to_string() +} + +/// Parse a size string (e.g. "10G", "20GB", "5120M", "1TB") to bytes. +pub fn parse_size(size_str: &str) -> Result { + let s = size_str.trim(); + if s.is_empty() { + bail!("empty size string"); + } + if let Ok(n) = s.parse::() { + return Ok(n); + } + let upper = s.to_uppercase(); + let (num_str, multiplier) = if let Some(n) = upper.strip_suffix("TB") { + (n, 1024_u64.pow(4)) + } else if let Some(n) = upper.strip_suffix("GB") { + (n, 1024_u64 * 1024 * 1024) + } else if let Some(n) = upper.strip_suffix("MB") { + (n, 1024_u64 * 1024) + } else if let Some(n) = upper.strip_suffix("KB") { + (n, 1024_u64) + } else if let Some(n) = upper.strip_suffix('T') { + (n, 1024_u64.pow(4)) + } else if let Some(n) = upper.strip_suffix('G') { + (n, 1024_u64 * 1024 * 1024) + } else if let Some(n) = upper.strip_suffix('M') { + (n, 1024_u64 * 1024) + } else if let Some(n) = upper.strip_suffix('K') { + (n, 1024_u64) + } else if let Some(n) = upper.strip_suffix('B') { + (n, 1) + } else { + bail!("invalid size format: '{}' (use e.g. 20G, 5120M, 1TB)", s); + }; + let num: u64 = num_str + .trim() + .parse() + .with_context(|| format!("invalid number in size: '{}'", num_str))?; + Ok(num * multiplier) +} + +/// Container image name for the nbdkit EROFS plugin. +pub const NBDKIT_IMAGE: &str = "localhost/bcvk-nbdkit:latest"; + +/// Generate a shell script that checks for and builds the nbdkit container image. +/// +/// The caller provides the plugin `.so` binary via `plugin_so` (typically from +/// `include_bytes!` in a platform-specific module). The script: +/// 1. Checks if the image already exists (early exit if so) +/// 2. Writes the `.so` to a temp path via base64 +/// 3. Builds a container image with nbdkit + the plugin baked in +/// 4. Cleans up the temp file +pub fn nbdkit_setup_script(plugin_so: &[u8]) -> String { + use base64::Engine; + let b64 = base64::engine::general_purpose::STANDARD.encode(plugin_so); + format!( + "set -e; \ + if podman image exists {image}; then exit 0; fi; \ + mkdir -p /var/tmp/bcvk; \ + printf '%s' '{b64}' | base64 -d > /var/tmp/bcvk/plugin.so; \ + printf 'FROM quay.io/fedora/fedora:latest\\nRUN dnf install -y nbdkit nbdkit-basic-plugins && dnf clean all\\nCOPY plugin.so /plugin.so\\n' | \ + podman build -t {image} -f - /var/tmp/bcvk; \ + rm -f /var/tmp/bcvk/plugin.so", + image = NBDKIT_IMAGE, + b64 = b64, + ) +} +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_size() { + assert_eq!(parse_size("10G").unwrap(), 10 * 1024 * 1024 * 1024); + assert_eq!(parse_size("20GB").unwrap(), 20 * 1024 * 1024 * 1024); + assert_eq!(parse_size("5120M").unwrap(), 5120 * 1024 * 1024); + assert_eq!(parse_size("512MB").unwrap(), 512 * 1024 * 1024); + assert_eq!(parse_size("1024K").unwrap(), 1024 * 1024); + assert_eq!(parse_size("1TB").unwrap(), 1024_u64.pow(4)); + assert_eq!(parse_size("1073741824").unwrap(), 1073741824); + assert_eq!(parse_size("100B").unwrap(), 100); + assert!(parse_size("abc").is_err()); + assert!(parse_size("10X").is_err()); + assert!(parse_size("").is_err()); + } + + #[test] + fn test_parse_memory_to_mb() { + assert_eq!(parse_memory_to_mb("4G").unwrap(), 4096); + assert_eq!(parse_memory_to_mb("2048M").unwrap(), 2048); + assert_eq!(parse_memory_to_mb("512").unwrap(), 512); + assert_eq!(parse_memory_to_mb("1g").unwrap(), 1024); + assert_eq!(parse_memory_to_mb("256m").unwrap(), 256); + } + + #[test] + fn test_parse_memory_to_mb_errors() { + assert!(parse_memory_to_mb("abc").is_err()); + } + + #[test] + fn test_default_vcpus() { + let vcpus = default_vcpus(); + assert!(vcpus >= 1); + } + + #[test] + fn test_sanitize_vm_name() { + assert_eq!( + sanitize_vm_name("quay.io/fedora/fedora-bootc:latest"), + "fedora-bootc-latest" + ); + assert_eq!(sanitize_vm_name("centos:stream10"), "centos-stream10"); + assert_eq!(sanitize_vm_name("simple"), "simple"); + } +} diff --git a/crates/nbdkit-erofs-plugin/src/erofs.rs b/crates/nbdkit-erofs-plugin/src/erofs.rs index a795b076a..317edba0d 100644 --- a/crates/nbdkit-erofs-plugin/src/erofs.rs +++ b/crates/nbdkit-erofs-plugin/src/erofs.rs @@ -458,7 +458,10 @@ pub fn build_erofs_regions(layout: &ErofsLayout, walk: &WalkResult) -> Vec = Vec::new(); - // BOOTAA64.EFI + let boot_efi_name = if grub_path + .file_name() + .map(|n| n == "grubx64.efi") + .unwrap_or(false) + { + "BOOTX64" + } else { + "BOOTAA64" + }; files.push(FatFile { - name_8_3: make_8_3("BOOTAA64", "EFI"), + name_8_3: make_8_3(boot_efi_name, "EFI"), size: grub_size, regions: vec![FileDataRegion::FromFile { path: grub_path.to_path_buf(), @@ -352,7 +360,11 @@ pub fn build_esp_regions( regions.push(Region { start: offset, len: *len, - region_type: RegionType::File { path: path.clone() }, + region_type: RegionType::File { + file: std::sync::Arc::new( + std::fs::File::open(path).expect("failed to open file for region"), + ), + }, }); offset += len; file_offset += len; diff --git a/crates/nbdkit-erofs-plugin/src/gpt.rs b/crates/nbdkit-erofs-plugin/src/gpt.rs index 88e8bcf44..02becd16d 100644 --- a/crates/nbdkit-erofs-plugin/src/gpt.rs +++ b/crates/nbdkit-erofs-plugin/src/gpt.rs @@ -69,7 +69,7 @@ pub fn build_gpt_disk( &LINUX_TYPE_GUID, erofs_start_lba, erofs_start_lba + erofs_sectors - 1, - b"root", + b"bcvk-root", ); let partition_table_crc = crc32fast::hash(&partition_table); diff --git a/crates/nbdkit-erofs-plugin/src/lib.rs b/crates/nbdkit-erofs-plugin/src/lib.rs index b2cd4075c..5f78a274d 100644 --- a/crates/nbdkit-erofs-plugin/src/lib.rs +++ b/crates/nbdkit-erofs-plugin/src/lib.rs @@ -7,11 +7,11 @@ mod regions; use std::ffi::{c_char, c_int, c_void, CStr, CString}; use std::path::PathBuf; -use std::sync::Mutex; +use std::sync::RwLock; use regions::Region; -static PLUGIN_STATE: Mutex> = Mutex::new(None); +static PLUGIN_STATE: RwLock> = RwLock::new(None); struct PluginState { dir: PathBuf, @@ -39,7 +39,7 @@ pub extern "C" fn plugin_config(key: *const c_char, value: *const c_char) -> c_i let key = unsafe { CStr::from_ptr(key) }.to_str().unwrap_or(""); let value = unsafe { CStr::from_ptr(value) }.to_str().unwrap_or(""); - let mut state = PLUGIN_STATE.lock().unwrap(); + let mut state = PLUGIN_STATE.write().unwrap(); let state = state.get_or_insert_with(|| PluginState { dir: PathBuf::new(), cmdline: None, @@ -62,7 +62,7 @@ pub extern "C" fn plugin_config(key: *const c_char, value: *const c_char) -> c_i #[no_mangle] pub extern "C" fn plugin_config_complete() -> c_int { - let state = PLUGIN_STATE.lock().unwrap(); + let state = PLUGIN_STATE.read().unwrap(); let state = match state.as_ref() { Some(s) => s, None => { @@ -116,12 +116,12 @@ fn find_grub(dir: &std::path::Path) -> Option { } None } - walk(&dir.join("usr/lib"), "grubaa64.efi") + walk(&dir.join("usr/lib"), "grubaa64.efi").or_else(|| walk(&dir.join("usr/lib"), "grubx64.efi")) } #[no_mangle] pub extern "C" fn plugin_get_ready() -> c_int { - let mut state_guard = PLUGIN_STATE.lock().unwrap(); + let mut state_guard = PLUGIN_STATE.write().unwrap(); let state = match state_guard.as_mut() { Some(s) => s, None => return -1, @@ -144,7 +144,8 @@ pub extern "C" fn plugin_get_ready() -> c_int { } }; - let erofs_regions = erofs::build_erofs_regions(&erofs_layout, &walk); + let erofs_regions = + regions::consolidate_regions(erofs::build_erofs_regions(&erofs_layout, &walk)); // Discover boot files from dir let (kernel_path, initrd_path) = match find_kernel_dir(&state.dir) { @@ -240,7 +241,7 @@ pub extern "C" fn plugin_close(_handle: *mut c_void) {} #[no_mangle] pub extern "C" fn plugin_get_size(_handle: *mut c_void) -> i64 { - let state = PLUGIN_STATE.lock().unwrap(); + let state = PLUGIN_STATE.read().unwrap(); state.as_ref().map(|s| s.total_size as i64).unwrap_or(-1) } @@ -257,7 +258,7 @@ pub extern "C" fn plugin_pread( offset: u64, _flags: u32, ) -> c_int { - let state = PLUGIN_STATE.lock().unwrap(); + let state = PLUGIN_STATE.read().unwrap(); let state = match state.as_ref() { Some(s) => s, None => return -1, @@ -339,7 +340,7 @@ static PLUGIN_MAGIC_KEY: &[u8] = b"dir\0"; static PLUGIN: NbdkitPlugin = NbdkitPlugin { _struct_size: std::mem::size_of::() as u64, _api_version: 2, - _thread_model: 0, + _thread_model: 3, // NBDKIT_THREAD_MODEL_PARALLEL name: PLUGIN_NAME.as_ptr() as *const c_char, longname: PLUGIN_LONGNAME.as_ptr() as *const c_char, version: PLUGIN_VERSION.as_ptr() as *const c_char, diff --git a/crates/nbdkit-erofs-plugin/src/regions.rs b/crates/nbdkit-erofs-plugin/src/regions.rs index 16268d623..d79e8e228 100644 --- a/crates/nbdkit-erofs-plugin/src/regions.rs +++ b/crates/nbdkit-erofs-plugin/src/regions.rs @@ -1,13 +1,13 @@ //! Region-based virtual block device composition. //! Inspired by the regions pattern in nbdkit's floppy plugin (BSD-3-Clause). -use std::path::PathBuf; +use std::fs::File; use std::sync::Arc; #[derive(Debug, Clone)] pub enum RegionType { Data(Arc>), - File { path: PathBuf }, + File { file: Arc }, Zero, } @@ -39,6 +39,76 @@ pub fn find_region(regions: &[Region], offset: u64) -> Option<&Region> { .map(|i| ®ions[i]) } +const PRELOAD_THRESHOLD: u64 = 4096; +const MERGE_CHUNK_MAX: u64 = 4 * 1024 * 1024; + +pub fn consolidate_regions(regions: Vec) -> Vec { + use std::os::unix::fs::FileExt; + + let mut out: Vec = Vec::new(); + let mut merge_buf: Vec = Vec::new(); + let mut merge_start: u64 = 0; + + for r in regions { + let should_inline = match &r.region_type { + RegionType::File { file } => r.len <= PRELOAD_THRESHOLD, + RegionType::Data(_) | RegionType::Zero => true, + }; + + if should_inline { + if merge_buf.is_empty() { + merge_start = r.start; + } + let needed = (r.start + r.len - merge_start) as usize; + if needed as u64 > MERGE_CHUNK_MAX && !merge_buf.is_empty() { + out.push(Region { + start: merge_start, + len: merge_buf.len() as u64, + region_type: RegionType::Data(Arc::new(merge_buf.clone())), + }); + merge_buf.clear(); + merge_start = r.start; + } + let offset_in_buf = (r.start - merge_start) as usize; + if merge_buf.len() < offset_in_buf + r.len as usize { + merge_buf.resize(offset_in_buf + r.len as usize, 0); + } + match &r.region_type { + RegionType::Data(data) => { + merge_buf[offset_in_buf..offset_in_buf + r.len as usize] + .copy_from_slice(&data[..r.len as usize]); + } + RegionType::File { file } => { + let _ = file.read_exact_at( + &mut merge_buf[offset_in_buf..offset_in_buf + r.len as usize], + 0, + ); + } + RegionType::Zero => { + merge_buf[offset_in_buf..offset_in_buf + r.len as usize].fill(0); + } + } + } else { + if !merge_buf.is_empty() { + out.push(Region { + start: merge_start, + len: merge_buf.len() as u64, + region_type: RegionType::Data(Arc::new(std::mem::take(&mut merge_buf))), + }); + } + out.push(r); + } + } + if !merge_buf.is_empty() { + out.push(Region { + start: merge_start, + len: merge_buf.len() as u64, + region_type: RegionType::Data(Arc::new(merge_buf)), + }); + } + out +} + pub fn pread(regions: &[Region], buf: &mut [u8], offset: u64) -> std::io::Result<()> { let mut remaining = buf.len(); let mut buf_offset = 0; @@ -61,10 +131,9 @@ pub fn pread(regions: &[Region], buf: &mut [u8], offset: u64) -> std::io::Result let start = region_offset as usize; buf[buf_offset..buf_offset + len].copy_from_slice(&data[start..start + len]); } - RegionType::File { path } => { + RegionType::File { file } => { use std::os::unix::fs::FileExt; - let f = std::fs::File::open(path)?; - f.read_exact_at(&mut buf[buf_offset..buf_offset + len], region_offset)?; + file.read_exact_at(&mut buf[buf_offset..buf_offset + len], region_offset)?; } RegionType::Zero => { buf[buf_offset..buf_offset + len].fill(0); From 7477a1deeafdbbc1c29169f930044ce95b8393c3 Mon Sep 17 00:00:00 2001 From: Shion Tanaka Date: Fri, 5 Jun 2026 23:26:30 +0900 Subject: [PATCH 4/6] macOS: cargo-zigbuild, initramfs dedup, cap-std, per-arch plugin targets - Cross-build .so via cargo-zigbuild (make plugin-so-aarch64/x86_64) - Deduplicate initramfs units with include_bytes! from shared units/ - Use cap-std for directory walking in nbdkit-erofs-plugin - Add per-architecture plugin-so Makefile targets Assisted-by: Claude Code (Claude Opus 4.6) Signed-off-by: Shion Tanaka --- .github/workflows/main.yml | 10 +++ .gitignore | 1 + Cargo.lock | 2 + Makefile | 14 +++- crates/kit/src/nbdkit_macos.rs | 2 +- crates/nbdkit-erofs-plugin/Cargo.toml | 2 + crates/nbdkit-erofs-plugin/src/dir_walk.rs | 80 ++++++++++++--------- crates/nbdkit-erofs-plugin/src/initramfs.rs | 43 +---------- crates/nbdkit-erofs-plugin/src/lib.rs | 10 ++- 9 files changed, 86 insertions(+), 78 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3973d21b6..dc207b152 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -21,6 +21,16 @@ jobs: - name: Setup Rust uses: dtolnay/rust-toolchain@stable + with: + targets: aarch64-unknown-linux-gnu + + - name: Install zig and cargo-zigbuild + run: | + brew install zig + cargo install cargo-zigbuild + + - name: Build nbdkit EROFS plugin (.so) + run: make plugin-so-aarch64 - name: Check build run: cargo check --all-targets diff --git a/.gitignore b/.gitignore index 606639ec7..d517a322b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ target/ .flox +*.so diff --git a/Cargo.lock b/Cargo.lock index 20fdb4e67..1b931ea94 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1846,9 +1846,11 @@ dependencies = [ name = "nbdkit-erofs-plugin" version = "0.1.0" dependencies = [ + "cap-std", "cpio", "crc32fast", "libc", + "rustix", ] [[package]] diff --git a/Makefile b/Makefile index 8b93389c1..0ba44a877 100644 --- a/Makefile +++ b/Makefile @@ -67,4 +67,16 @@ update-manpages: update-generated: sync-manpages manpages -.PHONY: all bin install manpages update-generated makesudoinstall sync-manpages update-manpages sync-cli-options +.PHONY: all bin install manpages update-generated makesudoinstall sync-manpages update-manpages sync-cli-options plugin-so-aarch64 plugin-so-x86_64 + +.PHONY: plugin-so-aarch64 +plugin-so-aarch64: + cargo zigbuild --target aarch64-unknown-linux-gnu --release -p nbdkit-erofs-plugin + cp target/aarch64-unknown-linux-gnu/release/libnbdkit_erofs_plugin.so \ + crates/kit/nbdkit-erofs-plugin-aarch64.so + +.PHONY: plugin-so-x86_64 +plugin-so-x86_64: + cargo zigbuild --target x86_64-unknown-linux-gnu --release -p nbdkit-erofs-plugin + cp target/x86_64-unknown-linux-gnu/release/libnbdkit_erofs_plugin.so \ + crates/kit/nbdkit-erofs-plugin-x86_64.so diff --git a/crates/kit/src/nbdkit_macos.rs b/crates/kit/src/nbdkit_macos.rs index d8e13c91e..226d8c147 100644 --- a/crates/kit/src/nbdkit_macos.rs +++ b/crates/kit/src/nbdkit_macos.rs @@ -11,7 +11,7 @@ use tracing::info; use crate::vm_helpers::detect_machine_name; /// EROFS plugin shared library, embedded at compile time. -const EROFS_PLUGIN_SO: &[u8] = include_bytes!("../nbdkit-erofs-plugin.so"); +const EROFS_PLUGIN_SO: &[u8] = include_bytes!("../nbdkit-erofs-plugin-aarch64.so"); fn shell_escape(s: &str) -> String { format!("'{}'", s.replace('\'', "'\\''")) diff --git a/crates/nbdkit-erofs-plugin/Cargo.toml b/crates/nbdkit-erofs-plugin/Cargo.toml index 0f645c08c..8e8e38c72 100644 --- a/crates/nbdkit-erofs-plugin/Cargo.toml +++ b/crates/nbdkit-erofs-plugin/Cargo.toml @@ -11,3 +11,5 @@ crate-type = ["cdylib"] libc = "0.2" cpio = "0.4" crc32fast = "1.4" +cap-std = "4" +rustix = { version = "1", features = ["fs"] } diff --git a/crates/nbdkit-erofs-plugin/src/dir_walk.rs b/crates/nbdkit-erofs-plugin/src/dir_walk.rs index 674556d4b..de17b99bb 100644 --- a/crates/nbdkit-erofs-plugin/src/dir_walk.rs +++ b/crates/nbdkit-erofs-plugin/src/dir_walk.rs @@ -1,8 +1,9 @@ use std::ffi::OsString; -use std::fs; -use std::os::unix::fs::MetadataExt; +use std::os::unix::io::AsFd; use std::path::{Path, PathBuf}; +use cap_std::fs::Dir; + #[derive(Debug)] pub struct FileEntry { pub host_path: PathBuf, @@ -53,7 +54,7 @@ pub struct WalkResult { pub symlinks: Vec, } -pub fn walk_directory(root: &Path) -> std::io::Result { +pub fn walk_directory(root_dir: &Dir, root_path: &Path) -> std::io::Result { let mut result = WalkResult { dirs: Vec::new(), files: Vec::new(), @@ -61,73 +62,82 @@ pub fn walk_directory(root: &Path) -> std::io::Result { }; let mut next_inode: u64 = 0; - walk_recursive(root, root, &mut result, &mut next_inode, 0)?; + walk_recursive(root_dir, root_path, &mut result, &mut next_inode, 0)?; Ok(result) } +fn statat(dir: &Dir, name: &Path) -> std::io::Result { + rustix::fs::statat(dir.as_fd(), name, rustix::fs::AtFlags::SYMLINK_NOFOLLOW) + .map_err(std::io::Error::from) +} + fn walk_recursive( - root: &Path, - dir: &Path, + cap_dir: &Dir, + current_path: &Path, result: &mut WalkResult, next_inode: &mut u64, parent_inode_id: u64, ) -> std::io::Result { - let meta = fs::symlink_metadata(dir)?; + let dir_stat = rustix::fs::fstat(cap_dir.as_fd()).map_err(std::io::Error::from)?; let dir_inode = *next_inode; *next_inode += 1; let di = result.dirs.len(); result.dirs.push(DirInfo { - name: dir.file_name().unwrap_or_default().to_os_string(), - mode: meta.mode(), - uid: meta.uid(), - gid: meta.gid(), - mtime: meta.mtime() as u64, + name: current_path.file_name().unwrap_or_default().to_os_string(), + mode: dir_stat.st_mode, + uid: dir_stat.st_uid, + gid: dir_stat.st_gid, + mtime: dir_stat.st_mtime as u64, inode_id: dir_inode, parent_inode_id, children: Vec::new(), }); - let mut entries: Vec<_> = fs::read_dir(dir)?.filter_map(|e| e.ok()).collect(); + let mut entries: Vec<_> = cap_dir.entries()?.filter_map(|e| e.ok()).collect(); entries.sort_by_key(|e| e.file_name()); for entry in entries { - let path = entry.path(); - let meta = fs::symlink_metadata(&path)?; - let ft = meta.file_type(); + let name = entry.file_name(); + let name_path: &Path = name.as_ref(); + let stat = statat(cap_dir, name_path)?; + let mode = stat.st_mode; + let ft = rustix::fs::FileType::from_raw_mode(mode); - if ft.is_dir() { - let child_di = walk_recursive(root, &path, result, next_inode, dir_inode)?; + if ft == rustix::fs::FileType::Directory { + let child_dir = cap_dir.open_dir(&name)?; + let child_path = current_path.join(&name); + let child_di = walk_recursive(&child_dir, &child_path, result, next_inode, dir_inode)?; result.dirs[di].children.push(ChildRef::Dir(child_di)); - } else if ft.is_symlink() { - let target = fs::read_link(&path)?; - let target_bytes = target.as_os_str().as_encoded_bytes().to_vec(); - let name = entry.file_name().as_encoded_bytes().to_vec(); + } else if ft == rustix::fs::FileType::Symlink { + let target = rustix::fs::readlinkat(cap_dir.as_fd(), name_path, [])?; + let target_bytes = target.into_bytes(); + let name_bytes = name.as_encoded_bytes().to_vec(); let si = result.symlinks.len(); let inode = *next_inode; *next_inode += 1; result.symlinks.push(SymlinkEntry { - name, + name: name_bytes, target: target_bytes, - mode: meta.mode(), - uid: meta.uid(), - gid: meta.gid(), - mtime: meta.mtime() as u64, + mode, + uid: stat.st_uid, + gid: stat.st_gid, + mtime: stat.st_mtime as u64, inode_id: inode, }); result.dirs[di].children.push(ChildRef::Symlink(si)); - } else if ft.is_file() { + } else if ft == rustix::fs::FileType::RegularFile { let fi = result.files.len(); let inode = *next_inode; *next_inode += 1; result.files.push(FileEntry { - host_path: path, - size: meta.len(), - mode: meta.mode(), - uid: meta.uid(), - gid: meta.gid(), - mtime: meta.mtime() as u64, - nlink: meta.nlink() as u32, + host_path: current_path.join(&name), + size: stat.st_size as u64, + mode, + uid: stat.st_uid, + gid: stat.st_gid, + mtime: stat.st_mtime as u64, + nlink: stat.st_nlink as u32, inode_id: inode, }); result.dirs[di].children.push(ChildRef::File(fi)); diff --git a/crates/nbdkit-erofs-plugin/src/initramfs.rs b/crates/nbdkit-erofs-plugin/src/initramfs.rs index 87d0d7732..abe241ab6 100644 --- a/crates/nbdkit-erofs-plugin/src/initramfs.rs +++ b/crates/nbdkit-erofs-plugin/src/initramfs.rs @@ -44,56 +44,19 @@ pub fn build_units_cpio() -> Vec { write_file( &mut out, "usr/lib/systemd/system/bcvk-var-ephemeral.service", - b"[Unit]\n\ - Description=Setup ephemeral /var from image content\n\ - DefaultDependencies=no\n\ - ConditionPathExists=/etc/initrd-release\n\ - Before=initrd-fs.target\n\ - After=sysroot.mount initrd-parse-etc.service\n\ - Requires=sysroot.mount\n\ - \n\ - [Service]\n\ - Type=oneshot\n\ - RemainAfterExit=yes\n\ - TimeoutStartSec=60\n\ - ExecStart=/usr/bin/mkdir -p /run/var-ephemeral\n\ - ExecStart=/usr/bin/cp -a /sysroot/var/. /run/var-ephemeral/\n\ - ExecStart=/usr/bin/mount --bind /run/var-ephemeral /sysroot/var\n", + include_bytes!("../../kit/src/units/bcvk-var-ephemeral.service"), ); write_file( &mut out, "usr/lib/systemd/system/bcvk-etc-overlay.service", - b"[Unit]\n\ - Description=Setup ephemeral /etc overlay\n\ - DefaultDependencies=no\n\ - ConditionPathExists=/etc/initrd-release\n\ - Before=initrd-fs.target\n\ - After=sysroot.mount initrd-parse-etc.service\n\ - Requires=sysroot.mount\n\ - \n\ - [Service]\n\ - Type=oneshot\n\ - RemainAfterExit=yes\n\ - TimeoutStartSec=30\n\ - ExecStart=/usr/bin/mkdir -p /run/etc-lower /run/etc-upper /run/etc-work\n\ - ExecStart=/usr/bin/mount --bind /sysroot/etc /run/etc-lower\n\ - ExecStart=/usr/bin/mount -t overlay overlay -o lowerdir=/run/etc-lower,upperdir=/run/etc-upper,workdir=/run/etc-work,index=off,metacopy=off /sysroot/etc\n", + include_bytes!("../../kit/src/units/bcvk-etc-overlay.service"), ); write_file( &mut out, "usr/lib/systemd/system/bcvk-copy-units.service", - b"[Unit]\n\ - Description=Copy bcvk units for post-switch-root on systemd <256\n\ - DefaultDependencies=no\n\ - ConditionPathExists=/etc/initrd-release\n\ - Before=initrd-fs.target\n\ - \n\ - [Service]\n\ - Type=oneshot\n\ - RemainAfterExit=yes\n\ - ExecStart=/bin/sh -c 'mkdir -p /run/systemd/system/sysinit.target.wants && cp /usr/lib/systemd/system/bcvk-journal-stream.service /run/systemd/system/ && ln -s ../bcvk-journal-stream.service /run/systemd/system/sysinit.target.wants/'\n", + include_bytes!("../../kit/src/units/bcvk-copy-units.service"), ); write_file( diff --git a/crates/nbdkit-erofs-plugin/src/lib.rs b/crates/nbdkit-erofs-plugin/src/lib.rs index 5f78a274d..7c58c40b7 100644 --- a/crates/nbdkit-erofs-plugin/src/lib.rs +++ b/crates/nbdkit-erofs-plugin/src/lib.rs @@ -128,7 +128,15 @@ pub extern "C" fn plugin_get_ready() -> c_int { }; // Walk directory for EROFS - let walk = match dir_walk::walk_directory(&state.dir) { + let root_dir = + match cap_std::fs::Dir::open_ambient_dir(&state.dir, cap_std::ambient_authority()) { + Ok(d) => d, + Err(e) => { + log_error(&format!("failed to open directory {:?}: {}", state.dir, e)); + return -1; + } + }; + let walk = match dir_walk::walk_directory(&root_dir, &state.dir) { Ok(w) => w, Err(e) => { log_error(&format!("failed to walk directory: {}", e)); From 30058b4b312b57529e4381d1a5e6fdc0ecc6de0d Mon Sep 17 00:00:00 2001 From: Shion Tanaka Date: Tue, 9 Jun 2026 23:22:20 +0900 Subject: [PATCH 5/6] macOS: bcvk-nbd replacement, CLI unification, SSH backoff optimization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Squashed from wip/macos-vfkit (125 commits): - feat: replace nbdkit container with bcvk-nbd standalone binary - feat: eliminate container, use systemd-run + gvproxy expose - refactor: extract vm_helpers.rs, align CLI with Linux/Windows - fix: fd leak, orphan sweep, gvproxy expose retry, dynamic NBD port - perf: reduce SSH backoff (500/1000/2000 → 100/200/500ms) Assisted-by: Claude Code (Claude Opus 4.6) Signed-off-by: Shion Tanaka --- .github/workflows/main.yml | 3 + Cargo.lock | 11 + Makefile | 14 +- crates/bcvk-nbd/Cargo.toml | 16 + crates/bcvk-nbd/src/dir_walk.rs | 149 +++++++ crates/bcvk-nbd/src/erofs.rs | 502 +++++++++++++++++++++++ crates/bcvk-nbd/src/fat32.rs | 556 ++++++++++++++++++++++++++ crates/bcvk-nbd/src/gpt.rs | 290 ++++++++++++++ crates/bcvk-nbd/src/initramfs.rs | 145 +++++++ crates/bcvk-nbd/src/main.rs | 248 ++++++++++++ crates/bcvk-nbd/src/nbd.rs | 359 +++++++++++++++++ crates/bcvk-nbd/src/regions.rs | 151 +++++++ crates/kit/bcvk-nbd-aarch64 | Bin 0 -> 518296 bytes crates/kit/src/ephemeral_macos.rs | 27 +- crates/kit/src/lib.rs | 2 +- crates/kit/src/main.rs | 2 +- crates/kit/src/nbd_macos.rs | 148 +++++++ crates/kit/src/nbdkit_macos.rs | 230 ----------- crates/kit/src/run_ephemeral_macos.rs | 51 ++- crates/kit/src/vfkit/inspect.rs | 5 +- crates/kit/src/vfkit/list.rs | 4 +- crates/kit/src/vfkit/mod.rs | 6 +- crates/kit/src/vfkit/run.rs | 10 +- crates/kit/src/vfkit/ssh.rs | 7 +- crates/kit/src/vfkit/start.rs | 6 +- crates/kit/src/vm_helpers.rs | 196 +++++++-- 26 files changed, 2829 insertions(+), 309 deletions(-) create mode 100644 crates/bcvk-nbd/Cargo.toml create mode 100644 crates/bcvk-nbd/src/dir_walk.rs create mode 100644 crates/bcvk-nbd/src/erofs.rs create mode 100644 crates/bcvk-nbd/src/fat32.rs create mode 100644 crates/bcvk-nbd/src/gpt.rs create mode 100644 crates/bcvk-nbd/src/initramfs.rs create mode 100644 crates/bcvk-nbd/src/main.rs create mode 100644 crates/bcvk-nbd/src/nbd.rs create mode 100644 crates/bcvk-nbd/src/regions.rs create mode 100644 crates/kit/bcvk-nbd-aarch64 create mode 100644 crates/kit/src/nbd_macos.rs delete mode 100644 crates/kit/src/nbdkit_macos.rs diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index dc207b152..0a4b27de3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -32,6 +32,9 @@ jobs: - name: Build nbdkit EROFS plugin (.so) run: make plugin-so-aarch64 + - name: Build NBD server binary + run: make nbd-server-aarch64 + - name: Check build run: cargo check --all-targets diff --git a/Cargo.lock b/Cargo.lock index 1b931ea94..966c44d5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -271,6 +271,17 @@ dependencies = [ "zstd", ] +[[package]] +name = "bcvk-nbd" +version = "0.1.0" +dependencies = [ + "cap-std", + "cpio", + "crc32fast", + "libc", + "rustix", +] + [[package]] name = "bcvk-qemu" version = "0.1.0" diff --git a/Makefile b/Makefile index 0ba44a877..9950a9c0a 100644 --- a/Makefile +++ b/Makefile @@ -67,7 +67,7 @@ update-manpages: update-generated: sync-manpages manpages -.PHONY: all bin install manpages update-generated makesudoinstall sync-manpages update-manpages sync-cli-options plugin-so-aarch64 plugin-so-x86_64 +.PHONY: all bin install manpages update-generated makesudoinstall sync-manpages update-manpages sync-cli-options plugin-so-aarch64 plugin-so-x86_64 nbd-server-aarch64 nbd-server-x86_64 .PHONY: plugin-so-aarch64 plugin-so-aarch64: @@ -80,3 +80,15 @@ plugin-so-x86_64: cargo zigbuild --target x86_64-unknown-linux-gnu --release -p nbdkit-erofs-plugin cp target/x86_64-unknown-linux-gnu/release/libnbdkit_erofs_plugin.so \ crates/kit/nbdkit-erofs-plugin-x86_64.so + +.PHONY: nbd-server-aarch64 +nbd-server-aarch64: + cargo zigbuild --target aarch64-unknown-linux-gnu --release -p bcvk-nbd + cp target/aarch64-unknown-linux-gnu/release/bcvk-nbd \ + crates/kit/bcvk-nbd-aarch64 + +.PHONY: nbd-server-x86_64 +nbd-server-x86_64: + cargo zigbuild --target x86_64-unknown-linux-gnu --release -p bcvk-nbd + cp target/x86_64-unknown-linux-gnu/release/bcvk-nbd \ + crates/kit/bcvk-nbd-x86_64 diff --git a/crates/bcvk-nbd/Cargo.toml b/crates/bcvk-nbd/Cargo.toml new file mode 100644 index 000000000..feb9d5cdc --- /dev/null +++ b/crates/bcvk-nbd/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "bcvk-nbd" +version = "0.1.0" +edition = "2021" +publish = false + +[[bin]] +name = "bcvk-nbd" +path = "src/main.rs" + +[dependencies] +cpio = "0.4" +crc32fast = "1.4" +cap-std = "4" +rustix = { version = "1", features = ["fs"] } +libc = "0.2" diff --git a/crates/bcvk-nbd/src/dir_walk.rs b/crates/bcvk-nbd/src/dir_walk.rs new file mode 100644 index 000000000..1e4fc527b --- /dev/null +++ b/crates/bcvk-nbd/src/dir_walk.rs @@ -0,0 +1,149 @@ +use std::ffi::OsString; +use std::os::unix::io::AsFd; +use std::path::{Path, PathBuf}; + +use cap_std::fs::Dir; + +#[derive(Debug)] +pub struct FileEntry { + pub host_path: PathBuf, + pub size: u64, + pub mode: u32, + pub uid: u32, + pub gid: u32, + pub mtime: u64, + pub nlink: u32, + pub inode_id: u64, +} + +#[derive(Debug)] +pub struct DirInfo { + pub name: OsString, + pub mode: u32, + pub uid: u32, + pub gid: u32, + pub mtime: u64, + pub inode_id: u64, + pub parent_inode_id: u64, + pub children: Vec, +} + +#[derive(Debug)] +pub struct SymlinkEntry { + pub name: Vec, + pub target: Vec, + pub mode: u32, + pub uid: u32, + pub gid: u32, + pub mtime: u64, + pub inode_id: u64, +} + +/// Child entry in a directory: either a file index, dir index, or symlink index +#[derive(Debug, Clone, Copy)] +pub enum ChildRef { + File(usize), + Dir(usize), + Symlink(usize), +} + +#[derive(Debug)] +pub struct WalkResult { + pub dirs: Vec, + pub files: Vec, + pub symlinks: Vec, +} + +pub fn walk_directory(root_dir: &Dir, root_path: &Path) -> std::io::Result { + let mut result = WalkResult { + dirs: Vec::new(), + files: Vec::new(), + symlinks: Vec::new(), + }; + let mut next_inode: u64 = 0; + + walk_recursive(root_dir, root_path, &mut result, &mut next_inode, 0)?; + Ok(result) +} + +fn statat(dir: &Dir, name: &Path) -> std::io::Result { + rustix::fs::statat(dir.as_fd(), name, rustix::fs::AtFlags::SYMLINK_NOFOLLOW) + .map_err(std::io::Error::from) +} + +fn walk_recursive( + cap_dir: &Dir, + current_path: &Path, + result: &mut WalkResult, + next_inode: &mut u64, + parent_inode_id: u64, +) -> std::io::Result { + let dir_stat = rustix::fs::fstat(cap_dir.as_fd()).map_err(std::io::Error::from)?; + let dir_inode = *next_inode; + *next_inode += 1; + + let di = result.dirs.len(); + result.dirs.push(DirInfo { + name: current_path.file_name().unwrap_or_default().to_os_string(), + mode: dir_stat.st_mode as u32, + uid: dir_stat.st_uid, + gid: dir_stat.st_gid, + mtime: dir_stat.st_mtime as u64, + inode_id: dir_inode, + parent_inode_id, + children: Vec::new(), + }); + + let mut entries: Vec<_> = cap_dir.entries()?.filter_map(|e| e.ok()).collect(); + entries.sort_by_key(|e| e.file_name()); + + for entry in entries { + let name = entry.file_name(); + let name_path: &Path = name.as_ref(); + let stat = statat(cap_dir, name_path)?; + let raw_mode = stat.st_mode; + let mode = raw_mode as u32; + let ft = rustix::fs::FileType::from_raw_mode(raw_mode); + + if ft == rustix::fs::FileType::Directory { + let child_dir = cap_dir.open_dir(&name)?; + let child_path = current_path.join(&name); + let child_di = walk_recursive(&child_dir, &child_path, result, next_inode, dir_inode)?; + result.dirs[di].children.push(ChildRef::Dir(child_di)); + } else if ft == rustix::fs::FileType::Symlink { + let target = rustix::fs::readlinkat(cap_dir.as_fd(), name_path, [])?; + let target_bytes = target.into_bytes(); + let name_bytes = name.as_encoded_bytes().to_vec(); + let si = result.symlinks.len(); + let inode = *next_inode; + *next_inode += 1; + result.symlinks.push(SymlinkEntry { + name: name_bytes, + target: target_bytes, + mode, + uid: stat.st_uid, + gid: stat.st_gid, + mtime: stat.st_mtime as u64, + inode_id: inode, + }); + result.dirs[di].children.push(ChildRef::Symlink(si)); + } else if ft == rustix::fs::FileType::RegularFile { + let fi = result.files.len(); + let inode = *next_inode; + *next_inode += 1; + result.files.push(FileEntry { + host_path: current_path.join(&name), + size: stat.st_size as u64, + mode, + uid: stat.st_uid, + gid: stat.st_gid, + mtime: stat.st_mtime as u64, + nlink: stat.st_nlink as u32, + inode_id: inode, + }); + result.dirs[di].children.push(ChildRef::File(fi)); + } + } + + Ok(di) +} diff --git a/crates/bcvk-nbd/src/erofs.rs b/crates/bcvk-nbd/src/erofs.rs new file mode 100644 index 000000000..a77edf2b0 --- /dev/null +++ b/crates/bcvk-nbd/src/erofs.rs @@ -0,0 +1,502 @@ +use crate::dir_walk::{ChildRef, DirInfo, WalkResult}; +use crate::regions::{Region, RegionType}; +use std::sync::Arc; + +const EROFS_MAGIC: u32 = 0xE0F5E1E2; +const BLOCK_SIZE: u64 = 4096; +const BLOCK_BITS: u8 = 12; +const SUPERBLOCK_OFFSET: u64 = 1024; + +// EROFS inode formats +const EROFS_INODE_LAYOUT_COMPACT: u16 = 0; + +// EROFS data layouts +const EROFS_INODE_FLAT_PLAIN: u16 = 0; + +// EROFS file types (matching Linux DT_* values) +const EROFS_FT_REG_FILE: u8 = 1; +const EROFS_FT_DIR: u8 = 2; +const EROFS_FT_SYMLINK: u8 = 7; + +#[derive(Debug)] +pub struct FileRegion { + pub file_index: usize, + pub offset_in_erofs: u64, + pub size: u64, +} + +#[derive(Debug)] +pub struct ErofsLayout { + pub metadata: Vec, + pub file_regions: Vec, + pub total_size: u64, +} + +struct DirEntryOnDisk { + nid: u64, + file_type: u8, + name: Vec, +} + +pub fn build_erofs(walk: &WalkResult) -> std::io::Result { + let total_inodes = walk.dirs.len() + walk.files.len() + walk.symlinks.len(); + + // Phase 1: Assign inode positions + // Inodes start at block 1 (block 0 has superblock) + let inode_table_offset = BLOCK_SIZE; // block 1 + let inode_size: u64 = 32; // compact inode + let inode_table_size = align_up(total_inodes as u64 * inode_size, BLOCK_SIZE); + + // Phase 2: Build directory entry blocks + let dir_blocks_offset = inode_table_offset + inode_table_size; + let mut dir_data: Vec = Vec::new(); + let mut dir_block_offsets: Vec = Vec::new(); // per-directory offset in dir_data + + for dir in &walk.dirs { + let offset = align_up(dir_data.len() as u64, BLOCK_SIZE); + dir_data.resize(offset as usize, 0); + dir_block_offsets.push(dir_blocks_offset + offset); + + let mut entries = Vec::new(); + + // "." entry + entries.push(DirEntryOnDisk { + nid: dir.inode_id, + file_type: EROFS_FT_DIR, + name: b".".to_vec(), + }); + + // ".." entry (root points to self) + let parent_nid = dir.parent_inode_id; + entries.push(DirEntryOnDisk { + nid: parent_nid, + file_type: EROFS_FT_DIR, + name: b"..".to_vec(), + }); + + // children (sorted by name in walk) + for child in &dir.children { + match child { + ChildRef::Dir(di) => { + let child_dir = &walk.dirs[*di]; + entries.push(DirEntryOnDisk { + nid: child_dir.inode_id, + file_type: EROFS_FT_DIR, + name: child_dir.name.as_encoded_bytes().to_vec(), + }); + } + ChildRef::File(fi) => { + let file = &walk.files[*fi]; + entries.push(DirEntryOnDisk { + nid: file.inode_id, + file_type: EROFS_FT_REG_FILE, + name: file + .host_path + .file_name() + .unwrap_or_default() + .as_encoded_bytes() + .to_vec(), + }); + } + ChildRef::Symlink(si) => { + let symlink = &walk.symlinks[*si]; + entries.push(DirEntryOnDisk { + nid: symlink.inode_id, + file_type: EROFS_FT_SYMLINK, + name: symlink.name.clone(), + }); + } + } + } + + // Write EROFS directory blocks (splits at 4096-byte boundaries) + write_dir_blocks(&mut dir_data, &entries); + } + let dir_data_size = align_up(dir_data.len() as u64, BLOCK_SIZE); + dir_data.resize(dir_data_size as usize, 0); + + // Phase 3: Compute data region layout + let data_offset = dir_blocks_offset + dir_data_size; + let mut file_regions = Vec::new(); + let mut current_data_offset = data_offset; + + for (i, file) in walk.files.iter().enumerate() { + if file.size > 0 { + let aligned_offset = align_up(current_data_offset, BLOCK_SIZE); + file_regions.push(FileRegion { + file_index: i, + offset_in_erofs: aligned_offset, + size: file.size, + }); + current_data_offset = aligned_offset + align_up(file.size, BLOCK_SIZE); + } + } + + // Symlink targets also need data blocks + for (si, symlink) in walk.symlinks.iter().enumerate() { + if !symlink.target.is_empty() { + let aligned_offset = align_up(current_data_offset, BLOCK_SIZE); + file_regions.push(FileRegion { + file_index: walk.files.len() + si, // files.len() + symlink index + offset_in_erofs: aligned_offset, + size: symlink.target.len() as u64, + }); + current_data_offset = + aligned_offset + align_up(symlink.target.len() as u64, BLOCK_SIZE); + } + } + + let total_size = align_up(current_data_offset, BLOCK_SIZE); + let total_blocks = total_size / BLOCK_SIZE; + + // Phase 4: Build metadata blob + let mut metadata = vec![0u8; (dir_blocks_offset + dir_data_size) as usize]; + + // Write superblock at offset 1024 + write_superblock( + &mut metadata, + total_inodes as u32, + total_blocks as u32, + 0, // root nid + ); + + // Write inodes + // Directories + for (i, dir) in walk.dirs.iter().enumerate() { + let dir_size = compute_dir_size(dir, walk); + let dir_block = (dir_block_offsets[i] - dir_blocks_offset) / BLOCK_SIZE; + write_compact_inode( + &mut metadata, + inode_table_offset as usize + (dir.inode_id as usize * 32), + 0o040000 | (dir.mode & 0o7777), + dir.uid as u16, + dir.gid as u16, + dir_size as u32, + dir.mtime as u32, + 2 + dir + .children + .iter() + .filter(|c| matches!(c, ChildRef::Dir(_))) + .count() as u16, + EROFS_INODE_FLAT_PLAIN, + (dir_blocks_offset / BLOCK_SIZE + dir_block) as u32, + ); + } + + // Regular files + for (i, file) in walk.files.iter().enumerate() { + let data_block = if file.size > 0 { + let fr = file_regions.iter().find(|r| r.file_index == i); + fr.map(|r| (r.offset_in_erofs / BLOCK_SIZE) as u32) + .unwrap_or(0) + } else { + 0 + }; + write_compact_inode( + &mut metadata, + inode_table_offset as usize + (file.inode_id as usize * 32), + 0o100000 | (file.mode & 0o7777), + file.uid as u16, + file.gid as u16, + file.size as u32, + file.mtime as u32, + file.nlink as u16, + EROFS_INODE_FLAT_PLAIN, + data_block, + ); + } + + // Symlinks: FlatPlain with target in data region + // File regions for symlinks start after file regions + let file_region_count = walk.files.iter().filter(|f| f.size > 0).count(); + let mut sym_fr_idx = file_region_count; + for symlink in &walk.symlinks { + let data_block = if !symlink.target.is_empty() { + let fr = &file_regions[sym_fr_idx]; + sym_fr_idx += 1; + (fr.offset_in_erofs / BLOCK_SIZE) as u32 + } else { + 0 + }; + + write_compact_inode( + &mut metadata, + inode_table_offset as usize + (symlink.inode_id as usize * 32), + 0o120000 | (symlink.mode & 0o7777), + symlink.uid as u16, + symlink.gid as u16, + symlink.target.len() as u32, + symlink.mtime as u32, + 1, + EROFS_INODE_FLAT_PLAIN, + data_block, + ); + } + + // Write directory data + let dir_start = dir_blocks_offset as usize; + if dir_start + dir_data.len() <= metadata.len() { + metadata[dir_start..dir_start + dir_data.len()].copy_from_slice(&dir_data); + } + + Ok(ErofsLayout { + metadata, + file_regions, + total_size, + }) +} + +fn write_superblock(buf: &mut [u8], inodes: u32, blocks: u32, root_nid: u16) { + let off = SUPERBLOCK_OFFSET as usize; + // magic + buf[off..off + 4].copy_from_slice(&EROFS_MAGIC.to_le_bytes()); + // checksum (unused) + // feature_compat + buf[off + 8..off + 12].copy_from_slice(&0u32.to_le_bytes()); + // blkszbits + buf[off + 12] = BLOCK_BITS; + // sb_extslots + buf[off + 13] = 0; + // root_nid + buf[off + 14..off + 16].copy_from_slice(&root_nid.to_le_bytes()); + // inos + buf[off + 16..off + 24].copy_from_slice(&(inodes as u64).to_le_bytes()); + // build_time + buf[off + 24..off + 32].copy_from_slice(&0u64.to_le_bytes()); + // build_time_nsec + buf[off + 32..off + 36].copy_from_slice(&0u32.to_le_bytes()); + // blocks + buf[off + 36..off + 40].copy_from_slice(&blocks.to_le_bytes()); + // meta_blkaddr (inode table starts at block 1) + buf[off + 40..off + 44].copy_from_slice(&1u32.to_le_bytes()); + // xattr_blkaddr + buf[off + 44..off + 48].copy_from_slice(&0u32.to_le_bytes()); + // uuid (16 bytes) + // volume_name (16 bytes) + // feature_incompat + buf[off + 80..off + 84].copy_from_slice(&0u32.to_le_bytes()); + // available_compr_algs (union with checksum) + // lz4_max_distance +} + +fn write_compact_inode( + buf: &mut [u8], + offset: usize, + mode: u32, + uid: u16, + gid: u16, + size: u32, + _mtime: u32, + nlink: u16, + data_layout: u16, + u_field: u32, +) { + if offset + 32 > buf.len() { + return; + } + + // format: layout(compact=0) | data_layout << 1 + let format = (EROFS_INODE_LAYOUT_COMPACT) | (data_layout << 1); + buf[offset..offset + 2].copy_from_slice(&format.to_le_bytes()); + // xattr_icount + buf[offset + 2..offset + 4].copy_from_slice(&0u16.to_le_bytes()); + // mode + buf[offset + 4..offset + 6].copy_from_slice(&(mode as u16).to_le_bytes()); + // nlink + buf[offset + 6..offset + 8].copy_from_slice(&nlink.to_le_bytes()); + // size + buf[offset + 8..offset + 12].copy_from_slice(&size.to_le_bytes()); + // reserved + buf[offset + 12..offset + 16].copy_from_slice(&0u32.to_le_bytes()); + // u (union: raw_blkaddr for FlatPlain) + buf[offset + 16..offset + 20].copy_from_slice(&u_field.to_le_bytes()); + // ino (on-disk inode number, optional) + buf[offset + 20..offset + 24].copy_from_slice(&0u32.to_le_bytes()); + // uid + buf[offset + 24..offset + 26].copy_from_slice(&uid.to_le_bytes()); + // gid + buf[offset + 26..offset + 28].copy_from_slice(&gid.to_le_bytes()); + // reserved2 + buf[offset + 28..offset + 32].copy_from_slice(&0u32.to_le_bytes()); +} + +fn write_dir_blocks(buf: &mut Vec, entries: &[DirEntryOnDisk]) { + // EROFS directories are split into 4096-byte blocks. + // Each block contains: [headers...][names...] + // header = 12 bytes: nid(8) + nameoff(2) + file_type(1) + reserved(1) + // nameoff is relative to block start. + + let mut remaining = entries; + + while !remaining.is_empty() { + // Determine how many entries fit in this block + let mut count = 0; + let mut total_size: usize = 0; + for entry in remaining { + let entry_size = 12 + entry.name.len(); + if total_size + entry_size > BLOCK_SIZE as usize && count > 0 { + break; + } + total_size += entry_size; + count += 1; + } + + let block_entries = &remaining[..count]; + remaining = &remaining[count..]; + + // Write headers + let header_total = 12 * block_entries.len(); + let mut nameoff = header_total as u16; + for entry in block_entries { + buf.extend_from_slice(&(entry.nid as u64).to_le_bytes()); + buf.extend_from_slice(&nameoff.to_le_bytes()); + buf.push(entry.file_type); + buf.push(0); + nameoff += entry.name.len() as u16; + } + + // Write names + for entry in block_entries { + buf.extend_from_slice(&entry.name); + } + + // Pad to block boundary (except last block which is sized by inode.size) + if !remaining.is_empty() { + let written = total_size; + let pad = BLOCK_SIZE as usize - (written % BLOCK_SIZE as usize); + if pad < BLOCK_SIZE as usize { + buf.resize(buf.len() + pad, 0); + } + } + } +} + +fn compute_dir_size(dir: &DirInfo, walk: &WalkResult) -> u64 { + // Build entry list to accurately compute size including block splits + let mut entries = Vec::new(); + entries.push(DirEntryOnDisk { + nid: 0, + file_type: EROFS_FT_DIR, + name: b".".to_vec(), + }); + entries.push(DirEntryOnDisk { + nid: 0, + file_type: EROFS_FT_DIR, + name: b"..".to_vec(), + }); + for child in &dir.children { + let name_len = match child { + ChildRef::Dir(di) => walk.dirs[*di].name.len(), + ChildRef::File(fi) => walk.files[*fi] + .host_path + .file_name() + .unwrap_or_default() + .len(), + ChildRef::Symlink(si) => walk.symlinks[*si].name.len(), + }; + entries.push(DirEntryOnDisk { + nid: 0, + file_type: 0, + name: vec![0; name_len], + }); + } + + // Simulate block splitting to get total size + let mut total = 0u64; + let mut remaining = &entries[..]; + while !remaining.is_empty() { + let mut count = 0; + let mut block_size = 0usize; + for entry in remaining { + let entry_size = 12 + entry.name.len(); + if block_size + entry_size > BLOCK_SIZE as usize && count > 0 { + break; + } + block_size += entry_size; + count += 1; + } + remaining = &remaining[count..]; + if remaining.is_empty() { + total += block_size as u64; // last block: actual size + } else { + total += BLOCK_SIZE; // full block + } + } + total +} + +fn align_up(val: u64, align: u64) -> u64 { + (val + align - 1) & !(align - 1) +} + +pub fn build_erofs_regions(layout: &ErofsLayout, walk: &WalkResult) -> Vec { + let files = &walk.files; + let mut regions = Vec::new(); + + // Metadata region (superblock + inode table + dir blocks) + regions.push(Region { + start: 0, + len: layout.metadata.len() as u64, + region_type: RegionType::Data(Arc::new(layout.metadata.clone())), + }); + + // File and symlink data regions + for fr in &layout.file_regions { + // Padding gap + let current_end = regions.last().map(|r| r.start + r.len).unwrap_or(0); + if fr.offset_in_erofs > current_end { + regions.push(Region { + start: current_end, + len: fr.offset_in_erofs - current_end, + region_type: RegionType::Zero, + }); + } + + if fr.file_index < files.len() { + // Regular file: read from host + regions.push(Region { + start: fr.offset_in_erofs, + len: fr.size, + region_type: RegionType::FilePath { + path: files[fr.file_index].host_path.clone(), + }, + }); + } else { + // Symlink target: inline data + let sym_idx = fr.file_index - files.len(); + if sym_idx < walk.symlinks.len() { + // Pad symlink target to fill the block + let mut data = walk.symlinks[sym_idx].target.clone(); + data.resize(fr.size as usize, 0); + regions.push(Region { + start: fr.offset_in_erofs, + len: fr.size, + region_type: RegionType::Data(Arc::new(data)), + }); + } + } + + // Padding to block boundary + let end = fr.offset_in_erofs + fr.size; + let aligned_end = align_up(end, BLOCK_SIZE); + if aligned_end > end { + regions.push(Region { + start: end, + len: aligned_end - end, + region_type: RegionType::Zero, + }); + } + } + + // Ensure total size + let last_end = regions.last().map(|r| r.start + r.len).unwrap_or(0); + if last_end < layout.total_size { + regions.push(Region { + start: last_end, + len: layout.total_size - last_end, + region_type: RegionType::Zero, + }); + } + + regions +} diff --git a/crates/bcvk-nbd/src/fat32.rs b/crates/bcvk-nbd/src/fat32.rs new file mode 100644 index 000000000..06e8f1f61 --- /dev/null +++ b/crates/bcvk-nbd/src/fat32.rs @@ -0,0 +1,556 @@ +//! FAT32 ESP generation using the regions pattern. +//! +//! Generates a virtual FAT32 filesystem with boot files for EFI boot. +//! Metadata (BPB, FAT tables, directory entries) are in-memory Data regions. +//! File data uses File regions for lazy pread from source files. + +use crate::regions::{Region, RegionType}; +use std::path::PathBuf; +use std::sync::Arc; + +const SECTOR_SIZE: u64 = 512; +const CLUSTER_SIZE: u64 = 512; +const SECTORS_PER_CLUSTER: u64 = 1; +const RESERVED_SECTORS: u64 = 32; +const NUM_FATS: u64 = 2; +const DIR_ENTRY_SIZE: u64 = 32; + +const FAT32_EOC: u32 = 0x0FFF_FFFF; +const FAT32_MEDIA: u32 = 0x0FFF_FFF8; + +// Fixed cluster assignments for the ESP directory structure. +// Root directory is always cluster 2 per FAT32 spec. +const CLUSTER_ROOT: u32 = 2; +const CLUSTER_EFI: u32 = 3; +const CLUSTER_EFI_BOOT: u32 = 4; +const CLUSTER_BOOT: u32 = 5; + +struct FatFile { + name_8_3: [u8; 11], + size: u64, + regions: Vec, +} + +pub enum FileDataRegion { + FromFile { path: PathBuf, len: u64 }, + FromData(Vec), + Zero(u64), +} + +struct FatDir { + name_8_3: [u8; 11], + cluster: u32, + entries: Vec, +} + +enum FatDirChild { + Dir(usize), + File(usize), +} + +fn clusters_for(size: u64) -> u64 { + if size == 0 { + 1 + } else { + (size + CLUSTER_SIZE - 1) / CLUSTER_SIZE + } +} + +fn make_8_3(name: &str, ext: &str) -> [u8; 11] { + let mut r = [b' '; 11]; + for (i, b) in name.bytes().take(8).enumerate() { + r[i] = b; + } + for (i, b) in ext.bytes().take(3).enumerate() { + r[8 + i] = b; + } + r +} + +pub fn build_esp_regions( + grub_path: &std::path::Path, + grub_size: u64, + grub_cfg: &[u8], + kernel_path: &std::path::Path, + kernel_size: u64, + initrd_parts: Vec<(FileDataRegion, u64)>, + initrd_total_size: u64, +) -> (Vec, u64) { + // Files + let mut files: Vec = Vec::new(); + + let boot_efi_name = if grub_path + .file_name() + .map(|n| n == "grubx64.efi") + .unwrap_or(false) + { + "BOOTX64" + } else { + "BOOTAA64" + }; + files.push(FatFile { + name_8_3: make_8_3(boot_efi_name, "EFI"), + size: grub_size, + regions: vec![FileDataRegion::FromFile { + path: grub_path.to_path_buf(), + len: grub_size, + }], + }); + + // GRUB.CFG + files.push(FatFile { + name_8_3: make_8_3("GRUB", "CFG"), + size: grub_cfg.len() as u64, + regions: vec![FileDataRegion::FromData(grub_cfg.to_vec())], + }); + + // VMLINUZ + files.push(FatFile { + name_8_3: make_8_3("VMLINUZ", ""), + size: kernel_size, + regions: vec![FileDataRegion::FromFile { + path: kernel_path.to_path_buf(), + len: kernel_size, + }], + }); + + // INITRD.IMG + files.push(FatFile { + name_8_3: make_8_3("INITRD", "IMG"), + size: initrd_total_size, + regions: initrd_parts.into_iter().map(|(r, _)| r).collect(), + }); + + // Directory structure: + // / (root, cluster 2) → EFI/, boot/ + // /EFI (cluster 3) → BOOT/ + // /EFI/BOOT (cluster 4) → BOOTAA64.EFI, GRUB.CFG + // /boot (cluster 5) → VMLINUZ, INITRD.IMG + // Note: /EFI/BOOT and /boot both use 8.3 name "BOOT" but are in different + // parent directories so there is no conflict in the FAT32 namespace. + let dirs = vec![ + FatDir { + name_8_3: make_8_3("", ""), + cluster: CLUSTER_ROOT, + entries: vec![FatDirChild::Dir(1), FatDirChild::Dir(3)], + }, + FatDir { + name_8_3: make_8_3("EFI", ""), + cluster: CLUSTER_EFI, + entries: vec![FatDirChild::Dir(2)], + }, + FatDir { + name_8_3: make_8_3("BOOT", ""), + cluster: CLUSTER_EFI_BOOT, + entries: vec![FatDirChild::File(0), FatDirChild::File(1)], + }, + FatDir { + name_8_3: make_8_3("BOOT", ""), + cluster: CLUSTER_BOOT, + entries: vec![FatDirChild::File(2), FatDirChild::File(3)], + }, + ]; + + let dir_clusters = dirs.len() as u32; + + // Assign file clusters (starting after directory clusters) + let mut file_start_clusters: Vec = Vec::new(); + let mut next_cluster = 2 + dir_clusters; + for f in &files { + file_start_clusters.push(next_cluster); + next_cluster += clusters_for(f.size) as u32; + } + let total_clusters = next_cluster; + let data_clusters = total_clusters - 2; + + // FAT table + let fat_entries = total_clusters as usize; + let fat_bytes = ((fat_entries * 4 + SECTOR_SIZE as usize - 1) / SECTOR_SIZE as usize) + * SECTOR_SIZE as usize; + let fat_sectors = fat_bytes as u64 / SECTOR_SIZE; + + let mut fat = vec![0u8; fat_bytes]; + // Entry 0: media descriptor + fat[0..4].copy_from_slice(&FAT32_MEDIA.to_le_bytes()); + // Entry 1: EOC + fat[4..8].copy_from_slice(&FAT32_EOC.to_le_bytes()); + + // Directory clusters (each is single-cluster, EOC) + for d in &dirs { + let off = d.cluster as usize * 4; + fat[off..off + 4].copy_from_slice(&FAT32_EOC.to_le_bytes()); + } + + // File cluster chains + for (fi, f) in files.iter().enumerate() { + let start = file_start_clusters[fi]; + let num = clusters_for(f.size) as u32; + for c in 0..num { + let cluster = start + c; + let off = cluster as usize * 4; + if c == num - 1 { + fat[off..off + 4].copy_from_slice(&FAT32_EOC.to_le_bytes()); + } else { + fat[off..off + 4].copy_from_slice(&(cluster + 1).to_le_bytes()); + } + } + } + + // Data region start (in sectors) + let data_start_sector = RESERVED_SECTORS + NUM_FATS * fat_sectors; + + // Build directory entry blocks + let mut dir_blocks: Vec> = Vec::new(); + for (di, d) in dirs.iter().enumerate() { + let mut block = vec![0u8; CLUSTER_SIZE as usize]; + let mut pos = 0usize; + + // "." and ".." entries for subdirectories + if di > 0 { + write_dir_entry(&mut block, pos, b". ", 0x10, d.cluster, 0); + pos += DIR_ENTRY_SIZE as usize; + // Parent cluster: dirs at index 1 (EFI) and 3 (boot) are children of root (0). + // Dir at index 2 (EFI/BOOT) is a child of EFI (dirs[1]). + debug_assert!(dirs.len() == 4, "directory structure changed"); + let parent_cluster = if di == 1 || di == 3 { + 0u32 + } else { + dirs[1].cluster + }; + write_dir_entry(&mut block, pos, b".. ", 0x10, parent_cluster, 0); + pos += DIR_ENTRY_SIZE as usize; + } + + for child in &d.entries { + match child { + FatDirChild::Dir(idx) => { + let cd = &dirs[*idx]; + write_dir_entry(&mut block, pos, &cd.name_8_3, 0x10, cd.cluster, 0); + } + FatDirChild::File(idx) => { + let cf = &files[*idx]; + write_dir_entry( + &mut block, + pos, + &cf.name_8_3, + 0x20, + file_start_clusters[*idx], + cf.size, + ); + } + } + pos += DIR_ENTRY_SIZE as usize; + } + dir_blocks.push(block); + } + + // Total size of ESP partition + let total_sectors = data_start_sector + data_clusters as u64 * SECTORS_PER_CLUSTER; + let total_size = total_sectors * SECTOR_SIZE; + + // BPB (Boot Parameter Block) + let bpb = build_bpb( + total_sectors as u32, + fat_sectors as u32, + data_clusters as u64, + ); + + // FSInfo + let fsinfo = build_fsinfo( + (data_clusters as u32).saturating_sub( + dir_clusters as u32 + + files + .iter() + .map(|f| clusters_for(f.size) as u32) + .sum::(), + ), + next_cluster, + ); + + // Assemble regions + let mut regions: Vec = Vec::new(); + let mut offset = 0u64; + + // Sector 0: BPB + regions.push(Region { + start: offset, + len: SECTOR_SIZE, + region_type: RegionType::Data(Arc::new(bpb.clone())), + }); + offset += SECTOR_SIZE; + + // Sector 1: FSInfo + regions.push(Region { + start: offset, + len: SECTOR_SIZE, + region_type: RegionType::Data(Arc::new(fsinfo.clone())), + }); + offset += SECTOR_SIZE; + + // Sectors 2-5: zero padding + let pad_to_backup = 4 * SECTOR_SIZE; + regions.push(Region { + start: offset, + len: pad_to_backup, + region_type: RegionType::Zero, + }); + offset += pad_to_backup; + + // Sector 6: Backup BPB + regions.push(Region { + start: offset, + len: SECTOR_SIZE, + region_type: RegionType::Data(Arc::new(bpb)), + }); + offset += SECTOR_SIZE; + + // Sector 7: Backup FSInfo + regions.push(Region { + start: offset, + len: SECTOR_SIZE, + region_type: RegionType::Data(Arc::new(fsinfo)), + }); + offset += SECTOR_SIZE; + + // Sectors 8-31: zero padding to reserved end + let remaining_reserved = (RESERVED_SECTORS * SECTOR_SIZE) - offset; + if remaining_reserved > 0 { + regions.push(Region { + start: offset, + len: remaining_reserved, + region_type: RegionType::Zero, + }); + offset += remaining_reserved; + } + + // FAT1 + let fat_data = Arc::new(fat.clone()); + regions.push(Region { + start: offset, + len: fat_bytes as u64, + region_type: RegionType::Data(fat_data.clone()), + }); + offset += fat_bytes as u64; + + // FAT2 (copy) + regions.push(Region { + start: offset, + len: fat_bytes as u64, + region_type: RegionType::Data(fat_data), + }); + offset += fat_bytes as u64; + + // Data area: directory clusters + for block in &dir_blocks { + regions.push(Region { + start: offset, + len: CLUSTER_SIZE, + region_type: RegionType::Data(Arc::new(block.clone())), + }); + offset += CLUSTER_SIZE; + } + + // Data area: file clusters + for (_fi, f) in files.iter().enumerate() { + let mut file_offset = 0u64; + + for part in &f.regions { + match part { + FileDataRegion::FromFile { path, len } => { + regions.push(Region { + start: offset, + len: *len, + region_type: RegionType::FilePath { path: path.clone() }, + }); + offset += len; + file_offset += len; + } + FileDataRegion::FromData(data) => { + let len = data.len() as u64; + regions.push(Region { + start: offset, + len, + region_type: RegionType::Data(Arc::new(data.clone())), + }); + offset += len; + file_offset += len; + } + FileDataRegion::Zero(len) => { + if *len > 0 { + regions.push(Region { + start: offset, + len: *len, + region_type: RegionType::Zero, + }); + offset += len; + file_offset += len; + } + } + } + } + + // Pad to cluster boundary + let used_in_last = file_offset % CLUSTER_SIZE; + if used_in_last > 0 { + let pad = CLUSTER_SIZE - used_in_last; + regions.push(Region { + start: offset, + len: pad, + region_type: RegionType::Zero, + }); + offset += pad; + } + } + + // Ensure total_size is correct + debug_assert!( + offset <= total_size, + "regions exceeded total_size: {} > {}", + offset, + total_size + ); + if offset < total_size { + regions.push(Region { + start: offset, + len: total_size - offset, + region_type: RegionType::Zero, + }); + } + + (regions, total_size) +} + +/// Build initrd regions: original file + 4-byte alignment + CPIO data. +pub fn build_initrd_regions( + initrd_path: &std::path::Path, + initrd_size: u64, + units_cpio: &[u8], + ssh_cpio: Option<&[u8]>, +) -> (Vec<(FileDataRegion, u64)>, u64) { + let mut parts = Vec::new(); + let mut total = 0u64; + + // Original initramfs + parts.push(( + FileDataRegion::FromFile { + path: initrd_path.to_path_buf(), + len: initrd_size, + }, + initrd_size, + )); + total += initrd_size; + + // 4-byte alignment padding + let pad = ((4 - (initrd_size % 4)) % 4) as u64; + if pad > 0 { + parts.push((FileDataRegion::Zero(pad), pad)); + total += pad; + } + + // Units CPIO + let len = units_cpio.len() as u64; + parts.push((FileDataRegion::FromData(units_cpio.to_vec()), len)); + total += len; + + // SSH CPIO (if provided) + if let Some(ssh) = ssh_cpio { + let pad2 = ((4 - (total % 4)) % 4) as u64; + if pad2 > 0 { + parts.push((FileDataRegion::Zero(pad2), pad2)); + total += pad2; + } + let len = ssh.len() as u64; + parts.push((FileDataRegion::FromData(ssh.to_vec()), len)); + total += len; + } + + (parts, total) +} + +fn write_dir_entry(buf: &mut [u8], pos: usize, name: &[u8; 11], attr: u8, cluster: u32, size: u64) { + buf[pos..pos + 11].copy_from_slice(name); + buf[pos + 11] = attr; + // cluster high + buf[pos + 20..pos + 22].copy_from_slice(&((cluster >> 16) as u16).to_le_bytes()); + // cluster low + buf[pos + 26..pos + 28].copy_from_slice(&(cluster as u16).to_le_bytes()); + // file size (32-bit) + buf[pos + 28..pos + 32].copy_from_slice(&(size as u32).to_le_bytes()); +} + +fn build_bpb(total_sectors: u32, fat_sectors: u32, _data_clusters: u64) -> Vec { + let mut bpb = vec![0u8; SECTOR_SIZE as usize]; + // Jump instruction + bpb[0] = 0xEB; + bpb[1] = 0x58; + bpb[2] = 0x90; + // OEM name + bpb[3..11].copy_from_slice(b"MSWIN4.1"); + // Bytes per sector + bpb[11..13].copy_from_slice(&(SECTOR_SIZE as u16).to_le_bytes()); + // Sectors per cluster + bpb[13] = SECTORS_PER_CLUSTER as u8; + // Reserved sectors + bpb[14..16].copy_from_slice(&(RESERVED_SECTORS as u16).to_le_bytes()); + // Number of FATs + bpb[16] = NUM_FATS as u8; + // Root entry count (0 for FAT32) + bpb[17..19].copy_from_slice(&0u16.to_le_bytes()); + // Total sectors 16 (0 for FAT32) + bpb[19..21].copy_from_slice(&0u16.to_le_bytes()); + // Media type + bpb[21] = 0xF8; + // Sectors per FAT 16 (0 for FAT32) + bpb[22..24].copy_from_slice(&0u16.to_le_bytes()); + // Sectors per track + bpb[24..26].copy_from_slice(&32u16.to_le_bytes()); + // Number of heads + bpb[26..28].copy_from_slice(&64u16.to_le_bytes()); + // Hidden sectors + bpb[28..32].copy_from_slice(&0u32.to_le_bytes()); + // Total sectors 32 + bpb[32..36].copy_from_slice(&total_sectors.to_le_bytes()); + // --- FAT32 specific --- + // Sectors per FAT + bpb[36..40].copy_from_slice(&fat_sectors.to_le_bytes()); + // Extended flags + bpb[40..42].copy_from_slice(&0u16.to_le_bytes()); + // FS version + bpb[42..44].copy_from_slice(&0u16.to_le_bytes()); + // Root cluster + bpb[44..48].copy_from_slice(&2u32.to_le_bytes()); + // FSInfo sector + bpb[48..50].copy_from_slice(&1u16.to_le_bytes()); + // Backup boot sector + bpb[50..52].copy_from_slice(&6u16.to_le_bytes()); + // Reserved (12 bytes, already zero) + // Drive number + bpb[64] = 0x80; + // Boot signature + bpb[66] = 0x29; + // Volume serial number + bpb[67..71].copy_from_slice(&0x42424242u32.to_le_bytes()); + // Volume label + bpb[71..82].copy_from_slice(b"BCVK-ESP "); + // Filesystem type + bpb[82..90].copy_from_slice(b"FAT32 "); + // Boot signature + bpb[510] = 0x55; + bpb[511] = 0xAA; + bpb +} + +fn build_fsinfo(free_clusters: u32, next_free: u32) -> Vec { + let mut fs = vec![0u8; SECTOR_SIZE as usize]; + // Signature1 + fs[0..4].copy_from_slice(&0x41615252u32.to_le_bytes()); + // Signature2 + fs[484..488].copy_from_slice(&0x61417272u32.to_le_bytes()); + // Free cluster count + fs[488..492].copy_from_slice(&free_clusters.to_le_bytes()); + // Next free cluster + fs[492..496].copy_from_slice(&next_free.to_le_bytes()); + // Signature3 + fs[508..512].copy_from_slice(&0xAA550000u32.to_le_bytes()); + fs +} diff --git a/crates/bcvk-nbd/src/gpt.rs b/crates/bcvk-nbd/src/gpt.rs new file mode 100644 index 000000000..02becd16d --- /dev/null +++ b/crates/bcvk-nbd/src/gpt.rs @@ -0,0 +1,290 @@ +use crate::regions::{Region, RegionType}; +use std::sync::Arc; + +const SECTOR_SIZE: u64 = 512; +const GPT_HEADER_SIZE: u64 = 92; +const GPT_ENTRY_SIZE: u64 = 128; +const GPT_ENTRIES: u64 = 128; + +// EFI System Partition type GUID +const ESP_TYPE_GUID: [u8; 16] = [ + 0x28, 0x73, 0x2A, 0xC1, 0x1F, 0xF8, 0xD2, 0x11, 0xBA, 0x4B, 0x00, 0xA0, 0xC9, 0x3E, 0xC9, 0x3B, +]; + +// Linux filesystem type GUID +const LINUX_TYPE_GUID: [u8; 16] = [ + 0xAF, 0x3D, 0xC6, 0x0F, 0x83, 0x84, 0x72, 0x47, 0x8E, 0x79, 0x3D, 0x69, 0xD8, 0x47, 0x7D, 0xE4, +]; + +pub struct DiskLayout { + pub regions: Vec, + pub total_size: u64, +} + +pub fn build_gpt_disk( + esp_regions: Vec, + esp_size: u64, + erofs_regions: Vec, + erofs_size: u64, +) -> std::io::Result { + // GPT layout: + // LBA 0: Protective MBR + // LBA 1: GPT Header + // LBA 2-33: Partition Table (128 entries * 128 bytes = 16384 bytes = 32 sectors) + // LBA 34+: ESP partition (aligned to 2048 sectors / 1MB) + // After ESP: EROFS partition + // End: Backup GPT + + let partition_table_sectors = (GPT_ENTRIES * GPT_ENTRY_SIZE + SECTOR_SIZE - 1) / SECTOR_SIZE; + let first_usable_lba = 34u64; // standard + let esp_start_lba = 2048u64; // 1MB aligned + let esp_sectors = (esp_size + SECTOR_SIZE - 1) / SECTOR_SIZE; + let erofs_start_lba = esp_start_lba + esp_sectors; + // Align to 2048 sectors + let erofs_start_lba = (erofs_start_lba + 2047) & !2047; + let erofs_sectors = (erofs_size + SECTOR_SIZE - 1) / SECTOR_SIZE; + let last_usable_lba = erofs_start_lba + erofs_sectors - 1; + let backup_table_lba = last_usable_lba + 1; + let backup_header_lba = backup_table_lba + partition_table_sectors; + let total_sectors = backup_header_lba + 1; + let total_size = total_sectors * SECTOR_SIZE; + + // Build partition table entries + let mut partition_table = vec![0u8; (GPT_ENTRIES * GPT_ENTRY_SIZE) as usize]; + + // Entry 0: ESP + write_gpt_entry( + &mut partition_table, + 0, + &ESP_TYPE_GUID, + esp_start_lba, + esp_start_lba + esp_sectors - 1, + b"EFI System", + ); + + // Entry 1: EROFS rootfs + write_gpt_entry( + &mut partition_table, + 1, + &LINUX_TYPE_GUID, + erofs_start_lba, + erofs_start_lba + erofs_sectors - 1, + b"bcvk-root", + ); + + let partition_table_crc = crc32fast::hash(&partition_table); + + // Build GPT header + let mut gpt_header = vec![0u8; SECTOR_SIZE as usize]; + write_gpt_header( + &mut gpt_header, + 1, // my LBA + backup_header_lba, + first_usable_lba, + last_usable_lba, + 2, // partition table LBA + 2, // num entries used + partition_table_crc, + ); + + // Build backup GPT header + let mut backup_header = vec![0u8; SECTOR_SIZE as usize]; + write_gpt_header( + &mut backup_header, + backup_header_lba, + 1, // alternate LBA + first_usable_lba, + last_usable_lba, + backup_table_lba, + 2, + partition_table_crc, + ); + + // Build protective MBR + let mut mbr = vec![0u8; SECTOR_SIZE as usize]; + write_protective_mbr(&mut mbr, total_sectors); + + // Assemble regions + let mut regions = Vec::new(); + + // MBR + regions.push(Region { + start: 0, + len: SECTOR_SIZE, + region_type: RegionType::Data(Arc::new(mbr)), + }); + + // GPT Header + regions.push(Region { + start: SECTOR_SIZE, + len: SECTOR_SIZE, + region_type: RegionType::Data(Arc::new(gpt_header)), + }); + + // Partition Table + regions.push(Region { + start: 2 * SECTOR_SIZE, + len: partition_table.len() as u64, + region_type: RegionType::Data(Arc::new(partition_table.clone())), + }); + + // Padding to ESP start + let pad_start = 2 * SECTOR_SIZE + partition_table.len() as u64; + let esp_byte_offset = esp_start_lba * SECTOR_SIZE; + if esp_byte_offset > pad_start { + regions.push(Region { + start: pad_start, + len: esp_byte_offset - pad_start, + region_type: RegionType::Zero, + }); + } + + // ESP partition (from provided regions, offset-adjusted) + for mut r in esp_regions { + r.start += esp_byte_offset; + regions.push(r); + } + + // Padding between ESP and EROFS + let esp_end = esp_byte_offset + esp_size; + let erofs_byte_offset = erofs_start_lba * SECTOR_SIZE; + if erofs_byte_offset > esp_end { + regions.push(Region { + start: esp_end, + len: erofs_byte_offset - esp_end, + region_type: RegionType::Zero, + }); + } + + // EROFS partition (offset all regions) + for mut r in erofs_regions { + r.start += erofs_byte_offset; + regions.push(r); + } + + // Padding to backup GPT + let erofs_end = erofs_byte_offset + erofs_size; + let backup_table_offset = backup_table_lba * SECTOR_SIZE; + if backup_table_offset > erofs_end { + regions.push(Region { + start: erofs_end, + len: backup_table_offset - erofs_end, + region_type: RegionType::Zero, + }); + } + + // Backup partition table + regions.push(Region { + start: backup_table_offset, + len: partition_table.len() as u64, + region_type: RegionType::Data(Arc::new(partition_table)), + }); + + // Backup GPT header + regions.push(Region { + start: backup_header_lba * SECTOR_SIZE, + len: SECTOR_SIZE, + region_type: RegionType::Data(Arc::new(backup_header)), + }); + + Ok(DiskLayout { + regions, + total_size, + }) +} + +fn write_gpt_entry( + table: &mut [u8], + index: usize, + type_guid: &[u8; 16], + first_lba: u64, + last_lba: u64, + name: &[u8], +) { + let off = index * GPT_ENTRY_SIZE as usize; + // Partition type GUID + table[off..off + 16].copy_from_slice(type_guid); + // Unique partition GUID (generate simple one from index) + let mut unique = [0u8; 16]; + unique[0] = index as u8 + 1; + unique[15] = 0x42; + table[off + 16..off + 32].copy_from_slice(&unique); + // First LBA + table[off + 32..off + 40].copy_from_slice(&first_lba.to_le_bytes()); + // Last LBA + table[off + 40..off + 48].copy_from_slice(&last_lba.to_le_bytes()); + // Attributes + table[off + 48..off + 56].copy_from_slice(&0u64.to_le_bytes()); + // Name (UTF-16LE) + for (i, &b) in name.iter().enumerate().take(36) { + table[off + 56 + i * 2] = b; + table[off + 56 + i * 2 + 1] = 0; + } +} + +fn write_gpt_header( + buf: &mut [u8], + my_lba: u64, + alternate_lba: u64, + first_usable: u64, + last_usable: u64, + partition_table_lba: u64, + _num_entries: u32, + partition_crc: u32, +) { + // Signature "EFI PART" + buf[0..8].copy_from_slice(b"EFI PART"); + // Revision 1.0 + buf[8..12].copy_from_slice(&0x00010000u32.to_le_bytes()); + // Header size + buf[12..16].copy_from_slice(&(GPT_HEADER_SIZE as u32).to_le_bytes()); + // Header CRC32 (computed after all fields set) + // My LBA + buf[24..32].copy_from_slice(&my_lba.to_le_bytes()); + // Alternate LBA + buf[32..40].copy_from_slice(&alternate_lba.to_le_bytes()); + // First usable LBA + buf[40..48].copy_from_slice(&first_usable.to_le_bytes()); + // Last usable LBA + buf[48..56].copy_from_slice(&last_usable.to_le_bytes()); + // Fixed disk GUID for reproducible builds (not security-sensitive) + const DISK_GUID: [u8; 16] = [ + 0xAA, 0xBB, 0xCC, 0xDD, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, + 0xCC, + ]; + let disk_guid = DISK_GUID; + buf[56..72].copy_from_slice(&disk_guid); + // Partition entry start LBA + buf[72..80].copy_from_slice(&partition_table_lba.to_le_bytes()); + // Number of partition entries + buf[80..84].copy_from_slice(&(GPT_ENTRIES as u32).to_le_bytes()); + // Size of partition entry + buf[84..88].copy_from_slice(&(GPT_ENTRY_SIZE as u32).to_le_bytes()); + // Partition table CRC32 + buf[88..92].copy_from_slice(&partition_crc.to_le_bytes()); + + // Compute header CRC32 + buf[16..20].copy_from_slice(&0u32.to_le_bytes()); // zero CRC field first + let crc = crc32fast::hash(&buf[0..GPT_HEADER_SIZE as usize]); + buf[16..20].copy_from_slice(&crc.to_le_bytes()); +} + +fn write_protective_mbr(buf: &mut [u8], total_sectors: u64) { + // Partition entry at offset 446 + buf[446] = 0x00; // not bootable + buf[447] = 0x00; // CHS start + buf[448] = 0x02; + buf[449] = 0x00; + buf[450] = 0xEE; // type: GPT protective + buf[451] = 0xFF; // CHS end + buf[452] = 0xFF; + buf[453] = 0xFF; + // LBA start + buf[454..458].copy_from_slice(&1u32.to_le_bytes()); + // LBA size + let size = std::cmp::min(total_sectors - 1, 0xFFFFFFFF) as u32; + buf[458..462].copy_from_slice(&size.to_le_bytes()); + // Boot signature + buf[510] = 0x55; + buf[511] = 0xAA; +} diff --git a/crates/bcvk-nbd/src/initramfs.rs b/crates/bcvk-nbd/src/initramfs.rs new file mode 100644 index 000000000..abe241ab6 --- /dev/null +++ b/crates/bcvk-nbd/src/initramfs.rs @@ -0,0 +1,145 @@ +//! CPIO newc archive generation for initramfs append. + +use std::io::Write; + +use cpio::newc::Builder as NewcBuilder; +use cpio::newc::ModeFileType; + +fn write_dir(out: &mut Vec, path: &str) { + NewcBuilder::new(path) + .mode(0o755) + .set_mode_file_type(ModeFileType::Directory) + .write(out, 0) + .finish() + .unwrap(); +} + +fn write_file(out: &mut Vec, path: &str, data: &[u8]) { + let mut w = NewcBuilder::new(path) + .mode(0o644) + .set_mode_file_type(ModeFileType::Regular) + .write(out, data.len() as u32); + w.write_all(data).unwrap(); + w.finish().unwrap(); +} + +fn write_file_exec(out: &mut Vec, path: &str, data: &[u8]) { + let mut w = NewcBuilder::new(path) + .mode(0o755) + .set_mode_file_type(ModeFileType::Regular) + .write(out, data.len() as u32); + w.write_all(data).unwrap(); + w.finish().unwrap(); +} + +pub fn build_units_cpio() -> Vec { + let mut out = Vec::with_capacity(32768); + + write_dir(&mut out, "usr"); + write_dir(&mut out, "usr/lib"); + write_dir(&mut out, "usr/lib/systemd"); + write_dir(&mut out, "usr/lib/systemd/system"); + write_dir(&mut out, "usr/lib/systemd/system/initrd-fs.target.d"); + + write_file( + &mut out, + "usr/lib/systemd/system/bcvk-var-ephemeral.service", + include_bytes!("../../kit/src/units/bcvk-var-ephemeral.service"), + ); + + write_file( + &mut out, + "usr/lib/systemd/system/bcvk-etc-overlay.service", + include_bytes!("../../kit/src/units/bcvk-etc-overlay.service"), + ); + + write_file( + &mut out, + "usr/lib/systemd/system/bcvk-copy-units.service", + include_bytes!("../../kit/src/units/bcvk-copy-units.service"), + ); + + write_file( + &mut out, + "usr/lib/systemd/system/bcvk-journal-stream.service", + b"[Unit]\n\ + Description=Stream journal to virtio-serial\n\ + DefaultDependencies=no\n\ + \n\ + [Service]\n\ + Type=simple\n\ + ExecStart=/bin/sh -c 'journalctl -f --no-hostname -o short-monotonic > /dev/hvc1 2>&1 || true'\n", + ); + + write_file( + &mut out, + "usr/lib/systemd/system/initrd-fs.target.d/bcvk-var-ephemeral.conf", + b"[Unit]\nWants=bcvk-var-ephemeral.service\n", + ); + write_file( + &mut out, + "usr/lib/systemd/system/initrd-fs.target.d/bcvk-etc-overlay.conf", + b"[Unit]\nWants=bcvk-etc-overlay.service\n", + ); + write_file( + &mut out, + "usr/lib/systemd/system/initrd-fs.target.d/bcvk-copy-units.conf", + b"[Unit]\nWants=bcvk-copy-units.service\n", + ); + + cpio::newc::trailer(out).unwrap() +} + +pub fn build_ssh_cpio(pubkey: &str) -> Vec { + let mut out = Vec::with_capacity(4096); + + write_dir(&mut out, "usr"); + write_dir(&mut out, "usr/lib"); + write_dir(&mut out, "usr/lib/bcvk"); + write_dir(&mut out, "usr/lib/systemd"); + write_dir(&mut out, "usr/lib/systemd/system"); + write_dir(&mut out, "usr/lib/systemd/system/initrd-fs.target.d"); + + let setup_script = format!( + "#!/bin/bash\n\ + mkdir -p /sysroot/var/roothome /sysroot/var/empty /sysroot/var/log /sysroot/var/tmp\n\ + chmod 700 /sysroot/var/roothome\n\ + chmod 711 /sysroot/var/empty\n\ + mkdir -p /sysroot/var/roothome/.ssh\n\ + chmod 700 /sysroot/var/roothome/.ssh\n\ + echo '{}' > /sysroot/var/roothome/.ssh/authorized_keys\n\ + chmod 600 /sysroot/var/roothome/.ssh/authorized_keys\n\ + chown -R 0:0 /sysroot/var/roothome/.ssh\n", + pubkey + ); + write_file_exec( + &mut out, + "usr/lib/bcvk/setup-ssh.sh", + setup_script.as_bytes(), + ); + + write_file( + &mut out, + "usr/lib/systemd/system/bcvk-ssh-setup.service", + b"[Unit]\n\ + Description=Setup SSH authorized_keys for root\n\ + DefaultDependencies=no\n\ + ConditionPathExists=/etc/initrd-release\n\ + Before=initrd-fs.target\n\ + After=bcvk-var-ephemeral.service\n\ + Requires=bcvk-var-ephemeral.service\n\ + \n\ + [Service]\n\ + Type=oneshot\n\ + RemainAfterExit=yes\n\ + ExecStart=/usr/bin/bash /usr/lib/bcvk/setup-ssh.sh\n", + ); + + write_file( + &mut out, + "usr/lib/systemd/system/initrd-fs.target.d/bcvk-ssh-setup.conf", + b"[Unit]\nWants=bcvk-ssh-setup.service\n", + ); + + cpio::newc::trailer(out).unwrap() +} diff --git a/crates/bcvk-nbd/src/main.rs b/crates/bcvk-nbd/src/main.rs new file mode 100644 index 000000000..11a3f29aa --- /dev/null +++ b/crates/bcvk-nbd/src/main.rs @@ -0,0 +1,248 @@ +mod dir_walk; +mod erofs; +mod fat32; +mod gpt; +mod initramfs; +pub mod nbd; +pub mod regions; + +use std::net::TcpListener; +use std::path::PathBuf; + +use regions::Region; + +struct Args { + dir: PathBuf, + port: u16, + cmdline: String, + ssh_pubkey: Option, + vsock: bool, +} + +fn parse_args() -> Args { + let mut args = Args { + dir: PathBuf::new(), + port: 10809, + cmdline: String::new(), + ssh_pubkey: None, + vsock: false, + }; + + let mut argv = std::env::args().skip(1); + while let Some(arg) = argv.next() { + match arg.as_str() { + "--dir" => { + args.dir = PathBuf::from(argv.next().unwrap_or_else(|| { + eprintln!("--dir requires a value"); + std::process::exit(1); + })); + } + "--port" => { + let val = argv.next().unwrap_or_else(|| { + eprintln!("--port requires a value"); + std::process::exit(1); + }); + args.port = val.parse().unwrap_or_else(|_| { + eprintln!("--port: invalid number: {val}"); + std::process::exit(1); + }); + } + "--cmdline" => { + args.cmdline = argv.next().unwrap_or_else(|| { + eprintln!("--cmdline requires a value"); + std::process::exit(1); + }); + } + "--ssh-pubkey" => { + args.ssh_pubkey = Some(argv.next().unwrap_or_else(|| { + eprintln!("--ssh-pubkey requires a value"); + std::process::exit(1); + })); + } + "--vsock" => { + args.vsock = true; + } + other => { + eprintln!("unknown argument: {other}"); + eprintln!( + "usage: bcvk-nbd --dir DIR --port PORT --cmdline CMDLINE [--ssh-pubkey KEY] [--vsock]" + ); + std::process::exit(1); + } + } + } + + if args.dir.as_os_str().is_empty() { + eprintln!("--dir is required"); + std::process::exit(1); + } + if args.cmdline.is_empty() { + eprintln!("--cmdline is required"); + std::process::exit(1); + } + + args +} + +fn find_kernel_dir(dir: &std::path::Path) -> Option<(PathBuf, PathBuf)> { + let modules = dir.join("usr/lib/modules"); + if let Ok(entries) = std::fs::read_dir(&modules) { + for entry in entries.flatten() { + let kdir = entry.path(); + let vmlinuz = kdir.join("vmlinuz"); + let initramfs = kdir.join("initramfs.img"); + if vmlinuz.exists() && initramfs.exists() { + return Some((vmlinuz, initramfs)); + } + } + } + None +} + +fn find_grub(dir: &std::path::Path) -> Option { + fn walk(path: &std::path::Path, target: &str) -> Option { + if let Ok(entries) = std::fs::read_dir(path) { + for entry in entries.flatten() { + let p = entry.path(); + if p.is_file() && p.file_name().map(|n| n == target).unwrap_or(false) { + return Some(p); + } + if p.is_dir() { + if let Some(found) = walk(&p, target) { + return Some(found); + } + } + } + } + None + } + walk(&dir.join("usr/lib"), "grubaa64.efi").or_else(|| walk(&dir.join("usr/lib"), "grubx64.efi")) +} + +fn file_size(path: &std::path::Path) -> u64 { + match std::fs::metadata(path) { + Ok(m) => m.len(), + Err(e) => { + eprintln!("cannot stat {}: {}", path.display(), e); + std::process::exit(1); + } + } +} + +fn build_disk(args: &Args) -> (Vec, u64) { + let root_dir = match cap_std::fs::Dir::open_ambient_dir(&args.dir, cap_std::ambient_authority()) + { + Ok(d) => d, + Err(e) => { + eprintln!("failed to open directory {:?}: {}", args.dir, e); + std::process::exit(1); + } + }; + + let walk = match dir_walk::walk_directory(&root_dir, &args.dir) { + Ok(w) => w, + Err(e) => { + eprintln!("failed to walk directory: {e}"); + std::process::exit(1); + } + }; + + let erofs_layout = match erofs::build_erofs(&walk) { + Ok(l) => l, + Err(e) => { + eprintln!("failed to build EROFS: {e}"); + std::process::exit(1); + } + }; + + let erofs_regions = + regions::consolidate_regions(erofs::build_erofs_regions(&erofs_layout, &walk)); + + let (kernel_path, initrd_path) = match find_kernel_dir(&args.dir) { + Some(paths) => paths, + None => { + eprintln!( + "kernel/initramfs not found in {}/usr/lib/modules/", + args.dir.display() + ); + std::process::exit(1); + } + }; + + let grub_path = match find_grub(&args.dir) { + Some(p) => p, + None => { + eprintln!( + "grubaa64.efi/grubx64.efi not found in {}/usr/lib/", + args.dir.display() + ); + std::process::exit(1); + } + }; + + let kernel_size = file_size(&kernel_path); + let initrd_size = file_size(&initrd_path); + let grub_size = file_size(&grub_path); + + let grub_cfg = format!( + "set timeout=0\nset default=0\nmenuentry \"bcvk\" {{\n linux /boot/vmlinuz {}\n initrd /boot/initrd.img\n}}\n", + args.cmdline + ); + + let units_cpio = initramfs::build_units_cpio(); + let ssh_cpio = args.ssh_pubkey.as_deref().map(initramfs::build_ssh_cpio); + + let (initrd_parts, initrd_total) = + fat32::build_initrd_regions(&initrd_path, initrd_size, &units_cpio, ssh_cpio.as_deref()); + + let (esp_regions, esp_size) = fat32::build_esp_regions( + &grub_path, + grub_size, + grub_cfg.as_bytes(), + &kernel_path, + kernel_size, + initrd_parts, + initrd_total, + ); + + match gpt::build_gpt_disk( + esp_regions, + esp_size, + erofs_regions, + erofs_layout.total_size, + ) { + Ok(disk) => (disk.regions, disk.total_size), + Err(e) => { + eprintln!("failed to build GPT disk: {e}"); + std::process::exit(1); + } + } +} + +fn main() { + let args = parse_args(); + let vsock = args.vsock; + let port = args.port; + + eprintln!("bcvk-nbd: scanning {}...", args.dir.display()); + let (regions, total_size) = build_disk(&args); + eprintln!( + "bcvk-nbd: disk ready ({} regions, {} bytes)", + regions.len(), + total_size + ); + + let device = nbd::RegionBlockDevice::new(regions, total_size); + + if vsock { + eprintln!("bcvk-nbd: listening on vsock port {port}"); + nbd::serve_vsock(port as u32, device); + } else { + let listener = TcpListener::bind(("0.0.0.0", port)).unwrap_or_else(|e| { + eprintln!("bcvk-nbd: failed to bind TCP port {port}: {e}"); + std::process::exit(1); + }); + eprintln!("bcvk-nbd: listening on TCP port {port}"); + nbd::serve_tcp(listener, device); + } +} diff --git a/crates/bcvk-nbd/src/nbd.rs b/crates/bcvk-nbd/src/nbd.rs new file mode 100644 index 000000000..b6d4ed0d4 --- /dev/null +++ b/crates/bcvk-nbd/src/nbd.rs @@ -0,0 +1,359 @@ +// NBD protocol implementation inspired by: +// https://github.com/vi/rust-nbd (MIT/Apache-2.0) +// https://github.com/tchajed/rust-nbd (MIT) + +use std::io::{self, Read, Write}; +use std::net::TcpListener; +use std::sync::Arc; +use std::thread; + +use crate::regions::{self, Region}; + +// --- BlockDevice trait (tchajed's Blocks pattern, read-only) --- + +pub trait BlockDevice: Send + Sync { + fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<()>; + fn size(&self) -> u64; +} + +pub struct RegionBlockDevice { + regions: Vec, + total_size: u64, +} + +impl RegionBlockDevice { + pub fn new(regions: Vec, total_size: u64) -> Self { + Self { + regions, + total_size, + } + } +} + +impl BlockDevice for RegionBlockDevice { + fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<()> { + regions::pread(&self.regions, buf, offset) + } + + fn size(&self) -> u64 { + self.total_size + } +} + +// --- BigEndian I/O helpers (no byteorder dependency) --- + +trait ReadBE: Read { + fn read_u16_be(&mut self) -> io::Result { + let mut buf = [0u8; 2]; + self.read_exact(&mut buf)?; + Ok(u16::from_be_bytes(buf)) + } + fn read_u32_be(&mut self) -> io::Result { + let mut buf = [0u8; 4]; + self.read_exact(&mut buf)?; + Ok(u32::from_be_bytes(buf)) + } + fn read_u64_be(&mut self) -> io::Result { + let mut buf = [0u8; 8]; + self.read_exact(&mut buf)?; + Ok(u64::from_be_bytes(buf)) + } +} +impl ReadBE for R {} + +trait WriteBE: Write { + fn write_u16_be(&mut self, v: u16) -> io::Result<()> { + self.write_all(&v.to_be_bytes()) + } + fn write_u32_be(&mut self, v: u32) -> io::Result<()> { + self.write_all(&v.to_be_bytes()) + } + fn write_u64_be(&mut self, v: u64) -> io::Result<()> { + self.write_all(&v.to_be_bytes()) + } +} +impl WriteBE for W {} + +// --- NBD protocol constants --- + +const MAGIC: &[u8; 8] = b"NBDMAGIC"; +const IHAVEOPT: u64 = 0x49484156454F5054; +const REPLY_MAGIC: u64 = 0x3e889045565a9; +const REQUEST_MAGIC: u32 = 0x25609513; +const SIMPLE_REPLY_MAGIC: u32 = 0x67446698; + +const NBD_FLAG_FIXED_NEWSTYLE: u16 = 1 << 0; +const NBD_FLAG_NO_ZEROES: u16 = 1 << 1; + +const NBD_FLAG_HAS_FLAGS: u16 = 1 << 0; +const NBD_FLAG_READ_ONLY: u16 = 1 << 1; +const NBD_FLAG_CAN_MULTI_CONN: u16 = 1 << 8; + +const NBD_FLAG_C_FIXED_NEWSTYLE: u32 = 1; +const NBD_FLAG_C_NO_ZEROES: u32 = 2; + +const NBD_OPT_EXPORT_NAME: u32 = 1; +const NBD_OPT_ABORT: u32 = 2; +const NBD_OPT_LIST: u32 = 3; +const NBD_OPT_INFO: u32 = 6; +const NBD_OPT_GO: u32 = 7; + +const NBD_REP_ACK: u32 = 1; +const NBD_REP_SERVER: u32 = 2; +const NBD_REP_INFO: u32 = 3; +const NBD_REP_ERR_UNSUP: u32 = (1 << 31) | 1; + +const NBD_INFO_EXPORT: u16 = 0; +const NBD_INFO_BLOCK_SIZE: u16 = 3; + +const NBD_CMD_READ: u16 = 0; +const NBD_CMD_DISC: u16 = 2; + +const TRANSMIT_FLAGS: u16 = NBD_FLAG_HAS_FLAGS | NBD_FLAG_READ_ONLY | NBD_FLAG_CAN_MULTI_CONN; + +// --- Server --- + +pub fn serve_tcp(listener: TcpListener, device: B) { + let device = Arc::new(device); + for stream in listener.incoming() { + let stream = match stream { + Ok(s) => s, + Err(e) => { + eprintln!("nbd: accept error: {e}"); + continue; + } + }; + let _ = stream.set_nodelay(true); + let dev = Arc::clone(&device); + thread::spawn(move || { + if let Err(e) = handle_connection(stream, &*dev) { + if e.kind() != io::ErrorKind::UnexpectedEof { + eprintln!("nbd: connection error: {e}"); + } + } + }); + } +} + +pub fn serve_vsock(port: u32, device: B) { + let fd = unsafe { libc::socket(libc::AF_VSOCK, libc::SOCK_STREAM, 0) }; + if fd < 0 { + eprintln!("nbd: failed to create vsock socket"); + std::process::exit(1); + } + + let mut addr: libc::sockaddr_vm = unsafe { std::mem::zeroed() }; + addr.svm_family = libc::AF_VSOCK as _; + addr.svm_port = port; + addr.svm_cid = libc::VMADDR_CID_ANY; + let ret = unsafe { + libc::bind( + fd, + &addr as *const libc::sockaddr_vm as *const libc::sockaddr, + std::mem::size_of::() as libc::socklen_t, + ) + }; + if ret < 0 { + eprintln!("nbd: vsock bind failed on port {port}"); + std::process::exit(1); + } + + let ret = unsafe { libc::listen(fd, 4) }; + if ret < 0 { + eprintln!("nbd: vsock listen failed"); + std::process::exit(1); + } + + eprintln!("nbd: listening on vsock port {port}"); + + let device = Arc::new(device); + loop { + let client_fd = unsafe { libc::accept(fd, std::ptr::null_mut(), std::ptr::null_mut()) }; + if client_fd < 0 { + eprintln!("nbd: vsock accept error"); + continue; + } + let dev = Arc::clone(&device); + thread::spawn(move || { + use std::os::unix::io::FromRawFd; + let stream = unsafe { std::net::TcpStream::from_raw_fd(client_fd) }; + if let Err(e) = handle_connection(stream, &*dev) { + if e.kind() != io::ErrorKind::UnexpectedEof { + eprintln!("nbd: vsock connection error: {e}"); + } + } + }); + } +} + +fn handle_connection(mut stream: S, device: &B) -> io::Result<()> { + handshake(&mut stream, device)?; + transmission(&mut stream, device) +} + +// --- Handshake (vi/rust-nbd base + tchajed's OPT_GO) --- + +fn handshake(s: &mut S, device: &B) -> io::Result<()> { + s.write_all(MAGIC)?; + s.write_u64_be(IHAVEOPT)?; + s.write_u16_be(NBD_FLAG_FIXED_NEWSTYLE | NBD_FLAG_NO_ZEROES)?; + s.flush()?; + + let client_flags = s.read_u32_be()?; + if client_flags & NBD_FLAG_C_FIXED_NEWSTYLE == 0 { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "client must support FIXED_NEWSTYLE", + )); + } + let no_zeroes = client_flags & NBD_FLAG_C_NO_ZEROES != 0; + + loop { + let opt_magic = s.read_u64_be()?; + if opt_magic != IHAVEOPT { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "invalid option magic", + )); + } + let opt = s.read_u32_be()?; + let opt_len = s.read_u32_be()?; + if opt_len > 100_000 { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "option data too large", + )); + } + let mut opt_data = vec![0u8; opt_len as usize]; + s.read_exact(&mut opt_data)?; + + match opt { + NBD_OPT_EXPORT_NAME => { + send_export_reply(s, device, no_zeroes)?; + return Ok(()); + } + NBD_OPT_GO => { + send_info_export(s, opt, device)?; + send_info_block_size(s, opt)?; + opt_reply(s, opt, NBD_REP_ACK, &[])?; + return Ok(()); + } + NBD_OPT_INFO => { + send_info_export(s, opt, device)?; + send_info_block_size(s, opt)?; + opt_reply(s, opt, NBD_REP_ACK, &[])?; + } + NBD_OPT_LIST => { + let name = b"bcvk"; + let mut data = (name.len() as u32).to_be_bytes().to_vec(); + data.extend_from_slice(name); + opt_reply(s, opt, NBD_REP_SERVER, &data)?; + opt_reply(s, opt, NBD_REP_ACK, &[])?; + } + NBD_OPT_ABORT => { + opt_reply(s, opt, NBD_REP_ACK, &[])?; + return Err(io::Error::new( + io::ErrorKind::ConnectionAborted, + "client abort", + )); + } + _ => { + opt_reply(s, opt, NBD_REP_ERR_UNSUP, &[])?; + } + } + } +} + +fn send_export_reply( + s: &mut S, + device: &B, + no_zeroes: bool, +) -> io::Result<()> { + s.write_u64_be(device.size())?; + s.write_u16_be(TRANSMIT_FLAGS)?; + if !no_zeroes { + s.write_all(&[0u8; 124])?; + } + s.flush() +} + +fn send_info_export(s: &mut S, opt: u32, device: &B) -> io::Result<()> { + let mut data = Vec::with_capacity(12); + data.extend_from_slice(&NBD_INFO_EXPORT.to_be_bytes()); + data.extend_from_slice(&device.size().to_be_bytes()); + data.extend_from_slice(&TRANSMIT_FLAGS.to_be_bytes()); + opt_reply(s, opt, NBD_REP_INFO, &data) +} + +fn send_info_block_size(s: &mut S, opt: u32) -> io::Result<()> { + let mut data = Vec::with_capacity(14); + data.extend_from_slice(&NBD_INFO_BLOCK_SIZE.to_be_bytes()); + data.extend_from_slice(&1u32.to_be_bytes()); // minimum + data.extend_from_slice(&4096u32.to_be_bytes()); // preferred + data.extend_from_slice(&(4096u32 * 32).to_be_bytes()); // maximum + opt_reply(s, opt, NBD_REP_INFO, &data) +} + +fn opt_reply(s: &mut S, opt: u32, reply_type: u32, data: &[u8]) -> io::Result<()> { + s.write_u64_be(REPLY_MAGIC)?; + s.write_u32_be(opt)?; + s.write_u32_be(reply_type)?; + s.write_u32_be(data.len() as u32)?; + s.write_all(data)?; + s.flush() +} + +// --- Transmission (vi/rust-nbd dispatch, pread-native) --- + +fn transmission(s: &mut S, device: &B) -> io::Result<()> { + let mut buf = vec![0u8; 256 * 1024]; + loop { + let magic = s.read_u32_be()?; + if magic != REQUEST_MAGIC { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "invalid request magic", + )); + } + let _flags = s.read_u16_be()?; + let cmd = s.read_u16_be()?; + let handle = s.read_u64_be()?; + let offset = s.read_u64_be()?; + let length = s.read_u32_be()?; + + match cmd { + NBD_CMD_READ => { + let len = length as usize; + if buf.len() < len { + buf.resize(len, 0); + } + match device.read_at(&mut buf[..len], offset) { + Ok(()) => { + simple_reply(s, 0, handle)?; + s.write_all(&buf[..len])?; + s.flush()?; + } + Err(e) => { + let errno = e.raw_os_error().unwrap_or(5) as u32; + simple_reply(s, errno, handle)?; + s.flush()?; + } + } + } + NBD_CMD_DISC => { + return Ok(()); + } + _ => { + // Read-only: reject WRITE, FLUSH, TRIM, etc. + simple_reply(s, 95, handle)?; // ENOTSUP + s.flush()?; + } + } + } +} + +fn simple_reply(s: &mut S, error: u32, handle: u64) -> io::Result<()> { + s.write_u32_be(SIMPLE_REPLY_MAGIC)?; + s.write_u32_be(error)?; + s.write_u64_be(handle) +} diff --git a/crates/bcvk-nbd/src/regions.rs b/crates/bcvk-nbd/src/regions.rs new file mode 100644 index 000000000..a495bf351 --- /dev/null +++ b/crates/bcvk-nbd/src/regions.rs @@ -0,0 +1,151 @@ +//! Region-based virtual block device composition. +//! Inspired by the regions pattern in nbdkit's floppy plugin (BSD-3-Clause). + +use std::path::PathBuf; +use std::sync::Arc; + +#[derive(Debug, Clone)] +pub enum RegionType { + Data(Arc>), + FilePath { path: PathBuf }, + Zero, +} + +#[derive(Debug, Clone)] +pub struct Region { + pub start: u64, + pub len: u64, + pub region_type: RegionType, +} + +impl Region { + pub fn end(&self) -> u64 { + self.start + self.len + } +} + +pub fn find_region(regions: &[Region], offset: u64) -> Option<&Region> { + regions + .binary_search_by(|r| { + if offset < r.start { + std::cmp::Ordering::Greater + } else if offset >= r.end() { + std::cmp::Ordering::Less + } else { + std::cmp::Ordering::Equal + } + }) + .ok() + .map(|i| ®ions[i]) +} + +const PRELOAD_THRESHOLD: u64 = 4096; +const MERGE_CHUNK_MAX: u64 = 4 * 1024 * 1024; + +pub fn consolidate_regions(regions: Vec) -> Vec { + let mut out: Vec = Vec::new(); + let mut merge_buf: Vec = Vec::new(); + let mut merge_start: u64 = 0; + + for r in regions { + let should_inline = match &r.region_type { + RegionType::FilePath { .. } => r.len <= PRELOAD_THRESHOLD, + RegionType::Data(_) | RegionType::Zero => true, + }; + + if should_inline { + if merge_buf.is_empty() { + merge_start = r.start; + } + let needed = (r.start + r.len - merge_start) as usize; + if needed as u64 > MERGE_CHUNK_MAX && !merge_buf.is_empty() { + out.push(Region { + start: merge_start, + len: merge_buf.len() as u64, + region_type: RegionType::Data(Arc::new(merge_buf.clone())), + }); + merge_buf.clear(); + merge_start = r.start; + } + let offset_in_buf = (r.start - merge_start) as usize; + if merge_buf.len() < offset_in_buf + r.len as usize { + merge_buf.resize(offset_in_buf + r.len as usize, 0); + } + match &r.region_type { + RegionType::Data(data) => { + merge_buf[offset_in_buf..offset_in_buf + r.len as usize] + .copy_from_slice(&data[..r.len as usize]); + } + RegionType::FilePath { path } => { + if let Ok(file) = std::fs::File::open(path) { + use std::os::unix::fs::FileExt; + let _ = file.read_exact_at( + &mut merge_buf[offset_in_buf..offset_in_buf + r.len as usize], + 0, + ); + } + } + RegionType::Zero => { + merge_buf[offset_in_buf..offset_in_buf + r.len as usize].fill(0); + } + } + } else { + if !merge_buf.is_empty() { + out.push(Region { + start: merge_start, + len: merge_buf.len() as u64, + region_type: RegionType::Data(Arc::new(std::mem::take(&mut merge_buf))), + }); + } + out.push(r); + } + } + if !merge_buf.is_empty() { + out.push(Region { + start: merge_start, + len: merge_buf.len() as u64, + region_type: RegionType::Data(Arc::new(merge_buf)), + }); + } + out +} + +pub fn pread(regions: &[Region], buf: &mut [u8], offset: u64) -> std::io::Result<()> { + let mut remaining = buf.len(); + let mut buf_offset = 0; + let mut disk_offset = offset; + + while remaining > 0 { + let region = find_region(regions, disk_offset).ok_or_else(|| { + std::io::Error::new( + std::io::ErrorKind::InvalidInput, + format!("offset {} outside disk", disk_offset), + ) + })?; + + let region_offset = disk_offset - region.start; + let avail = (region.len - region_offset) as usize; + let len = remaining.min(avail); + + match ®ion.region_type { + RegionType::Data(data) => { + let start = region_offset as usize; + buf[buf_offset..buf_offset + len].copy_from_slice(&data[start..start + len]); + } + RegionType::FilePath { path } => { + use std::os::unix::fs::FileExt; + let file = std::fs::File::open(path)?; + file.read_exact_at(&mut buf[buf_offset..buf_offset + len], region_offset)?; + } + RegionType::Zero => { + buf[buf_offset..buf_offset + len].fill(0); + } + } + + remaining -= len; + buf_offset += len; + disk_offset += len as u64; + } + + Ok(()) +} diff --git a/crates/kit/bcvk-nbd-aarch64 b/crates/kit/bcvk-nbd-aarch64 new file mode 100644 index 0000000000000000000000000000000000000000..4285762d0b5f513c8350fbf0f5bf9a6588700325 GIT binary patch literal 518296 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�%rs=GB=JwC8(LMUW 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

Jdx_tH2_#0nGgcdcd*Q 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

5=^w~Rr-F@X)B>8Ew-(6+LA0ecOd7c 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%0Ds%3_AoZ=5s@9*8^-=2y*ICX#a`u~ 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
af7~~8NP87~mZj`VB3UN) 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 jM&6f1?$cS1RnCaqH%04@ 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

YU@bErwY0+^RNae_wh|}Yk>ba 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 AuK)l5 literal 0 HcmV?d00001 diff --git a/crates/kit/src/ephemeral_macos.rs b/crates/kit/src/ephemeral_macos.rs index 97d599ec7..154bfeaa9 100644 --- a/crates/kit/src/ephemeral_macos.rs +++ b/crates/kit/src/ephemeral_macos.rs @@ -158,8 +158,8 @@ fn cmd_rm_all(force: bool) -> Result<()> { std::thread::sleep(std::time::Duration::from_millis(100)); } } - if let Some(ref container) = vm.nbd_container { - crate::nbdkit_macos::stop_nbdkit_container(container); + if let Some(ref name) = vm.nbd_container { + crate::nbd_macos::stop_nbd_server(name, vm.nbd_port); } EphemeralVmMetadata::remove(&vm.name); println!("Removed {}", vm.name); @@ -167,30 +167,33 @@ fn cmd_rm_all(force: bool) -> Result<()> { // Sweep orphaned resources inside podman machine if let Ok(machine) = run_ephemeral_macos::detect_machine_name() { - // Remove orphaned nbdkit containers - let _ = Command::new("podman") + // Stop orphaned bcvk-nbd systemd units + if let Err(e) = Command::new("podman") .args([ "machine", "ssh", &machine, "--", - "podman", - "rm", - "-f", - "--filter", - "name=bcvk-nbd-", + "bash", "-c", + "for u in $(systemctl list-units --plain --no-legend 'bcvk-nbd-*' 2>/dev/null | awk '{print $1}'); do systemctl stop $u 2>/dev/null; systemctl reset-failed $u 2>/dev/null; done", ]) .stdout(Stdio::null()) .stderr(Stdio::null()) - .status(); + .status() + { + tracing::debug!("failed to stop orphaned nbd units: {}", e); + } // Unmount any remaining container image overlays - let _ = Command::new("podman") + if let Err(e) = Command::new("podman") .args([ "machine", "ssh", &machine, "--", "podman", "image", "umount", "--all", ]) .stdout(Stdio::null()) .stderr(Stdio::null()) - .status(); + .status() + { + tracing::debug!("failed to unmount container images: {}", e); + } } Ok(()) } diff --git a/crates/kit/src/lib.rs b/crates/kit/src/lib.rs index f98fce92e..db14bcf6d 100644 --- a/crates/kit/src/lib.rs +++ b/crates/kit/src/lib.rs @@ -14,7 +14,7 @@ pub mod kernel; // macOS-only modules (vfkit backend) #[cfg(target_os = "macos")] -pub mod nbdkit_macos; +pub mod nbd_macos; #[cfg(target_os = "macos")] pub mod run_ephemeral_macos; diff --git a/crates/kit/src/main.rs b/crates/kit/src/main.rs index efa91d614..4078ac2c9 100644 --- a/crates/kit/src/main.rs +++ b/crates/kit/src/main.rs @@ -65,7 +65,7 @@ mod varlink_ipc; #[cfg(target_os = "macos")] mod ephemeral_macos; #[cfg(target_os = "macos")] -mod nbdkit_macos; +mod nbd_macos; #[cfg(target_os = "macos")] mod run_ephemeral_macos; #[cfg(target_os = "macos")] diff --git a/crates/kit/src/nbd_macos.rs b/crates/kit/src/nbd_macos.rs new file mode 100644 index 000000000..9d99ba1cb --- /dev/null +++ b/crates/kit/src/nbd_macos.rs @@ -0,0 +1,148 @@ +//! NBD server management for macOS ephemeral VMs. +//! +//! macOS-specific: gvproxy expose API for TCP port forwarding. +//! Common logic (deploy, systemd-run, stop) lives in vm_helpers.rs. + +use color_eyre::{eyre::bail, Result}; +use std::time::Duration; +use tracing::info; + +use crate::vm_helpers; + +/// NBD server binary (aarch64 ELF), embedded at compile time. +const NBD_SERVER: &[u8] = include_bytes!("../bcvk-nbd-aarch64"); + +/// Deploy the NBD server binary to the podman machine. +pub(crate) fn deploy_nbd_server(machine: &str) -> Result<()> { + vm_helpers::deploy_nbd_server(machine, NBD_SERVER) +} + +/// Start the NBD server via systemd-run and expose the port via gvproxy. +#[allow(dead_code)] +pub(crate) fn start_nbd_server( + machine: &str, + merged_path: &str, + cmdline: &str, + ssh_pubkey: &str, + nbd_port: u16, + vm_name: &str, +) -> Result { + let unit_name = format!("bcvk-nbd-{}", vm_name); + + vm_helpers::start_nbd_unit( + machine, + &unit_name, + merged_path, + cmdline, + ssh_pubkey, + &format!("--port {}", nbd_port), + )?; + + // macOS-specific: unexpose stale entry then expose via gvproxy's in-VM API + let unexpose_cmd = format!( + "curl -s -X POST http://192.168.127.1:80/services/forwarder/unexpose \ + -H 'Content-Type: application/json' \ + -d '{{\"local\":\":{nbd_port}\",\"protocol\":\"tcp\"}}' >/dev/null 2>&1; true", + nbd_port = nbd_port, + ); + if let Err(e) = vm_helpers::machine_ssh(machine, &unexpose_cmd) { + tracing::debug!("failed to unexpose port {}: {}", nbd_port, e); + } + + let expose_cmd = format!( + "curl -s -X POST http://192.168.127.1:80/services/forwarder/expose \ + -H 'Content-Type: application/json' \ + -d '{{\"local\":\":{nbd_port}\",\"remote\":\"192.168.127.2:{nbd_port}\",\"protocol\":\"tcp\"}}'", + nbd_port = nbd_port, + ); + let mut exposed = false; + for i in 0..5 { + if let Ok(output) = vm_helpers::machine_ssh_output(machine, &expose_cmd) { + if output.status.success() { + exposed = true; + break; + } + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); + tracing::debug!( + "gvproxy expose attempt {}: {}{}", + i + 1, + stdout.trim(), + stderr.trim() + ); + } + std::thread::sleep(Duration::from_millis(500)); + } + if !exposed { + bail!("gvproxy expose failed for port {}", nbd_port); + } + + info!("waiting for nbd server on port {}...", nbd_port); + loop { + if let Ok(mut stream) = std::net::TcpStream::connect_timeout( + &std::net::SocketAddr::from(([127, 0, 0, 1], nbd_port)), + Duration::from_millis(500), + ) { + use std::io::Read; + stream.set_read_timeout(Some(Duration::from_secs(2))).ok(); + let mut buf = [0u8; 8]; + if stream.read_exact(&mut buf).is_ok() && &buf == b"NBDMAGIC" { + break; + } + } + if vm_helpers::is_nbd_unit_dead(machine, &unit_name) { + bail!( + "nbd server '{}' died before becoming ready on port {}", + unit_name, + nbd_port + ); + } + std::thread::sleep(Duration::from_millis(500)); + } + + Ok(unit_name) +} + +/// Find an available TCP port for NBD in range 10800-10900. +pub fn find_available_nbd_port() -> u16 { + use rand::Rng; + let mut rng = rand::rng(); + const PORT_RANGE_START: u16 = 10800; + const PORT_RANGE_END: u16 = 10900; + for _ in 0..100 { + let port = rng.random_range(PORT_RANGE_START..PORT_RANGE_END); + if std::net::TcpListener::bind(("127.0.0.1", port)).is_ok() { + return port; + } + } + for port in PORT_RANGE_START..PORT_RANGE_END { + if std::net::TcpListener::bind(("127.0.0.1", port)).is_ok() { + return port; + } + } + PORT_RANGE_START +} + +/// Stop an NBD server and unexpose its gvproxy port (best-effort). +pub fn stop_nbd_server(unit_name: &str, nbd_port: Option) { + if let Ok(machine) = vm_helpers::detect_machine_name() { + vm_helpers::stop_nbd_unit(&machine, unit_name); + // macOS-specific: unexpose gvproxy port + if let Some(port) = nbd_port { + if let Err(e) = vm_helpers::machine_ssh( + &machine, + &format!( + "curl -sf -X POST http://192.168.127.1:80/services/forwarder/unexpose \ + -H 'Content-Type: application/json' \ + -d '{{\"local\":\":{}\",\"protocol\":\"tcp\"}}'", + port + ), + ) { + tracing::debug!("failed to unexpose port {}: {}", port, e); + } + } + } +} + +/// Re-export for run_ephemeral_macos.rs +pub use vm_helpers::get_merged_path; diff --git a/crates/kit/src/nbdkit_macos.rs b/crates/kit/src/nbdkit_macos.rs deleted file mode 100644 index 226d8c147..000000000 --- a/crates/kit/src/nbdkit_macos.rs +++ /dev/null @@ -1,230 +0,0 @@ -//! nbdkit EROFS plugin management for macOS ephemeral VMs. - -use color_eyre::{ - eyre::{bail, Context}, - Result, -}; -use std::process::{Command, Stdio}; -use std::time::Duration; -use tracing::info; - -use crate::vm_helpers::detect_machine_name; - -/// EROFS plugin shared library, embedded at compile time. -const EROFS_PLUGIN_SO: &[u8] = include_bytes!("../nbdkit-erofs-plugin-aarch64.so"); - -fn shell_escape(s: &str) -> String { - format!("'{}'", s.replace('\'', "'\\''")) -} - -/// Get the merged overlay path from podman image mount. -pub(crate) fn get_merged_path(machine: &str, rootful: bool, image: &str) -> Result { - let output = if rootful { - Command::new("podman") - .args([ - "machine", "ssh", machine, "--", "podman", "image", "mount", image, - ]) - .output() - .context("podman image mount")? - } else { - Command::new("podman") - .args([ - "machine", "ssh", machine, "--", "podman", "unshare", "podman", "image", "mount", - image, - ]) - .output() - .context("podman image mount")? - }; - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - bail!("podman image mount failed: {}", stderr.trim()); - } - Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) -} - -/// Ensure the nbdkit container image exists in podman machine. -/// On first run, transfers embedded .so and builds container image. -pub(crate) fn ensure_nbdkit_ready(machine: &str) -> Result<()> { - let script = crate::vm_helpers::nbdkit_setup_script(EROFS_PLUGIN_SO); - info!("checking nbdkit container image..."); - let mut child = Command::new("podman") - .args(["machine", "ssh", machine, "--", "bash", "-s"]) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .context("nbdkit setup in podman machine")?; - if let Some(mut stdin) = child.stdin.take() { - use std::io::Write; - stdin.write_all(script.as_bytes())?; - } - let output = child.wait_with_output()?; - if !output.status.success() { - bail!( - "nbdkit setup failed: {}", - String::from_utf8_lossy(&output.stderr).trim() - ); - } - Ok(()) -} - -#[allow(dead_code)] -pub(crate) fn start_nbdkit_erofs_plugin( - machine: &str, - merged_path: &str, - cmdline: &str, - ssh_pubkey: &str, - nbd_port: u16, - vm_name: &str, -) -> Result { - let container_name = format!("bcvk-nbd-{}", vm_name); - - let _ = Command::new("podman") - .args([ - "machine", - "ssh", - machine, - "--", - "podman", - "rm", - "-f", - &container_name, - ]) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status(); - - let cmdline_esc = shell_escape(&format!("cmdline={}", cmdline)); - let dir_esc = shell_escape(&format!("dir={}", merged_path)); - - let mut ssh_param = String::new(); - if !ssh_pubkey.is_empty() { - ssh_param = format!(" {}", shell_escape(&format!("ssh_pubkey={}", ssh_pubkey))); - } - - let podman_cmd = format!( - "podman run -d --name {name} --security-opt label=disable \ - -p {port}:10809 \ - -v {merged}:{merged}:ro \ - {image} \ - nbdkit -f --threads 4 -p 10809 -r /plugin.so \ - {dir} {cmdline}{ssh}", - name = container_name, - port = nbd_port, - merged = merged_path, - image = crate::vm_helpers::NBDKIT_IMAGE, - dir = dir_esc, - cmdline = cmdline_esc, - ssh = ssh_param, - ); - - let output = Command::new("podman") - .args(["machine", "ssh", machine, "--", &podman_cmd]) - .output() - .context("failed to start nbdkit erofs plugin")?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - bail!("failed to start nbdkit erofs plugin: {}", stderr.trim()); - } - - info!("waiting for nbdkit on port {}...", nbd_port); - loop { - if let Ok(mut stream) = std::net::TcpStream::connect_timeout( - &std::net::SocketAddr::from(([127, 0, 0, 1], nbd_port)), - Duration::from_millis(500), - ) { - use std::io::Read; - stream.set_read_timeout(Some(Duration::from_secs(2))).ok(); - let mut buf = [0u8; 8]; - if stream.read_exact(&mut buf).is_ok() && &buf == b"NBDMAGIC" { - break; - } - } - // Check if container is still alive (no fixed timeout — wait as long - // as plugin_get_ready() is running, which scans the entire overlay - // directory and scales with image size) - let ps_output = Command::new("podman") - .args([ - "machine", - "ssh", - machine, - "--", - "podman", - "ps", - "-a", - "--filter", - &format!("name=^{}$", container_name), - "--format", - "{{.Status}}", - ]) - .output(); - if let Ok(out) = &ps_output { - let stdout = String::from_utf8_lossy(&out.stdout); - if stdout.contains("Exited") { - let _ = Command::new("podman") - .args([ - "machine", - "ssh", - machine, - "--", - "podman", - "rm", - "-f", - &container_name, - ]) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status(); - bail!( - "nbdkit container '{}' exited before becoming ready on port {}", - container_name, - nbd_port - ); - } - } - std::thread::sleep(Duration::from_millis(500)); - } - - Ok(container_name) -} - -/// Find an available TCP port for NBD in range 10800-10900. -pub fn find_available_nbd_port() -> u16 { - use rand::Rng; - let mut rng = rand::rng(); - const PORT_RANGE_START: u16 = 10800; - const PORT_RANGE_END: u16 = 10900; - for _ in 0..100 { - let port = rng.random_range(PORT_RANGE_START..PORT_RANGE_END); - if std::net::TcpListener::bind(("127.0.0.1", port)).is_ok() { - return port; - } - } - for port in PORT_RANGE_START..PORT_RANGE_END { - if std::net::TcpListener::bind(("127.0.0.1", port)).is_ok() { - return port; - } - } - PORT_RANGE_START -} - -/// Stop and remove an nbdkit container (best-effort). -pub fn stop_nbdkit_container(container_name: &str) { - if let Ok(machine) = detect_machine_name() { - let _ = Command::new("podman") - .args([ - "machine", - "ssh", - &machine, - "--", - "podman", - "rm", - "-f", - container_name, - ]) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status(); - } -} diff --git a/crates/kit/src/run_ephemeral_macos.rs b/crates/kit/src/run_ephemeral_macos.rs index 156de0517..4bd2603e3 100644 --- a/crates/kit/src/run_ephemeral_macos.rs +++ b/crates/kit/src/run_ephemeral_macos.rs @@ -83,7 +83,7 @@ impl EphemeralVmMetadata { /// Remove metadata file for the named VM. pub fn remove(name: &str) { let path = Self::vms_dir().join(format!("{}.json", name)); - let _ = fs::remove_file(path); + crate::vm_helpers::remove_file_if_exists(&path); } /// Load metadata for the named VM from its JSON file. @@ -164,6 +164,7 @@ struct VmCleanup { vfkit_pid: u32, gvproxy_pid: u32, nbd_container: Option, + nbd_port: Option, image: String, vm_name: String, } @@ -172,7 +173,7 @@ impl Drop for VmCleanup { fn drop(&mut self) { tracing::debug!("cleaning up VM processes..."); if let Some(ref name) = self.nbd_container { - crate::nbdkit_macos::stop_nbdkit_container(name); + crate::nbd_macos::stop_nbd_server(name, self.nbd_port); } if let Err(e) = rustix::process::kill_process( rustix::process::Pid::from_raw(self.vfkit_pid as i32).unwrap(), @@ -188,7 +189,7 @@ impl Drop for VmCleanup { } // Release container image overlay mount if let Ok(machine) = detect_machine_name() { - let _ = Command::new("podman") + if let Err(e) = Command::new("podman") .args([ "machine", "ssh", @@ -201,7 +202,10 @@ impl Drop for VmCleanup { ]) .stdout(Stdio::null()) .stderr(Stdio::null()) - .status(); + .status() + { + tracing::debug!("failed to umount image {}: {}", self.image, e); + } } EphemeralVmMetadata::remove(&self.vm_name); } @@ -253,8 +257,8 @@ fn run_vfkit(opts: RunEphemeralOpts) -> Result<()> { let mut ssh_pubkey = String::new(); if opts.ssh_keygen || !opts.execute.is_empty() { info!("generating SSH keypair..."); - let _ = fs::remove_file(&ssh_key_path); - let _ = fs::remove_file(ssh_key_path.with_extension("pub")); + crate::vm_helpers::remove_file_if_exists(&ssh_key_path); + crate::vm_helpers::remove_file_if_exists(&ssh_key_path.with_extension("pub")); let status = Command::new("ssh-keygen") .args([ "-t", @@ -289,16 +293,16 @@ fn run_vfkit(opts: RunEphemeralOpts) -> Result<()> { cmdline_parts.extend(&user_args); let cmdline = cmdline_parts.join(" "); - // Ensure nbdkit container image is ready (auto-build on first run) - crate::nbdkit_macos::ensure_nbdkit_ready(&machine)?; + // Deploy NBD server binary to podman machine (hash-checked, idempotent) + crate::nbd_macos::deploy_nbd_server(&machine)?; // Get container image merged overlay path - let merged_path = crate::nbdkit_macos::get_merged_path(&machine, rootful, &opts.image)?; + let merged_path = crate::nbd_macos::get_merged_path(&machine, rootful, &opts.image)?; info!("overlay merged: {}", merged_path); - let nbd_port = crate::nbdkit_macos::find_available_nbd_port(); + let nbd_port = crate::nbd_macos::find_available_nbd_port(); info!("NBD transport: TCP (port {})", nbd_port); - let nbd_container_name = crate::nbdkit_macos::start_nbdkit_erofs_plugin( + let nbd_container_name = crate::nbd_macos::start_nbd_server( &machine, &merged_path, &cmdline, @@ -410,6 +414,7 @@ fn run_vfkit(opts: RunEphemeralOpts) -> Result<()> { vfkit_pid: vfkit_child.id(), gvproxy_pid: gvproxy_child.id(), nbd_container: Some(nbd_container_name.clone()), + nbd_port: Some(nbd_port), image: opts.image.clone(), vm_name: vm_name.clone(), }; @@ -463,12 +468,12 @@ fn run_vfkit(opts: RunEphemeralOpts) -> Result<()> { std::mem::forget(_cleanup); let status = vfkit_child.wait()?; info!("vfkit exited: {}", status); - crate::nbdkit_macos::stop_nbdkit_container(&nbd_container_name); + crate::nbd_macos::stop_nbd_server(&nbd_container_name, Some(nbd_port)); if let Err(e) = gvproxy_child.kill() { tracing::debug!("failed to kill gvproxy: {}", e); } // Release container image overlay mount - let _ = Command::new("podman") + if let Err(e) = Command::new("podman") .args([ "machine", "ssh", @@ -481,7 +486,10 @@ fn run_vfkit(opts: RunEphemeralOpts) -> Result<()> { ]) .stdout(Stdio::null()) .stderr(Stdio::null()) - .status(); + .status() + { + tracing::debug!("failed to umount image {}: {}", opts.image, e); + } EphemeralVmMetadata::remove(&vm_name); Ok(()) } @@ -547,11 +555,14 @@ fn run_detached(opts: &RunEphemeralOpts) -> Result<()> { /// `security.selinux` or `user.containers.override_stat` that are added /// by podman/buildah when creating images inside containers. pub fn clear_xattr(path: &Path) { - let _ = Command::new("xattr") + if let Err(e) = Command::new("xattr") .args(["-c", &path.to_string_lossy()]) .stdout(Stdio::null()) .stderr(Stdio::null()) - .status(); + .status() + { + tracing::debug!("failed to clear xattr on {}: {}", path.display(), e); + } } /// Find the vfkit binary, checking PATH and Podman PKG location. @@ -593,8 +604,8 @@ fn find_gvproxy() -> Result { /// Start a gvproxy instance with the given socket paths. pub fn start_gvproxy(gvproxy_sock: &str, services_sock: &str) -> Result { let gvproxy_bin = find_gvproxy()?; - let _ = fs::remove_file(gvproxy_sock); - let _ = fs::remove_file(services_sock); + crate::vm_helpers::remove_file_if_exists(std::path::Path::new(gvproxy_sock)); + crate::vm_helpers::remove_file_if_exists(std::path::Path::new(services_sock)); let child = Command::new(&gvproxy_bin) .args([ "-listen-vfkit", @@ -641,7 +652,9 @@ pub fn expose_port( std::io::Write::write_all(&mut stream, request.as_bytes())?; std::io::Write::flush(&mut stream)?; let mut response = vec![0u8; 1024]; - let _ = std::io::Read::read(&mut stream, &mut response); + if let Err(e) = std::io::Read::read(&mut stream, &mut response) { + tracing::debug!("failed to read gvproxy response: {}", e); + } let response_str = String::from_utf8_lossy(&response); if !response_str.contains("200") { bail!( diff --git a/crates/kit/src/vfkit/inspect.rs b/crates/kit/src/vfkit/inspect.rs index a539c2422..cbcc445b6 100644 --- a/crates/kit/src/vfkit/inspect.rs +++ b/crates/kit/src/vfkit/inspect.rs @@ -63,13 +63,12 @@ pub fn run(opts: VmInspectOpts) -> Result<()> { println!(); println!("SSH:"); println!(" Port: {}", meta.ssh_port); - println!(" User: {}", meta.ssh_user); println!(" Key: {}", meta.ssh_key); if state == "running" { println!(); println!( - " ssh -p {} -i {} {}@localhost", - meta.ssh_port, meta.ssh_key, meta.ssh_user + " ssh -p {} -i {} root@localhost", + meta.ssh_port, meta.ssh_key ); } if !meta.labels.is_empty() { diff --git a/crates/kit/src/vfkit/list.rs b/crates/kit/src/vfkit/list.rs index 397856573..6ba07e108 100644 --- a/crates/kit/src/vfkit/list.rs +++ b/crates/kit/src/vfkit/list.rs @@ -56,8 +56,8 @@ pub fn run(opts: VmListOpts) -> Result<()> { for vm in &vms { let state = if vm.is_alive() { "running" } else { "stopped" }; println!( - "{:<20} {:<10} {:<30} ssh -p {} -i {} {}@localhost", - vm.name, state, vm.disk_image, vm.ssh_port, vm.ssh_key, vm.ssh_user + "{:<20} {:<10} {:<30} :{}", + vm.name, state, vm.disk_image, vm.ssh_port ); } } diff --git a/crates/kit/src/vfkit/mod.rs b/crates/kit/src/vfkit/mod.rs index 347f242d5..0a09cbae1 100644 --- a/crates/kit/src/vfkit/mod.rs +++ b/crates/kit/src/vfkit/mod.rs @@ -99,8 +99,6 @@ pub struct VmMetadata { pub ssh_port: u16, /// Path to the SSH private key. pub ssh_key: String, - /// SSH username for connecting to the VM. - pub ssh_user: String, /// Number of vCPUs allocated. pub vcpus: u32, /// Memory in megabytes. @@ -150,7 +148,7 @@ impl VmMetadata { /// Remove metadata file for the named VM. pub fn remove(name: &str) { let path = Self::vms_dir().join(format!("{}.json", name)); - let _ = fs::remove_file(path); + crate::vm_helpers::remove_file_if_exists(&path); } /// List all persistent VM metadata from the VMs directory. @@ -199,7 +197,6 @@ mod tests { gvproxy_pid: 0, ssh_port: 2222, ssh_key: "/tmp/key".to_string(), - ssh_user: "root".to_string(), vcpus: 2, memory_mb: 4096, efi_store: "/tmp/efi.fd".to_string(), @@ -221,7 +218,6 @@ mod tests { assert_eq!(loaded.disk_image, "/tmp/disk.raw"); assert_eq!(loaded.vcpus, 2); assert_eq!(loaded.memory_mb, 4096); - assert_eq!(loaded.ssh_user, "root"); assert_eq!(loaded.state, "running"); assert!(!loaded.gui); } diff --git a/crates/kit/src/vfkit/run.rs b/crates/kit/src/vfkit/run.rs index 5fadf89f9..95273cd3d 100644 --- a/crates/kit/src/vfkit/run.rs +++ b/crates/kit/src/vfkit/run.rs @@ -71,9 +71,6 @@ pub struct VmRunOpts { /// Path to an existing SSH private key #[clap(long)] pub ssh_key: Option, - /// SSH username (default: root) - #[clap(long, default_value = "root")] - pub ssh_user: String, /// SSH port (default: auto-allocate) #[clap(long)] pub ssh_port: Option, @@ -327,7 +324,7 @@ pub fn run(opts: VmRunOpts) -> Result<()> { } let key_path = std::path::Path::new(&ssh_key_path); - wait_for_ssh(ssh_port, key_path, &opts.ssh_user)?; + wait_for_ssh(ssh_port, key_path, "root")?; let metadata = VmMetadata { name: vm_name.clone(), @@ -337,7 +334,6 @@ pub fn run(opts: VmRunOpts) -> Result<()> { gvproxy_pid: gvproxy_child.id(), ssh_port, ssh_key: ssh_key_path.clone(), - ssh_user: opts.ssh_user.clone(), vcpus, memory_mb, efi_store: efi_store.to_string_lossy().to_string(), @@ -357,7 +353,7 @@ pub fn run(opts: VmRunOpts) -> Result<()> { println!("VM '{}' is running", vm_name); println!( " ssh -p {} -i {} {}@localhost", - ssh_port, ssh_key_path, opts.ssh_user + ssh_port, ssh_key_path, "root" ); println!(); println!("To connect: bcvk vm ssh {}", vm_name); @@ -368,7 +364,7 @@ pub fn run(opts: VmRunOpts) -> Result<()> { return Ok(()); } if opts.ssh { - let status = run_ssh_interactive(ssh_port, key_path, &opts.ssh_user)?; + let status = run_ssh_interactive(ssh_port, key_path, "root")?; std::process::exit(status.code().unwrap_or(1)); } diff --git a/crates/kit/src/vfkit/ssh.rs b/crates/kit/src/vfkit/ssh.rs index 84527e8a8..ec5b08773 100644 --- a/crates/kit/src/vfkit/ssh.rs +++ b/crates/kit/src/vfkit/ssh.rs @@ -10,6 +10,9 @@ use color_eyre::{eyre::bail, Result}; pub struct VmSshOpts { /// VM name pub name: String, + /// SSH username to use for connection (defaults to 'root') + #[clap(long, default_value = "root")] + pub user: String, /// Additional SSH arguments #[clap(trailing_var_arg = true, allow_hyphen_values = true)] pub args: Vec, @@ -23,11 +26,11 @@ pub fn run(opts: VmSshOpts) -> Result<()> { } let key_path = std::path::Path::new(&vm.ssh_key); if opts.args.is_empty() { - run_ssh_interactive(vm.ssh_port, key_path, &vm.ssh_user)?; + run_ssh_interactive(vm.ssh_port, key_path, &opts.user)?; } else { let cmd = shlex::try_join(opts.args.iter().map(|s| s.as_str())) .map_err(|e| color_eyre::eyre::eyre!("failed to escape SSH args: {}", e))?; - let status = run_ssh_command(vm.ssh_port, key_path, &vm.ssh_user, &cmd)?; + let status = run_ssh_command(vm.ssh_port, key_path, &opts.user, &cmd)?; std::process::exit(status.code().unwrap_or(1)); } Ok(()) diff --git a/crates/kit/src/vfkit/start.rs b/crates/kit/src/vfkit/start.rs index 4e97c2e30..67975463d 100644 --- a/crates/kit/src/vfkit/start.rs +++ b/crates/kit/src/vfkit/start.rs @@ -107,7 +107,7 @@ pub fn run(opts: VmStartOpts) -> Result<()> { } let key_path = std::path::Path::new(&meta.ssh_key); - wait_for_ssh(meta.ssh_port, key_path, &meta.ssh_user)?; + wait_for_ssh(meta.ssh_port, key_path, &"root")?; meta.vfkit_pid = vfkit_child.id(); meta.gvproxy_pid = gvproxy_child.id(); @@ -118,11 +118,11 @@ pub fn run(opts: VmStartOpts) -> Result<()> { println!("Started '{}'", meta.name); println!( " ssh -p {} -i {} {}@localhost", - meta.ssh_port, meta.ssh_key, meta.ssh_user + meta.ssh_port, meta.ssh_key, "root" ); if opts.ssh { - let status = run_ssh_interactive(meta.ssh_port, key_path, &meta.ssh_user)?; + let status = run_ssh_interactive(meta.ssh_port, key_path, &"root")?; std::process::exit(status.code().unwrap_or(1)); } diff --git a/crates/kit/src/vm_helpers.rs b/crates/kit/src/vm_helpers.rs index 0c03157d0..c5fdf5d4d 100644 --- a/crates/kit/src/vm_helpers.rs +++ b/crates/kit/src/vm_helpers.rs @@ -123,11 +123,11 @@ pub fn wait_for_ssh(port: u16, key_path: &Path, user: &str) -> Result<()> { } } let backoff = if attempt < 2 { - 500 + 100 } else if attempt < 4 { - 1000 + 200 } else { - 2000 + 500 }; std::thread::sleep(Duration::from_millis(backoff)); attempt += 1; @@ -263,32 +263,182 @@ pub fn parse_size(size_str: &str) -> Result { Ok(num * multiplier) } -/// Container image name for the nbdkit EROFS plugin. -pub const NBDKIT_IMAGE: &str = "localhost/bcvk-nbdkit:latest"; +// --- NBD server helpers (shared by macOS/Windows) --- -/// Generate a shell script that checks for and builds the nbdkit container image. -/// -/// The caller provides the plugin `.so` binary via `plugin_so` (typically from -/// `include_bytes!` in a platform-specific module). The script: -/// 1. Checks if the image already exists (early exit if so) -/// 2. Writes the `.so` to a temp path via base64 -/// 3. Builds a container image with nbdkit + the plugin baked in -/// 4. Cleans up the temp file -pub fn nbdkit_setup_script(plugin_so: &[u8]) -> String { +/// Shell-escape a string for safe embedding in shell commands. +pub fn shell_escape(s: &str) -> String { + format!("'{}'", s.replace('\'', "'\\''")) +} + +/// Compute a fast hash of binary data for deployment change detection. +pub fn binary_hash(data: &[u8]) -> String { + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + let mut h = DefaultHasher::new(); + data.hash(&mut h); + let hash1 = h.finish(); + data.len().hash(&mut h); + let hash2 = h.finish(); + format!("{:016x}{:016x}", hash1, hash2) +} + +/// Run a command inside the podman machine via SSH (best-effort, no output). +pub fn machine_ssh(machine: &str, cmd: &str) -> Result<()> { + let status = Command::new("podman") + .args(["machine", "ssh", machine, "--", cmd]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .context("podman machine ssh")?; + if !status.success() { + bail!("machine ssh command failed"); + } + Ok(()) +} + +/// Run a command inside the podman machine via SSH and capture output. +pub fn machine_ssh_output(machine: &str, cmd: &str) -> Result { + Command::new("podman") + .args(["machine", "ssh", machine, "--", cmd]) + .output() + .context("podman machine ssh") +} + +/// Get the merged overlay path from podman image mount. +pub fn get_merged_path(machine: &str, rootful: bool, image: &str) -> Result { + let output = if rootful { + Command::new("podman") + .args([ + "machine", "ssh", machine, "--", "podman", "image", "mount", image, + ]) + .output() + .context("podman image mount")? + } else { + Command::new("podman") + .args([ + "machine", "ssh", machine, "--", "podman", "unshare", "podman", "image", "mount", + image, + ]) + .output() + .context("podman image mount")? + }; + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + bail!("podman image mount failed: {}", stderr.trim()); + } + Ok(String::from_utf8_lossy(&output.stdout).trim().to_string()) +} + +/// Deploy the NBD server binary to the podman machine (idempotent, hash-checked). +pub fn deploy_nbd_server(machine: &str, binary: &[u8]) -> Result<()> { use base64::Engine; - let b64 = base64::engine::general_purpose::STANDARD.encode(plugin_so); - format!( + let hash = binary_hash(binary); + let b64 = base64::engine::general_purpose::STANDARD.encode(binary); + let script = format!( "set -e; \ - if podman image exists {image}; then exit 0; fi; \ mkdir -p /var/tmp/bcvk; \ - printf '%s' '{b64}' | base64 -d > /var/tmp/bcvk/plugin.so; \ - printf 'FROM quay.io/fedora/fedora:latest\\nRUN dnf install -y nbdkit nbdkit-basic-plugins && dnf clean all\\nCOPY plugin.so /plugin.so\\n' | \ - podman build -t {image} -f - /var/tmp/bcvk; \ - rm -f /var/tmp/bcvk/plugin.so", - image = NBDKIT_IMAGE, + H=/var/tmp/bcvk/bcvk-nbd.sha256; \ + if [ -f \"$H\" ] && [ \"$(cat \"$H\")\" = '{hash}' ]; then exit 0; fi; \ + printf '%s' '{b64}' | base64 -d > /var/tmp/bcvk/bcvk-nbd; \ + chmod +x /var/tmp/bcvk/bcvk-nbd; \ + chcon -t bin_t /var/tmp/bcvk/bcvk-nbd 2>/dev/null || true; \ + printf '{hash}' > \"$H\"", + hash = hash, b64 = b64, - ) + ); + info!("deploying nbd server to podman machine..."); + let mut child = Command::new("podman") + .args(["machine", "ssh", machine, "--", "bash", "-s"]) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .context("nbd server deploy to podman machine")?; + if let Some(mut stdin) = child.stdin.take() { + use std::io::Write; + stdin.write_all(script.as_bytes())?; + } + let output = child.wait_with_output()?; + if !output.status.success() { + bail!( + "nbd server deploy failed: {}", + String::from_utf8_lossy(&output.stderr).trim() + ); + } + Ok(()) +} + +/// Start the NBD server as a systemd-run unit inside the podman machine. +/// +/// `listen_arg` controls the transport: `"--port {nbd_port}"` for TCP (macOS) +/// or `"--vsock --port {vsock_port}"` for vsock (Windows). +pub fn start_nbd_unit( + machine: &str, + unit_name: &str, + merged_path: &str, + cmdline: &str, + ssh_pubkey: &str, + listen_arg: &str, +) -> Result<()> { + if let Err(e) = machine_ssh( + machine, + &format!( + "systemctl stop {u} 2>/dev/null; systemctl reset-failed {u} 2>/dev/null", + u = unit_name + ), + ) { + tracing::debug!("pre-cleanup of unit {} failed: {}", unit_name, e); + } + + let cmdline_esc = shell_escape(cmdline); + let mut ssh_args = String::new(); + if !ssh_pubkey.is_empty() { + ssh_args = format!(" --ssh-pubkey {}", shell_escape(ssh_pubkey)); + } + + let start_cmd = format!( + "systemd-run --unit={unit} --service-type=simple --quiet \ + --property=LimitNOFILE=524288 \ + /var/tmp/bcvk/bcvk-nbd {listen} --dir {merged} \ + --cmdline {cmdline}{ssh}", + unit = unit_name, + listen = listen_arg, + merged = merged_path, + cmdline = cmdline_esc, + ssh = ssh_args, + ); + let output = machine_ssh_output(machine, &start_cmd)?; + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + bail!("failed to start nbd server: {}", stderr.trim()); + } + Ok(()) +} + +/// Stop an NBD server systemd-run unit (best-effort). +pub fn stop_nbd_unit(machine: &str, unit_name: &str) { + if let Err(e) = machine_ssh( + machine, + &format!( + "systemctl stop {u} 2>/dev/null; systemctl reset-failed {u} 2>/dev/null", + u = unit_name + ), + ) { + tracing::debug!("stop_nbd_unit failed for {}: {}", unit_name, e); + } } + +/// Check if a systemd-run unit has died. +pub fn is_nbd_unit_dead(machine: &str, unit_name: &str) -> bool { + if let Ok(out) = machine_ssh_output(machine, &format!("systemctl is-active {}", unit_name)) { + let stdout = String::from_utf8_lossy(&out.stdout); + let state = stdout.trim(); + state == "inactive" || state == "failed" + } else { + false + } +} + #[cfg(test)] mod tests { use super::*; From 692c8c0b466289d660bad198c7613dc718e852a8 Mon Sep 17 00:00:00 2001 From: Shion Tanaka Date: Wed, 10 Jun 2026 17:47:20 +0900 Subject: [PATCH 6/6] =?UTF-8?q?macOS:=20review=20feedback=20=E2=80=94=20po?= =?UTF-8?q?lling=20helpers,=20keygen=20dedup,=20constants,=20script=20exte?= =?UTF-8?q?rnalization?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace polling loops with wait_for_readiness() in nbd_macos.rs - Deduplicate SSH keygen with vm_helpers::generate_ssh_keypair() - Extract shared kernel cmdline into kernel_cmdline.rs - Externalize to-disk install script to scripts/bootc-install-to-disk.sh - Extract gvproxy IPs into GVPROXY_GATEWAY/GVPROXY_VM_IP constants - Remove pregenerated bcvk-nbd-aarch64 binary from git - Make utils.rs cross-platform (gate Linux-specific functions individually) Assisted-by: Antigravity (Claude Opus 4.6) Signed-off-by: Shion Tanaka --- .gitignore | 1 + crates/kit/bcvk-nbd-aarch64 | Bin 518296 -> 0 bytes crates/kit/scripts/bootc-install-to-disk.sh | 22 +++++ crates/kit/src/ephemeral_macos.rs | 9 +- crates/kit/src/kernel_cmdline.rs | 9 ++ crates/kit/src/lib.rs | 2 + crates/kit/src/main.rs | 2 +- crates/kit/src/nbd_macos.rs | 97 ++++++++++---------- crates/kit/src/run_ephemeral_macos.rs | 38 +++----- crates/kit/src/to_disk_macos.rs | 40 ++------ crates/kit/src/utils.rs | 30 +++--- crates/kit/src/vfkit/run.rs | 9 +- crates/kit/src/vfkit/start.rs | 14 ++- crates/kit/src/vm_helpers.rs | 6 ++ 14 files changed, 155 insertions(+), 124 deletions(-) delete mode 100644 crates/kit/bcvk-nbd-aarch64 create mode 100644 crates/kit/scripts/bootc-install-to-disk.sh create mode 100644 crates/kit/src/kernel_cmdline.rs diff --git a/.gitignore b/.gitignore index d517a322b..abd2abb1d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ target/ .flox *.so +bcvk-nbd-* diff --git a/crates/kit/bcvk-nbd-aarch64 b/crates/kit/bcvk-nbd-aarch64 deleted file mode 100644 index 4285762d0b5f513c8350fbf0f5bf9a6588700325..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 518296 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�%rs=GB=JwC8(LMUW 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

Jdx_tH2_#0nGgcdcd*Q zkps)#q

#|68ON90T*g(7PVh1ct#5uodhF+xAjUU>`UEj)VS2>h*o>3@rQrelQI7 zgXtr*f3OYAxt((Qkn#fCe~tZsWB*M$zzHzsZTHk%ms(`(SLzyf1*7J{+aZH zjo=vA3aUHkXU4I2F!wLy8?5>(_6X+fCp}>5N0b{_1Ew{RJ}?M&|ATacnG@6#xCd+p z(!kec)bj81y?5)EJlts=LS!m;$zf8Q=hz2llxVR594>PEd7VhX;LNCcCi> zf~j798PJT~fkChuECn0DDzFg@gI!=d*bDZ8<%z@(_JL{NK);W8z&0=hc7e6v7`O-Y z&qz@HU?w;Y_V^Q2&fTPYR)VSqtHFA(25bSlzz(oEIYAA8WwVJ7tOC_Hv3oEDtOqj$ z=a6o&AFKig!7w-sHiMN%p$E*Lo1k1R*ddq-<{z7&%E5gnl0R@bopgdNCnc!;VCE^5 z%eQD(r;;zQXc79r^rZ=EFW9{-L8aeAIV?|5W#IS<>J98%nV?3&WWGesyq9tSOTpAU z(g&7;jbIDd0lHSB4@{=Zk<0Og3zehpc*k-61u7qZDsFF|L)C(+ASqDjpC7o(b-(96 zudmL(AW)d3UVs0+f*HOdnl>cCz2Ee;`0r1;FWINn`xoRF6ZOy=Z@x7w(y@L- z^KnBs^{f9p?NTPGq&Wv2xuT1!2$u=16q>HTAgQ>-Kflc*(pzkc z*H>=yL~aqu#MPr?A3CZH9mUkWkq)n2}F0Se3j`)9YH1Xx*rgmAV=v zY&^)jeUS7>9YyWPup(zIxr)?>X@mK&^QuoxF6BKhh-`UjeElZfW7M!S+384sON)~v(^z?%s#RlEofRW$F>TN%? zo`dL3abf?f|L^KeC(Tvp?Ont72;7Uk(WmHDhoJ=NP^ewX4%ZCdz?o*dC`gL*8wFOc zA>z0m_`2ci;vRjmeTLa%)Y^Io!-69nh#sHVpUX{ueKy}&n0hd&fV2a>x6|^`pf>@% zl7*k8SGDH|_(Sjy9|38>P(sUmIKaVtO{0R7YbWj-u zho9dr_{-rp>pj|U8U00b#OSGEsjpG^N8xXowtndzZS^Z{IE5jYh6)6v0!QQ zmLlD6Q>D3h~evMEVSJXUsGpOWlsc*9l*W@Nr3h<4QDWpCp?JUmLQi zx}-7g=0htxk8gH_7CT;7M#eeYxGQO>L8b;7xtB|@0a`UQlBZunYlRkqMmHCsbwVqF zW~E=oygrj=j^nYmY0jOT0R}y*-evBDTu`+7sFMwR6D0Ss>93kD{rRxvAmb|i&*u#i zZ!J2!eA9QhEcd{-{L+lp2M2^ zijGor41eiA*CAs$Gr(9m(vd2U-=+ZQvB%&Vj`Kt2Z-LZ7RV@8b-8)7q!Kn-cRZgtryBcH|1A0Td*S zILiF<%dH8jJQRG33E91KT2t|p6IOX-MvPKmWTL0bR|E;w~Iy^;>HZ0 z)@vIyBp>GVE0Twdne?^DR~qv6=OK-G$yDQmFCSNqT;?PHJ3DAYM>#qU*Y2gvhmom! zR8xK2Q+BasZqAKDwjM!BDj(M5B--X<|9N_o(ZREi8hR?W->QVx7e}ju))hxc9!JYH>eR4_0$3*DAA!H!u&?SvnU}ND z*;Gq;+4TyHx=C{vSDN!`}y{k`ziGldG58>l>Xdn z%zu#`i^>`{X~kP(kI82}i|O%N0}~#-++0*z{;QEX&ySt|%6uU@{}tQHfxnmUQLBtJ z+uMSbTRpS(c99Nm6>|HLllDknZF1z#Y(b*G89p!Hw^mQ#^I5r?qCW`l0CM@r zi$q`>izP1Eu&VscNZxH}@@Y)prft_mqjHf8@C|K^&Hjp@h2M_YL0mb~@!HB!?6nU5 z?jzvufPWnRE=jljdHo>s-o5bGymR>TUjH1{Yv8v(y9S^Qj_^&kF;^YbBIAjtRgc!k z&#NNw9 zew?xsg@(QB&%5MH>`ISY`0C;7hA%&k&uphUpX9XzzCrlXjJ(PiX06F-%yaebO>Y>? z+@c;C@gqKczO^4cZGRzMJUfuCL-u>_LcWYnbx-a4Jcar*h@`(Dg+7e$wBUXi$5CX>DwZVWRE7RAzFRlf zYnrrz`&kR!gC#HzD+k~a`)h`8=)mE(^FH`X72h=@OBu%XJ9K@ven--=AO0@*tAxK& z?}v4}G&}D|eWlIi(a-BNo~d9>#RZ`iLko#)xw(L2h7}RkLYU0mRC)9nEDdiZVSR+@ z^C!*rfJT_`HxM4Wf^TZgdi;dB-Y`!yz0=M0MAs0y2DXuIv)z7zt{!wXO{Yup8<@vD z^lH9A=H71G5>Fms!D`CFST77~hqk?b{{mR1vAtS!HK40-7vGp0X)cekJ+nU?VA0XC zJZk`qCD0eg5*We`O~@f`7a7gp&3DY)n|X}%EA|05G8_sUE)TidYx#YIAvbN`+Zxl^ zPVIXo{(AIyKhO8}h93JEY>z%NiIKcZ{5{AOAvbyi@zYjgI)CZ=UB~czfF8OM{YvL= zs!0n8rOZB8iB2U%HXpw9TD~zCJ}$u$Xn{CdCA2gfEg!x*Xn9}YcLUs0?$Kuw-g)Tj z?^>NTX?^2O$+X>vNItF?9c}0cb3eTfx;$gzdr%#&W0?n|qyC^eFr!blb)jL2(IPO` zPV$iHN2dBOtnRNK7kr@x0{pUgKO88pf%e*Nj-+CWyy>JtAkhaqcUm<*E-*0a_ znmK>aU)0&#uE7|81Dt6f<`(k zk?TaRXsTRv@=vq2$0s(i2f04vG)DqkI;&tg}xl6NgN?)tzY4HBiyT^ z=(rff8z!usuwn^|wQqYpM2bb?=t8CsnSjV}<>UIH4MOV{!nA!!gWVXX^d+L#e>|cM zj6tGx;kU|G&p))$Tg^JKy|0roG;D^+J3aJwe!^H? zsmXHHG=pe6;sfX^YUKAeUR{@cejI(1sSljaR`lpiQ};5tjC!0IF=190Je5&nw2g`} z_}hF|Z=p-cY%sz|((-V%3usHt{KklT^1bI^^IomhzktBVtU(mEAkvNeAo5kL0U%!- z^UUrX_p%Zfd1&`(BeCAeSnp`8m(<#1$)wGv?Tzs|yX94CBqEX$#+3wUw+~U@yk{b9 z!NSOz9j}zzon~HG4Pnl!zsl#&Tk$`0%?f6 z4-k9YPguq@VI_nmpTPLRZw~vpM=owIEwhJ6Zhh3a%}3W2_1PGWc2SAmzz>|pnkuTL z?bkt@fOfcTU;5upWLlo&w`kl;`sk*j?@-KV6s?VazuVOCIJ>l{Rq@UMQW4?aUVcBzz1W=An52(eA?Wqcdxg$b zfUAWTK)e``iMmLWb;a&=slVGM?PQV ztD}}JVd;d8B$6%(v#n!EJI^Pq+|O_GC`!WiIHq}UC8yBMK3k99S<4t3OG8}RT@(7M zX1OB!^h^A0&_d9pJrhTyFBkq^!m4NUn^g(p650^7L4G%@*SSgC2d($0s9xclgw}1N zNnFXNAUk(zIf(vD!uAuE!M(j4%(-r4-K50i6VsAQ;;Mw!JdZT#6oV%FdDKJm+h|SD z>TEPU|Ilin6$&4AWPLXyY3e0x4`HPeRvul;2^llSNSgM+Hwd23C_K$AE1jQia6`JKU z^tK?Id@R2SHDqJzyo)eDVdWCWCFR=>trS|ZPBGS$_CjlhR%O`c_@UQloaNXgJf@wR z-K;Ef2GN;us?58wbJ1^W1HMYDpbKW$?LcJ8klBk&p&=8=gG?N(&mSq$$oC0i-%ZGj zBU5h3*q`UjD%GE_=#Zk%Wts5yBbR!-ng36ir%OB&@aMu`H8mb)e5US5mWUHRictEQ>$$QsMs8REjrX9%54d0G<3xGoANkm>nZhbTkQUrP)TPo z`o_`6*~j`Nv=FqyAZeF*1DDuCEwtg~{N`F{1xbU49)FyTWB{J2wkf*$(AAyG@7)bu zy`P|K3|;U{)g^VBOy!KMLYGkKTvY`>FI^P^a%LJ;cq$u{=n(bGkYLG)X-f*BM+)QgDpy@TlJe}X+d zM2Bsy)u%Pss)qT6QB6fNV3Ry}molID5qp3bc{t=a ztndE?XKW0I?>kglv_l2PhRHn5R%(ZeKUVNioYsWfC|_HrMw>a5Tx?jZmjBdO9FxD8 zJ}dHG)JSJ+SlJ=uVmV`dhl=kIoYB}YZRsz{mxC{`7Z3Mx39SZNlZ}=KtwH!+q}}-O z@J&i-WX;(AohM&;cFfSbD1QH?uY<`vUUc>&pZ6MjL7Dx2HMG6Z3Vy-d(duvQbJ=ei zFV&q*^hbMQS0`j?CXPgw<0y=Wu@qxnzjy`DDX+)OSBvAbBEJ%I#?MuG!jk?=_O5Gq z19|Q#hw*9V-`W7rskZ62F4vD-+FOx%hyA%s9uM_Kp%1AX_R;zi8%6cdx)oSFMTEV(~b#U_CY|OiBB05 zM$s{Wj({Dv_t}!}dcs14wNIb!QCmtW>cSXr8uER}5B!C;AJuJszhcBa zN_aov(zOtGWbP*Q#8G7I@fVqM$ow+;&2Z$aI-_Te_0lCom#p-6(M9O$_#ft(Mn0l* zRqIp9$U8y~HZYn(t);{yPG2A{n@tc&!4 zwuUQNqIAtP5iN3QtFaA@{ZR}Zw)(Wb&!LFz3l=ivxBHQk{DjccoWTAshMq{8bMc1> z%k;6wkYO`%@AJ$BmlS$YYvJ`R3U~Hty6WbzSCXk;>?#DU`zZEPns%%( z#$jwAIsoYQrN6R|rJ+m8vkQGgyr>FrZ*R|1o`ZzBPG_$mBQLSv(OJWi)`P9a3`+X* zlmATY2H7@4Htu)GKBrVe)!T8d6FXl>Kbx}g>DLsXZ^mjez!%U#BPOG1lnZ`NofB}=76%v`r4SH7$t zM%v0$JEz6i&05l%i%j#XNIR7FrT6Dj2PK5H6Skjw(h>U`>_{DSQeLrjpywai@-x`8 zNMyN$)|O8@fi`5?R5`S6X!)y&->9qDu~~*BJ&Q)aP0wWWSw&%lQHRkm8)G_gq@6{d zxCXoB-Yic&k92$@qqjpzUxCTz6+SMZ$=^w-Zq-{U^h3JrbxdgpuBWlwtv^0|@ zwk(&>g3yZKv(pNomDpsZ>`G0VZu{7PWgjwz7Mq19`oi!v*<^**Y|^Z9Du&h$tpPrB zY^t8J)^C1_-w?CVuXiML%C@zCA)!y=nM7yqnbC5Pbju#QIneUCx7RsyvY0y0g|7%c ztIow*PZe~;3pQNK6xoVp(p|wB@*=VA_16nDxBWd4aSEI<)A*Z(= z!#-@^j2L5#&zT%I$naK zc#J+E_BqI?FUlp_2^e4X8+Fry{NTCK@|Aw216m)nGSOK${kJd9OnWwr-?>V8?n8Ez zJx%TPH3@APS_9#vacaLO%X#*W54IpKXD#o-*blY-U~+N&rPN&|GPO~e>S(#?KT4=J zN=|=%G4G9X*n?aja=J~SCmq)XE%j5M+%N4l`K<9pWXGYUAzQ?~-M(vV_APl#U572h zXU++xlnp0)+RH{Smm*|>><4?eY+)U+tXuilJx> zWp+X_s!GwDej)W{q%A!C^RY$`Pmr$M+|fqgJ|>QaJ}IjXbPit}?fa#j^_n#E+4i8n z)g7Crp@~izc&av-wuHADmjW#e&0GtO%va@$>&WUhEjM!SOrek>K8DB0J#&r7J^gUx zo-xU~r{5&b2I4IJES2EGBj4jpvsQz$Xu%kD5!64xu?o=Ew-2MwUmh(h$?KR&^Kvhj zp8xgS*!W;kDJHEh0S{|1pYYv4Azv{~``v)(FGttjt!5vCUuZSZiYw_uH9TBGYk*dF z6?-n~6ob|ZEpRpST(Kc8;p>DJx+a>hAhbS{<~3|B+E$I{HrZ{-?8S@%5}etTzho*=YbXf4p} z=`MoS2u(~_F5#0s*Xy9A=oEuiW6~^HDdz@g)$olApY%ia=LP+x03_)rBvta!iOoyH ziufeXAxX!zvFY6htpS=frxLzNXyG`%WEx{#94#GMEwnUA8<)uDLW`Fznnt$Vloc7d z1Z$wx#KqA7ts0tN=Q8+Op;gAwI-!-v(fXj3LbJ+K;*h=Si{fZw(DLJCIc`toLbInY z1zJuVUk0?yI9eXG^f+2Ew6r){2wDm>YmAe)YN4eVUCtqa=Z?%1-D zJ@@y=(e^?cgJzFo9NGvpnG47zX>py;ydaJ*71}5?yWM7*G`qfhXv1-`CC~<-*~_#N zT7Mj^4q9IvtqEFh9IXvnPaLfqT6Y|609qF`yPb}hvi3CZht>(7o#wrO`D&cLG-!L` zXhCSLakK(xEpfC`Xw7l7Drim6>~;_~`Ruf2Q=eb2A)}7kO<6mw2U-LA?D-gkRtL?_ zC+8y6#L*_8RmIW##f&X+v;efSI9d+0k~mr+v?6Hsw3I>1kE2yX%Za1aL(7PxwLnXQ zX3s|lw3IknFEoFg><}~s&2GE9?^Iixqd?m*C8lCg$6Mc)*u*DpBz1O={IdXC(;|hgV&z|Tyg}MXFpjAPWVkZ6U z-^edJc*IG0+_i_oXL}*`&wq5DlDn%c(LSeFfUoQOhgmn|^^zSDv!>d3h*(B;wc>{vIQZVd@*D59sfH$`RFM{ zPihlwk$ci_q{n>s!PHq#jXsr|HZjI;c3RQVg^o5uM|56qeLordqf~ueD+k^ouhF*uP%Y=jjsv0DAhrfgYocMB9I)y!J)904v{O zYpxCa`DVn{B;C<>fK-<8@T5nhuf65KAB5j;q&GZ$daZq_n5)BKm6q}2oq`$8dc73m z==7N+Mb7Wp+rl{nMm}PnRgBjq(%D27&U8Dfi6>xKF&wg*SFD%!e)Ol`!?_4YpkF?7 zv#Ha^*1S^M%DtR7K$@wyWUVVn@9RsTr?qlEfm5ds9>k8q)Ffe6$>_Pzc$AEz8Dm8| zI{gns#u8h5Lc(lKxkNVzUk-fcw^hBdahu?+~eOxZQec$6O1?1!s`66fykC@N!_OVvpN7&?rSI$aGd6Y9SMA%q+wAPB{XBN82 zNy3D;4PK6}R~d%A+0r#0qc;feAiQ;tMf677fVmccIY(XwNQaS+jO(+E{m7Ji@JRcM zd-3bs=aT+5x(4RV!+4{FLc|KuG5I8YhjilhcMw+Ft0iJOUGiovDNFX8g{Y1a5JkKh zUG?4M*GNx!BsrEY=aB5bYoy2ca-5|hyWh~W7d@e8$(L#8Q}l?nneXfjHS!^doxu1_ zdeSeWOrK-yGVHnY(CuA5)P%({YpcQQuTH0-OL5^Eh@<=k=A8%ayF&JYq9luB{pir^ zA3d2bn(trZ#vPXIWZaWnN5&uN_g!>4g?(<$@Q}pBe~;K?zPd109^@-k{Y{23?bPk3 z0QvrxBK_{vHOR>CF!VR5R=eW^F1_jL?WqAB`(HNKd`X7bR4cU9SLmm(Q_}Sb{j9Sm zaoV2Nn3L>7SLH9G^LlAllP0arXjj$NJ1XAv(lu3^-}W2w8JE*WhNA1Fh^jnj`=L!3 zb}{|>vo#E8Bb2&*1={$)!&ypHQ|bB~s1^NTSLUAZ>EV z+-d|`J+u^^V$kHAF`=c|XmaLQV;rBHKPEJF}4DG^s~b9R1Cj zSh4l)RQSU1?LoFk_zc_A-?1{B8Ow{1B|q)(^}$zd@Wu6O@-SrfBFwxc-^1{`-ihX0 z<_BXYE#IiGsq+(O7yX*KRBBETB#r4C85`b<%xfa=y0zwLBBgwb;A?`Rb$N9LwFznr_a5B@HLKblVCeJ-&^mUH#I_Q4-;0AWj=*0Ce>?Z8CeHTi?Cd-US+cN7TxnObb}*asLb#t| zw=$kbqteH)BKT7{k4cX!ejI^6I&SzFilV-fsK>B>^wglI%7{1K{z+(@{lni0zgPIV zr0j;FwL!DWPH1D$+M!96%cb{!o0)4L#Thq3U|+IGQoB4Piq^n{xPt)(}T4f!1o$rt|~WJSh4>qOrG=%n9rbIQ|E9 z=nUfaqc=F0b7vfQrs)T?NxK?DCL=_7%%klgGM$V(P1~cEJe48Slp34Q>S<{8aWpCS z7L%se5gn#JC_vB0b(vu;8XjBOa&$Xw)#{Yw^Q_2yW%S-CZ#k23^jOXya-u`j;QSSg zTgP+mkx?$u@4u|_CXe>np-*dxC7?sVW-SYYV{C2`z{yre?RB8XEzDh@9)p+751EG zW2nWb+l%iraZQ`dGiPQ@$5vs&sbV)Da>y#*-<(f5kuj&7b;ir3 z2#tQPChscpjx+Pd=st2sRG%>(_2jQ6zw|8@+inORq7hd(O5R%X){D$v;=P~7w{~LR zcg*6!TP^0W+3LoKAB`(P|M%uQW;SnIIi$s8MD}dvnx}q5zE@FxDBs`v;1cEGLbLKR zK5tQe|F^KmT+_c6;wHv~wTIP^Qy*F5W-&(B@ZB%7T+8o6HNsDFa5Sp(28PV}LNK~r zi>@zF!2O^f-!zl9@oyr3wb1rL`&j3OapX0eEj%vs z%hdl#i!aQGAKfK>4DWCqsMGXfhv^@30;Wx2o6&8?m@x+ZUUc8}d@E5ZvEvbzjCxoC znqv3we@XoV$@?*RYt3=Gi0>qdB-VGrd`FWEJ1gI69XizNr%R`2P~S$<`jA#^$*pZ_ zC9R&cIm7vO8rRZpciP9KEpH_)MA~zt8TKWnKKcHuq*-&&IlCzJ^(Ac&X<24{j$N0u zaiqOPnp`WpAzaFRiB)chq`B;wq%9{c)yKSSAx*w1yPdR_Fn5qvOIntci|NA$R=Ly` z(oT}Lg>pG&n$K>kv@PX6)(xZ`jW;l}M#UF;;z!5DkI@s+)a{flGl||JPvj1nAp1DV z-ggsnA$G5jr%xkg(T_?m)JN-M3JHBWB~3lOoN~cJd@w1;Kk4gp&_<1Q^uN=_6w+!* z`l_kAnD6nO#D8JnKmP9$v$X&cS7EL*mO z!Qvf=!t{~0fP8%>^Nm9@U&PM2e62{u#(J&CLzRb&b z!FO_;ES25X0p1vI`0fc_&pC|O^fvapS|Vc_jEt$|8%Ms0#nxKNwUn}-;}yP1o9C17 zIr7Q1{FAaPpjBR?tmNB5zK_XgJ@ewU#dtWw5-`d40r>(m96v9TuNx-G%GZ~C>Ool- zI&;mjABo-^GMb+y<0SNQ`&!Bjq|Az$=pS=_y2=b}v5d@<63X=bL7V8a+x9ts0-@iR|SM>TbXqz>2fl=d5i@p6cgI}93?bYFy5G9Clx zp)c->v|IRBXwfdk=Q-rtMA=K_dyageOXQQb)RS-24_%_@NEeNfMe0{l= ze?mJCZB7g=g@&z)q4k7T9YY%e?e!R%eAhb=Lo0@M9-856SoO|_mT`YHZ3VP}F|>8i zCPK4qqqKPowEJRcuR~kmqDfsJNWKS|*IZk2&z`RND6D*B%$RQ|IpIg#L-1oRK9)J3 zOC$SVmNAld|3WpT_}~8$z7CCG_{C1&jcWiw9XXf@^Z z`BtB?MYjFKfhu;#&9{>;`4ahrUjg!^kWcKk{1e(qXsOWR42r2w^dG?oXvR3X^aK$l zTTK{ouD(`2TNeq0lqrNBqQ6x_u-C@+`T-+q@SBCGjOBdt-bYtH{6C%$s~hNiYKB@43i|y>KtIlY#+F=UV;=O<#x%yJ#|pyFnWYZM9@# zE=Gll%rjpIiSr<-A$wA;h;o&bpxC18$&Ie10{UsP}YFXawy-M#;wo;qp$k_LN1lcRM;%prF za7`FP4@@LoUxhzPUi$7zj=b4M+BAgZ#pGQ_-b!;0MCG-tuklK@#Hr3ylp_mOMR4f zwvsM#G?4Veq~B+jcVy3e?mn_9BNJO}<4p2rt&g!WmTk1HjKpZAZSqfhWa2pEYwStu zX8qGtQr+zDviBr?$IY5ix1K%a>n8bU^T1wDxIhWnMvO0#J(2Py{2c~kNJr*s<83y^ zrQRc`Qd)VBpkVIcZ_n+Tab`y^bK3RQCDsQ47M|p3zCI94Irp$*rlyu(K|6Okn5CC^ z-(g?&mC4f_vk*++)X}Ay@qReH*LUr{Z)v_4;%op*z?Sp~p2(K8Zwpv+mTSftE&vI^;tYXeVieUx1d&a}rYS-Z!tW3&De|3 z+qckuJ4Ps9xQRps4ZAJ#QsQ3IUewfTd>#E0r_PJp8(aqX+RWexsQ4-zXH|V(ws_jM zg*KUUN*)|=agnxAmLpth;5JQt%v{hva@rGUaX*xo%40xDv&UBk2N3>=55RV+TJ*xa^uZ@f{u`L!>q!n~ zq-~Cj_?+fhN4ktTav$Zt`n%P)$e2XRe*+yUV_OSO);`U6PF~;OjBRP__5^m38tIie zzQQM2kc7bUF=zDI4p#jo_*BWcBJxBKZGGSavIxw>?`p<7ZS?65X7pOdUMm`B6r%-C z%M<{Af)Z~-s%^c=+ z(TAD}aP6`Ov15ln>z2!lsyicYUJ7ove`&^hYxI{K{obOVJtxM`#>SG-h06ucnn`g=KaeKW>w32Poi=p*5u;hoMLc7Cp6;> zldC8CaJT8M>nyoIWQlAMIhMU$Hn5<0O5F!ST_rufUlG&P=ZL8yuWyzs6n& z8^?U3eR;tcn9SIL!IDPl-c~R!;x7uu0x)X9*e)1vI&vD_4(Dsg%^M;ZJHW^}W!dak zTf-RFsN4UcU{rIdzt|cElj9M=U0c8@3|s4i_nmcQwA@@|eIPpWBp7qR2;n2>&pR0P zo=fgo=J47Lvf28=bPo(QIThS-?Iot>G1edYAqV#x?1k#aa<3&^U=4q+8tfGw^V{G0 ziw<`4xQe}m^z4rctyqse7-Mk~tmQ2FjJfxigB3d#7nL=Uk)bK*lX#CQ_Y)kPZ*YbY z|Hyr)QFzDSD)1J!;d|I3a~&PL7Hwg5jO{~$ZLu?AP=fa{c%yWWl5-LI=eOGH+DJbg ze{Hoh8_W&9=v$x+zV*4B^&EJ$-0Ry~z%%`~iyD2Iqw~R8(avM~BugBe|H>zk^}!Bs zHn#T|?^V>tIyieHdr8&;VYxiu7I~Dakg_4l)}|m&vPQbuDO<(9d32!8`rf!Vgoj0{ zE&4`)4V;1ejp_ieJMwLB@zo|cSaV!w8oV1RKF2)BU@R@9++r!$$&$m7PPr}SbGOz4 z0gV8GYE5n8qg*ZJrlomI`_tPg7p(&>Vt+(7LSR&aaguBOM3F5UEy~)Z{4U{VA#trC z>6Yy7cJP{yX*>G^?jJ{anl?YBZIxhUWO!7v=%=*~)+N^b&$aMjGc%@g7`**gSaN%h zgBQ)Oi`ZI`*_>3?qnRExRp!!Q2O~0*A5|da5-d9(9j59*|OAcP7U5@^W_yP8MQ_6o#`T4y(#`^>GCofhW z-^B6;?D9U!%eUC4@&M?4TH{2%z@=?%DG$t&%^5D^ z3xxKP#8{K;0DI&09+f3Amw!0eTUuVbTF-L7#g+*1B?5 z*0+r5Y(Tt@dLxeu)sQaPMQVX8eBo~~b*;MDV|;&JU*gmii0E%;fIpDst#cRvFAI+} zn&7FAp_qO{fI0$WJnAi(xAUAj_BNX*ww_n?gjBmHwqBCC+b4r|kMkJMaOgKUSj~M9 zYwluIqc(R$@yohzJ~#uv<1yZ&s&8{}qT>VRW77z>Yp;`j8xNA&3pQ)yioo0hX3BVv z@lIp?VF$B5dfjI|$d3C#g4IPsPswrPXcCx$o7EYA5#0J{{;j@*Jjlb!(Gwm zaJAT|0w=IJl{!lJ8`=e#xZPu%Vb?!#>S?Z@FraSrB2QvP-A}yuz3A3z>Nwj)*ih-; zP6wm;8cFsQ+-oA^vwv=asXMoTH+4CFruc@F@3h+#xP*+!!BX->xMs&|rHye{vUl@1 z6CoMQ-VWv^{LN~;r9CFWD*`X@j7OEAllAm(insXkmhc9Gw-LP1W@ID}yulV8gV5^v zV#*l)59JPj$b2+pYr4&=h%7kfwKQ4*QC|AdEfc%)5jrQ1dKWq6FTt03SlxW@P4vTk z;Q0dZMfCDm2k%n0WV!gQQJUGBh@v0r!L0feJ0fjt>tOC}eg1f}5LD75vLLc5d``}S zZ(n-My|+Vm+5C$3_t52$P?$MSMo}*LD|{v}^nbKcPUJgMFe<3%qudHGN>5n6=C)Qa zF0$_@{Cx|IYA{~sT3_N|MEl?NeD&Z2I1vM5y1GLM&iu{l&RXoG<yf-pK&%1(F6 zM)yNXx;iq-eMdRr<92XT&w30WO~0WPoG6hq_2*ZV>r1)4Qh!IMTq`m{@WC0;)@5W= zHfvfijXr(k==f+%OGdC>4q|IEQVHJt^H$$}==d${!h4u;4UXP_(1U=A@EQy`)QOKyxYT?Iyl z@|tnUb9dPKr!*=agsO&s@-m7UGW9y;wotBC+W)yzE>^Ci#ZexDc1fr7A^9p~OY^Ey z!FkESxd`8*`7nxd6Tz4RhS8SC9E{kuL=J43A4iXBi`cH`z}e{an)?z@wt(Z=H__<-`sab8m=&vwd3_k~5cYn~Z%JLPKh(^Xdc?{LaR{1q$CgZNp_i8&6^=5dsb zyV`4<%hCt5P}XTPLKyAZ81{M<(LjB3Tm_L-?A?0^ptbFtKi!B~U`TaRw|mC*9J6rZZu4m;c_O~7y{_@W&yT?@UgkC4QKS!WFr(LR zrd{@K!E1L`Z>CB!)&N<(*=OOwvvnTYmF-|$Z0>kJj#&ZN>*krXX%2Wp9`maE1TV68 z-eR1}y8xi*sFe8@c6*vjJLT+CbROgU?+4WL!78uu-4f^QA?tkSp#bynw(pG`p`k;Ntw~l-hMUU)u^2MC1vlyC8+aqsPG&m}2sySf1zMB3E#?ual zGv}UhbacR;bk=)jn%g;v%5mR7jike8-RnMRoxYlPo?GgPLVZF5mk^H6P-~V$f|JaM? zpGy8MUNz7Q{yZmt%ox~<977Z%qhPwQFfxV&uE=KcHS9fAdDXZCu%|fKEz0^@ zXA16#k#=(iCVHo$HJh)4#ZJ$so&|od88?iamuS&n-WcOAOZi&Lm;MeO$Q){P=45GP z%*_EZUV8)8&<@s|79Z^-<>$Z5+>r8-^VqG$v5lt!VguPeaE4yXc>f7KD6$u^AJP7b zJrweSBgbPg2EvGi%eUB5_RH$b*lsiTk9+n{&}y*1y2rvU|NjqLWbG)}J!hk6#BUq!U{_rtcDx@&PM%oF zFykpc7Mp$NGA1Iw5zJ^EXvIEcO%dg~u!xKz&_>Z;m0;FB6sH1$neMc+l{sN8SP;MH z8DT1pp5Pq@Z`8wlgI4gyT6h>_^MlNW@-qCO~D4p-PP4JUClQt+_D4X`A^j5<6xi2ZN0_kQiq$jrs=;!iQTHXYO6D zb;<>zz8n5*qWzISdHV%vmi$=Gu<&LvI75FLr#4DI9&_e%E92}q9NWQ*9%>6yWM?m! zzLjx`EhWy*IG7Pz<$M>p+UBeE8Dy_KL}(c>6HxH71~b;HtiHbN;6;2v$6wv#c!QRA zNpK}P$gmo`Ip7ukEY5gek^ZoQx0mBLZU@og9YRftn{$cACu(Df9$)i}p3>$tLSQjP z#YV?rLGh(q5!AgFbIVAq+6bCC>A2>b{&2|)k z>707ci#us|IpQZ(gIn?(-_w>km+jz2`_-nNL8~~L)tEuX)|w4zwY7wPp9gctFZuTL zWngx7V)0upp$}8oKOv9Pd&^QLde6(zlI979A5@ahzSwWjz2fJ!ck248IgjlFoZi>P z$8&MnIKnA1Sqql_65srm@oIEt~!gBlF%`v+Z@}xLKLe{~L%E)Ww<43PsAcu!h9Njao1! z%FFqpDAJZ9@UmVtziF<2>9j@sRMX_xIs-Su-VBh|NZHkt9knCQj3Ewi`W?|XRzJ2o z%Ej!5_{VQiw(lSL_PNZFB&Tetr&-0uhgYGSGAsx_%FDOdm;WhFRdKC<64mZzYu7}X zRCeRvH|MFeegfA=ka}yrg`Kz33Bb+#h!8ndmydD?0@f?OhkxtAd8)-;%1^IFJUWo z2^wX^<}9H6ihwoNy`1t9Ik)FXOsL-V0d&L6iXZbjSS1J1UtH^62g{6KIerWS7kOE& z(T4Msn|g$KDthXjVtc=!`FhY%$rI65uJxaFsNP4tg~O5E6LIFA=$8(b$bH1mn_~;T zF;a{~#IX~MRbadxiZkA?rT@dhh_-Q#9EDtFE;9f2f)PAx>5u0fjAru>pV0DkO#LDC zC-dLpCaVumMwR_WeGs2+A{faU-{cbe|A2#0b@8>Q_b9fGJh3*_jK^&RvxotM$AiPd9NmkeJuK-rCyT_9zf9NCC1i!yVSWyR;ZGh%`{1?UuTssv|ut2lCs+Fev- zk$)y-s}js_@aZo)Wu5m$iVUwH&-~SX8h>S z%x@Ly)#DvM!+pPg41j2`ZeYC6*^7LEDh;FX@-C_DF9P9@DVs zot@rpp~eku{6~i?+D>}*4gSIl!z=W^&3~#>ysu*0l)?VS>r`z{M_+hgCfL19?BGZh zmN6;3&_5-3kJn#2V2Ho2?DkMj)9{Lplzqh0xiJWDM|0$=y6n3lUF-bjbg$l+(_aWrxAR+-uWGlt>e(sv z{P`zR&qBMNOzM$3s>|*QjiHR6dh)2F4(vSY$)ldy-V6M7)G@k~FPxB~YA0Xr3qRB$ zp&VKASF=4OGT*(eDhdwvg?UwXU~UD_KSPTL2n+5_IsH8K|G;K>z?M;&cH*HO{GyF$YmM@?5nvP7%)P6-+H%%PrM z$bV;5u72Y4XVAXy`c-8nb1|4sU0$sw>|MI1Tk7*`I`<20`)*rRdyu*U)FbtN#C2(3 zska;Tb~fs5x>c(@m#)H6w_R^3pYM;BE8rM~QqWkt4b-3{hkXIh%O}&9O$s7v0`fQKW)=X6?{+2pqjuetl=7!9<+@#Lsy_r+Yr$$>r_5gEg%zES}PF0-pCf6MJx~a(c^(NHhdbP&vTU1e=u9f$W(~|zc zTn|I}rW;}ZGcLj4&>iEb?9|6ryOCxPPY5yWk>M&)uYmL&nlutyiq}=V^uH|Rw ze+GS){wqr#cKdRPU;4a+xl{t*yMEoY^^~g(2fbZu?0U{4AAAzq}{<8PGA^$bL z<1*G&$U&hu;Y}S~-Bs(-*zjeOrEg8_YV@n<+a-p+?NYwsi>9JQhQ94$>D#U~-D#_| zbA_i%`43xZ!$-8CcJ?EF8|Ut$O+{aj_hshh%h){8S)CZCIL4jV#~$1If~pA6ul38O zhn6-q)mCBa#GW4fwyF$KPUg%W%kEWC*0y^elI};32XF9&x0X!_<<8J5hHHAGzbqr9 z{jI5pGbyR&IGOs%=F!X2zIz_=+jix9u-pqvm)i;|w z$~1VCX4s?*gGXr=k1}dLDAFq5^JKuIG{YuwM$MRSVrNc%_Q*RsHw^WQjXa0$dI(!2 z_3kXYBD9scQ0>hKJ&Qg%0WE!os>tx7XR+;~XR5t-hWxZ|40dx*kBqgs*v>Khy$AlS zT0(iptCYV>f(pL^R=!s|25&2}7}K+DMupbE*Xpuqp--?SyDlKhht$5)SE|ZJ_}hSf zoQJ*6Nk}N~_MgS?tfc-rboIlGi`0J>J?pOj-d5`GO#NN0`fnep9TR;d^`C2VV`yy) z^}lD;{|Yktb5H1%5Pc4KGw|usYWgwHx_Quv@Tm#^0o#`!q3b5C)au_pjlHYBa>v|+I8S)A zmRhc>xVm6(e1Wf<_BNn{h2Oq?BQ6BJdY;Ibv{BbR;hYQO-Vy$*RL_M(##Q_Sy=Poq zHRV##&FW{b4Qs$2_`e?aj?glbJ>fuh=`c0=wPE55mDKf5Y3OSDMWeU($5)N3t3qxw zC&tw^WNZFFdg(BcYZxbomeS^pW_~Mhi3jVePbU=snaz8Q_%*0Q?A6bE35PiKAo#*%?{i6Mm&iEtw z>7i}BIw3VvB{cTHKdfdvZ9k&$MHz2Z8^(V)&?&TyZFa}@_vmZc{&D(R!KtIZzu|}5 zKE+?<8h^DoRn?xsw|v#&rRZEAZQ>FB)WPt9u-gsZ%M806@FvyB zHODm@y;<-nD|PL3O&vSnO{~e)o>-HwbuON3r$;(i_U`H9eP5%?>G;B zJfQJc4&P^KE7v4x32);2R2-#WJ+yxh&DQFNeXBt2y9VBjYOjx9b-6nB!cTnP+09?B zrd5nsb@=|f`BTde-rs%sq5D~N7G*6Dj91%_-Ibtz@XIlmCzb13(wk%B3qnhf(cDfe z*AR+4hONSW-Vu6(wS!l!nDl0c(YgA<1tD20{dtnF?!E7671)M~yT7FdGcvPk65iflUBNyTeik~gM^%pr>CH`CLE75YTptqwNdzzt4$B(q9YH{pPw_XpJ3-@4IQLyqC-2=Ceef0mHksh zZpDv(dx%!~Jaju<#`p+-d;Y!Tsor;M6>stTLv32<{`kTWuQfcD8^3bRC;X0ye|*iV z1bk8I6@57sTPSuXx2s;h$D_ZQiyh629~Bar&S?9bGj?Kkqz&&s-ajQ3JC{P;v@h@8 z4u!e1j4`d8^!+iux_KkCigm0XbK%oS_*8|@(E~n-o$4iX9bP>Guh!AeA0Wq{!H@N2 zcbc-DBs%KOkj{J;S-|%S>+nVVvlkf8m%%Dfoyv!+v_^cKqT#Aj+Pe404VJh)}DZ~YCBR+5|ae^Y^4U_%E4JstgApF?<)BY(3(V_P<9&Bk^K2#cG zl;QZ9-$Rep-tmav_Ja;?YbyHlg{E{V?_DZm+KK#WHFtu6UP%?5UhPdc_`T=R{zR)y z-E7l>(1O^*^~mi(=1R}&u>a%{omwq+5!|w}{wb=obkb7(4k8yffh~IZOp*#8O!9@r zcSxgLyV&}E+(LcOg6*yP+A}7U%TihUrLDK|Cvlp=ZPefZb5Z7C2WW)$QZ*~>Lu^<8 zJuJRR7id@U$NDvu@RdQWyMFu5ZbR7G)R^HArWzBW3 zl)Zwo94|HU4dS;af7$%mINOjXj#C)z5!`JbE$~asV!x)gwx)}9Fn`1&+cNJ(rllS) zG~uIL=c6kOonMdt80)K_xxv)=Qm6QtLFPi0$bGzC&T(dkZ_*adPB&|dEw`V7BQlOW zZx#9NPP--U_Q^<@2NC)HhZu^{y28@ zHe#gPj*O%a9{<*BM)+?)SEs43j!(u84h2K}RKaRw-bwr+0Uagyf-Cr8qfiwwS>k)#BheQR_$?_>A&UNsjp4LhP{#++ID8P=D)3-s?8v-bvwMY zef`P8*N)M=f5ki0|0DSS2y4q%yjO%ywN>G%-n7PCaI3tsmXFqN!k^r3s&d==iwzwY zL>Ei^F^#oefNL}Uh<*ceE9*Q-e`We&|5og2rl#_5g`Z--Oul3`mf*v=`}MsrzC)M9 z@-%$W+uDV;okx#I+fJf4PcaW=K4xGmdb;{`1=ppWqWe`q{ULNz5T3vC$uR#bpMTpg z>(oD#WroBCN^HgOM_WjrVU0_2S2LDq%U42n!dhFtLz}|@r8wtD_Misd}Ph`b|USPxmCj)v**_ut6zd4^H}<& zgXL#FCoqQ-7)!-`Za_~2%61Qx7;LepbXZBE3ZI-X9)0&6^xio1-&pkE82p0Ke)OW@ z7f9Pgp3U}THdYf4`;tD}{TNO^#MWiOGwFW;_hL_lhp&=P>_XKBweR_G)3ah@uO6To z{=d+75vQucW{3^6^F2d8x$j0ko3B;JM zqjgoWhii#Jt>AY|3c8@A$AanH3w>;c+~+P>!o7}O8<(Xjyd`-Hp69-h`|tEs6>Up; zFW4#NxgYIQ6>UoTY#%P=xgRr8%71J7bSY0fbZoJz*woQiw}kuTlHBb%&_5N88Z%2abdTqQQf(*`ekTJl|3w5!_j`+VZJd63@(Y?fa01wN7ZT_cAJJpp>6 zK@VE=-NS^Q2tCQ5AGGri6?zi%WP|>(o&S2F6Dww|DfsW%`SXR&TGXm{ubqFe&?EJ} zVdozt^hmw0+xZ6yUFtR4|0g^DfM}fUcK&`Qjju6fd`xEHO9H)ejn>8 zRdXwG1&Pz0X97Vd>)%%cw%X28q=M@d0+> z&zxi)l%%DVYv>leyQZmL!2c=5Mh%vY61Nne2u&B1rlSZj#fnR|jGY10VWCVPXg zSFg*Ly}@Xjz0dgH?r+}WYkhz7cLQ49-&{=n?)^>KOAW%89G)<;`(7`7r_c1;*&n^1 zaAP^SCIUKV%2Yc5i|2qg{M2f$y@nDtx#7(UTKK(MDhR^puhCe+2yhbM;Vz|IO*rrzWrLzji=> zc0`Y+Ql=AqOQUb;{tIPs75)Lf@Ck2Q{Kz)g`S_YM-ng3JlNx)no*LQrlGvS&Z5V`1 zG+nF04aCQSOWOD=USOYywJEX0)%t<7iYokr;eFJx8Dba4J@NLqtrke$(-{ ze){Xv;KJK0W?cpZ^@s)so&Aqb0wAzg8pjI`LPHDZ^P4H>5w` zMZf&FJ*QrCxmCr_~>W^V<4@_voe_Cms;W zP?dU{(n$^QGyv|3tM#=jnTyz@VF6;C4TBZFG1tAQGof_YK3BWn;JWIU5>Hb#4>Rs^ zFLprIXuG-gy@;RHk+TKR&XVWz@tR>5j5seoh25W=>B=u-a(cJ7zxs5t^f41sxCM^(vQDn{CE9$p7z=Ne~#!fU}#Vs5VV`~0gk{FJMb*7|!5@wYYxn|kN_WgMz^t9?~o&pz~N-n;4S zk3PO)lC&ewzj9JS+{U@;pCc~(hCB`MsR16I5ZQr`B0tq!AAz?u>;6Dy>985%(;^!Wy4ox4ETO%#E+QLagr}*dOpg?vJ|TNBs%DhXEmU z1(opHuB&9<7Z~xQ<87%F1El+hxK*g<#JuWiuD>}B$zUW-B|<5 z-htp<<;r^{*MV{q@5pZN?`O+AdGxhmQtsZ@w2&6Rdsq+|ytlt+d&wGAH=1zns;*kfg75qDhzqkvsJh=Evn5X?2SYgt?Wtec3P|6KiX=k)72 zKhZU$Ux6-V?Jsjv;*sxx)rq~O!NlSuJ|X+HX$uE#m-u5X`Gajnh7vBcm9^8c+zX@M z$?5cbm#wd@jE)@QosN3@vO&HQD@4& zpQvtmZ!r8~-%RH7+gulSR<$Kve04GwGIm)BBQK~^KYj;2dLdJ#Hoo?#Dw;LISEs8# zb*R2KYkL-HLiY&$USFM?_Pxc>o{FZuwn!Bnfq#-O4_|)MV~_Zg&`lEW6duUj5S`n2 zp_E{HFwB{urlug=u8#;j$k=o(_J|DbQ5z>_cO*B9{Qw_i66 zd~#vQ_Pz1y7V$y;j-QdzM(z7DUR^G6nZToFySHF>Uz??hjzRw&Z8{CFdT_s)>l0jy zjmbT?x#@W&#`sh` z2EN#)mmcx|r|nJtyvq~IuhD#UIne&DsX;O(-rYT*}G zsJiZFn~LgKQ_22>>;)DaXWnFM`-*?1DxYNhWj>}dCdsaOqRaJXOBrtY56(GvaW0S zg@r376&6;{)%(V6*Atgd(y=vqAa1+py@WY&p}*#=czfl&zdk;?&FjY#;tn5A$chj7 z$2>DB;k>xgu>`Smel{ZSpnLcFyvtNp=E%z|Sa)0Rkekr=rYk0{( zh0If3|MBm6X{*uSk?-|OIl!93123OpO*sf2i>~Eyh*V_pcoK6{_Oy-;$CmJWulD$w zW!Qy8E%D9XGra6;CDz=n6`h}{`7Zphz_agxU8>^y+U@6m(9d^a1AAJB`MZCY+DBT& zpSfPuT^;*1zq7SA756MZIDR#McRv>x|2zJc^Y<(6-{$u({C>WCaQl>>4sL&UM#cE$ z8BdLWarxj0d-?mc+uh^aTsd?6hUFXXe|`DJ`-_)XjbFNa{rFwP0kXJO+kZDcNnL$q zGP*;joujWxUYk2Z_FYDX#-YQeqr+CA!yZS6q3e!~)sm8Azvkh~)#bC1C0jq-!?o{D zRa-db@kt5Z^x0#^KQSq%PxaiqoK17Z$3({Jf&*4|(SurD^_Z=b^z&Ms=(5DaT3y;v zt!`)bFznBjq1#-T6Xg1KiJNsbcrHHQym4CPcZ7~?h#z|s*TuJ(_VpEfiv-3-{cP8; z9L6PKCg=8uFD2mj1~+FNBpG6 zMs98xYsP6M4pT$>u93Q_Lv&&8<$#b>yRd~^*cj}R{J(#2j93l(P&>I?R%g0P9?UKL2Rjse))(Mm7dx) zY%FcwMw_MYp&L}##{0UHoWGG6KW8K9W01Z|Kc$aUW}Wn5l*o$gucaDm zllrk**tgcp8fLuVg5 z9X>sb$DX2~#vDXWW>|A=a3}kG?wz3{^lvo%l>Sb` zN6NTc9XrE5%6lH&*~Gx5ie;+-)ug$ z<+{FqG@p9Ir+DZ#pS~W5er%(*-UFYyP`*oAXj{64`5|Y6yAfj!lgGw9&UKb^M%O$W zoCf9&cqe>3`#@X&**kRq^YHJ?SQRcMHYsO>)4@q+Z%O3r^e2*@(8yYb*v41K8(g~Q zlhkNAS_yxJ&*FogWO1 zWBM9(irk9)N?i?{xt1|Khc9v-`M=GZR(=O(BW0{2=Y5;^)s~Up#^6aGY&^Rkf+c;) zVm`<|oXAOjnHhIn&w0)xjKQpI-|;Ng4dRQ7>`1(7ik9%E@KAKQ%}2NHev-QFc_@7m zp4s(F-BOoeOTAL3#P)4$!OY){zv)eET;GVycJze@|MTH@8a(W$A#*h{cFYZ_?|Jgr z`eUxEPU+_gYwnA^SV^6Vy2M^ceBc%4eFOXQiuJwhTW1l|$+=VWzk(koIv~Bfd481G zt1*`X)O)}>KN>(M^k6Pe8fflG%b6)T=OugfU9+^x59y2e{c?XUTXc^v+zI-A=!b|O zYbmNibn7*JwaR}$6CZjH>nT~!R#CTo{`7kA<(z}~%2MAT?q!`?P2MWjw;F56WzhRV zFQH9BFP3LLSO*J6p3vF9HR$Z~*z}%4&$H;)MCexv{o59OM1+q2O*@EL62B1KRPH6_ zQ^Y#-^Z?nn<}4|CL}I8ijzCDvqWmDA~qaSv}K-t-K#+ie`1M8`NPmd?>s|K`azDs*|3mKkb1!{bwouj( zDpmGZJBGY+ro+C!iF;Y&$i2)3VhDK!(6ZS7 z6WZ03ml$e**yQ`{rwze|Tupmy+S||uK6y7z#p9WW&>~AJQJM5y#{-*Hp6BkYPH>G^oo00bg zX&+c=f)~I~5}r;X&vNeV^GP;+9CSI;X57<9sZaVKSRZjMaX2$Ay~gWYcdL=PBXiKh z{FC)haDb}&A?e5Ylk%eDf{DCBhyKd_wmTO4Z{yrax^>Te-=Pdih+sJ=_{Ng{zK8VPX$hyIE)9z{=e^8emNKh0RB zOIdg+JeTv2(zl1L-+`UvOcb_*-x6#^P8z>r8{`*Tkdw}D3%dUo)Nxkiz@`6DAeBwj zBQ{_@&oj2BUmqu582c9-;HwL=?;~eOI__h?GoCVB>l*dza{j7w%@XFv8OxSrh))`0 zOE^)?Utu*fPy?j+F~F zHCX0R8u8E@X|vQ(g`a8Hkx8BfUpHMB{<`fTnNN1v&d`nd$NFcm^i}FmYU|&54k8w# z9eMXaH|G>`ZOtj&!Z;m=47z+Bk$t=Vrhd`B&N=Q4c|F4UJD0C>3c4Y`(2HHZ&X+752XCY4%-1UN8Hvp2&GV)>2K`${z&(-@l;0 zWxIw-obgO==2v^3$Cw`H=lOz)`pZdstYGsIZ~E9Ea@Ek=7nXg&@5)(pd|2^k&w}N{ zCp|*j8j!^>7!5r8I-T{{Qr3uq^ILGFykN<5pMo>kn_ezu)zW1{wWVc46RzTU9`+|+ zpl|m1HmPe3_pHZi?em4Q&i~WRj4i&u=qS;5vW5xY5$6xy;q?b;$7$Li>(v=n{ZCMT z0&_*$aD=+eHEO};srce&SPx`bYmN}@*~+@Hf&LDnkHxNj31$QMnUOIFo$;hPZ978S0$%p7 z!7i3FQg>;{&=_QCG%{6)Y~73>dK2fPJcb{78hiB*WKV3C*izdT8|R32tBP%BHibH5YM63 ziXZdu!PZ$0##ek8i5U&wEpk%e=WKHRdTg=G6Z^UH#D3Awm=`CqS7$wcK7u|-9d=n{ z#wdHCugGC)%{k6q$39OleMPp7c`x-GC-01QK8Y9B7I!e`=zoO9{FiZ(vs1FSw@cDF zZ!cx+b0vrRMxQZp^FYp{CELg3(arT#oCQMHPN+htQTmjuZ5}~>!9QTNS@4HJll@cnl8tj(rZ0fcXRYaF{7z4ib6UHr`x~+rxi`-|MZXJT zV;{zXwi{#7%DW(lqeaQAU&L9u>Lm2W)tOpz%yNL{i$d9@`YdPk!dCR&Cn;WRR?&c z%dPLeZ}n#v`2^ejK9bjWwXQQ;vKuy!Cbqz%-x)k=*={Uukw<3U85R%bSv-1>zBY~2 z@W(vtq3Y?YhMyOJ zC&J%pV9Qxb>A#%MJ4O8N!lDs=>M?Bbg++fLPNZR@J=kiV&zKU2-ikN$cT?GA`>J<) z_MP|(YulTDGD)7ZmiSdTViF<22xkBm)GBoHgXR9?e5$+-J$Aa zP27ZEQOom8LC%3uiq349=Ktg@&;7GDtP)va z?PfpQlMMgP3?SBxUAdn1=012S&s+wiO|u*o}LF&)cx;6TKKz;M@of@%KoXL{2uz}&&v61+-^p~}5EoT+;$G|tI zo&mn?Z{Gh~qn=q`{A|*!`!*YWT{?sOG7j+l!}e`j}Gm#_NU@!o&? zkHIH@a;eeAUDM4ru1|@y@v}qpn>IGVqk3>p!J{DK_%bppGIs!Z68UF z^~q}MO6qjl+0mC@-pArcJF~7C)6BXKTo$S8O;=r6cT(5q)BM||9@gaIvr9d)COn1w zgs6x2t89IMdW0{-H>(E~XFG;(RS(uD^UJ-Q(M%wwucwx-8O~UFkfQ|r|3nQw?ozi* zze+t=!n205-&4$fPf|R*fxZNKbs}fY@h!w>8S|~=uEms1N^qq!CPE+1d6iQ35%kM^ z`8s8h&jTqvvUecI_nUkkqmNIt+ z=hTxr`G(ka8|T)^!+S*bjp^PyNzU+$WewZc!Vt`@@oMma8+>6~cg&Qv@{dy{%bdLZ zQ|3MEU5V8T&+a7cD)_XMxhM0i;p?Vniw`%|_5#nQ9fs!nys35#?*XuBpFpc(Os70+@#EFP_j{3CDOt|L3Om6^!2J#(7e@Z-7o;Cdx9ef4*sPg_T1zz=phx~E|qx7e~&C5Kyhd%BZ#dFCCjRzNT z#5qjtx`&d6wn|oZw8vc0|Hrh=3b^mF4miC4O zQ*i2U<_yPUk8FOy`~FaQufV}O1UGSW@KvjfH@@NkwoCSzGHMRd?;rjgd*AkUgGV(VrN64h7LC2bK||ST-V5x~zWadhgS; zJ2lh^c?fPOH2ewizYcUPkh2U=-8L}V{}QZiJ54OXX|QlkP?zXz!SjQsp+9t>XPOK@us>yAVcrE8-ys*+lVDsseXnau z@X$@h*r4x<0w4Lp_t3tM$l!s0m}|qpJ7#-&P+r!_OW2Pt9f)lfIqP?1j?7I*&S>Ka zo)LKa8P+A`)BKVryYG>S(l)VY%i-hAUTiacu;qL`dPMTQaLti9?)fWYQ4daS`9lWI z$ZL*FtYTaw7C#dl^aK34Ri4Q;?4|H0%$h>>ECNq5&)`9oC4&dBvr7%TYTDE<$@em2 zVE4y&ttxuu^Lc*59- zKgTv6>81QLPEfw>kNi^hz}@x34!w&1#e58Aqd(rn@0iDRqovD`kAw7?J>R_Na?Lqq z_c1FET|4?%r z?D2}c{7`5eEgCjaXeA;Wj&Eej%iWT1gO%?^Gv8g%f-kxDieHYbd#8{VKsUd44*Sh{ z;3Xqio87~^40+i_a31HcFE0pl7OoapuL zi=EuUoRPhrBhWeG|*=$TjNbzV6PXFcsCDrs!nVt&x9Ab>p9WHS#$Cm_IKtW zqpmBdOJtTcP~OWr*Ed+a!p|0-b`<{IY|+M=w01(99ErWjy1*F2DQ0>b(peu>oSbkY zYo<}Gokp^T8o^ph;u(K2Vq1as;=gt>#`kP*Q-_Ky?E124Ybm-;^ykaSM>TU@-iK$( zh^b4zAiu3kU*_B^`ee1(cJVRDXT~g%%MO2v=v2|CGms7G)4wS%y7W`V*wLZyapoNz zng(qPYvps7hX5KtJ%KU7Byrr`mk-qUe9QM3K&k0Re=wQhQEr`xK;2r7z1GwVz{rz=S`AaKbmUg7?diGMq zKa+CY&sh)lOIF_9JD+=%nz^1LXjQStxYRl+Xs;@G}|CbiV@4;9G zhWH|~rq1Yq9azE{F_BZ|LvDAMoX$@$%06k8-9XuS?)vd>#*ja_ftWtNpCQ|!6oYTe ztTI2R%nv9d?F--C%`bkT(YIeU%n^BCVC7pyzG*S}4EY^+mC=U#t$fSKC+98X*$Iik z3m=Z)PaAW!D|!og@LP4vnrrgGXjh*O2F^4KXAU?%!5ILK*olkaU+OpbX|>1X=^&FoRy(e@%8#c!@g8SP{rEt_(`s)@NO%elzEXj922a0b zl_{W%Jg;o%sxsP-ei?&)2{Mi{hPE8cy4R6|z3MBo|J^K%zF=f=-a-RM_JVAmMCOmi zvp6z;+A)7VwbHM)esitgKKurkaGubJS?M(+utf`??a1c2XJ`#pzW4ck!MYaRb5(&> zc|Z66W6{KR{fcYx6QtY>-p?RDpVa?Fo~nF+G&xHk{+OgQ@7X^zeX`F;w|%lI?JyWOd@+pt^WTe)=AKP>D5_FrXBuUdYwO*yhZ-jRFx zMUUn1ZW1|PAbV}n=3SIC@)H9V`zCv$LVs20QXYB$+a`8S=v##@c)SD1u<=|A{UxD; zU(&&f0dOsJKlCT4Py92x4~OU%IxqE3c<-kF3;G?EhGTuB}3oxhC;9>4V(gVCA{f z`W1b2Dn(UFdwW}0h2)cUjHHdF9kR#NQ`+p3yN%GtTNnk_ui*ELq1kCSTJ+oam2sDT ziCuG#{X}>iD_3J>JQz=jySvBrM(7fc6db8n#<*I16W16Im9jF%@WPN8WNDFC4JN*u z>K^m{l22qxVwdQ*1J;=LfqsttemR?Ck3(Ph?$|VG|L!&w3!ufW39g`Qz}u!GfwTbn z`X}&9{GR9J7r&>#r!hCQiq3M6FMK(6_RStz{%TT zb%`F7n8euu@@@~k5#PVah$Apx_{PLm;{01B&NzdZlGJyqpH?X`3OS!?$0)9|VidA( zbA}j&;(TE2S%BSzV6^i ziAl?uBoEJR$FUdZtqDP!_-DrVZk~xw@`a5xy%&9*t?~oyIale`_Q{@pSJnYdNyfQ? zaNq{f=UTola)N%b_x!49Pw-HIu_p8p_p7F?D$^<^d?bE3?@~hkx^osGr))$htE`@N z{upT@aW--b^e|%L%S0f$dR}}na1}SZFg(vev zP0z~O4<54DF&SB!giPInY)wSQCg4Yp=j>{{u}>jwG1tuxi@ys}Pz_Yh@ z`#!ridR(P_H@n(L8w@+23XZg|tE+wMjd|e8nX${*d&tX;`ci9_QD4(Y^Sx-H>50g45_&P&cputHc+HuFJak>1VolQ> zUB^55^3u4j{x;7Psn8%9gZf`Kq#WRx&AGc(We3JrSEU^e$aryflJ@5{E#ZAD*=pgb z4W8UzQm#}B4NCB;uQ+pBk$V+BaIZM=nYXB~lJJ?eiehkNoi(5R$-BW5A9N}APgAza zs?)coVa^QJX7cXISS)E%)yCjN!Qy>x7309mq5M*hZ?lJI=?;{6HXr027dOEJ1%^i4 zYdF_cJmV5*J7Tkxul#w#oP%BKhn?BLvpmf80P9@E^Yk9QbePywu}j@4dmY%)K6$sg zysz+n+L+bUH25^WgYZuJA$IEUH5zq=wrwxFl6SImeuA^dQsxl+M0SaTr`3Gs?UeTg zXO|kXNBJArcagn>h7BIx8LPs95!k19SzF`Jh#vbGJh%Q2(9R2Wvi3I9_gm@R(Fr@{ zoKO#J8F4FluQ~QBY>e9z4SnS|JE5;Jmk#sGxul9+jN7^_^IcC0-e~l*zN2|&OXRVN zwmi@idnRi+_NT;e68W-a>p#8BInjtNlf0*ORk;B?DJyGk>4RKLKWbX&$D6KxykMu} zli2;Zl75JNeZs;F(B?MS0P%fRTC@Z3U+|x@%FnRNqZhV!SC#VK;2%5XQ)_OZKP#{y zB}y6ZBHTgSOPRawer>b*wUso zX|yku{qpGZC|RUQo?98i*fPtkGNtsXsZ7pP#$m(av1M(rX}rf|ijF==U|(IU7?>>M zrq!f@^(-;5Gs`*|F__S~Yg0r=1enia;81&^`sDY}~2kKs?EbHxwY#5feqHA zXl+GMiKyhF5{#`z@RqbCfnG9W=|zJktvv>?Jy`nn6lt}swoX8B0=7r)376*k{ydjt zG6W9xobMmM*YA&c&GYPMU)ElG?X}lld#$ziW#Vfud{qxHRzG*FYeSZQkDqH@4zm& z1Hi34j0?B+F!B>o-voObvYBB23b5Y~>^|r~uz#8H6Wq<8UX;Y#EscfuvHazAV)&+A8^Ue2%r;D

5=^w~Rr-F@X)B>8Ew-(6+LA0ecOd7c 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%0Ds%3_AoZ=5s@9*8^-=2y*ICX#a`u~ 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
af7~~8NP87~mZj`VB3UN) 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 jM&6f1?$cS1RnCaqH%04@ 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

YU@bErwY0+^RNae_wh|}Yk>ba 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 AuK)l5 diff --git a/crates/kit/scripts/bootc-install-to-disk.sh b/crates/kit/scripts/bootc-install-to-disk.sh new file mode 100644 index 000000000..a5f8f03ba --- /dev/null +++ b/crates/kit/scripts/bootc-install-to-disk.sh @@ -0,0 +1,22 @@ +#!/bin/bash +set -euo pipefail +@@RUST_LOG@@ +LOOP=$(@@SUDO@@losetup -fP --show @@DISK_PATH@@) +echo "Loop device: $LOOP" +trap '@@SUDO@@losetup -d $LOOP 2>/dev/null' EXIT + +printf '%s' '@@SSH_PUBKEY_B64@@' | base64 -d > /dev/shm/bcvk-ssh-key.pub + +echo "Running bootc install to-disk..." +podman run --rm --privileged --pid=host --net=none \ + -v /dev:/dev \ + -v /dev/shm:/dev/shm \ + -v /var/lib/containers:/var/lib/containers \ +@@LABEL_LINE@@ @@IMAGE@@ bootc install to-disk \ + --generic-image --skip-fetch-check --wipe \ + --root-ssh-authorized-keys /dev/shm/bcvk-ssh-key.pub \ + @@BOOTC_ARGS@@ $LOOP + +rm -f /dev/shm/bcvk-ssh-key.pub + +echo "Installation complete!" diff --git a/crates/kit/src/ephemeral_macos.rs b/crates/kit/src/ephemeral_macos.rs index 154bfeaa9..e70cc2055 100644 --- a/crates/kit/src/ephemeral_macos.rs +++ b/crates/kit/src/ephemeral_macos.rs @@ -209,9 +209,12 @@ fn cmd_ssh(name: &str, args: &[String]) -> Result<()> { let base = run_ephemeral_macos::ephemeral_base_dir(); let svc_sock = format!("{}/{}-gvproxy-svc.sock", base.display(), name); if std::path::Path::new(&svc_sock).exists() { - if let Err(e) = - run_ephemeral_macos::expose_port(&svc_sock, "192.168.127.2", vm.ssh_port, 22) - { + if let Err(e) = run_ephemeral_macos::expose_port( + &svc_sock, + crate::vm_helpers::GVPROXY_VM_IP, + vm.ssh_port, + 22, + ) { tracing::debug!("SSH port forward re-expose: {}", e); } } diff --git a/crates/kit/src/kernel_cmdline.rs b/crates/kit/src/kernel_cmdline.rs new file mode 100644 index 000000000..0edc12753 --- /dev/null +++ b/crates/kit/src/kernel_cmdline.rs @@ -0,0 +1,9 @@ +//! Cross-platform kernel command-line parameters shared across backends. + +/// Base kernel command-line parameters common to all backends. +#[allow(dead_code)] +pub const BASE_KERNEL_CMDLINE: &[&str] = &[ + "console=hvc0", + "selinux=0", + "systemd.journald.storage=volatile", +]; diff --git a/crates/kit/src/lib.rs b/crates/kit/src/lib.rs index db14bcf6d..0c9c19940 100644 --- a/crates/kit/src/lib.rs +++ b/crates/kit/src/lib.rs @@ -6,7 +6,9 @@ pub mod xml_utils; // Cross-platform modules pub mod install_options; +pub mod kernel_cmdline; pub mod ssh_options; +pub mod utils; // Linux-only modules #[cfg(target_os = "linux")] diff --git a/crates/kit/src/main.rs b/crates/kit/src/main.rs index 4078ac2c9..742464c3d 100644 --- a/crates/kit/src/main.rs +++ b/crates/kit/src/main.rs @@ -10,6 +10,7 @@ mod common_opts; mod cpio; mod install_options; mod instancetypes; +mod kernel_cmdline; mod qemu_img; mod ssh_options; mod xml_utils; @@ -56,7 +57,6 @@ mod supervisor_status; pub(crate) mod systemd; #[cfg(target_os = "linux")] mod to_disk; -#[cfg(target_os = "linux")] mod utils; #[cfg(target_os = "linux")] mod varlink_ipc; diff --git a/crates/kit/src/nbd_macos.rs b/crates/kit/src/nbd_macos.rs index 9d99ba1cb..a4e316a85 100644 --- a/crates/kit/src/nbd_macos.rs +++ b/crates/kit/src/nbd_macos.rs @@ -4,9 +4,11 @@ //! Common logic (deploy, systemd-run, stop) lives in vm_helpers.rs. use color_eyre::{eyre::bail, Result}; +use indicatif::ProgressBar; use std::time::Duration; use tracing::info; +use crate::utils::wait_for_readiness; use crate::vm_helpers; /// NBD server binary (aarch64 ELF), embedded at compile time. @@ -39,66 +41,66 @@ pub(crate) fn start_nbd_server( )?; // macOS-specific: unexpose stale entry then expose via gvproxy's in-VM API + let gw = vm_helpers::GVPROXY_GATEWAY; + let vm_ip = vm_helpers::GVPROXY_VM_IP; let unexpose_cmd = format!( - "curl -s -X POST http://192.168.127.1:80/services/forwarder/unexpose \ + "curl -s -X POST http://{gw}:80/services/forwarder/unexpose \ -H 'Content-Type: application/json' \ -d '{{\"local\":\":{nbd_port}\",\"protocol\":\"tcp\"}}' >/dev/null 2>&1; true", - nbd_port = nbd_port, ); if let Err(e) = vm_helpers::machine_ssh(machine, &unexpose_cmd) { tracing::debug!("failed to unexpose port {}: {}", nbd_port, e); } let expose_cmd = format!( - "curl -s -X POST http://192.168.127.1:80/services/forwarder/expose \ + "curl -s -X POST http://{gw}:80/services/forwarder/expose \ -H 'Content-Type: application/json' \ - -d '{{\"local\":\":{nbd_port}\",\"remote\":\"192.168.127.2:{nbd_port}\",\"protocol\":\"tcp\"}}'", - nbd_port = nbd_port, + -d '{{\"local\":\":{nbd_port}\",\"remote\":\"{vm_ip}:{nbd_port}\",\"protocol\":\"tcp\"}}'", ); - let mut exposed = false; - for i in 0..5 { - if let Ok(output) = vm_helpers::machine_ssh_output(machine, &expose_cmd) { - if output.status.success() { - exposed = true; - break; - } - let stderr = String::from_utf8_lossy(&output.stderr); - let stdout = String::from_utf8_lossy(&output.stdout); - tracing::debug!( - "gvproxy expose attempt {}: {}{}", - i + 1, - stdout.trim(), - stderr.trim() - ); - } - std::thread::sleep(Duration::from_millis(500)); - } - if !exposed { - bail!("gvproxy expose failed for port {}", nbd_port); - } + let expose_cmd_clone = expose_cmd.clone(); + let machine_clone = machine.to_string(); + wait_for_readiness( + ProgressBar::hidden(), + &format!("Exposing NBD port {}", nbd_port), + move || match vm_helpers::machine_ssh_output(&machine_clone, &expose_cmd_clone) { + Ok(output) if output.status.success() => Ok(true), + _ => Ok(false), + }, + Duration::from_secs(5), + Duration::from_millis(500), + ) + .map_err(|_| color_eyre::eyre::eyre!("gvproxy expose failed for port {}", nbd_port))?; info!("waiting for nbd server on port {}...", nbd_port); - loop { - if let Ok(mut stream) = std::net::TcpStream::connect_timeout( - &std::net::SocketAddr::from(([127, 0, 0, 1], nbd_port)), - Duration::from_millis(500), - ) { - use std::io::Read; - stream.set_read_timeout(Some(Duration::from_secs(2))).ok(); - let mut buf = [0u8; 8]; - if stream.read_exact(&mut buf).is_ok() && &buf == b"NBDMAGIC" { - break; + let machine_clone = machine.to_string(); + let unit_name_clone = unit_name.clone(); + wait_for_readiness( + ProgressBar::hidden(), + &format!("Waiting for NBD server on port {}", nbd_port), + move || { + if vm_helpers::is_nbd_unit_dead(&machine_clone, &unit_name_clone) { + bail!( + "nbd server '{}' died before becoming ready on port {}", + unit_name_clone, + nbd_port + ); } - } - if vm_helpers::is_nbd_unit_dead(machine, &unit_name) { - bail!( - "nbd server '{}' died before becoming ready on port {}", - unit_name, - nbd_port - ); - } - std::thread::sleep(Duration::from_millis(500)); - } + if let Ok(mut stream) = std::net::TcpStream::connect_timeout( + &std::net::SocketAddr::from(([127, 0, 0, 1], nbd_port)), + Duration::from_millis(500), + ) { + use std::io::Read; + stream.set_read_timeout(Some(Duration::from_secs(2))).ok(); + let mut buf = [0u8; 8]; + if stream.read_exact(&mut buf).is_ok() && &buf == b"NBDMAGIC" { + return Ok(true); + } + } + Ok(false) + }, + Duration::from_secs(30), + Duration::from_millis(500), + )?; Ok(unit_name) } @@ -132,9 +134,10 @@ pub fn stop_nbd_server(unit_name: &str, nbd_port: Option) { if let Err(e) = vm_helpers::machine_ssh( &machine, &format!( - "curl -sf -X POST http://192.168.127.1:80/services/forwarder/unexpose \ + "curl -sf -X POST http://{}:80/services/forwarder/unexpose \ -H 'Content-Type: application/json' \ -d '{{\"local\":\":{}\",\"protocol\":\"tcp\"}}'", + vm_helpers::GVPROXY_GATEWAY, port ), ) { diff --git a/crates/kit/src/run_ephemeral_macos.rs b/crates/kit/src/run_ephemeral_macos.rs index 4bd2603e3..e863d4098 100644 --- a/crates/kit/src/run_ephemeral_macos.rs +++ b/crates/kit/src/run_ephemeral_macos.rs @@ -253,42 +253,21 @@ fn run_vfkit(opts: RunEphemeralOpts) -> Result<()> { fs::create_dir_all(&cache_base)?; - // Generate SSH keypair on macOS host let mut ssh_pubkey = String::new(); if opts.ssh_keygen || !opts.execute.is_empty() { info!("generating SSH keypair..."); - crate::vm_helpers::remove_file_if_exists(&ssh_key_path); - crate::vm_helpers::remove_file_if_exists(&ssh_key_path.with_extension("pub")); - let status = Command::new("ssh-keygen") - .args([ - "-t", - "ed25519", - "-f", - &ssh_key_path.to_string_lossy(), - "-N", - "", - "-q", - ]) - .status()?; - if !status.success() { - bail!("ssh-keygen failed"); - } - ssh_pubkey = fs::read_to_string(ssh_key_path.with_extension("pub"))? - .trim() - .to_string(); + ssh_pubkey = crate::vm_helpers::generate_ssh_keypair(&ssh_key_path)?; } - let mut cmdline_parts: Vec<&str> = vec![ + let mut cmdline_parts: Vec<&str> = Vec::from(crate::kernel_cmdline::BASE_KERNEL_CMDLINE); + cmdline_parts.extend([ "root=/dev/vda2", "ro", "rootfstype=erofs", "console=tty0", - "console=hvc0", "loglevel=4", - "selinux=0", "net.ifnames=0", - "systemd.journald.storage=volatile", - ]; + ]); let user_args: Vec<&str> = opts.kernel_args.iter().map(|s| s.as_str()).collect(); cmdline_parts.extend(&user_args); let cmdline = cmdline_parts.join(" "); @@ -422,7 +401,12 @@ fn run_vfkit(opts: RunEphemeralOpts) -> Result<()> { if opts.ssh_keygen || !opts.execute.is_empty() { info!("setting up SSH port forwarding..."); for attempt in 0..15u32 { - match expose_port(&services_sock_str, "192.168.127.2", ssh_port, 22) { + match expose_port( + &services_sock_str, + crate::vm_helpers::GVPROXY_VM_IP, + ssh_port, + 22, + ) { Ok(_) => { info!("SSH port {} forwarded", ssh_port); break; @@ -577,7 +561,7 @@ pub fn find_vfkit() -> Result { bail!("vfkit not found. Install: brew install vfkit") } -/// Fixed MAC address matching gvproxy's DHCP static lease for 192.168.127.2. +/// Fixed MAC address matching gvproxy's DHCP static lease for [`GVPROXY_VM_IP`](crate::vm_helpers::GVPROXY_VM_IP). const GVPROXY_STATIC_MAC: [u8; 6] = [0x5a, 0x94, 0xef, 0xe4, 0x0c, 0xee]; /// Generate the fixed MAC address for gvproxy DHCP static lease. diff --git a/crates/kit/src/to_disk_macos.rs b/crates/kit/src/to_disk_macos.rs index 834ea1398..5e2623031 100644 --- a/crates/kit/src/to_disk_macos.rs +++ b/crates/kit/src/to_disk_macos.rs @@ -137,37 +137,14 @@ fn generate_bootc_install_script( format!(" {} \\\n", label_args) }; - format!( - r#"set -euo pipefail -{rust_log} -LOOP=$({sudo}losetup -fP --show {disk_path}) -echo "Loop device: $LOOP" -trap '{sudo}losetup -d $LOOP 2>/dev/null' EXIT - -printf '%s' '{b64}' | base64 -d > /dev/shm/bcvk-ssh-key.pub - -echo "Running bootc install to-disk..." -podman run --rm --privileged --pid=host --net=none \ - -v /dev:/dev \ - -v /dev/shm:/dev/shm \ - -v /var/lib/containers:/var/lib/containers \ -{label_line} {image} bootc install to-disk \ - --generic-image --skip-fetch-check --wipe \ - --root-ssh-authorized-keys /dev/shm/bcvk-ssh-key.pub \ - {bootc_args} $LOOP - -rm -f /dev/shm/bcvk-ssh-key.pub - -echo "Installation complete!" -"#, - rust_log = rust_log_line, - sudo = sudo, - disk_path = disk_path_in_machine, - b64 = pub_key_b64, - image = image_quoted, - bootc_args = bootc_args, - label_line = label_line, - ) + include_str!("../scripts/bootc-install-to-disk.sh") + .replace("@@RUST_LOG@@", &rust_log_line) + .replace("@@SUDO@@", sudo) + .replace("@@DISK_PATH@@", disk_path_in_machine) + .replace("@@SSH_PUBKEY_B64@@", &pub_key_b64) + .replace("@@LABEL_LINE@@", &label_line) + .replace("@@IMAGE@@", &image_quoted) + .replace("@@BOOTC_ARGS@@", &bootc_args) } const CACHE_HASH_XATTR: &str = "user.bcvk.cache_hash"; @@ -388,6 +365,7 @@ pub fn run(opts: ToDiskMacosOpts) -> Result<()> { Ok(()) } +#[cfg(test)] #[cfg(test)] mod tests { use super::*; diff --git a/crates/kit/src/utils.rs b/crates/kit/src/utils.rs index ac0649184..93ab18bb0 100644 --- a/crates/kit/src/utils.rs +++ b/crates/kit/src/utils.rs @@ -1,13 +1,21 @@ -use camino::{Utf8Path, Utf8PathBuf}; -use color_eyre::eyre::{eyre, Context}; +//! Cross-platform utility functions. + +use color_eyre::eyre::eyre; use color_eyre::Result; use indicatif::ProgressBar; -use std::io::{Seek as _, Write as _}; -use std::os::fd::OwnedFd; use std::time::{Duration, Instant}; +use tracing::debug; +#[cfg(target_os = "linux")] +use camino::{Utf8Path, Utf8PathBuf}; +#[cfg(target_os = "linux")] use cap_std_ext::cap_std::io_lifetimes::AsFilelike as _; -use tracing::debug; +#[cfg(target_os = "linux")] +use color_eyre::eyre::Context as _; +#[cfg(target_os = "linux")] +use std::io::{Seek as _, Write as _}; +#[cfg(target_os = "linux")] +use std::os::fd::OwnedFd; /// Wait for a condition to become ready with progress indication /// @@ -60,7 +68,7 @@ where debug!("Readiness check attempt {} returned false", attempt); } Err(e) => { - debug!("Readiness check attempt {} failed: {}", attempt, e); + return Err(e); } } @@ -75,8 +83,8 @@ where )) } +#[cfg(target_os = "linux")] /// Creates a sealed memory file descriptor for secure data transfer. -/// The sealed memfd cannot be modified after creation, providing tamper protection. #[allow(dead_code)] pub(crate) fn impl_sealed_memfd(description: &str, content: &[u8]) -> Result { use rustix::fs::{MemfdFlags, SealFlags}; @@ -97,7 +105,7 @@ pub(crate) fn impl_sealed_memfd(description: &str, content: &[u8]) -> Result Result { use std::process::Command; @@ -143,7 +151,7 @@ pub(crate) fn detect_container_storage_path() -> Result { Ok(storage_path) } -/// Validate that a container storage path exists and has the expected structure +#[cfg(target_os = "linux")] pub(crate) fn validate_container_storage_path(path: &Utf8Path) -> Result<()> { if !path.exists() { return Err(eyre!("Container storage path does not exist: {}", path)); @@ -167,7 +175,7 @@ pub(crate) fn validate_container_storage_path(path: &Utf8Path) -> Result<()> { Ok(()) } -/// Parse size string (e.g., "10G", "5120M", "1T") to bytes +#[cfg(target_os = "linux")] pub(crate) fn parse_size(size_str: &str) -> Result { let size_str = size_str.trim().to_uppercase(); @@ -206,7 +214,7 @@ pub(crate) fn parse_size(size_str: &str) -> Result { Ok(number * multiplier) } -/// Parse a memory string (like "2G", "1024M", "512") to megabytes +#[cfg(target_os = "linux")] pub(crate) fn parse_memory_to_mb(memory_str: &str) -> Result { let memory_str = memory_str.trim(); diff --git a/crates/kit/src/vfkit/run.rs b/crates/kit/src/vfkit/run.rs index 95273cd3d..93afdd4db 100644 --- a/crates/kit/src/vfkit/run.rs +++ b/crates/kit/src/vfkit/run.rs @@ -299,7 +299,12 @@ pub fn run(opts: VmRunOpts) -> Result<()> { info!("setting up SSH port forwarding..."); for attempt in 0..15u32 { - match expose_port(&services_sock_str, "192.168.127.2", ssh_port, 22) { + match expose_port( + &services_sock_str, + crate::vm_helpers::GVPROXY_VM_IP, + ssh_port, + 22, + ) { Ok(_) => { info!("SSH port {} forwarded", ssh_port); break; @@ -316,7 +321,7 @@ pub fn run(opts: VmRunOpts) -> Result<()> { for pm in &opts.port_mappings { expose_port( &services_sock_str, - "192.168.127.2", + crate::vm_helpers::GVPROXY_VM_IP, pm.host_port, pm.guest_port, )?; diff --git a/crates/kit/src/vfkit/start.rs b/crates/kit/src/vfkit/start.rs index 67975463d..5b969abac 100644 --- a/crates/kit/src/vfkit/start.rs +++ b/crates/kit/src/vfkit/start.rs @@ -87,7 +87,12 @@ pub fn run(opts: VmStartOpts) -> Result<()> { info!("setting up SSH port forwarding..."); for attempt in 0..15u32 { - match expose_port(&services_sock_str, "192.168.127.2", meta.ssh_port, 22) { + match expose_port( + &services_sock_str, + crate::vm_helpers::GVPROXY_VM_IP, + meta.ssh_port, + 22, + ) { Ok(_) => { info!("SSH port {} forwarded", meta.ssh_port); break; @@ -102,7 +107,12 @@ pub fn run(opts: VmStartOpts) -> Result<()> { } for &(host_port, guest_port) in &meta.port_mappings { - expose_port(&services_sock_str, "192.168.127.2", host_port, guest_port)?; + expose_port( + &services_sock_str, + crate::vm_helpers::GVPROXY_VM_IP, + host_port, + guest_port, + )?; info!("port {}:{} forwarded", host_port, guest_port); } diff --git a/crates/kit/src/vm_helpers.rs b/crates/kit/src/vm_helpers.rs index c5fdf5d4d..639f8d2cf 100644 --- a/crates/kit/src/vm_helpers.rs +++ b/crates/kit/src/vm_helpers.rs @@ -12,6 +12,12 @@ use tracing::info; use crate::ssh_options::CommonSshOptions; +/// gvproxy default gateway IP (from `-subnet 192.168.127.0/24`). +pub(crate) const GVPROXY_GATEWAY: &str = "192.168.127.1"; + +/// VM device IP assigned by gvproxy DHCP static lease. +pub(crate) const GVPROXY_VM_IP: &str = "192.168.127.2"; + /// SSH connection timeout (shared by wait_for_ssh). pub const SSH_TIMEOUT: Duration = Duration::from_secs(240);