Skip to content
Open
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
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions crates/blockdev/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ bootc-mount = { package = "bootc-internal-mount", path = "../mount", version = "

# Workspace dependencies
anyhow = { workspace = true }
cfg-if = { workspace = true }
camino = { workspace = true, features = ["serde1"] }
cap-std-ext = { workspace = true, features = ["fs_utf8"] }
fn-error-context = { workspace = true }
Expand All @@ -26,6 +27,7 @@ tracing = { workspace = true }

[dev-dependencies]
indoc = { workspace = true }
regex = { workspace = true }

[lib]
path = "src/blockdev.rs"
57 changes: 51 additions & 6 deletions crates/blockdev/src/blockdev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ use serde::Deserialize;

use bootc_utils::CommandRunExt;

pub mod dps;
use dps::{BIOS_BOOT, ESP, XBOOTLDR};

/// Check whether the udev database is accessible (cached for the process lifetime).
///
/// When running inside a container or sandbox without `/run/udev`
Expand Down Expand Up @@ -76,12 +79,6 @@ fn blkid_probe(dev: &str) -> Result<HashMap<String, String>> {
/// Refer to <https://en.wikipedia.org/wiki/Partition_type>
pub const ESP_ID_MBR: &[u8] = &[0x06, 0xEF];

/// EFI System Partition (ESP) for UEFI boot on GPT
pub const ESP: &str = "c12a7328-f81f-11d2-ba4b-00a0c93ec93b";

/// BIOS boot partition type GUID for GPT
pub const BIOS_BOOT: &str = "21686148-6449-6e6f-744e-656564454649";

#[derive(Debug, Deserialize)]
struct DevicesOutput {
blockdevices: Vec<Device>,
Expand Down Expand Up @@ -240,6 +237,27 @@ impl Device {
})
}

/// Find a VFAT Extended Boot Loader Partition (XBOOTLDR) among children.
///
/// Other filesystems are skipped: an XBOOTLDR partition holding UKIs and
/// boot entries for systemd-boot must be readable by the firmware.
pub fn find_partition_of_xbootldr(&self) -> Option<&Device> {
self.find_partition_of_type(XBOOTLDR)
.filter(|part| part.fstype.as_deref() == Some("vfat"))
}

/// Find a VFAT Extended Boot Loader Partition (XBOOTLDR) on the same disk
/// as this partition, typically the ESP.
pub fn find_sibling_xbootldr(&self) -> Result<Option<Device>> {
for parent in self.list_parents()?.unwrap_or_default() {
let parent = list_dev(Utf8Path::new(&parent.path()))?;
if let Some(xbootldr) = parent.find_partition_of_xbootldr() {
return Ok(Some(xbootldr.clone()));
}
}
Ok(None)
}

/// Find the EFI System Partition (ESP) among children.
///
/// For GPT disks, this matches by the ESP partition type GUID.
Expand Down Expand Up @@ -1051,6 +1069,33 @@ mod test {
assert!(dev.find_partition_of_esp().is_err());
}

#[test]
fn test_find_partition_of_xbootldr() {
let cases = [
(XBOOTLDR, Some("vfat"), Some(2)),
(XBOOTLDR, Some("ext4"), None),
(XBOOTLDR, None, None),
(ESP, Some("vfat"), None),
];
for (parttype, fstype, expected) in cases {
let disk: Device = serde_json::from_value(serde_json::json!({
"name": "vda",
"size": 10737418240_u64,
"pttype": "gpt",
"children": [
{"name": "vda1", "size": 104857600, "partn": 1, "parttype": ESP, "fstype": "vfat"},
{"name": "vda2", "size": 2147483648_u64, "partn": 2, "parttype": parttype, "fstype": fstype},
]
}))
.unwrap();
assert_eq!(
disk.find_partition_of_xbootldr().and_then(|p| p.partn),
expected,
"{parttype} {fstype:?}"
);
}
}

#[test]
fn test_parse_partition_number_from_suffix() {
// Short alias like /dev/mapper/mpatha → /dev/mapper/mpatha2
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#![allow(dead_code)]

//! Partition type GUIDs from the Discoverable Partitions Specification (DPS)
//!
//! This module contains constants for partition type GUIDs as defined by the
Expand Down Expand Up @@ -55,12 +53,10 @@
//! - **Verity signature partitions**: Signed verity root hashes
//! - **Special partitions**: ESP, XBOOTLDR, swap, home, var, etc.
//!
//! # Usage (internal)
//!
//! This is an internal module. Within bootc, it is used like:
//! # Usage
//!
//! ```ignore
//! use crate::discoverable_partition_specification::{this_arch_root, ESP};
//! use bootc_blockdev::dps::{this_arch_root, ESP};
//!
//! // Get the root partition type GUID for the current architecture
//! let root_type: &str = this_arch_root();
Expand Down
2 changes: 0 additions & 2 deletions crates/lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ anyhow = { workspace = true }
camino = { workspace = true, features = ["serde1"] }
canon-json = { workspace = true }
cap-std-ext = { workspace = true, features = ["fs_utf8"] }
cfg-if = { workspace = true }
chrono = { workspace = true, features = ["serde"] }
clap = { workspace = true, features = ["derive","cargo"] }
clap_complete = "4"
Expand All @@ -47,7 +46,6 @@ indoc = { workspace = true }
libc = { workspace = true }
openssl = { workspace = true }
percent-encoding = { workspace = true }
regex = { workspace = true }
rustix = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
Expand Down
46 changes: 26 additions & 20 deletions crates/lib/src/bootc_composefs/boot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ use rustix::{mount::MountFlags, path::Arg};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use crate::bootc_composefs::boot_partition::BootPartition;
use crate::bootc_composefs::state::{get_booted_bls, write_composefs_state};
use crate::bootc_composefs::status::build_composefs_karg;
use crate::bootc_kargs::compute_new_kargs;
Expand All @@ -106,7 +107,6 @@ use crate::composefs_consts::{
};
use crate::parsers::bls_config::{BLSConfig, BLSConfigType, EFIKey};
use crate::spec::BootloaderKind;
use crate::store::find_booted_from_esp;
use crate::task::Task;
use crate::{
bootc_composefs::repo::open_composefs_repo,
Expand Down Expand Up @@ -781,7 +781,7 @@ pub(crate) fn setup_composefs_bls_boot(
) -> Result<String> {
let id_hex = id.to_hex();

let (esp_device, mut cmdline_refs, bootloader) = match setup_type {
let (boot_partition, mut cmdline_refs, bootloader) = match setup_type {
BootSetupType::Setup((root_setup, state, postfetch, allow_missing_fsverity)) => {
// root_setup.kargs has [root=UUID=<UUID>, "rw"]
let mut cmdline_options = Cmdline::new();
Expand Down Expand Up @@ -821,7 +821,7 @@ pub(crate) fn setup_composefs_bls_boot(
let esp_part = root_setup.device_info.find_first_colocated_esp()?;

(
esp_part.path(),
BootPartition::for_install(esp_part.path(), postfetch.detected_bootloader)?,
cmdline_options,
postfetch.detected_bootloader.clone(),
)
Expand Down Expand Up @@ -854,7 +854,7 @@ pub(crate) fn setup_composefs_bls_boot(
)?;

(
find_booted_from_esp(&storage.physical_root)?,
storage.require_boot_partition()?.clone(),
cmdline,
bootloader,
)
Expand Down Expand Up @@ -890,7 +890,8 @@ pub(crate) fn setup_composefs_bls_boot(
}

BootloaderKind::BLSCompatible => {
let efi_mount = mount_esp_writable(&esp_device).context("Mounting ESP")?;
let efi_mount =
mount_esp_writable(boot_partition.device()).context("Mounting boot partition")?;

let mounted_efi = Utf8PathBuf::from(efi_mount.dir.path().as_str()?);
let efi_linux_dir = mounted_efi.join(EFI_LINUX);
Expand Down Expand Up @@ -1621,7 +1622,7 @@ fn write_grub_uki_menuentry(
setup_type: &BootSetupType,
boot_label: String,
id: &Sha512HashValue,
esp_device: &String,
esp_device: &str,
) -> Result<()> {
let boot_dir = setup_type.grub_boot_path()?;
create_dir_all(&boot_dir).context("Failed to create boot dir")?;
Expand Down Expand Up @@ -1672,7 +1673,7 @@ fn write_grub_uki_menuentry(
// Open grub2/efiuuid.cfg and write the EFI partition fs-UUID in there
// This will be sourced by grub2/user.cfg to be used for `--fs-uuid`
let esp_uuid = Task::new("blkid for ESP UUID", "blkid")
.args(["-s", "UUID", "-o", "value", &esp_device])
.args(["-s", "UUID", "-o", "value", esp_device])
.read()?;

grub_dir.atomic_write(
Expand Down Expand Up @@ -1702,6 +1703,7 @@ fn write_grub_uki_menuentry(
#[context("Writing systemd UKI config")]
fn write_systemd_uki_config(
esp_dir: &Dir,
boot_partition: &BootPartition,
setup_type: &BootSetupType,
boot_label: String,
version: Option<String>,
Expand Down Expand Up @@ -1759,8 +1761,10 @@ fn write_systemd_uki_config(
)?;
}

// Write the timeout for bootloader menu if not exists
if !esp_dir.exists(SYSTEMD_LOADER_CONF_PATH) {
// Write the timeout for bootloader menu if not exists. systemd-boot only
// reads it from the ESP.
if matches!(boot_partition, BootPartition::Esp(_)) && !esp_dir.exists(SYSTEMD_LOADER_CONF_PATH)
{
esp_dir
.atomic_write(SYSTEMD_LOADER_CONF_PATH, SYSTEMD_TIMEOUT)
.with_context(|| format!("Writing to {SYSTEMD_LOADER_CONF_PATH}"))?;
Expand All @@ -1782,7 +1786,7 @@ pub(crate) fn setup_composefs_uki_boot(
boot_ids: &ExpectedBootImageIds,
entries: Vec<ComposefsBootEntry<Sha512HashValue>>,
) -> Result<(String, Sha512HashValue)> {
let (esp_device, bootloader, missing_fsverity_allowed, uki_addons) = match setup_type {
let (boot_partition, bootloader, missing_fsverity_allowed, uki_addons) = match setup_type {
BootSetupType::Setup((root_setup, state, postfetch, allow_missing_fsverity)) => {
state.require_no_kargs_for_uki()?;

Expand All @@ -1793,7 +1797,7 @@ pub(crate) fn setup_composefs_uki_boot(
let esp_part = root_setup.device_info.find_first_colocated_esp()?;

(
esp_part.path(),
BootPartition::for_install(esp_part.path(), postfetch.detected_bootloader)?,
postfetch.detected_bootloader.clone(),
allow_missing_fsverity,
state.composefs_options.uki_addon.as_ref(),
Expand All @@ -1804,7 +1808,7 @@ pub(crate) fn setup_composefs_uki_boot(
let bootloader = host.require_composefs_booted()?.bootloader.clone();

(
find_booted_from_esp(&storage.physical_root)?,
storage.require_boot_partition()?.clone(),
bootloader,
booted_cfs.cmdline.allow_missing_fsverity,
// TODO: We never (re)install UKI addons on upgrade, only on initial
Expand All @@ -1816,7 +1820,8 @@ pub(crate) fn setup_composefs_uki_boot(
}
};

let esp_mount = mount_esp_writable(&esp_device).context("Mounting ESP")?;
let esp_mount =
mount_esp_writable(boot_partition.device()).context("Mounting boot partition")?;

let mut uki_info: Option<UKIInfo> = None;

Expand Down Expand Up @@ -1887,11 +1892,12 @@ pub(crate) fn setup_composefs_uki_boot(

match bootloader.kind()? {
BootloaderKind::GRUBClassic => {
write_grub_uki_menuentry(&setup_type, boot_label, &deploy_id, &esp_device)?
write_grub_uki_menuentry(&setup_type, boot_label, &deploy_id, boot_partition.device())?
}

BootloaderKind::BLSCompatible => write_systemd_uki_config(
&esp_mount.fd,
&boot_partition,
&setup_type,
boot_label,
version,
Expand All @@ -1910,8 +1916,7 @@ pub(crate) fn setup_composefs_uki_boot(
/// The composefs image (a detached `fsmount(2)` fd with no VFS path) is
/// attached to a tmpdir via `move_mount(2)`, giving us a real filesystem path
/// that `mount(2)` and bootctl can use. The ESP is mounted at
/// `<tmpdir>/efi` (if that directory exists in the image) or `<tmpdir>/boot`,
/// per the Boot Loader Specification. A tmpfs is also mounted at
/// `<tmpdir>/boot`. A tmpfs is also mounted at
/// `<tmpdir>/tmp` to provide a writable scratch area for tools invoked with
/// `--root`.
///
Expand Down Expand Up @@ -1966,10 +1971,11 @@ impl MountedImageRoot {
let composefs = TempMount::mount_fd(composefs_mnt_fd)
.context("Attaching composefs image to temporary directory")?;

// TODO: support XBOOTLDR. Per BLS, the ESP should be mounted at /efi
// when a separate XBOOTLDR partition is present at /boot. bootc does
// not yet detect or use XBOOTLDR in the composefs install path, so
// unconditionally mount the ESP at /boot for now.
// The ESP goes on /boot even when there's an XBOOTLDR partition,
// where BLS would mount it on /efi: that would need an /efi
// directory in the image, and only the bootloader is installed
// through this mount, with the ESP's path passed explicitly. bootc
// writes the boot entries itself (see `BootPartition`).
let esp_subdir = "boot";

// Mount a tmpfs over /tmp so that tools invoked with --root have a
Expand Down
Loading
Loading