Skip to content

Commit 3172b7a

Browse files
committed
WIP: switch composefs-boot to use bootc-kernel-cmdline
Signed-off-by: John Eckersberg <[email protected]>
1 parent ce28ee8 commit 3172b7a

File tree

4 files changed

+73
-72
lines changed

4 files changed

+73
-72
lines changed

crates/composefs-boot/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ version.workspace = true
1212

1313
[dependencies]
1414
anyhow = { version = "1.0.87", default-features = false }
15+
bootc-kernel-cmdline = { git = "https://github.com/jeckersb/bootc", branch = "cmdline" }
1516
composefs = { workspace = true }
1617
hex = { version = "0.4.0", default-features = false, features = ["std"] }
1718
regex-automata = { version = "0.4.4", default-features = false, features=["hybrid", "std", "syntax"] }

crates/composefs-boot/src/bootloader.rs

Lines changed: 59 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use composefs::{
99
tree::{Directory, FileSystem, ImageError, Inode, LeafContent, RegularFile},
1010
};
1111

12-
use crate::cmdline::{make_cmdline_composefs, split_cmdline};
12+
use crate::cmdline::{Cmdline, Parameter};
1313

1414
/// Strips the key (if it matches) plus the following whitespace from a single line in a "Type #1
1515
/// Boot Loader Specification Entry" file.
@@ -65,30 +65,17 @@ impl BootLoaderEntryFile {
6565
///
6666
/// arg can be something like "composefs=xyz" but it can also be something like "rw". In
6767
/// either case, if the argument already existed, it will be replaced.
68-
pub fn add_cmdline(&mut self, arg: &str) {
69-
let key = match arg.find('=') {
70-
Some(pos) => &arg[..=pos], // include the '='
71-
None => arg,
72-
};
73-
68+
pub fn add_cmdline(&mut self, arg: &Parameter) {
7469
// There are three possible paths in this function:
7570
// 1. options line with key= already in it (replace it)
7671
// 2. options line with no key= in it (append key=value)
7772
// 3. no options line (append the entire thing)
7873
for line in &mut self.lines {
7974
if let Some(cmdline) = strip_ble_key(line, "options") {
80-
let segment = split_cmdline(cmdline).find(|s| s.starts_with(key));
81-
82-
if let Some(old) = segment {
83-
// 1. Replace existing key
84-
let range = substr_range(line, old).unwrap();
85-
line.replace_range(range, arg);
86-
} else {
87-
// 2. Append new argument
88-
line.push(' ');
89-
line.push_str(arg);
90-
}
75+
let mut cmdline = Cmdline::from(cmdline);
76+
cmdline.add_or_modify(arg);
9177

78+
*line = format!("options {cmdline}");
9279
return;
9380
}
9481
}
@@ -99,9 +86,21 @@ impl BootLoaderEntryFile {
9986

10087
/// Adjusts the kernel command-line arguments by adding a composefs= parameter (if appropriate)
10188
/// and adding additional arguments, as requested.
102-
pub fn adjust_cmdline(&mut self, composefs: Option<&str>, insecure: bool, extra: &[&str]) {
89+
pub fn adjust_cmdline<T: FsVerityHashValue>(
90+
&mut self,
91+
composefs: Option<&T>,
92+
insecure: bool,
93+
extra: &[Parameter],
94+
) {
10395
if let Some(id) = composefs {
104-
self.add_cmdline(&make_cmdline_composefs(id, insecure));
96+
let id = id.to_hex();
97+
let cfs_str = match insecure {
98+
true => format!("composefs=?{id}"),
99+
false => format!("composefs={id}"),
100+
};
101+
102+
let param = Parameter::parse(&cfs_str).unwrap();
103+
self.add_cmdline(&param);
105104
}
106105

107106
for item in extra {
@@ -400,8 +399,27 @@ pub fn get_boot_resources<ObjectID: FsVerityHashValue>(
400399

401400
#[cfg(test)]
402401
mod tests {
402+
use composefs::fsverity::Sha256HashValue;
403+
use zerocopy::FromZeros;
404+
403405
use super::*;
404406

407+
fn sha256() -> Sha256HashValue {
408+
Sha256HashValue::new_zeroed()
409+
}
410+
411+
fn sha256str() -> String {
412+
sha256().to_hex()
413+
}
414+
415+
fn param(input: &str) -> Parameter<'_> {
416+
Parameter::parse(input).unwrap()
417+
}
418+
419+
fn params<'a>(input: &'a [&'a str]) -> Vec<Parameter<'a>> {
420+
input.iter().map(|p| param(*p)).collect()
421+
}
422+
405423
#[test]
406424
fn test_bootloader_entry_file_new() {
407425
let content = "title Test Entry\nversion 1.0\nlinux /vmlinuz\ninitrd /initramfs.img\noptions quiet splash\n";
@@ -490,7 +508,7 @@ mod tests {
490508
#[test]
491509
fn test_add_cmdline_new_options_line() {
492510
let mut entry = BootLoaderEntryFile::new("title Test Entry\nlinux /vmlinuz\n");
493-
entry.add_cmdline("quiet");
511+
entry.add_cmdline(&param("quiet"));
494512

495513
assert_eq!(entry.lines.len(), 3);
496514
assert_eq!(entry.lines[2], "options quiet");
@@ -499,7 +517,7 @@ mod tests {
499517
#[test]
500518
fn test_add_cmdline_append_to_existing_options() {
501519
let mut entry = BootLoaderEntryFile::new("title Test Entry\noptions splash\n");
502-
entry.add_cmdline("quiet");
520+
entry.add_cmdline(&param("quiet"));
503521

504522
assert_eq!(entry.lines.len(), 2);
505523
assert_eq!(entry.lines[1], "options splash quiet");
@@ -509,7 +527,7 @@ mod tests {
509527
fn test_add_cmdline_replace_existing_key_value() {
510528
let mut entry =
511529
BootLoaderEntryFile::new("title Test Entry\noptions quiet splash root=/dev/sda1\n");
512-
entry.add_cmdline("root=/dev/sda2");
530+
entry.add_cmdline(&param("root=/dev/sda2"));
513531

514532
assert_eq!(entry.lines.len(), 2);
515533
assert_eq!(entry.lines[1], "options quiet splash root=/dev/sda2");
@@ -518,20 +536,20 @@ mod tests {
518536
#[test]
519537
fn test_add_cmdline_replace_existing_key_only() {
520538
let mut entry = BootLoaderEntryFile::new("title Test Entry\noptions quiet rw splash\n");
521-
entry.add_cmdline("rw"); // Same key, should replace itself (no-op in this case)
539+
entry.add_cmdline(&param("rw")); // Same key, should replace itself (no-op in this case)
522540

523541
assert_eq!(entry.lines.len(), 2);
524542
assert_eq!(entry.lines[1], "options quiet rw splash");
525543

526544
// Test replacing with different key
527-
entry.add_cmdline("ro");
545+
entry.add_cmdline(&param("ro"));
528546
assert_eq!(entry.lines[1], "options quiet rw splash ro");
529547
}
530548

531549
#[test]
532550
fn test_add_cmdline_key_with_equals() {
533551
let mut entry = BootLoaderEntryFile::new("title Test Entry\noptions quiet\n");
534-
entry.add_cmdline("composefs=abc123");
552+
entry.add_cmdline(&param("composefs=abc123"));
535553

536554
assert_eq!(entry.lines.len(), 2);
537555
assert_eq!(entry.lines[1], "options quiet composefs=abc123");
@@ -541,7 +559,7 @@ mod tests {
541559
fn test_add_cmdline_replace_key_with_equals() {
542560
let mut entry =
543561
BootLoaderEntryFile::new("title Test Entry\noptions quiet composefs=old123\n");
544-
entry.add_cmdline("composefs=new456");
562+
entry.add_cmdline(&param("composefs=new456"));
545563

546564
assert_eq!(entry.lines.len(), 2);
547565
assert_eq!(entry.lines[1], "options quiet composefs=new456");
@@ -550,26 +568,33 @@ mod tests {
550568
#[test]
551569
fn test_adjust_cmdline_with_composefs() {
552570
let mut entry = BootLoaderEntryFile::new("title Test Entry\nlinux /vmlinuz\n");
553-
entry.adjust_cmdline(Some("abc123"), false, &["quiet", "splash"]);
571+
entry.adjust_cmdline(Some(&sha256()), false, &params(&["quiet", "splash"]));
554572

555573
assert_eq!(entry.lines.len(), 3);
556-
assert_eq!(entry.lines[2], "options composefs=abc123 quiet splash");
574+
assert_eq!(
575+
entry.lines[2],
576+
format!("options composefs={} quiet splash", sha256str())
577+
);
557578
}
558579

559580
#[test]
560581
fn test_adjust_cmdline_with_composefs_insecure() {
561582
let mut entry = BootLoaderEntryFile::new("title Test Entry\nlinux /vmlinuz\n");
562-
entry.adjust_cmdline(Some("abc123"), true, &[]);
583+
entry.adjust_cmdline(Some(&sha256()), true, &[]);
563584

564585
assert_eq!(entry.lines.len(), 3);
565586
// Assuming make_cmdline_composefs adds digest=off for insecure mode
566-
assert!(entry.lines[2].contains("abc123"));
587+
assert!(entry.lines[2].contains(&sha256str()));
567588
}
568589

569590
#[test]
570591
fn test_adjust_cmdline_no_composefs() {
571592
let mut entry = BootLoaderEntryFile::new("title Test Entry\nlinux /vmlinuz\n");
572-
entry.adjust_cmdline(None, false, &["quiet", "splash"]);
593+
entry.adjust_cmdline(
594+
None::<&Sha256HashValue>,
595+
false,
596+
&params(&["quiet", "splash"]),
597+
);
573598

574599
assert_eq!(entry.lines.len(), 3);
575600
assert_eq!(entry.lines[2], "options quiet splash");
@@ -578,11 +603,11 @@ mod tests {
578603
#[test]
579604
fn test_adjust_cmdline_existing_options() {
580605
let mut entry = BootLoaderEntryFile::new("title Test Entry\noptions root=/dev/sda1\n");
581-
entry.adjust_cmdline(Some("abc123"), false, &["quiet"]);
606+
entry.adjust_cmdline(Some(&sha256()), false, &params(&["quiet"]));
582607

583608
assert_eq!(entry.lines.len(), 2);
584609
assert!(entry.lines[1].contains("root=/dev/sda1"));
585-
assert!(entry.lines[1].contains("abc123"));
610+
assert!(entry.lines[1].contains(&sha256str()));
586611
assert!(entry.lines[1].contains("quiet"));
587612
}
588613

Lines changed: 4 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,15 @@
1-
use anyhow::{Context, Result};
1+
use anyhow::Result;
2+
pub(crate) use bootc_kernel_cmdline::utf8::{Cmdline, Parameter};
23
use composefs::fsverity::FsVerityHashValue;
34

4-
/// Perform kernel command line splitting.
5-
///
6-
/// The way this works in the kernel is to split on whitespace with an extremely simple quoting
7-
/// mechanism: whitespace inside of double quotes is literal, but there is no escaping mechanism.
8-
/// That means that having a literal double quote in the cmdline is effectively impossible.
9-
pub(crate) fn split_cmdline(cmdline: &str) -> impl Iterator<Item = &str> {
10-
let mut in_quotes = false;
11-
12-
cmdline.split(move |c: char| {
13-
if c == '"' {
14-
in_quotes = !in_quotes;
15-
}
16-
!in_quotes && c.is_ascii_whitespace()
17-
})
18-
}
19-
20-
/// Gets the value of an entry from the kernel cmdline.
21-
///
22-
/// The prefix should be something like "composefs=".
23-
///
24-
/// This iterates the entries in the provided cmdline string searching for an entry that starts
25-
/// with the provided prefix. This will successfully handle quoting of other items in the cmdline,
26-
/// but the value of the searched entry is returned verbatim (ie: not dequoted).
27-
pub fn get_cmdline_value<'a>(cmdline: &'a str, prefix: &str) -> Option<&'a str> {
28-
split_cmdline(cmdline).find_map(|item| item.strip_prefix(prefix))
29-
}
30-
315
pub fn get_cmdline_composefs<ObjectID: FsVerityHashValue>(
326
cmdline: &str,
337
) -> Result<(ObjectID, bool)> {
34-
let id = get_cmdline_value(cmdline, "composefs=").context("composefs= value not found")?;
8+
let cmdline = Cmdline::from(cmdline);
9+
let id = cmdline.require_value_of("composefs")?;
3510
if let Some(stripped) = id.strip_prefix('?') {
3611
Ok((ObjectID::from_hex(stripped)?, true))
3712
} else {
3813
Ok((ObjectID::from_hex(id)?, false))
3914
}
4015
}
41-
42-
pub fn make_cmdline_composefs(id: &str, insecure: bool) -> String {
43-
match insecure {
44-
true => format!("composefs=?{id}"),
45-
false => format!("composefs={id}"),
46-
}
47-
}

crates/composefs-boot/src/write_boot.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use composefs::{fsverity::FsVerityHashValue, repository::Repository};
99

1010
use crate::{
1111
bootloader::{BootEntry, Type1Entry, Type2Entry},
12-
cmdline::get_cmdline_composefs,
12+
cmdline::{get_cmdline_composefs, Parameter},
1313
uki,
1414
};
1515

@@ -29,8 +29,15 @@ pub fn write_t1_simple<ObjectID: FsVerityHashValue>(
2929
bootdir.to_path_buf()
3030
};
3131

32+
let cmdline_extra = cmdline_extra
33+
.iter()
34+
.map(|p| {
35+
Parameter::parse(*p).ok_or(anyhow::anyhow!("could not parse command line parameter"))
36+
})
37+
.collect::<Result<Vec<_>>>()?;
38+
3239
t1.entry
33-
.adjust_cmdline(Some(&root_id.to_hex()), insecure, cmdline_extra);
40+
.adjust_cmdline(Some(root_id), insecure, &cmdline_extra);
3441

3542
// Write the content before we write the loader entry
3643
for (filename, file) in &t1.files {

0 commit comments

Comments
 (0)