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
211 changes: 167 additions & 44 deletions crates/lib/src/bootc_composefs/boot.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::ffi::OsStr;
use std::fs::create_dir_all;
use std::io::Write;
use std::path::Path;
use std::{ffi::OsStr, path::PathBuf};

use anyhow::{anyhow, Context, Result};
use bootc_blockdev::find_parent_devices;
Expand Down Expand Up @@ -35,7 +35,8 @@ use serde::{Deserialize, Serialize};
use crate::bootc_composefs::repo::open_composefs_repo;
use crate::bootc_composefs::state::{get_booted_bls, write_composefs_state};
use crate::bootc_composefs::status::get_sorted_uki_boot_entries;
use crate::parsers::bls_config::BLSConfig;
use crate::composefs_consts::{TYPE1_ENT_PATH, TYPE1_ENT_PATH_STAGED};
use crate::parsers::bls_config::{BLSConfig, BLSConfigType};
use crate::parsers::grub_menuconfig::MenuEntry;
use crate::spec::ImageReference;
use crate::task::Task;
Expand All @@ -55,6 +56,16 @@ pub(crate) const EFI_UUID_FILE: &str = "efiuuid.cfg";
/// The EFI Linux directory
const EFI_LINUX: &str = "EFI/Linux";

/// Timeout for systemd-boot bootloader menu
const SYSTEMD_TIMEOUT: &str = "timeout 5";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This relates to coreos/bootupd#978 - I think the default for systems-boot should actually be set at install time.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. But, for now at least we keep it in bootc until we have support for systemd-boot in bootupd

const SYSTEMD_LOADER_CONF_PATH: &str = "loader/loader.conf";

/// We want to be able to control the ordering of UKIs so we put them in a directory that's not the
/// directory specified by the BLS spec. We do this because we want systemd-boot to only look at
/// our config files and not show the actual UKIs in the bootloader menu
/// This is relative to the ESP
const SYSTEMD_UKI_DIR: &str = "EFI/Linux/bootc";

