Skip to content

Commit 4e04eb9

Browse files
committed
crates: Add discoverable partition module and parttype APIs
This fleshes out what we had with a more rigorous binding to the spec. As part of this though, the ESP constant we had here was uppercase, but the spec version uses lowercase. Add APIs to find a partition by type, comparing case insensitively. Assisted-by: Claude Code Signed-off-by: Colin Walters <[email protected]>
1 parent b346d8d commit 4e04eb9

File tree

11 files changed

+1414
-33
lines changed

11 files changed

+1414
-33
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ anyhow = "1.0.82"
3636
camino = "1.1.6"
3737
canon-json = "0.2.1"
3838
cap-std-ext = "4.0.3"
39+
cfg-if = "1.0"
3940
chrono = { version = "0.4.38", default-features = false }
4041
clap = "4.5.4"
4142
clap_mangen = { version = "0.2.20" }

crates/blockdev/src/blockdev.rs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,13 +161,29 @@ impl PartitionTable {
161161
.ok_or_else(|| anyhow::anyhow!("Missing partition for index {partno}"))?;
162162
Ok(r)
163163
}
164+
165+
/// Find the partition with the given type UUID (case-insensitive).
166+
///
167+
/// Partition type UUIDs are compared case-insensitively per the GPT specification,
168+
/// as different tools may report them in different cases.
169+
pub fn find_partition_of_type(&self, uuid: &str) -> Option<&Partition> {
170+
self.partitions.iter().find(|p| p.parttype_matches(uuid))
171+
}
164172
}
165173

166174
impl Partition {
167175
#[allow(dead_code)]
168176
pub fn path(&self) -> &Utf8Path {
169177
self.node.as_str().into()
170178
}
179+
180+
/// Check if this partition's type matches the given UUID (case-insensitive).
181+
///
182+
/// Partition type UUIDs are compared case-insensitively per the GPT specification,
183+
/// as different tools may report them in different cases.
184+
pub fn parttype_matches(&self, uuid: &str) -> bool {
185+
self.parttype.eq_ignore_ascii_case(uuid)
186+
}
171187
}
172188

173189
#[context("Listing partitions of {dev}")]
@@ -505,4 +521,85 @@ mod test {
505521
);
506522
Ok(())
507523
}
524+
525+
#[test]
526+
fn test_parttype_matches() {
527+
let partition = Partition {
528+
node: "/dev/loop0p1".to_string(),
529+
start: 2048,
530+
size: 8192,
531+
parttype: "c12a7328-f81f-11d2-ba4b-00a0c93ec93b".to_string(), // lowercase ESP UUID
532+
uuid: Some("58A4C5F0-BD12-424C-B563-195AC65A25DD".to_string()),
533+
name: Some("EFI System".to_string()),
534+
};
535+
536+
// Test exact match (lowercase)
537+
assert!(partition.parttype_matches("c12a7328-f81f-11d2-ba4b-00a0c93ec93b"));
538+
539+
// Test case-insensitive match (uppercase)
540+
assert!(partition.parttype_matches("C12A7328-F81F-11D2-BA4B-00A0C93EC93B"));
541+
542+
// Test case-insensitive match (mixed case)
543+
assert!(partition.parttype_matches("C12a7328-F81f-11d2-Ba4b-00a0C93ec93b"));
544+
545+
// Test non-match
546+
assert!(!partition.parttype_matches("0FC63DAF-8483-4772-8E79-3D69D8477DE4"));
547+
}
548+
549+
#[test]
550+
fn test_find_partition_of_type() -> Result<()> {
551+
let fixture = indoc::indoc! { r#"
552+
{
553+
"partitiontable": {
554+
"label": "gpt",
555+
"id": "A67AA901-2C72-4818-B098-7F1CAC127279",
556+
"device": "/dev/loop0",
557+
"unit": "sectors",
558+
"firstlba": 34,
559+
"lastlba": 20971486,
560+
"sectorsize": 512,
561+
"partitions": [
562+
{
563+
"node": "/dev/loop0p1",
564+
"start": 2048,
565+
"size": 8192,
566+
"type": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
567+
"uuid": "58A4C5F0-BD12-424C-B563-195AC65A25DD",
568+
"name": "EFI System"
569+
},{
570+
"node": "/dev/loop0p2",
571+
"start": 10240,
572+
"size": 20961247,
573+
"type": "0FC63DAF-8483-4772-8E79-3D69D8477DE4",
574+
"uuid": "F51ABB0D-DA16-4A21-83CB-37F4C805AAA0",
575+
"name": "root"
576+
}
577+
]
578+
}
579+
}
580+
"# };
581+
let table: SfDiskOutput = serde_json::from_str(fixture).unwrap();
582+
583+
// Find ESP partition using lowercase UUID (should match uppercase in fixture)
584+
let esp = table
585+
.partitiontable
586+
.find_partition_of_type("c12a7328-f81f-11d2-ba4b-00a0c93ec93b");
587+
assert!(esp.is_some());
588+
assert_eq!(esp.unwrap().node, "/dev/loop0p1");
589+
590+
// Find root partition using uppercase UUID (should match case-insensitively)
591+
let root = table
592+
.partitiontable
593+
.find_partition_of_type("0fc63daf-8483-4772-8e79-3d69d8477de4");
594+
assert!(root.is_some());
595+
assert_eq!(root.unwrap().node, "/dev/loop0p2");
596+
597+
// Try to find non-existent partition type
598+
let nonexistent = table
599+
.partitiontable
600+
.find_partition_of_type("00000000-0000-0000-0000-000000000000");
601+
assert!(nonexistent.is_none());
602+
603+
Ok(())
604+
}
508605
}

