diff --git a/crates/lib/src/bootc_composefs/delete.rs b/crates/lib/src/bootc_composefs/delete.rs index f92183de3..da4b61ecb 100644 --- a/crates/lib/src/bootc_composefs/delete.rs +++ b/crates/lib/src/bootc_composefs/delete.rs @@ -6,7 +6,7 @@ use cap_std_ext::{cap_std::fs::Dir, dirext::CapStdExtDirExt}; use crate::{ bootc_composefs::{ boot::{BootType, get_efi_uuid_source}, - gc::composefs_gc, + gc::{GCOpts, composefs_gc}, rollback::{composefs_rollback, rename_exchange_user_cfg}, status::{get_composefs_status, get_sorted_grub_uki_boot_entries}, }, @@ -261,7 +261,15 @@ pub(crate) async fn delete_composefs_deployment( delete_depl_boot_entries(&depl_to_del, &storage, deleting_staged)?; - composefs_gc(storage, booted_cfs, false, true).await?; + composefs_gc( + storage, + booted_cfs, + GCOpts { + dry_run: false, + prune_repo: true, + }, + ) + .await?; Ok(()) } diff --git a/crates/lib/src/bootc_composefs/finalize.rs b/crates/lib/src/bootc_composefs/finalize.rs index de7f2dc73..e1d67d99f 100644 --- a/crates/lib/src/bootc_composefs/finalize.rs +++ b/crates/lib/src/bootc_composefs/finalize.rs @@ -1,7 +1,7 @@ use std::path::Path; use crate::bootc_composefs::boot::BootType; -use crate::bootc_composefs::gc::composefs_gc; +use crate::bootc_composefs::gc::{GCOpts, composefs_gc}; use crate::bootc_composefs::rollback::{rename_exchange_bls_entries, rename_exchange_user_cfg}; use crate::bootc_composefs::status::get_composefs_status; use crate::composefs_consts::STATE_DIR_ABS; @@ -150,7 +150,15 @@ pub(crate) async fn composefs_backend_finalize( // Now that we have successfully updated bootloader entires, we can GC the unreferenced ones // We do not prune the composefs repository here though - composefs_gc(storage, booted_cfs, false, false).await?; + composefs_gc( + storage, + booted_cfs, + GCOpts { + dry_run: false, + prune_repo: false, + }, + ) + .await?; Ok(()) } diff --git a/crates/lib/src/bootc_composefs/gc.rs b/crates/lib/src/bootc_composefs/gc.rs index 828f66278..d8bafd467 100644 --- a/crates/lib/src/bootc_composefs/gc.rs +++ b/crates/lib/src/bootc_composefs/gc.rs @@ -192,6 +192,11 @@ fn unreferenced_boot_binaries<'a>( .collect() } +pub(crate) struct GCOpts { + pub(crate) dry_run: bool, + pub(crate) prune_repo: bool, +} + /// 1. List all bootloader entries /// 2. List all EROFS images /// 3. List all state directories @@ -212,8 +217,7 @@ fn unreferenced_boot_binaries<'a>( pub(crate) async fn composefs_gc( storage: &Storage, booted_cfs: &BootedComposefs, - dry_run: bool, - prune_repo: bool, + gc_opts: GCOpts, ) -> Result { const COMPOSEFS_GC_JOURNAL_ID: &str = "3b2a1f0e9d8c7b6a5f4e3d2c1b0a9f8e7"; @@ -284,12 +288,14 @@ pub(crate) async fn composefs_gc( for (ty, verity) in unreferenced_boot_binaries { match ty { - BootType::Bls => delete_kernel_initrd(storage, &get_type1_dir_name(verity), dry_run)?, - BootType::Uki => delete_uki(storage, verity, dry_run)?, + BootType::Bls => { + delete_kernel_initrd(storage, &get_type1_dir_name(verity), gc_opts.dry_run)? + } + BootType::Uki => delete_uki(storage, verity, gc_opts.dry_run)?, } } - if !prune_repo { + if !gc_opts.prune_repo { return Ok(GcResult::default()); } @@ -329,13 +335,13 @@ pub(crate) async fn composefs_gc( for verity in &orphaned_state_dirs { tracing::debug!("Cleaning up orphaned state dir: {verity}"); - delete_staged(staged, &all_orphans, dry_run)?; - delete_state_dir(&sysroot, verity, dry_run)?; + delete_staged(staged, &all_orphans, gc_opts.dry_run)?; + delete_state_dir(&sysroot, verity, gc_opts.dry_run)?; } for verity in &orphaned_boot_entries { tracing::debug!("Cleaning up orphaned bootloader entry: {verity}"); - delete_staged(staged, &all_orphans, dry_run)?; + delete_staged(staged, &all_orphans, gc_opts.dry_run)?; } // Collect the set of manifest digests referenced by live deployments, @@ -431,7 +437,7 @@ pub(crate) async fn composefs_gc( .any(|(tag_name, _)| tag_name == &expected_tag); if !has_tag { tracing::info!("Creating missing bootc tag for live deployment: {expected_tag}"); - if !dry_run { + if !gc_opts.dry_run { composefs_oci::tag_image(&*booted_cfs.repo, manifest_digest, &expected_tag) .with_context(|| format!("Creating migration tag {expected_tag}"))?; } @@ -450,7 +456,7 @@ pub(crate) async fn composefs_gc( if !live_manifest_digests.iter().any(|d| d == manifest_digest) { tracing::debug!("Removing unreferenced bootc tag: {tag_name}"); - if !dry_run { + if !gc_opts.dry_run { composefs_oci::untag_image(&*booted_cfs.repo, tag_name) .with_context(|| format!("Removing tag {tag_name}"))?; } @@ -463,7 +469,7 @@ pub(crate) async fn composefs_gc( .collect::>(); // Prune containers-storage: remove images not backing any live deployment. - if !dry_run && !live_container_images.is_empty() { + if !gc_opts.dry_run && !live_container_images.is_empty() { let subpath = crate::podstorage::CStorage::subpath(); if sysroot.try_exists(&subpath).unwrap_or(false) { let run = Dir::open_ambient_dir("/run", cap_std_ext::cap_std::ambient_authority())?; @@ -482,7 +488,7 @@ pub(crate) async fn composefs_gc( // images for deployments that predate the manifest→image link; // once all deployments have been pulled with the new code, these // become redundant. - let gc_result = if dry_run { + let gc_result = if gc_opts.dry_run { booted_cfs.repo.gc_dry_run(&additional_roots)? } else { booted_cfs.repo.gc(&additional_roots)? diff --git a/crates/lib/src/bootc_composefs/status.rs b/crates/lib/src/bootc_composefs/status.rs index bd8b7a19c..2706b2216 100644 --- a/crates/lib/src/bootc_composefs/status.rs +++ b/crates/lib/src/bootc_composefs/status.rs @@ -405,22 +405,30 @@ pub(crate) async fn get_container_manifest_and_config( #[context("Getting bootloader")] pub(crate) fn get_bootloader() -> Result { - match read_uefi_var(EFI_LOADER_INFO) { + static BOOTLOADER: OnceLock = OnceLock::new(); + + if let Some(bootloader) = BOOTLOADER.get() { + return Ok(*bootloader); + } + + let bootloader = match read_uefi_var(EFI_LOADER_INFO) { Ok(loader) => { if loader.to_lowercase().contains("systemd-boot") { - return Ok(Bootloader::Systemd); + Bootloader::Systemd + } else { + Bootloader::Grub } - - return Ok(Bootloader::Grub); } Err(efi_error) => match efi_error { - EfiError::SystemNotUEFI => return Ok(Bootloader::Grub), - EfiError::MissingVar => return Ok(Bootloader::Grub), - - e => return Err(anyhow::anyhow!("Failed to read EfiLoaderInfo: {e:?}")), + EfiError::SystemNotUEFI | EfiError::MissingVar => Bootloader::Grub, + e => anyhow::bail!("Failed to read EfiLoaderInfo: {e:?}"), }, - } + }; + + BOOTLOADER.get_or_init(|| bootloader); + + return Ok(bootloader); } /// Retrieves the OCI manifest and config for a deployment from the composefs repository. diff --git a/crates/lib/src/bootc_composefs/update.rs b/crates/lib/src/bootc_composefs/update.rs index e88d1f3e9..a1fb2722f 100644 --- a/crates/lib/src/bootc_composefs/update.rs +++ b/crates/lib/src/bootc_composefs/update.rs @@ -11,6 +11,7 @@ use fn_error_context::context; use ocidir::cap_std::ambient_authority; use ostree_ext::container::ManifestDiff; +use crate::bootc_composefs::gc::GCOpts; use crate::{ bootc_composefs::{ boot::{BootSetupType, BootType, setup_composefs_bls_boot, setup_composefs_uki_boot}, @@ -324,7 +325,15 @@ pub(crate) async fn do_upgrade( // We take into account the staged bootloader entries so this won't remove // the currently staged entry - composefs_gc(storage, booted_cfs, false, true).await?; + composefs_gc( + storage, + booted_cfs, + GCOpts { + dry_run: false, + prune_repo: true, + }, + ) + .await?; apply_upgrade(storage, booted_cfs, &id.to_hex(), opts).await } diff --git a/crates/lib/src/cli.rs b/crates/lib/src/cli.rs index 0be914c46..dd80fd6ad 100644 --- a/crates/lib/src/cli.rs +++ b/crates/lib/src/cli.rs @@ -38,7 +38,7 @@ use schemars::schema_for; use serde::{Deserialize, Serialize}; use crate::bootc_composefs::delete::delete_composefs_deployment; -use crate::bootc_composefs::gc::composefs_gc; +use crate::bootc_composefs::gc::{GCOpts, composefs_gc}; use crate::bootc_composefs::soft_reboot::{prepare_soft_reboot_composefs, reset_soft_reboot}; use crate::bootc_composefs::{ digest::{compute_composefs_digest, new_temp_composefs_repo}, @@ -2231,12 +2231,18 @@ async fn run_from_opt(opt: Opt) -> Result<()> { } BootedStorageKind::Composefs(booted_cfs) => { - let effective_dry_run = dry_run || assert_no_op; - let gc_result = - composefs_gc(storage, &booted_cfs, effective_dry_run, prune_repo) - .await?; + let dry_run = dry_run || assert_no_op; + let gc_result = composefs_gc( + storage, + &booted_cfs, + GCOpts { + dry_run, + prune_repo, + }, + ) + .await?; - if effective_dry_run { + if dry_run { println!("Dry run (no files deleted)"); } diff --git a/crates/lib/src/spec.rs b/crates/lib/src/spec.rs index 4cc3e234e..ea2c5a77c 100644 --- a/crates/lib/src/spec.rs +++ b/crates/lib/src/spec.rs @@ -230,7 +230,7 @@ pub struct BootEntryOstree { /// Bootloader type to determine whether system was booted via Grub or Systemd #[derive( - clap::ValueEnum, Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema, + clap::ValueEnum, Debug, Default, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema, )] #[serde(rename_all = "kebab-case")] pub enum Bootloader {