Skip to content

Commit 4f49e6a

Browse files
authored
Merge pull request #1604 from Johan-Liebert1/composefs-finalize
Composefs finalize
2 parents 389a922 + e46daf7 commit 4f49e6a

File tree

14 files changed

+348
-90
lines changed

14 files changed

+348
-90
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/etc-merge/src/lib.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ use cap_std_ext::dirext::CapStdExtDirExt;
2020
use composefs::fsverity::{FsVerityHashValue, Sha256HashValue, Sha512HashValue};
2121
use composefs::generic_tree::{Directory, Inode, Leaf, LeafContent, Stat};
2222
use composefs::tree::ImageError;
23-
use rustix::fs::{AtFlags, Gid, Uid, XattrFlags, lgetxattr, llistxattr, lsetxattr, readlinkat};
23+
use rustix::fs::{
24+
AtFlags, Gid, Uid, XattrFlags, lgetxattr, llistxattr, lsetxattr, readlinkat, symlinkat,
25+
};
2426

2527
/// Metadata associated with a file, directory, or symlink entry.
2628
#[derive(Debug)]
@@ -627,9 +629,8 @@ fn merge_leaf(
627629
.context(format!("Deleting {file:?}"))?;
628630

629631
if let Some(target) = symlink {
630-
new_etc_fd
631-
.symlink(target.as_ref(), &file)
632-
.context(format!("Creating symlink {file:?}"))?;
632+
// Using rustix's symlinkat here as we might have absolute symlinks which clash with ambient_authority
633+
symlinkat(&**target, new_etc_fd, file).context(format!("Creating symlink {file:?}"))?;
633634
} else {
634635
current_etc_fd
635636
.copy(&file, new_etc_fd, &file)

crates/initramfs/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,9 @@ pub fn mount_at_wrapper(
112112
.with_context(|| format!("Mounting at path {path:?}"))
113113
}
114114

115+
/// Wrapper around [`rustix::openat`]
115116
#[context("Opening dir {name:?}")]
116-
fn open_dir(dirfd: impl AsFd, name: impl AsRef<Path> + Debug) -> Result<OwnedFd> {
117+
pub fn open_dir(dirfd: impl AsFd, name: impl AsRef<Path> + Debug) -> Result<OwnedFd> {
117118
let res = openat(
118119
dirfd,
119120
name.as_ref(),

crates/lib/src/bootc_composefs/boot.rs

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,19 @@ pub fn get_esp_partition(device: &str) -> Result<(String, Option<String>)> {
125125
Ok((esp.node, esp.uuid))
126126
}
127127

128+
pub fn get_sysroot_parent_dev() -> Result<String> {
129+
let sysroot = Utf8PathBuf::from("/sysroot");
130+
131+
let fsinfo = inspect_filesystem(&sysroot)?;
132+
let parent_devices = find_parent_devices(&fsinfo.source)?;
133+
134+
let Some(parent) = parent_devices.into_iter().next() else {
135+
anyhow::bail!("Could not find parent device for mountpoint /sysroot");
136+
};
137+
138+
return Ok(parent);
139+
}
140+
128141
/// Compute SHA256Sum of VMlinuz + Initrd
129142
///
130143
/// # Arguments
@@ -310,20 +323,12 @@ pub(crate) fn setup_composefs_bls_boot(
310323
}
311324

312325
BootSetupType::Upgrade((fs, host)) => {
313-
let sysroot = Utf8PathBuf::from("/sysroot");
314-
315-
let fsinfo = inspect_filesystem(&sysroot)?;
316-
let parent_devices = find_parent_devices(&fsinfo.source)?;
317-
318-
let Some(parent) = parent_devices.into_iter().next() else {
319-
anyhow::bail!("Could not find parent device for mountpoint /sysroot");
320-
};
321-
326+
let sysroot_parent = get_sysroot_parent_dev()?;
322327
let bootloader = host.require_composefs_booted()?.bootloader.clone();
323328

324329
(
325330
Utf8PathBuf::from("/sysroot"),
326-
get_esp_partition(&parent)?.0,
331+
get_esp_partition(&sysroot_parent)?.0,
327332
[
328333
format!("root=UUID={DPS_UUID}"),
329334
RW_KARG.to_string(),
@@ -554,15 +559,9 @@ pub(crate) fn setup_composefs_uki_boot(
554559

555560
BootSetupType::Upgrade(..) => {
556561
let sysroot = Utf8PathBuf::from("/sysroot");
562+
let sysroot_parent = get_sysroot_parent_dev()?;
557563

558-
let fsinfo = inspect_filesystem(&sysroot)?;
559-
let parent_devices = find_parent_devices(&fsinfo.source)?;
560-
561-
let Some(parent) = parent_devices.into_iter().next() else {
562-
anyhow::bail!("Could not find parent device for mountpoint /sysroot");
563-
};
564-
565-
(sysroot, get_esp_partition(&parent)?.0, None)
564+
(sysroot, get_esp_partition(&sysroot_parent)?.0, None)
566565
}
567566
};
568567

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
use std::path::Path;
2+
3+
use crate::bootc_composefs::boot::{get_esp_partition, get_sysroot_parent_dev, BootType};
4+
use crate::bootc_composefs::rollback::{rename_exchange_bls_entries, rename_exchange_user_cfg};
5+
use crate::spec::Bootloader;
6+
use crate::{
7+
bootc_composefs::status::composefs_deployment_status, composefs_consts::STATE_DIR_ABS,
8+
};
9+
use anyhow::{Context, Result};
10+
use bootc_initramfs_setup::{mount_composefs_image, open_dir};
11+
use bootc_mount::tempmount::TempMount;
12+
use cap_std_ext::cap_std::{ambient_authority, fs::Dir};
13+
use cap_std_ext::dirext::CapStdExtDirExt;
14+
use etc_merge::{compute_diff, merge, traverse_etc};
15+
use rustix::fs::{fsync, renameat, CWD};
16+
use rustix::path::Arg;
17+
18+
use fn_error_context::context;
19+
20+
pub(crate) async fn composefs_native_finalize() -> Result<()> {
21+
let host = composefs_deployment_status().await?;
22+
23+
let booted_composefs = host.require_composefs_booted()?;
24+
25+
let Some(staged_depl) = host.status.staged.as_ref() else {
26+
tracing::debug!("No staged deployment found");
27+
return Ok(());
28+
};
29+
30+
let staged_composefs = staged_depl.composefs.as_ref().ok_or(anyhow::anyhow!(
31+
"Staged deployment is not a composefs deployment"
32+
))?;
33+
34+
// Mount the booted EROFS image to get pristine etc
35+
let sysroot = open_dir(CWD, "/sysroot")?;
36+
let composefs_fd = mount_composefs_image(&sysroot, &booted_composefs.verity, false)?;
37+
38+
let erofs_tmp_mnt = TempMount::mount_fd(&composefs_fd)?;
39+
40+
// Perform the /etc merge
41+
let pristine_etc =
42+
Dir::open_ambient_dir(erofs_tmp_mnt.dir.path().join("etc"), ambient_authority())?;
43+
let current_etc = Dir::open_ambient_dir("/etc", ambient_authority())?;
44+
45+
let new_etc_path = Path::new(STATE_DIR_ABS)
46+
.join(&staged_composefs.verity)
47+
.join("etc");
48+
49+
let new_etc = Dir::open_ambient_dir(new_etc_path, ambient_authority())?;
50+
51+
let (pristine_files, current_files, new_files) =
52+
traverse_etc(&pristine_etc, &current_etc, &new_etc)?;
53+
54+
let diff = compute_diff(&pristine_files, &current_files)?;
55+
merge(&current_etc, &current_files, &new_etc, &new_files, diff)?;
56+
57+
// Unmount EROFS
58+
drop(erofs_tmp_mnt);
59+
60+
let sysroot_parent = get_sysroot_parent_dev()?;
61+
// NOTE: Assumption here that ESP will always be present
62+
let (esp_part, ..) = get_esp_partition(&sysroot_parent)?;
63+
64+
let esp_mount = TempMount::mount_dev(&esp_part)?;
65+
let boot_dir = Dir::open_ambient_dir("/sysroot/boot", ambient_authority())
66+
.context("Opening sysroot/boot")?;
67+
68+
// NOTE: Assuming here we won't have two bootloaders at the same time
69+
match booted_composefs.bootloader {
70+
Bootloader::Grub => match staged_composefs.boot_type {
71+
BootType::Bls => {
72+
let entries_dir = boot_dir.open_dir("loader")?;
73+
rename_exchange_bls_entries(&entries_dir)?;
74+
}
75+
BootType::Uki => finalize_staged_grub_uki(&esp_mount.fd, &boot_dir)?,
76+
},
77+
78+
Bootloader::Systemd => match staged_composefs.boot_type {
79+
BootType::Bls => {
80+
let entries_dir = esp_mount.fd.open_dir("loader")?;
81+
rename_exchange_bls_entries(&entries_dir)?;
82+
}
83+
BootType::Uki => rename_staged_uki_entries(&esp_mount.fd)?,
84+
},
85+
};
86+
87+
Ok(())
88+
}
89+
90+
#[context("Grub: Finalizing staged UKI")]
91+
fn finalize_staged_grub_uki(esp_mount: &Dir, boot_fd: &Dir) -> Result<()> {
92+
rename_staged_uki_entries(esp_mount)?;
93+
94+
let entries_dir = boot_fd.open_dir("grub2")?;
95+
rename_exchange_user_cfg(&entries_dir)?;
96+
97+
let entries_dir = entries_dir.reopen_as_ownedfd()?;
98+
fsync(entries_dir).context("fsync")?;
99+
100+
Ok(())
101+
}
102+
103+
#[context("Renaming staged UKI entries")]
104+
fn rename_staged_uki_entries(esp_mount: &Dir) -> Result<()> {
105+
for entry in esp_mount.entries()? {
106+
let entry = entry?;
107+
108+
let filename = entry.file_name();
109+
let filename = filename.as_str()?;
110+
111+
if !filename.ends_with(".staged") {
112+
continue;
113+
}
114+
115+
renameat(
116+
&esp_mount,
117+
filename,
118+
&esp_mount,
119+
// SAFETY: We won't reach here if not for the above condition
120+
filename.strip_suffix(".staged").unwrap(),
121+
)
122+
.context("Renaming {filename}")?;
123+
}
124+
125+
let esp_mount = esp_mount.reopen_as_ownedfd()?;
126+
fsync(esp_mount).context("fsync")?;
127+
128+
Ok(())
129+
}

crates/lib/src/bootc_composefs/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub(crate) mod boot;
2+
pub(crate) mod finalize;
23
pub(crate) mod repo;
34
pub(crate) mod rollback;
45
pub(crate) mod state;

crates/lib/src/bootc_composefs/rollback.rs

Lines changed: 55 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,60 @@ use crate::bootc_composefs::status::{composefs_deployment_status, get_sorted_bls
1212
use crate::{
1313
bootc_composefs::{boot::get_efi_uuid_source, status::get_sorted_uki_boot_entries},
1414
composefs_consts::{
15-
BOOT_LOADER_ENTRIES, ROLLBACK_BOOT_LOADER_ENTRIES, USER_CFG, USER_CFG_ROLLBACK,
15+
BOOT_LOADER_ENTRIES, STAGED_BOOT_LOADER_ENTRIES, USER_CFG, USER_CFG_STAGED,
1616
},
1717
spec::BootOrder,
1818
};
1919

20+
pub(crate) fn rename_exchange_user_cfg(entries_dir: &Dir) -> Result<()> {
21+
tracing::debug!("Atomically exchanging {USER_CFG_STAGED} and {USER_CFG}");
22+
renameat_with(
23+
&entries_dir,
24+
USER_CFG_STAGED,
25+
&entries_dir,
26+
USER_CFG,
27+
RenameFlags::EXCHANGE,
28+
)
29+
.context("renameat")?;
30+
31+
tracing::debug!("Removing {USER_CFG_STAGED}");
32+
rustix::fs::unlinkat(&entries_dir, USER_CFG_STAGED, AtFlags::empty()).context("unlinkat")?;
33+
34+
tracing::debug!("Syncing to disk");
35+
let entries_dir = entries_dir
36+
.reopen_as_ownedfd()
37+
.context(format!("Reopening entries dir as owned fd"))?;
38+
39+
fsync(entries_dir).context(format!("fsync entries dir"))?;
40+
41+
Ok(())
42+
}
43+
44+
pub(crate) fn rename_exchange_bls_entries(entries_dir: &Dir) -> Result<()> {
45+
tracing::debug!("Atomically exchanging {STAGED_BOOT_LOADER_ENTRIES} and {BOOT_LOADER_ENTRIES}");
46+
renameat_with(
47+
&entries_dir,
48+
STAGED_BOOT_LOADER_ENTRIES,
49+
&entries_dir,
50+
BOOT_LOADER_ENTRIES,
51+
RenameFlags::EXCHANGE,
52+
)
53+
.context("renameat")?;
54+
55+
tracing::debug!("Removing {STAGED_BOOT_LOADER_ENTRIES}");
56+
rustix::fs::unlinkat(&entries_dir, STAGED_BOOT_LOADER_ENTRIES, AtFlags::REMOVEDIR)
57+
.context("unlinkat")?;
58+
59+
tracing::debug!("Syncing to disk");
60+
let entries_dir = entries_dir
61+
.reopen_as_ownedfd()
62+
.with_context(|| format!("Reopening /sysroot/boot/loader as owned fd"))?;
63+
64+
fsync(entries_dir).context("fsync")?;
65+
66+
Ok(())
67+
}
68+
2069
#[context("Rolling back UKI")]
2170
pub(crate) fn rollback_composefs_uki() -> Result<()> {
2271
let user_cfg_path = PathBuf::from("/sysroot/boot/grub2");
@@ -45,31 +94,10 @@ pub(crate) fn rollback_composefs_uki() -> Result<()> {
4594
.with_context(|| format!("Opening {user_cfg_path:?}"))?;
4695

4796
entries_dir
48-
.atomic_write(USER_CFG_ROLLBACK, buffer)
49-
.with_context(|| format!("Writing to {USER_CFG_ROLLBACK}"))?;
50-
51-
tracing::debug!("Atomically exchanging for {USER_CFG_ROLLBACK} and {USER_CFG}");
52-
renameat_with(
53-
&entries_dir,
54-
USER_CFG_ROLLBACK,
55-
&entries_dir,
56-
USER_CFG,
57-
RenameFlags::EXCHANGE,
58-
)
59-
.context("renameat")?;
60-
61-
tracing::debug!("Removing {USER_CFG_ROLLBACK}");
62-
rustix::fs::unlinkat(&entries_dir, USER_CFG_ROLLBACK, AtFlags::empty()).context("unlinkat")?;
63-
64-
tracing::debug!("Syncing to disk");
65-
fsync(
66-
entries_dir
67-
.reopen_as_ownedfd()
68-
.with_context(|| format!("Reopening {user_cfg_path:?} as owned fd"))?,
69-
)
70-
.with_context(|| format!("fsync {user_cfg_path:?}"))?;
97+
.atomic_write(USER_CFG_STAGED, buffer)
98+
.with_context(|| format!("Writing to {USER_CFG_STAGED}"))?;
7199

72-
Ok(())
100+
rename_exchange_user_cfg(&entries_dir)
73101
}
74102

75103
#[context("Rolling back BLS")]
@@ -93,9 +121,7 @@ pub(crate) fn rollback_composefs_bls() -> Result<()> {
93121
assert!(all_configs.len() == 2);
94122

95123
// Write these
96-
let dir_path = PathBuf::from(format!(
97-
"/sysroot/boot/loader/{ROLLBACK_BOOT_LOADER_ENTRIES}",
98-
));
124+
let dir_path = PathBuf::from(format!("/sysroot/boot/loader/{STAGED_BOOT_LOADER_ENTRIES}",));
99125
create_dir_all(&dir_path).with_context(|| format!("Failed to create dir: {dir_path:?}"))?;
100126

101127
let rollback_entries_dir =
@@ -124,30 +150,7 @@ pub(crate) fn rollback_composefs_bls() -> Result<()> {
124150
let dir = Dir::open_ambient_dir("/sysroot/boot/loader", cap_std::ambient_authority())
125151
.context("Opening loader dir")?;
126152

127-
tracing::debug!(
128-
"Atomically exchanging for {ROLLBACK_BOOT_LOADER_ENTRIES} and {BOOT_LOADER_ENTRIES}"
129-
);
130-
renameat_with(
131-
&dir,
132-
ROLLBACK_BOOT_LOADER_ENTRIES,
133-
&dir,
134-
BOOT_LOADER_ENTRIES,
135-
RenameFlags::EXCHANGE,
136-
)
137-
.context("renameat")?;
138-
139-
tracing::debug!("Removing {ROLLBACK_BOOT_LOADER_ENTRIES}");
140-
rustix::fs::unlinkat(&dir, ROLLBACK_BOOT_LOADER_ENTRIES, AtFlags::empty())
141-
.context("unlinkat")?;
142-
143-
tracing::debug!("Syncing to disk");
144-
fsync(
145-
dir.reopen_as_ownedfd()
146-
.with_context(|| format!("Reopening /sysroot/boot/loader as owned fd"))?,
147-
)
148-
.context("fsync")?;
149-
150-
Ok(())
153+
rename_exchange_bls_entries(&dir)
151154
}
152155

153156
#[context("Rolling back composefs")]

0 commit comments

Comments
 (0)