diff --git a/docs/2-podman-docker.md b/docs/2-podman-docker.md index 8595683..a7d9926 100644 --- a/docs/2-podman-docker.md +++ b/docs/2-podman-docker.md @@ -336,8 +336,9 @@ $ podman run \ ### System emulation To use system emulation instead of hardware-assisted virtualization, specify the -`--emulated` flag. Without this flag, attempting to create a VM on a host tbat -doesn't support KVM will fail. +`--emulated` flag. Without this flag, attempting to create a VM from a guest +with a different architecture from the host's or on a host that doesn't support +KVM will fail. It's not currently possible to use this flag when the container image is a bootc bootable container. diff --git a/src/commands/create/domain.rs b/src/commands/create/domain.rs index 13964e3..f6d6bfd 100644 --- a/src/commands/create/domain.rs +++ b/src/commands/create/domain.rs @@ -58,11 +58,13 @@ fn generate( st(w, "memory", &[("unit", "b")], memory.as_str())?; s(w, "os", &[("firmware", "efi")], |w| { - let attrs = match ["x86", "x86_64"].contains(&env::consts::ARCH) { - true => [("machine", "q35")].as_slice(), - false => [].as_slice(), // use libvirt's default - }; - st(w, "type", attrs, "hvm")?; + let guest_arch = vm_image_info.arch.as_deref().unwrap_or(env::consts::ARCH); + + let mut attrs = vec![("arch", guest_arch)]; + if ["x86", "x86_64"].contains(&guest_arch) { + attrs.push(("machine", "q35")); + } + st(w, "type", &attrs, "hvm")?; s(w, "firmware", &[], |w| { se(w, "feature", &[("enabled", "no"), ("name", "secure-boot")]) diff --git a/src/commands/create/mod.rs b/src/commands/create/mod.rs index bca9fd2..2370a03 100644 --- a/src/commands/create/mod.rs +++ b/src/commands/create/mod.rs @@ -290,6 +290,7 @@ fn set_up_vm_image( // the image will be generated later return Ok(VmImageInfo { path: mirror_vm_image_path_in_container, + arch: None, size: 0, format: "qcow2".to_string(), }); @@ -315,7 +316,7 @@ fn set_up_vm_image( fs::create_dir_all(&image_dir_path)?; if !image_dir_path.join("image").try_exists()? { - fs::hard_link(vm_image_path_in_host, image_dir_path.join("image"))?; + fs::hard_link(&vm_image_path_in_host, image_dir_path.join("image"))?; } if custom_options.persistent { @@ -338,7 +339,8 @@ fn set_up_vm_image( bind_mount_file(&mirror_vm_image_path_in_host, &mirror_vm_image_path_in_host)?; - let mut vm_image_info = VmImageInfo::of(&mirror_vm_image_path_in_host)?; + let mut vm_image_info = + VmImageInfo::of(&mirror_vm_image_path_in_host, custom_options.emulated)?; vm_image_info.path = mirror_vm_image_path_in_container; Ok(vm_image_info) @@ -366,7 +368,8 @@ fn set_up_vm_image( let overlay_vm_image_path_in_container = Utf8Path::new("/").join(overlay_vm_image_path_in_container); - let mut base_vm_image_info = VmImageInfo::of(&mirror_vm_image_path_in_host)?; + let mut base_vm_image_info = + VmImageInfo::of(&vm_image_path_in_host, custom_options.emulated)?; base_vm_image_info.path = mirror_vm_image_path_in_container; if is_first_create { @@ -375,6 +378,7 @@ fn set_up_vm_image( Ok(VmImageInfo { path: Utf8Path::new("/").join(overlay_vm_image_path_in_container), + arch: base_vm_image_info.arch, size: base_vm_image_info.size, format: "qcow2".to_string(), }) diff --git a/src/util.rs b/src/util.rs index 84ce83f..2a75789 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,8 +1,9 @@ // SPDX-License-Identifier: GPL-2.0-or-later +use std::env; use std::ffi::{c_char, CString, OsStr}; use std::fs::{self, OpenOptions, Permissions}; -use std::io::{self, ErrorKind}; +use std::io::{self, ErrorKind, Write}; use std::os::unix::ffi::OsStrExt; use std::os::unix::fs::{MetadataExt, PermissionsExt}; use std::process::{Command, Stdio}; @@ -336,6 +337,9 @@ pub struct VmImageInfo { #[serde(skip)] pub path: Utf8PathBuf, + #[serde(skip)] + pub arch: Option, + #[serde(rename = "virtual-size")] pub size: u64, @@ -343,7 +347,7 @@ pub struct VmImageInfo { } impl VmImageInfo { - pub fn of(vm_image_path: impl AsRef) -> Result { + pub fn of(vm_image_path: impl AsRef, identify_arch: bool) -> Result { let vm_image_path = vm_image_path.as_ref().to_path_buf(); let output = Command::new("qemu-img") @@ -362,6 +366,12 @@ impl VmImageInfo { let mut info: VmImageInfo = serde_json::from_slice(&output.stdout)?; info.path = vm_image_path; + if identify_arch { + info.arch = identify_image_arch(&info.path)? + .ok_or_else(|| anyhow!("Could not identify VM image architecture"))? + .into(); + } + Ok(info) } } @@ -393,6 +403,62 @@ pub fn create_overlay_vm_image( Ok(()) } +pub fn identify_image_arch(image_path: impl AsRef) -> Result> { + let xml = virt_inspector([ + "--add", + image_path.as_ref().as_str(), + "--no-applications", + "--no-icon", + ])?; + + xpath(&xml, "string(//arch)") +} + +fn virt_inspector(args: impl IntoIterator>) -> Result { + let cache_dir = format!("/var/tmp/crun-vm-{}", env::var("_CONTAINERS_ROOTLESS_UID")?); + fs::create_dir_all(&cache_dir)?; + + let output = Command::new("virt-inspector") + .args(args) + .env("LIBGUESTFS_BACKEND", "direct") + .env("LIBGUESTFS_CACHEDIR", &cache_dir) + .output()?; + + ensure!( + output.status.success(), + "virt-inspector failed: {}", + String::from_utf8_lossy(&output.stderr), + ); + + Ok(String::from_utf8(output.stdout)?) +} + +fn xpath(xml: &str, path: &str) -> Result> { + let mut child = Command::new("virt-inspector") + .arg("--xpath") + .arg(path) + .stdin(Stdio::piped()) + .spawn()?; + + child.stdin.take().unwrap().write_all(xml.as_bytes())?; + + let output = child.wait_with_output()?; + + ensure!( + output.status.success(), + "virt-inspector --xpath failed: {}", + String::from_utf8_lossy(&output.stderr), + ); + + let result = String::from_utf8(output.stdout)?; + + if result.is_empty() { + Ok(None) + } else { + Ok(Some(result)) + } +} + /// Run `crun`. /// /// `crun` will inherit this process' standard streams.