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

use anyhow::{anyhow, Context, Result};
use bootc_blockdev::find_parent_devices;
use bootc_mount::inspect_filesystem;
use bootc_utils::CommandRunExt;
use bootc_mount::tempmount::TempMount;
use camino::{Utf8Path, Utf8PathBuf};
use cap_std_ext::{
cap_std::{ambient_authority, fs::Dir},
Expand Down Expand Up @@ -44,7 +44,7 @@ use crate::{
BOOT_LOADER_ENTRIES, COMPOSEFS_CMDLINE, ORIGIN_KEY_BOOT, ORIGIN_KEY_BOOT_DIGEST,
STAGED_BOOT_LOADER_ENTRIES, STATE_DIR_ABS, USER_CFG, USER_CFG_STAGED,
},
install::{DPS_UUID, ESP_GUID, RW_KARG},
install::{dps_uuid::DPS_UUID, ESP_GUID, RW_KARG},
spec::{Bootloader, Host},
};

Expand Down Expand Up @@ -142,6 +142,10 @@ pub fn get_sysroot_parent_dev() -> Result<String> {
return Ok(parent);
}

pub fn type1_entry_conf_file_name(sort_key: impl std::fmt::Display) -> String {
format!("bootc-composefs-{sort_key}.conf")
}

/// Compute SHA256Sum of VMlinuz + Initrd
///
/// # Arguments
Expand Down Expand Up @@ -264,6 +268,51 @@ fn write_bls_boot_entries_to_disk(
Ok(())
}

/// Parses /usr/lib/os-release and returns title and version fields
/// # Returns
/// - (title, version)
fn osrel_title_and_version(
fs: &FileSystem<Sha256HashValue>,
repo: &ComposefsRepository<Sha256HashValue>,
) -> Result<Option<(Option<String>, Option<String>)>> {
// Every update should have its own /usr/lib/os-release
let (dir, fname) = fs
.root
.split(OsStr::new("/usr/lib/os-release"))
.context("Getting /usr/lib/os-release")?;

let os_release = dir
.get_file_opt(fname)
.context("Getting /usr/lib/os-release")?;

let Some(os_rel_file) = os_release else {
return Ok(None);
};

let file_contents = match read_file(os_rel_file, repo) {
Ok(c) => c,
Err(e) => {
tracing::warn!("Could not read /usr/lib/os-release: {e:?}");
return Ok(None);
}
};

let file_contents = match std::str::from_utf8(&file_contents) {
Ok(c) => c,
Err(e) => {
tracing::warn!("/usr/lib/os-release did not have valid UTF-8: {e}");
return Ok(None);
}
};

let parsed_contents = OsReleaseInfo::parse(file_contents);

let title = parsed_contents.get_pretty_name();
let version = parsed_contents.get_version();

Ok(Some((title, version)))
}

struct BLSEntryPath<'a> {
/// Where to write vmlinuz/initrd
entries_path: Utf8PathBuf,
Expand All @@ -272,8 +321,6 @@ struct BLSEntryPath<'a> {
abs_entries_path: &'a str,
/// Where to write the .conf files
config_path: Utf8PathBuf,
/// If we mounted EFI, the target path
mount_path: Option<Utf8PathBuf>,
}

/// Sets up and writes BLS entries and binaries (VMLinuz + Initrd) to disk
Expand Down Expand Up @@ -352,85 +399,55 @@ pub(crate) fn setup_composefs_bls_boot(
entries_path: root_path.join("boot"),
config_path: root_path.join("boot"),
abs_entries_path: "boot",
mount_path: None,
},
None,
),

