Skip to content

Commit 9987b04

Browse files
composefs-native/boot: Handle systemd-boot and grub
Fix Grub boot error caused by bootc-dev#1541. Introduce a `--bootloader` cli option to `--composefs-native`. Depending upon the type of bootloader passed in we write BLS configs respectively Signed-off-by: Johan-Liebert1 <[email protected]>
1 parent fd703ec commit 9987b04

File tree

5 files changed

+172
-37
lines changed

5 files changed

+172
-37
lines changed

crates/lib/src/cli.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -955,14 +955,16 @@ async fn upgrade_composefs(_opts: UpgradeOpts) -> Result<()> {
955955
match boot_type {
956956
BootType::Bls => {
957957
boot_digest = Some(setup_composefs_bls_boot(
958-
BootSetupType::Upgrade(&fs),
958+
BootSetupType::Upgrade((&fs, &host)),
959959
repo,
960960
&id,
961961
entry,
962962
)?)
963963
}
964964

965-
BootType::Uki => setup_composefs_uki_boot(BootSetupType::Upgrade(&fs), repo, &id, entry)?,
965+
BootType::Uki => {
966+
setup_composefs_uki_boot(BootSetupType::Upgrade((&fs, &host)), repo, &id, entry)?
967+
}
966968
};
967969

968970
write_composefs_state(
@@ -1140,13 +1142,15 @@ async fn switch_composefs(opts: SwitchOpts) -> Result<()> {
11401142
match boot_type {
11411143
BootType::Bls => {
11421144
boot_digest = Some(setup_composefs_bls_boot(
1143-
BootSetupType::Upgrade(&fs),
1145+
BootSetupType::Upgrade((&fs, &host)),
11441146
repo,
11451147
&id,
11461148
entry,
11471149
)?)
11481150
}
1149-
BootType::Uki => setup_composefs_uki_boot(BootSetupType::Upgrade(&fs), repo, &id, entry)?,
1151+
BootType::Uki => {
1152+
setup_composefs_uki_boot(BootSetupType::Upgrade((&fs, &host)), repo, &id, entry)?
1153+
}
11501154
};
11511155

11521156
write_composefs_state(

crates/lib/src/composefs_consts.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ pub(crate) const ORIGIN_KEY_BOOT: &str = "boot";
1919
pub(crate) const ORIGIN_KEY_BOOT_TYPE: &str = "boot_type";
2020
/// Key to store the SHA256 sum of vmlinuz + initrd for a deployment
2121
pub(crate) const ORIGIN_KEY_BOOT_DIGEST: &str = "digest";
22+
/// Bootloader, Grub or Systemd
23+
pub(crate) const ORIGIN_KEY_BOOTLOADER: &str = "bootloader";
2224

2325
/// Filename for `loader/entries`
2426
pub(crate) const BOOT_LOADER_ENTRIES: &str = "entries";

crates/lib/src/install.rs

Lines changed: 114 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ use crate::lsm;
9494
use crate::parsers::bls_config::{parse_bls_config, BLSConfig};
9595
use crate::parsers::grub_menuconfig::MenuEntry;
9696
use crate::progress_jsonl::ProgressWriter;
97-
use crate::spec::ImageReference;
97+
use crate::spec::{Bootloader, Host, ImageReference};
9898
use crate::store::Storage;
9999
use crate::task::Task;
100100
use crate::utils::{path_relative_to, sigpolicy_from_opt};
@@ -311,6 +311,10 @@ pub(crate) struct InstallComposefsOpts {
311311
#[clap(long, default_value_t)]
312312
#[serde(default)]
313313
pub(crate) insecure: bool,
314+
315+
#[clap(long, default_value_t)]
316+
#[serde(default)]
317+
pub(crate) bootloader: Bootloader,
314318
}
315319

316320
#[cfg(feature = "install-to-disk")]
@@ -1581,7 +1585,7 @@ pub(crate) enum BootSetupType<'a> {
15811585
/// For initial setup, i.e. install to-disk
15821586
Setup((&'a RootSetup, &'a State, &'a FileSystem<Sha256HashValue>)),
15831587
/// For `bootc upgrade`
1584-
Upgrade(&'a FileSystem<Sha256HashValue>),
1588+
Upgrade((&'a FileSystem<Sha256HashValue>, &'a Host)),
15851589
}
15861590

15871591
/// Compute SHA256Sum of VMlinuz + Initrd
@@ -1707,6 +1711,18 @@ fn write_bls_boot_entries_to_disk(
17071711
Ok(())
17081712
}
17091713

1714+
struct BLSEntryPath<'a> {
1715+
/// Where to write vmlinuz/initrd
1716+
entries_path: Utf8PathBuf,
1717+
/// The absolute path, with reference to the partition's root, where the vmlinuz/initrd are written to
1718+
/// We need this as when installing, the mounted path will not
1719+
abs_entries_path: &'a str,
1720+
/// Where to write the .conf files
1721+
config_path: Utf8PathBuf,
1722+
/// If we mounted EFI, the target path
1723+
mount_path: Option<Utf8PathBuf>,
1724+
}
1725+
17101726
/// Sets up and writes BLS entries and binaries (VMLinuz + Initrd) to disk
17111727
///
17121728
/// # Returns
@@ -1721,7 +1737,7 @@ pub(crate) fn setup_composefs_bls_boot(
17211737
) -> Result<String> {
17221738
let id_hex = id.to_hex();
17231739

1724-
let (esp_device, cmdline_refs, fs) = match setup_type {
1740+
let (root_path, esp_device, cmdline_refs, fs, bootloader) = match setup_type {
17251741
BootSetupType::Setup((root_setup, state, fs)) => {
17261742
// root_setup.kargs has [root=UUID=<UUID>, "rw"]
17271743
let mut cmdline_options = String::from(root_setup.kargs.join(" "));
@@ -1743,10 +1759,20 @@ pub(crate) fn setup_composefs_bls_boot(
17431759
.find(|p| p.parttype.as_str() == ESP_GUID)
17441760
.ok_or_else(|| anyhow::anyhow!("ESP partition not found"))?;
17451761

1746-
(esp_part.node.clone(), cmdline_options, fs)
1762+
(
1763+
root_setup.physical_root_path.clone(),
1764+
esp_part.node.clone(),
1765+
cmdline_options,
1766+
fs,
1767+
state
1768+
.composefs_options
1769+
.as_ref()
1770+
.map(|opts| opts.bootloader.clone())
1771+
.unwrap_or(Bootloader::default()),
1772+
)
17471773
}
17481774

1749-
BootSetupType::Upgrade(fs) => {
1775+
BootSetupType::Upgrade((fs, host)) => {
17501776
let sysroot = Utf8PathBuf::from("/sysroot");
17511777

17521778
let fsinfo = inspect_filesystem(&sysroot)?;
@@ -1756,7 +1782,17 @@ pub(crate) fn setup_composefs_bls_boot(
17561782
anyhow::bail!("Could not find parent device for mountpoint /sysroot");
17571783
};
17581784

1785+
// SAFETY: All of these will exist as we have checks prior to Upgrading
1786+
let bootloader = host
1787+
.status
1788+
.booted
1789+
.as_ref()
1790+
.and_then(|b| b.composefs.as_ref())
1791+
.map(|c| c.bootloader.clone())
1792+
.unwrap();
1793+
17591794
(
1795+
Utf8PathBuf::from("/sysroot"),
17601796
get_esp_partition(&parent)?.0,
17611797
vec![
17621798
format!("root=UUID={DPS_UUID}"),
@@ -1765,24 +1801,45 @@ pub(crate) fn setup_composefs_bls_boot(
17651801
]
17661802
.join(" "),
17671803
fs,
1804+
bootloader,
17681805
)
17691806
}
17701807
};
17711808

1772-
let temp_efi_dir = tempfile::tempdir()
1773-
.map_err(|e| anyhow::anyhow!("Failed to create temporary directory for EFI mount: {e}"))?;
1774-
let mounted_efi = temp_efi_dir.path().to_path_buf();
1809+
let is_upgrade = matches!(setup_type, BootSetupType::Upgrade(..));
17751810

1776-
Command::new("mount")
1777-
.args([&PathBuf::from(&esp_device), &mounted_efi])
1778-
.log_debug()
1779-
.run_inherited_with_cmd_context()
1780-
.context("Mounting EFI")?;
1811+
let entry_paths = match bootloader {
1812+
Bootloader::Grub => BLSEntryPath {
1813+
entries_path: root_path.join("boot"),
1814+
config_path: root_path.join("boot"),
1815+
abs_entries_path: "boot",
1816+
mount_path: None,
1817+
},
17811818

1782-
let is_upgrade = matches!(setup_type, BootSetupType::Upgrade(..));
1819+
Bootloader::Systemd => {
1820+
let temp_efi_dir = tempfile::tempdir().map_err(|e| {
1821+
anyhow::anyhow!("Failed to create temporary directory for EFI mount: {e}")
1822+
})?;
1823+
1824+
let mounted_efi = Utf8PathBuf::from_path_buf(temp_efi_dir.path().to_path_buf())
1825+
.map_err(|_| anyhow::anyhow!("EFI dir is not valid UTF-8"))?;
17831826

1784-
let efi_dir = Utf8PathBuf::from_path_buf(mounted_efi.join(EFI_LINUX))
1785-
.map_err(|_| anyhow::anyhow!("EFI dir is not valid UTF-8"))?;
1827+
Command::new("mount")
1828+
.args([&PathBuf::from(&esp_device), mounted_efi.as_std_path()])
1829+
.log_debug()
1830+
.run_inherited_with_cmd_context()
1831+
.context("Mounting EFI")?;
1832+
1833+
let efi_linux_dir = mounted_efi.join(EFI_LINUX);
1834+
1835+
BLSEntryPath {
1836+
entries_path: efi_linux_dir,
1837+
config_path: mounted_efi.clone(),
1838+
abs_entries_path: EFI_LINUX,
1839+
mount_path: Some(mounted_efi),
1840+
}
1841+
}
1842+
};
17861843

17871844
let (bls_config, boot_digest) = match &entry {
17881845
ComposefsBootEntry::Type1(..) => unimplemented!(),
@@ -1831,44 +1888,66 @@ pub(crate) fn setup_composefs_bls_boot(
18311888
.with_title(id_hex.clone())
18321889
.with_sort_key(default_sort_key.into())
18331890
.with_version(version.unwrap_or(default_sort_key.into()))
1834-
.with_linux(format!("/{EFI_LINUX}/{id_hex}/vmlinuz"))
1835-
.with_initrd(vec![format!("/{EFI_LINUX}/{id_hex}/initrd")])
1891+
.with_linux(format!(
1892+
"/{}/{id_hex}/vmlinuz",
1893+
entry_paths.abs_entries_path
1894+
))
1895+
.with_initrd(vec![format!(
1896+
"/{}/{id_hex}/initrd",
1897+
entry_paths.abs_entries_path
1898+
)])
18361899
.with_options(cmdline_refs);
18371900

18381901
if let Some(symlink_to) = find_vmlinuz_initrd_duplicates(&boot_digest)? {
1839-
bls_config.linux = format!("/{EFI_LINUX}/{symlink_to}/vmlinuz");
1840-
bls_config.initrd = vec![format!("/{EFI_LINUX}/{symlink_to}/initrd")];
1902+
bls_config.linux =
1903+
format!("/{}/{symlink_to}/vmlinuz", entry_paths.abs_entries_path);
1904+
1905+
bls_config.initrd = vec![format!(
1906+
"/{}/{symlink_to}/initrd",
1907+
entry_paths.abs_entries_path
1908+
)];
18411909
} else {
1842-
write_bls_boot_entries_to_disk(&efi_dir, id, usr_lib_modules_vmlinuz, &repo)?;
1910+
write_bls_boot_entries_to_disk(
1911+
&entry_paths.entries_path,
1912+
id,
1913+
usr_lib_modules_vmlinuz,
1914+
&repo,
1915+
)?;
18431916
}
18441917

18451918
(bls_config, boot_digest)
18461919
}
18471920
};
18481921

1849-
let (entries_path, booted_bls) = if is_upgrade {
1922+
let (config_path, booted_bls) = if is_upgrade {
18501923
let mut booted_bls = get_booted_bls()?;
18511924
booted_bls.sort_key = Some("0".into()); // entries are sorted by their filename in reverse order
18521925

18531926
// This will be atomically renamed to 'loader/entries' on shutdown/reboot
18541927
(
1855-
mounted_efi.join(format!("loader/{STAGED_BOOT_LOADER_ENTRIES}")),
1928+
entry_paths
1929+
.config_path
1930+
.join("loader")
1931+
.join(STAGED_BOOT_LOADER_ENTRIES),
18561932
Some(booted_bls),
18571933
)
18581934
} else {
18591935
(
1860-
mounted_efi.join(format!("loader/{BOOT_LOADER_ENTRIES}")),
1936+
entry_paths
1937+
.config_path
1938+
.join("loader")
1939+
.join(BOOT_LOADER_ENTRIES),
18611940
None,
18621941
)
18631942
};
18641943

1865-
create_dir_all(&entries_path).with_context(|| format!("Creating {:?}", entries_path))?;
1944+
create_dir_all(&config_path).with_context(|| format!("Creating {:?}", config_path))?;
18661945

18671946
// Scope to allow for proper unmounting
18681947
{
18691948
let loader_entries_dir =
1870-
cap_std::fs::Dir::open_ambient_dir(&entries_path, cap_std::ambient_authority())
1871-
.with_context(|| format!("Opening {entries_path:?}"))?;
1949+
cap_std::fs::Dir::open_ambient_dir(&config_path, cap_std::ambient_authority())
1950+
.with_context(|| format!("Opening {config_path:?}"))?;
18721951

18731952
loader_entries_dir.atomic_write(
18741953
// SAFETY: We set sort_key above
@@ -1893,14 +1972,17 @@ pub(crate) fn setup_composefs_bls_boot(
18931972
let owned_loader_entries_fd = loader_entries_dir
18941973
.reopen_as_ownedfd()
18951974
.context("Reopening as owned fd")?;
1975+
18961976
rustix::fs::fsync(owned_loader_entries_fd).context("fsync")?;
18971977
}
18981978

1899-
Command::new("umount")
1900-
.arg(&mounted_efi)
1901-
.log_debug()
1902-
.run_inherited_with_cmd_context()
1903-
.context("Unmounting EFI")?;
1979+
if let Some(mounted_efi) = entry_paths.mount_path {
1980+
Command::new("umount")
1981+
.arg(mounted_efi)
1982+
.log_debug()
1983+
.run_inherited_with_cmd_context()
1984+
.context("Unmounting EFI")?;
1985+
}
19041986

19051987
Ok(boot_digest)
19061988
}

crates/lib/src/spec.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! The definition for host system state.
22
33
use std::fmt::Display;
4+
use std::str::FromStr;
45

56
use anyhow::Result;
67
use ostree_ext::container::Transport;
@@ -161,6 +162,39 @@ pub struct BootEntryOstree {
161162
pub deploy_serial: u32,
162163
}
163164

165+
/// Bootloader type to determine whether system was booted via Grub or Systemd
166+
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
167+
pub enum Bootloader {
168+
/// Booted via Grub
169+
#[default]
170+
Grub,
171+
/// Booted via Systemd
172+
Systemd,
173+
}
174+
175+
impl Display for Bootloader {
176+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
177+
let string = match self {
178+
Bootloader::Grub => "grub",
179+
Bootloader::Systemd => "systemd",
180+
};
181+
182+
write!(f, "{}", string)
183+
}
184+
}
185+
186+
impl FromStr for Bootloader {
187+
type Err = anyhow::Error;
188+
189+
fn from_str(value: &str) -> Result<Self> {
190+
match value {
191+
"grub" => Ok(Self::Grub),
192+
"systemd" => Ok(Self::Systemd),
193+
unrecognized => Err(anyhow::anyhow!("Unrecognized bootloader: '{unrecognized}'")),
194+
}
195+
}
196+
}
197+
164198
/// A bootable entry
165199
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
166200
#[serde(rename_all = "camelCase")]
@@ -169,6 +203,8 @@ pub struct BootEntryComposefs {
169203
pub verity: String,
170204
/// Whether this deployment is to be booted via BLS or UKI
171205
pub boot_type: BootType,
206+
/// Whether we boot using systemd or grub
207+
pub bootloader: Bootloader,
172208
}
173209

174210
/// A bootable entry

crates/lib/src/status.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,15 @@ use ostree_ext::ostree;
2929
use tokio::io::AsyncReadExt;
3030

3131
use crate::cli::OutputFormat;
32+
use crate::composefs_consts::ORIGIN_KEY_BOOTLOADER;
3233
use crate::composefs_consts::{
3334
COMPOSEFS_CMDLINE, COMPOSEFS_STAGED_DEPLOYMENT_FNAME, COMPOSEFS_TRANSIENT_STATE_DIR,
3435
ORIGIN_KEY_BOOT, ORIGIN_KEY_BOOT_TYPE, STATE_DIR_RELATIVE,
3536
};
3637
use crate::deploy::get_sorted_bls_boot_entries;
3738
use crate::deploy::get_sorted_uki_boot_entries;
3839
use crate::install::BootType;
40+
use crate::spec::Bootloader;
3941
use crate::spec::ImageStatus;
4042
use crate::spec::{BootEntry, BootOrder, Host, HostSpec, HostStatus, HostType};
4143
use crate::spec::{ImageReference, ImageSignature};
@@ -473,14 +475,23 @@ async fn boot_entry_from_composefs_deployment(
473475
None => anyhow::bail!("{ORIGIN_KEY_BOOT} not found"),
474476
};
475477

478+
let bootloader = match origin.get::<String>(ORIGIN_KEY_BOOT, ORIGIN_KEY_BOOTLOADER) {
479+
Some(s) => Bootloader::from_str(s.as_str())?,
480+
None => anyhow::bail!("{ORIGIN_KEY_BOOTLOADER} not found"),
481+
};
482+
476483
let e = BootEntry {
477484
image,
478485
cached_update: None,
479486
incompatible: false,
480487
pinned: false,
481488
store: None,
482489
ostree: None,
483-
composefs: Some(crate::spec::BootEntryComposefs { verity, boot_type }),
490+
composefs: Some(crate::spec::BootEntryComposefs {
491+
verity,
492+
boot_type,
493+
bootloader,
494+
}),
484495
soft_reboot_capable: false,
485496
};
486497

0 commit comments

Comments
 (0)