Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/kit/src/qemu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ impl Default for ResourceLimits {
#[derive(Debug)]
pub enum BootMode {
/// Direct kernel boot (fast, testing-focused)
/// Also used for UKI boot after extracting kernel/initramfs from UKI PE sections
DirectBoot {
kernel_path: String,
initramfs_path: String,
Expand Down
135 changes: 82 additions & 53 deletions crates/kit/src/run_ephemeral.rs
Original file line number Diff line number Diff line change
Expand Up @@ -737,17 +737,36 @@ pub(crate) async fn run_impl(opts: RunEphemeralOpts) -> Result<()> {
};
tracing::debug!("Target image has cloud-init: {cloudinit}");

// Find kernel and initramfs from the container image (not the host)
// Verify KVM access
if !Utf8Path::new("/dev/kvm").exists() || !fs::File::open("/dev/kvm").is_ok() {
return Err(eyre!("KVM device not accessible"));
}

// Create QEMU mount points
fs::create_dir_all("/run/qemu")?;

// Find kernel and initramfs in /usr/lib/modules/
let modules_dir = Utf8Path::new("/run/source-image/usr/lib/modules");
let mut vmlinuz_path = None;
let mut initramfs_path = None;
let mut uki_file: Option<Utf8PathBuf> = None;
let mut vmlinuz_path: Option<Utf8PathBuf> = None;
let mut initramfs_path: Option<Utf8PathBuf> = None;

for entry in fs::read_dir(modules_dir)? {
let entry = entry?;
let kernel_dir = entry.path();
if kernel_dir.is_dir() {
let vmlinuz = kernel_dir.join("vmlinuz");
let initramfs = kernel_dir.join("initramfs.img");
let path = Utf8PathBuf::from_path_buf(entry.path())
.map_err(|p| eyre!("Path is not valid UTF-8: {}", p.display()))?;

// Check for UKI (.efi file)
if path.is_file() && path.extension() == Some("efi") {
debug!("Found UKI file: {:?}", path);
uki_file = Some(path);
break;
}

// Check for traditional kernel in subdirectories
if path.is_dir() {
let vmlinuz = path.join("vmlinuz");
let initramfs = path.join("initramfs.img");
if vmlinuz.exists() && initramfs.exists() {
debug!("Found kernel at: {:?}", vmlinuz);
vmlinuz_path = Some(vmlinuz);
Expand All @@ -757,50 +776,59 @@ pub(crate) async fn run_impl(opts: RunEphemeralOpts) -> Result<()> {
}
}

let vmlinuz_path = vmlinuz_path
.ok_or_else(|| eyre!("No kernel found in /run/source-image/usr/lib/modules"))?;
let initramfs_path = initramfs_path
.ok_or_else(|| eyre!("No initramfs found in /run/source-image/usr/lib/modules"))?;

// Verify KVM access
if !Utf8Path::new("/dev/kvm").exists() || !fs::File::open("/dev/kvm").is_ok() {
return Err(eyre!("KVM device not accessible"));
}

// Create QEMU mount points
fs::create_dir_all("/run/qemu")?;
let kernel_mount = "/run/qemu/kernel";
let initramfs_mount = "/run/qemu/initramfs";
fs::File::create(&kernel_mount)?;
fs::File::create(&initramfs_mount)?;

// Bind mount kernel and initramfs
let mut mount_cmd = Command::new("mount");
mount_cmd.args([
"--bind",
"-o",
"ro",
vmlinuz_path.to_str().unwrap(),
&kernel_mount,
]);
let status = mount_cmd.status().context("Failed to bind mount kernel")?;
if !status.success() {
return Err(eyre!("Failed to bind mount kernel"));
}

let mut mount_cmd = Command::new("mount");
mount_cmd.args([
"--bind",
"-o",
"ro",
initramfs_path.to_str().unwrap(),
&initramfs_mount,
]);
let status = mount_cmd
.status()
.context("Failed to bind mount initramfs")?;
if !status.success() {
return Err(eyre!("Failed to bind mount initramfs"));
// Extract from UKI if found, otherwise use traditional kernel
if let Some(uki_path) = uki_file {
debug!("Extracting kernel and initramfs from UKI: {:?}", uki_path);

// Extract .linux section (kernel) from UKI
Command::new("objcopy")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't tested this, but I suspect if binutils isn't installed this is probably going to error with a bare "No such file or directory", and then down below it's going to map into "Failed to extract kernel from UKI: No such file or directory" which sounds like the UKI is missing. The sort of thing future me would waste entirely too much time on before I finally figured out that actually objcopy is missing 😆

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good thing to think about but there's a map_err in there which adds error context.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh sorry yes you're right. Will look at adding a check for this

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.args([
"--dump-section",
&format!(".linux={}", kernel_mount),
uki_path.as_str(),
])
.run()
.map_err(|e| eyre!("Failed to extract kernel from UKI: {e}"))?;
debug!("Extracted kernel from UKI to {}", kernel_mount);

// Extract .initrd section (initramfs) from UKI
Command::new("objcopy")
.args([
"--dump-section",
&format!(".initrd={}", initramfs_mount),
uki_path.as_str(),
])
.run()
.map_err(|e| eyre!("Failed to extract initramfs from UKI: {e}"))?;
debug!("Extracted initramfs from UKI to {}", initramfs_mount);
} else {
let vmlinuz_path = vmlinuz_path
.ok_or_else(|| eyre!("No kernel found in /run/source-image/usr/lib/modules"))?;
let initramfs_path = initramfs_path
.ok_or_else(|| eyre!("No initramfs found in /run/source-image/usr/lib/modules"))?;

fs::File::create(&kernel_mount)?;
fs::File::create(&initramfs_mount)?;

// Bind mount kernel and initramfs
Command::new("mount")
.args(["--bind", "-o", "ro", vmlinuz_path.as_str(), &kernel_mount])
.run()
.map_err(|e| eyre!("Failed to bind mount kernel: {e}"))?;

Command::new("mount")
.args([
"--bind",
"-o",
"ro",
initramfs_path.as_str(),
&initramfs_mount,
])
.run()
.map_err(|e| eyre!("Failed to bind mount initramfs: {e}"))?;
}

// Process host mounts and prepare virtiofsd instances for each using async manager
Expand Down Expand Up @@ -993,7 +1021,8 @@ StandardOutput=file:/dev/virtio-ports/executestatus

std::fs::create_dir_all(CONTAINER_STATEDIR)?;

// Configure qemu
// Configure qemu for direct kernel boot
debug!("Configuring QEMU for direct kernel boot");
let mut qemu_config = crate::qemu::QemuConfig::new_direct_boot(
opts.common.memory_mb()?,
opts.common.vcpus()?,
Expand All @@ -1014,7 +1043,8 @@ StandardOutput=file:/dev/virtio-ports/executestatus
let credential = crate::credentials::smbios_cred_for_root_ssh(&pubkey)?;
qemu_config.add_smbios_credential(credential);
}
// Build kernel command line

// Build kernel command line for direct boot
let mut kernel_cmdline = [
// At the core we boot from the mounted container's root,
"rootfstype=virtiofs",
Expand Down Expand Up @@ -1044,6 +1074,7 @@ StandardOutput=file:/dev/virtio-ports/executestatus
}

kernel_cmdline.extend(opts.kernel_args.clone());
qemu_config.set_kernel_cmdline(kernel_cmdline);

// TODO allocate unlinked unnamed file and pass via fd
let mut tmp_swapfile = None;
Expand Down Expand Up @@ -1151,9 +1182,7 @@ Options=
}
}

qemu_config
.set_kernel_cmdline(kernel_cmdline)
.set_console(opts.common.console);
qemu_config.set_console(opts.common.console);

// Add virtio-serial device for journal streaming
qemu_config.add_virtio_serial_out("org.bcvk.journal", "/run/journal.log".to_string(), false);
Expand Down
Loading