Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions crates/lib/src/bootc_composefs/boot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use rustix::{mount::MountFlags, path::Arg};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use crate::bootc_composefs::status::get_sorted_uki_boot_entries;
use crate::bootc_composefs::status::get_sorted_grub_uki_boot_entries;
use crate::composefs_consts::{TYPE1_ENT_PATH, TYPE1_ENT_PATH_STAGED};
use crate::parsers::bls_config::{BLSConfig, BLSConfigType};
use crate::parsers::grub_menuconfig::MenuEntry;
Expand Down Expand Up @@ -705,7 +705,7 @@ fn write_grub_uki_menuentry(
let mut str_buf = String::new();
let boot_dir =
Dir::open_ambient_dir(boot_dir, ambient_authority()).context("Opening boot dir")?;
let entries = get_sorted_uki_boot_entries(&boot_dir, &mut str_buf)?;
let entries = get_sorted_grub_uki_boot_entries(&boot_dir, &mut str_buf)?;

// Write out only the currently booted entry, which should be the very first one
// Even if we have booted into the second menuentry "boot entry", the default will be the
Expand Down
118 changes: 73 additions & 45 deletions crates/lib/src/bootc_composefs/rollback.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
use std::path::PathBuf;
use std::{fmt::Write, fs::create_dir_all};
use std::fmt::Write;

use anyhow::{anyhow, Context, Result};
use cap_std_ext::cap_std::ambient_authority;
use cap_std_ext::cap_std::fs::Dir;
use cap_std_ext::{cap_std, dirext::CapStdExtDirExt};
use cap_std_ext::dirext::CapStdExtDirExt;
use fn_error_context::context;
use rustix::fs::{fsync, renameat_with, AtFlags, RenameFlags};

use crate::bootc_composefs::boot::BootType;
use crate::bootc_composefs::boot::{
get_esp_partition, get_sysroot_parent_dev, mount_esp, type1_entry_conf_file_name, BootType,
};
use crate::bootc_composefs::status::{composefs_deployment_status, get_sorted_type1_boot_entries};
use crate::composefs_consts::TYPE1_ENT_PATH_STAGED;
use crate::spec::Bootloader;
use crate::{
bootc_composefs::{boot::get_efi_uuid_source, status::get_sorted_uki_boot_entries},
bootc_composefs::{boot::get_efi_uuid_source, status::get_sorted_grub_uki_boot_entries},
composefs_consts::{
BOOT_LOADER_ENTRIES, STAGED_BOOT_LOADER_ENTRIES, USER_CFG, USER_CFG_STAGED,
},
spec::BootOrder,
};

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

fsync(entries_dir).context(format!("fsync entries dir"))?;
fsync(entries_dir).context("fsync entries dir")?;

Ok(())
}

