Skip to content

Commit 4196722

Browse files
committed
WIP: Switch bootc_kargs to use kernel_cmdline for parsing
1 parent 9962bba commit 4196722

File tree

4 files changed

+101
-57
lines changed

4 files changed

+101
-57
lines changed

crates/lib/src/bootc_kargs.rs

Lines changed: 86 additions & 47 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,17 +47,20 @@ 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 = Default::default();
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
}
@@ -78,7 +82,7 @@ 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 = Default::default();
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,23 +167,21 @@ 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

171177
// get the diff between the existing and remote kargs
172-
let mut added_kargs = remote_kargs
173-
.clone()
178+
let added_kargs = &remote_kargs
174179
.into_iter()
175-
.filter(|item| !existing_kargs.contains(item))
180+
.filter(|item| existing_kargs.find(&item.key()).is_none())
176181
.collect::<Vec<_>>();
177-
let removed_kargs = existing_kargs
178-
.clone()
182+
let removed_kargs = &existing_kargs
179183
.into_iter()
180-
.filter(|item| !remote_kargs.contains(item))
184+
.filter(|item| remote_kargs.find(&item.key()).is_none())
181185
.collect::<Vec<_>>();
182186

183187
tracing::debug!(
@@ -187,24 +191,33 @@ pub(crate) fn get_kargs(
187191
);
188192

189193
// apply the diff to the system kargs
190-
kargs.retain(|x| !removed_kargs.contains(x));
191-
kargs.append(&mut added_kargs);
194+
//kargs.retain(|x| !removed_kargs.contains(x));
195+
for arg in removed_kargs {
196+
kargs.remove(&arg.key());
197+
}
198+
199+
//kargs.extend(added_kargs.iter().map(|p| *p));
200+
kargs.extend(added_kargs.iter().cloned());
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,24 @@ mod tests {
216229

217230
use super::*;
218231

232+
use bootc_kernel_cmdline::utf8::Parameter;
233+
219234
#[test]
220235
/// Verify that kargs are only applied to supported architectures
221236
fn test_arch() {
222237
// no arch specified, kargs ensure that kargs are applied unconditionally
223238
let sys_arch = "x86_64";
224239
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"]);
240+
let parsed_kargs = parse_kargs_toml(&file_content, sys_arch).unwrap().unwrap();
241+
let mut iter = parsed_kargs.iter();
242+
assert_eq!(iter.next(), Some(Parameter::parse("console=tty0").unwrap()));
243+
assert_eq!(iter.next(), Some(Parameter::parse("nosmt").unwrap()));
244+
227245
let sys_arch = "aarch64";
228-
let parsed_kargs = parse_kargs_toml(&file_content, sys_arch).unwrap();
229-
assert_eq!(parsed_kargs, ["console=tty0", "nosmt"]);
246+
let parsed_kargs = parse_kargs_toml(&file_content, sys_arch).unwrap().unwrap();
247+
let mut iter = parsed_kargs.iter();
248+
assert_eq!(iter.next(), Some(Parameter::parse("console=tty0").unwrap()));
249+
assert_eq!(iter.next(), Some(Parameter::parse("nosmt").unwrap()));
230250

231251
// one arch matches and one doesn't, ensure that kargs are only applied for the matching arch
232252
let sys_arch = "aarch64";
@@ -235,25 +255,32 @@ match-architectures = ["x86_64"]
235255
"##
236256
.to_string();
237257
let parsed_kargs = parse_kargs_toml(&file_content, sys_arch).unwrap();
238-
assert_eq!(parsed_kargs, [] as [String; 0]);
258+
assert!(parsed_kargs.is_none());
239259
let file_content = r##"kargs = ["console=tty0", "nosmt"]
240260
match-architectures = ["aarch64"]
241261
"##
242262
.to_string();
243-
let parsed_kargs = parse_kargs_toml(&file_content, sys_arch).unwrap();
244-
assert_eq!(parsed_kargs, ["console=tty0", "nosmt"]);
263+
let parsed_kargs = parse_kargs_toml(&file_content, sys_arch).unwrap().unwrap();
264+
let mut iter = parsed_kargs.iter();
265+
assert_eq!(iter.next(), Some(Parameter::parse("console=tty0").unwrap()));
266+
assert_eq!(iter.next(), Some(Parameter::parse("nosmt").unwrap()));
245267

246268
// multiple arch specified, ensure that kargs are applied to both archs
247269
let sys_arch = "x86_64";
248270
let file_content = r##"kargs = ["console=tty0", "nosmt"]
249271
match-architectures = ["x86_64", "aarch64"]
250272
"##
251273
.to_string();
252-
let parsed_kargs = parse_kargs_toml(&file_content, sys_arch).unwrap();
253-
assert_eq!(parsed_kargs, ["console=tty0", "nosmt"]);
274+
let parsed_kargs = parse_kargs_toml(&file_content, sys_arch).unwrap().unwrap();
275+
let mut iter = parsed_kargs.iter();
276+
assert_eq!(iter.next(), Some(Parameter::parse("console=tty0").unwrap()));
277+
assert_eq!(iter.next(), Some(Parameter::parse("nosmt").unwrap()));
278+
254279
let sys_arch = "aarch64";
255-
let parsed_kargs = parse_kargs_toml(&file_content, sys_arch).unwrap();
256-
assert_eq!(parsed_kargs, ["console=tty0", "nosmt"]);
280+
let parsed_kargs = parse_kargs_toml(&file_content, sys_arch).unwrap().unwrap();
281+
let mut iter = parsed_kargs.iter();
282+
assert_eq!(iter.next(), Some(Parameter::parse("console=tty0").unwrap()));
283+
assert_eq!(iter.next(), Some(Parameter::parse("nosmt").unwrap()));
257284
}
258285

259286
#[test]
@@ -285,18 +312,24 @@ match-architectures = ["x86_64", "aarch64"]
285312
let td = cap_std_ext::cap_tempfile::TempDir::new(cap_std::ambient_authority())?;
286313

287314
// No directory
288-
assert_eq!(get_kargs_in_root(&td, "x86_64").unwrap().len(), 0);
315+
assert_eq!(get_kargs_in_root(&td, "x86_64").unwrap().iter().count(), 0);
289316
// Empty directory
290317
td.create_dir_all("usr/lib/bootc/kargs.d")?;
291-
assert_eq!(get_kargs_in_root(&td, "x86_64").unwrap().len(), 0);
318+
assert_eq!(get_kargs_in_root(&td, "x86_64").unwrap().iter().count(), 0);
292319
// Non-toml file
293320
td.write("usr/lib/bootc/kargs.d/somegarbage", "garbage")?;
294-
assert_eq!(get_kargs_in_root(&td, "x86_64").unwrap().len(), 0);
321+
assert_eq!(get_kargs_in_root(&td, "x86_64").unwrap().iter().count(), 0);
295322

296323
write_test_kargs(&td)?;
297324

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

301334
Ok(())
302335
}
@@ -354,7 +387,7 @@ match-architectures = ["x86_64", "aarch64"]
354387

355388
ostree_commit(repo, &test_rootfs, ".".into(), "testref")?;
356389
// Helper closure to read the kargs
357-
let get_kargs = |sys_arch: &str| -> Result<Vec<String>> {
390+
let get_kargs = |sys_arch: &str| -> Result<Cmdline<'static>> {
358391
let rootfs = repo.read_commit("testref", cancellable)?.0;
359392
let rootfs = rootfs.downcast_ref::<ostree::RepoFile>().unwrap();
360393
let fetched_tree = rootfs.resolve_relative_path("/usr/lib/bootc/kargs.d");
@@ -368,14 +401,20 @@ match-architectures = ["x86_64", "aarch64"]
368401
};
369402

370403
// rootfs is empty
371-
assert_eq!(get_kargs("x86_64").unwrap().len(), 0);
404+
assert_eq!(get_kargs("x86_64").unwrap().iter().count(), 0);
372405

373406
test_rootfs.create_dir_all("usr/lib/bootc/kargs.d")?;
374407
write_test_kargs(&test_rootfs).unwrap();
375408
ostree_commit(repo, &test_rootfs, ".".into(), "testref")?;
376409

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

380419
Ok(())
381420
}