pub(crate) enum BootSetupType<'a> {
/// For initial setup, i.e. install to-disk
Setup((&'a RootSetup, &'a State, &'a FileSystem<Sha256HashValue>)),
Expand Down Expand Up @@ -448,25 +459,35 @@ pub(crate) fn setup_composefs_bls_boot(
.with_title(title)
.with_sort_key(default_sort_key.into())
.with_version(version)
.with_linux(format!(
"/{}/{id_hex}/vmlinuz",
entry_paths.abs_entries_path
))
.with_initrd(vec![format!(
"/{}/{id_hex}/initrd",
entry_paths.abs_entries_path
)])
.with_options(cmdline_refs);
.with_cfg(BLSConfigType::NonEFI {
linux: format!("/{}/{id_hex}/vmlinuz", entry_paths.abs_entries_path).into(),
initrd: vec![
format!("/{}/{id_hex}/initrd", entry_paths.abs_entries_path).into()
],
options: Some(cmdline_refs),
});

match find_vmlinuz_initrd_duplicates(&boot_digest)? {
Some(symlink_to) => {
bls_config.linux =
format!("/{}/{symlink_to}/vmlinuz", entry_paths.abs_entries_path);

bls_config.initrd = vec![format!(
"/{}/{symlink_to}/initrd",
entry_paths.abs_entries_path
)];
match bls_config.cfg_type {
BLSConfigType::NonEFI {
ref mut linux,
ref mut initrd,
..
} => {
*linux =
format!("/{}/{symlink_to}/vmlinuz", entry_paths.abs_entries_path)
.into();

*initrd = vec![format!(
"/{}/{symlink_to}/initrd",
entry_paths.abs_entries_path
)
.into()];
}

_ => unreachable!(),
};
}

None => {
Expand All @@ -486,7 +507,9 @@ pub(crate) fn setup_composefs_bls_boot(
let loader_path = entry_paths.config_path.join("loader");

let (config_path, booted_bls) = if is_upgrade {
let mut booted_bls = get_booted_bls()?;
let boot_dir = Dir::open_ambient_dir(&entry_paths.config_path, ambient_authority())?;

let mut booted_bls = get_booted_bls(&boot_dir)?;
booted_bls.sort_key = Some("0".into()); // entries are sorted by their filename in reverse order

// This will be atomically renamed to 'loader/entries' on shutdown/reboot
Expand Down Expand Up @@ -530,11 +553,12 @@ pub(crate) fn setup_composefs_bls_boot(
fn write_pe_to_esp(
repo: &ComposefsRepository<Sha256HashValue>,
file: &RegularFile<Sha256HashValue>,
file_path: &PathBuf,
file_path: &Utf8Path,
pe_type: PEType,
uki_id: &String,
is_insecure_from_opts: bool,
mounted_efi: impl AsRef<Path>,
bootloader: &Bootloader,
) -> Result<Option<String>> {
let efi_bin = read_file(file, &repo).context("Reading .efi binary")?;

Expand Down Expand Up @@ -571,12 +595,16 @@ fn write_pe_to_esp(
}

// Write the UKI to ESP
let efi_linux_path = mounted_efi.as_ref().join(EFI_LINUX);
let efi_linux_path = mounted_efi.as_ref().join(match bootloader {
Bootloader::Grub => EFI_LINUX,
Bootloader::Systemd => SYSTEMD_UKI_DIR,
});

create_dir_all(&efi_linux_path).context("Creating EFI/Linux")?;

let final_pe_path = match file_path.parent() {
Some(parent) => {
let renamed_path = match parent.as_str()?.ends_with(EFI_ADDON_DIR_EXT) {
let renamed_path = match parent.as_str().ends_with(EFI_ADDON_DIR_EXT) {
true => {
let dir_name = format!("{}{}", uki_id, EFI_ADDON_DIR_EXT);

Expand All @@ -602,8 +630,12 @@ fn write_pe_to_esp(
.with_context(|| format!("Opening {final_pe_path:?}"))?;

let pe_name = match pe_type {
PEType::Uki => format!("{}{}", uki_id, EFI_EXT),
PEType::UkiAddon => format!("{}{}", uki_id, EFI_ADDON_FILE_EXT),
PEType::Uki => &format!("{}{}", uki_id, EFI_EXT),
PEType::UkiAddon => file_path
.components()
.last()
.ok_or_else(|| anyhow::anyhow!("Failed to get UKI Addon file name"))?
.as_str(),
};

pe_dir
Expand All @@ -624,7 +656,7 @@ fn write_pe_to_esp(
fn write_grub_uki_menuentry(
root_path: Utf8PathBuf,
setup_type: &BootSetupType,
boot_label: &String,
boot_label: String,
id: &Sha256HashValue,
esp_device: &String,
) -> Result<()> {
Expand Down Expand Up @@ -708,6 +740,76 @@ fn write_grub_uki_menuentry(
Ok(())
}

#[context("Writing systemd UKI config")]
fn write_systemd_uki_config(
esp_dir: &Dir,
setup_type: &BootSetupType,
boot_label: String,
id: &Sha256HashValue,
) -> Result<()> {
let default_sort_key = "0";

let mut bls_conf = BLSConfig::default();
bls_conf
.with_title(boot_label)
.with_cfg(BLSConfigType::EFI {
efi: format!("/{SYSTEMD_UKI_DIR}/{}{}", id.to_hex(), EFI_EXT).into(),
})
.with_sort_key(default_sort_key.into())
// TODO (Johan-Liebert1): Get version from UKI like we get boot label
.with_version(default_sort_key.into());

let (entries_dir, booted_bls) = match setup_type {
BootSetupType::Setup(..) => {
esp_dir
.create_dir_all(TYPE1_ENT_PATH)
.with_context(|| format!("Creating {TYPE1_ENT_PATH}"))?;

(esp_dir.open_dir(TYPE1_ENT_PATH)?, None)
}

BootSetupType::Upgrade(_) => {
esp_dir
.create_dir_all(TYPE1_ENT_PATH_STAGED)
.with_context(|| format!("Creating {TYPE1_ENT_PATH_STAGED}"))?;

let mut booted_bls = get_booted_bls(&esp_dir)?;
booted_bls.sort_key = Some("1".into());

(esp_dir.open_dir(TYPE1_ENT_PATH_STAGED)?, Some(booted_bls))
}
};

entries_dir
.atomic_write(
type1_entry_conf_file_name(default_sort_key),
bls_conf.to_string().as_bytes(),
)
.context("Writing conf file")?;

if let Some(booted_bls) = booted_bls {
entries_dir.atomic_write(
// SAFETY: We set sort_key above
type1_entry_conf_file_name(booted_bls.sort_key.as_ref().unwrap()),
booted_bls.to_string().as_bytes(),
)?;
}

// Write the timeout for bootloader menu if not exists
if !esp_dir.exists(SYSTEMD_LOADER_CONF_PATH) {
esp_dir
.atomic_write(SYSTEMD_LOADER_CONF_PATH, SYSTEMD_TIMEOUT)
.with_context(|| format!("Writing to {SYSTEMD_LOADER_CONF_PATH}"))?;
}

let esp_dir = esp_dir
.reopen_as_ownedfd()
.context("Reopening as owned fd")?;
rustix::fs::fsync(esp_dir).context("fsync")?;

Ok(())
}

#[context("Setting up UKI boot")]
pub(crate) fn setup_composefs_uki_boot(
setup_type: BootSetupType,
Expand All @@ -716,38 +818,31 @@ pub(crate) fn setup_composefs_uki_boot(
id: &Sha256HashValue,
entries: Vec<ComposefsBootEntry<Sha256HashValue>>,
) -> Result<()> {
let (root_path, esp_device, bootloader, is_insecure_from_opts) = match setup_type {
let (root_path, esp_device, bootloader, is_insecure_from_opts, uki_addons) = match setup_type {
BootSetupType::Setup((root_setup, state, ..)) => {
if let Some(v) = &state.config_opts.karg {
if v.len() > 0 {
tracing::warn!("kargs passed for UKI will be ignored");
}
}

let Some(cfs_opts) = &state.composefs_options else {
anyhow::bail!("ComposeFS options not found");
};

let esp_part = root_setup
.device_info
.partitions
.iter()
.find(|p| p.parttype.as_str() == ESP_GUID)
.ok_or_else(|| anyhow!("ESP partition not found"))?;

let bootloader = state
.composefs_options
.as_ref()
.map(|opts| opts.bootloader.clone())
.unwrap_or(Bootloader::default());

let is_insecure = state
.composefs_options
.as_ref()
.map(|x| x.insecure)
.unwrap_or(false);

(
root_setup.physical_root_path.clone(),
esp_part.node.clone(),
bootloader,
is_insecure,
cfs_opts.bootloader.clone(),
cfs_opts.insecure,
cfs_opts.uki_addon.as_ref(),
)
}

Expand All @@ -761,6 +856,7 @@ pub(crate) fn setup_composefs_uki_boot(
get_esp_partition(&sysroot_parent)?.0,
bootloader,
false,
None,
)
}
};
Expand All @@ -777,14 +873,42 @@ pub(crate) fn setup_composefs_uki_boot(
}

ComposefsBootEntry::Type2(entry) => {
// If --uki-addon is not passed, we don't install any addon
if matches!(entry.pe_type, PEType::UkiAddon) {
let Some(addons) = uki_addons else {
continue;
};

let addon_name = entry
.file_path
.components()
.last()
.ok_or_else(|| anyhow::anyhow!("Could not get UKI addon name"))?;

let addon_name = addon_name.as_str()?;

let addon_name =
addon_name.strip_suffix(EFI_ADDON_FILE_EXT).ok_or_else(|| {
anyhow::anyhow!("UKI addon doesn't end with {EFI_ADDON_DIR_EXT}")
})?;

if !addons.iter().any(|passed_addon| passed_addon == addon_name) {
continue;
}
}

let utf8_file_path = Utf8Path::from_path(&entry.file_path)
.ok_or_else(|| anyhow::anyhow!("Path is not valid UTf8"))?;

let ret = write_pe_to_esp(
&repo,
&entry.file,
&entry.file_path,
utf8_file_path,
entry.pe_type,
&id.to_hex(),
is_insecure_from_opts,
esp_mount.dir.path(),
&bootloader,
)?;

if let Some(label) = ret {
Expand All @@ -796,12 +920,11 @@ pub(crate) fn setup_composefs_uki_boot(

match bootloader {
Bootloader::Grub => {
write_grub_uki_menuentry(root_path, &setup_type, &boot_label, id, &esp_device)?
write_grub_uki_menuentry(root_path, &setup_type, boot_label, id, &esp_device)?
}

Bootloader::Systemd => {
// No-op for now, but later we want to have .conf files so we can control the order of
// entries.
write_systemd_uki_config(&esp_mount.fd, &setup_type, boot_label, id)?
}
};

Expand Down
7 changes: 6 additions & 1 deletion crates/lib/src/bootc_composefs/finalize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,12 @@ pub(crate) async fn composefs_native_finalize() -> Result<()> {
let entries_dir = esp_mount.fd.open_dir("loader")?;
rename_exchange_bls_entries(&entries_dir)?;
}
BootType::Uki => rename_staged_uki_entries(&esp_mount.fd)?,
BootType::Uki => {
rename_staged_uki_entries(&esp_mount.fd)?;

let entries_dir = esp_mount.fd.open_dir("loader")?;
rename_exchange_bls_entries(&entries_dir)?;
}
},
};

Expand Down
Loading