Skip to content

Commit d5df176

Browse files
committed
bootc_kargs: Use kernel_cmdline for parsing
Signed-off-by: John Eckersberg <jeckersb@redhat.com>
1 parent 8d0bf73 commit d5df176

File tree

4 files changed

+117
-94
lines changed

4 files changed

+117
-94
lines changed

crates/lib/src/bootc_kargs.rs

Lines changed: 85 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! This module handles the bootc-owned kernel argument lists in `/usr/lib/bootc/kargs.d`.
22
use anyhow::{Context, Result};
3+
use bootc_kernel_cmdline::utf8::Cmdline;
34
use camino::Utf8Path;
45
use cap_std_ext::cap_std::fs::Dir;
56
use cap_std_ext::cap_std::fs_utf8::Dir as DirUtf8;
@@ -20,11 +21,11 @@ use crate::store::Storage;
2021
const KARGS_PATH: &str = "usr/lib/bootc/kargs.d";
2122

2223
/// The default root filesystem mount specification.
23-
pub(crate) const ROOT: &str = "root=";
24+
pub(crate) const ROOT_KEY: &str = "root";
2425
/// This is used by dracut.
2526
pub(crate) const INITRD_ARG_PREFIX: &str = "rd.";
2627
/// The kernel argument for configuring the rootfs flags.
27-
pub(crate) const ROOTFLAGS: &str = "rootflags=";
28+
pub(crate) const ROOTFLAGS_KEY: &str = "rootflags";
2829

2930
/// The kargs.d configuration file.
3031
#[derive(Deserialize)]
@@ -46,39 +47,44 @@ impl Config {
4647

4748
/// Load and parse all bootc kargs.d files in the specified root, returning
4849
/// a combined list.
49-
pub(crate) fn get_kargs_in_root(d: &Dir, sys_arch: &str) -> Result<Vec<String>> {
50+
pub(crate) fn get_kargs_in_root(d: &Dir, sys_arch: &str) -> Result<Cmdline<'static>> {
5051
// If the directory doesn't exist, that's OK.
5152
let Some(d) = d.open_dir_optional(KARGS_PATH)?.map(DirUtf8::from_cap_std) else {
5253
return Ok(Default::default());
5354
};
54-
let mut ret = Vec::new();
55+
let mut ret = Cmdline::new();
5556
let entries = d.filenames_filtered_sorted(|_, name| Config::filename_matches(name))?;
5657
for name in entries {
5758
let buf = d.read_to_string(&name)?;
58-
let kargs = parse_kargs_toml(&buf, sys_arch).with_context(|| format!("Parsing {name}"))?;
59-
ret.extend(kargs)
59+
if let Some(kargs) =
60+
parse_kargs_toml(&buf, sys_arch).with_context(|| format!("Parsing {name}"))?
61+
{
62+
ret.extend(&kargs)
63+
}
6064
}
6165
Ok(ret)
6266
}
6367

64-
pub(crate) fn root_args_from_cmdline<'a>(cmdline: &'a [&str]) -> Vec<&'a str> {
65-
cmdline
66-
.iter()
67-
.filter(|arg| {
68-
arg.starts_with(ROOT)
69-
|| arg.starts_with(ROOTFLAGS)
70-
|| arg.starts_with(INITRD_ARG_PREFIX)
71-
})
72-
.copied()
73-
.collect()
68+
pub(crate) fn root_args_from_cmdline(cmdline: &Cmdline) -> Cmdline<'static> {
69+
let mut result = Cmdline::new();
70+
for param in cmdline {
71+
let key = param.key();
72+
if key == ROOT_KEY.into()
73+
|| key == ROOTFLAGS_KEY.into()
74+
|| key.starts_with(INITRD_ARG_PREFIX)
75+
{
76+
result.add(&param);
77+
}
78+
}
79+
result
7480
}
7581

