bootc_lib/bootc_composefs/
finalize.rs1use std::path::Path;
2
3use crate::bootc_composefs::boot::BootType;
4use crate::bootc_composefs::rollback::{rename_exchange_bls_entries, rename_exchange_user_cfg};
5use crate::bootc_composefs::status::get_composefs_status;
6use crate::composefs_consts::STATE_DIR_ABS;
7use crate::spec::Bootloader;
8use crate::store::{BootedComposefs, Storage};
9use anyhow::{Context, Result};
10use bootc_initramfs_setup::mount_composefs_image;
11use bootc_mount::tempmount::TempMount;
12use cap_std_ext::cap_std::{ambient_authority, fs::Dir};
13use cap_std_ext::dirext::CapStdExtDirExt;
14use etc_merge::{compute_diff, merge, print_diff, traverse_etc};
15use rustix::fs::{fsync, renameat};
16use rustix::path::Arg;
17
18use fn_error_context::context;
19
20pub(crate) async fn get_etc_diff(storage: &Storage, booted_cfs: &BootedComposefs) -> Result<()> {
21 let host = get_composefs_status(storage, booted_cfs).await?;
22 let booted_composefs = host.require_composefs_booted()?;
23
24 let sysroot_fd = storage.physical_root.reopen_as_ownedfd()?;
26 let composefs_fd = mount_composefs_image(&sysroot_fd, &booted_composefs.verity, false)?;
27
28 let erofs_tmp_mnt = TempMount::mount_fd(&composefs_fd)?;
29
30 let pristine_etc =
31 Dir::open_ambient_dir(erofs_tmp_mnt.dir.path().join("etc"), ambient_authority())?;
32 let current_etc = Dir::open_ambient_dir("/etc", ambient_authority())?;
33
34 let (pristine_files, current_files, _) = traverse_etc(&pristine_etc, ¤t_etc, None)?;
35 let diff = compute_diff(&pristine_files, ¤t_files)?;
36
37 print_diff(&diff, &mut std::io::stdout());
38
39 Ok(())
40}
41
42pub(crate) async fn composefs_backend_finalize(
43 storage: &Storage,
44 booted_cfs: &BootedComposefs,
45) -> Result<()> {
46 let host = get_composefs_status(storage, booted_cfs).await?;
47
48 let booted_composefs = host.require_composefs_booted()?;
49
50 let Some(staged_depl) = host.status.staged.as_ref() else {
51 tracing::debug!("No staged deployment found");
52 return Ok(());
53 };
54
55 let staged_composefs = staged_depl.composefs.as_ref().ok_or(anyhow::anyhow!(
56 "Staged deployment is not a composefs deployment"
57 ))?;
58
59 let sysroot_fd = storage.physical_root.reopen_as_ownedfd()?;
61 let composefs_fd = mount_composefs_image(&sysroot_fd, &booted_composefs.verity, false)?;
62
63 let erofs_tmp_mnt = TempMount::mount_fd(&composefs_fd)?;
64
65 let pristine_etc =
67 Dir::open_ambient_dir(erofs_tmp_mnt.dir.path().join("etc"), ambient_authority())?;
68 let current_etc = Dir::open_ambient_dir("/etc", ambient_authority())?;
69
70 let new_etc_path = Path::new(STATE_DIR_ABS)
71 .join(&staged_composefs.verity)
72 .join("etc");
73
74 let new_etc = Dir::open_ambient_dir(new_etc_path, ambient_authority())?;
75
76 let (pristine_files, current_files, new_files) =
77 traverse_etc(&pristine_etc, ¤t_etc, Some(&new_etc))?;
78
79 let new_files = new_files.ok_or(anyhow::anyhow!("Failed to get dirtree for new etc"))?;
80
81 let diff = compute_diff(&pristine_files, ¤t_files)?;
82 merge(¤t_etc, ¤t_files, &new_etc, &new_files, diff)?;
83
84 drop(erofs_tmp_mnt);
86
87 let boot_dir = storage.require_boot_dir()?;
88
89 let esp_mount = storage
90 .esp
91 .as_ref()
92 .ok_or_else(|| anyhow::anyhow!("ESP not found"))?;
93
94 match booted_composefs.bootloader {
96 Bootloader::Grub => match staged_composefs.boot_type {
97 BootType::Bls => {
98 let entries_dir = boot_dir.open_dir("loader")?;
99 rename_exchange_bls_entries(&entries_dir)?;
100 }
101 BootType::Uki => finalize_staged_grub_uki(&esp_mount.fd, boot_dir)?,
102 },
103
104 Bootloader::Systemd => {
105 if matches!(staged_composefs.boot_type, BootType::Uki) {
106 rename_staged_uki_entries(&esp_mount.fd)?;
107 }
108
109 let entries_dir = boot_dir.open_dir("loader")?;
110 rename_exchange_bls_entries(&entries_dir)?;
111 }
112 };
113
114 Ok(())
115}
116
117#[context("Grub: Finalizing staged UKI")]
118fn finalize_staged_grub_uki(esp_mount: &Dir, boot_fd: &Dir) -> Result<()> {
119 rename_staged_uki_entries(esp_mount)?;
120
121 let entries_dir = boot_fd.open_dir("grub2")?;
122 rename_exchange_user_cfg(&entries_dir)?;
123
124 let entries_dir = entries_dir.reopen_as_ownedfd()?;
125 fsync(entries_dir).context("fsync")?;
126
127 Ok(())
128}
129
130#[context("Renaming staged UKI entries")]
131fn rename_staged_uki_entries(esp_mount: &Dir) -> Result<()> {
132 for entry in esp_mount.entries()? {
133 let entry = entry?;
134
135 let filename = entry.file_name();
136 let filename = filename.as_str()?;
137
138 if !filename.ends_with(".staged") {
139 continue;
140 }
141
142 renameat(
143 &esp_mount,
144 filename,
145 &esp_mount,
146 filename.strip_suffix(".staged").unwrap(),
148 )
149 .context("Renaming {filename}")?;
150 }
151
152 let esp_mount = esp_mount.reopen_as_ownedfd()?;
153 fsync(esp_mount).context("fsync")?;
154
155 Ok(())
156}