1
0
mirror of https://github.com/containers/bootc.git synced 2026-02-05 06:45:13 +01:00

kernel: Add KernelPath enum

This just clarifies things in a few places to distinguish between a
UKI which has just a single path versus a traditional kernel with
separate vmlinuz and initramfs.

Also renames `find_uki_filename` to `find_uki_path` and updates the
return type to use `Utf8PathBuf` instead of just `String`.

Signed-off-by: John Eckersberg <jeckersb@redhat.com>
This commit is contained in:
John Eckersberg
2026-02-03 11:49:27 -05:00
parent 534fb40b00
commit 1b898758c9
2 changed files with 59 additions and 54 deletions

View File

@@ -26,20 +26,28 @@ pub(crate) struct Kernel {
pub(crate) unified: bool,
}
/// Internal-only kernel wrapper with extra information (paths to
/// vmlinuz, initramfs) that are useful but we don't want to leak out
/// via serialization to inspection.
/// Path to kernel component(s)
///
/// UKI kernels only have the single PE binary, whereas
/// traditional "vmlinuz" kernels have distinct kernel and
/// initramfs.
pub(crate) enum KernelPath {
Uki(Utf8PathBuf),
Vmlinuz {
path: Utf8PathBuf,
initramfs: Utf8PathBuf,
},
}
/// Internal-only kernel wrapper with extra path information that are
/// useful but we don't want to leak out via serialization to
/// inspection.
///
/// `Kernel` implements `From<KernelInternal>` so we can just `.into()`
/// to get the "public" form where needed.
pub(crate) struct KernelInternal {
pub(crate) kernel: Kernel,
/// Path to vmlinuz for traditional kernels.
/// This is `None` for UKI images.
pub(crate) vmlinuz: Option<Utf8PathBuf>,
/// Path to initramfs.img for traditional kernels.
/// This is `None` for UKI images.
pub(crate) initramfs: Option<Utf8PathBuf>,
pub(crate) path: KernelPath,
}
impl From<KernelInternal> for Kernel {
@@ -57,18 +65,14 @@ impl From<KernelInternal> for Kernel {
/// Returns `None` if no kernel is found.
pub(crate) fn find_kernel(root: &Dir) -> Result<Option<KernelInternal>> {
// First, try to find a UKI
if let Some(uki_filename) = find_uki_filename(root)? {
let version = uki_filename
.strip_suffix(".efi")
.unwrap_or(&uki_filename)
.to_owned();
if let Some(uki_path) = find_uki_path(root)? {
let version = uki_path.file_stem().unwrap_or(uki_path.as_str()).to_owned();
return Ok(Some(KernelInternal {
kernel: Kernel {
version,
unified: true,
},
vmlinuz: None,
initramfs: None,
path: KernelPath::Uki(uki_path),
}));
}
@@ -85,19 +89,21 @@ pub(crate) fn find_kernel(root: &Dir) -> Result<Option<KernelInternal>> {
version,
unified: false,
},
vmlinuz: Some(vmlinuz),
initramfs: Some(initramfs),
path: KernelPath::Vmlinuz {
path: vmlinuz,
initramfs,
},
}));
}
Ok(None)
}
/// Returns the filename of the first UKI found in the container root, if any.
/// Returns the path to the first UKI found in the container root, if any.
///
/// Looks in `/boot/EFI/Linux/*.efi`. If multiple UKIs are present, returns
/// the first one in sorted order for determinism.
fn find_uki_filename(root: &Dir) -> Result<Option<String>> {
fn find_uki_path(root: &Dir) -> Result<Option<Utf8PathBuf>> {
let Some(boot) = root.open_dir_optional(crate::install::BOOT)? else {
return Ok(None);
};
@@ -120,13 +126,15 @@ fn find_uki_filename(root: &Dir) -> Result<Option<String>> {
// Sort for deterministic behavior when multiple UKIs are present
uki_files.sort();
Ok(uki_files.into_iter().next())
Ok(uki_files
.into_iter()
.next()
.map(|filename| Utf8PathBuf::from(format!("boot/{EFI_LINUX}/{filename}"))))
}
#[cfg(test)]
mod tests {
use super::*;
use camino::Utf8Path;
use cap_std_ext::{cap_std, cap_tempfile, dirext::CapStdExtDirExt};
#[test]
@@ -148,18 +156,19 @@ mod tests {
let kernel_internal = find_kernel(&tempdir)?.expect("should find kernel");
assert_eq!(kernel_internal.kernel.version, "6.12.0-100.fc41.x86_64");
assert!(!kernel_internal.kernel.unified);
assert_eq!(
kernel_internal.vmlinuz.as_deref(),
Some(Utf8Path::new(
"usr/lib/modules/6.12.0-100.fc41.x86_64/vmlinuz"
))
);
assert_eq!(
kernel_internal.initramfs.as_deref(),
Some(Utf8Path::new(
"usr/lib/modules/6.12.0-100.fc41.x86_64/initramfs.img"
))
);
match &kernel_internal.path {
KernelPath::Vmlinuz { path, initramfs } => {
assert_eq!(
path.as_str(),
"usr/lib/modules/6.12.0-100.fc41.x86_64/vmlinuz"
);
assert_eq!(
initramfs.as_str(),
"usr/lib/modules/6.12.0-100.fc41.x86_64/initramfs.img"
);
}
KernelPath::Uki(_) => panic!("Expected Vmlinuz, got Uki"),
}
Ok(())
}
@@ -172,8 +181,12 @@ mod tests {
let kernel_internal = find_kernel(&tempdir)?.expect("should find kernel");
assert_eq!(kernel_internal.kernel.version, "fedora-6.12.0");
assert!(kernel_internal.kernel.unified);
assert!(kernel_internal.vmlinuz.is_none());
assert!(kernel_internal.initramfs.is_none());
match &kernel_internal.path {
KernelPath::Uki(path) => {
assert_eq!(path.as_str(), "boot/EFI/Linux/fedora-6.12.0.efi");
}
KernelPath::Vmlinuz { .. } => panic!("Expected Uki, got Vmlinuz"),
}
Ok(())
}
@@ -197,7 +210,7 @@ mod tests {
}
#[test]
fn test_find_uki_filename_sorted() -> Result<()> {
fn test_find_uki_path_sorted() -> Result<()> {
let tempdir = cap_tempfile::tempdir(cap_std::ambient_authority())?;
tempdir.create_dir_all("boot/EFI/Linux")?;
tempdir.atomic_write("boot/EFI/Linux/zzz.efi", b"fake uki")?;
@@ -205,8 +218,8 @@ mod tests {
tempdir.atomic_write("boot/EFI/Linux/mmm.efi", b"fake uki")?;
// Should return first in sorted order
let filename = find_uki_filename(&tempdir)?.expect("should find uki");
assert_eq!(filename, "aaa.efi");
let path = find_uki_path(&tempdir)?.expect("should find uki");
assert_eq!(path.as_str(), "boot/EFI/Linux/aaa.efi");
Ok(())
}
}

View File

@@ -54,21 +54,13 @@ pub(crate) fn build_ukify(
let kernel = crate::kernel::find_kernel(&root)?
.ok_or_else(|| anyhow::anyhow!("No kernel found in {rootfs}"))?;
// We can only build a UKI from a traditional kernel, not from an existing UKI
if kernel.kernel.unified {
anyhow::bail!(
"Cannot build UKI: rootfs already contains a UKI at boot/EFI/Linux/{}.efi",
kernel.kernel.version
);
}
// Get paths from the kernel info
let vmlinuz_path = kernel
.vmlinuz
.ok_or_else(|| anyhow::anyhow!("Traditional kernel should have vmlinuz path"))?;
let initramfs_path = kernel
.initramfs
.ok_or_else(|| anyhow::anyhow!("Traditional kernel should have initramfs path"))?;
// Extract vmlinuz and initramfs paths, or bail if this is already a UKI
let (vmlinuz_path, initramfs_path) = match kernel.path {
crate::kernel::KernelPath::Vmlinuz { path, initramfs } => (path, initramfs),
crate::kernel::KernelPath::Uki(path) => {
anyhow::bail!("Cannot build UKI: rootfs already contains a UKI at {path}");
}
};
// Verify kernel and initramfs exist
if !root