Skip to content

Commit

Permalink
blsforme: Rework APIs to reuse bootloader bits, add enumeration of $BOOT
Browse files Browse the repository at this point in the history
Signed-off-by: Ikey Doherty <[email protected]>
  • Loading branch information
ikeycode committed Dec 22, 2024
1 parent f3935d4 commit 1291bba
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 50 deletions.
26 changes: 19 additions & 7 deletions blsforme/src/bootloader/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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<dyn std::error::Error + Send + Sync>),
}
Expand All @@ -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<Self, Error> {
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!(),
}
}
Expand All @@ -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<Vec<Kernel>, Error> {
match &self {
Bootloader::Systemd(s) => s.install(cmdline, schema, entry),
Bootloader::Systemd(s) => s.installed_kernels(),
}
}
}
107 changes: 73 additions & 34 deletions blsforme/src/bootloader/systemd_boot/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<Self, super::Error> {
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..)
Expand Down Expand Up @@ -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
Expand All @@ -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::<Vec<_>>();
Expand All @@ -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)?;
}
Expand All @@ -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 {
Expand All @@ -154,30 +172,51 @@ 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::<String>();
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 {
cmdline.to_string()
};
format!(
r###"title {title}
linux /EFI/{asset_dir}/{}{}
linux /{asset_dir}/{}{}
options {}
"###,
vmlinuz, initrd, options
)
}

pub fn installed_kernels(&self) -> Result<Vec<Kernel>, 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::<Vec<_>>();
all_paths.extend(paths);
}

if let Ok(kernels) = self.schema.discover_system_kernels(all_paths.iter()) {
Ok(kernels)
} else {
Ok(vec![])
}
}
}
30 changes: 21 additions & 9 deletions blsforme/src/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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<Vec<Kernel>, 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<ScopedMount, Error> {
Expand Down Expand Up @@ -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<Bootloader<'a, 'a>, Error> {
Ok(Bootloader::new(
schema,
&self.bootloader_assets,
&self.mounts,
&self.boot_env.firmware,
)?)
}
}

/// Encapsulated mountpoint to ensure auto-unmount (Scoped)
Expand Down

0 comments on commit 1291bba

Please sign in to comment.