Skip to content

Commit

Permalink
create: Auto-detect image architecture
Browse files Browse the repository at this point in the history
When combined with --emulated, this enables running VMs with an
architecture different from the host's.

Closes #62.

Signed-off-by: Alberto Faria <[email protected]>
  • Loading branch information
albertofaria committed May 8, 2024
1 parent 8944e2d commit dc4616c
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 12 deletions.
5 changes: 3 additions & 2 deletions docs/2-podman-docker.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
12 changes: 7 additions & 5 deletions src/commands/create/domain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")])
Expand Down
10 changes: 7 additions & 3 deletions src/commands/create/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
});
Expand All @@ -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 {
Expand All @@ -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)
Expand Down Expand Up @@ -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 {
Expand All @@ -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(),
})
Expand Down
70 changes: 68 additions & 2 deletions src/util.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -336,14 +337,17 @@ pub struct VmImageInfo {
#[serde(skip)]
pub path: Utf8PathBuf,

#[serde(skip)]
pub arch: Option<String>,

#[serde(rename = "virtual-size")]
pub size: u64,

pub format: String,
}

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 output = Command::new("qemu-img")
Expand All @@ -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)
}
}
Expand Down Expand Up @@ -393,6 +403,62 @@ pub fn create_overlay_vm_image(
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`.
///
/// `crun` will inherit this process' standard streams.
Expand Down

0 comments on commit dc4616c

Please sign in to comment.