Skip to content

Commit 0b43a0b

Browse files
composefs/rollback: Handle UKI rollback
We parse the grub menuentries, get the rollback deployment then perform the rollback, which basically consists of writing a new .staged menuentry file then atomically swapping the staged and the current menuentry. Rollback while there is a staged deployment is still to be handled. Signed-off-by: Johan-Liebert1 <[email protected]>
1 parent 6b046c3 commit 0b43a0b

File tree

4 files changed

+78
-61
lines changed

4 files changed

+78
-61
lines changed

crates/lib/src/deploy.rs

Lines changed: 55 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
//! Create a merged filesystem tree with the image and mounted configmaps.
44
55
use std::collections::HashSet;
6+
use std::fmt::Write as _;
67
use std::fs::create_dir_all;
7-
use std::io::{BufRead, Write};
8+
use std::io::{BufRead, Read, Write};
89
use std::path::PathBuf;
910

1011
use anyhow::Ok;
@@ -25,10 +26,11 @@ use ostree_ext::tokio_util::spawn_blocking_cancellable_flatten;
2526
use rustix::fs::{fsync, renameat_with, AtFlags, RenameFlags};
2627

2728
use crate::bls_config::{parse_bls_config, BLSConfig};
28-
use crate::install::{get_efi_uuid_source, get_user_config, BootType};
29+
use crate::install::{get_efi_uuid_source, BootType};
30+
use crate::parsers::grub_menuconfig::{parse_grub_menuentry_file, MenuEntry};
2931
use crate::progress_jsonl::{Event, ProgressWriter, SubTaskBytes, SubTaskStep};
3032
use crate::spec::ImageReference;
31-
use crate::spec::{BootEntry, BootOrder, HostSpec};
33+
use crate::spec::{BootOrder, HostSpec};
3234
use crate::status::{composefs_deployment_status, labels_of_config};
3335
use crate::store::Storage;
3436
use crate::utils::async_task_with_spinner;
@@ -742,38 +744,59 @@ pub(crate) async fn stage(
742744
Ok(())
743745
}
744746

