Skip to content

Commit 5afe838

Browse files
composefs-backend/gc: Refactor and add logs
Add debug logs for whatever is being deleted Remove the `delete` param from `delete_deployment` function Use `atomic_replace_with` instead of writing to a buffer then writing Signed-off-by: Pragyan Poudyal <[email protected]>
1 parent 90b88f9 commit 5afe838

File tree

5 files changed

+87
-87
lines changed

5 files changed

+87
-87
lines changed

crates/lib/src/bootc_composefs/boot.rs

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -696,28 +696,27 @@ fn write_grub_uki_menuentry(
696696
//
697697
// TODO: We might find a staged deployment here
698698
if is_upgrade {
699-
let mut buffer = vec![];
700-
701-
// Shouldn't really fail so no context here
702-
buffer.write_all(efi_uuid_source.as_bytes())?;
703-
buffer.write_all(
704-
MenuEntry::new(&boot_label, &id.to_hex())
705-
.to_string()
706-
.as_bytes(),
707-
)?;
708-
709699
let mut str_buf = String::new();
710700
let boot_dir =
711701
Dir::open_ambient_dir(boot_dir, ambient_authority()).context("Opening boot dir")?;
712702
let entries = get_sorted_grub_uki_boot_entries(&boot_dir, &mut str_buf)?;
713703

714-
// Write out only the currently booted entry, which should be the very first one
715-
// Even if we have booted into the second menuentry "boot entry", the default will be the
716-
// first one
717-
buffer.write_all(entries[0].to_string().as_bytes())?;
718-
719704
grub_dir
720-
.atomic_write(user_cfg_name, buffer)
705+
.atomic_replace_with(user_cfg_name, |f| -> std::io::Result<_> {
706+
f.write_all(efi_uuid_source.as_bytes())?;
707+
f.write_all(
708+
MenuEntry::new(&boot_label, &id.to_hex())
709+
.to_string()
710+
.as_bytes(),
711+
)?;
712+
713+
// Write out only the currently booted entry, which should be the very first one
714+
// Even if we have booted into the second menuentry "boot entry", the default will be the
715+
// first one
716+
f.write_all(entries[0].to_string().as_bytes())?;
717+
718+
Ok(())
719+
})
721720
.with_context(|| format!("Writing to {user_cfg_name}"))?;
722721

723722
rustix::fs::fsync(grub_dir.reopen_as_ownedfd()?).context("fsync")?;
@@ -737,18 +736,17 @@ fn write_grub_uki_menuentry(
737736
)?;
738737

739738
// Write to grub2/user.cfg
740-
let mut buffer = vec![];
741-
742-
// Shouldn't really fail so no context here
743-
buffer.write_all(efi_uuid_source.as_bytes())?;
744-
buffer.write_all(
745-
MenuEntry::new(&boot_label, &id.to_hex())
746-
.to_string()
747-
.as_bytes(),
748-
)?;
749-
750739
grub_dir
751-
.atomic_write(user_cfg_name, buffer)
740+
.atomic_replace_with(user_cfg_name, |f| -> std::io::Result<_> {
741+
f.write_all(efi_uuid_source.as_bytes())?;
742+
f.write_all(
743+
MenuEntry::new(&boot_label, &id.to_hex())
744+
.to_string()
745+
.as_bytes(),
746+
)?;
747+
748+
Ok(())
749+
})
752750
.with_context(|| format!("Writing to {user_cfg_name}"))?;
753751

754752
rustix::fs::fsync(grub_dir.reopen_as_ownedfd()?).context("fsync")?;

crates/lib/src/bootc_composefs/delete.rs

Lines changed: 39 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,6 @@ use crate::{
2828
status::Slot,
2929
};
3030

31-
pub(crate) struct ObjectRefs {
32-
pub(crate) other_depl: HashSet<Sha512HashValue>,
33-
pub(crate) depl_to_del: HashSet<Sha512HashValue>,
34-
}
35-
3631
#[fn_error_context::context("Deleting Type1 Entry {}", depl.deployment.verity)]
3732
fn delete_type1_entry(depl: &DeploymentEntry, boot_dir: &Dir, deleting_staged: bool) -> Result<()> {
3833
let entries_dir_path = if deleting_staged {
@@ -78,6 +73,7 @@ fn delete_type1_entry(depl: &DeploymentEntry, boot_dir: &Dir, deleting_staged: b
7873
}
7974

8075
// Boot dir in case of EFI will be the ESP
76+
tracing::debug!("Deleting EFI .conf file: {}", file_name);
8177
entry.remove_file().context("Removing .conf file")?;
8278
delete_uki(&depl.deployment.verity, boot_dir)?;
8379

@@ -93,6 +89,7 @@ fn delete_type1_entry(depl: &DeploymentEntry, boot_dir: &Dir, deleting_staged: b
9389
continue;
9490
}
9591

92+
tracing::debug!("Deleting non-EFI .conf file: {}", file_name);
9693
entry.remove_file().context("Removing .conf file")?;
9794

9895
if should_del_kernel {
@@ -107,6 +104,10 @@ fn delete_type1_entry(depl: &DeploymentEntry, boot_dir: &Dir, deleting_staged: b
107104
}
108105

109106
if deleting_staged {
107+
tracing::debug!(
108+
"Deleting staged entries directory: {}",
109+
TYPE1_ENT_PATH_STAGED
110+
);
110111
boot_dir
111112
.remove_dir_all(TYPE1_ENT_PATH_STAGED)
112113
.context("Removing staged entries dir")?;
@@ -122,11 +123,13 @@ fn delete_kernel_initrd(bls_config: &BLSConfigType, boot_dir: &Dir) -> Result<()
122123
};
123124

124125
// "linux" and "initrd" are relative to the boot_dir in our config files
126+
tracing::debug!("Deleting kernel: {:?}", linux);
125127
boot_dir
126128
.remove_file(linux)
127129
.with_context(|| format!("Removing {linux:?}"))?;
128130

129131
for ird in initrd {
132+
tracing::debug!("Deleting initrd: {:?}", ird);
130133
boot_dir
131134
.remove_file(ird)
132135
.with_context(|| format!("Removing {ird:?}"))?;
@@ -144,6 +147,7 @@ fn delete_kernel_initrd(bls_config: &BLSConfigType, boot_dir: &Dir) -> Result<()
144147
if kernel_parent_dir.entries().iter().len() == 0 {
145148
// We don't have anything other than kernel and initrd in this directory for now
146149
// So this directory should *always* be empty, for now at least
150+
tracing::debug!("Deleting empty kernel directory: {:?}", dir);
147151
kernel_parent_dir.remove_open_dir()?;
148152
};
149153

@@ -162,9 +166,11 @@ fn delete_uki(uki_id: &str, esp_mnt: &Dir) -> Result<()> {
162166

163167
// The actual UKI PE binary
164168
if entry_name == format!("{}{}", uki_id, EFI_EXT) {
169+
tracing::debug!("Deleting UKI: {}", entry_name);
165170
entry.remove_file().context("Deleting UKI")?;
166171
} else if entry_name == format!("{}{}", uki_id, EFI_ADDON_DIR_EXT) {
167172
// Addons dir
173+
tracing::debug!("Deleting UKI addons directory: {}", entry_name);
168174
ukis.remove_dir_all(entry_name)
169175
.context("Deleting UKI addons dir")?;
170176
}
@@ -178,6 +184,7 @@ fn remove_grub_menucfg_entry(id: &str, boot_dir: &Dir, deleting_staged: bool) ->
178184
let grub_dir = boot_dir.open_dir("grub2").context("Opening grub2")?;
179185

180186
if deleting_staged {
187+
tracing::debug!("Deleting staged grub menuentry file: {}", USER_CFG_STAGED);
181188
return grub_dir
182189
.remove_file(USER_CFG_STAGED)
183190
.context("Deleting staged Menuentry");
@@ -186,20 +193,20 @@ fn remove_grub_menucfg_entry(id: &str, boot_dir: &Dir, deleting_staged: bool) ->
186193
let mut string = String::new();
187194
let menuentries = get_sorted_grub_uki_boot_entries(boot_dir, &mut string)?;
188195

189-
let mut buffer = vec![];
190-
191-
buffer.write_all(get_efi_uuid_source().as_bytes())?;
196+
grub_dir
197+
.atomic_replace_with(USER_CFG_STAGED, move |f| -> std::io::Result<_> {
198+
f.write_all(get_efi_uuid_source().as_bytes())?;
192199

193-
for entry in menuentries {
194-
if entry.body.chainloader.contains(id) {
195-
continue;
196-
}
200+
for entry in menuentries {
201+
if entry.body.chainloader.contains(id) {
202+
continue;
203+
}
197204

198-
buffer.write_all(entry.to_string().as_bytes())?;
199-
}
205+
f.write_all(entry.to_string().as_bytes())?;
206+
}
200207

201-
grub_dir
202-
.atomic_write(USER_CFG_STAGED, buffer)
208+
Ok(())
209+
})
203210
.with_context(|| format!("Writing to {USER_CFG_STAGED}"))?;
204211

205212
rustix::fs::fsync(grub_dir.reopen_as_ownedfd().context("Reopening")?).context("fsync")?;
@@ -245,7 +252,8 @@ fn delete_depl_boot_entries(deployment: &DeploymentEntry, deleting_staged: bool)
245252
}
246253
}
247254

248-
pub(crate) fn get_image_objects(sysroot: &Dir, deployment_id: Option<&str>) -> Result<ObjectRefs> {
255+
#[fn_error_context::context("Getting image objects")]
256+
pub(crate) fn get_image_objects(sysroot: &Dir) -> Result<HashSet<Sha512HashValue>> {
249257
let repo = open_composefs_repo(&sysroot)?;
250258

251259
let images_dir = sysroot
@@ -256,10 +264,7 @@ pub(crate) fn get_image_objects(sysroot: &Dir, deployment_id: Option<&str>) -> R
256264
.entries_utf8()
257265
.context("Reading entries in images dir")?;
258266

259-
let mut object_refs = ObjectRefs {
260-
other_depl: HashSet::new(),
261-
depl_to_del: HashSet::new(),
262-
};
267+
let mut object_refs = HashSet::new();
263268

264269
for image in image_entries {
265270
let image = image?;
@@ -270,52 +275,56 @@ pub(crate) fn get_image_objects(sysroot: &Dir, deployment_id: Option<&str>) -> R
270275
.objects_for_image(&img_name)
271276
.with_context(|| format!("Getting objects for image {img_name}"))?;
272277

273-
if let Some(deployment_id) = deployment_id {
274-
if deployment_id == img_name {
275-
object_refs.depl_to_del.extend(objects);
276-
}
277-
} else {
278-
object_refs.other_depl.extend(objects);
279-
}
278+
object_refs.extend(objects);
280279
}
281280

282281
Ok(object_refs)
283282
}
284283

284+
#[fn_error_context::context("Deleting image for deployment {}", deployment_id)]
285285
pub(crate) fn delete_image(sysroot: &Dir, deployment_id: &str) -> Result<()> {
286286
let img_path = Path::new("composefs").join("images").join(deployment_id);
287287

288+
tracing::debug!("Deleting EROFS image: {:?}", img_path);
288289
sysroot
289290
.remove_file(&img_path)
290291
.context("Deleting EROFS image")
291292
}
292293

294+
#[fn_error_context::context("Deleting state directory for deployment {}", deployment_id)]
293295
pub(crate) fn delete_state_dir(sysroot: &Dir, deployment_id: &str) -> Result<()> {
294296
let state_dir = Path::new(STATE_DIR_RELATIVE).join(deployment_id);
295297

298+
tracing::debug!("Deleting state directory: {:?}", state_dir);
296299
sysroot
297300
.remove_dir_all(&state_dir)
298301
.with_context(|| format!("Removing dir {state_dir:?}"))
299302
}
300303

304+
#[fn_error_context::context("Deleting staged deployment")]
301305
pub(crate) fn delete_staged(staged: &Option<BootEntry>) -> Result<()> {
302306
if staged.is_none() {
303307
tracing::debug!("No staged deployment");
304308
return Ok(());
305309
};
306310

307311
let file = Path::new(COMPOSEFS_TRANSIENT_STATE_DIR).join(COMPOSEFS_STAGED_DEPLOYMENT_FNAME);
308-
tracing::debug!("Deleting staged file {file:?}");
312+
tracing::debug!("Deleting staged deployment file: {file:?}");
309313
std::fs::remove_file(file).context("Removing staged file")?;
310314

311315
Ok(())
312316
}
313317

314-
pub(crate) async fn delete_composefs_deployment(deployment_id: &str, delete: bool) -> Result<()> {
318+
#[fn_error_context::context("Deleting composefs deployment {}", deployment_id)]
319+
pub(crate) async fn delete_composefs_deployment(deployment_id: &str) -> Result<()> {
315320
let host = composefs_deployment_status().await?;
316321

317322
let booted = host.require_composefs_booted()?;
318323

324+
if deployment_id == &booted.verity {
325+
anyhow::bail!("Cannot delete currently booted deployment");
326+
}
327+
319328
let all_depls = host.all_composefs_deployments()?;
320329

321330
let depl_to_del = all_depls
@@ -338,15 +347,6 @@ pub(crate) async fn delete_composefs_deployment(deployment_id: &str, delete: boo
338347
composefs_rollback().await?;
339348
}
340349

341-
// For debugging, but maybe useful elsewhere?
342-
if !delete {
343-
return Ok(());
344-
}
345-
346-
if deployment_id == &booted.verity {
347-
anyhow::bail!("Cannot delete currently booted deployment");
348-
}
349-
350350
let kind = if depl_to_del.pinned {
351351
"pinned "
352352
} else if deleting_staged {

crates/lib/src/bootc_composefs/gc.rs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ use crate::{
2424
spec::Bootloader,
2525
};
2626

27+
#[fn_error_context::context("Listing EROFS images")]
2728
fn list_erofs_images(sysroot: &Dir) -> Result<Vec<String>> {
2829
let images_dir = sysroot
2930
.open_dir("composefs/images")
@@ -44,6 +45,7 @@ fn list_erofs_images(sysroot: &Dir) -> Result<Vec<String>> {
4445
///
4546
/// # Returns
4647
/// The fsverity of EROFS images corresponding to boot entries
48+
#[fn_error_context::context("Listing bootloader entries")]
4749
fn list_bootloader_entries() -> Result<Vec<String>> {
4850
let bootloader = get_bootloader()?;
4951

@@ -92,6 +94,7 @@ fn list_bootloader_entries() -> Result<Vec<String>> {
9294
Ok(entries)
9395
}
9496

97+
#[fn_error_context::context("Listing state directories")]
9598
fn list_state_dirs(sysroot: &Dir) -> Result<Vec<String>> {
9699
let state = sysroot
97100
.open_dir(STATE_DIR_RELATIVE)
@@ -116,9 +119,13 @@ fn list_state_dirs(sysroot: &Dir) -> Result<Vec<String>> {
116119
/// present EROFS images
117120
///
118121
/// We do not delete streams though
122+
#[fn_error_context::context("Garbage collecting objects")]
123+
// TODO(Johan-Liebert1): This will be moved to composefs-rs
119124
pub(crate) fn gc_objects(sysroot: &Dir) -> Result<()> {
125+
tracing::debug!("Running garbage collection on unreferenced objects");
126+
120127
// Get all the objects referenced by all available images
121-
let obj_refs = get_image_objects(sysroot, None)?;
128+
let obj_refs = get_image_objects(sysroot)?;
122129

123130
// List all objects in the objects directory
124131
let objects_dir = sysroot
@@ -141,8 +148,8 @@ pub(crate) fn gc_objects(sysroot: &Dir) -> Result<()> {
141148
let id = Sha512HashValue::from_object_dir_and_basename(dir_name, filename.as_bytes())?;
142149

143150
// If this object is not referenced by any image, delete it
144-
if !obj_refs.other_depl.contains(&id) {
145-
tracing::trace!("Removed unreferenced object: {filename}");
151+
if !obj_refs.contains(&id) {
152+
tracing::trace!("Deleting unreferenced object: {filename}");
146153

147154
entry
148155
.remove_file()
@@ -164,6 +171,7 @@ pub(crate) fn gc_objects(sysroot: &Dir) -> Result<()> {
164171
///
165172
/// Similarly if EROFS image B1 doesn't exist, but state dir does, then delete the state dir and
166173
/// perform GC
174+
#[fn_error_context::context("Running composefs garbage collection")]
167175
pub(crate) async fn composefs_gc() -> Result<()> {
168176
let host = composefs_deployment_status().await?;
169177
let booted_cfs = host.require_composefs_booted()?;
@@ -207,8 +215,6 @@ pub(crate) async fn composefs_gc() -> Result<()> {
207215
.collect::<Vec<_>>();
208216

209217
for verity in &state_img_diff {
210-
tracing::debug!("Cleaning up orphaned state directory: {verity}");
211-
212218
delete_staged(staged)?;
213219
delete_state_dir(&sysroot, verity)?;
214220
}

crates/lib/src/bootc_composefs/rollback.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::fmt::Write;
1+
use std::io::Write;
22

33
use anyhow::{anyhow, Context, Result};
44
use cap_std_ext::cap_std::ambient_authority;
@@ -92,16 +92,18 @@ fn rollback_grub_uki_entries(boot_dir: &Dir) -> Result<()> {
9292
let (first, second) = menuentries.split_at_mut(1);
9393
std::mem::swap(&mut first[0], &mut second[0]);
9494

95-
let mut buffer = get_efi_uuid_source();
96-
97-
for entry in menuentries {
98-
write!(buffer, "{entry}")?;
99-
}
100-
10195
let entries_dir = boot_dir.open_dir("grub2").context("Opening grub dir")?;
10296

10397
entries_dir
104-
.atomic_write(USER_CFG_STAGED, buffer)
98+
.atomic_replace_with(USER_CFG_STAGED, |f| -> std::io::Result<_> {
99+
f.write_all(get_efi_uuid_source().as_bytes())?;
100+
101+
for entry in menuentries {
102+
f.write_all(entry.to_string().as_bytes())?;
103+
}
104+
105+
Ok(())
106+
})
105107
.with_context(|| format!("Writing to {USER_CFG_STAGED}"))?;
106108

107109
rename_exchange_user_cfg(&entries_dir)

0 commit comments

Comments
 (0)