Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add /usr/lib/bootc/kargs.d support #401

Merged
merged 1 commit into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions lib/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,7 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> {
}
} else {
let fetched = crate::deploy::pull(sysroot, imgref, opts.quiet).await?;
let kargs = crate::kargs::get_kargs(repo, &booted_deployment, fetched.as_ref())?;
let staged_digest = staged_image.as_ref().map(|s| s.image_digest.as_str());
let fetched_digest = fetched.manifest_digest.as_str();
tracing::debug!("staged: {staged_digest:?}");
Expand All @@ -452,7 +453,10 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> {
println!("No update available.")
} else {
let osname = booted_deployment.osname();
crate::deploy::stage(sysroot, &osname, &fetched, &spec).await?;
let mut opts = ostree::SysrootDeployTreeOpts::default();
let kargs: Vec<&str> = kargs.iter().map(|s| s.as_str()).collect();
opts.override_kernel_argv = Some(kargs.as_slice());
lukewarmtemp marked this conversation as resolved.
Show resolved Hide resolved
crate::deploy::stage(sysroot, &osname, &fetched, &spec, Some(opts)).await?;
changed = true;
if let Some(prev) = booted_image.as_ref() {
if let Some(fetched_manifest) = fetched.get_manifest(repo)? {
Expand Down Expand Up @@ -525,6 +529,7 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
let new_spec = RequiredHostSpec::from_spec(&new_spec)?;

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

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

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

Ok(())
}
Expand Down Expand Up @@ -583,11 +591,16 @@ async fn edit(opts: EditOpts) -> Result<()> {
}

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

// TODO gc old layers here

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

Ok(())
}
Expand Down
6 changes: 5 additions & 1 deletion lib/src/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,16 +278,18 @@ async fn deploy(
stateroot: &str,
image: &ImageState,
origin: &glib::KeyFile,
opts: Option<ostree::SysrootDeployTreeOpts<'_>>,
) -> Result<()> {
let stateroot = Some(stateroot);
let opts = opts.unwrap_or_default();
// Copy to move into thread
let cancellable = gio::Cancellable::NONE;
let _new_deployment = sysroot.stage_tree_with_options(
stateroot,
image.ostree_commit.as_str(),
Some(origin),
merge_deployment,
&Default::default(),
&opts,
cancellable,
)?;
Ok(())
Expand All @@ -312,6 +314,7 @@ pub(crate) async fn stage(
stateroot: &str,
image: &ImageState,
spec: &RequiredHostSpec<'_>,
opts: Option<ostree::SysrootDeployTreeOpts<'_>>,
) -> Result<()> {
let merge_deployment = sysroot.merge_deployment(Some(stateroot));
let origin = origin_from_imageref(spec.image)?;
Expand All @@ -321,6 +324,7 @@ pub(crate) async fn stage(
stateroot,
image,
&origin,
opts,
)
.await?;
crate::deploy::cleanup(sysroot).await?;
Expand Down
168 changes: 168 additions & 0 deletions lib/src/kargs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
use anyhow::Ok;
use anyhow::Result;

use crate::deploy::ImageState;
use ostree::gio;
use ostree_ext::ostree;
use ostree_ext::ostree::Deployment;
use ostree_ext::prelude::Cast;
use ostree_ext::prelude::FileEnumeratorExt;
use ostree_ext::prelude::FileExt;

use serde::Deserialize;

#[derive(Deserialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
struct Config {
kargs: Vec<String>,
match_architectures: Option<Vec<String>>,
}

/// Compute the kernel arguments for the new deployment. This starts from the booted
/// karg, but applies the diff between the bootc karg files in /usr/lib/bootc/kargs.d
/// between the booted deployment and the new one.
pub(crate) fn get_kargs(
lukewarmtemp marked this conversation as resolved.
Show resolved Hide resolved
repo: &ostree::Repo,
booted_deployment: &Deployment,
fetched: &ImageState,
) -> Result<Vec<String>> {
let cancellable = gio::Cancellable::NONE;
let mut kargs: Vec<String> = vec![];
let sys_arch = std::env::consts::ARCH.to_string();

// Get the running kargs of the booted system
if let Some(bootconfig) = ostree::Deployment::bootconfig(booted_deployment) {
Comment on lines +33 to +34
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks correct, though I would say we should probably add a convenience helper to this in ostree.

if let Some(options) = ostree::BootconfigParser::get(&bootconfig, "options") {
let options: Vec<&str> = options.split_whitespace().collect();
lukewarmtemp marked this conversation as resolved.
Show resolved Hide resolved
let mut options: Vec<String> = options.into_iter().map(|s| s.to_string()).collect();
kargs.append(&mut options);
}
};

// Get the kargs in kargs.d of the booted system
let mut existing_kargs: Vec<String> = vec![];
let fragments = liboverdrop::scan(&["/usr/lib"], "bootc/kargs.d", &["toml"], true);
for (_name, path) in fragments {
let s = std::fs::read_to_string(&path)?;
let mut parsed_kargs = parse_file(s.clone(), sys_arch.clone())?;
existing_kargs.append(&mut parsed_kargs);
}

// Get the kargs in kargs.d of the pending image
let mut remote_kargs: Vec<String> = vec![];
let (fetched_tree, _) = repo.read_commit(fetched.ostree_commit.as_str(), cancellable)?;
cgwalters marked this conversation as resolved.
Show resolved Hide resolved
let fetched_tree = fetched_tree.resolve_relative_path("/usr/lib/bootc/kargs.d");
let fetched_tree = fetched_tree
.downcast::<ostree::RepoFile>()
.expect("downcast");
if !fetched_tree.query_exists(cancellable) {
// if the kargs.d directory does not exist in the fetched image, return the existing kargs
kargs.append(&mut existing_kargs);
return Ok(kargs);
}
let queryattrs = "standard::name,standard::type";
let queryflags = gio::FileQueryInfoFlags::NOFOLLOW_SYMLINKS;
let fetched_iter = fetched_tree.enumerate_children(queryattrs, queryflags, cancellable)?;
while let Some(fetched_info) = fetched_iter.next_file(cancellable)? {
lukewarmtemp marked this conversation as resolved.
Show resolved Hide resolved
// only read and parse the file if it is a toml file
let name = fetched_info.name();
if let Some(name) = name.to_str() {
if name.ends_with(".toml") {
let fetched_child = fetched_iter.child(&fetched_info);
let fetched_child = fetched_child
.downcast::<ostree::RepoFile>()
.expect("downcast");
fetched_child.ensure_resolved()?;
let fetched_contents_checksum = fetched_child.checksum();
let f =
ostree::Repo::load_file(repo, fetched_contents_checksum.as_str(), cancellable)?;
let file_content = f.0;
let mut reader =
ostree_ext::prelude::InputStreamExtManual::into_read(file_content.unwrap());
let s = std::io::read_to_string(&mut reader)?;
let mut parsed_kargs = parse_file(s.clone(), sys_arch.clone())?;
remote_kargs.append(&mut parsed_kargs);
}
}
}

// get the diff between the existing and remote kargs
let mut added_kargs: Vec<String> = remote_kargs
.clone()
.into_iter()
.filter(|item| !existing_kargs.contains(item))
.collect();
let removed_kargs: Vec<String> = existing_kargs
.clone()
.into_iter()
.filter(|item| !remote_kargs.contains(item))
.collect();

lukewarmtemp marked this conversation as resolved.
Show resolved Hide resolved
tracing::debug!(
"kargs: added={:?} removed={:?}",
&added_kargs,
removed_kargs
);

// apply the diff to the system kargs
kargs.retain(|x| !removed_kargs.contains(x));
kargs.append(&mut added_kargs);

Ok(kargs)
}

pub fn parse_file(file_content: String, sys_arch: String) -> Result<Vec<String>> {
let mut de: Config = toml::from_str(&file_content)?;
let mut parsed_kargs: Vec<String> = vec![];
// if arch specified, apply kargs only if the arch matches
// if arch not specified, apply kargs unconditionally
match de.match_architectures {
None => parsed_kargs = de.kargs,
Some(match_architectures) => {
if match_architectures.contains(&sys_arch) {
parsed_kargs.append(&mut de.kargs);
}
}
}
Ok(parsed_kargs)
}

#[test]
/// Verify that kargs are only applied to supported architectures
fn test_arch() {
// no arch specified, kargs ensure that kargs are applied unconditionally
let sys_arch = "x86_64".to_string();
let file_content = r##"kargs = ["console=tty0", "nosmt"]"##.to_string();
let parsed_kargs = parse_file(file_content.clone(), sys_arch.clone()).unwrap();
assert_eq!(parsed_kargs, ["console=tty0", "nosmt"]);
let sys_arch = "aarch64".to_string();
let parsed_kargs = parse_file(file_content.clone(), sys_arch.clone()).unwrap();
assert_eq!(parsed_kargs, ["console=tty0", "nosmt"]);

// one arch matches and one doesn't, ensure that kargs are only applied for the matching arch
let sys_arch = "aarch64".to_string();
let file_content = r##"kargs = ["console=tty0", "nosmt"]
match-architectures = ["x86_64"]
"##
.to_string();
let parsed_kargs = parse_file(file_content.clone(), sys_arch.clone()).unwrap();
assert_eq!(parsed_kargs, [] as [String; 0]);
let file_content = r##"kargs = ["console=tty0", "nosmt"]
match-architectures = ["aarch64"]
"##
.to_string();
let parsed_kargs = parse_file(file_content.clone(), sys_arch.clone()).unwrap();
assert_eq!(parsed_kargs, ["console=tty0", "nosmt"]);

// multiple arch specified, ensure that kargs are applied to both archs
let sys_arch = "x86_64".to_string();
let file_content = r##"kargs = ["console=tty0", "nosmt"]
match-architectures = ["x86_64", "aarch64"]
"##
.to_string();
let parsed_kargs = parse_file(file_content.clone(), sys_arch.clone()).unwrap();
assert_eq!(parsed_kargs, ["console=tty0", "nosmt"]);
std::env::set_var("ARCH", "aarch64");
let parsed_kargs = parse_file(file_content.clone(), sys_arch.clone()).unwrap();
assert_eq!(parsed_kargs, ["console=tty0", "nosmt"]);
}
1 change: 1 addition & 0 deletions lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub mod cli;
pub(crate) mod deploy;
pub(crate) mod generator;
pub(crate) mod journal;
pub(crate) mod kargs;
mod lints;
mod lsm;
pub(crate) mod metadata;
Expand Down