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 <[email protected]>
  • Loading branch information
lukewarmtemp committed May 9, 2024
1 parent 3b3e437 commit fc170bb
Show file tree
Hide file tree
Showing 5 changed files with 406 additions and 8 deletions.
19 changes: 16 additions & 3 deletions lib/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,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 @@ -460,7 +461,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());
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 @@ -533,6 +537,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 @@ -546,7 +551,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 @@ -591,11 +599,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 @@ -221,16 +221,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 @@ -255,6 +257,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 @@ -264,6 +267,7 @@ pub(crate) async fn stage(
stateroot,
image,
&origin,
opts,
)
.await?;
crate::deploy::cleanup(sysroot).await?;
Expand Down
230 changes: 226 additions & 4 deletions lib/src/install/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ pub(crate) struct InstallConfiguration {
/// Kernel arguments, applied at installation time
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) kargs: Option<Vec<String>>,
pub(crate) arch: Option<Vec<String>>,
}

fn merge_basic<T>(s: &mut Option<T>, o: Option<T>) {
Expand Down Expand Up @@ -100,9 +101,19 @@ impl Mergeable for InstallConfiguration {
merge_basic(&mut self.block, other.block);
self.filesystem.merge(other.filesystem);
if let Some(other_kargs) = other.kargs {
self.kargs
.get_or_insert_with(Default::default)
.extend(other_kargs)
// if arch is specified, only apply kargs if it matches the current arch
// if arch is not specified, apply kargs unconditionally
if let Some(other_arch) = other.arch {
if other_arch.contains(&std::env::consts::ARCH.to_string()) {
self.kargs
.get_or_insert_with(Default::default)
.extend(other_kargs.clone());
}
} else {
self.kargs
.get_or_insert_with(Default::default)
.extend(other_kargs)
}
}
}
}
Expand Down Expand Up @@ -161,7 +172,7 @@ pub(crate) fn load_config() -> Result<Option<InstallConfiguration>> {
let buf = std::fs::read_to_string(&path)?;
let mut unused = std::collections::HashSet::new();
let de = toml::Deserializer::new(&buf);
let c: InstallConfigurationToplevel = serde_ignored::deserialize(de, |path| {
let mut c: InstallConfigurationToplevel = serde_ignored::deserialize(de, |path| {
unused.insert(path.to_string());
})
.with_context(|| format!("Parsing {path:?}"))?;
Expand All @@ -174,6 +185,15 @@ pub(crate) fn load_config() -> Result<Option<InstallConfiguration>> {
config.merge(install);
}
} else {
// If arch is specified and doesn't match the current arch, remove kargs
// We need to add this since the merge function isn't applied for the first config file
if let Some(ref mut install) = c.install {
if let Some(arch) = install.arch.as_ref() {
if !arch.contains(&std::env::consts::ARCH.to_string()) {
install.kargs = None;
}
}
}
config = c.install;
}
}
Expand Down Expand Up @@ -319,3 +339,205 @@ block = ["tpm2-luks"]"##,
// And verify passing a disallowed config is an error
assert!(install.get_block_setup(Some(BlockSetup::Direct)).is_err());
}