Bootloader::Systemd => {
let temp_efi_dir = tempfile::tempdir().map_err(|e| {
anyhow::anyhow!("Failed to create temporary directory for EFI mount: {e}")
})?;

let mounted_efi = Utf8PathBuf::from_path_buf(temp_efi_dir.path().to_path_buf())
.map_err(|_| anyhow::anyhow!("EFI dir is not valid UTF-8"))?;

Command::new("mount")
.args([&PathBuf::from(&esp_device), mounted_efi.as_std_path()])
.log_debug()
.run_inherited_with_cmd_context()
.context("Mounting EFI")?;
let efi_mount = TempMount::mount_dev(&esp_device).context("Mounting ESP")?;

let mounted_efi = Utf8PathBuf::from(efi_mount.dir.path().as_str()?);
let efi_linux_dir = mounted_efi.join(EFI_LINUX);

(
BLSEntryPath {
entries_path: efi_linux_dir,
config_path: mounted_efi.clone(),
abs_entries_path: EFI_LINUX,
mount_path: Some(mounted_efi),
},
Some(temp_efi_dir),
Some(efi_mount),
)
}
};

let (bls_config, boot_digest) = match &entry {
ComposefsBootEntry::Type1(..) => unimplemented!(),
ComposefsBootEntry::Type2(..) => unimplemented!(),
ComposefsBootEntry::Type1(..) => anyhow::bail!("Found Type1 entries in /boot"),
ComposefsBootEntry::Type2(..) => anyhow::bail!("Found UKI"),

ComposefsBootEntry::UsrLibModulesVmLinuz(usr_lib_modules_vmlinuz) => {
let boot_digest = compute_boot_digest(usr_lib_modules_vmlinuz, &repo)
.context("Computing boot digest")?;

// Every update should have its own /usr/lib/os-release
let (dir, fname) = fs
.root
.split(OsStr::new("/usr/lib/os-release"))
.context("Getting /usr/lib/os-release")?;

let os_release = dir
.get_file_opt(fname)
.context("Getting /usr/lib/os-release")?;

let version = os_release.and_then(|os_rel_file| {
let file_contents = match read_file(os_rel_file, &repo) {
Ok(c) => c,
Err(e) => {
tracing::warn!("Could not read /usr/lib/os-release: {e:?}");
return None;
}
};

let file_contents = match std::str::from_utf8(&file_contents) {
Ok(c) => c,
Err(..) => {
tracing::warn!("/usr/lib/os-release did not have valid UTF-8");
return None;
}
};

OsReleaseInfo::parse(file_contents).get_version()
});

let default_sort_key = "1";
let default_title_version = (id.to_hex(), default_sort_key.to_string());

let osrel_res = osrel_title_and_version(fs, &repo)?;

let (title, version) = match osrel_res {
Some((t, v)) => (
t.unwrap_or(default_title_version.0),
v.unwrap_or(default_title_version.1),
),

None => default_title_version,
};
Comment thread
Johan-Liebert1 marked this conversation as resolved.

let mut bls_config = BLSConfig::default();

bls_config
.with_title(id_hex.clone())
.with_title(title)
.with_sort_key(default_sort_key.into())
.with_version(version.unwrap_or(default_sort_key.into()))
.with_version(version)
.with_linux(format!(
"/{}/{id_hex}/vmlinuz",
entry_paths.abs_entries_path
Expand All @@ -441,90 +458,70 @@ pub(crate) fn setup_composefs_bls_boot(
)])
.with_options(cmdline_refs);

if let Some(symlink_to) = find_vmlinuz_initrd_duplicates(&boot_digest)? {
bls_config.linux =
format!("/{}/{symlink_to}/vmlinuz", entry_paths.abs_entries_path);
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
)];
} else {
write_bls_boot_entries_to_disk(
&entry_paths.entries_path,
id,
usr_lib_modules_vmlinuz,
&repo,
)?;
}
bls_config.initrd = vec![format!(
"/{}/{symlink_to}/initrd",
entry_paths.abs_entries_path
)];
}

None => {
write_bls_boot_entries_to_disk(
&entry_paths.entries_path,
id,
usr_lib_modules_vmlinuz,
&repo,
)?;
}
};

(bls_config, boot_digest)
}
};

let loader_path = entry_paths.config_path.join("loader");

let (config_path, booted_bls) = if is_upgrade {
let mut booted_bls = get_booted_bls()?;
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
(
entry_paths
.config_path
.join("loader")
.join(STAGED_BOOT_LOADER_ENTRIES),
loader_path.join(STAGED_BOOT_LOADER_ENTRIES),
Some(booted_bls),
)
} else {
(
entry_paths
.config_path
.join("loader")
.join(BOOT_LOADER_ENTRIES),
None,
)
(loader_path.join(BOOT_LOADER_ENTRIES), None)
};

