Repository navigation
UKI Addons Support #2448
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
UKI Addons Support #2448
Changes from all commits
72f7399
89a7e14
aa6cb4a
ae49ce8
9d77327
5b2f98c
a794dbd
c5d6e48
35622a3
3d6b91c
d1b737c
451cd36
6a61180
4002d2a
ffd1a0e
6aaec11
02cc7dd
3f74c4b
3fec90a
caf5380
fb01f99
d3053e8
4524504
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,6 +17,11 @@ use ostree_ext::composefs_oci::linked_erofs_images; | |
| use rustix::fs::AtFlags; | ||
| use rustix::fs::{statat, unlinkat}; | ||
|
|
||
| use crate::bootc_composefs::boot::GLOBAL_UKI_ADDONS_DIR; | ||
| use crate::bootc_composefs::boot::get_global_uki_addon_name; | ||
| use crate::bootc_composefs::uki_addon::UkiAddonType; | ||
| use crate::bootc_composefs::uki_addon::list_installed_uki_addons; | ||
| use crate::bootc_composefs::uki_addon::list_referenced_uki_addons; | ||
| use crate::{ | ||
| bootc_composefs::{ | ||
| boot::{BOOTC_UKI_DIR, BootType, get_type1_dir_name, get_uki_addon_dir_name, get_uki_name}, | ||
|
|
@@ -157,9 +162,6 @@ fn delete_kernel_initrd(storage: &Storage, dir_to_delete: &str, dry_run: bool) - | |
| fn delete_uki(storage: &Storage, uki_id: &str, dry_run: bool) -> Result<()> { | ||
| let esp_mnt = storage.require_esp()?; | ||
|
|
||
| // NOTE: We don't delete global addons here (see `GLOBAL_UKI_ADDONS_DIR`) | ||
| // Which is fine as global addons don't belong to any single deployment, but it also | ||
| // means they're never cleaned up at all: see the TODO on `GLOBAL_UKI_ADDONS_DIR`. | ||
| let uki_dir = esp_mnt.fd.open_dir(BOOTC_UKI_DIR)?; | ||
|
|
||
| for entry in uki_dir.entries_utf8()? { | ||
|
|
@@ -212,6 +214,82 @@ fn unreferenced_boot_binaries<'a>( | |
| .collect() | ||
| } | ||
|
|
||
| #[fn_error_context::context("GC Global UKI Addons")] | ||
| fn gc_global_uki_addons( | ||
| storage: &Storage, | ||
| booted_cfs: &BootedComposefs, | ||
| gc_opts: &GCOpts, | ||
| bootloader_entries: &Vec<BootloaderEntry>, | ||
| ) -> Result<()> { | ||
| // GC always runs *after* finalizing deployments so there would be no staged deployment | ||
| // by definition but still only consider live addons for GC | ||
| let currently_installed_addons = list_installed_uki_addons(storage, &None)? | ||
| .into_iter() | ||
| .filter(|addon| !addon.staged) | ||
| .collect::<Vec<_>>(); | ||
|
|
||
| if !currently_installed_addons | ||
| .iter() | ||
| .any(|addon| addon.addon_type == UkiAddonType::Global) | ||
| { | ||
| tracing::debug!("No bootc managed Global UKI Addon installed"); | ||
| return Ok(()); | ||
| } | ||
|
|
||
| // Remove unreferenced global UKI Addons | ||
| // GC always runs *after* finalizing deployments so there would be no staged deployment | ||
| // by definition | ||
| let currently_referenced_addons = | ||
| list_referenced_uki_addons(booted_cfs, &bootloader_entries, &None)?; | ||
| let mut unreferenced_global_addons = vec![]; | ||
|
|
||
| tracing::debug!("currently_referenced_addons: {currently_referenced_addons:#?}"); | ||
| tracing::debug!("currently_installed_addons: {currently_installed_addons:#?}"); | ||
|
|
||
| for installed_addon in ¤tly_installed_addons { | ||
| // We handle scoped UKI Addons along with the UKI itself | ||
| if installed_addon.addon_type != UkiAddonType::Global { | ||
| continue; | ||
| } | ||
|
|
||
| let is_referenced = currently_referenced_addons.iter().any(|(_, refs)| { | ||
| refs.iter().any(|r| { | ||
| r.addon_type == installed_addon.addon_type && r.name == installed_addon.name | ||
| }) | ||
| }); | ||
|
|
||
| if !is_referenced { | ||
| unreferenced_global_addons.push(installed_addon.name.clone()); | ||
| } | ||
| } | ||
|
|
||
| tracing::debug!("Unreferenced Global Addons: {unreferenced_global_addons:?}"); | ||
|
|
||
| if !unreferenced_global_addons.is_empty() { | ||
| let global_uki_dir = storage | ||
| .require_esp()? | ||
| .fd | ||
| .open_dir(GLOBAL_UKI_ADDONS_DIR) | ||
| .context("Opening global UKI Addons dir")?; | ||
|
|
||
| for addon in &unreferenced_global_addons { | ||
| let global_addon_name = get_global_uki_addon_name(&addon); | ||
|
|
||
| tracing::debug!("Deleting Global UKI Addon: {}", addon); | ||
|
|
||
| if gc_opts.dry_run { | ||
| continue; | ||
| } | ||
|
|
||
| global_uki_dir | ||
| .remove_file(&global_addon_name) | ||
| .with_context(|| format!("Removing global addon {global_addon_name}"))?; | ||
| } | ||
| } | ||
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| pub(crate) struct GCOpts { | ||
| pub(crate) dry_run: bool, | ||
| pub(crate) prune_repo: bool, | ||
|
|
@@ -333,7 +411,7 @@ pub(crate) async fn composefs_gc( | |
| ) | ||
| } | ||
|
|
||
| for (ty, verity) in unreferenced_boot_binaries { | ||
| for (ty, verity) in &unreferenced_boot_binaries { | ||
| match ty { | ||
| BootType::Bls => { | ||
| delete_kernel_initrd(storage, &get_type1_dir_name(verity), gc_opts.dry_run)? | ||
|
|
@@ -345,6 +423,10 @@ pub(crate) async fn composefs_gc( | |
| } | ||
| } | ||
|
|
||
| if booted_cfs_status.boot_type == BootType::Uki { | ||
| gc_global_uki_addons(storage, booted_cfs, &gc_opts, &bootloader_entries)?; | ||
| } | ||
|
|
||
| if !gc_opts.prune_repo { | ||
| return Ok(GcResult::default()); | ||
| } | ||
|
|
@@ -624,14 +706,21 @@ pub(crate) async fn composefs_gc( | |
| } | ||
|
|
||
| // Get file size before removing | ||
| if let Ok(stat) = statat(&objects_dir, path.as_ref(), AtFlags::empty()) { | ||
| objects_bytes += stat.st_size as u64; | ||
| match statat(&objects_dir, path.as_ref(), AtFlags::empty()) { | ||
| Ok(stat) => { | ||
| objects_bytes += stat.st_size as u64; | ||
| } | ||
| Err(e) if e.kind() == std::io::ErrorKind::NotFound => { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This loop unlinks every object in an orphan's Reproduced: A, B and C ship identical |
||
| tracing::debug!("Object {} not found... skipping", path.display()); | ||
| continue; | ||
| } | ||
| Err(e) => Err(e).with_context(|| format!("statat {} failed", path.display()))?, | ||
| } | ||
|
|
||
| objects_removed += 1; | ||
|
|
||
| unlinkat(&objects_dir, path.as_ref(), AtFlags::empty()) | ||
| .with_context(|| format!("Unlinking object {path:?}"))?; | ||
| .with_context(|| format!("Unlinking (from bootc) object {path:?}"))?; | ||
| } | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On classic GRUB,
boot_diris/boot, not the ESP, but staging anduki-addon addwriteloader/addons.stagedon the ESP. So on GRUB + UKI a staged global addon is never applied.Reproduced with an image built with
BOOTC_bootloader=grub BOOTC_boot_type=uki:switch --global-uki-addon foo, reboot.fooisn't on the cmdline,addons.stagedis still on the ESP, anduki-addon list --verbosekeeps showing it as staged. Usingstorage.require_esp()?.fdhere should fix it. The GRUB branch in rollback.rs also needs theaddons.stagedcleanup that the BLS branch got.