Skip to content

Commit

Permalink
add /usr/lib/bootc/kargs.d support
Browse files Browse the repository at this point in the history
Fixes #255. Allows users to
create files within /usr/lib/bootc/kargs.d with kernel arguments. These
arguments can now be applied on a switch, upgrade, or edit.

General process:
- use ostree commit of fetched container image to return
the file tree
- navigate to /usr/lib/bootc/kargs.d
- read each file within the directory
- calculate the diff between the booted and fetched kargs in kargs.d
- apply the diff to the kargs currently on the running system
- pass the kargs to the stage() function

Signed-off-by: Luke Yang <luyang@redhat.com>
  • Loading branch information
lukewarmtemp committed Mar 21, 2024
1 parent 5390a98 commit 4e0e48d
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 4 deletions.
19 changes: 16 additions & 3 deletions lib/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> {
}
} else {
let fetched = crate::deploy::pull(sysroot, imgref, opts.quiet).await?;
let mut kargs = crate::deploy::get_kargs(repo, 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 @@ -378,7 +379,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_mut().map(|s| s.as_str() ).collect();
opts.override_kernel_argv = Some(kargs.as_slice());
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 @@ -451,6 +455,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 mut kargs = crate::deploy::get_kargs(repo, fetched.as_ref())?;

if !opts.retain {
// By default, we prune the previous ostree ref so it will go away after later upgrades
Expand All @@ -464,7 +469,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_mut().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 @@ -493,11 +501,16 @@ async fn edit(opts: EditOpts) -> Result<()> {
}
let new_spec = RequiredHostSpec::from_spec(&new_host.spec)?;
let fetched = crate::deploy::pull(sysroot, new_spec.image, opts.quiet).await?;
let repo = &sysroot.repo();
let mut kargs = crate::deploy::get_kargs(repo, 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_mut().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
67 changes: 66 additions & 1 deletion lib/src/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ use ostree_ext::container::store::PrepareResult;
use ostree_ext::ostree;
use ostree_ext::ostree::Deployment;
use ostree_ext::sysroot::SysrootLock;
use ostree_ext::prelude::FileExt;
use ostree_ext::prelude::Cast;
use ostree_ext::prelude::FileEnumeratorExt;

use crate::spec::HostSpec;
use crate::spec::ImageReference;
Expand Down Expand Up @@ -219,16 +222,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::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 @@ -253,6 +258,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 @@ -262,6 +268,7 @@ pub(crate) async fn stage(
stateroot,
image,
&origin,
opts,
)
.await?;
crate::deploy::cleanup(sysroot).await?;
Expand Down Expand Up @@ -340,6 +347,64 @@ pub(crate) fn switch_origin_inplace(root: &Dir, imgref: &ImageReference) -> Resu
Ok(newest_deployment)
}

pub fn get_kargs(repo: &ostree::Repo, fetched: &ImageState) -> Result<Vec<String>> {
let cancellable = gio::Cancellable::NONE;

// Get the running kernel commandline arguments
let kargs = ostree::KernelArgs::new();
ostree::KernelArgs::append_proc_cmdline(
&kargs,
cancellable,
)?;
let mut kargs: Vec<String> = kargs.to_strv().iter().map(|s| { s.as_str().to_string() }).collect();

// Get the kargs in kargs.d of the booted system
let mut existing_kargs = vec![];
let fragments = liboverdrop::scan(&["/usr/lib"], "bootc/kargs.d", &["toml"], true);
for (_name, path) in fragments {
let buffer = std::fs::read_to_string(&path)?;
existing_kargs.push(buffer.trim().to_string());
}

// Get the kargs in kargs.d of the remote image
let mut remote_kargs = vec![];
let (fetched_tree, _) = repo.read_commit(fetched.ostree_commit.as_str(), cancellable)?;
let fetched_tree = fetched_tree.resolve_relative_path("/usr/lib/bootc/kargs.d");
let fetched_tree = fetched_tree.downcast::<ostree::RepoFile>().expect("downcast");
match fetched_tree.query_exists(cancellable) {
true => {}
false => {
return Ok(vec![]);
}
}
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)? {
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 buffer = vec![];
let mut reader = ostree_ext::prelude::InputStreamExtManual::into_read(file_content.unwrap());
let _ = std::io::Read::read_to_end(&mut reader, &mut buffer);
let s = std::string::String::from_utf8(buffer)?;
remote_kargs.push(s.trim().to_string());
}

// 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();

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

Ok(kargs)
}

#[test]
fn test_switch_inplace() -> Result<()> {
use cap_std::fs::DirBuilderExt;
Expand Down

0 comments on commit 4e0e48d

Please sign in to comment.