Skip to content

Commit 93a6d75

Browse files
Johan-Liebert1cgwalters
authored andcommitted
composefs/status: Read UKI entries to check for queued rollback
Parse the Grub menuentry file, `boot/grub2/user.cfg` to get a list of bootable UKIs and figure out if a rollback is currently queued. Signed-off-by: Johan-Liebert1 <[email protected]>
1 parent d9b384d commit 93a6d75

File tree

4 files changed

+62
-14
lines changed

4 files changed

+62
-14
lines changed

crates/lib/src/deploy.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
55
use std::collections::HashSet;
66
use std::fs::create_dir_all;
7-
use std::io::{BufRead, Write};
7+
use std::io::{BufRead, Read as _, Write};
88
use std::path::PathBuf;
99

1010
use anyhow::Ok;
@@ -27,6 +27,7 @@ use rustix::fs::{fsync, renameat_with, AtFlags, RenameFlags};
2727
use crate::bls_config::{parse_bls_config, BLSConfig};
2828
#[allow(unused_imports)]
2929
use crate::install::{get_efi_uuid_source, get_user_config, BootType};
30+
use crate::parsers::grub_menuconfig::{parse_grub_menuentry_file, MenuEntry};
3031
use crate::progress_jsonl::{Event, ProgressWriter, SubTaskBytes, SubTaskStep};
3132
use crate::spec::ImageReference;
3233
use crate::spec::{BootEntry, BootOrder, HostSpec};
@@ -786,8 +787,15 @@ const CURRENT_ENTRIES: &str = "entries";
786787
const STAGED_ENTRIES: &str = "entries.staged";
787788
const ROLLBACK_ENTRIES: &str = STAGED_ENTRIES;
788789

