From 83850b9b3a2c9a8c009765b285ed3f8e674b9f28 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Mon, 16 Sep 2024 18:23:03 -0400 Subject: [PATCH] core: stop wrapping kernel-install when layout=ostree is set Co-authored-by: Colin Walters Signed-off-by: Colin Walters Signed-off-by: Joseph Marrero --- docs/treefile.md | 4 + rpmostree-cxxrs.cxx | 17 ++- rpmostree-cxxrs.h | 3 +- rust/src/cliwrap.rs | 26 +++- ...rnel_install.rs => kernel_install_wrap.rs} | 4 +- rust/src/core.rs | 42 ++++-- rust/src/initramfs.rs | 5 +- rust/src/kernel_install.rs | 121 ++++++++++++++++++ rust/src/lib.rs | 4 +- rust/src/main.rs | 1 + rust/src/scripts.rs | 50 ++++++-- rust/src/treefile.rs | 18 ++- src/libpriv/rpmostree-core.cxx | 18 ++- src/libpriv/rpmostree-scripts.cxx | 27 ++-- src/libpriv/rpmostree-scripts.h | 12 +- 15 files changed, 295 insertions(+), 57 deletions(-) rename rust/src/cliwrap/{kernel_install.rs => kernel_install_wrap.rs} (93%) create mode 100644 rust/src/kernel_install.rs diff --git a/docs/treefile.md b/docs/treefile.md index 6fb1499245..1055c80edc 100644 --- a/docs/treefile.md +++ b/docs/treefile.md @@ -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 diff --git a/rpmostree-cxxrs.cxx b/rpmostree-cxxrs.cxx index d86c326821..5b76e8b953 100644 --- a/rpmostree-cxxrs.cxx +++ b/rpmostree-cxxrs.cxx @@ -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; @@ -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; @@ -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, @@ -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 @@ -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 { diff --git a/rpmostree-cxxrs.h b/rpmostree-cxxrs.h index a98349e7bb..91e5464883 100644 --- a/rpmostree-cxxrs.h +++ b/rpmostree-cxxrs.h @@ -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; @@ -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); diff --git a/rust/src/cliwrap.rs b/rust/src/cliwrap.rs index 567cd9e42b..88d3009b69 100644 --- a/rust/src/cliwrap.rs +++ b/rust/src/cliwrap.rs @@ -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 { @@ -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 { @@ -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::>(); + 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) { @@ -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( diff --git a/rust/src/cliwrap/kernel_install.rs b/rust/src/cliwrap/kernel_install_wrap.rs similarity index 93% rename from rust/src/cliwrap/kernel_install.rs rename to rust/src/cliwrap/kernel_install_wrap.rs index 8123efafb6..09d86a476e 100644 --- a/rust/src/cliwrap/kernel_install.rs +++ b/rust/src/cliwrap/kernel_install_wrap.rs @@ -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; @@ -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(()) diff --git a/rust/src/core.rs b/rust/src/core.rs index a2e339aa06..6b68e11f2f 100644 --- a/rust/src/core.rs +++ b/rust/src/core.rs @@ -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; @@ -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) } @@ -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, @@ -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(()) } diff --git a/rust/src/initramfs.rs b/rust/src/initramfs.rs index da8f9ea9cb..537858801a 100644 --- a/rust/src/initramfs.rs +++ b/rust/src/initramfs.rs @@ -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; @@ -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"); diff --git a/rust/src/kernel_install.rs b/rust/src/kernel_install.rs new file mode 100644 index 0000000000..ad445f06e8 --- /dev/null +++ b/rust/src/kernel_install.rs @@ -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 { + 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 { + 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), + } +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index ddbd2ee770..fe00440e59 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -513,7 +513,7 @@ pub mod ffi { // scripts.rs extern "Rust" { - fn script_is_ignored(pkg: &str, script: &str) -> bool; + fn script_is_ignored(pkg: &str, script: &str, use_kernel_install: bool) -> bool; } // testutils.rs @@ -627,6 +627,7 @@ pub mod ffi { fn get_machineid_compat(&self) -> bool; fn get_etc_group_members(&self) -> Vec; fn get_boot_location_is_modules(&self) -> bool; + fn use_kernel_install(&self) -> bool; fn get_ima(&self) -> bool; fn get_releasever(&self) -> String; fn get_repo_metadata_target(&self) -> RepoMetadataTarget; @@ -990,6 +991,7 @@ mod initramfs; pub(crate) use self::initramfs::*; mod isolation; mod journal; +pub mod kernel_install; pub(crate) use self::journal::*; mod kickstart; mod lockfile; diff --git a/rust/src/main.rs b/rust/src/main.rs index bf10d45dcc..0bde1a12ee 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -28,6 +28,7 @@ async fn inner_async_main(args: Vec) -> Result { match *arg { // Add custom Rust commands here, and also in `libmain.cxx` if user-visible. "countme" => rpmostree_rust::countme::entrypoint(args).map(|_| 0), + "kernel-install" => rpmostree_rust::kernel_install::main(args).map(Into::into), "fix-shadow-perms" => rpmostree_rust::passwd::fix_shadow_perms_entrypoint(args).map(|_| 0), "cliwrap" => rpmostree_rust::cliwrap::entrypoint(args).map(|_| 0), // A hidden wrapper to intercept some binaries in RPM scriptlets. diff --git a/rust/src/scripts.rs b/rust/src/scripts.rs index e5ac83fec7..bfd7544347 100644 --- a/rust/src/scripts.rs +++ b/rust/src/scripts.rs @@ -9,14 +9,11 @@ use phf::phf_set; -/// Some RPM scripts we don't want to execute. A notable example is the kernel ones; -/// we want rpm-ostree to own running dracut, installing the kernel to /boot etc. -/// Ideally more of these migrate out, e.g. in the future we should change the kernel -/// package to do nothing if `/run/ostree-booted` exists. +/// If we're not using boot-location: kernel-install, then we take over the +/// kernel RPM scripts. /// /// NOTE FOR GIT history: This list used to live in src/libpriv/rpmostree-script-gperf.gperf -static IGNORED_PKG_SCRIPTS: phf::Set<&'static str> = phf_set! { - "glibc.prein", +static IGNORED_KERNEL_SCRIPTS: phf::Set<&'static str> = phf_set! { // We take over depmod/dracut etc. It's `kernel` in C7 and kernel-core in F25+ // XXX: we should probably change this to instead ignore based on the kernel virtual Provides "kernel.posttrans", @@ -50,6 +47,10 @@ static IGNORED_PKG_SCRIPTS: phf::Set<&'static str> = phf_set! { "kernel-ml.posttrans", "kernel-ml-core.posttrans", "kernel-ml-modules.posttrans", +}; + +static IGNORED_PKG_SCRIPTS: phf::Set<&'static str> = phf_set! { + "glibc.prein", // Legacy workaround "glibc-headers.prein", // workaround for old bug? @@ -90,8 +91,41 @@ static IGNORED_PKG_SCRIPTS: phf::Set<&'static str> = phf_set! { /// Returns true if we should simply ignore (not execute) an RPM script. /// The format is .