crates/lib/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ anyhow = { workspace = true }
3030
camino = { workspace = true, features = ["serde1"] }
3131
canon-json = { workspace = true }
3232
cap-std-ext = { workspace = true, features = ["fs_utf8"] }
33+
cfg-if = { workspace = true }
3334
chrono = { workspace = true, features = ["serde"] }
3435
clap = { workspace = true, features = ["derive","cargo"] }
3536
clap_mangen = { workspace = true, optional = true }

crates/lib/src/bootc_composefs/boot.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ use crate::{
4444
BOOT_LOADER_ENTRIES, COMPOSEFS_CMDLINE, ORIGIN_KEY_BOOT, ORIGIN_KEY_BOOT_DIGEST,
4545
STAGED_BOOT_LOADER_ENTRIES, STATE_DIR_ABS, USER_CFG, USER_CFG_STAGED,
4646
},
47-
install::{dps_uuid::DPS_UUID, RW_KARG},
47+
discoverable_partition_specification::this_arch_root,
48+
install::RW_KARG,
4849
spec::{Bootloader, Host},
4950
};
5051

@@ -400,7 +401,7 @@ pub(crate) fn setup_composefs_bls_boot(
400401
Utf8PathBuf::from("/sysroot"),
401402
get_esp_partition(&sysroot_parent)?.0,
402403
[
403-
format!("root=UUID={DPS_UUID}"),
404+
format!("root=UUID={}", this_arch_root()),
404405
RW_KARG.to_string(),
405406
format!("{COMPOSEFS_CMDLINE}={id_hex}"),
406407
]

crates/lib/src/bootloader.rs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,19 @@ use bootc_mount as mount;
1010

1111
#[cfg(any(feature = "composefs-backend", feature = "install-to-disk"))]
1212
use crate::bootc_composefs::boot::mount_esp;
13-
use crate::utils;
13+
use crate::{discoverable_partition_specification, utils};
1414

1515
/// The name of the mountpoint for efi (as a subdirectory of /boot, or at the toplevel)
1616
pub(crate) const EFI_DIR: &str = "efi";
1717
/// The EFI system partition GUID
18-
#[allow(dead_code)]
19-
pub(crate) const ESP_GUID: &str = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B";
2018
/// Path to the bootupd update payload
2119
#[allow(dead_code)]
2220
const BOOTUPD_UPDATES: &str = "usr/lib/bootupd/updates";
2321

2422
#[allow(dead_code)]
2523
pub(crate) fn esp_in(device: &PartitionTable) -> Result<&Partition> {
2624
device
27-
.partitions
28-
.iter()
29-
.find(|p| p.parttype.as_str() == ESP_GUID)
25+
.find_partition_of_type(discoverable_partition_specification::ESP)
3026
.ok_or(anyhow::anyhow!("ESP not found in partition table"))
3127
}
3228

@@ -84,9 +80,7 @@ pub(crate) fn install_systemd_boot(
8480
_deployment_path: Option<&str>,
8581
) -> Result<()> {
8682
let esp_part = device
87-
.partitions
88-
.iter()
89-
.find(|p| p.parttype.as_str() == ESP_GUID)
83+
.find_partition_of_type(discoverable_partition_specification::ESP)
9084
.ok_or_else(|| anyhow::anyhow!("ESP partition not found"))?;
9185

9286
let esp_mount = mount_esp(&esp_part.node).context("Mounting ESP")?;

0 commit comments

Comments
 (0)