790+
// Need str to store lifetime
791+
pub(crate) fn get_sorted_uki_boot_entries<'a>(str: &'a mut String) -> Result<Vec<MenuEntry<'a>>> {
792+
let mut file = std::fs::File::open("/sysroot/boot/grub2/user.cfg")?;
793+
file.read_to_string(str)?;
794+
parse_grub_menuentry_file(str)
795+
}
796+
789797
#[context("Getting boot entries")]
790-
pub(crate) fn get_sorted_boot_entries(ascending: bool) -> Result<Vec<BLSConfig>> {
798+
pub(crate) fn get_sorted_bls_boot_entries(ascending: bool) -> Result<Vec<BLSConfig>> {
791799
let mut all_configs = vec![];
792800

793801
for entry in std::fs::read_dir(format!("/sysroot/boot/loader/{CURRENT_ENTRIES}"))? {
@@ -822,7 +830,7 @@ pub(crate) fn rollback_composefs_bls() -> Result<()> {
822830
// After this:
823831
// all_configs[0] -> booted depl
824832
// all_configs[1] -> rollback depl
825-
let mut all_configs = get_sorted_boot_entries(false)?;
833+
let mut all_configs = get_sorted_bls_boot_entries(false)?;
826834

827835
// Update the indicies so that they're swapped
828836
for (idx, cfg) in all_configs.iter_mut().enumerate() {

crates/lib/src/kernel_cmdline.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ mod tests {
328328
// non-UTF8 things are in fact valid
329329
let non_utf8_byte = b"\xff";
330330
#[allow(invalid_from_utf8)]
331-
let failed_conversion = str::from_utf8(non_utf8_byte);
331+
let failed_conversion = std::str::from_utf8(non_utf8_byte);
332332
assert!(failed_conversion.is_err());
333333
let mut p = b"foo=".to_vec();
334334
p.push(non_utf8_byte[0]);

crates/lib/src/parsers/grub_menuconfig.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@ use nom::{
1414
#[derive(Debug, PartialEq, Eq)]
1515
pub(crate) struct MenuentryBody<'a> {
1616
/// Kernel modules to load
17-
insmod: Vec<&'a str>,
17+
pub(crate) insmod: Vec<&'a str>,
1818
/// Chainloader path (optional)
19-
chainloader: Option<&'a str>,
19+
pub(crate) chainloader: Option<&'a str>,
2020
/// Search command (optional)
21-
search: Option<&'a str>,
21+
pub(crate) search: Option<&'a str>,
2222
/// Additional commands
23-
extra: Vec<(&'a str, &'a str)>,
23+
pub(crate) extra: Vec<(&'a str, &'a str)>,
2424
}
2525

2626
impl<'a> Display for MenuentryBody<'a> {

crates/lib/src/status.rs

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ use ostree_ext::ostree;
2424
use tokio::io::AsyncReadExt;
2525

2626
use crate::cli::OutputFormat;
27-
use crate::deploy::get_sorted_boot_entries;
27+
use crate::deploy::get_sorted_bls_boot_entries;
28+
use crate::deploy::get_sorted_uki_boot_entries;
2829
use crate::install::BootType;
2930
use crate::install::ORIGIN_KEY_BOOT;
3031
use crate::install::ORIGIN_KEY_BOOT_TYPE;
@@ -438,6 +439,9 @@ pub(crate) async fn composefs_deployment_status() -> Result<Host> {
438439
Err(e) => Err(e),
439440
}?;
440441

442+
// NOTE: This cannot work if we support both BLS and UKI at the same time
443+
let mut boot_type: Option<BootType> = None;
444+
441445
for depl in deployments {
442446
let depl = depl?;
443447

@@ -457,6 +461,21 @@ pub(crate) async fn composefs_deployment_status() -> Result<Host> {
457461
let boot_entry =
458462
boot_entry_from_composefs_deployment(ini, depl_file_name.to_string()).await?;
459463

464+
// SAFETY: boot_entry.composefs will always be present
465+
let boot_type_from_origin = boot_entry.composefs.as_ref().unwrap().boot_type;
466+
467+
match boot_type {
468+
Some(current_type) => {
469+
if current_type != boot_type_from_origin {
470+
anyhow::bail!("Conflicting boot types")
471+
}
472+
}
473+
474+
None => {
475+
boot_type = Some(boot_type_from_origin);
476+
}
477+
};
478+
460479
if depl.file_name() == booted_image_verity {
461480
host.spec.image = boot_entry.image.as_ref().map(|x| x.image.clone());
462481
host.status.booted = Some(boot_entry);
@@ -473,11 +492,32 @@ pub(crate) async fn composefs_deployment_status() -> Result<Host> {
473492
host.status.rollback = Some(boot_entry);
474493
}
475494

476-
host.status.rollback_queued = !get_sorted_boot_entries(false)?
477-
.first()
478-
.ok_or(anyhow::anyhow!("First boot entry not found"))?
479-
.options
480-
.contains(composefs_arg.as_ref());
495+
// Shouldn't really happen, but for sanity nonetheless
496+
let Some(boot_type) = boot_type else {
497+
anyhow::bail!("Could not determine boot type");
498+
};
499+
500+
match boot_type {
501+
BootType::Bls => {
502+
host.status.rollback_queued = !get_sorted_bls_boot_entries(false)?
503+
.first()
504+
.ok_or(anyhow::anyhow!("First boot entry not found"))?
505+
.options
506+
.contains(composefs_arg.as_ref());
507+
}
508+
509+
BootType::Uki => {
510+
let mut s = String::new();
511+
512+
host.status.rollback_queued = !get_sorted_uki_boot_entries(&mut s)?
513+
.first()
514+
.ok_or(anyhow::anyhow!("First boot entry not found"))?
515+
.body
516+
.chainloader
517+
.map(|v| v.contains(composefs_arg.as_ref()))
518+
.unwrap_or_default()
519+
}
520+
};
481521

482522
if host.status.rollback_queued {
483523
host.spec.boot_order = BootOrder::Rollback

0 commit comments

Comments
 (0)