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

kernel-install: integration #5135

Merged
merged 1 commit into from
Jan 9, 2025
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
4 changes: 4 additions & 0 deletions docs/treefile.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ It supports the following parameters:
this for new systems, and systems that don't need to be upgraded
from very old libostree versions. This is the default for editions 2024
and above.
* "kernel-install": The system is integrated with `/sbin/kernel-install`
from systemd. You likely want to additionally pair this with configuring `layout=ostree`
in `/usr/lib/kernel/install.conf`, and adding a wrapper script to
`/usr/lib/kernel/install.d/05-rpmostree.install`

* `etc-group-members`: Array of strings, optional: Unix groups in this
list will be stored in `/etc/group` instead of `/usr/lib/group`. Use
Expand Down
17 changes: 14 additions & 3 deletions rpmostree-cxxrs.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -1783,6 +1783,7 @@ struct Treefile final : public ::rust::Opaque
bool get_machineid_compat () const noexcept;
::rust::Vec< ::rust::String> get_etc_group_members () const noexcept;
bool get_boot_location_is_modules () const noexcept;
bool use_kernel_install () const noexcept;
bool get_ima () const noexcept;
::rust::String get_releasever () const noexcept;
::rpmostreecxx::RepoMetadataTarget get_repo_metadata_target () const noexcept;
Expand Down Expand Up @@ -2432,7 +2433,8 @@ extern "C"
::rpmostreecxx::TokioEnterGuard *
rpmostreecxx$cxxbridge1$TokioHandle$enter (::rpmostreecxx::TokioHandle const &self) noexcept;

bool rpmostreecxx$cxxbridge1$script_is_ignored (::rust::Str pkg, ::rust::Str script) noexcept;
bool rpmostreecxx$cxxbridge1$script_is_ignored (::rust::Str pkg, ::rust::Str script,
bool use_kernel_install) noexcept;

::rust::repr::PtrLen
rpmostreecxx$cxxbridge1$testutils_entrypoint (::rust::Vec< ::rust::String> *argv) noexcept;
Expand Down Expand Up @@ -2633,6 +2635,9 @@ extern "C"
bool rpmostreecxx$cxxbridge1$Treefile$get_boot_location_is_modules (
::rpmostreecxx::Treefile const &self) noexcept;

bool rpmostreecxx$cxxbridge1$Treefile$use_kernel_install (
::rpmostreecxx::Treefile const &self) noexcept;

bool rpmostreecxx$cxxbridge1$Treefile$get_ima (::rpmostreecxx::Treefile const &self) noexcept;

void rpmostreecxx$cxxbridge1$Treefile$get_releasever (::rpmostreecxx::Treefile const &self,
Expand Down Expand Up @@ -4648,9 +4653,9 @@ TokioHandle::enter () const noexcept
}

bool
script_is_ignored (::rust::Str pkg, ::rust::Str script) noexcept
script_is_ignored (::rust::Str pkg, ::rust::Str script, bool use_kernel_install) noexcept
{
return rpmostreecxx$cxxbridge1$script_is_ignored (pkg, script);
return rpmostreecxx$cxxbridge1$script_is_ignored (pkg, script, use_kernel_install);
}

void
Expand Down Expand Up @@ -5203,6 +5208,12 @@ Treefile::get_boot_location_is_modules () const noexcept
return rpmostreecxx$cxxbridge1$Treefile$get_boot_location_is_modules (*this);
}

bool
Treefile::use_kernel_install () const noexcept
{
return rpmostreecxx$cxxbridge1$Treefile$use_kernel_install (*this);
}