/// Atomically rename exchange "entries" <-> "entries.staged"
/// Performed as the last step in rollback/update/switch operation
///
/// `entries_dir` is the directory that contains the BLS entries directories
/// Ex: entries_dir = ESP/loader or boot/loader
#[context("Atomically exchanging BLS entries")]
pub(crate) fn rename_exchange_bls_entries(entries_dir: &Dir) -> Result<()> {
tracing::debug!("Atomically exchanging {STAGED_BOOT_LOADER_ENTRIES} and {BOOT_LOADER_ENTRIES}");
renameat_with(
Expand All @@ -60,23 +73,18 @@ pub(crate) fn rename_exchange_bls_entries(entries_dir: &Dir) -> Result<()> {
tracing::debug!("Syncing to disk");
let entries_dir = entries_dir
.reopen_as_ownedfd()
.with_context(|| format!("Reopening /sysroot/boot/loader as owned fd"))?;
.context("Reopening as owned fd")?;

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

Ok(())
}

#[context("Rolling back UKI")]
pub(crate) fn rollback_composefs_uki() -> Result<()> {
let user_cfg_path = PathBuf::from("/sysroot/boot/grub2");

#[context("Rolling back Grub UKI")]
fn rollback_grub_uki_entries(boot_dir: &Dir) -> Result<()> {
let mut str = String::new();
let boot_dir =
cap_std::fs::Dir::open_ambient_dir("/sysroot/boot", cap_std::ambient_authority())
.context("Opening boot dir")?;
let mut menuentries =
get_sorted_uki_boot_entries(&boot_dir, &mut str).context("Getting UKI boot entries")?;
let mut menuentries = get_sorted_grub_uki_boot_entries(&boot_dir, &mut str)
.context("Getting UKI boot entries")?;

// TODO(Johan-Liebert): Currently assuming there are only two deployments
assert!(menuentries.len() == 2);
Expand All @@ -90,9 +98,7 @@ pub(crate) fn rollback_composefs_uki() -> Result<()> {
write!(buffer, "{entry}")?;
}

let entries_dir =
cap_std::fs::Dir::open_ambient_dir(&user_cfg_path, cap_std::ambient_authority())
.with_context(|| format!("Opening {user_cfg_path:?}"))?;
let entries_dir = boot_dir.open_dir("grub2").context("Opening grub dir")?;

entries_dir
.atomic_write(USER_CFG_STAGED, buffer)
Expand All @@ -101,12 +107,14 @@ pub(crate) fn rollback_composefs_uki() -> Result<()> {
rename_exchange_user_cfg(&entries_dir)
}

#[context("Rolling back BLS")]
pub(crate) fn rollback_composefs_bls() -> Result<()> {
let boot_dir =
cap_std::fs::Dir::open_ambient_dir("/sysroot/boot", cap_std::ambient_authority())
.context("Opening boot dir")?;

/// Performs rollback for
/// - Grub Type1 boot entries
/// - Systemd Typ1 boot entries
/// - Systemd UKI (Type2) boot entries [since we use BLS entries for systemd boot]
///
/// The bootloader parameter is only for logging purposes
#[context("Rolling back {bootloader} entries")]
fn rollback_composefs_entries(boot_dir: &Dir, bootloader: Bootloader) -> Result<()> {
// Sort in descending order as that's the order they're shown on the boot screen
// After this:
// all_configs[0] -> booted depl
Expand All @@ -122,34 +130,33 @@ pub(crate) fn rollback_composefs_bls() -> Result<()> {
assert!(all_configs.len() == 2);

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

let rollback_entries_dir =
cap_std::fs::Dir::open_ambient_dir(&dir_path, cap_std::ambient_authority())
.with_context(|| format!("Opening {dir_path:?}"))?;
let rollback_entries_dir = boot_dir
.open_dir(TYPE1_ENT_PATH_STAGED)
.context("Opening staged entries dir")?;

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

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

let rollback_entries_dir = rollback_entries_dir
.reopen_as_ownedfd()
.context("Reopening as owned fd")?;

// Should we sync after every write?
fsync(
rollback_entries_dir
.reopen_as_ownedfd()
.with_context(|| format!("Reopening {dir_path:?} as owned fd"))?,
)
.with_context(|| format!("fsync {dir_path:?}"))?;
fsync(rollback_entries_dir).context("fsync")?;

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

rename_exchange_bls_entries(&dir)
}
Expand Down Expand Up @@ -180,14 +187,35 @@ pub(crate) async fn composefs_rollback() -> Result<()> {
// TODO: Handle staged deployment
// Ostree will drop any staged deployment on rollback but will keep it if it is the first item
// in the new deployment list
let Some(rollback_composefs_entry) = &rollback_status.composefs else {
let Some(rollback_entry) = &rollback_status.composefs else {
anyhow::bail!("Rollback deployment not a composefs deployment")
};

match rollback_composefs_entry.boot_type {
BootType::Bls => rollback_composefs_bls(),
BootType::Uki => rollback_composefs_uki(),
}?;
match &rollback_entry.bootloader {
Bootloader::Grub => {
let boot_dir = Dir::open_ambient_dir("/sysroot/boot", ambient_authority())
.context("Opening boot dir")?;

match rollback_entry.boot_type {
BootType::Bls => {
rollback_composefs_entries(&boot_dir, rollback_entry.bootloader.clone())?;
}

BootType::Uki => {
rollback_grub_uki_entries(&boot_dir)?;
}
}
}

Bootloader::Systemd => {
let parent = get_sysroot_parent_dev()?;
let (esp_part, ..) = get_esp_partition(&parent)?;
let esp_mount = mount_esp(&esp_part)?;

// We use BLS entries for systemd UKI as well
rollback_composefs_entries(&esp_mount.fd, rollback_entry.bootloader.clone())?;
}
}

if reverting {
println!("Next boot: current deployment");
Expand Down
6 changes: 3 additions & 3 deletions crates/lib/src/bootc_composefs/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ pub(crate) fn composefs_booted() -> Result<Option<&'static ComposefsCmdline>> {
}

// Need str to store lifetime
pub(crate) fn get_sorted_uki_boot_entries<'a>(
pub(crate) fn get_sorted_grub_uki_boot_entries<'a>(
boot_dir: &Dir,
str: &'a mut String,
) -> Result<Vec<MenuEntry<'a>>> {
Expand Down Expand Up @@ -381,7 +381,7 @@ pub(crate) async fn composefs_deployment_status() -> Result<Host> {
BootType::Uki => {
let mut s = String::new();

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

let mut s = String::new();
let result = get_sorted_uki_boot_entries(&bootdir, &mut s)?;
let result = get_sorted_grub_uki_boot_entries(&bootdir, &mut s)?;

let expected = vec![
MenuEntry {
Expand Down