747+
/// Filename for `loader/entries`
748+
pub(crate) const USER_CFG: &str = "user.cfg";
749+
pub(crate) const USER_CFG_STAGED: &str = "user.cfg.staged";
750+
pub(crate) const USER_CFG_ROLLBACK: &str = USER_CFG_STAGED;
751+
745752
#[context("Rolling back UKI")]
746-
pub(crate) fn rollback_composefs_uki(current: &BootEntry, rollback: &BootEntry) -> Result<()> {
747-
let user_cfg_name = "grub2/user.cfg.staged";
748-
let user_cfg_path = PathBuf::from("/sysroot/boot").join(user_cfg_name);
753+
pub(crate) fn rollback_composefs_uki() -> Result<()> {
754+
let user_cfg_path = PathBuf::from("/sysroot/boot/grub2");
749755

750-
let efi_uuid_source = get_efi_uuid_source();
756+
let mut str = String::new();
757+
let mut menuentries =
758+
get_sorted_uki_boot_entries(&mut str).context("Getting UKI boot entries")?;
751759

752-
// TODO: Need to check if user.cfg.staged exists
753-
let mut usr_cfg = std::fs::OpenOptions::new()
754-
.write(true)
755-
.create(true)
756-
.truncate(true)
757-
.open(user_cfg_path)
758-
.with_context(|| format!("Opening {user_cfg_name}"))?;
760+
// TODO(Johan-Liebert): Currently assuming there are only two deployments
761+
assert!(menuentries.len() == 2);
759762

760-
usr_cfg.write(efi_uuid_source.as_bytes())?;
763+
let (first, second) = menuentries.split_at_mut(1);
764+
std::mem::swap(&mut first[0], &mut second[0]);
761765

762-
let verity = if let Some(composefs) = &rollback.composefs {
763-
composefs.verity.clone()
764-
} else {
765-
// Shouldn't really happen
766-
anyhow::bail!("Verity not found for rollback deployment")
767-
};
768-
usr_cfg.write(get_user_config(todo!(), &verity).as_bytes())?;
766+
let mut buffer = get_efi_uuid_source();
769767

770-
let verity = if let Some(composefs) = &current.composefs {
771-
composefs.verity.clone()
772-
} else {
773-
// Shouldn't really happen
774-
anyhow::bail!("Verity not found for booted deployment")
775-
};
776-
usr_cfg.write(get_user_config(todo!(), &verity).as_bytes())?;
768+
for entry in menuentries {
769+
write!(buffer, "{entry}")?;
770+
}
771+
772+
let entries_dir =
773+
cap_std::fs::Dir::open_ambient_dir(&user_cfg_path, cap_std::ambient_authority())
774+
.with_context(|| format!("Opening {user_cfg_path:?}"))?;
775+
776+
entries_dir
777+
.atomic_write(USER_CFG_ROLLBACK, buffer)
778+
.with_context(|| format!("Writing to {USER_CFG_ROLLBACK}"))?;
779+
780+
tracing::debug!("Atomically exchanging for {USER_CFG_ROLLBACK} and {USER_CFG}");
781+
renameat_with(
782+
&entries_dir,
783+
USER_CFG_ROLLBACK,
784+
&entries_dir,
785+
USER_CFG,
786+
RenameFlags::EXCHANGE,
787+
)
788+
.context("renameat")?;
789+
790+
tracing::debug!("Removing {USER_CFG_ROLLBACK}");
791+
rustix::fs::unlinkat(&entries_dir, USER_CFG_ROLLBACK, AtFlags::REMOVEDIR).context("unlinkat")?;
792+
793+
tracing::debug!("Syncing to disk");
794+
fsync(
795+
entries_dir
796+
.reopen_as_ownedfd()
797+
.with_context(|| format!("Reopening {user_cfg_path:?} as owned fd"))?,
798+
)
799+
.with_context(|| format!("fsync {user_cfg_path:?}"))?;
777800

778801
Ok(())
779802
}
@@ -834,6 +857,7 @@ pub(crate) fn rollback_composefs_bls() -> Result<()> {
834857
cfg.version = idx as u32;
835858
}
836859

860+
// TODO(Johan-Liebert): Currently assuming there are only two deployments
837861
assert!(all_configs.len() == 2);
838862

839863
// Write these
@@ -849,7 +873,7 @@ pub(crate) fn rollback_composefs_bls() -> Result<()> {
849873
let file_name = format!("bootc-composefs-{}.conf", cfg.version);
850874

851875
rollback_entries_dir
852-
.atomic_write(&file_name, cfg.to_string().as_bytes())
876+
.atomic_write(&file_name, cfg.to_string())
853877
.with_context(|| format!("Writing to {file_name}"))?;
854878
}
855879

@@ -920,7 +944,7 @@ pub(crate) async fn composefs_rollback() -> Result<()> {
920944

921945
match rollback_composefs_entry.boot_type {
922946
BootType::Bls => rollback_composefs_bls(),
923-
BootType::Uki => rollback_composefs_uki(&host.status.booted.unwrap(), &rollback_status),
947+
BootType::Uki => rollback_composefs_uki(),
924948
}?;
925949

926950
Ok(())

crates/lib/src/install.rs

Lines changed: 21 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,12 @@ use self::baseline::InstallBlockDeviceOpts;
7676
use crate::bls_config::{parse_bls_config, BLSConfig};
7777
use crate::boundimage::{BoundImage, ResolvedBoundImage};
7878
use crate::containerenv::ContainerExecutionInfo;
79-
use crate::deploy::{prepare_for_pull, pull_from_prepared, PreparedImportMeta, PreparedPullResult};
79+
use crate::deploy::{
80+
get_sorted_uki_boot_entries, prepare_for_pull, pull_from_prepared, PreparedImportMeta,
81+
PreparedPullResult, USER_CFG, USER_CFG_STAGED,
82+
};
8083
use crate::lsm;
84+
use crate::parsers::grub_menuconfig::MenuEntry;
8185
use crate::progress_jsonl::ProgressWriter;
8286
use crate::spec::ImageReference;
8387
use crate::store::Storage;
@@ -1730,22 +1734,6 @@ pub fn get_esp_partition(device: &str) -> Result<(String, Option<String>)> {
17301734
Ok((esp.node, esp.uuid))
17311735
}
17321736

1733-
pub(crate) fn get_user_config(boot_label: &String, uki_id: &str) -> String {
1734-
// TODO: Full EFI path here
1735-
let s = format!(
1736-
r#"
1737-
menuentry "{boot_label}: ({uki_id})" {{
1738-
insmod fat
1739-
insmod chain
1740-
search --no-floppy --set=root --fs-uuid "${{EFI_PART_UUID}}"
1741-
chainloader /EFI/Linux/{uki_id}.efi
1742-
}}
1743-
"#
1744-
);
1745-
1746-
return s;
1747-
}
1748-
17491737
/// Contains the EFP's filesystem UUID. Used by grub
17501738
pub(crate) const EFI_UUID_FILE: &str = "efiuuid.cfg";
17511739

@@ -1894,9 +1882,9 @@ pub(crate) fn setup_composefs_uki_boot(
18941882
let efi_uuid_source = get_efi_uuid_source();
18951883

18961884
let user_cfg_name = if is_upgrade {
1897-
"user.cfg.staged"
1885+
USER_CFG_STAGED
18981886
} else {
1899-
"user.cfg"
1887+
USER_CFG
19001888
};
19011889

19021890
let grub_dir =
@@ -1911,17 +1899,17 @@ pub(crate) fn setup_composefs_uki_boot(
19111899

19121900
// Shouldn't really fail so no context here
19131901
buffer.write_all(efi_uuid_source.as_bytes())?;
1914-
buffer.write_all(get_user_config(&boot_label, &id.to_hex()).as_bytes())?;
1915-
1916-
// root_path here will be /sysroot
1917-
for entry in std::fs::read_dir(root_path.join(STATE_DIR_RELATIVE))? {
1918-
let entry = entry?;
1902+
buffer.write_all(
1903+
MenuEntry::new(&boot_label, &id.to_hex())
1904+
.to_string()
1905+
.as_bytes(),
1906+
)?;
19191907

1920-
let depl_file_name = entry.file_name();
1921-
// SAFETY: Deployment file name shouldn't containg non UTF-8 chars
1922-
let depl_file_name = depl_file_name.to_string_lossy();
1908+
let mut str_buf = String::new();
1909+
let entries = get_sorted_uki_boot_entries(&mut str_buf)?;
19231910

1924-
buffer.write_all(get_user_config(&boot_label, &depl_file_name).as_bytes())?;
1911+
for entry in entries {
1912+
buffer.write_all(entry.to_string().as_bytes())?;
19251913
}
19261914

19271915
grub_dir
@@ -1949,7 +1937,11 @@ pub(crate) fn setup_composefs_uki_boot(
19491937

19501938
// Shouldn't really fail so no context here
19511939
buffer.write_all(efi_uuid_source.as_bytes())?;
1952-
buffer.write_all(get_user_config(&boot_label, &id.to_hex()).as_bytes())?;
1940+
buffer.write_all(
1941+
MenuEntry::new(&boot_label, &id.to_hex())
1942+
.to_string()
1943+
.as_bytes(),
1944+
)?;
19531945

19541946
grub_dir
19551947
.atomic_write(user_cfg_name, buffer)

crates/lib/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ mod store;
2828
mod task;
2929
mod utils;
3030
mod bls_config;
31+
pub(crate) mod parsers;
3132

3233
#[cfg(feature = "docgen")]
3334
mod docgen;

crates/ostree-ext/src/container_utils.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ pub fn ostree_booted() -> io::Result<bool> {
7878
}
7979

8080

81-
/// Returns true if the system appears to have been booted with composefs.
81+
/// Returns true if the system appears to have been booted with composefs without ostree.
8282
pub fn composefs_booted() -> io::Result<bool> {
8383
let cmdline = std::fs::read_to_string("/proc/cmdline")?;
8484
Ok(cmdline.contains("composefs="))

0 commit comments

Comments
 (0)