Skip to content

Commit d8685e3

Browse files
composefs/bls: Use custom prefix for BLS binaris dir
Until now we were using only the deployment verity for the name of the directory where we store BLS Type1 boot binary (vmlinuz + initrd) which made it ambiguous when trying to seach for them while GC-ing. Introduce a custom prefix `bootc_composefs-` to all such directories which makes it easier for us to find them Also, update test to reflect this change Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com> composefs/gc: Refactor Remove `println!` from Non-CLI code and move it to the CLI Remove `dry_run` argument from DeleteDeployment because we delete the image anyway so it isn't really a "dry run" Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
1 parent 45d33d0 commit d8685e3

6 files changed

Lines changed: 96 additions & 80 deletions

File tree

crates/lib/src/bootc_composefs/boot.rs

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@ use rustix::{mount::MountFlags, path::Arg};
9191
use schemars::JsonSchema;
9292
use serde::{Deserialize, Serialize};
9393

94-
use crate::bootc_composefs::state::{get_booted_bls, write_composefs_state};
9594
use crate::parsers::bls_config::{BLSConfig, BLSConfigType};
9695
use crate::task::Task;
9796
use crate::{
@@ -102,6 +101,10 @@ use crate::{
102101
bootc_composefs::repo::open_composefs_repo,
103102
store::{ComposefsFilesystem, Storage},
104103
};
104+
use crate::{
105+
bootc_composefs::state::{get_booted_bls, write_composefs_state},
106+
composefs_consts::TYPE1_BOOT_DIR_PREFIX,
107+
};
105108
use crate::{
106109
bootc_composefs::status::get_container_manifest_and_config, bootc_kargs::compute_new_kargs,
107110
};
@@ -270,6 +273,11 @@ pub(crate) fn secondary_sort_key(os_id: &str) -> String {
270273
format!("bootc-{os_id}-{SORTKEY_PRIORITY_SECONDARY}")
271274
}
272275

276+
/// Returns the name of the directory where we store Type1 boot entries
277+
pub(crate) fn get_type1_dir_name(depl_verity: &String) -> String {
278+
format!("{TYPE1_BOOT_DIR_PREFIX}{depl_verity}")
279+
}
280+
273281
/// Compute SHA256Sum of VMlinuz + Initrd
274282
///
275283
/// # Arguments
@@ -381,10 +389,10 @@ fn write_bls_boot_entries_to_disk(
381389
entry: &UsrLibModulesVmlinuz<Sha512HashValue>,
382390
repo: &crate::store::ComposefsRepository,
383391
) -> Result<()> {
384-
let id_hex = deployment_id.to_hex();
392+
let dir_name = get_type1_dir_name(&deployment_id.to_hex());
385393

386-
// Write the initrd and vmlinuz at /boot/<id>/
387-
let path = boot_dir.join(&id_hex);
394+
// Write the initrd and vmlinuz at /boot/composefs-<id>/
395+
let path = boot_dir.join(&dir_name);
388396
create_dir_all(&path)?;
389397

390398
let entries_dir = Dir::open_ambient_dir(&path, ambient_authority())
@@ -496,6 +504,7 @@ pub(crate) fn setup_composefs_bls_boot(
496504

497505
cmdline_options.extend(&root_setup.kargs);
498506

507+
// TODO(Johan-Liebert1): Use ComposefsCmdline
499508
let composefs_cmdline = if state.composefs_options.allow_missing_verity {
500509
format!("{COMPOSEFS_CMDLINE}=?{id_hex}")
501510
} else {
@@ -648,13 +657,18 @@ pub(crate) fn setup_composefs_bls_boot(
648657

649658
let mut bls_config = BLSConfig::default();
650659

660+
let entries_dir = get_type1_dir_name(&id_hex);
661+
651662
bls_config
652663
.with_title(title)
653664
.with_version(version)
654665
.with_sort_key(sort_key)
655666
.with_cfg(BLSConfigType::NonEFI {
656-
linux: entry_paths.abs_entries_path.join(&id_hex).join(VMLINUZ),
657-
initrd: vec![entry_paths.abs_entries_path.join(&id_hex).join(INITRD)],
667+
linux: entry_paths
668+
.abs_entries_path
669+
.join(&entries_dir)
670+
.join(VMLINUZ),
671+
initrd: vec![entry_paths.abs_entries_path.join(&entries_dir).join(INITRD)],
658672
options: Some(cmdline_refs),
659673
});
660674

@@ -679,7 +693,16 @@ pub(crate) fn setup_composefs_bls_boot(
679693
// We shouldn't error here as all our file names are UTF-8 compatible
680694
let ent_name = ent.file_name()?;
681695

682-
if shared_entries.contains(&ent_name) {
696+
let Some(entry_verity_part) = ent_name.strip_prefix(TYPE1_BOOT_DIR_PREFIX)
697+
else {
698+
// Not our directory
699+
continue;
700+
};
701+
702+
if shared_entries
703+
.iter()
704+
.any(|shared_ent| shared_ent == entry_verity_part)
705+
{
683706
shared_entry = Some(ent_name);
684707
break;
685708
}

crates/lib/src/bootc_composefs/delete.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,6 @@ pub(crate) async fn delete_composefs_deployment(
221221
deployment_id: &str,
222222
storage: &Storage,
223223
booted_cfs: &BootedComposefs,
224-
dry_run: bool,
225224
) -> Result<()> {
226225
const COMPOSEFS_DELETE_JOURNAL_ID: &str = "2a1f0e9d8c7b6a5f4e3d2c1b0a9f8e7d6";
227226

@@ -276,7 +275,7 @@ pub(crate) async fn delete_composefs_deployment(
276275

277276
delete_depl_boot_entries(&depl_to_del, &storage, deleting_staged)?;
278277

279-
composefs_gc(storage, booted_cfs, dry_run).await?;
278+
composefs_gc(storage, booted_cfs, true).await?;
280279

281280
Ok(())
282281
}

crates/lib/src/bootc_composefs/gc.rs

Lines changed: 26 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,19 @@
44
//! - We delete the bootloader entry but fail to delete image
55
//! - We delete bootloader + image but fail to delete the state/unrefenced objects etc
66
7-
use std::os::fd::AsRawFd;
8-
97
use anyhow::{Context, Result};
108
use cap_std_ext::{cap_std::fs::Dir, dirext::CapStdExtDirExt};
9+
use composefs::repository::GcResult;
1110
use composefs_boot::bootloader::{EFI_ADDON_DIR_EXT, EFI_EXT};
12-
use rustix::fs::readlink;
1311

1412
use crate::{
13+
bootc_composefs::boot::get_type1_dir_name,
1514
bootc_composefs::{
16-
boot::{BOOTC_UKI_DIR, BootType, VMLINUZ},
15+
boot::{BOOTC_UKI_DIR, BootType},
1716
delete::{delete_image, delete_staged, delete_state_dir},
1817
status::{get_composefs_status, get_imginfo, list_bootloader_entries},
1918
},
20-
composefs_consts::STATE_DIR_RELATIVE,
19+
composefs_consts::{STATE_DIR_RELATIVE, TYPE1_BOOT_DIR_PREFIX},
2120
store::{BootedComposefs, Storage},
2221
};
2322

@@ -63,7 +62,7 @@ type BootBinary = (BootType, String);
6362

6463
/// Collect all BLS Type1 boot binaries and UKI binaries by scanning filesystem
6564
///
66-
/// Returns a vector of binary type (UKI/Type1) + verity digest of all boot binaries
65+
/// Returns a vector of binary type (UKI/Type1) + name of all boot binaries
6766
#[fn_error_context::context("Collecting boot binaries")]
6867
fn collect_boot_binaries(storage: &Storage) -> Result<Vec<BootBinary>> {
6968
let mut boot_binaries = Vec::new();
@@ -99,62 +98,44 @@ fn collect_uki_binaries(boot_dir: &Dir, boot_binaries: &mut Vec<BootBinary>) ->
9998
Ok(())
10099
}
101100

102-
/// Scan for Type1 boot binaries (kernels + initrds) by looking for directories with verity hashes
101+
/// Scan for Type1 boot binaries (kernels + initrds) by looking for directories with
102+
/// that start with bootc_composefs-
103+
///
104+
/// Strips the prefix and returns the rest of the string
103105
#[fn_error_context::context("Collecting Type1 boot binaries")]
104106
fn collect_type1_boot_binaries(boot_dir: &Dir, boot_binaries: &mut Vec<BootBinary>) -> Result<()> {
105107
for entry in boot_dir.entries_utf8()? {
106108
let entry = entry?;
107-
let dir_path = entry.file_name()?;
109+
let dir_name = entry.file_name()?;
108110

109111
if !entry.file_type()?.is_dir() {
110112
continue;
111113
}
112114

113-
// Check if the directory name looks like a verity hash
114-
// 128 hex chars for SHA512, 64 hex chars for SHA256
115-
// TODO: There might be a better way to do this
116-
let valid_len = matches!(dir_path.len(), 64 | 128);
117-
let valid_hex = dir_path.bytes().all(|b| b.is_ascii_hexdigit());
118-
119-
if !(valid_len && valid_hex) {
115+
let Some(verity) = dir_name.strip_prefix(TYPE1_BOOT_DIR_PREFIX) else {
120116
continue;
121-
}
122-
123-
let verity_dir = boot_dir
124-
.open_dir(&dir_path)
125-
.with_context(|| format!("Opening {dir_path}"))?;
117+
};
126118

127-
// Scan inside this directory for kernel and initrd files
128-
for file_entry in verity_dir.entries_utf8()? {
129-
let file_entry = file_entry?;
130-
let file_name = file_entry.file_name()?;
131-
132-
// Look for kernel and initrd files
133-
if file_name.starts_with(VMLINUZ) {
134-
boot_binaries.push((BootType::Bls, dir_path.clone()));
135-
}
136-
}
119+
// The directory name starts with our custom prefix
120+
boot_binaries.push((BootType::Bls, verity.to_string()));
137121
}
138122

139123
Ok(())
140124
}
141125

142126
#[fn_error_context::context("Deleting kernel and initrd")]
143-
fn delete_kernel_initrd(storage: &Storage, entry_to_delete: &str, dry_run: bool) -> Result<()> {
127+
fn delete_kernel_initrd(storage: &Storage, dir_to_delete: &str, dry_run: bool) -> Result<()> {
144128
let boot_dir = storage.require_boot_dir()?;
145129

146-
let path = readlink(format!("/proc/self/fd/{}", boot_dir.as_raw_fd()), [])
147-
.context("Getting kernel path")?;
148-
149-
tracing::debug!("Deleting {path:?}/{entry_to_delete}");
130+
tracing::debug!("Deleting Type1 entry {dir_to_delete}");
150131

151132
if dry_run {
152133
return Ok(());
153134
}
154135

155136
boot_dir
156-
.remove_dir_all(entry_to_delete)
157-
.with_context(|| anyhow::anyhow!("Deleting {entry_to_delete}"))
137+
.remove_dir_all(dir_to_delete)
138+
.with_context(|| anyhow::anyhow!("Deleting {dir_to_delete}"))
158139
}
159140

160141
/// Deletes the UKI `uki_id` and any addons specific to it
@@ -217,7 +198,7 @@ pub(crate) async fn composefs_gc(
217198
storage: &Storage,
218199
booted_cfs: &BootedComposefs,
219200
dry_run: bool,
220-
) -> Result<()> {
201+
) -> Result<GcResult> {
221202
const COMPOSEFS_GC_JOURNAL_ID: &str = "3b2a1f0e9d8c7b6a5f4e3d2c1b0a9f8e7";
222203

223204
tracing::info!(
@@ -238,7 +219,7 @@ pub(crate) async fn composefs_gc(
238219
tracing::debug!("bootloader_entries: {bootloader_entries:?}");
239220

240221
// Bootloader entry is deleted, but the binary (UKI/kernel+initrd) still exists
241-
let unrefenced_boot_binaries = boot_binaries
222+
let unreferenced_boot_binaries = boot_binaries
242223
.iter()
243224
.filter(|bin_path| {
244225
// We reuse kernel + initrd if they're the same for two deployments
@@ -248,13 +229,13 @@ pub(crate) async fn composefs_gc(
248229
// filter the ones that are not referenced by any bootloader entry
249230
!bootloader_entries
250231
.iter()
251-
.any(|boot_entry| bin_path.1.contains(boot_entry))
232+
.any(|boot_entry| bin_path.1 == *boot_entry)
252233
})
253234
.collect::<Vec<_>>();
254235

255-
tracing::debug!("unrefenced_boot_binaries: {unrefenced_boot_binaries:?}");
236+
tracing::debug!("unreferenced_boot_binaries: {unreferenced_boot_binaries:?}");
256237

257-
if unrefenced_boot_binaries
238+
if unreferenced_boot_binaries
258239
.iter()
259240
.find(|be| be.1 == booted_cfs_status.verity)
260241
.is_some()
@@ -265,9 +246,9 @@ pub(crate) async fn composefs_gc(
265246
)
266247
}
267248

268-
for (ty, verity) in unrefenced_boot_binaries {
249+
for (ty, verity) in unreferenced_boot_binaries {
269250
match ty {
270-
BootType::Bls => delete_kernel_initrd(storage, verity, dry_run)?,
251+
BootType::Bls => delete_kernel_initrd(storage, &get_type1_dir_name(verity), dry_run)?,
271252
BootType::Uki => delete_uki(storage, verity, dry_run)?,
272253
}
273254
}
@@ -345,21 +326,5 @@ pub(crate) async fn composefs_gc(
345326
booted_cfs.repo.gc(&additional_roots)?
346327
};
347328

348-
if dry_run {
349-
println!("Dry run (no files deleted)");
350-
}
351-
352-
println!(
353-
"Objects: {} removed ({} bytes)",
354-
gc_result.objects_removed, gc_result.objects_bytes
355-
);
356-
357-
if gc_result.images_pruned > 0 || gc_result.streams_pruned > 0 {
358-
println!(
359-
"Pruned symlinks: {} images, {} streams",
360-
gc_result.images_pruned, gc_result.streams_pruned
361-
);
362-
}
363-
364-
Ok(())
329+
Ok(gc_result)
365330
}

crates/lib/src/cli.rs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -793,8 +793,6 @@ pub(crate) enum Opt {
793793
#[clap(hide = true)]
794794
DeleteDeployment {
795795
depl_id: String,
796-
#[clap(long)]
797-
dry_run: bool,
798796
},
799797
}
800798

@@ -1917,7 +1915,25 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
19171915
}
19181916

19191917
BootedStorageKind::Composefs(booted_cfs) => {
1920-
composefs_gc(storage, &booted_cfs, dry_run).await
1918+
let gc_result = composefs_gc(storage, &booted_cfs, dry_run).await?;
1919+
1920+
if dry_run {
1921+
println!("Dry run (no files deleted)");
1922+
}
1923+
1924+
println!(
1925+
"Objects: {} removed ({} bytes)",
1926+
gc_result.objects_removed, gc_result.objects_bytes
1927+
);
1928+
1929+
if gc_result.images_pruned > 0 || gc_result.streams_pruned > 0 {
1930+
println!(
1931+
"Pruned symlinks: {} images, {} streams",
1932+
gc_result.images_pruned, gc_result.streams_pruned
1933+
);
1934+
}
1935+
1936+
Ok(())
19211937
}
19221938
}
19231939
}
@@ -1955,14 +1971,14 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
19551971
}
19561972
}
19571973

1958-
Opt::DeleteDeployment { depl_id, dry_run } => {
1974+
Opt::DeleteDeployment { depl_id } => {
19591975
let storage = &get_storage().await?;
19601976
match storage.kind()? {
19611977
BootedStorageKind::Ostree(_) => {
19621978
anyhow::bail!("DeleteDeployment is only supported for composefs backend")
19631979
}
19641980
BootedStorageKind::Composefs(booted_cfs) => {
1965-
delete_composefs_deployment(&depl_id, storage, &booted_cfs, dry_run).await
1981+
delete_composefs_deployment(&depl_id, storage, &booted_cfs).await
19661982
}
19671983
}
19681984
}

crates/lib/src/composefs_consts.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
#![allow(dead_code)]
2-
31
/// composefs= parameter in kernel cmdline
42
pub const COMPOSEFS_CMDLINE: &str = "composefs";
53

@@ -38,3 +36,6 @@ pub(crate) const TYPE1_ENT_PATH: &str = "loader/entries";
3836
pub(crate) const TYPE1_ENT_PATH_STAGED: &str = "loader/entries.staged";
3937

4038
pub(crate) const BOOTC_FINALIZE_STAGED_SERVICE: &str = "bootc-finalize-staged.service";
39+
40+
/// The prefix for the directories containing kernel + initrd
41+
pub(crate) const TYPE1_BOOT_DIR_PREFIX: &str = "bootc_composefs-";

tmt/tests/booted/test-composefs-gc.nu

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ use tap.nu
1010
let st = bootc status --json | from json
1111
let booted = $st.status.booted.image
1212

13+
let dir_prefix = "bootc_composefs-"
14+
1315
if not (tap is_composefs) or ($st.status.booted.composefs.bootType | str downcase) == "uki" {
1416
exit 0
1517
}
@@ -111,15 +113,17 @@ def third_boot [] {
111113
"/sysroot/boot"
112114
}
113115

114-
assert ($"($boot_dir)/($booted_verity)" | path exists)
116+
print $"bootdir ($boot_dir)"
117+
118+
assert ($"($boot_dir)/($dir_prefix)($booted_verity)" | path exists)
115119

116120
# This is for the rollback, but since the rollback and the very
117121
# first boot have the same kernel + initrd pair, and this rollback
118122
# was deployed after the first boot, we will still be using the very
119123
# first verity for the boot binary name
120-
assert ($"($boot_dir)/(cat /var/first-verity)" | path exists)
124+
assert ($"($boot_dir)/($dir_prefix)(cat /var/first-verity)" | path exists)
121125

122-
echo $"($boot_dir)/($booted_verity)" | save /var/to-be-deleted-kernel
126+
echo $"($boot_dir)/($dir_prefix)(cat /var/first-verity)" | save /var/to-be-deleted-kernel
123127

124128
# Now we create a new image derived from the current kernel + initrd
125129
# Switching to this and rebooting should remove the old kernel + initrd
@@ -135,6 +139,14 @@ def third_boot [] {
135139
}
136140

137141
def fourth_boot [] {
142+
let bootloader = (bootc status --json | from json).status.booted.composefs.bootloader
143+
144+
if ($bootloader | str downcase) == "systemd" {
145+
# TODO: Some concrete API for this would be great
146+
mkdir /var/tmp/efi
147+
mount /dev/vda2 /var/tmp/efi
148+
}
149+
138150
assert equal $booted.image.image "localhost/bootc-final"
139151
assert (not ((cat /var/to-be-deleted-kernel | path exists)))
140152

0 commit comments

Comments
 (0)