crates/lib/src/deploy.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -608,12 +608,16 @@ async fn deploy(
608608
let mut opts = ostree::SysrootDeployTreeOpts::default();
609609

610610
// 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+
// that borrows from owned strings.
612+
let override_kargs_owned: Option<Vec<String>> =
613+
override_kargs.map(|v| v.iter().map(|s| s.to_string()).collect());
614+
let override_kargs_refs: Option<Vec<&str>> = override_kargs_owned
615+
.as_ref()
616+
.map(|v| v.iter().map(|s| s.as_str()).collect());
617+
if let Some(kargs) = override_kargs_refs.as_deref() {
618+
opts.override_kernel_argv = Some(&kargs);
619+
}
620+
617621
let deployments = ostree.deployments();
618622
let merge_deployment = merge_deployment.map(|m| &deployments[m]);
619623
let origin = glib::KeyFile::new();

crates/lib/src/install.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -905,7 +905,7 @@ async fn install_container(
905905
merged_ostree_root.downcast_ref().unwrap(),
906906
std::env::consts::ARCH,
907907
)?;
908-
let kargsd = kargsd.iter().map(|s| s.as_str());
908+
let kargsd: Vec<String> = kargsd.iter().map(|s| s.to_string()).collect();
909909

910910
// If the target uses aboot, then we need to set that bootloader in the ostree
911911
// config before deploying the commit
@@ -944,7 +944,7 @@ async fn install_container(
944944
.iter()
945945
.map(|v| v.as_str())
946946
.chain(install_config_kargs)
947-
.chain(kargsd)
947+
.chain(kargsd.iter().map(|s| s.as_str()))
948948
.chain(state.config_opts.karg.iter().flatten().map(|v| v.as_str()))
949949
.collect::<Vec<_>>();
950950
let mut options = ostree_container::deploy::DeployOpts::default();

crates/lib/src/install/completion.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,11 @@ fn reconcile_kargs(sysroot: &ostree::Sysroot, deployment: &ostree::Deployment) -
6767
.map(|s| s.as_str())
6868
.collect::<Vec<_>>();
6969
let kargsd = crate::bootc_kargs::get_kargs_in_root(deployment_root, std::env::consts::ARCH)?;
70-
let kargsd = kargsd.iter().map(|s| s.as_str()).collect::<Vec<_>>();
70+
let kargsd_strings: Vec<String> = kargsd.iter().map(|p| p.to_string()).collect();
71+
let kargsd_strs: Vec<&str> = kargsd_strings.iter().map(|s| s.as_str()).collect();
7172

7273
current_kargs.append_argv(&install_config_kargs);
73-
current_kargs.append_argv(&kargsd);
74+
current_kargs.append_argv(&kargsd_strs);
7475
let new_kargs = current_kargs.to_string();
7576
tracing::debug!("new_kargs={new_kargs}");
7677

0 commit comments

Comments
 (0)