7682
/// Load kargs.d files from the target ostree commit root
7783
pub(crate) fn get_kargs_from_ostree_root(
7884
repo: &ostree::Repo,
7985
root: &ostree::RepoFile,
8086
sys_arch: &str,
81-
) -> Result<Vec<String>> {
87+
) -> Result<Cmdline<'static>> {
8288
let kargsd = root.resolve_relative_path(KARGS_PATH);
8389
let kargsd = kargsd.downcast_ref::<ostree::RepoFile>().expect("downcast");
8490
if !kargsd.query_exists(gio::Cancellable::NONE) {
@@ -92,12 +98,12 @@ fn get_kargs_from_ostree(
9298
repo: &ostree::Repo,
9399
fetched_tree: &ostree::RepoFile,
94100
sys_arch: &str,
95-
) -> Result<Vec<String>> {
101+
) -> Result<Cmdline<'static>> {
96102
let cancellable = gio::Cancellable::NONE;
97103
let queryattrs = "standard::name,standard::type";
98104
let queryflags = gio::FileQueryInfoFlags::NOFOLLOW_SYMLINKS;
99105
let fetched_iter = fetched_tree.enumerate_children(queryattrs, queryflags, cancellable)?;
100-
let mut ret = Vec::new();
106+
let mut ret = Cmdline::new();
101107
while let Some(fetched_info) = fetched_iter.next_file(cancellable)? {
102108
// only read and parse the file if it is a toml file
103109
let name = fetched_info.name();
@@ -119,9 +125,11 @@ fn get_kargs_from_ostree(
119125
let mut reader =
120126
ostree_ext::prelude::InputStreamExtManual::into_read(file_content.unwrap());
121127
let s = std::io::read_to_string(&mut reader)?;
122-
let parsed_kargs =
123-
parse_kargs_toml(&s, sys_arch).with_context(|| format!("Parsing {name}"))?;
124-
ret.extend(parsed_kargs);
128+
if let Some(parsed_kargs) =
129+
parse_kargs_toml(&s, sys_arch).with_context(|| format!("Parsing {name}"))?
130+
{
131+
ret.extend(&parsed_kargs);
132+
}
125133
}
126134
Ok(ret)
127135
}
@@ -133,20 +141,19 @@ pub(crate) fn get_kargs(
133141
sysroot: &Storage,
134142
merge_deployment: &Deployment,
135143
fetched: &ImageState,
136-
) -> Result<Vec<String>> {
144+
) -> Result<Cmdline<'static>> {
137145
let cancellable = gio::Cancellable::NONE;
138146
let ostree = sysroot.get_ostree()?;
139147
let repo = &ostree.repo();
140-
let mut kargs = vec![];
141148
let sys_arch = std::env::consts::ARCH;
142149

143150
// Get the kargs used for the merge in the bootloader config
144-
if let Some(bootconfig) = ostree::Deployment::bootconfig(merge_deployment) {
145-
if let Some(options) = ostree::BootconfigParser::get(&bootconfig, "options") {
146-
let options = options.split_whitespace().map(|s| s.to_owned());
147-
kargs.extend(options);
148-
}
149-
};
151+
let mut kargs = ostree::Deployment::bootconfig(merge_deployment)
152+
.and_then(|bootconfig| {
153+
ostree::BootconfigParser::get(&bootconfig, "options")
154+
.map(|options| Cmdline::from(options.to_string()))
155+
})
156+
.unwrap_or_default();
150157

151158
// Get the kargs in kargs.d of the merge
152159
let merge_root = &crate::utils::deployment_fd(ostree, merge_deployment)?;
@@ -161,50 +168,56 @@ pub(crate) fn get_kargs(
161168
// A special case: if there's no kargs.d directory in the pending (fetched) image,
162169
// then we can just use the combined current kargs + kargs from booted
163170
if !fetched_tree.query_exists(cancellable) {
164-
kargs.extend(existing_kargs);
171+
kargs.extend(&existing_kargs);
165172
return Ok(kargs);
166173
}
167174

168175
// Fetch the kernel arguments from the new root
169176
let remote_kargs = get_kargs_from_ostree(repo, &fetched_tree, sys_arch)?;
170177

171-
// get the diff between the existing and remote kargs
172-
let mut added_kargs = remote_kargs
173-
.clone()
174-
.into_iter()
175-
.filter(|item| !existing_kargs.contains(item))
176-
.collect::<Vec<_>>();
177-
let removed_kargs = existing_kargs
178-
.clone()
179-
.into_iter()
180-
.filter(|item| !remote_kargs.contains(item))
181-
.collect::<Vec<_>>();
178+
// Calculate the diff between the existing and remote kargs
179+
let added_kargs: Vec<_> = remote_kargs
180+
.iter()
181+
.filter(|item| !existing_kargs.iter().any(|existing| *item == existing))
182+
.collect();
183+
let removed_kargs: Vec<_> = existing_kargs
184+
.iter()
185+
.filter(|item| !remote_kargs.iter().any(|remote| *item == remote))
186+
.collect();
182187

183188
tracing::debug!(
184189
"kargs: added={:?} removed={:?}",
185190
&added_kargs,
186191
removed_kargs
187192
);
188193

189-
// apply the diff to the system kargs
190-
kargs.retain(|x| !removed_kargs.contains(x));
191-
kargs.append(&mut added_kargs);
194+
// Apply the diff to the system kargs
195+
for arg in &removed_kargs {
196+
kargs.remove_exact(arg);
197+
}
198+
for arg in &added_kargs {
199+
kargs.add(arg);
200+
}
192201

193202
Ok(kargs)
194203
}
195204

196205
/// This parses a bootc kargs.d toml file, returning the resulting
197206
/// vector of kernel arguments. Architecture matching is performed using
198207
/// `sys_arch`.
199-
fn parse_kargs_toml(contents: &str, sys_arch: &str) -> Result<Vec<String>> {
208+
fn parse_kargs_toml(contents: &str, sys_arch: &str) -> Result<Option<Cmdline<'static>>> {
200209
let de: Config = toml::from_str(contents)?;
201210
// if arch specified, apply kargs only if the arch matches
202211
// if arch not specified, apply kargs unconditionally
203212
let matched = de
204213
.match_architectures
205214
.map(|arches| arches.iter().any(|s| s == sys_arch))
206215
.unwrap_or(true);
207-
let r = if matched { de.kargs } else { Vec::new() };
216+
let r = if matched {
217+
Some(Cmdline::from(de.kargs.join(" ")))
218+
} else {
219+
None
220+
};
208221
Ok(r)
209222
}
210223

@@ -216,17 +229,23 @@ mod tests {
216229

217230
use super::*;
218231

232+
fn assert_cmdline_eq(cmdline: &Cmdline, expected_params: &[&str]) {
233+
let actual_params: Vec<_> = cmdline.iter_str().collect();
234+
assert_eq!(actual_params, expected_params);
235+
}
236+
219237
#[test]
220238
/// Verify that kargs are only applied to supported architectures
221239
fn test_arch() {
222240
// no arch specified, kargs ensure that kargs are applied unconditionally
223241
let sys_arch = "x86_64";
224242
let file_content = r##"kargs = ["console=tty0", "nosmt"]"##.to_string();
225-
let parsed_kargs = parse_kargs_toml(&file_content, sys_arch).unwrap();
226-
assert_eq!(parsed_kargs, ["console=tty0", "nosmt"]);
243+
let parsed_kargs = parse_kargs_toml(&file_content, sys_arch).unwrap().unwrap();
244+
assert_cmdline_eq(&parsed_kargs, &["console=tty0", "nosmt"]);
245+
227246
let sys_arch = "aarch64";
228-
let parsed_kargs = parse_kargs_toml(&file_content, sys_arch).unwrap();
229-
assert_eq!(parsed_kargs, ["console=tty0", "nosmt"]);
247+
let parsed_kargs = parse_kargs_toml(&file_content, sys_arch).unwrap().unwrap();
248+
assert_cmdline_eq(&parsed_kargs, &["console=tty0", "nosmt"]);
230249

231250
// one arch matches and one doesn't, ensure that kargs are only applied for the matching arch
232251
let sys_arch = "aarch64";
@@ -235,25 +254,26 @@ match-architectures = ["x86_64"]
235254
"##
236255
.to_string();
237256
let parsed_kargs = parse_kargs_toml(&file_content, sys_arch).unwrap();
238-
assert_eq!(parsed_kargs, [] as [String; 0]);
257+
assert!(parsed_kargs.is_none());
239258
let file_content = r##"kargs = ["console=tty0", "nosmt"]
240259
match-architectures = ["aarch64"]
241260
"##
242261
.to_string();
243-
let parsed_kargs = parse_kargs_toml(&file_content, sys_arch).unwrap();
244-
assert_eq!(parsed_kargs, ["console=tty0", "nosmt"]);
262+
let parsed_kargs = parse_kargs_toml(&file_content, sys_arch).unwrap().unwrap();
263+
assert_cmdline_eq(&parsed_kargs, &["console=tty0", "nosmt"]);
245264

246265
// multiple arch specified, ensure that kargs are applied to both archs
247266
let sys_arch = "x86_64";
248267
let file_content = r##"kargs = ["console=tty0", "nosmt"]
249268
match-architectures = ["x86_64", "aarch64"]
250269
"##
251270
.to_string();
252-
let parsed_kargs = parse_kargs_toml(&file_content, sys_arch).unwrap();
253-
assert_eq!(parsed_kargs, ["console=tty0", "nosmt"]);
271+
let parsed_kargs = parse_kargs_toml(&file_content, sys_arch).unwrap().unwrap();
272+
assert_cmdline_eq(&parsed_kargs, &["console=tty0", "nosmt"]);
273+
254274
let sys_arch = "aarch64";
255-
let parsed_kargs = parse_kargs_toml(&file_content, sys_arch).unwrap();
256-
assert_eq!(parsed_kargs, ["console=tty0", "nosmt"]);
275+
let parsed_kargs = parse_kargs_toml(&file_content, sys_arch).unwrap().unwrap();
276+
assert_cmdline_eq(&parsed_kargs, &["console=tty0", "nosmt"]);
257277
}
258278

259279
#[test]
@@ -285,18 +305,18 @@ match-architectures = ["x86_64", "aarch64"]
285305
let td = cap_std_ext::cap_tempfile::TempDir::new(cap_std::ambient_authority())?;
286306

287307
// No directory
288-
assert_eq!(get_kargs_in_root(&td, "x86_64").unwrap().len(), 0);
308+
assert_eq!(get_kargs_in_root(&td, "x86_64").unwrap().iter().count(), 0);
289309
// Empty directory
290310
td.create_dir_all("usr/lib/bootc/kargs.d")?;
291-
assert_eq!(get_kargs_in_root(&td, "x86_64").unwrap().len(), 0);
311+
assert_eq!(get_kargs_in_root(&td, "x86_64").unwrap().iter().count(), 0);
292312
// Non-toml file
293313
td.write("usr/lib/bootc/kargs.d/somegarbage", "garbage")?;
294-
assert_eq!(get_kargs_in_root(&td, "x86_64").unwrap().len(), 0);
314+
assert_eq!(get_kargs_in_root(&td, "x86_64").unwrap().iter().count(), 0);
295315

296316
write_test_kargs(&td)?;
297317

298318
let args = get_kargs_in_root(&td, "x86_64").unwrap();
299-
similar_asserts::assert_eq!(args, ["console=tty0", "nosmt", "console=ttyS1"]);
319+
assert_cmdline_eq(&args, &["console=tty0", "nosmt", "console=ttyS1"]);
300320

301321
Ok(())
302322
}
@@ -354,7 +374,7 @@ match-architectures = ["x86_64", "aarch64"]
354374

355375
ostree_commit(repo, &test_rootfs, ".".into(), "testref")?;
356376
// Helper closure to read the kargs
357-
let get_kargs = |sys_arch: &str| -> Result<Vec<String>> {
377+
let get_kargs = |sys_arch: &str| -> Result<Cmdline<'static>> {
358378
let rootfs = repo.read_commit("testref", cancellable)?.0;
359379
let rootfs = rootfs.downcast_ref::<ostree::RepoFile>().unwrap();
360380
let fetched_tree = rootfs.resolve_relative_path("/usr/lib/bootc/kargs.d");
@@ -368,14 +388,14 @@ match-architectures = ["x86_64", "aarch64"]
368388
};
369389

370390
// rootfs is empty
371-
assert_eq!(get_kargs("x86_64").unwrap().len(), 0);
391+
assert_eq!(get_kargs("x86_64").unwrap().iter().count(), 0);
372392

373393
test_rootfs.create_dir_all("usr/lib/bootc/kargs.d")?;
374394
write_test_kargs(&test_rootfs).unwrap();
375395
ostree_commit(repo, &test_rootfs, ".".into(), "testref")?;
376396

377397
let args = get_kargs("x86_64").unwrap();
378-
similar_asserts::assert_eq!(args, ["console=tty0", "nosmt", "console=ttyS1"]);
398+
assert_cmdline_eq(&args, &["console=tty0", "nosmt", "console=ttyS1"]);
379399

380400
Ok(())
381401
}

