From ef52c2cc6c5f7822cf65e657b22a20c888343890 Mon Sep 17 00:00:00 2001 From: Pragyan Poudyal Date: Wed, 19 Nov 2025 14:42:32 +0530 Subject: [PATCH 1/3] composefs/bls: Get cmdline from usr/lib/bootc/kargs.d Parse toml files in usr/lib/bootc/kargs.d and append them to kernel cmdline on install and upgrade/switch. Also, copy over current deployment's cmdline args on upgrade/switch to another deployment Signed-off-by: Pragyan Poudyal --- crates/lib/src/bootc_composefs/boot.rs | 47 ++++++++++++++++++++++-- crates/lib/src/bootc_composefs/switch.rs | 9 +++++ crates/lib/src/bootc_composefs/update.rs | 13 ++++++- crates/lib/src/bootc_kargs.rs | 10 +++++ 4 files changed, 74 insertions(+), 5 deletions(-) diff --git a/crates/lib/src/bootc_composefs/boot.rs b/crates/lib/src/bootc_composefs/boot.rs index efd9e18c8..c460758d4 100644 --- a/crates/lib/src/bootc_composefs/boot.rs +++ b/crates/lib/src/bootc_composefs/boot.rs @@ -5,7 +5,7 @@ use std::path::Path; use anyhow::{anyhow, Context, Result}; use bootc_blockdev::find_parent_devices; -use bootc_kernel_cmdline::utf8::Cmdline; +use bootc_kernel_cmdline::utf8::{Cmdline, Parameter}; use bootc_mount::inspect_filesystem_of_dir; use bootc_mount::tempmount::TempMount; use camino::{Utf8Path, Utf8PathBuf}; @@ -33,6 +33,7 @@ use rustix::{mount::MountFlags, path::Arg}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use crate::bootc_kargs::kargs_from_composefs_filesystem; use crate::composefs_consts::{TYPE1_ENT_PATH, TYPE1_ENT_PATH_STAGED}; use crate::parsers::bls_config::{BLSConfig, BLSConfigType}; use crate::parsers::grub_menuconfig::MenuEntry; @@ -51,7 +52,6 @@ use crate::{ BOOT_LOADER_ENTRIES, COMPOSEFS_CMDLINE, ORIGIN_KEY_BOOT, ORIGIN_KEY_BOOT_DIGEST, STAGED_BOOT_LOADER_ENTRIES, STATE_DIR_ABS, USER_CFG, USER_CFG_STAGED, }, - install::RW_KARG, spec::{Bootloader, Host}, }; @@ -381,10 +381,11 @@ pub(crate) fn setup_composefs_bls_boot( repo: crate::store::ComposefsRepository, id: &Sha512HashValue, entry: &ComposefsBootEntry, + mounted_erofs: &Dir ) -> Result { let id_hex = id.to_hex(); - let (root_path, esp_device, cmdline_refs, fs, bootloader) = match setup_type { + let (root_path, esp_device, mut cmdline_refs, fs, bootloader) = match setup_type { BootSetupType::Setup((root_setup, state, postfetch, fs)) => { // root_setup.kargs has [root=UUID=, "rw"] let mut cmdline_options = Cmdline::new(); @@ -415,16 +416,53 @@ pub(crate) fn setup_composefs_bls_boot( let sysroot_parent = get_sysroot_parent_dev(&storage.physical_root)?; let bootloader = host.require_composefs_booted()?.bootloader.clone(); + let current_cfg = match bootloader { + Bootloader::Grub => { + let boot_dir = storage + .physical_root + .open_dir("boot") + .context("Opening boot")?; + + get_booted_bls(&boot_dir)? + } + + Bootloader::Systemd => { + let esp = get_esp_partition(&sysroot_parent)?.0; + let esp_mnt = mount_esp(&esp)?; + + get_booted_bls(&esp_mnt.fd)? + } + }; + + let mut cmdline = match current_cfg.cfg_type { + BLSConfigType::NonEFI { options, .. } => { + let options = options + .ok_or_else(|| anyhow::anyhow!("No 'options' found in BLS Config"))?; + + Cmdline::from(options) + } + + _ => anyhow::bail!("Found NonEFI config"), + }; + + // Copy all cmdline args, replacing only `composefs=` + let param = format!("{COMPOSEFS_CMDLINE}={id_hex}"); + let param = + Parameter::parse(¶m).context("Failed to create 'composefs=' parameter")?; + cmdline.add_or_modify(¶m); + ( Utf8PathBuf::from("/sysroot"), get_esp_partition(&sysroot_parent)?.0, - Cmdline::from(format!("{RW_KARG} {COMPOSEFS_CMDLINE}={id_hex}")), + cmdline, fs, bootloader, ) } }; + kargs_from_composefs_filesystem(mounted_erofs, &mut cmdline_refs)?; + let is_upgrade = matches!(setup_type, BootSetupType::Upgrade(..)); let (entry_paths, _tmpdir_guard) = match bootloader { @@ -1053,6 +1091,7 @@ pub(crate) fn setup_composefs_boot( repo, &id, entry, + &mounted_fs )?; boot_digest = Some(digest); diff --git a/crates/lib/src/bootc_composefs/switch.rs b/crates/lib/src/bootc_composefs/switch.rs index 5cb1e2c1f..28ca67c48 100644 --- a/crates/lib/src/bootc_composefs/switch.rs +++ b/crates/lib/src/bootc_composefs/switch.rs @@ -1,5 +1,7 @@ use anyhow::{Context, Result}; use camino::Utf8PathBuf; +use cap_std_ext::cap_std::fs::Dir; +use composefs::fsverity::FsVerityHashValue; use fn_error_context::context; use crate::{ @@ -54,6 +56,12 @@ pub(crate) async fn switch_composefs( let boot_type = BootType::from(entry); let mut boot_digest = None; + let mounted_fs = Dir::reopen_dir( + &repo + .mount(&id.to_hex()) + .context("Failed to mount composefs image")?, + )?; + match boot_type { BootType::Bls => { boot_digest = Some(setup_composefs_bls_boot( @@ -61,6 +69,7 @@ pub(crate) async fn switch_composefs( repo, &id, entry, + &mounted_fs, )?) } BootType::Uki => setup_composefs_uki_boot( diff --git a/crates/lib/src/bootc_composefs/update.rs b/crates/lib/src/bootc_composefs/update.rs index eebfd3faa..aa2d70ac8 100644 --- a/crates/lib/src/bootc_composefs/update.rs +++ b/crates/lib/src/bootc_composefs/update.rs @@ -1,6 +1,10 @@ use anyhow::{Context, Result}; use camino::Utf8PathBuf; -use composefs::util::{parse_sha256, Sha256Digest}; +use cap_std_ext::cap_std::fs::Dir; +use composefs::{ + fsverity::FsVerityHashValue, + util::{parse_sha256, Sha256Digest}, +}; use fn_error_context::context; use ostree_ext::oci_spec::image::{ImageConfiguration, ImageManifest}; @@ -154,6 +158,12 @@ pub(crate) async fn upgrade_composefs( anyhow::bail!("No boot entries!"); }; + let mounted_fs = Dir::reopen_dir( + &repo + .mount(&id.to_hex()) + .context("Failed to mount composefs image")?, + )?; + let boot_type = BootType::from(entry); let mut boot_digest = None; @@ -164,6 +174,7 @@ pub(crate) async fn upgrade_composefs( repo, &id, entry, + &mounted_fs, )?) } diff --git a/crates/lib/src/bootc_kargs.rs b/crates/lib/src/bootc_kargs.rs index af709f1af..50540d6f5 100644 --- a/crates/lib/src/bootc_kargs.rs +++ b/crates/lib/src/bootc_kargs.rs @@ -45,6 +45,16 @@ impl Config { } } +/// Looks for files in usr/lib/bootc/kargs.d and parses cmdline agruments +pub(crate) fn kargs_from_composefs_filesystem( + new_fs: &Dir, + cmdline: &mut Cmdline, +) -> Result<()> { + let remote_kargs = get_kargs_in_root(new_fs, std::env::consts::ARCH)?; + cmdline.extend(&remote_kargs); + Ok(()) +} + /// Load and parse all bootc kargs.d files in the specified root, returning /// a combined list. pub(crate) fn get_kargs_in_root(d: &Dir, sys_arch: &str) -> Result { From 2f51c8100342ad54ba3889643915f1f03b708470 Mon Sep 17 00:00:00 2001 From: Pragyan Poudyal Date: Wed, 19 Nov 2025 16:38:34 +0530 Subject: [PATCH 2/3] storage: Add `boot_dir` and `esp` fields We have a lot of places where we mount the ESP temporarily and a lot of switch cases for Grub's vs SystemdBoot's 'boot' directory. We add a `boot_dir` field in Storage which points to `/sysroot/boot` for systems with Grub as the bootloader and points to the ESP for systems with SystemdBoot as the bootloader. Also we mount the ESP temporarily while creating the storage struct, which cleans up the code quite a bit. Signed-off-by: Pragyan Poudyal --- crates/lib/src/bootc_composefs/boot.rs | 19 +-------- crates/lib/src/bootc_composefs/delete.rs | 49 +++++++++------------- crates/lib/src/bootc_composefs/finalize.rs | 35 ++++++---------- crates/lib/src/bootc_composefs/gc.rs | 18 +++----- crates/lib/src/bootc_composefs/rollback.rs | 34 +++++---------- crates/lib/src/bootc_composefs/status.rs | 38 +++++------------ crates/lib/src/store/mod.rs | 40 +++++++++++++++++- 7 files changed, 101 insertions(+), 132 deletions(-) diff --git a/crates/lib/src/bootc_composefs/boot.rs b/crates/lib/src/bootc_composefs/boot.rs index c460758d4..705b4626d 100644 --- a/crates/lib/src/bootc_composefs/boot.rs +++ b/crates/lib/src/bootc_composefs/boot.rs @@ -416,23 +416,8 @@ pub(crate) fn setup_composefs_bls_boot( let sysroot_parent = get_sysroot_parent_dev(&storage.physical_root)?; let bootloader = host.require_composefs_booted()?.bootloader.clone(); - let current_cfg = match bootloader { - Bootloader::Grub => { - let boot_dir = storage - .physical_root - .open_dir("boot") - .context("Opening boot")?; - - get_booted_bls(&boot_dir)? - } - - Bootloader::Systemd => { - let esp = get_esp_partition(&sysroot_parent)?.0; - let esp_mnt = mount_esp(&esp)?; - - get_booted_bls(&esp_mnt.fd)? - } - }; + let boot_dir = storage.require_boot_dir()?; + let current_cfg = get_booted_bls(&boot_dir)?; let mut cmdline = match current_cfg.cfg_type { BLSConfigType::NonEFI { options, .. } => { diff --git a/crates/lib/src/bootc_composefs/delete.rs b/crates/lib/src/bootc_composefs/delete.rs index b16930e83..f1a31101c 100644 --- a/crates/lib/src/bootc_composefs/delete.rs +++ b/crates/lib/src/bootc_composefs/delete.rs @@ -7,10 +7,7 @@ use composefs_boot::bootloader::{EFI_ADDON_DIR_EXT, EFI_EXT}; use crate::{ bootc_composefs::{ - boot::{ - find_vmlinuz_initrd_duplicates, get_efi_uuid_source, get_esp_partition, - get_sysroot_parent_dev, mount_esp, BootType, SYSTEMD_UKI_DIR, - }, + boot::{find_vmlinuz_initrd_duplicates, get_efi_uuid_source, BootType, SYSTEMD_UKI_DIR}, gc::composefs_gc, repo::open_composefs_repo, rollback::{composefs_rollback, rename_exchange_user_cfg}, @@ -215,40 +212,34 @@ fn remove_grub_menucfg_entry(id: &str, boot_dir: &Dir, deleting_staged: bool) -> #[fn_error_context::context("Deleting boot entries for deployment {}", deployment.deployment.verity)] fn delete_depl_boot_entries( deployment: &DeploymentEntry, - physical_root: &Dir, + storage: &Storage, deleting_staged: bool, ) -> Result<()> { - match deployment.deployment.bootloader { - Bootloader::Grub => { - let boot_dir = physical_root.open_dir("boot").context("Opening boot dir")?; + let boot_dir = storage.require_boot_dir()?; - match deployment.deployment.boot_type { - BootType::Bls => delete_type1_entry(deployment, &boot_dir, deleting_staged), + match deployment.deployment.bootloader { + Bootloader::Grub => match deployment.deployment.boot_type { + BootType::Bls => delete_type1_entry(deployment, boot_dir, deleting_staged), - BootType::Uki => { - let device = get_sysroot_parent_dev(physical_root)?; - let (esp_part, ..) = get_esp_partition(&device)?; - let esp_mount = mount_esp(&esp_part)?; + BootType::Uki => { + let esp = storage + .esp + .as_ref() + .ok_or_else(|| anyhow::anyhow!("ESP not found"))?; - remove_grub_menucfg_entry( - &deployment.deployment.verity, - &boot_dir, - deleting_staged, - )?; + remove_grub_menucfg_entry( + &deployment.deployment.verity, + boot_dir, + deleting_staged, + )?; - delete_uki(&deployment.deployment.verity, &esp_mount.fd) - } + delete_uki(&deployment.deployment.verity, &esp.fd) } - } + }, Bootloader::Systemd => { - let device = get_sysroot_parent_dev(physical_root)?; - let (esp_part, ..) = get_esp_partition(&device)?; - - let esp_mount = mount_esp(&esp_part)?; - // For Systemd UKI as well, we use .conf files - delete_type1_entry(deployment, &esp_mount.fd, deleting_staged) + delete_type1_entry(deployment, boot_dir, deleting_staged) } } } @@ -362,7 +353,7 @@ pub(crate) async fn delete_composefs_deployment( tracing::info!("Deleting {kind}deployment '{deployment_id}'"); - delete_depl_boot_entries(&depl_to_del, &storage.physical_root, deleting_staged)?; + delete_depl_boot_entries(&depl_to_del, &storage, deleting_staged)?; composefs_gc(storage, booted_cfs).await?; diff --git a/crates/lib/src/bootc_composefs/finalize.rs b/crates/lib/src/bootc_composefs/finalize.rs index d397c9f5c..027ffb5ee 100644 --- a/crates/lib/src/bootc_composefs/finalize.rs +++ b/crates/lib/src/bootc_composefs/finalize.rs @@ -1,8 +1,6 @@ use std::path::Path; -use crate::bootc_composefs::boot::{ - get_esp_partition, get_sysroot_parent_dev, mount_esp, BootType, -}; +use crate::bootc_composefs::boot::BootType; 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; @@ -86,15 +84,12 @@ pub(crate) async fn composefs_backend_finalize( // Unmount EROFS drop(erofs_tmp_mnt); - let sysroot_parent = get_sysroot_parent_dev(&storage.physical_root)?; - // NOTE: Assumption here that ESP will always be present - let (esp_part, ..) = get_esp_partition(&sysroot_parent)?; + let boot_dir = storage.require_boot_dir()?; - let esp_mount = mount_esp(&esp_part)?; - let boot_dir = storage - .physical_root - .open_dir("boot") - .context("Opening boot")?; + let esp_mount = storage + .esp + .as_ref() + .ok_or_else(|| anyhow::anyhow!("ESP not found"))?; // NOTE: Assuming here we won't have two bootloaders at the same time match booted_composefs.bootloader { @@ -103,21 +98,17 @@ pub(crate) async fn composefs_backend_finalize( let entries_dir = boot_dir.open_dir("loader")?; rename_exchange_bls_entries(&entries_dir)?; } - BootType::Uki => finalize_staged_grub_uki(&esp_mount.fd, &boot_dir)?, + BootType::Uki => finalize_staged_grub_uki(&esp_mount.fd, boot_dir)?, }, - Bootloader::Systemd => match staged_composefs.boot_type { - BootType::Bls => { - let entries_dir = esp_mount.fd.open_dir("loader")?; - rename_exchange_bls_entries(&entries_dir)?; - } - BootType::Uki => { + Bootloader::Systemd => { + if matches!(staged_composefs.boot_type, BootType::Uki) { rename_staged_uki_entries(&esp_mount.fd)?; - - let entries_dir = esp_mount.fd.open_dir("loader")?; - rename_exchange_bls_entries(&entries_dir)?; } - }, + + let entries_dir = boot_dir.open_dir("loader")?; + rename_exchange_bls_entries(&entries_dir)?; + } }; Ok(()) diff --git a/crates/lib/src/bootc_composefs/gc.rs b/crates/lib/src/bootc_composefs/gc.rs index 8195cade2..7926d250c 100644 --- a/crates/lib/src/bootc_composefs/gc.rs +++ b/crates/lib/src/bootc_composefs/gc.rs @@ -10,7 +10,6 @@ use composefs::fsverity::{FsVerityHashValue, Sha512HashValue}; use crate::{ bootc_composefs::{ - boot::{get_esp_partition, get_sysroot_parent_dev, mount_esp}, delete::{delete_image, delete_staged, delete_state_dir, get_image_objects}, status::{ get_bootloader, get_composefs_status, get_sorted_grub_uki_boot_entries, @@ -44,20 +43,19 @@ fn list_erofs_images(sysroot: &Dir) -> Result> { /// # Returns /// The fsverity of EROFS images corresponding to boot entries #[fn_error_context::context("Listing bootloader entries")] -fn list_bootloader_entries(physical_root: &Dir) -> Result> { +fn list_bootloader_entries(storage: &Storage) -> Result> { let bootloader = get_bootloader()?; + let boot_dir = storage.require_boot_dir()?; let entries = match bootloader { Bootloader::Grub => { - let boot_dir = physical_root.open_dir("boot").context("Opening boot dir")?; - // Grub entries are always in boot let grub_dir = boot_dir.open_dir("grub2").context("Opening grub dir")?; if grub_dir.exists(USER_CFG) { // Grub UKI let mut s = String::new(); - let boot_entries = get_sorted_grub_uki_boot_entries(&boot_dir, &mut s)?; + let boot_entries = get_sorted_grub_uki_boot_entries(boot_dir, &mut s)?; boot_entries .into_iter() @@ -65,7 +63,7 @@ fn list_bootloader_entries(physical_root: &Dir) -> Result> { .collect::, _>>()? } else { // Type1 Entry - let boot_entries = get_sorted_type1_boot_entries(&boot_dir, true)?; + let boot_entries = get_sorted_type1_boot_entries(boot_dir, true)?; boot_entries .into_iter() @@ -75,11 +73,7 @@ fn list_bootloader_entries(physical_root: &Dir) -> Result> { } Bootloader::Systemd => { - let device = get_sysroot_parent_dev(physical_root)?; - let (esp_part, ..) = get_esp_partition(&device)?; - let esp_mount = mount_esp(&esp_part)?; - - let boot_entries = get_sorted_type1_boot_entries(&esp_mount.fd, true)?; + let boot_entries = get_sorted_type1_boot_entries(boot_dir, true)?; boot_entries .into_iter() @@ -175,7 +169,7 @@ pub(crate) async fn composefs_gc(storage: &Storage, booted_cfs: &BootedComposefs let sysroot = &storage.physical_root; - let bootloader_entries = list_bootloader_entries(&storage.physical_root)?; + let bootloader_entries = list_bootloader_entries(&storage)?; let images = list_erofs_images(&sysroot)?; // Collect the deployments that have an image but no bootloader entry diff --git a/crates/lib/src/bootc_composefs/rollback.rs b/crates/lib/src/bootc_composefs/rollback.rs index 6338bf9b5..5cfd60c39 100644 --- a/crates/lib/src/bootc_composefs/rollback.rs +++ b/crates/lib/src/bootc_composefs/rollback.rs @@ -6,9 +6,7 @@ use cap_std_ext::dirext::CapStdExtDirExt; use fn_error_context::context; use rustix::fs::{fsync, renameat_with, AtFlags, RenameFlags}; -use crate::bootc_composefs::boot::{ - get_esp_partition, get_sysroot_parent_dev, mount_esp, type1_entry_conf_file_name, BootType, -}; +use crate::bootc_composefs::boot::{type1_entry_conf_file_name, BootType}; use crate::bootc_composefs::status::{get_composefs_status, get_sorted_type1_boot_entries}; use crate::composefs_consts::TYPE1_ENT_PATH_STAGED; use crate::spec::Bootloader; @@ -196,31 +194,21 @@ pub(crate) async fn composefs_rollback( anyhow::bail!("Rollback deployment not a composefs deployment") }; + let boot_dir = storage.require_boot_dir()?; + match &rollback_entry.bootloader { - Bootloader::Grub => { - let boot_dir = storage - .physical_root - .open_dir("boot") - .context("Opening boot dir")?; - - match rollback_entry.boot_type { - BootType::Bls => { - rollback_composefs_entries(&boot_dir, rollback_entry.bootloader.clone())?; - } - - BootType::Uki => { - rollback_grub_uki_entries(&boot_dir)?; - } + Bootloader::Grub => match rollback_entry.boot_type { + BootType::Bls => { + rollback_composefs_entries(boot_dir, rollback_entry.bootloader.clone())?; } - } + BootType::Uki => { + rollback_grub_uki_entries(boot_dir)?; + } + }, Bootloader::Systemd => { - let parent = get_sysroot_parent_dev(&storage.physical_root)?; - let (esp_part, ..) = get_esp_partition(&parent)?; - let esp_mount = mount_esp(&esp_part)?; - // We use BLS entries for systemd UKI as well - rollback_composefs_entries(&esp_mount.fd, rollback_entry.bootloader.clone())?; + rollback_composefs_entries(boot_dir, rollback_entry.bootloader.clone())?; } } diff --git a/crates/lib/src/bootc_composefs/status.rs b/crates/lib/src/bootc_composefs/status.rs index 29ae212ba..40ec1b757 100644 --- a/crates/lib/src/bootc_composefs/status.rs +++ b/crates/lib/src/bootc_composefs/status.rs @@ -5,7 +5,7 @@ use bootc_kernel_cmdline::utf8::Cmdline; use fn_error_context::context; use crate::{ - bootc_composefs::boot::{get_esp_partition, get_sysroot_parent_dev, mount_esp, BootType}, + bootc_composefs::boot::BootType, composefs_consts::{COMPOSEFS_CMDLINE, ORIGIN_KEY_BOOT_DIGEST, TYPE1_ENT_PATH, USER_CFG}, install::EFI_LOADER_INFO, parsers::{ @@ -13,6 +13,7 @@ use crate::{ grub_menuconfig::{parse_grub_menuentry_file, MenuEntry}, }, spec::{BootEntry, BootOrder, Host, HostSpec, ImageReference, ImageStatus}, + store::Storage, utils::{read_uefi_var, EfiError}, }; @@ -254,17 +255,20 @@ pub(crate) async fn get_composefs_status( storage: &crate::store::Storage, booted_cfs: &crate::store::BootedComposefs, ) -> Result { - composefs_deployment_status_from(&storage.physical_root, booted_cfs.cmdline).await + composefs_deployment_status_from(&storage, booted_cfs.cmdline).await } #[context("Getting composefs deployment status")] pub(crate) async fn composefs_deployment_status_from( - sysroot: &Dir, + storage: &Storage, cmdline: &ComposefsCmdline, ) -> Result { let composefs_digest = &cmdline.digest; - let deployments = sysroot + let boot_dir = storage.require_boot_dir()?; + + let deployments = storage + .physical_root .read_dir(STATE_DIR_RELATIVE) .with_context(|| format!("Reading sysroot {STATE_DIR_RELATIVE}"))?; @@ -348,30 +352,10 @@ pub(crate) async fn composefs_deployment_status_from( let booted = host.require_composefs_booted()?; - let (boot_dir, _temp_guard) = match booted.bootloader { - Bootloader::Grub => (sysroot.open_dir("boot").context("Opening boot dir")?, None), - - // TODO: This is redundant as we should already have ESP mounted at `/efi/` accoding to - // spec; currently we do not - // - // See: https://uapi-group.org/specifications/specs/boot_loader_specification/#mount-points - Bootloader::Systemd => { - let parent = get_sysroot_parent_dev(sysroot)?; - let (esp_part, ..) = get_esp_partition(&parent)?; - - let esp_mount = mount_esp(&esp_part)?; - - let dir = esp_mount.fd.try_clone().context("Cloning fd")?; - let guard = Some(esp_mount); - - (dir, guard) - } - }; - let is_rollback_queued = match booted.bootloader { Bootloader::Grub => match boot_type { BootType::Bls => { - let bls_config = get_sorted_type1_boot_entries(&boot_dir, false)?; + let bls_config = get_sorted_type1_boot_entries(boot_dir, false)?; let bls_config = bls_config .first() .ok_or(anyhow::anyhow!("First boot entry not found"))?; @@ -392,7 +376,7 @@ pub(crate) async fn composefs_deployment_status_from( BootType::Uki => { let mut s = String::new(); - !get_sorted_grub_uki_boot_entries(&boot_dir, &mut s)? + !get_sorted_grub_uki_boot_entries(boot_dir, &mut s)? .first() .ok_or(anyhow::anyhow!("First boot entry not found"))? .body @@ -403,7 +387,7 @@ pub(crate) async fn composefs_deployment_status_from( // We will have BLS stuff and the UKI stuff in the same DIR Bootloader::Systemd => { - let bls_config = get_sorted_type1_boot_entries(&boot_dir, false)?; + let bls_config = get_sorted_type1_boot_entries(boot_dir, false)?; let bls_config = bls_config .first() .ok_or(anyhow::anyhow!("First boot entry not found"))?; diff --git a/crates/lib/src/store/mod.rs b/crates/lib/src/store/mod.rs index eb65e6eb9..4779093c5 100644 --- a/crates/lib/src/store/mod.rs +++ b/crates/lib/src/store/mod.rs @@ -21,6 +21,7 @@ use std::ops::Deref; use std::sync::Arc; use anyhow::{Context, Result}; +use bootc_mount::tempmount::TempMount; use cap_std_ext::cap_std; use cap_std_ext::cap_std::fs::{Dir, DirBuilder, DirBuilderExt as _}; use cap_std_ext::dirext::CapStdExtDirExt; @@ -31,10 +32,11 @@ use ostree_ext::sysroot::SysrootLock; use ostree_ext::{gio, ostree}; use rustix::fs::Mode; -use crate::bootc_composefs::status::{composefs_booted, ComposefsCmdline}; +use crate::bootc_composefs::boot::{get_esp_partition, get_sysroot_parent_dev, mount_esp}; +use crate::bootc_composefs::status::{composefs_booted, get_bootloader, ComposefsCmdline}; use crate::lsm; use crate::podstorage::CStorage; -use crate::spec::ImageStatus; +use crate::spec::{Bootloader, ImageStatus}; use crate::utils::{deployment_fd, open_dir_remount_rw}; /// See https://github.com/containers/composefs-rs/issues/159 @@ -169,9 +171,23 @@ impl BootedStorage { } let composefs = Arc::new(composefs); + // NOTE: This is assuming that we'll only have composefs in a UEFI system + // We do have this assumptions in a lot of other places + let parent = get_sysroot_parent_dev(&physical_root)?; + let (esp_part, ..) = get_esp_partition(&parent)?; + let esp_mount = mount_esp(&esp_part)?; + + let boot_dir = match get_bootloader()? { + Bootloader::Grub => physical_root.open_dir("boot").context("Opening boot")?, + // NOTE: Handle XBOOTLDR partitions here if and when we use it + Bootloader::Systemd => esp_mount.fd.try_clone().context("Cloning fd")?, + }; + let storage = Storage { physical_root, run, + boot_dir: Some(boot_dir), + esp: Some(esp_mount), ostree: Default::default(), composefs: OnceCell::from(composefs), imgstore: Default::default(), @@ -194,6 +210,8 @@ impl BootedStorage { let storage = Storage { physical_root, run, + boot_dir: None, + esp: None, ostree: OnceCell::from(sysroot), composefs: Default::default(), imgstore: Default::default(), @@ -235,6 +253,15 @@ impl BootedStorage { pub(crate) struct Storage { /// Directory holding the physical root pub physical_root: Dir, + + /// The 'boot' directory, useful and `Some` only for composefs systems + /// For grub booted systems, this points to `/sysroot/boot` + /// For systemd booted systems, this points to the ESP + pub boot_dir: Option, + + /// The ESP mounted at a tmp location + pub esp: Option, + /// Our runtime state run: Dir, @@ -286,12 +313,21 @@ impl Storage { Ok(Self { physical_root, run, + boot_dir: None, + esp: None, ostree: ostree_cell, composefs: Default::default(), imgstore: Default::default(), }) } + /// Returns `boot_dir` if it exists + pub(crate) fn require_boot_dir(&self) -> Result<&Dir> { + self.boot_dir + .as_ref() + .ok_or_else(|| anyhow::anyhow!("Boot dir not found")) + } + /// Access the underlying ostree repository pub(crate) fn get_ostree(&self) -> Result<&SysrootLock> { self.ostree From 2b995c3dd7f45b400c8b3cf4c88f21358c98dca0 Mon Sep 17 00:00:00 2001 From: Pragyan Poudyal Date: Thu, 20 Nov 2025 11:43:17 +0530 Subject: [PATCH 3/3] kargs: Handle addition/removal of kargs from kargs.d Signed-off-by: Pragyan Poudyal --- crates/lib/src/bootc_composefs/boot.rs | 14 ++++-- crates/lib/src/bootc_kargs.rs | 64 ++++++++++++++++---------- 2 files changed, 49 insertions(+), 29 deletions(-) diff --git a/crates/lib/src/bootc_composefs/boot.rs b/crates/lib/src/bootc_composefs/boot.rs index 705b4626d..1196c1311 100644 --- a/crates/lib/src/bootc_composefs/boot.rs +++ b/crates/lib/src/bootc_composefs/boot.rs @@ -381,7 +381,7 @@ pub(crate) fn setup_composefs_bls_boot( repo: crate::store::ComposefsRepository, id: &Sha512HashValue, entry: &ComposefsBootEntry, - mounted_erofs: &Dir + mounted_erofs: &Dir, ) -> Result { let id_hex = id.to_hex(); @@ -446,10 +446,16 @@ pub(crate) fn setup_composefs_bls_boot( } }; - kargs_from_composefs_filesystem(mounted_erofs, &mut cmdline_refs)?; - let is_upgrade = matches!(setup_type, BootSetupType::Upgrade(..)); + let current_root = if is_upgrade { + Some(&Dir::open_ambient_dir("/", ambient_authority()).context("Opening root")?) + } else { + None + }; + + kargs_from_composefs_filesystem(mounted_erofs, current_root, &mut cmdline_refs)?; + let (entry_paths, _tmpdir_guard) = match bootloader { Bootloader::Grub => { let root = Dir::open_ambient_dir(&root_path, ambient_authority()) @@ -1076,7 +1082,7 @@ pub(crate) fn setup_composefs_boot( repo, &id, entry, - &mounted_fs + &mounted_fs, )?; boot_digest = Some(digest); diff --git a/crates/lib/src/bootc_kargs.rs b/crates/lib/src/bootc_kargs.rs index 50540d6f5..8aaf991a4 100644 --- a/crates/lib/src/bootc_kargs.rs +++ b/crates/lib/src/bootc_kargs.rs @@ -45,13 +45,49 @@ impl Config { } } +/// Compute the diff between existing and remote kargs +/// Apply the diff to the new kargs +fn compute_apply_kargs_diff( + existing_kargs: &Cmdline, + remote_kargs: &Cmdline, + new_kargs: &mut Cmdline, +) { + // Calculate the diff between the existing and remote kargs + let added_kargs: Vec<_> = remote_kargs + .iter() + .filter(|item| !existing_kargs.iter().any(|existing| *item == existing)) + .collect(); + let removed_kargs: Vec<_> = existing_kargs + .iter() + .filter(|item| !remote_kargs.iter().any(|remote| *item == remote)) + .collect(); + + tracing::debug!("kargs: added={:?} removed={:?}", added_kargs, removed_kargs); + + // Apply the diff to the system kargs + for arg in &removed_kargs { + new_kargs.remove_exact(arg); + } + for arg in &added_kargs { + new_kargs.add(arg); + } +} + /// Looks for files in usr/lib/bootc/kargs.d and parses cmdline agruments pub(crate) fn kargs_from_composefs_filesystem( new_fs: &Dir, - cmdline: &mut Cmdline, + current_root: Option<&Dir>, + new_kargs: &mut Cmdline, ) -> Result<()> { let remote_kargs = get_kargs_in_root(new_fs, std::env::consts::ARCH)?; - cmdline.extend(&remote_kargs); + + let existing_kargs = match current_root { + Some(root) => get_kargs_in_root(root, std::env::consts::ARCH)?, + None => Cmdline::new(), + }; + + compute_apply_kargs_diff(&existing_kargs, &remote_kargs, new_kargs); + Ok(()) } @@ -185,29 +221,7 @@ pub(crate) fn get_kargs( // Fetch the kernel arguments from the new root let remote_kargs = get_kargs_from_ostree(repo, &fetched_tree, sys_arch)?; - // Calculate the diff between the existing and remote kargs - let added_kargs: Vec<_> = remote_kargs - .iter() - .filter(|item| !existing_kargs.iter().any(|existing| *item == existing)) - .collect(); - let removed_kargs: Vec<_> = existing_kargs - .iter() - .filter(|item| !remote_kargs.iter().any(|remote| *item == remote)) - .collect(); - - tracing::debug!( - "kargs: added={:?} removed={:?}", - &added_kargs, - removed_kargs - ); - - // Apply the diff to the system kargs - for arg in &removed_kargs { - kargs.remove_exact(arg); - } - for arg in &added_kargs { - kargs.add(arg); - } + compute_apply_kargs_diff(&existing_kargs, &remote_kargs, &mut kargs); Ok(kargs) }