Skip to content

Commit 30a66a4

Browse files
composefs-backend: Add support for systemd-boot uki
We did not have config files for systemd-boot and were only using UKIs which did not allow proper sorting of the UKIs. This adds .efi config files to `$ESP/loader/entries` Also, preserves UKI addons' names so we don't overwrite previously added addon Signed-off-by: Pragyan Poudyal <[email protected]>
1 parent 3bbd4cf commit 30a66a4

File tree

2 files changed

+92
-8
lines changed

2 files changed

+92
-8
lines changed

crates/lib/src/bootc_composefs/boot.rs

Lines changed: 87 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use cap_std_ext::{
1515
use clap::ValueEnum;
1616
use composefs::fs::read_file;
1717
use composefs::tree::{FileSystem, RegularFile};
18-
use composefs_boot::bootloader::{PEType, EFI_ADDON_DIR_EXT, EFI_ADDON_FILE_EXT, EFI_EXT};
18+
use composefs_boot::bootloader::{PEType, EFI_ADDON_DIR_EXT, EFI_EXT};
1919
use composefs_boot::BootOps;
2020
use fn_error_context::context;
2121
use ostree_ext::composefs::{
@@ -35,6 +35,7 @@ use serde::{Deserialize, Serialize};
3535
use crate::bootc_composefs::repo::open_composefs_repo;
3636
use crate::bootc_composefs::state::{get_booted_bls, write_composefs_state};
3737
use crate::bootc_composefs::status::get_sorted_uki_boot_entries;
38+
use crate::composefs_consts::{TYPE1_ENT_PATH, TYPE1_ENT_PATH_STAGED};
3839
use crate::parsers::bls_config::{BLSConfig, BLSConfigType};
3940
use crate::parsers::grub_menuconfig::MenuEntry;
4041
use crate::spec::ImageReference;
@@ -55,6 +56,16 @@ pub(crate) const EFI_UUID_FILE: &str = "efiuuid.cfg";
5556
/// The EFI Linux directory
5657
const EFI_LINUX: &str = "EFI/Linux";
5758

59+
/// Timeout for systemd-boot bootloader menu
60+
const SYSTEMD_TIMEOUT: &str = "timeout 5";
61+
const SYSTEMD_LOADER_CONF_PATH: &str = "loader/loader.conf";
62+
63+
/// We want to be able to control the ordering of UKIs so we put them in a directory that's not the
64+
/// directory specified by the BLS spec. We do this because we want systemd-boot to only look at
65+
/// our config files and not show the actual UKIs in the bootloader menu
66+
/// This is relative to the ESP
67+
const SYSTEMD_UKI_DIR: &str = "EFI/Linux/uki";
68+
5869
pub(crate) enum BootSetupType<'a> {
5970
/// For initial setup, i.e. install to-disk
6071
Setup((&'a RootSetup, &'a State, &'a FileSystem<Sha256HashValue>)),
@@ -143,7 +154,7 @@ pub fn get_sysroot_parent_dev() -> Result<String> {
143154
}
144155

145156
pub fn type1_entry_conf_file_name(sort_key: impl AsRef<str> + std::fmt::Display) -> String {
146-
format!("bootc-composefs-{sort_key}.conf")
157+
format!("bootc-composefs-{sort_key}.conf",)
147158
}
148159

149160
/// Compute SHA256Sum of VMlinuz + Initrd
@@ -541,6 +552,7 @@ fn write_pe_to_esp(
541552
uki_id: &String,
542553
is_insecure_from_opts: bool,
543554
mounted_efi: impl AsRef<Path>,
555+
bootloader: &Bootloader,
544556
) -> Result<Option<String>> {
545557
let efi_bin = read_file(file, &repo).context("Reading .efi binary")?;
546558

@@ -577,7 +589,11 @@ fn write_pe_to_esp(
577589
}
578590

579591
// Write the UKI to ESP
580-
let efi_linux_path = mounted_efi.as_ref().join(EFI_LINUX);
592+
let efi_linux_path = mounted_efi.as_ref().join(match bootloader {
593+
Bootloader::Grub => EFI_LINUX,
594+
Bootloader::Systemd => SYSTEMD_UKI_DIR,
595+
});
596+
581597
create_dir_all(&efi_linux_path).context("Creating EFI/Linux")?;
582598

583599
let final_pe_path = match file_path.parent() {
@@ -609,7 +625,12 @@ fn write_pe_to_esp(
609625

610626
let pe_name = match pe_type {
611627
PEType::Uki => format!("{}{}", uki_id, EFI_EXT),
612-
PEType::UkiAddon => format!("{}{}", uki_id, EFI_ADDON_FILE_EXT),
628+
PEType::UkiAddon => file_path
629+
.components()
630+
.last()
631+
.unwrap()
632+
.to_string_lossy()
633+
.to_string(),
613634
};
614635

615636
pe_dir
@@ -630,7 +651,7 @@ fn write_pe_to_esp(
630651
fn write_grub_uki_menuentry(
631652
root_path: Utf8PathBuf,
632653
setup_type: &BootSetupType,
633-
boot_label: &String,
654+
boot_label: String,
634655
id: &Sha256HashValue,
635656
esp_device: &String,
636657
) -> Result<()> {
@@ -714,6 +735,64 @@ fn write_grub_uki_menuentry(
714735
Ok(())
715736
}
716737

738+
#[context("Writing systemd UKI config")]
739+
fn write_systemd_uki_config(
740+
esp_dir: &Dir,
741+
setup_type: &BootSetupType,
742+
boot_label: String,
743+
id: &Sha256HashValue,
744+
) -> Result<()> {
745+
let default_sort_key = "0";
746+
747+
let mut bls_conf = BLSConfig::default();
748+
bls_conf
749+
.with_title(boot_label)
750+
.with_cfg(BLSConfigType::EFI {
751+
efi: format!("/{SYSTEMD_UKI_DIR}/{}{}", id.to_hex(), EFI_EXT),
752+
})
753+
.with_sort_key(default_sort_key.into())
754+
.with_version(default_sort_key.into());
755+
756+
let entries_dir = match setup_type {
757+
BootSetupType::Setup(..) => {
758+
esp_dir
759+
.create_dir_all(TYPE1_ENT_PATH)
760+
.with_context(|| format!("Creating {TYPE1_ENT_PATH}"))?;
761+
762+
esp_dir.open_dir(TYPE1_ENT_PATH)?
763+
}
764+
765+
BootSetupType::Upgrade(_) => {
766+
esp_dir
767+
.create_dir_all(TYPE1_ENT_PATH_STAGED)
768+
.with_context(|| format!("Creating {TYPE1_ENT_PATH_STAGED}"))?;
769+
770+
esp_dir.open_dir(TYPE1_ENT_PATH_STAGED)?
771+
}
772+
};
773+
774+
entries_dir
775+
.atomic_write(
776+
type1_entry_conf_file_name(default_sort_key),
777+
bls_conf.to_string().as_bytes(),
778+
)
779+
.context("Writing conf file")?;
780+
781+
// Write the timeout for bootloader menu if not exists
782+
if !esp_dir.exists(SYSTEMD_LOADER_CONF_PATH) {
783+
esp_dir
784+
.atomic_write(SYSTEMD_LOADER_CONF_PATH, SYSTEMD_TIMEOUT)
785+
.with_context(|| format!("Writing to {SYSTEMD_LOADER_CONF_PATH}"))?;
786+
}
787+
788+
let esp_dir = esp_dir
789+
.reopen_as_ownedfd()
790+
.context("Reopening as owned fd")?;
791+
rustix::fs::fsync(esp_dir).context("fsync")?;
792+
793+
Ok(())
794+
}
795+
717796
#[context("Setting up UKI boot")]
718797
pub(crate) fn setup_composefs_uki_boot(
719798
setup_type: BootSetupType,
@@ -791,6 +870,7 @@ pub(crate) fn setup_composefs_uki_boot(
791870
&id.to_hex(),
792871
is_insecure_from_opts,
793872
esp_mount.dir.path(),
873+
&bootloader,
794874
)?;
795875

796876
if let Some(label) = ret {
@@ -802,12 +882,11 @@ pub(crate) fn setup_composefs_uki_boot(
802882

803883
match bootloader {
804884
Bootloader::Grub => {
805-
write_grub_uki_menuentry(root_path, &setup_type, &boot_label, id, &esp_device)?
885+
write_grub_uki_menuentry(root_path, &setup_type, boot_label, id, &esp_device)?
806886
}
807887

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

crates/lib/src/composefs_consts.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,8 @@ pub(crate) const STAGED_BOOT_LOADER_ENTRIES: &str = "entries.staged";
3131
pub(crate) const USER_CFG: &str = "user.cfg";
3232
/// Filename for staged grub user config
3333
pub(crate) const USER_CFG_STAGED: &str = "user.cfg.staged";
34+
35+
/// Path to the config files directory for Type1 boot entries
36+
/// This is relative to the boot/efi directory
37+
pub(crate) const TYPE1_ENT_PATH: &str = "loader/entries";
38+
pub(crate) const TYPE1_ENT_PATH_STAGED: &str = "loader/entries.staged";

0 commit comments

Comments
 (0)