From e7c27913dbb090cdce36b87a1d0d3d14564b5b65 Mon Sep 17 00:00:00 2001 From: Dudecake Date: Wed, 10 Jun 2026 12:30:58 +0000 Subject: [PATCH 1/4] Filter out BLS entries not managed by bootc Signed-off-by: Dudecake --- crates/lib/src/bootc_composefs/status.rs | 24 ++++++++++++------- crates/lib/src/composefs_consts.rs | 3 +++ ...st-composefs-corrupted-state-resilience.nu | 6 ++--- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/crates/lib/src/bootc_composefs/status.rs b/crates/lib/src/bootc_composefs/status.rs index 7006d9f2a1..01ff4def67 100644 --- a/crates/lib/src/bootc_composefs/status.rs +++ b/crates/lib/src/bootc_composefs/status.rs @@ -23,8 +23,8 @@ use crate::{ utils::{compute_store_boot_digest_for_uki, get_uki_cmdline}, }, composefs_consts::{ - ORIGIN_KEY_BOOT_DIGEST, ORIGIN_KEY_IMAGE, ORIGIN_KEY_MANIFEST_DIGEST, TYPE1_ENT_PATH, - TYPE1_ENT_PATH_STAGED, USER_CFG, USER_CFG_STAGED, + BLS_ENTRY_PREFIX, ORIGIN_KEY_BOOT_DIGEST, ORIGIN_KEY_IMAGE, ORIGIN_KEY_MANIFEST_DIGEST, + TYPE1_ENT_PATH, TYPE1_ENT_PATH_STAGED, USER_CFG, USER_CFG_STAGED, }, install::EFI_LOADER_INFO, parsers::{ @@ -306,7 +306,7 @@ fn get_sorted_type1_boot_entries_helper( .to_str() .ok_or(anyhow::anyhow!("Found non UTF-8 characters in filename"))?; - if !file_name.ends_with(".conf") { + if !(file_name.starts_with(BLS_ENTRY_PREFIX) && file_name.ends_with(".conf")) { continue; } @@ -1548,8 +1548,16 @@ mod tests { "loader/entries/random_file.txt", "Random file that we won't parse", )?; - tempdir.atomic_write("loader/entries/entry1.conf", entry1)?; - tempdir.atomic_write("loader/entries/entry2.conf", entry2)?; + tempdir.atomic_write( + "loader/entries/random_file.conf", + "Random file that we won't parse", + )?; + tempdir.atomic_write( + "loader/entries/bootc_random_file.txt", + "Random file that we won't parse", + )?; + tempdir.atomic_write("loader/entries/bootc_entry1.conf", entry1)?; + tempdir.atomic_write("loader/entries/bootc_entry2.conf", entry2)?; let result = get_sorted_type1_boot_entries_helper(&tempdir, true, false, Bootloader::Systemd) @@ -1933,8 +1941,8 @@ mod tests { tempdir.create_dir_all("loader/entries")?; tempdir.create_dir_all("loader/entries.staged")?; - tempdir.atomic_write("loader/entries/active.conf", active_entry)?; - tempdir.atomic_write("loader/entries.staged/staged.conf", staged_entry)?; + tempdir.atomic_write("loader/entries/bootc_active.conf", active_entry)?; + tempdir.atomic_write("loader/entries.staged/bootc_staged.conf", staged_entry)?; let result = list_type1_entries(&tempdir)?; assert_eq!(result.len(), 2); @@ -1971,7 +1979,7 @@ mod tests { let entry2 = format!( r#" - title Fedora Linux (2.0.0) + title Fedora Linux (2.0.0) version 2.0.0 sort-key {} linux /boot/vmlinuz diff --git a/crates/lib/src/composefs_consts.rs b/crates/lib/src/composefs_consts.rs index fdaf7e1682..45a7dd071e 100644 --- a/crates/lib/src/composefs_consts.rs +++ b/crates/lib/src/composefs_consts.rs @@ -56,6 +56,9 @@ pub(crate) const BLS_ENTRY_FILE_PREFIX: &str = "bootc_"; /// The prefix for names of UKI and UKI Addons pub(crate) const UKI_NAME_PREFIX: &str = TYPE1_BOOT_DIR_PREFIX; +/// The prefix for BLS file entries +pub(crate) const BLS_ENTRY_PREFIX: &str = "bootc_"; + /// Prefix for OCI tags owned by bootc in the composefs repository. /// /// Tags are created as `localhost/bootc-` to act as GC roots diff --git a/tmt/tests/booted/test-composefs-corrupted-state-resilience.nu b/tmt/tests/booted/test-composefs-corrupted-state-resilience.nu index 01d4d8424b..f53977bf42 100644 --- a/tmt/tests/booted/test-composefs-corrupted-state-resilience.nu +++ b/tmt/tests/booted/test-composefs-corrupted-state-resilience.nu @@ -40,9 +40,9 @@ def first_boot [] { systemd-run -p MountFlags=slave -qdPG -- /bin/sh -c $" mount -orw,remount /sysroot cd ($entries_dir) - cp * new-entry.conf - - sed -i 's;($booted_verity);($missing_verity);' new-entry.conf + cp * bootc_new-entry.conf + + sed -i 's;($booted_verity);($missing_verity);' bootc_new-entry.conf " # This should work but log a warning in journal From 8a9432352b4d54886abc2b52fb7485925cd62752 Mon Sep 17 00:00:00 2001 From: Dudecake Date: Wed, 10 Jun 2026 12:37:32 +0000 Subject: [PATCH 2/4] Copy unmanaged BLS entries to staging dir on upgrade Signed-off-by: Dudecake --- .../backwards_compat/bcompat_boot.rs | 69 +++++++++- crates/lib/src/bootc_composefs/boot.rs | 118 ++++++++++++++++++ crates/lib/src/bootc_composefs/utils.rs | 18 +++ .../booted/test-image-pushpull-upgrade.nu | 18 +++ tmt/tests/booted/test-image-upgrade-reboot.nu | 17 +++ 5 files changed, 239 insertions(+), 1 deletion(-) diff --git a/crates/lib/src/bootc_composefs/backwards_compat/bcompat_boot.rs b/crates/lib/src/bootc_composefs/backwards_compat/bcompat_boot.rs index b7cbc33c17..8c21c12873 100644 --- a/crates/lib/src/bootc_composefs/backwards_compat/bcompat_boot.rs +++ b/crates/lib/src/bootc_composefs/backwards_compat/bcompat_boot.rs @@ -11,10 +11,11 @@ use crate::{ ComposefsCmdline, get_bootloader, get_sorted_grub_uki_boot_entries, get_sorted_type1_boot_entries, }, + utils::stage_non_manged_bls_entries, }, composefs_consts::{ ORIGIN_KEY_BOOT, ORIGIN_KEY_BOOT_TYPE, STATE_DIR_RELATIVE, TYPE1_BOOT_DIR_PREFIX, - TYPE1_ENT_PATH_STAGED, UKI_NAME_PREFIX, USER_CFG_STAGED, + TYPE1_ENT_PATH, TYPE1_ENT_PATH_STAGED, UKI_NAME_PREFIX, USER_CFG_STAGED, }, parsers::bls_config::{BLSConfig, BLSConfigType, EFIKey}, spec::BootloaderKind, @@ -244,6 +245,8 @@ fn create_staged_bls_entries(boot_dir: &Dir, entries: &Vec<(String, BLSConfig)>) staged_entries.atomic_write(filename, new_entry.to_string().as_bytes())?; } + stage_non_manged_bls_entries(&boot_dir.open_dir(TYPE1_ENT_PATH)?, &staged_entries)?; + fsync(staged_entries.reopen_as_ownedfd()?).context("fsync") } @@ -399,3 +402,67 @@ pub(crate) async fn prepend_custom_prefix( Ok(()) } + +#[cfg(test)] +mod tests { + use crate::bootc_composefs::backwards_compat::bcompat_boot::*; + use crate::parsers::bls_config::parse_bls_config; + use anyhow::Result; + use cap_std_ext::{cap_std, dirext::CapStdExtDirExt}; + + #[test] + fn test_create_staged_bls_entries() -> Result<()> { + let tempdir = cap_std_ext::cap_tempfile::tempdir(cap_std::ambient_authority())?; + + let entry1 = r#" + title Fedora 42.20250623.3.1 (CoreOS) + version fedora-42.0 + sort-key 1 + linux /boot/7e11ac46e3e022053e7226a20104ac656bf72d1a84e3a398b7cce70e9df188b6/vmlinuz-5.14.10 + initrd /boot/7e11ac46e3e022053e7226a20104ac656bf72d1a84e3a398b7cce70e9df188b6/initramfs-5.14.10.img + options root=UUID=abc123 rw composefs=7e11ac46e3e022053e7226a20104ac656bf72d1a84e3a398b7cce70e9df188b6 + "#; + let entry2 = r#" + title Fedora 41.20250214.2.0 (CoreOS) + version fedora-42.0 + sort-key 2 + efi /EFI/Linux/f7415d75017a12a387a39d2281e033a288fc15775108250ef70a01dcadb93346.efi + options root=UUID=abc123 rw composefs=febdf62805de2ae7b6b597f2a9775d9c8a753ba1e5f09298fc8fbe0b0d13bf01 + "#; + let entry3 = r#" + title Test + version 1 + sort-key 9 + efi /EFI/boot/test.efi + "#; + + tempdir.create_dir_all("loader/entries")?; + tempdir.atomic_write("loader/entries/bootc_entry1.conf", entry1)?; + tempdir.atomic_write("loader/entries/bootc_entry2.conf", entry2)?; + tempdir.atomic_write("loader/entries/entry3.conf", entry3)?; + + let entries = vec![ + ( + "bootc_entry1.conf".to_string(), + parse_bls_config(entry1).unwrap(), + ), + ( + "bootc_entry2.conf".to_string(), + parse_bls_config(entry2).unwrap(), + ), + ]; + + create_staged_bls_entries(&tempdir, &entries).unwrap(); + + assert!(tempdir.exists("loader/entries.staged/bootc_entry1.conf")); + assert!(tempdir.exists("loader/entries.staged/bootc_entry2.conf")); + assert!(tempdir.exists("loader/entries.staged/entry3.conf")); + + assert_eq!( + str::from_utf8(&tempdir.read("loader/entries.staged/entry3.conf")?[..])?, + entry3 + ); + + Ok(()) + } +} diff --git a/crates/lib/src/bootc_composefs/boot.rs b/crates/lib/src/bootc_composefs/boot.rs index 155bd6dce7..7f9a8dea98 100644 --- a/crates/lib/src/bootc_composefs/boot.rs +++ b/crates/lib/src/bootc_composefs/boot.rs @@ -103,6 +103,7 @@ use serde::{Deserialize, Serialize}; use crate::bootc_composefs::state::{get_booted_bls, write_composefs_state}; use crate::bootc_composefs::status::build_composefs_karg; +use crate::bootc_composefs::utils::stage_non_manged_bls_entries; use crate::bootc_kargs::compute_new_kargs; use crate::composefs_consts::{ BLS_ENTRY_FILE_PREFIX, TYPE1_BOOT_DIR_PREFIX, TYPE1_ENT_PATH, TYPE1_ENT_PATH_STAGED, @@ -1078,6 +1079,12 @@ pub(crate) fn setup_composefs_bls_boot( )?; } + if is_upgrade { + let original_entries = + Dir::open_ambient_dir(&loader_path.join(BOOT_LOADER_ENTRIES), ambient_authority())?; + stage_non_manged_bls_entries(&original_entries, &loader_entries_dir)?; + } + let owned_loader_entries_fd = loader_entries_dir .reopen_as_ownedfd() .context("Reopening as owned fd")?; @@ -1795,6 +1802,10 @@ fn write_systemd_uki_config( )?; } + if let BootSetupType::Upgrade(_) = setup_type { + stage_non_manged_bls_entries(&esp_dir.open_dir(TYPE1_ENT_PATH)?, &entries_dir)?; + } + // Write the timeout for bootloader menu if not exists if !esp_dir.exists(SYSTEMD_LOADER_CONF_PATH) { esp_dir @@ -2709,6 +2720,16 @@ pub(crate) fn expected_boot_image_ids( #[cfg(test)] mod tests { + use cap_std::fs::Dir; + use cap_std_ext::cap_std; + use ostree_ext::{ostree::SysrootBuilder, sysroot::SysrootLock}; + use std::fs::File; + + use crate::{ + bootc_composefs::{digest::new_temp_composefs_repo, status::ComposefsCmdline}, + k8sapitypes::Resource, + }; + use super::*; use composefs::erofs::format::FormatVersion; use composefs_boot::bootloader::{AbootArtifact, AbootEncoding, AbootEntry, Type2Entry}; @@ -3544,4 +3565,101 @@ mod tests { } } } + + #[tokio::test] + async fn test_write_systemd_uki_config() -> Result<()> { + let tempdir = cap_std_ext::cap_tempfile::tempdir(cap_std::ambient_authority())?; + + let entry1 = r#" + title Fedora 42.20250623.3.1 (CoreOS) + version fedora-42.0 + sort-key 1 + linux /boot/7e11ac46e3e022053e7226a20104ac656bf72d1a84e3a398b7cce70e9df188b6/vmlinuz-5.14.10 + initrd /boot/7e11ac46e3e022053e7226a20104ac656bf72d1a84e3a398b7cce70e9df188b6/initramfs-5.14.10.img + options root=UUID=abc123 rw composefs=7e11ac46e3e022053e7226a20104ac656bf72d1a84e3a398b7cce70e9df188b6 + "#; + let entry2 = r#" + title Fedora 41.20250214.2.0 (CoreOS) + version fedora-42.0 + sort-key 2 + efi /EFI/Linux/f7415d75017a12a387a39d2281e033a288fc15775108250ef70a01dcadb93346.efi + options root=UUID=abc123 rw composefs=febdf62805de2ae7b6b597f2a9775d9c8a753ba1e5f09298fc8fbe0b0d13bf01 + "#; + let entry3 = r#" + title Test + version 1 + sort-key 9 + efi /EFI/boot/test.efi + "#; + + tempdir.create_dir_all("loader/entries")?; + tempdir.atomic_write("loader/entries/bootc_entry1.conf", entry1)?; + tempdir.atomic_write("loader/entries/bootc_entry2.conf", entry2)?; + tempdir.atomic_write("loader/entries/entry3.conf", entry3)?; + + let sysroot_path = format!( + "{}/test_write_systemd_uki_config", + std::env::temp_dir().display() + ); + std::fs::create_dir_all(&sysroot_path)?; + let dir = Dir::from_std_file(File::open(&sysroot_path)?); + let sysroot_dir = cap_std_ext::cap_tempfile::tempdir_in(&dir)?; + sysroot_dir.create_dir_all("ostree/repo")?; + sysroot_dir.atomic_write( + "ostree/repo/config", + r#"[core] + repo_version=1 + mode=bare-split-xattrs + "#, + )?; + + let sysroot = SysrootBuilder::new() + .path(Some(sysroot_path.into())) + .create(None)?; + let sysroot_lock = SysrootLock::new_from_sysroot(&sysroot).await?; + let storage = Storage::new_ostree(sysroot_lock, &sysroot_dir)?; + let cmdline = Box::new(ComposefsCmdline::new(Default::default())); + let (_repo_dir, repo) = new_temp_composefs_repo(FormatVersion::V1)?; + let booted_composefs = BootedComposefs { + repo: repo, + cmdline: Box::leak(cmdline), + }; + let host = Host { + resource: Resource { + api_version: Default::default(), + kind: Default::default(), + metadata: Default::default(), + }, + spec: Default::default(), + status: Default::default(), + }; + let boot_setup_type = BootSetupType::Upgrade((&storage, &booted_composefs, &host)); + let boot_label = "label".to_string(); + let id = Sha512HashValue::EMPTY; + let bootloader = Bootloader::Systemd; + + write_systemd_uki_config( + &tempdir, + &boot_setup_type, + boot_label, + None, + None, + &id, + &bootloader, + )?; + + assert!(tempdir.exists(SYSTEMD_LOADER_CONF_PATH)); + assert!(tempdir.exists("loader/entries.staged")); + assert!(tempdir.exists("loader/entries.staged/bootc_bootc-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000-1.conf")); + assert!(tempdir.exists("loader/entries.staged/bootc_bootc-fedora-42.0-0.conf")); + assert!(!tempdir.exists("loader/entries.staged/bootc_bootc-fedora-42.0-1.conf")); + assert!(tempdir.exists("loader/entries.staged/entry3.conf")); + + assert_eq!( + str::from_utf8(&tempdir.read("loader/entries.staged/entry3.conf")?[..])?, + entry3 + ); + + Ok(()) + } } diff --git a/crates/lib/src/bootc_composefs/utils.rs b/crates/lib/src/bootc_composefs/utils.rs index f7aab3eea2..10baaa4de5 100644 --- a/crates/lib/src/bootc_composefs/utils.rs +++ b/crates/lib/src/bootc_composefs/utils.rs @@ -3,9 +3,11 @@ use crate::{ boot::{BOOTC_UKI_DIR, compute_boot_digest_uki, get_uki_name}, state::update_boot_digest_in_origin, }, + composefs_consts::BLS_ENTRY_PREFIX, store::Storage, }; use anyhow::Result; +use cap_std_ext::{cap_std::fs::Dir, dirext::CapStdExtDirExt}; use composefs_ctl::composefs_boot; use fn_error_context::context; use linux_kernel_cmdline::utf8::Cmdline; @@ -52,3 +54,19 @@ pub(crate) fn get_uki_cmdline( return Ok(Cmdline::from(cmdline.to_owned())); } + +#[context("Staging non-managed BLS entry")] +pub(crate) fn stage_non_manged_bls_entries( + original_entries: &Dir, + staged_entries: &Dir, +) -> Result<()> { + for entry in original_entries.entries_utf8()? { + let entry = entry?; + let entry_name = entry.file_name()?; + if entry.file_type()?.is_file() && !entry_name.starts_with(BLS_ENTRY_PREFIX) { + let content = original_entries.read(&entry_name)?; + staged_entries.atomic_write(entry_name, content)?; + } + } + Ok(()) +} diff --git a/tmt/tests/booted/test-image-pushpull-upgrade.nu b/tmt/tests/booted/test-image-pushpull-upgrade.nu index 237cb3c3f1..f8744a51ad 100644 --- a/tmt/tests/booted/test-image-pushpull-upgrade.nu +++ b/tmt/tests/booted/test-image-pushpull-upgrade.nu @@ -168,6 +168,15 @@ def second_boot [] { let td = mktemp -d cd $td + if ($is_composefs and ("/boot/loader/entries" | path exists)) { + (" + title Test + version 1 + sort-key 9 + efi /EFI/boot/test.efi + ") | save /boot/loader/entries/entry.conf + } + mkdir usr/lib/bootc/kargs.d { kargs: $kargsv1 } | to toml | save usr/lib/bootc/kargs.d/05-testkargs.toml let dockerfile = $"FROM localhost/bootc as base @@ -181,6 +190,11 @@ RUN echo test content2 > /usr/share/blah.txt print $"booted_digest = ($booted_digest)" # We should already be fetching updates from container storage bootc upgrade + + if ($is_composefs and ("/boot/loader/entries" | path exists)) { + assert ("/boot/loader/entries.staged/entry.conf" | path exists) "custom BLS entry must exist" + } + # Verify we staged an update let st = bootc status --json | from json let staged_digest = $st.status.staged.image.imageDigest @@ -217,6 +231,10 @@ def third_boot [] { assert ($quoted_karg in $cmdline) $"Expected quoted karg ($quoted_karg) not found in cmdline after upgrade" } + if ($is_composefs and ("/boot/loader/entries" | path exists)) { + assert ("/boot/loader/entries/entry.conf" | path exists) "custom BLS entry must exist" + } + tap ok } diff --git a/tmt/tests/booted/test-image-upgrade-reboot.nu b/tmt/tests/booted/test-image-upgrade-reboot.nu index 94bb59f91c..1488ec5cca 100644 --- a/tmt/tests/booted/test-image-upgrade-reboot.nu +++ b/tmt/tests/booted/test-image-upgrade-reboot.nu @@ -86,6 +86,15 @@ def initial_build [] { if $is_composefs { "true" | save /var/was-composefs $st.status.booted.composefs.verity | save /var/original-verity + + if (("/boot/loader/entries" | path exists)) { + (" + title Test + version 1 + sort-key 9 + efi /EFI/boot/test.efi + ") | save /boot/loader/entries/entry.conf + } } # Now, switch into the new image. First stage it, then run switch --apply for @@ -93,6 +102,11 @@ def initial_build [] { # staged deployments. print $"Staging ($imgsrc)" bootc switch --transport containers-storage ($imgsrc) + + if ($is_composefs and ("/boot/loader/entries" | path exists)) { + assert ("/boot/loader/entries.staged/entry.conf" | path exists) "custom BLS entry must exist" + } + print "Re-running with --apply (spec unchanged, should still reboot)" tmt-reboot -c $"bootc switch --apply --transport containers-storage ($imgsrc)" } @@ -139,6 +153,9 @@ def second_boot [] { print $"EFI files: ($efi_files)" assert (($efi_files | length) >= 2) $"expected at least 2 UKIs on ESP, found ($efi_files | length)" } + if (("/boot/loader/entries" | path exists)) { + assert ("/boot/loader/entries/entry.conf" | path exists) "custom BLS entry must exist" + } } tap ok From 05aec35cc8982a6a07d9ae18ad84b57d246647e8 Mon Sep 17 00:00:00 2001 From: Dudecake Date: Tue, 14 Jul 2026 19:00:32 +0000 Subject: [PATCH 3/4] Copy unmanaged BLS entries to staging dir on rollback Signed-off-by: Dudecake --- crates/lib/src/bootc_composefs/rollback.rs | 73 +++++++++++++++++++++- tmt/tests/booted/test-rollback.nu | 14 +++++ 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/crates/lib/src/bootc_composefs/rollback.rs b/crates/lib/src/bootc_composefs/rollback.rs index fe686c69ee..5d46d6003c 100644 --- a/crates/lib/src/bootc_composefs/rollback.rs +++ b/crates/lib/src/bootc_composefs/rollback.rs @@ -13,8 +13,10 @@ use crate::bootc_composefs::boot::{ secondary_sort_key, type1_entry_conf_file_name, }; use crate::bootc_composefs::status::{get_composefs_status, get_sorted_type1_boot_entries}; +use crate::bootc_composefs::utils::stage_non_manged_bls_entries; use crate::composefs_consts::{ - COMPOSEFS_STAGED_DEPLOYMENT_FNAME, COMPOSEFS_TRANSIENT_STATE_DIR, TYPE1_ENT_PATH_STAGED, + COMPOSEFS_STAGED_DEPLOYMENT_FNAME, COMPOSEFS_TRANSIENT_STATE_DIR, TYPE1_ENT_PATH, + TYPE1_ENT_PATH_STAGED, }; use crate::deploy::ROLLBACK_JOURNAL_ID; use crate::spec::{Bootloader, BootloaderKind, Host}; @@ -199,6 +201,8 @@ fn rollback_composefs_entries(host: &Host, boot_dir: &Dir, bootloader: Bootloade .with_context(|| format!("Writing to {file_name}"))?; } + stage_non_manged_bls_entries(&boot_dir.open_dir(TYPE1_ENT_PATH)?, &rollback_entries_dir)?; + let rollback_entries_dir = rollback_entries_dir .reopen_as_ownedfd() .context("Reopening as owned fd")?; @@ -299,3 +303,70 @@ pub(crate) async fn composefs_rollback( Ok(()) } + +#[cfg(test)] +mod tests { + use anyhow::{Ok, Result}; + use cap_std_ext::{cap_std, dirext::CapStdExtDirExt}; + + use crate::k8sapitypes::Resource; + use crate::spec::Bootloader; + use crate::{bootc_composefs::rollback::rollback_composefs_entries, spec::Host}; + + #[test] + fn test_rollback_composefs_entries() -> Result<()> { + let tempdir = cap_std_ext::cap_tempfile::tempdir(cap_std::ambient_authority())?; + + let entry1 = r#" + title Fedora 42.20250623.3.1 (CoreOS) + version fedora-42.0 + sort-key 1 + linux /boot/7e11ac46e3e022053e7226a20104ac656bf72d1a84e3a398b7cce70e9df188b6/vmlinuz-5.14.10 + initrd /boot/7e11ac46e3e022053e7226a20104ac656bf72d1a84e3a398b7cce70e9df188b6/initramfs-5.14.10.img + options root=UUID=abc123 rw composefs=7e11ac46e3e022053e7226a20104ac656bf72d1a84e3a398b7cce70e9df188b6 + "#; + let entry2 = r#" + title Fedora 41.20250214.2.0 (CoreOS) + version fedora-42.0 + sort-key 2 + efi /EFI/Linux/f7415d75017a12a387a39d2281e033a288fc15775108250ef70a01dcadb93346.efi + options root=UUID=abc123 rw composefs=febdf62805de2ae7b6b597f2a9775d9c8a753ba1e5f09298fc8fbe0b0d13bf01 + "#; + let entry3 = r#" + title Test + version 1 + sort-key 9 + efi /EFI/boot/test.efi + "#; + + tempdir.create_dir_all("loader/entries")?; + tempdir.atomic_write("loader/entries/bootc_entry1.conf", entry1)?; + tempdir.atomic_write("loader/entries/bootc_entry2.conf", entry2)?; + tempdir.atomic_write("loader/entries/entry3.conf", entry3)?; + + let host = Host { + resource: Resource { + api_version: Default::default(), + kind: Default::default(), + metadata: Default::default(), + }, + spec: Default::default(), + status: Default::default(), + }; + let bootloader = Bootloader::None; + + rollback_composefs_entries(&host, &tempdir, bootloader)?; + + assert!(tempdir.exists("loader/entries")); + assert!(tempdir.exists("loader/entries/bootc_bootc-fedora-42.0-0.conf")); + assert!(tempdir.exists("loader/entries/bootc_bootc-fedora-42.0-1.conf")); + assert!(tempdir.exists("loader/entries/entry3.conf")); + + assert_eq!( + str::from_utf8(&tempdir.read("loader/entries/entry3.conf")?[..])?, + entry3 + ); + + Ok(()) + } +} diff --git a/tmt/tests/booted/test-rollback.nu b/tmt/tests/booted/test-rollback.nu index 9e62810893..be13f93f5c 100644 --- a/tmt/tests/booted/test-rollback.nu +++ b/tmt/tests/booted/test-rollback.nu @@ -19,6 +19,7 @@ journalctl --list-boots let st = bootc status --json | from json let booted = $st.status.booted.image +let is_composefs = (tap is_composefs) def imgsrc [] { $env.BOOTC_upgrade_image? | default "localhost/bootc-derived-local" @@ -80,9 +81,22 @@ def second_boot_rollback [] { # A freshly booted deployment has no rollback queued assert_rollback_queued false + if ($is_composefs and ("/boot/loader/entries" | path exists)) { + (" + title Test + version 1 + sort-key 9 + efi /EFI/boot/test.efi + ") | save /boot/loader/entries/entry.conf + } + print "Performing bootc rollback..." bootc rollback + if ($is_composefs and ("/boot/loader/entries" | path exists)) { + assert ("/boot/loader/entries/entry.conf" | path exists) "custom BLS entry must exist" + } + # ...and a real queued rollback must be reported as one assert_rollback_queued true From 2df98c58fdfdf12e7a77d5b3e866564e69600cb0 Mon Sep 17 00:00:00 2001 From: Dudecake Date: Wed, 10 Jun 2026 12:37:32 +0000 Subject: [PATCH 4/4] Added tests for non-confirming efi BLS entries Signed-off-by: Dudecake --- crates/lib/src/parsers/bls_config.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/crates/lib/src/parsers/bls_config.rs b/crates/lib/src/parsers/bls_config.rs index c299a7b663..df4696e1e1 100644 --- a/crates/lib/src/parsers/bls_config.rs +++ b/crates/lib/src/parsers/bls_config.rs @@ -868,6 +868,34 @@ mod tests { Ok(()) } + #[test] + fn test_boot_artifact_verity_efi_no_prefix() -> Result<()> { + let input = r#" + title Test + version 1 + efi /EFI/boot/test.efi + "#; + let config = parse_bls_config(&input)?; + + let result = config.get_verity(); + assert!(result.is_err()); + Ok(()) + } + + #[test] + fn test_boot_artifact_verity_efi_no_extension() -> Result<()> { + let input = r#" + title Test + version 1 + efi /EFI/boot/test + "#; + let config = parse_bls_config(&input)?; + + let result = config.get_verity(); + assert!(result.is_err()); + Ok(()) + } + /// Test that Non-EFI boot_artifact_name fails when linux path has no parent #[test] fn test_boot_artifact_name_non_efi_no_parent() {