Skip to content

Commit 540794d

Browse files
authored
Merge pull request #401 from lukewarmtemp/luyang-kargs-support
add `/usr/lib/bootc/kargs.d` support
2 parents 55f1bc6 + 0d185b4 commit 540794d

File tree

4 files changed

+190
-4
lines changed

4 files changed

+190
-4
lines changed

lib/src/cli.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,7 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> {
431431
}
432432
} else {
433433
let fetched = crate::deploy::pull(sysroot, imgref, opts.quiet).await?;
434+
let kargs = crate::kargs::get_kargs(repo, &booted_deployment, fetched.as_ref())?;
434435
let staged_digest = staged_image.as_ref().map(|s| s.image_digest.as_str());
435436
let fetched_digest = fetched.manifest_digest.as_str();
436437
tracing::debug!("staged: {staged_digest:?}");
@@ -452,7 +453,10 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> {
452453
println!("No update available.")
453454
} else {
454455
let osname = booted_deployment.osname();
455-
crate::deploy::stage(sysroot, &osname, &fetched, &spec).await?;
456+
let mut opts = ostree::SysrootDeployTreeOpts::default();
457+
let kargs: Vec<&str> = kargs.iter().map(|s| s.as_str()).collect();
458+
opts.override_kernel_argv = Some(kargs.as_slice());
459+
crate::deploy::stage(sysroot, &osname, &fetched, &spec, Some(opts)).await?;
456460
changed = true;
457461
if let Some(prev) = booted_image.as_ref() {
458462
if let Some(fetched_manifest) = fetched.get_manifest(repo)? {
@@ -525,6 +529,7 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
525529
let new_spec = RequiredHostSpec::from_spec(&new_spec)?;
526530

527531
let fetched = crate::deploy::pull(sysroot, &target, opts.quiet).await?;
532+
let kargs = crate::kargs::get_kargs(repo, &booted_deployment, fetched.as_ref())?;
528533

529534
if !opts.retain {
530535
// By default, we prune the previous ostree ref so it will go away after later upgrades
@@ -538,7 +543,10 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
538543
}
539544

540545
let stateroot = booted_deployment.osname();
541-
crate::deploy::stage(sysroot, &stateroot, &fetched, &new_spec).await?;
546+
let mut opts = ostree::SysrootDeployTreeOpts::default();
547+
let kargs: Vec<&str> = kargs.iter().map(|s| s.as_str()).collect();
548+
opts.override_kernel_argv = Some(kargs.as_slice());
549+
crate::deploy::stage(sysroot, &stateroot, &fetched, &new_spec, Some(opts)).await?;
542550

543551
Ok(())
544552
}
@@ -583,11 +591,16 @@ async fn edit(opts: EditOpts) -> Result<()> {
583591
}
584592

585593
let fetched = crate::deploy::pull(sysroot, new_spec.image, opts.quiet).await?;
594+
let repo = &sysroot.repo();
595+
let kargs = crate::kargs::get_kargs(repo, &booted_deployment, fetched.as_ref())?;
586596

587597
// TODO gc old layers here
588598

589599
let stateroot = booted_deployment.osname();
590-
crate::deploy::stage(sysroot, &stateroot, &fetched, &new_spec).await?;
600+
let mut opts = ostree::SysrootDeployTreeOpts::default();
601+
let kargs: Vec<&str> = kargs.iter().map(|s| s.as_str()).collect();
602+
opts.override_kernel_argv = Some(kargs.as_slice());
603+
crate::deploy::stage(sysroot, &stateroot, &fetched, &new_spec, Some(opts)).await?;
591604

592605
Ok(())
593606
}

lib/src/deploy.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,16 +278,18 @@ async fn deploy(
278278
stateroot: &str,
279279
image: &ImageState,
280280
origin: &glib::KeyFile,
281+
opts: Option<ostree::SysrootDeployTreeOpts<'_>>,
281282
) -> Result<()> {
282283
let stateroot = Some(stateroot);
284+
let opts = opts.unwrap_or_default();
283285
// Copy to move into thread
284286
let cancellable = gio::Cancellable::NONE;
285287
let _new_deployment = sysroot.stage_tree_with_options(
286288
stateroot,
287289
image.ostree_commit.as_str(),
288290
Some(origin),
289291
merge_deployment,
290-
&Default::default(),
292+
&opts,
291293
cancellable,
292294
)?;
293295
Ok(())
@@ -312,6 +314,7 @@ pub(crate) async fn stage(
312314
stateroot: &str,
313315
image: &ImageState,
314316
spec: &RequiredHostSpec<'_>,
317+
opts: Option<ostree::SysrootDeployTreeOpts<'_>>,
315318
) -> Result<()> {
316319
let merge_deployment = sysroot.merge_deployment(Some(stateroot));
317320
let origin = origin_from_imageref(spec.image)?;
@@ -321,6 +324,7 @@ pub(crate) async fn stage(
321324
stateroot,
322325
image,
323326
&origin,
327+
opts,
324328
)
325329
.await?;
326330
crate::deploy::cleanup(sysroot).await?;

lib/src/kargs.rs

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
use anyhow::Ok;
2+
use anyhow::Result;
3+
4+
use crate::deploy::ImageState;
5+
use ostree::gio;
6+
use ostree_ext::ostree;
7+
use ostree_ext::ostree::Deployment;
8+
use ostree_ext::prelude::Cast;
9+
use ostree_ext::prelude::FileEnumeratorExt;
10+
use ostree_ext::prelude::FileExt;
11+
12+
use serde::Deserialize;
13+
14+
#[derive(Deserialize)]
15+
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
16+
struct Config {
17+
kargs: Vec<String>,
18+
match_architectures: Option<Vec<String>>,
19+
}
20+
21+
/// Compute the kernel arguments for the new deployment. This starts from the booted
22+
/// karg, but applies the diff between the bootc karg files in /usr/lib/bootc/kargs.d
23+
/// between the booted deployment and the new one.
24+
pub(crate) fn get_kargs(
25+
repo: &ostree::Repo,
26+
booted_deployment: &Deployment,
27+
fetched: &ImageState,
28+
) -> Result<Vec<String>> {
29+
let cancellable = gio::Cancellable::NONE;
30+
let mut kargs: Vec<String> = vec![];
31+
let sys_arch = std::env::consts::ARCH.to_string();
32+
33+
// Get the running kargs of the booted system
34+
if let Some(bootconfig) = ostree::Deployment::bootconfig(booted_deployment) {
35+
if let Some(options) = ostree::BootconfigParser::get(&bootconfig, "options") {
36+
let options: Vec<&str> = options.split_whitespace().collect();
37+
let mut options: Vec<String> = options.into_iter().map(|s| s.to_string()).collect();
38+
kargs.append(&mut options);
39+
}
40+
};
41+
42+
// Get the kargs in kargs.d of the booted system
43+
let mut existing_kargs: Vec<String> = vec![];
44+
let fragments = liboverdrop::scan(&["/usr/lib"], "bootc/kargs.d", &["toml"], true);
45+
for (_name, path) in fragments {
46+
let s = std::fs::read_to_string(&path)?;
47+
let mut parsed_kargs = parse_file(s.clone(), sys_arch.clone())?;
48+
existing_kargs.append(&mut parsed_kargs);
49+
}
50+
51+
// Get the kargs in kargs.d of the pending image
52+
let mut remote_kargs: Vec<String> = vec![];
53+
let (fetched_tree, _) = repo.read_commit(fetched.ostree_commit.as_str(), cancellable)?;
54+
let fetched_tree = fetched_tree.resolve_relative_path("/usr/lib/bootc/kargs.d");
55+
let fetched_tree = fetched_tree
56+
.downcast::<ostree::RepoFile>()
57+
.expect("downcast");
58+
if !fetched_tree.query_exists(cancellable) {
59+
// if the kargs.d directory does not exist in the fetched image, return the existing kargs
60+
kargs.append(&mut existing_kargs);
61+
return Ok(kargs);
62+
}
63+
let queryattrs = "standard::name,standard::type";
64+
let queryflags = gio::FileQueryInfoFlags::NOFOLLOW_SYMLINKS;
65+
let fetched_iter = fetched_tree.enumerate_children(queryattrs, queryflags, cancellable)?;
66+
while let Some(fetched_info) = fetched_iter.next_file(cancellable)? {
67+
// only read and parse the file if it is a toml file
68+
let name = fetched_info.name();
69+
if let Some(name) = name.to_str() {
70+
if name.ends_with(".toml") {
71+
let fetched_child = fetched_iter.child(&fetched_info);
72+
let fetched_child = fetched_child
73+
.downcast::<ostree::RepoFile>()
74+
.expect("downcast");
75+
fetched_child.ensure_resolved()?;
76+
let fetched_contents_checksum = fetched_child.checksum();
77+
let f =
78+
ostree::Repo::load_file(repo, fetched_contents_checksum.as_str(), cancellable)?;
79+
let file_content = f.0;
80+
let mut reader =
81+
ostree_ext::prelude::InputStreamExtManual::into_read(file_content.unwrap());
82+
let s = std::io::read_to_string(&mut reader)?;
83+
let mut parsed_kargs = parse_file(s.clone(), sys_arch.clone())?;
84+
remote_kargs.append(&mut parsed_kargs);
85+
}
86+
}
87+
}
88+
89+
// get the diff between the existing and remote kargs
90+
let mut added_kargs: Vec<String> = remote_kargs
91+
.clone()
92+
.into_iter()
93+
.filter(|item| !existing_kargs.contains(item))
94+
.collect();
95+
let removed_kargs: Vec<String> = existing_kargs
96+
.clone()
97+
.into_iter()
98+
.filter(|item| !remote_kargs.contains(item))
99+
.collect();
100+
101+
tracing::debug!(
102+
"kargs: added={:?} removed={:?}",
103+
&added_kargs,
104+
removed_kargs
105+
);
106+
107+
// apply the diff to the system kargs
108+
kargs.retain(|x| !removed_kargs.contains(x));
109+
kargs.append(&mut added_kargs);
110+
111+
Ok(kargs)
112+
}
113+
114+
pub fn parse_file(file_content: String, sys_arch: String) -> Result<Vec<String>> {
115+
let mut de: Config = toml::from_str(&file_content)?;
116+
let mut parsed_kargs: Vec<String> = vec![];
117+
// if arch specified, apply kargs only if the arch matches
118+
// if arch not specified, apply kargs unconditionally
119+
match de.match_architectures {
120+
None => parsed_kargs = de.kargs,
121+
Some(match_architectures) => {
122+
if match_architectures.contains(&sys_arch) {
123+
parsed_kargs.append(&mut de.kargs);
124+
}
125+
}
126+
}
127+
Ok(parsed_kargs)
128+
}
129+
130+
#[test]
131+
/// Verify that kargs are only applied to supported architectures
132+
fn test_arch() {
133+
// no arch specified, kargs ensure that kargs are applied unconditionally
134+
let sys_arch = "x86_64".to_string();
135+
let file_content = r##"kargs = ["console=tty0", "nosmt"]"##.to_string();
136+
let parsed_kargs = parse_file(file_content.clone(), sys_arch.clone()).unwrap();
137+
assert_eq!(parsed_kargs, ["console=tty0", "nosmt"]);
138+
let sys_arch = "aarch64".to_string();
139+
let parsed_kargs = parse_file(file_content.clone(), sys_arch.clone()).unwrap();
140+
assert_eq!(parsed_kargs, ["console=tty0", "nosmt"]);
141+
142+
// one arch matches and one doesn't, ensure that kargs are only applied for the matching arch
143+
let sys_arch = "aarch64".to_string();
144+
let file_content = r##"kargs = ["console=tty0", "nosmt"]
145+
match-architectures = ["x86_64"]
146+
"##
147+
.to_string();
148+
let parsed_kargs = parse_file(file_content.clone(), sys_arch.clone()).unwrap();
149+
assert_eq!(parsed_kargs, [] as [String; 0]);
150+
let file_content = r##"kargs = ["console=tty0", "nosmt"]
151+
match-architectures = ["aarch64"]
152+
"##
153+
.to_string();
154+
let parsed_kargs = parse_file(file_content.clone(), sys_arch.clone()).unwrap();
155+
assert_eq!(parsed_kargs, ["console=tty0", "nosmt"]);
156+
157+
// multiple arch specified, ensure that kargs are applied to both archs
158+
let sys_arch = "x86_64".to_string();
159+
let file_content = r##"kargs = ["console=tty0", "nosmt"]
160+
match-architectures = ["x86_64", "aarch64"]
161+
"##
162+
.to_string();
163+
let parsed_kargs = parse_file(file_content.clone(), sys_arch.clone()).unwrap();
164+
assert_eq!(parsed_kargs, ["console=tty0", "nosmt"]);
165+
std::env::set_var("ARCH", "aarch64");
166+
let parsed_kargs = parse_file(file_content.clone(), sys_arch.clone()).unwrap();
167+
assert_eq!(parsed_kargs, ["console=tty0", "nosmt"]);
168+
}

lib/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pub mod cli;
2121
pub(crate) mod deploy;
2222
pub(crate) mod generator;
2323
pub(crate) mod journal;
24+
pub(crate) mod kargs;
2425
mod lints;
2526
mod lsm;
2627
pub(crate) mod metadata;

0 commit comments

Comments
 (0)