Skip to content

Commit e28c0a1

Browse files
committed
WIP: Switch bootc_kargs to use kernel_cmdline for parsing
1 parent 67ba584 commit e28c0a1

File tree

4 files changed

+133
-94
lines changed

4 files changed

+133
-94
lines changed

crates/lib/src/bootc_kargs.rs

Lines changed: 102 additions & 64 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;
@@ -46,39 +47,42 @@ 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::default();
70+
for param in cmdline {
71+
let key = param.key();
72+
if key.starts_with(ROOT) || key.starts_with(ROOTFLAGS) || key.starts_with(INITRD_ARG_PREFIX)
73+
{
74+
result.add(&param);
75+
}
76+
}
77+
result
7478
}
7579

7680
/// Load kargs.d files from the target ostree commit root
7781
pub(crate) fn get_kargs_from_ostree_root(
7882
repo: &ostree::Repo,
7983
root: &ostree::RepoFile,
8084
sys_arch: &str,
81-
) -> Result<Vec<String>> {
85+
) -> Result<Cmdline<'static>> {
8286
let kargsd = root.resolve_relative_path(KARGS_PATH);
8387
let kargsd = kargsd.downcast_ref::<ostree::RepoFile>().expect("downcast");
8488
if !kargsd.query_exists(gio::Cancellable::NONE) {
@@ -92,12 +96,12 @@ fn get_kargs_from_ostree(
9296
repo: &ostree::Repo,
9397
fetched_tree: &ostree::RepoFile,
9498
sys_arch: &str,
95-
) -> Result<Vec<String>> {
99+
) -> Result<Cmdline<'static>> {
96100
let cancellable = gio::Cancellable::NONE;
97101
let queryattrs = "standard::name,standard::type";
98102
let queryflags = gio::FileQueryInfoFlags::NOFOLLOW_SYMLINKS;
99103
let fetched_iter = fetched_tree.enumerate_children(queryattrs, queryflags, cancellable)?;
100-
let mut ret = Vec::new();
104+
let mut ret = Cmdline::new();
101105
while let Some(fetched_info) = fetched_iter.next_file(cancellable)? {
102106
// only read and parse the file if it is a toml file
103107
let name = fetched_info.name();
@@ -119,9 +123,11 @@ fn get_kargs_from_ostree(
119123
let mut reader =
120124
ostree_ext::prelude::InputStreamExtManual::into_read(file_content.unwrap());
121125
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);
126+
if let Some(parsed_kargs) =
127+
parse_kargs_toml(&s, sys_arch).with_context(|| format!("Parsing {name}"))?
128+
{
129+
ret.extend(&parsed_kargs);
130+
}
125131
}
126132
Ok(ret)
127133
}
@@ -133,20 +139,20 @@ pub(crate) fn get_kargs(
133139
sysroot: &Storage,
134140
merge_deployment: &Deployment,
135141
fetched: &ImageState,
136-
) -> Result<Vec<String>> {
142+
) -> Result<Cmdline<'static>> {
137143
let cancellable = gio::Cancellable::NONE;
138144
let ostree = sysroot.get_ostree()?;
139145
let repo = &ostree.repo();
140-
let mut kargs = vec![];
141146
let sys_arch = std::env::consts::ARCH;
142147

143148
// 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-
};
149+
let mut kargs = ostree::Deployment::bootconfig(merge_deployment)
150+
.map(|bootconfig| {
151+
ostree::BootconfigParser::get(&bootconfig, "options")
152+
.map(|options| Cmdline::from(options.to_string()))
153+
})
154+
.flatten()
155+
.unwrap_or_default();
150156

151157
// Get the kargs in kargs.d of the merge
152158
let merge_root = &crate::utils::deployment_fd(ostree, merge_deployment)?;
@@ -161,50 +167,56 @@ pub(crate) fn get_kargs(
161167
// A special case: if there's no kargs.d directory in the pending (fetched) image,
162168
// then we can just use the combined current kargs + kargs from booted
163169
if !fetched_tree.query_exists(cancellable) {
164-
kargs.extend(existing_kargs);
170+
kargs.extend(&existing_kargs);
165171
return Ok(kargs);
166172
}
167173

168174
// Fetch the kernel arguments from the new root
169175
let remote_kargs = get_kargs_from_ostree(repo, &fetched_tree, sys_arch)?;
170176

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<_>>();
177+
// Calculate the diff between the existing and remote kargs
178+
let added_kargs: Vec<_> = remote_kargs
179+
.iter()
180+
.filter(|item| existing_kargs.find(&item.key()).is_none())
181+
.collect();
182+
let removed_kargs: Vec<_> = existing_kargs
183+
.iter()
184+
.filter(|item| remote_kargs.find(&item.key()).is_none())
185+
.collect();
182186

183187
tracing::debug!(
184188
"kargs: added={:?} removed={:?}",
185189
&added_kargs,
186-
removed_kargs
190+
&removed_kargs
187191
);
188192

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

193201
Ok(kargs)
194202
}
195203

