From 8eb6323ce8d333cd138f87e6fe2dd56af293973c Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 2 Oct 2026 11:20:40 +0200 Subject: [PATCH] Parse uki and aboot files using minimal composefs-boot dependency This uses a new feature of composefs-boot to drop all composefs integration dependencies, making it a very small library with parsers for boot artifacts (uki, aboot, os-release files). This allows us to drop the objcopy and android-tools dependency. Signed-off-by: Alexander Larsson --- Cargo.lock | 11 ++++ contrib/packaging/bcvk.spec | 4 +- crates/bcvk-qemu/src/qemu.rs | 2 +- .../src/tests/run_ephemeral.rs | 2 +- crates/kit/Cargo.toml | 1 + crates/kit/src/run_ephemeral.rs | 51 +++++++++---------- docs/src/installation.md | 2 - docs/todo/ephemeral-uefi.md | 2 +- 8 files changed, 40 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 003935cf8..6000281bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -200,6 +200,7 @@ dependencies = [ "clap_mangen", "color-eyre", "comfy-table", + "composefs-boot", "const_format", "cpio", "data-encoding", @@ -556,6 +557,16 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "composefs-boot" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61074a193dda9270830d3598d26b618b0181726c8cf61ab59d3f66924875d5e" +dependencies = [ + "thiserror", + "zerocopy", +] + [[package]] name = "concurrent-queue" version = "2.5.0" diff --git a/contrib/packaging/bcvk.spec b/contrib/packaging/bcvk.spec index dd3e7392b..7ff333212 100644 --- a/contrib/packaging/bcvk.spec +++ b/contrib/packaging/bcvk.spec @@ -19,10 +19,8 @@ Requires: podman Requires: qemu-img Requires: qemu-kvm Requires: virtiofsd -# Extraction and initramfs tools are needed for some ephemeral boot images. -Recommends: binutils +# Initramfs tools are needed for some ephemeral boot images. Recommends: kernel-tools -Recommends: android-tools # libvirt-client is optional but recommended for 'bcvk libvirt' commands Recommends: libvirt-client diff --git a/crates/bcvk-qemu/src/qemu.rs b/crates/bcvk-qemu/src/qemu.rs index 1c7140ba8..0398f8085 100644 --- a/crates/bcvk-qemu/src/qemu.rs +++ b/crates/bcvk-qemu/src/qemu.rs @@ -160,7 +160,7 @@ pub enum BootMode { /// Direct kernel boot (fast, testing-focused). /// Also used for UKI boot after extracting kernel/initramfs from UKI PE sections. /// - /// Note: For UKI images, we extract kernel/initramfs using objcopy rather than + /// Note: For UKI images, we extract kernel/initramfs from PE sections rather than /// booting the UKI directly via OVMF. This allows us to append bcvk units to /// the initramfs for /etc overlay and /var setup. The tradeoff is that this /// breaks the UKI signature chain, so Secure Boot is not supported for diff --git a/crates/integration-tests/src/tests/run_ephemeral.rs b/crates/integration-tests/src/tests/run_ephemeral.rs index 5441fb84f..01f64f48f 100644 --- a/crates/integration-tests/src/tests/run_ephemeral.rs +++ b/crates/integration-tests/src/tests/run_ephemeral.rs @@ -264,7 +264,7 @@ integration_test!(test_run_ephemeral_instancetype_invalid); /// Test that ephemeral VMs can boot from UKI-only images (no separate vmlinuz/initramfs) /// /// This tests compatibility with bootc images that only ship a Unified Kernel Image, -/// verifying that bcvk can extract kernel/initramfs from the UKI using objcopy. +/// verifying that bcvk can extract kernel/initramfs from the UKI. fn test_run_ephemeral_uki_only() -> TestResult { let sh = shell()?; let base_image = get_test_image(); diff --git a/crates/kit/Cargo.toml b/crates/kit/Cargo.toml index 8bfe412cd..7baed90e8 100644 --- a/crates/kit/Cargo.toml +++ b/crates/kit/Cargo.toml @@ -50,6 +50,7 @@ bcvk-qemu = { path = "../bcvk-qemu" } cap-std-ext = { workspace = true } bootc-mount = { package = "bootc-internal-mount", version = "1.16.14" } bootc-utils = { package = "bootc-internal-utils", version = "1.16.14" } +composefs-boot = { version = "0.9.4", default-features = false } rustix = { version = "1", features = ["thread", "net", "fs", "pipe", "system", "process", "mount"] } vsock = "0.5" nix = { version = "0.31", features = ["socket"] } diff --git a/crates/kit/src/run_ephemeral.rs b/crates/kit/src/run_ephemeral.rs index 4e408c963..c6cae8d19 100644 --- a/crates/kit/src/run_ephemeral.rs +++ b/crates/kit/src/run_ephemeral.rs @@ -1428,40 +1428,37 @@ pub(crate) async fn run_impl(opts: RunEphemeralOpts) -> Result<()> { match kernel_info.kind { crate::kernel::KernelKind::Uki => { - require_binary("objcopy")?; debug!( "Extracting kernel and initramfs from UKI: {:?}", kernel_info.kernel_path ); - for (section, output) in [(".linux", kernel_mount), (".initrd", initramfs_mount)] { - Command::new("objcopy") - .args([ - "--dump-section", - &format!("{section}={output}"), - kernel_info.kernel_path.as_str(), - ]) - .run_capture_stderr() - .map_err(|e| eyre!("Failed to extract {section} from UKI: {e}"))?; + let mut uki = File::open(&kernel_info.kernel_path).context("opening UKI")?; + for (section, target) in [(".linux", kernel_mount), (".initrd", initramfs_mount)] { + // The buffered parser starts at the current position in the image. + uki.rewind().context("rewinding UKI")?; + let contents = composefs_boot::uki::get_section_buffered(&mut uki, section) + .context(format!("extracting {section} from UKI"))?; + fs::write(target, contents).context(format!("writing {section} from UKI"))?; + debug!("Extracted {section} from UKI to {target}"); } } crate::kernel::KernelKind::AndroidBoot => { - require_binary("unpack_bootimg")?; - let unpacked = "/run/qemu/aboot-unpacked"; - fs::create_dir_all(unpacked)?; - Command::new("unpack_bootimg") - .args([ - "--boot_img", - kernel_info.kernel_path.as_str(), - "--out", - unpacked, - ]) - .stdout(Stdio::null()) - .run_capture_stderr() - .map_err(|e| eyre!("Failed to unpack Android boot image: {e}"))?; - fs::rename(format!("{unpacked}/kernel"), kernel_mount) - .context("Getting kernel from Android boot image")?; - fs::rename(format!("{unpacked}/ramdisk"), initramfs_mount) - .context("Getting ramdisk from Android boot image")?; + use composefs_boot::android_boot::{AndroidBootImage, Component}; + + let mut image = + File::open(&kernel_info.kernel_path).context("opening Android boot image")?; + let header = + AndroidBootImage::parse(&mut image).context("parsing Android boot image")?; + for (component, name, target) in [ + (Component::Kernel, "kernel", kernel_mount), + (Component::Ramdisk, "ramdisk", initramfs_mount), + ] { + let contents = header + .component(&mut image, component) + .with_context(|| format!("extracting {name} from Android boot image"))?; + fs::write(target, contents) + .with_context(|| format!("writing {name} from Android boot image"))?; + } } crate::kernel::KernelKind::Traditional => { let source_initramfs_path = kernel_info diff --git a/docs/src/installation.md b/docs/src/installation.md index be2f960d3..c87a0f234 100644 --- a/docs/src/installation.md +++ b/docs/src/installation.md @@ -22,8 +22,6 @@ For running bcvk: - virtiofsd - Podman - openssh-clients (for libvirt SSH operations) -- binutils (for extracting UKIs, including ukiboot payloads) -- android-tools (for extracting Android boot images) - kernel-tools (for initramfs bootconfig and ARM64 zstd EFI zboot kernels) Optional: diff --git a/docs/todo/ephemeral-uefi.md b/docs/todo/ephemeral-uefi.md index a7a410797..143491942 100644 --- a/docs/todo/ephemeral-uefi.md +++ b/docs/todo/ephemeral-uefi.md @@ -6,7 +6,7 @@ Tracking issue: https://github.com/bootc-dev/bcvk/issues/161 Ephemeral VMs currently use direct kernel boot via QEMU's `-kernel` and `-initrd` options. For UKI-only images, we extract the kernel and initramfs -from the UKI using `objcopy --dump-section`. +from the UKI using the `composefs-boot` UKI parser. ### Where UKIs Live in bootc Images