#[test]
/// Verify that kargs are only applied to supported architectures
fn test_arch() {
// no arch specified, kargs ensure that kargs are applied unconditionally
std::env::set_var("ARCH", "x86_64");
let c: InstallConfigurationToplevel = toml::from_str(
r##"[install]
root-fs-type = "xfs"
"##,
)
.unwrap();
let mut install = c.install.unwrap();
let other = InstallConfigurationToplevel {
install: Some(InstallConfiguration {
kargs: Some(
["console=tty0", "nosmt"]
.into_iter()
.map(ToOwned::to_owned)
.collect(),
),
..Default::default()
}),
};
install.merge(other.install.unwrap());
assert_eq!(
install.kargs,
Some(
["console=tty0", "nosmt"]
.into_iter()
.map(ToOwned::to_owned)
.collect()
)
);
std::env::set_var("ARCH", "aarch64");
let c: InstallConfigurationToplevel = toml::from_str(
r##"[install]
root-fs-type = "xfs"
"##,
)
.unwrap();
let mut install = c.install.unwrap();
let other = InstallConfigurationToplevel {
install: Some(InstallConfiguration {
kargs: Some(
["console=tty0", "nosmt"]
.into_iter()
.map(ToOwned::to_owned)
.collect(),
),
..Default::default()
}),
};
install.merge(other.install.unwrap());
assert_eq!(
install.kargs,
Some(
["console=tty0", "nosmt"]
.into_iter()
.map(ToOwned::to_owned)
.collect()
)
);

// one arch matches and one doesn't, ensure that kargs are only applied for the matching arch
std::env::set_var("ARCH", "x86_64");
let c: InstallConfigurationToplevel = toml::from_str(
r##"[install]
root-fs-type = "xfs"
"##,
)
.unwrap();
let mut install = c.install.unwrap();
let other = InstallConfigurationToplevel {
install: Some(InstallConfiguration {
kargs: Some(
["console=ttyS0", "foo=bar"]
.into_iter()
.map(ToOwned::to_owned)
.collect(),
),
arch: Some(
["x86_64"]
.into_iter()
.map(ToOwned::to_owned)
.collect(),
),
..Default::default()
}),
};
install.merge(other.install.unwrap());
assert_eq!(
install.kargs,
Some(
["console=ttyS0", "foo=bar"]
.into_iter()
.map(ToOwned::to_owned)
.collect()
)
);
let other = InstallConfigurationToplevel {
install: Some(InstallConfiguration {
kargs: Some(
["console=tty0", "nosmt"]
.into_iter()
.map(ToOwned::to_owned)
.collect(),
),
arch: Some(
["aarch64"]
.into_iter()
.map(ToOwned::to_owned)
.collect(),
),
..Default::default()
}),
};
install.merge(other.install.unwrap());
assert_eq!(
install.kargs,
Some(
["console=ttyS0", "foo=bar"]
.into_iter()
.map(ToOwned::to_owned)
.collect()
)
);

// multiple arch specified, ensure that kargs are applied to both archs
std::env::set_var("ARCH", "x86_64");
let c: InstallConfigurationToplevel = toml::from_str(
r##"[install]
root-fs-type = "xfs"
"##,
)
.unwrap();
let mut install = c.install.unwrap();
let other = InstallConfigurationToplevel {
install: Some(InstallConfiguration {
kargs: Some(
["console=tty0", "nosmt"]
.into_iter()
.map(ToOwned::to_owned)
.collect(),
),
arch: Some(
["x86_64", "aarch64"]
.into_iter()
.map(ToOwned::to_owned)
.collect(),
),
..Default::default()
}),
};
std::env::set_var("ARCH", "x86_64");
install.merge(other.install.unwrap());
assert_eq!(
install.kargs,
Some(
["console=tty0", "nosmt"]
.into_iter()
.map(ToOwned::to_owned)
.collect()
)
);
std::env::set_var("ARCH", "aarch64");
let c: InstallConfigurationToplevel = toml::from_str(
r##"[install]
root-fs-type = "xfs"
"##,
)
.unwrap();
let mut install = c.install.unwrap();
let other = InstallConfigurationToplevel {
install: Some(InstallConfiguration {
kargs: Some(
["console=tty0", "nosmt"]
.into_iter()
.map(ToOwned::to_owned)
.collect(),
),
arch: Some(
["x86_64", "aarch64"]
.into_iter()
.map(ToOwned::to_owned)
.collect(),
),
..Default::default()
}),
};
std::env::set_var("ARCH", "x86_64");
install.merge(other.install.unwrap());
assert_eq!(
install.kargs,
Some(
["console=tty0", "nosmt"]
.into_iter()
.map(ToOwned::to_owned)
.collect()
)
);
}
Loading

0 comments on commit fc170bb

Please sign in to comment.