From 1291bbaadc049c840d5e125dbc510a518e49acd4 Mon Sep 17 00:00:00 2001 From: Ikey Doherty Date: Sun, 22 Dec 2024 10:07:27 +0000 Subject: [PATCH] blsforme: Rework APIs to reuse bootloader bits, add enumeration of $BOOT Signed-off-by: Ikey Doherty --- blsforme/src/bootloader/mod.rs | 26 +++-- blsforme/src/bootloader/systemd_boot/mod.rs | 107 +++++++++++++------- blsforme/src/manager.rs | 30 ++++-- 3 files changed, 113 insertions(+), 50 deletions(-) diff --git a/blsforme/src/bootloader/mod.rs b/blsforme/src/bootloader/mod.rs index 33860a9..91695ee 100644 --- a/blsforme/src/bootloader/mod.rs +++ b/blsforme/src/bootloader/mod.rs @@ -4,11 +4,11 @@ //! Bootloader APIs -use std::path::PathBuf; +use std::path::{PathBuf, StripPrefixError}; use thiserror::Error; -use crate::{manager::Mounts, Configuration, Entry, Firmware, Schema}; +use crate::{manager::Mounts, Entry, Firmware, Kernel, Schema}; pub mod systemd_boot; @@ -24,6 +24,9 @@ pub enum Error { #[error("io: {0}")] IO(#[from] std::io::Error), + #[error("wip: {0}")] + Prefix(#[from] StripPrefixError), + #[error("error: {0}")] Any(#[from] Box), } @@ -37,13 +40,15 @@ pub enum Bootloader<'a, 'b> { impl<'a, 'b> Bootloader<'a, 'b> { /// Construct the firmware-appropriate bootloader manager pub(crate) fn new( - config: &'a Configuration, + schema: &'a Schema<'a>, assets: &'b [PathBuf], mounts: &'a Mounts, firmware: &Firmware, - ) -> Self { + ) -> Result { match firmware { - Firmware::UEFI => Bootloader::Systemd(Box::new(systemd_boot::Loader::new(config, assets, mounts))), + Firmware::UEFI => Ok(Bootloader::Systemd(Box::new(systemd_boot::Loader::new( + schema, assets, mounts, + )?))), Firmware::BIOS => unimplemented!(), } } @@ -56,9 +61,16 @@ impl<'a, 'b> Bootloader<'a, 'b> { } /// Install a single kernel, create records for it. - pub fn install(&self, cmdline: &str, schema: &Schema, entry: &Entry) -> Result<(), Error> { + pub fn install(&self, cmdline: &str, entry: &Entry) -> Result<(), Error> { + match &self { + Bootloader::Systemd(s) => s.install(cmdline, entry), + } + } + + /// Grab the installed entries + pub fn installed_kernels(&self) -> Result, Error> { match &self { - Bootloader::Systemd(s) => s.install(cmdline, schema, entry), + Bootloader::Systemd(s) => s.installed_kernels(), } } } diff --git a/blsforme/src/bootloader/systemd_boot/mod.rs b/blsforme/src/bootloader/systemd_boot/mod.rs index 90ca7f9..446fac9 100644 --- a/blsforme/src/bootloader/systemd_boot/mod.rs +++ b/blsforme/src/bootloader/systemd_boot/mod.rs @@ -12,7 +12,7 @@ use std::{ use crate::{ file_utils::{changed_files, copy_atomic_vfat, PathExt}, manager::Mounts, - Configuration, Entry, Schema, + Entry, Kernel, Schema, }; pub mod interface; @@ -23,15 +23,39 @@ pub mod interface; pub struct Loader<'a, 'b> { /// system configuration #[allow(dead_code)] - config: &'a Configuration, assets: &'b [PathBuf], mounts: &'a Mounts, + + schema: &'a Schema<'a>, + kernel_dir: PathBuf, + boot_root: PathBuf, } impl<'a, 'b> Loader<'a, 'b> { /// Construct a new systemd boot loader manager - pub(super) fn new(config: &'a Configuration, assets: &'b [PathBuf], mounts: &'a Mounts) -> Self { - Self { config, assets, mounts } + pub(super) fn new(schema: &'a Schema<'a>, assets: &'b [PathBuf], mounts: &'a Mounts) -> Result { + let boot_root = if let Some(xbootldr) = mounts.xbootldr.as_ref() { + xbootldr.clone() + } else if let Some(esp) = mounts.esp.as_ref() { + esp.clone() + } else { + return Err(super::Error::MissingMount("ESP (/efi)")); + }; + + let kernel_dir = match schema { + Schema::Legacy { namespace, .. } => boot_root.join_insensitive("EFI").join_insensitive(namespace), + Schema::Blsforme { os_release } => boot_root + .join_insensitive("EFI") + .join_insensitive(os_release.id.clone()), + }; + + Ok(Self { + schema, + assets, + mounts, + kernel_dir, + boot_root, + }) } /// Sync bootloader to ESP (not XBOOTLDR..) @@ -72,33 +96,19 @@ impl<'a, 'b> Loader<'a, 'b> { } /// Install a kernel to the ESP or XBOOTLDR, write a config for it - pub(super) fn install(&self, cmdline: &str, schema: &Schema, entry: &Entry) -> Result<(), super::Error> { - let base = if let Some(xbootldr) = self.mounts.xbootldr.as_ref() { - xbootldr.clone() - } else if let Some(esp) = self.mounts.esp.as_ref() { - esp.clone() - } else { - return Err(super::Error::MissingMount("ESP (/efi)")); - }; - let loader_id = base + pub(super) fn install(&self, cmdline: &str, entry: &Entry) -> Result<(), super::Error> { + let loader_id = self + .boot_root .join_insensitive("loader") .join_insensitive("entries") - .join_insensitive(entry.id(schema)) + .join_insensitive(entry.id(self.schema)) .with_extension("conf"); log::trace!("writing entry: {}", loader_id.display()); - // Old schema used `com.*`, now we use `$id` from os-release - let asset_dir_base = match schema { - Schema::Legacy { namespace, .. } => namespace.to_string(), - Schema::Blsforme { os_release } => os_release.id.clone(), - }; - - let asset_dir = base.join_insensitive("EFI").join_insensitive(&asset_dir_base); - // vmlinuz primary path - let vmlinuz = asset_dir.join_insensitive( + let vmlinuz = self.kernel_dir.join_insensitive( entry - .installed_kernel_name(schema) + .installed_kernel_name(self.schema) .ok_or_else(|| super::Error::MissingFile("vmlinuz"))?, ); // initrds requiring install @@ -109,7 +119,8 @@ impl<'a, 'b> Loader<'a, 'b> { .filter_map(|asset| { Some(( asset.path.clone(), - asset_dir.join_insensitive(entry.installed_asset_name(schema, asset)?), + self.kernel_dir + .join_insensitive(entry.installed_asset_name(self.schema, asset)?), )) }) .collect::>(); @@ -129,10 +140,17 @@ impl<'a, 'b> Loader<'a, 'b> { copy_atomic_vfat(source, dest)?; } - let loader_config = self.generate_entry(&asset_dir_base, cmdline, schema, entry); + let loader_config = self.generate_entry( + self.kernel_dir + .strip_prefix(&self.boot_root)? + .to_string_lossy() + .as_ref(), + cmdline, + entry, + ); log::trace!("loader config: {loader_config}"); - let entry_dir = base.join_insensitive("loader").join_insensitive("entries"); + let entry_dir = self.boot_root.join_insensitive("loader").join_insensitive("entries"); if !entry_dir.exists() { create_dir_all(entry_dir)?; } @@ -144,7 +162,7 @@ impl<'a, 'b> Loader<'a, 'b> { } /// Generate a usable loader config entry - fn generate_entry(&self, asset_dir: &str, cmdline: &str, schema: &Schema, entry: &Entry) -> String { + fn generate_entry(&self, asset_dir: &str, cmdline: &str, entry: &Entry) -> String { let initrd = if entry.kernel.initrd.is_empty() { "\n".to_string() } else { @@ -154,19 +172,19 @@ impl<'a, 'b> Loader<'a, 'b> { .iter() .filter_map(|asset| { Some(format!( - "\ninitrd /EFI/{asset_dir}/{}", - entry.installed_asset_name(schema, asset)? + "\ninitrd /{asset_dir}/{}", + entry.installed_asset_name(self.schema, asset)? )) }) .collect::(); format!("\n{}", initrds) }; - let title = if let Some(pretty) = schema.os_release().meta.pretty_name.as_ref() { + let title = if let Some(pretty) = self.schema.os_release().meta.pretty_name.as_ref() { format!("{pretty} ({})", entry.kernel.version) } else { - format!("{} ({})", schema.os_release().name, entry.kernel.version) + format!("{} ({})", self.schema.os_release().name, entry.kernel.version) }; - let vmlinuz = entry.installed_kernel_name(schema).expect("linux go boom"); + let vmlinuz = entry.installed_kernel_name(self.schema).expect("linux go boom"); let options = if let Some(k_cmdline) = entry.kernel.cmdline.as_ref() { format!("{cmdline} {k_cmdline}") } else { @@ -174,10 +192,31 @@ impl<'a, 'b> Loader<'a, 'b> { }; format!( r###"title {title} -linux /EFI/{asset_dir}/{}{} +linux /{asset_dir}/{}{} options {} "###, vmlinuz, initrd, options ) } + + pub fn installed_kernels(&self) -> Result, super::Error> { + let mut all_paths = vec![]; + for entry in fs::read_dir(&self.kernel_dir)? { + let entry = entry?; + if !entry.file_type()?.is_dir() { + continue; + } + let paths = fs::read_dir(entry.path())? + .filter_map(|p| p.ok()) + .map(|d| d.path()) + .collect::>(); + all_paths.extend(paths); + } + + if let Ok(kernels) = self.schema.discover_system_kernels(all_paths.iter()) { + Ok(kernels) + } else { + Ok(vec![]) + } + } } diff --git a/blsforme/src/manager.rs b/blsforme/src/manager.rs index 5d1794e..8a466ba 100644 --- a/blsforme/src/manager.rs +++ b/blsforme/src/manager.rs @@ -13,7 +13,8 @@ use nix::mount::{mount, umount, MsFlags}; use topology::disk; use crate::{ - bootloader::Bootloader, file_utils::cmdline_snippet, BootEnvironment, Configuration, Entry, Error, Root, Schema, + bootloader::Bootloader, file_utils::cmdline_snippet, BootEnvironment, Configuration, Entry, Error, Kernel, Root, + Schema, }; #[derive(Debug)] @@ -200,6 +201,13 @@ impl<'a> Manager<'a> { Ok(mounted_paths) } + /// Discover installed kernels using the mount tokens + pub fn installed_kernels(&self, schema: &Schema, _tokens: &[ScopedMount]) -> Result, Error> { + let bootloader = self.bootloader(schema)?; + let results = bootloader.installed_kernels()?; + Ok(results) + } + /// Mount an fat filesystem #[inline] fn mount_vfat_partition(&self, source: &Path, target: &Path) -> Result { @@ -228,21 +236,25 @@ impl<'a> Manager<'a> { } } // Firstly, get the bootloader updated. - let bootloader = Bootloader::new( - self.config, - &self.bootloader_assets, - &self.mounts, - &self.boot_env.firmware, - ); - bootloader.sync()?; + let bootloader = self.bootloader(schema)?; // Install every kernel that was passed to us for entry in self.entries.iter() { - bootloader.install(&self.cmdline, schema, entry)?; + bootloader.install(&self.cmdline, entry)?; } Ok(()) } + + /// factory - create bootloader instance + fn bootloader(&'a self, schema: &'a Schema) -> Result, Error> { + Ok(Bootloader::new( + schema, + &self.bootloader_assets, + &self.mounts, + &self.boot_env.firmware, + )?) + } } /// Encapsulated mountpoint to ensure auto-unmount (Scoped)