From b5a9e5b91dfb08ee01fd7a68421c78e2dc73c9c9 Mon Sep 17 00:00:00 2001 From: Luke Yang Date: Thu, 14 Mar 2024 10:59:13 -0400 Subject: [PATCH] add /usr/lib/bootc/kargs.d support Fixes https://github.com/containers/bootc/issues/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 - push the contents of each file (kargs) into a vector containing all the desired kargs - pass the kargs to the stage() function Signed-off-by: Luke Yang --- lib/src/cli.rs | 19 ++++++++++++++++--- lib/src/deploy.rs | 41 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/lib/src/cli.rs b/lib/src/cli.rs index 7d0fbea62..e0d064126 100644 --- a/lib/src/cli.rs +++ b/lib/src/cli.rs @@ -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:?}"); @@ -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.pop(); 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)? { @@ -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 @@ -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.pop(); s.as_str() }).collect(); + opts.override_kernel_argv = Some(kargs.as_slice()); + crate::deploy::stage(sysroot, &stateroot, &fetched, &new_spec, Some(opts)).await?; Ok(()) } @@ -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.pop(); s.as_str() }).collect(); + opts.override_kernel_argv = Some(kargs.as_slice()); + crate::deploy::stage(sysroot, &stateroot, &fetched, &new_spec, Some(opts)).await?; Ok(()) } diff --git a/lib/src/deploy.rs b/lib/src/deploy.rs index 90c5e40aa..2bad90c64 100644 --- a/lib/src/deploy.rs +++ b/lib/src/deploy.rs @@ -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; @@ -219,8 +222,10 @@ async fn deploy( stateroot: &str, image: &ImageState, origin: &glib::KeyFile, + opts: Option>, ) -> 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( @@ -228,7 +233,7 @@ async fn deploy( image.ostree_commit.as_str(), Some(origin), merge_deployment, - &Default::default(), + &opts, cancellable, )?; Ok(()) @@ -253,6 +258,7 @@ pub(crate) async fn stage( stateroot: &str, image: &ImageState, spec: &RequiredHostSpec<'_>, + opts: Option>, ) -> Result<()> { let merge_deployment = sysroot.merge_deployment(Some(stateroot)); let origin = origin_from_imageref(spec.image)?; @@ -262,6 +268,7 @@ pub(crate) async fn stage( stateroot, image, &origin, + opts, ) .await?; crate::deploy::cleanup(sysroot).await?; @@ -340,6 +347,38 @@ pub(crate) fn switch_origin_inplace(root: &Dir, imgref: &ImageReference) -> Resu Ok(newest_deployment) } +pub fn get_kargs(repo: &ostree::Repo, fetched: &ImageState) -> Result> { + let cancellable = gio::Cancellable::NONE; + 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::().expect("downcast"); + match fetched_tree.ensure_resolved() { + std::result::Result::Ok(()) => {} + Err(_) => { + return Ok(vec![]); + } + } + + let mut kargs = 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::().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)?; + kargs.push(s); + } + Ok(kargs) +} + #[test] fn test_switch_inplace() -> Result<()> { use cap_std::fs::DirBuilderExt;