Skip to content

Commit fea70e8

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 fea70e8

File tree

3 files changed

+76
-42
lines changed

3 files changed

+76
-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: 71 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,31 @@
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 bootc_mount::tempmount::TempMount;
6+
use cap_std_ext::cap_std::ambient_authority;
57
use cap_std_ext::cap_std::fs::Dir;
68
use cap_std_ext::{cap_std, dirext::CapStdExtDirExt};
79
use fn_error_context::context;
810
use rustix::fs::{fsync, renameat_with, AtFlags, RenameFlags};
911

10-
use crate::bootc_composefs::boot::BootType;
12+
use crate::bootc_composefs::boot::{
13+
get_esp_partition, get_sysroot_parent_dev, type1_entry_conf_file_name, BootType,
14+
};
1115
use crate::bootc_composefs::status::{composefs_deployment_status, get_sorted_type1_boot_entries};
16+
use crate::composefs_consts::TYPE1_ENT_PATH_STAGED;
17+
use crate::spec::Bootloader;
1218
use crate::{
13-
bootc_composefs::{boot::get_efi_uuid_source, status::get_sorted_uki_boot_entries},
19+
bootc_composefs::{boot::get_efi_uuid_source, status::get_sorted_grub_uki_boot_entries},
1420
composefs_consts::{
1521
BOOT_LOADER_ENTRIES, STAGED_BOOT_LOADER_ENTRIES, USER_CFG, USER_CFG_STAGED,
1622
},
1723
spec::BootOrder,
1824
};
1925

26+
/// Atomically rename exchange grub user.cfg with the staged version
27+
/// Performed as the last step in rollback/update/switch operation
28+
#[context("Atomically exchanging user.cfg")]
2029
pub(crate) fn rename_exchange_user_cfg(entries_dir: &Dir) -> Result<()> {
2130
tracing::debug!("Atomically exchanging {USER_CFG_STAGED} and {USER_CFG}");
2231
renameat_with(
@@ -34,13 +43,19 @@ pub(crate) fn rename_exchange_user_cfg(entries_dir: &Dir) -> Result<()> {
3443
tracing::debug!("Syncing to disk");
3544
let entries_dir = entries_dir
3645
.reopen_as_ownedfd()
37-
.context(format!("Reopening entries dir as owned fd"))?;
46+
.context("Reopening entries dir as owned fd")?;
3847

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

4150
Ok(())
4251
}
4352

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

6580
fsync(entries_dir).context("fsync")?;
6681

6782
Ok(())
6883
}
6984

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

7489
let mut str = String::new();
7590
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")?;
91+
Dir::open_ambient_dir("/sysroot/boot", ambient_authority()).context("Opening boot dir")?;
92+
let mut menuentries = get_sorted_grub_uki_boot_entries(&boot_dir, &mut str)
93+
.context("Getting UKI boot entries")?;
8094

8195
// TODO(Johan-Liebert): Currently assuming there are only two deployments
8296
assert!(menuentries.len() == 2);
@@ -101,12 +115,14 @@ pub(crate) fn rollback_composefs_uki() -> Result<()> {
101115
rename_exchange_user_cfg(&entries_dir)
102116
}
103117

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-
118+
/// Performs rollback for
119+
/// - Grub Type1 boot entries
120+
/// - Systemd Typ1 boot entries
121+
/// - Systemd UKI (Type2) boot entries [since we use BLS entries for systemd boot]
122+
///
123+
/// The bootloader parameter is only for logging purposes
124+
#[context("Rolling back {bootloader} entries")]
125+
fn rollback_composefs_entries(boot_dir: &Dir, bootloader: Bootloader) -> Result<()> {
110126
// Sort in descending order as that's the order they're shown on the boot screen
111127
// After this:
112128
// all_configs[0] -> booted depl
@@ -122,34 +138,33 @@ pub(crate) fn rollback_composefs_bls() -> Result<()> {
122138
assert!(all_configs.len() == 2);
123139

124140
// 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:?}"))?;
141+
boot_dir
142+
.create_dir_all(TYPE1_ENT_PATH_STAGED)
143+
.context("Creating staged dir")?;
127144

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:?}"))?;
145+
let rollback_entries_dir = boot_dir
146+
.open_dir(TYPE1_ENT_PATH_STAGED)
147+
.context("Opening staged entries dir")?;
131148

132149
// Write the BLS configs in there
133150
for cfg in all_configs {
134151
// SAFETY: We set sort_key above
135-
let file_name = format!("bootc-composefs-{}.conf", cfg.sort_key.as_ref().unwrap());
152+
let file_name = type1_entry_conf_file_name(cfg.sort_key.as_ref().unwrap());
136153

137154
rollback_entries_dir
138155
.atomic_write(&file_name, cfg.to_string())
139156
.with_context(|| format!("Writing to {file_name}"))?;
140157
}
141158

159+
let rollback_entries_dir = rollback_entries_dir
160+
.reopen_as_ownedfd()
161+
.context("Reopening as owned fd")?;
162+
142163
// 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:?}"))?;
164+
fsync(rollback_entries_dir).context("fsync")?;
149165

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

154169
rename_exchange_bls_entries(&dir)
155170
}
@@ -180,14 +195,33 @@ pub(crate) async fn composefs_rollback() -> Result<()> {
180195
// TODO: Handle staged deployment
181196
// Ostree will drop any staged deployment on rollback but will keep it if it is the first item
182197
// in the new deployment list
183-
let Some(rollback_composefs_entry) = &rollback_status.composefs else {
198+
let Some(rollback_entry) = &rollback_status.composefs else {
184199
anyhow::bail!("Rollback deployment not a composefs deployment")
185200
};
186201

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

192226
if reverting {
193227
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)