From 3d600847e36e498920e67cd58311abff2344d3f8 Mon Sep 17 00:00:00 2001 From: Pragyan Poudyal Date: Thu, 23 Apr 2026 14:46:00 +0530 Subject: [PATCH 1/2] efi: Choose update layout depending on /usr/lib/efi In https://github.com/coreos/bootupd/pull/995 changes were made to move files from `usr/lib/ostree-boot/EFI` to `usr/lib/efi` instead of `usr/lib/bootupd` which caused issues with tools that expect the older layout. We now check for the existence of `usr/lib/efi`, and only use the new layout if we find it. Else, default back to the older layout at `usr/lib/bootupd`. Signed-off-by: Pragyan Poudyal --- src/efi.rs | 165 +++++++++++++++++++++++-------------------- src/packagesystem.rs | 15 ---- 2 files changed, 89 insertions(+), 91 deletions(-) diff --git a/src/efi.rs b/src/efi.rs index 1022f4af..7d7c9513 100644 --- a/src/efi.rs +++ b/src/efi.rs @@ -568,50 +568,49 @@ impl Component for Efi { } fn generate_update_metadata(&self, sysroot: &str) -> Result> { - let sysroot_path = Path::new(sysroot); + let sysroot_path = Utf8Path::new(sysroot); let sysroot_dir = Dir::open_ambient_dir(sysroot_path, cap_std::ambient_authority())?; - if let Some(ostreeboot) = sysroot_dir + let ostreeboot = sysroot_dir .open_dir_optional(ostreeutil::BOOT_PREFIX) - .context("Opening usr/lib/ostree-boot")? - { + .context("Opening usr/lib/ostree-boot")?; + + // Newer images >= F44 have boot entries in /usr/lib/efi, but older images + // have them in /usr/lib/ostree-boot, which we move to /usr/lib/bootupd/updates/EFI + let metadata = if sysroot_path.join(EFILIB).exists() { + println!("Generating metadata from {EFILIB}"); + generate_meta_from_usr_efi(sysroot_path)? + } else { + match &ostreeboot { + Some(..) => { + println!( + "Moving {}/efi/EFI to {BOOTUPD_UPDATES_DIR}/EFI", + ostreeutil::BOOT_PREFIX + ); + // Transfer usr/lib/ostree-boot/EFI files to usr/lib/bootupd/updates/EFI + transfer_ostree_boot_to_bootupd_updates(sysroot_path, self)? + } + + // If /usr/lib/ostree-boot doesn't exist, assume /usr/lib/efi will + None => anyhow::bail!("/usr/lib/ostree-boot and /usr/lib/efi not found"), + } + }; + + if let Some(ostreeboot) = ostreeboot { let cruft = ["loader", "grub2"]; for p in cruft.iter() { + println!("Removing {p} from {}", ostreeutil::BOOT_PREFIX); ostreeboot.remove_all_optional(p)?; } - // Transfer ostree-boot EFI files to usr/lib/efi - transfer_ostree_boot_to_usr(sysroot_path)?; + println!("Removing efi/EFI from {}", ostreeutil::BOOT_PREFIX); // Remove usr/lib/ostree-boot/efi/EFI dir (after transfer) or if it is empty ostreeboot.remove_all_optional("efi/EFI")?; - } + }; - if let Some(efi_components) = - get_efi_component_from_usr(Utf8Path::from_path(sysroot_path).unwrap(), EFILIB)? - { - let mut packages = Vec::new(); - let mut modules_vec: Vec = vec![]; - for efi in efi_components { - packages.push(format!("{}-{}", efi.name, efi.version)); - modules_vec.push(Module { - name: efi.name, - rpm_evr: efi.version, - }); - } - modules_vec.sort_unstable(); - - // change to now to workaround https://github.com/coreos/bootupd/issues/933 - let timestamp = std::time::SystemTime::now(); - let meta = ContentMetadata { - timestamp: chrono::DateTime::::from(timestamp), - version: packages.join(","), - versions: Some(modules_vec), - }; - write_update_metadata(sysroot, self, &meta)?; - Ok(Some(meta)) - } else { - anyhow::bail!("Failed to find EFI components"); - } + write_update_metadata(sysroot_path.as_str(), self, &metadata)?; + + Ok(Some(metadata)) } fn query_update(&self, sysroot: &openat::Dir) -> Result> { @@ -801,6 +800,34 @@ fn find_file_recursive>(dir: P, target_file: &str) -> Result Result { + let Some(efi_components) = get_efi_component_from_usr(sysroot_path, EFILIB)? else { + anyhow::bail!("Failed to find EFI components"); + }; + + let mut packages = Vec::new(); + let mut modules_vec: Vec = vec![]; + for efi in efi_components { + packages.push(format!("{}-{}", efi.name, efi.version)); + modules_vec.push(Module { + name: efi.name, + rpm_evr: efi.version, + }); + } + modules_vec.sort_unstable(); + + // change to now to workaround https://github.com/coreos/bootupd/issues/933 + let timestamp = std::time::SystemTime::now(); + let meta = ContentMetadata { + timestamp: chrono::DateTime::::from(timestamp), + version: packages.join(","), + versions: Some(modules_vec), + }; + + Ok(meta) +} + #[derive(Debug, PartialEq, Eq)] pub struct EFIComponent { pub name: String, @@ -851,57 +878,43 @@ fn get_efi_component_from_usr<'a>( Ok(Some(components)) } -/// Copy files from usr/lib/ostree-boot/efi/EFI to /usr/lib/efi/// -fn transfer_ostree_boot_to_usr(sysroot: &Path) -> Result<()> { - let ostreeboot_efi = Path::new(ostreeutil::BOOT_PREFIX).join("efi"); - let ostreeboot_efi_path = sysroot.join(&ostreeboot_efi); +/// Copies usr/lib/ostree-boot/EFI to usr/lib/bootupd/updates +fn transfer_ostree_boot_to_bootupd_updates( + sysroot: &Utf8Path, + component: &Efi, +) -> Result { + let ostreebootdir = sysroot.join(ostreeutil::BOOT_PREFIX); - let efi = ostreeboot_efi_path.join("EFI"); - if !efi.exists() { - return Ok(()); + // move EFI files to updates dir from /usr/lib/ostree-boot + if !ostreebootdir.exists() { + anyhow::bail!("Failed to find {ostreebootdir}"); } - for entry in WalkDir::new(&efi) { - let entry = entry?; - if entry.file_type().is_file() { - let entry_path = entry.path(); + let efisrc = ostreebootdir.join("efi/EFI"); + if !efisrc.exists() { + bail!("Failed to find {:?}", &efisrc); + } - // get path EFI/{BOOT,}/ - let filepath = entry_path.strip_prefix(&ostreeboot_efi_path)?; - // get path /boot/efi/EFI/{BOOT,}/ - let boot_filepath = Path::new("/boot/efi").join(filepath); + let dest_efidir = component_updatedir(sysroot.as_str(), component); + let dest_efidir = Utf8PathBuf::from_path_buf(dest_efidir).expect("Path is invalid UTF-8"); - // Run `rpm -qf ` - let pkg = crate::packagesystem::query_file( - sysroot.to_str().unwrap(), - boot_filepath.to_str().unwrap(), - )?; + // Fork off mv() because on overlayfs one can't rename() a lower level + // directory today, and this will handle the copy fallback. + Command::new("mv") + .args([&efisrc, &dest_efidir]) + .run_capture_stderr()?; - let (name, evr) = pkg.split_once(' ').unwrap(); - let component = name.split('-').next().unwrap_or(""); - // get path usr/lib/efi// - let efilib_path = Path::new(EFILIB).join(component).join(evr); + let efidir = openat::Dir::open(dest_efidir.as_std_path()) + .with_context(|| format!("Opening {}", dest_efidir))?; - let sysroot_dir = openat::Dir::open(sysroot)?; - // Ensure dest parent directory exists - if let Some(parent) = efilib_path.join(filepath).parent() { - sysroot_dir.ensure_dir_all(parent, 0o755)?; - } + let files = crate::util::filenames(&efidir)?.into_iter().map(|mut f| { + f.insert_str(0, "/boot/efi/EFI"); + f + }); - // Source dir is usr/lib/ostree-boot/efi - let src = sysroot_dir - .sub_dir(&ostreeboot_efi) - .context("Opening ostree-boot dir")?; - // Dest dir is usr/lib/efi// - let dest = sysroot_dir - .sub_dir(&efilib_path) - .context("Opening usr/lib/efi dir")?; - // Copy file from ostree-boot to usr/lib/efi - src.copy_file_at(filepath, &dest, filepath) - .context("Copying file to usr/lib/efi")?; - } - } - Ok(()) + let files = files.collect::>(); + + query_files(sysroot.as_str(), files) } #[cfg(test)] diff --git a/src/packagesystem.rs b/src/packagesystem.rs index c123b4fc..f20e5e81 100644 --- a/src/packagesystem.rs +++ b/src/packagesystem.rs @@ -115,21 +115,6 @@ fn split_name_version(input: &str) -> Option<(String, String)> { Some((name.to_string(), format!("{version}-{release}"))) } -/// Query the rpm database and get package " " -pub(crate) fn query_file(sysroot_path: &str, path: &str) -> Result { - let mut c = ostreeutil::rpm_cmd(sysroot_path)?; - c.args(["-q", "--queryformat", "%{NAME} %{EVR}", "-f", path]); - - let rpmout = c.output()?; - if !rpmout.status.success() { - std::io::stderr().write_all(&rpmout.stderr)?; - bail!("Failed to invoke rpm -qf"); - } - - let output = String::from_utf8(rpmout.stdout)?; - Ok(output.trim().to_string()) -} - fn parse_evr(pkg: &str) -> Module { // assume it is "grub2-1:2.12-28.fc42" (from usr/lib/efi) if !pkg.ends_with(std::env::consts::ARCH) { From fbb5911b647e49939c9c0a47ed65ce5a05d55ec5 Mon Sep 17 00:00:00 2001 From: Pragyan Poudyal Date: Fri, 24 Apr 2026 14:42:53 +0530 Subject: [PATCH 2/2] tests: Update tests for new and old layouts Update e2e tests to handle both, new i.e. `/usr/lib/efi`, and old i.e. `/usr/lib/bootupd/updates` layouts, depending upon the existence of `/usr/lib/efi` Signed-off-by: Pragyan Poudyal --- tests/e2e-update/e2e-update-in-vm.sh | 20 +++++-- tests/e2e-update/e2e-update.sh | 2 + tests/kola/test-bootupd | 85 +++++++++++++++++++++------- 3 files changed, 83 insertions(+), 24 deletions(-) diff --git a/tests/e2e-update/e2e-update-in-vm.sh b/tests/e2e-update/e2e-update-in-vm.sh index 389f50cd..008ed2b8 100755 --- a/tests/e2e-update/e2e-update-in-vm.sh +++ b/tests/e2e-update/e2e-update-in-vm.sh @@ -53,11 +53,21 @@ ok validate bootupctl status | tee out.txt assert_file_has_content_literal out.txt 'Component EFI' -assert_file_has_content_literal out.txt ' Installed: grub2-1:' -assert_not_file_has_content out.txt ' Installed:.*test_bootupd_payload' -assert_not_file_has_content out.txt ' Installed:.*'"${TARGET_GRUB_EVR}" -assert_file_has_content out.txt 'Update: Available:.*'"${TARGET_GRUB_EVR}" -assert_file_has_content out.txt 'Update: Available:.*test_bootupd_payload-1.0' + +if [[ -d /usr/lib/efi ]]; then + assert_file_has_content_literal out.txt ' Installed: grub2-1:' + assert_not_file_has_content out.txt ' Installed:.*test_bootupd_payload' + assert_not_file_has_content out.txt ' Installed:.*'"${TARGET_GRUB_EVR}" + assert_file_has_content out.txt 'Update: Available:.*'"${TARGET_GRUB_EVR}" + assert_file_has_content out.txt 'Update: Available:.*test_bootupd_payload-1.0' +else + assert_file_has_content_literal out.txt ' Installed: grub2-efi-x64-' + assert_not_file_has_content out.txt ' Installed:.*test_bootupd_payload' + assert_not_file_has_content out.txt ' Installed:.*'"${TARGET_GRUB_PKG}" + assert_file_has_content out.txt 'Update: Available:.*'"${TARGET_GRUB_PKG}" + assert_file_has_content out.txt 'Update: Available:.*test_bootupd_payload-1.0' +fi + bootupctl status --print-if-available > out.txt assert_file_has_content_literal 'out.txt' 'Updates available: BIOS EFI' ok update avail diff --git a/tests/e2e-update/e2e-update.sh b/tests/e2e-update/e2e-update.sh index f2a25f4f..9635108e 100755 --- a/tests/e2e-update/e2e-update.sh +++ b/tests/e2e-update/e2e-update.sh @@ -77,6 +77,7 @@ case $(arch) in *) fatal "Unhandled arch $(arch)";; esac target_grub_name=grub2-efi-${grubarch} +target_grub_pkg=$(rpm -qp --queryformat='%{nevra}\n' ${overrides}/rpm/${target_grub_name}-2*.rpm) target_grub_evr=$(rpm -qp --queryformat='%{evr}\n' ${overrides}/rpm/${target_grub_name}-2*.rpm) target_commit=$(cosa meta --get-value ostree-commit) echo "Target commit: ${target_commit}" @@ -97,6 +98,7 @@ systemd: RemainAfterExit=yes Environment=TARGET_COMMIT=${target_commit} Environment=TARGET_GRUB_NAME=${target_grub_name} + Environment=TARGET_GRUB_PKG=${target_grub_pkg} Environment=TARGET_GRUB_EVR=${target_grub_evr} Environment=SRCDIR=/run/bootupd-source # Run via shell because selinux denies systemd writing to 9p apparently diff --git a/tests/kola/test-bootupd b/tests/kola/test-bootupd index 3d557267..4bf99406 100755 --- a/tests/kola/test-bootupd +++ b/tests/kola/test-bootupd @@ -4,7 +4,7 @@ set -xeuo pipefail . ${KOLA_EXT_DATA}/libtest.sh tmpdir=$(mktemp -d) -cd ${tmpdir} +cd "${tmpdir}" echo "using tmpdir: ${tmpdir}" touch .testtmp trap cleanup EXIT @@ -21,20 +21,38 @@ function cleanup () { # Mount the EFI partition. tmpefimount=$(mount_tmp_efi) -bootmount=/boot +# Path to /EFI tmpefidir=${tmpefimount}/EFI +# /usr/lib/bootupd/updates bootupdir=/usr/lib/bootupd/updates +# fedora efisubdir=fedora +# /usr/lib/bootupd/updates/EFI +efiupdir=${bootupdir}/EFI +# /usr/lib/ostree-boot/efi/EFI +ostbaseefi=/usr/lib/ostree-boot/efi/EFI +# /usr/lib/ostree-boot/efi/EFI/fedora +ostefi=${ostbaseefi}/${efisubdir} shimx64=shimx64.efi grubx64=grubx64.efi -efilib=/usr/lib/efi +usrlibefi=/usr/lib/efi +# Path to /EFI/fedora tmpefisubdir=${tmpefidir}/${efisubdir} +if [ -d efilib ]; then efilib_exists=true; else efilib_exists=false; fi + prepare_efi_update() { test -w /usr rm -rf ${bootupdir}/EFI.json } +prepare_efi_update_old_layout() { + test -w /usr + mkdir -p ${ostbaseefi} + cp -a ${efiupdir}.orig/* ${ostbaseefi}/ + rm -rf ${efiupdir} ${bootupdir}/EFI.json +} + # backup: update_metadata_move EFI EFI-bak # restore: update_metadata_move EFI-bak EFI update_metadata_move() { @@ -48,18 +66,30 @@ update_metadata_move() { mv -- "$src" "$dst" } -# Find the files under usr/lib/efi after transferred -shim_path=$(find ${efilib} -name shim.efi) -shimx64_path=$(find ${efilib} -name ${shimx64}) -grubx64_path=$(find ${efilib} -name ${grubx64}) +if $efilib_exists; then + # Find the files under usr/lib/efi after transferred + shim_path=$(find ${usrlibefi} -name shim.efi) + shimx64_path=$(find ${usrlibefi} -name ${shimx64}) + grubx64_path=$(find ${usrlibefi} -name ${grubx64}) -test -n "${shimx64_path}" && test -n "${grubx64_path}" -! test -d "/usr/lib/bootupd/updates/EFI" + test -n "${shimx64_path}" && test -n "${grubx64_path}" + test -d "/usr/lib/bootupd/updates/EFI" && exit 1 +else + shim_path=$(find ${bootupdir} -name shim.efi) + shimx64_path=$(find ${bootupdir} -name ${shimx64}) + grubx64_path=$(find ${bootupdir} -name ${grubx64}) + + test -n "${shimx64_path}" && test -n "${grubx64_path}" +fi bootupctl status > out.txt evr=$(rpm -q grub2-common --qf '%{EVR}') assert_file_has_content_literal out.txt 'Component EFI' -assert_file_has_content_literal out.txt ' Installed: grub2-'"${evr}" +if $efilib_exists; then + assert_file_has_content_literal out.txt ' Installed: grub2-'"${evr}" +else + assert_file_has_content_literal out.txt ' Installed: grub2-efi-x64-' +fi assert_file_has_content_literal out.txt 'Update: At latest version' assert_file_has_content out.txt '^CoreOS aleph version:' ok status @@ -75,10 +105,18 @@ assert_file_has_content err.txt 'error: This command requires root privileges' # From here we'll fake updates test -w /usr || rpm-ostree usroverlay -prepare_efi_update +if $efilib_exists; then + prepare_efi_update + rm -v "${shim_path}" + echo bootupd-test-changes >> "${grubx64_path}" +else + # Save a backup copy of the update dir + cp -a ${efiupdir} ${efiupdir}.orig + prepare_efi_update_old_layout + rm -v ${ostefi}/shim.efi + echo bootupd-test-changes >> ${ostefi}/grubx64.efi +fi -rm -v ${shim_path} -echo bootupd-test-changes >> ${grubx64_path} /usr/libexec/bootupd generate-update-metadata / ver=$(jq -r .version < ${bootupdir}/EFI.json) vers=$(jq -r .versions < ${bootupdir}/EFI.json) @@ -92,14 +130,23 @@ mv new.json ${bootupdir}/EFI.json bootupctl status | tee out.txt assert_file_has_content_literal out.txt 'Component EFI' -assert_file_has_content_literal out.txt ' Installed: grub2-'"${evr}" +if $efilib_exists; then + assert_file_has_content_literal out.txt ' Installed: grub2-'"${evr}" +else + assert_file_has_content_literal out.txt ' Installed: grub2-efi-x64-' +fi assert_not_file_has_content out.txt ' Installed: grub2-.*,test' assert_file_has_content_literal out.txt 'Update: Available:' ok update avail bootupctl status --json > status.json jq -r '.components.EFI.installed.version' < status.json > installed.txt -assert_file_has_content installed.txt '^grub2-'"${evr}" + +if $efilib_exists; then + assert_file_has_content installed.txt '^grub2-'"${evr}" +else + assert_file_has_content installed.txt '^grub2-efi-x64' +fi bootupctl update | tee out.txt assert_file_has_content out.txt 'Updated EFI: grub2-.*,test' @@ -115,13 +162,13 @@ ok validate after update # FIXME see above # assert_file_has_content ${tmpefidir}/${efisubdir}/somenew.efi 'somenewfile' -if test -f ${tmpefisubdir}/shim.efi; then +if test -f "${tmpefisubdir}/shim.efi"; then fatal "failed to remove file" fi -if ! grep -q 'bootupd-test-changes' ${tmpefisubdir}/${grubx64}; then +if ! grep -q 'bootupd-test-changes' "${tmpefisubdir}/${grubx64}"; then fatal "failed to update modified file" fi -cmp ${tmpefisubdir}/${shimx64} ${shimx64_path} +cmp "${tmpefisubdir}/${shimx64}" "${shimx64_path}" ok filesystem changes bootupctl update | tee out.txt @@ -162,7 +209,7 @@ else assert_not_file_has_content out.txt 'Adopted and updated: EFI:' fi -echo "some additions" >> ${tmpefisubdir}/${shimx64} +echo "some additions" >> "${tmpefisubdir}/${shimx64}" if bootupctl validate 2>err.txt; then fatal "unexpectedly passed validation" fi