Skip to content

Commit c55ad09

Browse files
composefs-backend: Handle rollback for systemd-boot
This piece was leftover when implementing systemd-boot support for composefs backend Signed-off-by: Pragyan Poudyal <[email protected]> Some refactor Signed-off-by: Pragyan Poudyal <[email protected]>
1 parent 84c7a19 commit c55ad09

File tree

3 files changed

+75
-42
lines changed

3 files changed

+75
-42
lines changed

crates/lib/src/bootc_composefs/boot.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ use rustix::{mount::MountFlags, path::Arg};
2929
use schemars::JsonSchema;
3030
use serde::{Deserialize, Serialize};
3131

32-
use crate::bootc_composefs::status::get_sorted_uki_boot_entries;
32+
use crate::bootc_composefs::status::get_sorted_grub_uki_boot_entries;
3333
use crate::composefs_consts::{TYPE1_ENT_PATH, TYPE1_ENT_PATH_STAGED};
3434
use crate::parsers::bls_config::{BLSConfig, BLSConfigType};
3535
use crate::parsers::grub_menuconfig::MenuEntry;
@@ -704,7 +704,7 @@ fn write_grub_uki_menuentry(
704704
let mut str_buf = String::new();
705705
let boot_dir =
706706
Dir::open_ambient_dir(boot_dir, ambient_authority()).context("Opening boot dir")?;
707-
let entries = get_sorted_uki_boot_entries(&boot_dir, &mut str_buf)?;
707+
let entries = get_sorted_grub_uki_boot_entries(&boot_dir, &mut str_buf)?;
708708

709709
// Write out only the currently booted entry, which should be the very first one
710710
// Even if we have booted into the second menuentry "boot entry", the default will be the

crates/lib/src/bootc_composefs/rollback.rs

Lines changed: 70 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,30 @@
1+
use std::fmt::Write;
12
use std::path::PathBuf;
2-
use std::{fmt::Write, fs::create_dir_all};
33

44
use anyhow::{anyhow, Context, Result};
5+
use cap_std_ext::cap_std::ambient_authority;
56
use cap_std_ext::cap_std::fs::Dir;
67
use cap_std_ext::{cap_std, dirext::CapStdExtDirExt};
78
use fn_error_context::context;
89
use rustix::fs::{fsync, renameat_with, AtFlags, RenameFlags};
910

10-
use crate::bootc_composefs::boot::BootType;
11+
use crate::bootc_composefs::boot::{
12+
get_esp_partition, get_sysroot_parent_dev, mount_esp, type1_entry_conf_file_name, BootType,
13+
};
1114
use crate::bootc_composefs::status::{composefs_deployment_status, get_sorted_type1_boot_entries};
15+
use crate::composefs_consts::TYPE1_ENT_PATH_STAGED;
16+
use crate::spec::Bootloader;
1217
use crate::{
13-
bootc_composefs::{boot::get_efi_uuid_source, status::get_sorted_uki_boot_entries},
18+
bootc_composefs::{boot::get_efi_uuid_source, status::get_sorted_grub_uki_boot_entries},
1419
composefs_consts::{
1520
BOOT_LOADER_ENTRIES, STAGED_BOOT_LOADER_ENTRIES, USER_CFG, USER_CFG_STAGED,
1621
},
1722
spec::BootOrder,
1823
};
1924

25+
/// Atomically rename exchange grub user.cfg with the staged version
26+
/// Performed as the last step in rollback/update/switch operation
27+
#[context("Atomically exchanging user.cfg")]
2028
pub(crate) fn rename_exchange_user_cfg(entries_dir: &Dir) -> Result<()> {
2129
tracing::debug!("Atomically exchanging {USER_CFG_STAGED} and {USER_CFG}");
2230
renameat_with(
@@ -34,13 +42,19 @@ pub(crate) fn rename_exchange_user_cfg(entries_dir: &Dir) -> Result<()> {
3442
tracing::debug!("Syncing to disk");
3543
let entries_dir = entries_dir
3644
.reopen_as_ownedfd()
37-
.context(format!("Reopening entries dir as owned fd"))?;
45+
.context("Reopening entries dir as owned fd")?;
3846

39-
fsync(entries_dir).context(format!("fsync entries dir"))?;
47+
fsync(entries_dir).context("fsync entries dir")?;
4048

4149
Ok(())
4250
}
4351

52+
/// Atomically rename exchange "entries" <-> "entries.staged"
53+
/// Performed as the last step in rollback/update/switch operation
54+
///
55+
/// `entries_dir` is the directory that contains the BLS entries directories
56+
/// Ex: entries_dir = ESP/loader or boot/loader
57+
#[context("Atomically exchanging BLS entries")]
4458
pub(crate) fn rename_exchange_bls_entries(entries_dir: &Dir) -> Result<()> {
4559
tracing::debug!("Atomically exchanging {STAGED_BOOT_LOADER_ENTRIES} and {BOOT_LOADER_ENTRIES}");
4660
renameat_with(
@@ -60,23 +74,22 @@ pub(crate) fn rename_exchange_bls_entries(entries_dir: &Dir) -> Result<()> {
6074
tracing::debug!("Syncing to disk");
6175
let entries_dir = entries_dir
6276
.reopen_as_ownedfd()
63-
.with_context(|| format!("Reopening /sysroot/boot/loader as owned fd"))?;
77+
.context("Reopening as owned fd")?;
6478

6579
fsync(entries_dir).context("fsync")?;
6680

6781
Ok(())
6882
}
6983

70-
#[context("Rolling back UKI")]
71-
pub(crate) fn rollback_composefs_uki() -> Result<()> {
84+
#[context("Rolling back Grub UKI")]
85+
fn rollback_grub_uki_entries() -> Result<()> {
7286
let user_cfg_path = PathBuf::from("/sysroot/boot/grub2");
7387

7488
let mut str = String::new();
7589
let boot_dir =
76-
cap_std::fs::Dir::open_ambient_dir("/sysroot/boot", cap_std::ambient_authority())
77-
.context("Opening boot dir")?;
78-
let mut menuentries =
79-
get_sorted_uki_boot_entries(&boot_dir, &mut str).context("Getting UKI boot entries")?;
90+
Dir::open_ambient_dir("/sysroot/boot", ambient_authority()).context("Opening boot dir")?;
91+
let mut menuentries = get_sorted_grub_uki_boot_entries(&boot_dir, &mut str)
92+
.context("Getting UKI boot entries")?;
8093

8194
// TODO(Johan-Liebert): Currently assuming there are only two deployments
8295
assert!(menuentries.len() == 2);
@@ -101,12 +114,14 @@ pub(crate) fn rollback_composefs_uki() -> Result<()> {
101114
rename_exchange_user_cfg(&entries_dir)
102115
}
103116

104-
#[context("Rolling back BLS")]
105-
pub(crate) fn rollback_composefs_bls() -> Result<()> {
106-
let boot_dir =
107-
cap_std::fs::Dir::open_ambient_dir("/sysroot/boot", cap_std::ambient_authority())
108-
.context("Opening boot dir")?;
109-
117+
/// Performs rollback for
118+
/// - Grub Type1 boot entries
119+
/// - Systemd Typ1 boot entries
120+
/// - Systemd UKI (Type2) boot entries [since we use BLS entries for systemd boot]
121+
///
122+
/// The bootloader parameter is only for logging purposes
123+
#[context("Rolling back {bootloader} entries")]
124+
fn rollback_composefs_entries(boot_dir: &Dir, bootloader: Bootloader) -> Result<()> {
110125
// Sort in descending order as that's the order they're shown on the boot screen
111126
// After this:
112127
// all_configs[0] -> booted depl
@@ -122,34 +137,33 @@ pub(crate) fn rollback_composefs_bls() -> Result<()> {
122137
assert!(all_configs.len() == 2);
123138

124139
// Write these
125-
let dir_path = PathBuf::from(format!("/sysroot/boot/loader/{STAGED_BOOT_LOADER_ENTRIES}",));
126-
create_dir_all(&dir_path).with_context(|| format!("Failed to create dir: {dir_path:?}"))?;
140+
boot_dir
141+
.create_dir_all(TYPE1_ENT_PATH_STAGED)
142+
.context("Creating staged dir")?;
127143

128-
let rollback_entries_dir =
129-
cap_std::fs::Dir::open_ambient_dir(&dir_path, cap_std::ambient_authority())
130-
.with_context(|| format!("Opening {dir_path:?}"))?;
144+
let rollback_entries_dir = boot_dir
145+
.open_dir(TYPE1_ENT_PATH_STAGED)
146+
.context("Opening staged entries dir")?;
131147

132148
// Write the BLS configs in there
133149
for cfg in all_configs {
134150
// SAFETY: We set sort_key above
135-
let file_name = format!("bootc-composefs-{}.conf", cfg.sort_key.as_ref().unwrap());
151+
let file_name = type1_entry_conf_file_name(cfg.sort_key.as_ref().unwrap());
136152

137153
rollback_entries_dir
138154
.atomic_write(&file_name, cfg.to_string())
139155
.with_context(|| format!("Writing to {file_name}"))?;
140156
}
141157

158+
let rollback_entries_dir = rollback_entries_dir
159+
.reopen_as_ownedfd()
160+
.context("Reopening as owned fd")?;
161+
142162
// Should we sync after every write?
143-
fsync(
144-
rollback_entries_dir
145-
.reopen_as_ownedfd()
146-
.with_context(|| format!("Reopening {dir_path:?} as owned fd"))?,
147-
)
148-
.with_context(|| format!("fsync {dir_path:?}"))?;
163+
fsync(rollback_entries_dir).context("fsync")?;
149164

150165
// Atomically exchange "entries" <-> "entries.rollback"
151-
let dir = Dir::open_ambient_dir("/sysroot/boot/loader", cap_std::ambient_authority())
152-
.context("Opening loader dir")?;
166+
let dir = boot_dir.open_dir("loader").context("Opening loader dir")?;
153167

154168
rename_exchange_bls_entries(&dir)
155169
}
@@ -180,14 +194,33 @@ pub(crate) async fn composefs_rollback() -> Result<()> {
180194
// TODO: Handle staged deployment
181195
// Ostree will drop any staged deployment on rollback but will keep it if it is the first item
182196
// in the new deployment list
183-
let Some(rollback_composefs_entry) = &rollback_status.composefs else {
197+
let Some(rollback_entry) = &rollback_status.composefs else {
184198
anyhow::bail!("Rollback deployment not a composefs deployment")
185199
};
186200

187-
match rollback_composefs_entry.boot_type {
188-
BootType::Bls => rollback_composefs_bls(),
189-
BootType::Uki => rollback_composefs_uki(),
190-
}?;
201+
match &rollback_entry.bootloader {
202+
Bootloader::Grub => match rollback_entry.boot_type {
203+
BootType::Bls => {
204+
let boot_dir = Dir::open_ambient_dir("/sysroot/boot", ambient_authority())
205+
.context("Opening boot dir")?;
206+
207+
rollback_composefs_entries(&boot_dir, rollback_entry.bootloader.clone())?;
208+
}
209+
210+
BootType::Uki => {
211+
rollback_grub_uki_entries()?;
212+
}
213+
},
214+
215+
Bootloader::Systemd => {
216+
let parent = get_sysroot_parent_dev()?;
217+
let (esp_part, ..) = get_esp_partition(&parent)?;
218+
let esp_mount = mount_esp(&esp_part)?;
219+
220+
// We use BLS entries for systemd UKI as well
221+
rollback_composefs_entries(&esp_mount.fd, rollback_entry.bootloader.clone())?;
222+
}
223+
}
191224

192225
if reverting {
193226
println!("Next boot: current deployment");

crates/lib/src/bootc_composefs/status.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ pub(crate) fn composefs_booted() -> Result<Option<&'static ComposefsCmdline>> {
7979
}
8080

8181
// Need str to store lifetime
82-
pub(crate) fn get_sorted_uki_boot_entries<'a>(
82+
pub(crate) fn get_sorted_grub_uki_boot_entries<'a>(
8383
boot_dir: &Dir,
8484
str: &'a mut String,
8585
) -> Result<Vec<MenuEntry<'a>>> {
@@ -381,7 +381,7 @@ pub(crate) async fn composefs_deployment_status() -> Result<Host> {
381381
BootType::Uki => {
382382
let mut s = String::new();
383383

384-
!get_sorted_uki_boot_entries(&boot_dir, &mut s)?
384+
!get_sorted_grub_uki_boot_entries(&boot_dir, &mut s)?
385385
.first()
386386
.ok_or(anyhow::anyhow!("First boot entry not found"))?
387387
.body
@@ -527,7 +527,7 @@ mod tests {
527527
bootdir.atomic_write(format!("grub2/{USER_CFG}"), user_cfg)?;
528528

529529
let mut s = String::new();
530-
let result = get_sorted_uki_boot_entries(&bootdir, &mut s)?;
530+
let result = get_sorted_grub_uki_boot_entries(&bootdir, &mut s)?;
531531

532532
let expected = vec![
533533
MenuEntry {

0 commit comments

Comments
 (0)