Skip to content

Commit dc4616c

Browse files
committedMay 8, 2024·
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>
1 parent 8944e2d commit dc4616c

File tree

4 files changed

+85
-12
lines changed

4 files changed

+85
-12
lines changed
 

‎docs/2-podman-docker.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -336,8 +336,9 @@ $ podman run \
336336
### System emulation
337337

338338
To use system emulation instead of hardware-assisted virtualization, specify the
339-
`--emulated` flag. Without this flag, attempting to create a VM on a host tbat
340-
doesn't support KVM will fail.
339+
`--emulated` flag. Without this flag, attempting to create a VM from a guest
340+
with a different architecture from the host's or on a host that doesn't support
341+
KVM will fail.
341342

342343
It's not currently possible to use this flag when the container image is a bootc
343344
bootable container.

‎src/commands/create/domain.rs

+7-5
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,13 @@ fn generate(
5858
st(w, "memory", &[("unit", "b")], memory.as_str())?;
5959

6060
s(w, "os", &[("firmware", "efi")], |w| {
61-
let attrs = match ["x86", "x86_64"].contains(&env::consts::ARCH) {
62-
true => [("machine", "q35")].as_slice(),
63-
false => [].as_slice(), // use libvirt's default
64-
};
65-
st(w, "type", attrs, "hvm")?;
61+
let guest_arch = vm_image_info.arch.as_deref().unwrap_or(env::consts::ARCH);
62+
63+
let mut attrs = vec![("arch", guest_arch)];
64+
if ["x86", "x86_64"].contains(&guest_arch) {
65+
attrs.push(("machine", "q35"));
66+
}
67+
st(w, "type", &attrs, "hvm")?;
6668

6769
s(w, "firmware", &[], |w| {
6870
se(w, "feature", &[("enabled", "no"), ("name", "secure-boot")])

‎src/commands/create/mod.rs

+7-3
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ fn set_up_vm_image(
290290
// the image will be generated later
291291
return Ok(VmImageInfo {
292292
path: mirror_vm_image_path_in_container,
293+
arch: None,
293294
size: 0,
294295
format: "qcow2".to_string(),
295296
});
@@ -315,7 +316,7 @@ fn set_up_vm_image(
315316
fs::create_dir_all(&image_dir_path)?;
316317

317318
if !image_dir_path.join("image").try_exists()? {
318-
fs::hard_link(vm_image_path_in_host, image_dir_path.join("image"))?;
319+
fs::hard_link(&vm_image_path_in_host, image_dir_path.join("image"))?;
319320
}
320321

321322
if custom_options.persistent {
@@ -338,7 +339,8 @@ fn set_up_vm_image(
338339

339340
bind_mount_file(&mirror_vm_image_path_in_host, &mirror_vm_image_path_in_host)?;
340341

341-
let mut vm_image_info = VmImageInfo::of(&mirror_vm_image_path_in_host)?;
342+
let mut vm_image_info =
343+
VmImageInfo::of(&mirror_vm_image_path_in_host, custom_options.emulated)?;
342344
vm_image_info.path = mirror_vm_image_path_in_container;
343345

344346
Ok(vm_image_info)
@@ -366,7 +368,8 @@ fn set_up_vm_image(
366368
let overlay_vm_image_path_in_container =
367369
Utf8Path::new("/").join(overlay_vm_image_path_in_container);
368370

369-
let mut base_vm_image_info = VmImageInfo::of(&mirror_vm_image_path_in_host)?;
371+
let mut base_vm_image_info =
372+
VmImageInfo::of(&vm_image_path_in_host, custom_options.emulated)?;
370373
base_vm_image_info.path = mirror_vm_image_path_in_container;
371374

372375
if is_first_create {
@@ -375,6 +378,7 @@ fn set_up_vm_image(
375378

376379
Ok(VmImageInfo {
377380
path: Utf8Path::new("/").join(overlay_vm_image_path_in_container),
381+
arch: base_vm_image_info.arch,
378382
size: base_vm_image_info.size,
379383
format: "qcow2".to_string(),
380384
})

‎src/util.rs

+68-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
// SPDX-License-Identifier: GPL-2.0-or-later
22

3+
use std::env;
34
use std::ffi::{c_char, CString, OsStr};
45
use std::fs::{self, OpenOptions, Permissions};
5-
use std::io::{self, ErrorKind};
6+
use std::io::{self, ErrorKind, Write};
67
use std::os::unix::ffi::OsStrExt;
78
use std::os::unix::fs::{MetadataExt, PermissionsExt};
89
use std::process::{Command, Stdio};
@@ -336,14 +337,17 @@ pub struct VmImageInfo {
336337
#[serde(skip)]
337338
pub path: Utf8PathBuf,
338339

340+
#[serde(skip)]
341+
pub arch: Option<String>,
342+
339343
#[serde(rename = "virtual-size")]
340344
pub size: u64,
341345

342346
pub format: String,
343347
}
344348

345349
impl VmImageInfo {
346-
pub fn of(vm_image_path: impl AsRef<Utf8Path>) -> Result<VmImageInfo> {
350+
pub fn of(vm_image_path: impl AsRef<Utf8Path>, identify_arch: bool) -> Result<VmImageInfo> {
347351
let vm_image_path = vm_image_path.as_ref().to_path_buf();
348352

349353
let output = Command::new("qemu-img")
@@ -362,6 +366,12 @@ impl VmImageInfo {
362366
let mut info: VmImageInfo = serde_json::from_slice(&output.stdout)?;
363367
info.path = vm_image_path;
364368

369+
if identify_arch {
370+
info.arch = identify_image_arch(&info.path)?
371+
.ok_or_else(|| anyhow!("Could not identify VM image architecture"))?
372+
.into();
373+
}
374+
365375
Ok(info)
366376
}
367377
}
@@ -393,6 +403,62 @@ pub fn create_overlay_vm_image(
393403
Ok(())
394404
}
395405

406+
pub fn identify_image_arch(image_path: impl AsRef<Utf8Path>) -> Result<Option<String>> {
407+
let xml = virt_inspector([
408+
"--add",
409+
image_path.as_ref().as_str(),
410+
"--no-applications",
411+
"--no-icon",
412+
])?;
413+
414+
xpath(&xml, "string(//arch)")
415+
}
416+
417+
fn virt_inspector(args: impl IntoIterator<Item = impl AsRef<OsStr>>) -> Result<String> {
418+
let cache_dir = format!("/var/tmp/crun-vm-{}", env::var("_CONTAINERS_ROOTLESS_UID")?);
419+
fs::create_dir_all(&cache_dir)?;
420+
421+
let output = Command::new("virt-inspector")
422+
.args(args)
423+
.env("LIBGUESTFS_BACKEND", "direct")
424+
.env("LIBGUESTFS_CACHEDIR", &cache_dir)
425+
.output()?;
426+
427+
ensure!(
428+
output.status.success(),
429+
"virt-inspector failed: {}",
430+
String::from_utf8_lossy(&output.stderr),
431+
);
432+
433+
Ok(String::from_utf8(output.stdout)?)
434+
}
435+
436+
fn xpath(xml: &str, path: &str) -> Result<Option<String>> {
437+
let mut child = Command::new("virt-inspector")
438+
.arg("--xpath")
439+
.arg(path)
440+
.stdin(Stdio::piped())
441+
.spawn()?;
442+
443+
child.stdin.take().unwrap().write_all(xml.as_bytes())?;
444+
445+
let output = child.wait_with_output()?;
446+
447+
ensure!(
448+
output.status.success(),
449+
"virt-inspector --xpath failed: {}",
450+
String::from_utf8_lossy(&output.stderr),
451+
);
452+
453+
let result = String::from_utf8(output.stdout)?;
454+
455+
if result.is_empty() {
456+
Ok(None)
457+
} else {
458+
Ok(Some(result))
459+
}
460+
}
461+
396462
/// Run `crun`.
397463
///
398464
/// `crun` will inherit this process' standard streams.

0 commit comments

Comments
 (0)
Please sign in to comment.