196204
/// This parses a bootc kargs.d toml file, returning the resulting
197205
/// vector of kernel arguments. Architecture matching is performed using
198206
/// `sys_arch`.
199-
fn parse_kargs_toml(contents: &str, sys_arch: &str) -> Result<Vec<String>> {
207+
fn parse_kargs_toml(contents: &str, sys_arch: &str) -> Result<Option<Cmdline<'static>>> {
200208
let de: Config = toml::from_str(contents)?;
201209
// if arch specified, apply kargs only if the arch matches
202210
// if arch not specified, apply kargs unconditionally
203211
let matched = de
204212
.match_architectures
205213
.map(|arches| arches.iter().any(|s| s == sys_arch))
206214
.unwrap_or(true);
207-
let r = if matched { de.kargs } else { Vec::new() };
215+
let r = if matched {
216+
Some(Cmdline::from(de.kargs.join(" ")))
217+
} else {
218+
None
219+
};
208220
Ok(r)
209221
}
210222

@@ -216,17 +228,24 @@ mod tests {
216228

217229
use super::*;
218230

231+
use bootc_kernel_cmdline::utf8::Parameter;
232+
219233
#[test]
220234
/// Verify that kargs are only applied to supported architectures
221235
fn test_arch() {
222236
// no arch specified, kargs ensure that kargs are applied unconditionally
223237
let sys_arch = "x86_64";
224238
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"]);
239+
let parsed_kargs = parse_kargs_toml(&file_content, sys_arch).unwrap().unwrap();
240+
let mut iter = parsed_kargs.iter();
241+
assert_eq!(iter.next(), Some(Parameter::parse("console=tty0").unwrap()));
242+
assert_eq!(iter.next(), Some(Parameter::parse("nosmt").unwrap()));
243+
227244
let sys_arch = "aarch64";
228-
let parsed_kargs = parse_kargs_toml(&file_content, sys_arch).unwrap();
229-
assert_eq!(parsed_kargs, ["console=tty0", "nosmt"]);
245+
let parsed_kargs = parse_kargs_toml(&file_content, sys_arch).unwrap().unwrap();
246+
let mut iter = parsed_kargs.iter();
247+
assert_eq!(iter.next(), Some(Parameter::parse("console=tty0").unwrap()));
248+
assert_eq!(iter.next(), Some(Parameter::parse("nosmt").unwrap()));
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,32 @@ 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+
let mut iter = parsed_kargs.iter();
264+
assert_eq!(iter.next(), Some(Parameter::parse("console=tty0").unwrap()));
265+
assert_eq!(iter.next(), Some(Parameter::parse("nosmt").unwrap()));
245266

246267
// multiple arch specified, ensure that kargs are applied to both archs
247268
let sys_arch = "x86_64";
248269
let file_content = r##"kargs = ["console=tty0", "nosmt"]
249270
match-architectures = ["x86_64", "aarch64"]
250271
"##
251272
.to_string();
252-
let parsed_kargs = parse_kargs_toml(&file_content, sys_arch).unwrap();
253-
assert_eq!(parsed_kargs, ["console=tty0", "nosmt"]);
273+
let parsed_kargs = parse_kargs_toml(&file_content, sys_arch).unwrap().unwrap();
274+
let mut iter = parsed_kargs.iter();
275+
assert_eq!(iter.next(), Some(Parameter::parse("console=tty0").unwrap()));
276+
assert_eq!(iter.next(), Some(Parameter::parse("nosmt").unwrap()));
277+
254278
let sys_arch = "aarch64";
255-
let parsed_kargs = parse_kargs_toml(&file_content, sys_arch).unwrap();
256-
assert_eq!(parsed_kargs, ["console=tty0", "nosmt"]);
279+
let parsed_kargs = parse_kargs_toml(&file_content, sys_arch).unwrap().unwrap();
280+
let mut iter = parsed_kargs.iter();
281+
assert_eq!(iter.next(), Some(Parameter::parse("console=tty0").unwrap()));
282+
assert_eq!(iter.next(), Some(Parameter::parse("nosmt").unwrap()));
257283
}
258284

259285
#[test]
@@ -285,18 +311,24 @@ match-architectures = ["x86_64", "aarch64"]
285311
let td = cap_std_ext::cap_tempfile::TempDir::new(cap_std::ambient_authority())?;
286312

287313
// No directory
288-
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);
289315
// Empty directory
290316
td.create_dir_all("usr/lib/bootc/kargs.d")?;
291-
assert_eq!(get_kargs_in_root(&td, "x86_64").unwrap().len(), 0);
317+
assert_eq!(get_kargs_in_root(&td, "x86_64").unwrap().iter().count(), 0);
292318
// Non-toml file
293319
td.write("usr/lib/bootc/kargs.d/somegarbage", "garbage")?;
294-
assert_eq!(get_kargs_in_root(&td, "x86_64").unwrap().len(), 0);
320+
assert_eq!(get_kargs_in_root(&td, "x86_64").unwrap().iter().count(), 0);
295321