create_dir_all(&config_path).with_context(|| format!("Creating {:?}", config_path))?;

// Scope to allow for proper unmounting
{
let loader_entries_dir = Dir::open_ambient_dir(&config_path, ambient_authority())
.with_context(|| format!("Opening {config_path:?}"))?;
let loader_entries_dir = Dir::open_ambient_dir(&config_path, ambient_authority())
.with_context(|| format!("Opening {config_path:?}"))?;

loader_entries_dir.atomic_write(
// SAFETY: We set sort_key above
type1_entry_conf_file_name(bls_config.sort_key.as_ref().unwrap()),
bls_config.to_string().as_bytes(),
)?;

if let Some(booted_bls) = booted_bls {
loader_entries_dir.atomic_write(
// SAFETY: We set sort_key above
format!(
"bootc-composefs-{}.conf",
bls_config.sort_key.as_ref().unwrap()
),
bls_config.to_string().as_bytes(),
type1_entry_conf_file_name(booted_bls.sort_key.as_ref().unwrap()),
booted_bls.to_string().as_bytes(),
)?;

if let Some(booted_bls) = booted_bls {
loader_entries_dir.atomic_write(
// SAFETY: We set sort_key above
format!(
"bootc-composefs-{}.conf",
booted_bls.sort_key.as_ref().unwrap()
),
booted_bls.to_string().as_bytes(),
)?;
}

let owned_loader_entries_fd = loader_entries_dir
.reopen_as_ownedfd()
.context("Reopening as owned fd")?;

rustix::fs::fsync(owned_loader_entries_fd).context("fsync")?;
}

if let Some(mounted_efi) = entry_paths.mount_path {
Command::new("umount")
.arg(mounted_efi)
.log_debug()
.run_inherited_with_cmd_context()
.context("Unmounting EFI")?;
}
let owned_loader_entries_fd = loader_entries_dir
.reopen_as_ownedfd()
.context("Reopening as owned fd")?;

rustix::fs::fsync(owned_loader_entries_fd).context("fsync")?;

Ok(boot_digest)
}
Expand All @@ -537,7 +534,7 @@ fn write_pe_to_esp(
pe_type: PEType,
uki_id: &String,
is_insecure_from_opts: bool,
mounted_efi: &PathBuf,
mounted_efi: impl AsRef<Path>,
) -> Result<Option<String>> {
let efi_bin = read_file(file, &repo).context("Reading .efi binary")?;

Expand Down Expand Up @@ -574,7 +571,7 @@ fn write_pe_to_esp(
}

// Write the UKI to ESP
let efi_linux_path = mounted_efi.join(EFI_LINUX);
let efi_linux_path = mounted_efi.as_ref().join(EFI_LINUX);
create_dir_all(&efi_linux_path).context("Creating EFI/Linux")?;

let final_pe_path = match file_path.parent() {
Expand Down Expand Up @@ -768,13 +765,7 @@ pub(crate) fn setup_composefs_uki_boot(
}
};

let temp_efi_dir = tempfile::tempdir()
.map_err(|e| anyhow::anyhow!("Failed to create temporary directory for EFI mount: {e}"))?;
let mounted_efi = temp_efi_dir.path().to_path_buf();

Task::new("Mounting ESP", "mount")
.args([&PathBuf::from(&esp_device), &mounted_efi.clone()])
.run()?;
let esp_mount = TempMount::mount_dev(&esp_device).context("Mounting ESP")?;

let mut boot_label = String::new();

Expand All @@ -793,7 +784,7 @@ pub(crate) fn setup_composefs_uki_boot(
entry.pe_type,
&id.to_hex(),
is_insecure_from_opts,
&mounted_efi,
esp_mount.dir.path(),
)?;

if let Some(label) = ret {
Expand All @@ -803,12 +794,6 @@ pub(crate) fn setup_composefs_uki_boot(
};
}

Command::new("umount")
.arg(&mounted_efi)
.log_debug()
.run_inherited_with_cmd_context()
.context("Unmounting ESP")?;

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