crates/lib/src/deploy.rs

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use std::io::{BufRead, Write};
77

88
use anyhow::Ok;
99
use anyhow::{anyhow, Context, Result};
10+
use bootc_kernel_cmdline::utf8::Cmdline;
1011
use cap_std::fs::{Dir, MetadataExt};
1112
use cap_std_ext::cap_std;
1213
use cap_std_ext::dirext::CapStdExtDirExt;
@@ -588,9 +589,9 @@ async fn deploy(
588589
let (stateroot, override_kargs) = match &from {
589590
MergeState::MergeDeployment(deployment) => {
590591
let kargs = crate::bootc_kargs::get_kargs(sysroot, &deployment, image)?;
591-
(deployment.stateroot().into(), kargs)
592+
(deployment.stateroot().into(), Some(kargs))
592593
}
593-
MergeState::Reset { stateroot, kargs } => (stateroot.clone(), kargs.clone()),
594+
MergeState::Reset { stateroot, kargs } => (stateroot.clone(), Some(kargs.clone())),
594595
};
595596
// Clone all the things to move to worker thread
596597
let ostree = sysroot.get_ostree_cloned()?;
@@ -607,13 +608,15 @@ async fn deploy(
607608
let stateroot = Some(stateroot);
608609
let mut opts = ostree::SysrootDeployTreeOpts::default();
609610

610-
// Because the C API expects a Vec<&str>, we need to generate a new Vec<>
611-
// that borrows.
612-
let override_kargs = override_kargs
613-
.iter()
614-
.map(|s| s.as_str())
615-
.collect::<Vec<_>>();
616-
opts.override_kernel_argv = Some(&override_kargs);
611+
// Because the C API expects a Vec<&str>, convert the Cmdline to string slices.
612+
// The references borrow from the Cmdline, which outlives this usage.
613+
let override_kargs_refs = override_kargs
614+
.as_ref()
615+
.map(|kargs| kargs.iter_str().collect::<Vec<_>>());
616+
if let Some(kargs) = override_kargs_refs.as_ref() {
617+
opts.override_kernel_argv = Some(kargs);
618+
}
619+
617620
let deployments = ostree.deployments();
618621
let merge_deployment = merge_deployment.map(|m| &deployments[m]);
619622
let origin = glib::KeyFile::new();
@@ -658,7 +661,7 @@ pub(crate) enum MergeState {
658661
/// provided initial state.
659662
Reset {
660663
stateroot: String,
661-
kargs: Vec<String>,
664+
kargs: Cmdline<'static>,
662665
},
663666
}
664667
impl MergeState {

0 commit comments

Comments
 (0)