Skip to content

Commit

Permalink
core: stop wrapping kernel-install when layout=ostree is set
Browse files Browse the repository at this point in the history
Co-authored-by: Colin Walters <[email protected]>
Signed-off-by: Colin Walters <[email protected]>
Signed-off-by: Joseph Marrero <[email protected]>
  • Loading branch information
cgwalters authored and jmarrero committed Jan 8, 2025
1 parent 8a5dfc0 commit 81be973
Show file tree
Hide file tree
Showing 15 changed files with 293 additions and 57 deletions.
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
24 changes: 19 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,13 @@ 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: &[&str] = if !is_ostree_layout()? {
&[WRAPPED_BINARIES, &[KERNEL_INSTALL]].concat()
} else {
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

0 comments on commit 81be973

Please sign in to comment.