create: Auto-detect image architecture

When combined with --emulated, this enables running VMs with an
architecture different from the host's.

Closes #62.

Signed-off-by: Alberto Faria <afaria@redhat.com>
This commit is contained in:
Alberto Faria 2024-04-26 18:33:42 +01:00
parent 8944e2df28
commit dc4616c19d
4 changed files with 85 additions and 12 deletions

View File

@ -336,8 +336,9 @@ $ podman run \
### System emulation ### System emulation
To use system emulation instead of hardware-assisted virtualization, specify the 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 `--emulated` flag. Without this flag, attempting to create a VM from a guest
doesn't support KVM will fail. 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 It's not currently possible to use this flag when the container image is a bootc
bootable container. bootable container.

View File

@ -58,11 +58,13 @@ fn generate(
st(w, "memory", &[("unit", "b")], memory.as_str())?; st(w, "memory", &[("unit", "b")], memory.as_str())?;
s(w, "os", &[("firmware", "efi")], |w| { s(w, "os", &[("firmware", "efi")], |w| {
let attrs = match ["x86", "x86_64"].contains(&env::consts::ARCH) { let guest_arch = vm_image_info.arch.as_deref().unwrap_or(env::consts::ARCH);
true => [("machine", "q35")].as_slice(),
false => [].as_slice(), // use libvirt's default let mut attrs = vec![("arch", guest_arch)];
}; if ["x86", "x86_64"].contains(&guest_arch) {
st(w, "type", attrs, "hvm")?; attrs.push(("machine", "q35"));
}
st(w, "type", &attrs, "hvm")?;
s(w, "firmware", &[], |w| { s(w, "firmware", &[], |w| {
se(w, "feature", &[("enabled", "no"), ("name", "secure-boot")]) se(w, "feature", &[("enabled", "no"), ("name", "secure-boot")])

View File

@ -290,6 +290,7 @@ fn set_up_vm_image(
// the image will be generated later // the image will be generated later
return Ok(VmImageInfo { return Ok(VmImageInfo {
path: mirror_vm_image_path_in_container, path: mirror_vm_image_path_in_container,
arch: None,
size: 0, size: 0,
format: "qcow2".to_string(), format: "qcow2".to_string(),
}); });
@ -315,7 +316,7 @@ fn set_up_vm_image(
fs::create_dir_all(&image_dir_path)?; fs::create_dir_all(&image_dir_path)?;
if !image_dir_path.join("image").try_exists()? { 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 { 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)?; 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; vm_image_info.path = mirror_vm_image_path_in_container;
Ok(vm_image_info) Ok(vm_image_info)
@ -366,7 +368,8 @@ fn set_up_vm_image(
let overlay_vm_image_path_in_container = let overlay_vm_image_path_in_container =
Utf8Path::new("/").join(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; base_vm_image_info.path = mirror_vm_image_path_in_container;
if is_first_create { if is_first_create {
@ -375,6 +378,7 @@ fn set_up_vm_image(
Ok(VmImageInfo { Ok(VmImageInfo {
path: Utf8Path::new("/").join(overlay_vm_image_path_in_container), path: Utf8Path::new("/").join(overlay_vm_image_path_in_container),
arch: base_vm_image_info.arch,
size: base_vm_image_info.size, size: base_vm_image_info.size,
format: "qcow2".to_string(), format: "qcow2".to_string(),
}) })

View File

@ -1,8 +1,9 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
use std::env;
use std::ffi::{c_char, CString, OsStr}; use std::ffi::{c_char, CString, OsStr};
use std::fs::{self, OpenOptions, Permissions}; 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::ffi::OsStrExt;
use std::os::unix::fs::{MetadataExt, PermissionsExt}; use std::os::unix::fs::{MetadataExt, PermissionsExt};
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
@ -336,6 +337,9 @@ pub struct VmImageInfo {
#[serde(skip)] #[serde(skip)]
pub path: Utf8PathBuf, pub path: Utf8PathBuf,
#[serde(skip)]
pub arch: Option<String>,
#[serde(rename = "virtual-size")] #[serde(rename = "virtual-size")]
pub size: u64, pub size: u64,
@ -343,7 +347,7 @@ pub struct VmImageInfo {
} }
impl VmImageInfo { impl VmImageInfo {
pub fn of(vm_image_path: impl AsRef<Utf8Path>) -> Result<VmImageInfo> { pub fn of(vm_image_path: impl AsRef<Utf8Path>, identify_arch: bool) -> Result<VmImageInfo> {
let vm_image_path = vm_image_path.as_ref().to_path_buf(); let vm_image_path = vm_image_path.as_ref().to_path_buf();
let output = Command::new("qemu-img") let output = Command::new("qemu-img")
@ -362,6 +366,12 @@ impl VmImageInfo {
let mut info: VmImageInfo = serde_json::from_slice(&output.stdout)?; let mut info: VmImageInfo = serde_json::from_slice(&output.stdout)?;
info.path = vm_image_path; 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) Ok(info)
} }
} }
@ -393,6 +403,62 @@ pub fn create_overlay_vm_image(
Ok(()) Ok(())
} }
pub fn identify_image_arch(image_path: impl AsRef<Utf8Path>) -> Result<Option<String>> {
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<Item = impl AsRef<OsStr>>) -> Result<String> {
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<Option<String>> {
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`. /// Run `crun`.
/// ///
/// `crun` will inherit this process' standard streams. /// `crun` will inherit this process' standard streams.