diff --git a/Cargo.lock b/Cargo.lock index 9d6e9bd1..ffc4b46e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -78,6 +78,7 @@ dependencies = [ "serde", "serde_json", "tempfile", + "widestring", ] [[package]] @@ -943,6 +944,12 @@ version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" +[[package]] +name = "widestring" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 93d7eab4..5991c0d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ openssl = "^0.10" serde = { version = "^1.0", features = ["derive"] } serde_json = "^1.0" tempfile = "^3.4" +widestring = "1.0.2" [profile.release] # We assume we're being delivered via e.g. RPM which supports split debuginfo diff --git a/src/efi.rs b/src/efi.rs index d407788f..12a051c4 100644 --- a/src/efi.rs +++ b/src/efi.rs @@ -12,6 +12,7 @@ use std::process::Command; use anyhow::{bail, Context, Result}; use openat_ext::OpenatDirExt; +use widestring::U16CString; use crate::component::*; use crate::filetree; @@ -21,12 +22,16 @@ use crate::util; use crate::util::CommandRunExt; /// Well-known paths to the ESP that may have been mounted external to us. -pub(crate) const ESP_MOUNTS: &[&str] = &["boot/efi", "efi"]; +pub(crate) const ESP_MOUNTS: &[&str] = &["boot", "boot/efi", "efi"]; /// The ESP partition label on Fedora CoreOS derivatives pub(crate) const COREOS_ESP_PART_LABEL: &str = "EFI-SYSTEM"; pub(crate) const ANACONDA_ESP_PART_LABEL: &str = "EFI\\x20System\\x20Partition"; +/// Systemd boot loader info EFI variable names +const LOADER_INFO_VAR_STR: &str = "LoaderInfo-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f"; +const STUB_INFO_VAR_STR: &str = "StubInfo-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f"; + #[derive(Default)] pub(crate) struct Efi { mountpoint: RefCell>, @@ -105,6 +110,70 @@ impl Efi { } } +/// Convert a nul-terminated UTF-16 byte array to a String. +fn string_from_utf16_bytes(slice: &[u8]) -> String { + // For some reason, systemd appends 3 nul bytes after the string. + // Drop the last byte if there's an odd number. + let size = slice.len() / 2; + let v: Vec = (0..size) + .map(|i| u16::from_ne_bytes([slice[2 * i], slice[2 * i + 1]])) + .collect(); + U16CString::from_vec(v).unwrap().to_string_lossy() +} + +/// Read a nul-terminated UTF-16 string from an EFI variable. +fn read_efi_var_utf16_string(name: &str) -> Option { + let efivars = Path::new("/sys/firmware/efi/efivars"); + if !efivars.exists() { + log::warn!("No efivars mount at {:?}", efivars); + return None; + } + let path = efivars.join(name); + if !path.exists() { + log::trace!("No EFI variable {name}"); + return None; + } + match std::fs::read(&path) { + Ok(buf) => { + // Skip the first 4 bytes, those are the EFI variable attributes. + if buf.len() < 4 { + log::warn!("Read less than 4 bytes from {:?}", path); + return None; + } + Some(string_from_utf16_bytes(&buf[4..])) + } + Err(reason) => { + log::warn!("Failed reading {:?}: {reason}", path); + None + } + } +} + +/// Read the LoaderInfo EFI variable if it exists. +fn get_loader_info() -> Option { + read_efi_var_utf16_string(LOADER_INFO_VAR_STR) +} + +/// Read the StubInfo EFI variable if it exists. +fn get_stub_info() -> Option { + read_efi_var_utf16_string(STUB_INFO_VAR_STR) +} + +/// Whether to skip adoption if a systemd bootloader is found. +fn skip_systemd_bootloaders() -> bool { + if let Some(loader_info) = get_loader_info() { + if loader_info.starts_with("systemd") { + log::trace!("Skipping adoption for {:?}", loader_info); + return true; + } + } + if let Some(stub_info) = get_stub_info() { + log::trace!("Skipping adoption for {:?}", stub_info); + return true; + } + false +} + impl Component for Efi { fn name(&self) -> &'static str { "EFI" @@ -130,6 +199,11 @@ impl Component for Efi { } else { log::trace!("No CoreOS aleph detected"); } + // Don't adopt if the system is booted with systemd-boot or + // systemd-stub since those will be managed with bootctl. + if skip_systemd_bootloaders() { + return Ok(None); + } let ostree_deploy_dir = Path::new("/ostree/deploy"); if ostree_deploy_dir.exists() { let btime = ostree_deploy_dir.metadata()?.created()?;