bool
Treefile::get_ima () const noexcept
{
Expand Down
3 changes: 2 additions & 1 deletion rpmostree-cxxrs.h
Original file line number Diff line number Diff line change
Expand Up @@ -1560,6 +1560,7 @@ struct Treefile final : public ::rust::Opaque
bool get_machineid_compat () const noexcept;
::rust::Vec< ::rust::String> get_etc_group_members () const noexcept;
bool get_boot_location_is_modules () const noexcept;
bool use_kernel_install () const noexcept;
bool get_ima () const noexcept;
::rust::String get_releasever () const noexcept;
::rpmostreecxx::RepoMetadataTarget get_repo_metadata_target () const noexcept;
Expand Down Expand Up @@ -1976,7 +1977,7 @@ void history_prune ();

::rust::Box< ::rpmostreecxx::TokioHandle> tokio_handle_get () noexcept;

bool script_is_ignored (::rust::Str pkg, ::rust::Str script) noexcept;
bool script_is_ignored (::rust::Str pkg, ::rust::Str script, bool use_kernel_install) noexcept;

void testutils_entrypoint (::rust::Vec< ::rust::String> argv);

Expand Down
26 changes: 21 additions & 5 deletions rust/src/cliwrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,25 @@ use std::io::prelude::*;
mod cliutil;
mod dracut;
mod grubby;
mod kernel_install;
mod kernel_install_wrap;
mod rpm;
mod yumdnf;
use crate::cxxrsutil::CxxResult;
use crate::ffi::SystemHostType;
use crate::ffiutil::*;
use crate::kernel_install::is_ostree_layout;

/// Location for the underlying (not wrapped) binaries.
pub const CLIWRAP_DESTDIR: &str = "usr/libexec/rpm-ostree/wrapped";

/// Kernel install binary we will wrap only if ostree layout is not specified.
const KERNEL_INSTALL: &str = "usr/bin/kernel-install";

/// Binaries that will be wrapped if they exist.
static WRAPPED_BINARIES: &[&str] = &["usr/bin/rpm", "usr/bin/dracut", "usr/sbin/grubby"];

/// Binaries we will wrap, or create if they don't exist.
static MUSTWRAP_BINARIES: &[&str] = &["usr/bin/yum", "usr/bin/dnf", "usr/bin/kernel-install"];
static MUSTWRAP_BINARIES: &[&str] = &["usr/bin/yum", "usr/bin/dnf"];

#[derive(Debug, PartialEq)]
pub(crate) enum RunDisposition {
Expand Down Expand Up @@ -74,7 +78,7 @@ pub fn entrypoint(args: &[&str]) -> Result<()> {
"yum" | "dnf" => Ok(self::yumdnf::main(host_type, args)?),
"dracut" => Ok(self::dracut::main(args)?),
"grubby" => Ok(self::grubby::main(args)?),
"kernel-install" => Ok(self::kernel_install::main(args)?),
"kernel-install" if !is_ostree_layout()? => Ok(self::kernel_install_wrap::main(args)?),
_ => Err(anyhow!("Unknown wrapped binary: {}", name)),
}
} else {
Expand Down Expand Up @@ -153,12 +157,16 @@ fn write_wrappers(rootfs_dfd: &Dir, allowlist: Option<&HashSet<&str>>) -> Result

let all_wrapped = WRAPPED_BINARIES.iter().map(Utf8Path::new);
let all_mustwrap = MUSTWRAP_BINARIES.iter().map(Utf8Path::new);
let all_names = all_wrapped
let mut all_names = all_wrapped
.clone()
.chain(all_mustwrap.clone())
.map(|p| p.file_name().unwrap())
.collect::<HashSet<_>>();

if !is_ostree_layout()? {
all_names.insert(Utf8Path::new(KERNEL_INSTALL).file_name().unwrap());
}

if let Some(allowlist) = allowlist.as_ref() {
for k in allowlist.iter() {
if !all_names.contains(k) {
Expand All @@ -170,7 +178,15 @@ fn write_wrappers(rootfs_dfd: &Dir, allowlist: Option<&HashSet<&str>>) -> Result
let allowlist_contains =
|v: &(&Utf8Path, bool)| allowlist.map_or(true, |l| l.contains(v.0.file_name().unwrap()));

WRAPPED_BINARIES
let final_binaries: Vec<&str> = if !is_ostree_layout()? {
let mut combined_bins = Vec::from(WRAPPED_BINARIES);
combined_bins.extend_from_slice(&[KERNEL_INSTALL]);
combined_bins
} else {
Vec::from(WRAPPED_BINARIES)
};

final_binaries
.par_iter()
.map(|p| (Utf8Path::new(p), true))
.chain(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use crate::cliwrap::cliutil;
use anyhow::Result;
use camino::Utf8Path;
use cap_std::fs::Dir as StdDir;
use cap_std::fs::FileType;
use cap_std::fs_utf8::Dir as Utf8Dir;
use cap_std_ext::cap_std;
Expand Down Expand Up @@ -39,7 +40,8 @@ pub(crate) fn main(argv: &[&str]) -> Result<()> {
}
if let Some(k) = new_kernel {
undo_systemctl_wrap()?;
crate::initramfs::run_dracut(&k)?;
let root_fs = StdDir::open_ambient_dir("/", cap_std::ambient_authority())?;
crate::initramfs::run_dracut(&root_fs, &k)?;
redo_systemctl_wrap()?;
}
Ok(())
Expand Down
42 changes: 31 additions & 11 deletions rust/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

use crate::cxxrsutil::*;
use crate::ffiutil;
use crate::kernel_install::is_ostree_layout;
use anyhow::Context;
use anyhow::{anyhow, Result};
use camino::Utf8Path;
Expand Down Expand Up @@ -166,9 +167,12 @@ impl FilesystemScriptPrep {
(SYSTEMCTL_PATH, SYSTEMCTL_WRAPPER),
(USERADD_PATH, USERADD_WRAPPER),
(USERMOD_PATH, USERMOD_WRAPPER),
(KERNEL_INSTALL_PATH, KERNEL_INSTALL_WRAPPER),
];

// Separate const as is used only to wrap kernel-install if !is_ostree_layout()
const REPLACE_KERNEL_PATHS: &'static [(&'static str, &'static [u8])] =
&[(KERNEL_INSTALL_PATH, KERNEL_INSTALL_WRAPPER)];

fn saved_name(name: &str) -> String {
format!("{}.rpmostreesave", name)
}
Expand All @@ -188,6 +192,16 @@ impl FilesystemScriptPrep {
rootfs.atomic_write_with_perms(path, contents, mode)?;
}
}
if !is_ostree_layout()? {
for &(path, contents) in Self::REPLACE_KERNEL_PATHS {
let mode = Permissions::from_mode(0o755);
let saved = &Self::saved_name(path);
if rootfs.try_exists(path)? {
rootfs.rename(path, &rootfs, saved)?;
rootfs.atomic_write_with_perms(path, contents, mode)?;
}
}
}
Ok(Box::new(Self {
rootfs,
enabled: true,
Expand Down Expand Up @@ -472,16 +486,22 @@ mod test {
}
// Replaced kernel-install.
{
let original_kernel_install = "original kernel_install";
d.atomic_write_with_perms(super::KERNEL_INSTALL_PATH, original_kernel_install, mode)?;
let contents = d.read_to_string(super::KERNEL_INSTALL_PATH)?;
assert_eq!(contents, original_kernel_install);
let mut g = super::prepare_filesystem_script_prep(d.as_raw_fd())?;
let contents = d.read_to_string(super::KERNEL_INSTALL_PATH)?;
assert_eq!(contents.as_bytes(), super::KERNEL_INSTALL_WRAPPER);
g.undo()?;
let contents = d.read_to_string(super::KERNEL_INSTALL_PATH)?;
assert_eq!(contents, original_kernel_install);
if !is_ostree_layout()? {
let original_kernel_install = "original kernel_install";
d.atomic_write_with_perms(
super::KERNEL_INSTALL_PATH,
original_kernel_install,
mode,
)?;
let contents = d.read_to_string(super::KERNEL_INSTALL_PATH)?;
assert_eq!(contents, original_kernel_install);
let mut g = super::prepare_filesystem_script_prep(d.as_raw_fd())?;
let contents = d.read_to_string(super::KERNEL_INSTALL_PATH)?;
assert_eq!(contents.as_bytes(), super::KERNEL_INSTALL_WRAPPER);
g.undo()?;
let contents = d.read_to_string(super::KERNEL_INSTALL_PATH)?;
assert_eq!(contents, original_kernel_install);
}
}
Ok(())
}
Expand Down
5 changes: 2 additions & 3 deletions rust/src/initramfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
use crate::cxxrsutil::*;
use anyhow::{anyhow, Context, Result};
use camino::Utf8Path;
use cap_std::fs_utf8::Dir as Utf8Dir;
use cap_std::fs::Dir;
use cap_std::io_lifetimes::AsFilelike;
use cap_std_ext::cap_std;
use cap_std_ext::prelude::CapStdExtCommandExt;
Expand Down Expand Up @@ -185,8 +185,7 @@ pub(crate) fn initramfs_overlay_generate(
}

#[context("Running dracut")]
pub(crate) fn run_dracut(kernel_dir: &str) -> Result<()> {
let root_fs = Utf8Dir::open_ambient_dir("/", cap_std::ambient_authority())?;
pub(crate) fn run_dracut(root_fs: &Dir, kernel_dir: &str) -> Result<()> {
let tmp_dir = tempfile::tempdir()?;
let tmp_initramfs_path = tmp_dir.path().join("initramfs.img");

Expand Down
121 changes: 121 additions & 0 deletions rust/src/kernel_install.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
//! Integration with the systemd-owned /sbin/kernel-install tooling.
//!
//! Note that there's two different phases of kernel handling:
//!
//! - build time
//! - deployment time (owned by ostree)
//!
//! This code is wholly concerned with "build time" today. The
//! "deployment time" logic is owned entirely by ostree.
//!

// SPDX-License-Identifier: Apache-2.0 OR MIT

use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::Path;
use std::process::Command;

use anyhow::{Context, Result};
use cap_std::fs::Dir;
use cap_std_ext::cap_std;
use cap_std_ext::dirext::CapStdExtDirExt;
use fn_error_context::context;

/// Parsed by kernel-install and set in the environment
const LAYOUT_VAR: &str = "KERNEL_INSTALL_LAYOUT";
/// The value we expect to find for layout
const LAYOUT_OSTREE: &str = "ostree";
/// What we should emit to skip further processing
const SKIP: u8 = 77;
/// The path to the kernel modules
const MODULES: &str = "usr/lib/modules";
/// The default name for the initramfs.
const INITRAMFS: &str = "initramfs.img";
/// The path to the instal.conf that sets layout.
const KERNEL_INSTALL_CONF: &str = "/usr/lib/kernel/install.conf";

#[context("Verifying kernel-install layout file")]
pub fn is_ostree_layout() -> Result<bool> {
let install_conf = Path::new(KERNEL_INSTALL_CONF);
if !install_conf.is_file() {
tracing::debug!("can not read /usr/lib/kernel/install.conf");
return Ok(false);
}
let buff = BufReader::new(
File::open(install_conf).context("Failed to open /usr/lib/kernel/install.conf")?,
);
// Check for "layout=ostree" in the file
for line in buff.lines() {
let line = line.context("Failed to read line")?;
if line.trim() == "layout=ostree" {
return Ok(true);
}
}
Ok(false)
}

#[context("Adding kernel")]
fn add(root: &Dir, argv: &[&str]) -> Result<()> {
let mut argv_it = argv.iter().copied();
let Some(kver) = argv_it.next() else {
anyhow::bail!("No kernel version provided");
};
tracing::debug!("Installing kernel kver={kver}");
println!("Generating initramfs");
crate::initramfs::run_dracut(root, &kver)?;
println!("Running depmod");
let st = Command::new("depmod")
.args(["-a", kver])
.status()
.context("Invoking depmod")?;
if !st.success() {
anyhow::bail!("Failed to run depmod: {st:?}");
}
Ok(())
}

#[context("Removing kernel")]
fn remove(root: &Dir, kver: &str) -> Result<()> {
tracing::debug!("Removing kernel kver={kver}");
let kdir = format!("{MODULES}/{kver}");
let Some(kernel_dir) = root.open_dir_optional(&kdir)? else {
return Ok(());
};
// We generate the initramfs, so remove it if it exists.
kernel_dir.remove_file_optional(INITRAMFS)?;
Ok(())
}

/// Primary entrypoint to `/usr/lib/kernel-install.d/05-rpmostree.install`.
#[context("rpm-ostree kernel-install")]
pub fn main(argv: &[&str]) -> Result<u8> {
let Some(layout) = std::env::var_os(LAYOUT_VAR) else {
return Ok(0);
};
tracing::debug!("The LAYOUT_OSTREE is: {:?}", layout.to_str());
if !matches!(layout.to_str(), Some(LAYOUT_OSTREE)) {
return Ok(0);
}
if !ostree_ext::container_utils::is_ostree_container()? {
eprintln!(
"warning: confused state: {LAYOUT_VAR}={LAYOUT_OSTREE} but not in an ostree container"
);
return Ok(0);
}
let root = &Dir::open_ambient_dir("/", cap_std::ambient_authority())?;
tracing::debug!("argv={argv:?}");
match argv {
[_, _, "add", rest @ ..] => {
add(root, rest)?;
// In the case of adding a new kernel, we intercept everything else
// today. In the future we can try to ensure we reuse what bits are there.
Ok(SKIP)
}
[_, _, "remove", kver, ..] => {
remove(root, kver)?;
Ok(0)
}
_ => Ok(0),
}
}
Loading
Loading