diff --git a/crates/kit/src/containerenv.rs b/crates/kit/src/containerenv.rs deleted file mode 100644 index 85f060c04..000000000 --- a/crates/kit/src/containerenv.rs +++ /dev/null @@ -1,57 +0,0 @@ -//! Helpers for parsing the `/run/.containerenv` file generated by podman. - -use std::io::{BufRead, BufReader}; - -use cap_std_ext::cap_std::fs::Dir; -use cap_std_ext::prelude::CapStdExtDirExt; -use color_eyre::Result; -use serde::{Deserialize, Serialize}; - -/// Path is relative to container rootfs (assumed to be /) -pub(crate) const PATH: &str = "run/.containerenv"; - -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub(crate) struct ContainerExecutionInfo { - pub(crate) engine: String, - pub(crate) name: String, - pub(crate) id: String, - pub(crate) image: String, - pub(crate) imageid: String, - pub(crate) rootless: Option, -} - -/// Return true if this environment is detected as a container -pub fn is_container(rootfs: &Dir) -> Result { - let r = rootfs.try_exists(PATH)?; - Ok(r) -} - -/// Load and parse the `/run/.containerenv` file. -pub(crate) fn get_container_execution_info(rootfs: &Dir) -> Result> { - let f = match rootfs.open_optional(PATH)? { - Some(f) => BufReader::new(f), - None => { - return Ok(None); - } - }; - let mut r = ContainerExecutionInfo::default(); - for line in f.lines() { - let line = line?; - let line = line.trim(); - let Some((k, v)) = line.split_once('=') else { - continue; - }; - // Assuming there's no quotes here - let v = v.trim_start_matches('"').trim_end_matches('"'); - match k { - "engine" => r.engine = v.to_string(), - "name" => r.name = v.to_string(), - "id" => r.id = v.to_string(), - "image" => r.image = v.to_string(), - "imageid" => r.imageid = v.to_string(), - "rootless" => r.rootless = Some(v.to_string()), - _ => {} - } - } - Ok(Some(r)) -} diff --git a/crates/kit/src/envdetect.rs b/crates/kit/src/envdetect.rs deleted file mode 100644 index f6109cac2..000000000 --- a/crates/kit/src/envdetect.rs +++ /dev/null @@ -1,107 +0,0 @@ -//! Environment detection for containerized and host environments -//! -//! Detects container environments, privilege levels (--privileged, --pid=host), -//! and extracts container metadata. Results are cached for performance. -//! - -use std::{ - os::unix::fs::MetadataExt, - path::Path, - sync::{Arc, OnceLock}, -}; - -use color_eyre::{eyre::Context, Result}; - -use cap_std_ext::cap_std::{self, fs::Dir}; -use serde::{Deserialize, Serialize}; - -/// Environment detection results -/// -/// Contains detected container state, privilege levels, and optional -/// container metadata. All fields are determined once and cached. -#[derive(Debug, Default, Serialize, Deserialize)] -pub struct Environment { - /// Container run with --privileged - pub privileged: bool, - /// Container run with --pid=host - pub pidhost: bool, - /// Running in container (detected via /run/.containerenv) - pub container: bool, - /// Parsed container execution info - pub containerenv: Option, -} - -/// Detect if running with host PID namespace access (--pid=host) -/// -/// Checks for UID differences between parent and current process, or -/// compares mount namespaces if UIDs are the same. -fn is_hostpid() -> Result { - let Some(ppid) = rustix::process::getppid() else { - return Ok(false); - }; - let myuid = rustix::process::getuid(); - let parent_proc = format!("/proc/{}", ppid.as_raw_nonzero()); - let parent_st = Path::new(&parent_proc).metadata()?; - // If the parent has a different uid, that's a strong signal we're - // running with a uid mapping but we can see our real parent in the - // host pidns. - if parent_st.uid() != myuid.as_raw() { - return Ok(true); - } - let parent_rootns = std::fs::read_link(format!("/proc/{}/ns/mnt", ppid.as_raw_nonzero())) - .context("Reading parent mountns")?; - let my_rootns = std::fs::read_link("/proc/self/ns/mnt").context("Reading self mountns")?; - Ok(parent_rootns != my_rootns) -} - -/// Get cached root filesystem directory handle -/// -/// Returns thread-safe cached `Arc` for root filesystem access. -/// Uses `OnceLock` for lazy initialization and concurrent safety. -pub(crate) fn global_rootfs(authority: cap_std::AmbientAuthority) -> Result> { - static ROOTFS: OnceLock> = OnceLock::new(); - if let Some(r) = ROOTFS.get() { - return Ok(r.clone()); - } - let r = Dir::open_ambient_dir("/", authority)?; - let _ = ROOTFS.set(Arc::new(r)); - Ok(ROOTFS.get().unwrap().clone()) -} - -impl Environment { - /// Detect current execution environment - /// - /// Performs privilege detection, container detection, and namespace analysis. - /// Designed to handle partial failures gracefully. - fn new() -> Result { - let rootfs = &global_rootfs(cap_std::ambient_authority())?; - let privileged = rustix::thread::capability_is_in_bounding_set( - rustix::thread::CapabilitySet::SYS_ADMIN, - )?; - let container = super::containerenv::is_container(&rootfs)?; - let containerenv = super::containerenv::get_container_execution_info(&rootfs)?; - let pidhost = is_hostpid()?; - Ok(Environment { - privileged, - pidhost, - containerenv, - container, - }) - } - - /// Get cached Environment instance - /// - /// Performs detection once per process lifetime and caches results. - /// Thread-safe with `OnceLock`. Retries on failure until successful. - pub fn get_cached() -> Result<&'static Self> { - static INFO: std::sync::OnceLock = std::sync::OnceLock::new(); - if let Some(r) = INFO.get() { - return Ok(r); - } - let r = Self::new()?; - // Discard duplicate initialization attempts from concurrent threads - let _ = INFO.set(r); - // SAFETY: We confirmed initialization occurred above - Ok(INFO.get().unwrap()) - } -} diff --git a/crates/kit/src/ephemeral.rs b/crates/kit/src/ephemeral.rs index 3bd58def2..b9522428c 100644 --- a/crates/kit/src/ephemeral.rs +++ b/crates/kit/src/ephemeral.rs @@ -4,13 +4,14 @@ //! Ephemeral VMs are temporary, non-persistent VMs that are useful for testing, development, //! and CI/CD workflows. +use std::process::Command; + use clap::Subcommand; use color_eyre::{eyre::eyre, Result}; use comfy_table::{presets::UTF8_FULL, Table}; use serde::{Deserialize, Serialize}; // Re-export the existing implementations -use crate::hostexec; use crate::run_ephemeral; use crate::run_ephemeral_ssh; use crate::ssh; @@ -160,7 +161,7 @@ impl EphemeralCommands { fn list_ephemeral_containers() -> Result> { use bootc_utils::CommandRunExt; - let containers: Vec = hostexec::command("podman", None)? + let containers: Vec = Command::new("podman") .args([ "ps", "--all", @@ -214,7 +215,7 @@ fn remove_all_ephemeral_containers(force: bool) -> Result<()> { "Removing container {}", &container.id[..12.min(container.id.len())] ); - let result = hostexec::command("podman", None)? + let result = Command::new("podman") .args(["rm", "-f", &container.id]) .run(); diff --git a/crates/kit/src/hostexec.rs b/crates/kit/src/hostexec.rs deleted file mode 100644 index 60c12d906..000000000 --- a/crates/kit/src/hostexec.rs +++ /dev/null @@ -1,166 +0,0 @@ -//! Host command execution from containerized environments -//! -//! Executes commands on the host system from containers using systemd-run. -//! Commands are bound to container lifecycle for automatic cleanup. -//! Requires --privileged and --pid=host container flags. -//! -use std::ffi::OsStr; -use std::io::BufRead; -use std::os::unix::ffi::OsStrExt; -use std::process::Command; -use std::{collections::HashMap, ffi::OsString}; - -use bootc_utils::CommandRunExt; -use color_eyre::eyre::eyre; -use color_eyre::Result; -use rand::distr::SampleString; - -use crate::containerenv::ContainerExecutionInfo; - -/// Configuration for systemd-run execution -#[derive(Debug, Default)] -pub struct SystemdConfig { - /// Run detached without output capture (default: false uses --pipe) - detached: bool, -} - -/// Validate host execution environment -/// -/// Returns None for host system (direct exec), Some(info) for valid container -/// with --privileged and --pid=host, or error for insufficient privileges. -fn ensure_hostexec_initialized() -> Result> { - // Check if we're in a toolbox environment - if so, we're on the host - if std::env::var("TOOLBOX_PATH").is_ok() { - return Ok(None); - } - - let hostenv = crate::envdetect::Environment::get_cached()?; - if !hostenv.container { - return Ok(None); - } - let Some(info) = hostenv.containerenv.as_ref() else { - return Err(eyre!("This command requires running with --privileged")); - }; - if !hostenv.privileged { - return Err(eyre!("This command requires running with --privileged")); - } - // This should be filled if run with --privileged and we're in a container - if !hostenv.pidhost { - return Err(eyre!("This command requires running with --pid=host")); - } - - Ok(Some(info)) -} - -/// Create Command for host execution -/// -/// Returns direct Command for host systems, or systemd-run wrapped Command -/// for containers. Generated service units are bound to container lifecycle. -pub fn command(exe: impl AsRef, config: Option) -> Result { - let exe = exe.as_ref(); - let config = config.unwrap_or_default(); - - let Some(info) = ensure_hostexec_initialized()? else { - return Ok(Command::new(exe)); - }; - - let containerid = &info.id; - // A random suffix, 8 alphanumeric chars gives 62 ** 8 possibilities, so low chance of collision - // And we only care about such collissions for *concurrent* processes bound to *the same* - // podman container ID; after a unit has exited it's fine if we reuse an ID. - let runid = rand::distr::Alphanumeric.sample_string(&mut rand::rng(), 8); - let unit = format!("hostcmd-{containerid}-{runid}.service"); - let scope = format!("libpod-{containerid}.scope"); - let properties = [format!("BindsTo={scope}"), format!("After={scope}")]; - - let properties = properties.into_iter().flat_map(|p| ["-p".to_owned(), p]); - let mut r = Command::new("systemd-run"); - // Note that we need to specify this ExecSearchPath property to suppress heuristics - // systemd-run has to search for the binary, which in the general case won't exist - // in the container. - r.args([ - "--quiet", - "--collect", - "-u", - unit.as_str(), - "--property=ExecSearchPath=/usr/bin", - ]); - if !config.detached { - r.arg("--pipe"); - } - if info.rootless.is_some() { - r.arg("--user"); - } - r.args(properties); - r.arg("--"); - r.arg(exe); - Ok(r) -} - -/// Execute command on host with error handling -/// -/// Convenience function that creates, executes, and validates command exit status. -/// Inherits stdin/stdout/stderr from calling process. -pub fn run(exe: impl AsRef, args: I) -> Result<()> -where - I: IntoIterator, - T: Into + Clone, -{ - let mut c = command(exe, None)?; - c.args(args.into_iter().map(|c| c.into())); - c.run().map_err(|e| eyre!("{e:?}")) -} - -/// Create podman command for host execution -/// -/// Convenience wrapper around `command("podman", None)` for container -/// management operations that need host access. -#[allow(dead_code)] -pub fn podman() -> Result { - command("podman", None) -} - -/// Parse environment variables from KEY=VALUE format -/// -/// Parses output from `env` command into HashMap. Handles binary data -/// and multiple `=` characters in values. -#[allow(dead_code)] -fn parse_env(e: impl BufRead) -> Result> { - e.split(b'\n').try_fold(HashMap::new(), |mut r, line| { - let line = line?; - let mut split = line.split(|&c| c == b'='); - let Some(k) = split.next() else { - return Ok(r); - }; - let Some(v) = split.next() else { - return Ok(r); - }; - r.insert( - OsStr::from_bytes(k).to_owned(), - OsStr::from_bytes(v).to_owned(), - ); - Ok(r) - }) -} - -#[cfg(test)] -mod tests { - use std::io::Cursor; - - use super::*; - - /// Test environment variable parsing - #[test] - fn test_parse_env() { - let input = b"FOO=bar\nBAZ=quux\n"; - let expected: HashMap = [ - (OsStr::new("FOO"), OsStr::new("bar")), - (OsStr::new("BAZ"), OsStr::new("quux")), - ] - .into_iter() - .map(|(k, v)| (k.to_owned(), v.to_owned())) - .collect(); - let actual = parse_env(Cursor::new(input)).unwrap(); - assert_eq!(actual, expected); - } -} diff --git a/crates/kit/src/images.rs b/crates/kit/src/images.rs index c25bc65e2..ccede7942 100644 --- a/crates/kit/src/images.rs +++ b/crates/kit/src/images.rs @@ -4,14 +4,13 @@ //! podman integration with both table and JSON output formats. use std::collections::HashMap; +use std::process::Command; use bootc_utils::CommandRunExt; use color_eyre::{eyre::eyre, Result}; use comfy_table::{presets::UTF8_FULL, Table}; use serde::{Deserialize, Serialize}; -use crate::hostexec; - /// Command-line options for image management operations. #[derive(clap::Subcommand, Debug)] pub(crate) enum ImagesOpts { @@ -188,7 +187,7 @@ fn parse_osrelease(s: &str) -> Result> { /// List all bootc container images using podman. #[allow(dead_code)] pub fn list() -> Result> { - let images: Vec = hostexec::command("podman", None)? + let images: Vec = Command::new("podman") .args([ "images", "--format", @@ -202,7 +201,7 @@ pub fn list() -> Result> { /// Inspect a container image and return metadata. pub fn inspect(name: &str) -> Result { - let mut r: Vec = hostexec::command("podman", None)? + let mut r: Vec = Command::new("podman") .args(["image", "inspect", name]) .run_and_parse_json() .map_err(|e| eyre!("{e}"))?; diff --git a/crates/kit/src/libvirt/mod.rs b/crates/kit/src/libvirt/mod.rs index ce27b0e6b..1f72b0fe9 100644 --- a/crates/kit/src/libvirt/mod.rs +++ b/crates/kit/src/libvirt/mod.rs @@ -48,13 +48,9 @@ pub struct LibvirtOptions { } impl LibvirtOptions { - /// Create a virsh Command with the appropriate connection URI using host execution - /// - /// Note: This method may panic if host execution setup fails, but this should - /// only happen in misconfigured environments where container lacks required privileges + /// Create a virsh Command with the appropriate connection URI pub fn virsh_command(&self) -> std::process::Command { - let mut cmd = crate::hostexec::command("virsh", None) - .expect("Failed to setup host execution for virsh - ensure container has --privileged and --pid=host"); + let mut cmd = std::process::Command::new("virsh"); if let Some(ref uri) = self.connect { cmd.arg("-c").arg(uri); } diff --git a/crates/kit/src/libvirt/run.rs b/crates/kit/src/libvirt/run.rs index 8fca34557..0613cf98d 100644 --- a/crates/kit/src/libvirt/run.rs +++ b/crates/kit/src/libvirt/run.rs @@ -21,7 +21,7 @@ use crate::xml_utils; /// Create a virsh command with optional connection URI pub(super) fn virsh_command(connect_uri: Option<&str>) -> Result { - let mut cmd = crate::hostexec::command("virsh", None)?; + let mut cmd = std::process::Command::new("virsh"); if let Some(uri) = connect_uri { cmd.arg("-c").arg(uri); } diff --git a/crates/kit/src/libvirt/secureboot.rs b/crates/kit/src/libvirt/secureboot.rs index da2f0f105..01c9f2f4e 100644 --- a/crates/kit/src/libvirt/secureboot.rs +++ b/crates/kit/src/libvirt/secureboot.rs @@ -2,8 +2,6 @@ //! //! This module provides utilities for loading existing UEFI Secure Boot //! keys (PK, KEK, db) and customizing OVMF firmware variables for VMs. -//! All operations are designed to work with host-side tools when running -//! from a container environment. use camino::{Utf8Path, Utf8PathBuf}; use color_eyre::{eyre::eyre, Result}; @@ -34,10 +32,10 @@ pub struct SecureBootKeys { } impl SecureBootKeys { - /// Load existing secure boot keys from a directory using host-aware checks + /// Load existing secure boot keys from a directory pub fn load(key_dir: &Utf8Path) -> Result { - // Use host-side test command to check if directory exists - let mut test_dir = crate::hostexec::command("test", None)?; + // Check if directory exists + let mut test_dir = std::process::Command::new("test"); test_dir.args(["-d", key_dir.as_str()]); if !test_dir.status()?.success() { @@ -49,8 +47,8 @@ impl SecureBootKeys { let guid_file = key_dir.join("GUID.txt"); - // Use host-side cat command to read GUID file - let mut cat_guid = crate::hostexec::command("cat", None)?; + // Read GUID file + let mut cat_guid = std::process::Command::new("cat"); cat_guid.arg(guid_file.as_str()); let guid_output = cat_guid.output()?; @@ -73,7 +71,7 @@ impl SecureBootKeys { guid, }; - // Verify all required files exist using host-side test commands + // Verify all required files exist let required_files = [ (&keys.pk_cert, "PK.crt"), (&keys.kek_cert, "KEK.crt"), @@ -81,7 +79,7 @@ impl SecureBootKeys { ]; for (path, name) in &required_files { - let mut test_file = crate::hostexec::command("test", None)?; + let mut test_file = std::process::Command::new("test"); test_file.args(["-f", path.as_str()]); if !test_file.status()?.success() { @@ -97,25 +95,25 @@ impl SecureBootKeys { } } -/// Customize OVMF variables with secure boot keys using host-side execution +/// Customize OVMF variables with secure boot keys pub fn customize_ovmf_vars( keys: &SecureBootKeys, ovmf_vars_path: &Utf8Path, output_path: &Utf8Path, ) -> Result<()> { - // Check if virt-fw-vars is available on the host - let mut check = crate::hostexec::command("which", None)?; + // Check if virt-fw-vars is available + let mut check = std::process::Command::new("which"); check.arg("virt-fw-vars"); let check_output = check.output()?; if !check_output.status.success() { return Err(eyre!( - "virt-fw-vars not found on host. Install it with: dnf install -y python3-virt-firmware" + "virt-fw-vars not found. Install it with: dnf install -y python3-virt-firmware" )); } - // Use virt-fw-vars to inject keys into OVMF_VARS via host execution - let mut cmd = crate::hostexec::command("virt-fw-vars", None)?; + // Use virt-fw-vars to inject keys into OVMF_VARS + let mut cmd = std::process::Command::new("virt-fw-vars"); cmd.args([ "--input", ovmf_vars_path.as_str(), @@ -156,8 +154,8 @@ pub fn setup_secure_boot(key_dir: &Utf8Path) -> Result { // Find the system OVMF_VARS.fd let ovmf_vars = find_ovmf_vars()?; - // Check if custom vars template already exists using host-side test - let mut test_template = crate::hostexec::command("test", None)?; + // Check if custom vars template already exists + let mut test_template = std::process::Command::new("test"); test_template.args(["-f", vars_template.as_str()]); if !test_template.status()?.success() { @@ -172,7 +170,7 @@ pub fn setup_secure_boot(key_dir: &Utf8Path) -> Result { }) } -/// Find the system OVMF_VARS.fd file using host-side checks +/// Find the system OVMF_VARS.fd file fn find_ovmf_vars() -> Result { // Common locations for OVMF_VARS.fd let locations = [ @@ -183,7 +181,7 @@ fn find_ovmf_vars() -> Result { ]; for path in &locations { - let mut test_file = crate::hostexec::command("test", None)?; + let mut test_file = std::process::Command::new("test"); test_file.args(["-f", path]); if test_file.status()?.success() { @@ -192,11 +190,11 @@ fn find_ovmf_vars() -> Result { } Err(eyre!( - "Could not find OVMF_VARS.fd on host. Please install edk2-ovmf package." + "Could not find OVMF_VARS.fd. Please install edk2-ovmf package." )) } -/// Find the secure boot OVMF_CODE file using host-side checks +/// Find the secure boot OVMF_CODE file pub fn find_ovmf_code_secboot() -> Result { // Common locations for OVMF_CODE.secboot.fd let locations = [ @@ -207,7 +205,7 @@ pub fn find_ovmf_code_secboot() -> Result { ]; for path in &locations { - let mut test_file = crate::hostexec::command("test", None)?; + let mut test_file = std::process::Command::new("test"); test_file.args(["-f", path]); if test_file.status()?.success() { @@ -216,7 +214,7 @@ pub fn find_ovmf_code_secboot() -> Result { } Err(eyre!( - "Could not find OVMF_CODE.secboot.fd on host. Please install edk2-ovmf package." + "Could not find OVMF_CODE.secboot.fd. Please install edk2-ovmf package." )) } @@ -226,9 +224,7 @@ mod tests { use std::fs; use tempfile::TempDir; - // Note: These tests assume we're running in an environment where - // hostexec::command can work directly (not in a container requiring systemd-run) - // In CI/testing environments, this should work as direct command execution + // Note: These tests use direct command execution #[test] fn test_load_missing_directory() { diff --git a/crates/kit/src/main.rs b/crates/kit/src/main.rs index dcee97747..2d831e285 100644 --- a/crates/kit/src/main.rs +++ b/crates/kit/src/main.rs @@ -1,7 +1,5 @@ //! Bootc Virtualization Kit (bcvk) - A toolkit for bootc containers and local virtualization -use std::ffi::OsString; - use cap_std_ext::cap_std::fs::Dir; use clap::{Parser, Subcommand}; use color_eyre::{eyre::Context as _, Report, Result}; @@ -12,12 +10,9 @@ mod cache_metadata; mod cli_json; mod common_opts; mod container_entrypoint; -pub(crate) mod containerenv; mod credentials; mod domain_list; -mod envdetect; mod ephemeral; -mod hostexec; mod images; mod install_options; mod instancetypes; @@ -25,7 +20,6 @@ mod libvirt; mod libvirt_upload_disk; #[allow(dead_code)] mod podman; -#[allow(dead_code)] mod qemu; mod qemu_img; mod run_ephemeral; @@ -53,25 +47,6 @@ struct Cli { command: Commands, } -/// Execute a command in the host context from within a container. -/// -/// This allows containers to run host commands with proper isolation -/// and resource management through the host execution system. -#[derive(Parser)] -struct HostExecOpts { - /// Binary executable to run on the host system - /// - /// Can be a full path or a command name available in PATH. - bin: OsString, - - /// Command-line arguments to pass to the binary - /// - /// All arguments after the binary name, including flags and options. - /// Supports arguments starting with hyphens. - #[clap(allow_hyphen_values = true)] - args: Vec, -} - #[derive(Parser)] struct DebugInternalsOpts { #[command(subcommand)] @@ -100,10 +75,6 @@ enum InternalsCmds { /// Available bcvk commands for container and VM management. #[derive(Subcommand)] enum Commands { - /// Execute commands on the host system from within containers - #[clap(hide = true)] - Hostexec(HostExecOpts), - /// Manage and inspect bootc container images #[clap(subcommand)] Images(images::ImagesOpts), @@ -186,9 +157,6 @@ fn main() -> Result<(), Report> { .context("Init tokio runtime")?; match cli.command { - Commands::Hostexec(opts) => { - hostexec::run(opts.bin, opts.args)?; - } Commands::Images(opts) => opts.run()?, Commands::Ephemeral(cmd) => cmd.run()?, Commands::ToDisk(opts) => { diff --git a/crates/kit/src/podman.rs b/crates/kit/src/podman.rs index dd4de7f0f..923adf443 100644 --- a/crates/kit/src/podman.rs +++ b/crates/kit/src/podman.rs @@ -1,9 +1,9 @@ +use std::process::Command; + use bootc_utils::CommandRunExt; use color_eyre::{eyre::eyre, Result}; use serde::Deserialize; -use crate::hostexec; - #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Store { @@ -25,7 +25,7 @@ pub struct ImageInspect { } pub fn get_system_info() -> Result { - hostexec::podman()? + Command::new("podman") .arg("system") .arg("info") .arg("--format=json") @@ -35,7 +35,7 @@ pub fn get_system_info() -> Result { /// Get the size of a container image in bytes pub fn get_image_size(image: &str) -> Result { - let inspect_result: Vec = hostexec::podman()? + let inspect_result: Vec = Command::new("podman") .arg("inspect") .arg("--format=json") .arg("--type=image") diff --git a/crates/kit/src/qemu.rs b/crates/kit/src/qemu.rs index c483c6710..39f5e3af6 100644 --- a/crates/kit/src/qemu.rs +++ b/crates/kit/src/qemu.rs @@ -1,7 +1,7 @@ //! QEMU virtualization integration and VM management. //! -//! Supports direct kernel boot and disk image boot with VirtIO devices, -//! automatic process cleanup, and SMBIOS credential injection. +//! Supports direct kernel boot with VirtIO devices, automatic process cleanup, +//! and SMBIOS credential injection. use std::fs::{File, OpenOptions}; use std::future::Future; @@ -111,7 +111,7 @@ impl Default for ResourceLimits { } } -/// VM boot configuration: direct kernel boot or disk image boot. +/// VM boot configuration: direct kernel boot. #[derive(Debug)] pub enum BootMode { /// Direct kernel boot (fast, testing-focused) @@ -122,12 +122,6 @@ pub enum BootMode { /// VirtIO-FS socket for root filesystem virtiofs_socket: Utf8PathBuf, }, - #[allow(dead_code)] - DiskBoot { - primary_disk: String, - /// Use UEFI instead of BIOS - uefi: bool, - }, } /// Complete QEMU VM configuration with builder pattern. @@ -153,10 +147,6 @@ pub struct QemuConfig { pub resource_limits: ResourceLimits, /// Deprecated: use display_mode pub enable_console: bool, - /// UEFI firmware path (auto-detected if None) - pub uefi_firmware_path: Option, - /// UEFI variables file - pub uefi_vars_path: Option, /// SMBIOS credentials for systemd smbios_credentials: Vec, @@ -207,18 +197,6 @@ impl QemuConfig { self } - /// Enable UEFI boot (only for disk boot) - #[allow(dead_code)] - pub fn set_uefi_boot(&mut self, uefi: bool) -> &mut Self { - if let Some(BootMode::DiskBoot { - uefi: uefi_flag, .. - }) = self.boot_mode.as_mut() - { - *uefi_flag = uefi; - } - self - } - /// Enable console output pub fn set_console(&mut self, enable: bool) -> &mut Self { self.enable_console = enable; @@ -486,67 +464,6 @@ fn spawn( let append_str = kernel_cmdline.join(" "); cmd.args(["-append", &append_str]); } - Some(BootMode::DiskBoot { primary_disk, uefi }) => { - // Configure UEFI firmware if requested - if *uefi { - if let Some(ref firmware_path) = config.uefi_firmware_path { - // UEFI firmware configuration - cmd.args([ - "-drive", - &format!("if=pflash,format=raw,readonly=on,file={}", firmware_path), - ]); - - // UEFI variables (if specified) - if let Some(ref vars_path) = config.uefi_vars_path { - cmd.args([ - "-drive", - &format!("if=pflash,format=raw,file={}", vars_path), - ]); - } - - // Disable default SeaBIOS - cmd.args(["-machine", "q35"]); - debug!("UEFI boot configured with firmware: {}", firmware_path); - } else { - // Try to auto-detect UEFI firmware paths - let common_uefi_paths = [ - "/usr/share/edk2/ovmf/OVMF_CODE.fd", - "/usr/share/OVMF/OVMF_CODE.fd", - "/usr/share/ovmf/OVMF.fd", - ]; - - let mut found_firmware = None; - for path in &common_uefi_paths { - if std::path::Path::new(path).exists() { - found_firmware = Some(path); - break; - } - } - - if let Some(firmware_path) = found_firmware { - cmd.args([ - "-drive", - &format!("if=pflash,format=raw,readonly=on,file={}", firmware_path), - ]); - cmd.args(["-machine", "q35"]); - debug!( - "UEFI boot configured with auto-detected firmware: {}", - firmware_path - ); - } else { - warn!("UEFI boot requested but no firmware found, falling back to BIOS"); - } - } - } - - // Add primary boot disk - cmd.args([ - "-drive", - &format!("file={},format=raw,if=none,id=boot_drive", primary_disk), - "-device", - "virtio-blk-pci,drive=boot_drive,serial=boot_disk,bootindex=1", - ]); - } None => {} } @@ -693,7 +610,9 @@ struct VsockCopier { pub struct RunningQemu { pub qemu_process: Child, + #[allow(dead_code)] pub virtiofsd_processes: Vec>>>>, + #[allow(dead_code)] sd_notification: Option, } @@ -1042,25 +961,6 @@ pub async fn spawn_virtiofsd_async(config: &VirtiofsConfig) -> Result Result<()> { - let start = std::time::Instant::now(); - - while start.elapsed() < timeout { - if std::path::Path::new(socket_path).exists() { - debug!("Virtiofsd socket ready: {}", socket_path); - return Ok(()); - } - tokio::time::sleep(Duration::from_millis(100)).await; - } - - Err(eyre!( - "Timeout waiting for virtiofsd socket: {}", - socket_path - )) -} - /// Validate virtiofsd configuration. /// Checks shared directory exists/readable, socket path valid, /// and cache/sandbox modes are valid values. diff --git a/docs/src/man/bcvk.md b/docs/src/man/bcvk.md index 9894bf831..8c5f55351 100644 --- a/docs/src/man/bcvk.md +++ b/docs/src/man/bcvk.md @@ -18,7 +18,6 @@ The toolkit includes commands for: - Running ephemeral VMs for testing container images - Installing bootc containers to persistent disk images - Managing libvirt integration and VM lifecycle -- Executing host commands from within containers - SSH access to running VMs @@ -26,10 +25,6 @@ The toolkit includes commands for: # SUBCOMMANDS -bcvk-hostexec(8) - -: Execute commands on the host system from within containers - bcvk-images(8) : Manage and inspect bootc container images