296322
write_test_kargs(&td)?;
297323

298324
let args = get_kargs_in_root(&td, "x86_64").unwrap();
299-
similar_asserts::assert_eq!(args, ["console=tty0", "nosmt", "console=ttyS1"]);
325+
let mut iter = args.iter();
326+
assert_eq!(iter.next(), Some(Parameter::parse("console=tty0").unwrap()));
327+
assert_eq!(iter.next(), Some(Parameter::parse("nosmt").unwrap()));
328+
assert_eq!(
329+
iter.next(),
330+
Some(Parameter::parse("console=ttyS1").unwrap())
331+
);
300332

301333
Ok(())
302334
}
@@ -354,7 +386,7 @@ match-architectures = ["x86_64", "aarch64"]
354386

355387
ostree_commit(repo, &test_rootfs, ".".into(), "testref")?;
356388
// Helper closure to read the kargs
357-
let get_kargs = |sys_arch: &str| -> Result<Vec<String>> {
389+
let get_kargs = |sys_arch: &str| -> Result<Cmdline<'static>> {
358390
let rootfs = repo.read_commit("testref", cancellable)?.0;
359391
let rootfs = rootfs.downcast_ref::<ostree::RepoFile>().unwrap();
360392
let fetched_tree = rootfs.resolve_relative_path("/usr/lib/bootc/kargs.d");
@@ -368,14 +400,20 @@ match-architectures = ["x86_64", "aarch64"]
368400
};
369401

370402
// rootfs is empty
371-
assert_eq!(get_kargs("x86_64").unwrap().len(), 0);
403+
assert_eq!(get_kargs("x86_64").unwrap().iter().count(), 0);
372404

373405
test_rootfs.create_dir_all("usr/lib/bootc/kargs.d")?;
374406
write_test_kargs(&test_rootfs).unwrap();
375407
ostree_commit(repo, &test_rootfs, ".".into(), "testref")?;
376408

377409
let args = get_kargs("x86_64").unwrap();
378-
similar_asserts::assert_eq!(args, ["console=tty0", "nosmt", "console=ttyS1"]);
410+
let mut iter = args.iter();
411+
assert_eq!(iter.next(), Some(Parameter::parse("console=tty0").unwrap()));
412+
assert_eq!(iter.next(), Some(Parameter::parse("nosmt").unwrap()));
413+
assert_eq!(
414+
iter.next(),
415+
Some(Parameter::parse("console=ttyS1").unwrap())
416+
);
379417

380418
Ok(())
381419
}

crates/lib/src/deploy.rs

Lines changed: 11 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,13 @@ 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.as_ref().map(|kargs| kargs.to_str_vec());
614+
if let Some(kargs) = override_kargs_refs.as_ref() {
615+
opts.override_kernel_argv = Some(kargs);
616+
}
617+
617618
let deployments = ostree.deployments();
618619
let merge_deployment = merge_deployment.map(|m| &deployments[m]);
619620
let origin = glib::KeyFile::new();
@@ -658,7 +659,7 @@ pub(crate) enum MergeState {
658659
/// provided initial state.
659660
Reset {
660661
stateroot: String,
661-
kargs: Vec<String>,
662+
kargs: Cmdline<'static>,
662663
},
663664
}
664665
impl MergeState {

0 commit comments

Comments
 (0)