Skip to content

Commit

Permalink
composepost: Support rootfs.transient=yes
Browse files Browse the repository at this point in the history
This pairs with ostreedev/ostree#3114

Basically we want to detect the case where the OS has opted-in
to this new mode and *not* symlink things.

I originally thought we could implement this by just moving
all the toplevel directories, but then I hit on the fact that
because the `filesystem` package is creating all the toplevel
directories in lua script which we ignore...that doesn't work.

So we need to keep making them by hand.
  • Loading branch information
cgwalters committed Dec 7, 2023
1 parent 69deaec commit e804dab
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 28 deletions.
115 changes: 87 additions & 28 deletions rust/src/composepost.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ use std::process::Stdio;
/// location to `/usr/lib/<entry>`.
pub(crate) static COMPAT_VARLIB_SYMLINKS: &[&str] = &["alternatives", "vagrant"];

const DEFAULT_DIRMODE: u32 = 0o755;

/* See rpmostree-core.h */
const RPMOSTREE_BASE_RPMDB: &str = "usr/lib/sysimage/rpm-ostree-base-db";
pub(crate) const RPMOSTREE_RPMDB_LOCATION: &str = "usr/share/rpm";
Expand All @@ -59,26 +61,15 @@ fn dir_move_if_exists(src: &cap_std::fs::Dir, dest: &cap_std::fs::Dir, name: &st

/// Initialize an ostree-oriented root filesystem.
///
/// This is hardcoded; in the future we may make more things configurable,
/// but the goal is for all state to be in `/etc` and `/var`.
#[context("Initializing rootfs")]
fn compose_init_rootfs(rootfs_dfd: &cap_std::fs::Dir, tmp_is_dir: bool) -> Result<()> {
println!("Initializing rootfs");

let default_dirmode: u32 = 0o755;
let default_dirbuilder = &dirbuilder_from_mode(default_dirmode);
let default_dirmode = cap_std::fs::Permissions::from_mode(default_dirmode);

/// Now unfortunately today, we're not generating toplevel filesystem entries
/// because the `filesystem` package does it from Lua code, which we don't run.
/// (See rpmostree-core.cxx)
#[context("Initializing rootfs (base)")]
fn compose_init_rootfs_base(rootfs_dfd: &cap_std::fs::Dir, tmp_is_dir: bool) -> Result<()> {
const TOPLEVEL_DIRS: &[&str] = &["dev", "proc", "run", "sys", "var", "sysroot"];
const TOPLEVEL_SYMLINKS: &[(&str, &str)] = &[
("var/opt", "opt"),
("var/srv", "srv"),
("var/mnt", "mnt"),
("var/roothome", "root"),
("var/home", "home"),
("run/media", "media"),
("sysroot/ostree", "ostree"),
];

let default_dirbuilder = &dirbuilder_from_mode(DEFAULT_DIRMODE);
let default_dirmode = cap_std::fs::Permissions::from_mode(DEFAULT_DIRMODE);

rootfs_dfd
.set_permissions(".", default_dirmode)
Expand All @@ -90,11 +81,6 @@ fn compose_init_rootfs(rootfs_dfd: &cap_std::fs::Dir, tmp_is_dir: bool) -> Resul
.with_context(|| format!("Creating {d}"))
.map(|_: bool| ())
})?;
TOPLEVEL_SYMLINKS.par_iter().try_for_each(|&(dest, src)| {
rootfs_dfd
.symlink(dest, src)
.with_context(|| format!("Creating {src}"))
})?;

if tmp_is_dir {
let tmp_mode = 0o1777;
Expand All @@ -108,15 +94,71 @@ fn compose_init_rootfs(rootfs_dfd: &cap_std::fs::Dir, tmp_is_dir: bool) -> Resul
rootfs_dfd.symlink("sysroot/tmp", "tmp")?;
}

rootfs_dfd
.symlink("sysroot/ostree", "ostree")
.context("Symlinking ostree -> sysroot/ostree")?;

Ok(())
}

/// Add extra toplevel directories.
#[context("Initializing rootfs (base)")]
fn compose_add_rootfs_extra(rootfs_dfd: &cap_std::fs::Dir) -> Result<()> {
const EXTRA_TOPLEVEL_DIRS: &[&str] = &["opt", "media", "mnt"];

let default_dirbuilder = &dirbuilder_from_mode(DEFAULT_DIRMODE);
EXTRA_TOPLEVEL_DIRS.par_iter().try_for_each(|&d| {
rootfs_dfd
.ensure_dir_with(d, default_dirbuilder)
.with_context(|| format!("Creating {d}"))
.map(|_: bool| ())
})?;

Ok(())
}

/// Initialize an ostree-oriented root filesystem.
///
/// This is hardcoded; in the future we may make more things configurable,
/// but the goal is for all state to be in `/etc` and `/var`.
#[context("Initializing rootfs")]
fn compose_init_rootfs_ostree_strict(
rootfs_dfd: &cap_std::fs::Dir,
tmp_is_dir: bool,
) -> Result<()> {
println!("Initializing rootfs");

compose_init_rootfs_base(rootfs_dfd, tmp_is_dir)?;

// This is used in the case where we don't have a transient rootfs; redirect
// these toplevel directories underneath /var.
const OSTREE_STRICT_MODE_SYMLINKS: &[(&str, &str)] = &[
("var/opt", "opt"),
("var/srv", "srv"),
("var/mnt", "mnt"),
("var/roothome", "root"),
("var/home", "home"),
("run/media", "media"),
];
OSTREE_STRICT_MODE_SYMLINKS
.par_iter()
.try_for_each(|&(dest, src)| {
rootfs_dfd
.symlink(dest, src)
.with_context(|| format!("Creating {src}"))
})?;

Ok(())
}

/// Prepare rootfs for commit.
///
/// Initialize a basic root filesystem in @target_root_dfd, then walk over the
/// In the default mode, we initialize a basic root filesystem in @target_root_dfd, then walk over the
/// root filesystem in @src_rootfs_fd and take the basic content (/usr, /boot, /var)
/// and cherry pick only specific bits of the rest of the toplevel like compatibility
/// symlinks (e.g. /lib64 -> /usr/lib64) if they exist.
///
/// However, if the rootfs is setup as transient, then we just copy everything.
#[context("Preparing rootfs for commit")]
pub fn compose_prepare_rootfs(
src_rootfs_dfd: i32,
Expand All @@ -127,7 +169,24 @@ pub fn compose_prepare_rootfs(
let target_rootfs_dfd = unsafe { &ffi_dirfd(target_rootfs_dfd)? };

let tmp_is_dir = treefile.parsed.base.tmp_is_dir.unwrap_or_default();
compose_init_rootfs(target_rootfs_dfd, tmp_is_dir)?;

if crate::ostree_prepareroot::transient_root_enabled(src_rootfs_dfd)? {
println!("Target has transient root enabled");
for entry in src_rootfs_dfd.entries()? {
let entry = entry?;
let name = entry.file_name();
src_rootfs_dfd
.rename(&name, target_rootfs_dfd, &name)
.with_context(|| format!("Moving {name:?}"))?;
}
// Now unfortunately because we're not executing the `filesystem` packages'
// lua script, we need to make base directories.
compose_init_rootfs_base(target_rootfs_dfd, tmp_is_dir)?;
compose_add_rootfs_extra(target_rootfs_dfd)?;
return Ok(());
}

compose_init_rootfs_ostree_strict(target_rootfs_dfd, tmp_is_dir)?;

println!("Moving /usr to target");
src_rootfs_dfd.rename("usr", target_rootfs_dfd, "usr")?;
Expand Down Expand Up @@ -1312,13 +1371,13 @@ OSTREE_VERSION='33.4'
fn test_init_rootfs() -> Result<()> {
{
let rootfs = cap_tempfile::tempdir(cap_tempfile::ambient_authority())?;
compose_init_rootfs(&rootfs, false)?;
compose_init_rootfs_ostree_strict(&rootfs, false)?;
let target = rootfs.read_link("tmp").unwrap();
assert_eq!(target, Path::new("sysroot/tmp"));
}
{
let rootfs = cap_tempfile::tempdir(cap_tempfile::ambient_authority())?;
compose_init_rootfs(&rootfs, true)?;
compose_init_rootfs_ostree_strict(&rootfs, true)?;
let tmpdir_meta = rootfs.metadata("tmp").unwrap();
assert!(tmpdir_meta.is_dir());
assert_eq!(tmpdir_meta.permissions().mode() & 0o7777, 0o1777);
Expand Down
1 change: 1 addition & 0 deletions rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -970,6 +970,7 @@ pub(crate) use self::modularity::*;
mod nameservice;
mod normalization;
mod origin;
mod ostree_prepareroot;
pub(crate) use self::origin::*;
mod passwd;
use passwd::*;
Expand Down
50 changes: 50 additions & 0 deletions rust/src/ostree_prepareroot.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//! Logic related to parsing ostree-prepare-root.conf.
//!

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

use std::io::BufReader;
use std::io::Read;

use anyhow::{Context, Result};
use camino::Utf8Path;
use cap_std::fs::Dir;
use cap_std_ext::dirext::CapStdExtDirExt;
use ostree_ext::glib;
use ostree_ext::keyfileext::KeyFileExt;

pub(crate) const CONF_PATH: &str = "ostree/prepare-root.conf";

pub(crate) fn load_config(rootfs: &Dir) -> Result<Option<glib::KeyFile>> {
let kf = glib::KeyFile::new();
for path in ["etc", "usr/lib"].into_iter().map(Utf8Path::new) {
let path = &path.join(CONF_PATH);
if let Some(fd) = rootfs
.open_optional(path)
.with_context(|| format!("Opening {path}"))?
{
let mut fd = BufReader::new(fd);
let mut buf = String::new();
fd.read_to_string(&mut buf)
.with_context(|| format!("Reading {path}"))?;
kf.load_from_data(&buf, glib::KeyFileFlags::NONE)
.with_context(|| format!("Parsing {path}"))?;
tracing::debug!("Loaded {path}");
return Ok(Some(kf));
}
}
tracing::debug!("No {CONF_PATH} found");
Ok(None)
}

/// Query whether the target root has the `root.transient` key
/// which sets up a transient overlayfs.
pub(crate) fn transient_root_enabled(rootfs: &Dir) -> Result<bool> {
if let Some(config) = load_config(rootfs)? {
Ok(config
.optional_bool("root", "transient")?
.unwrap_or_default())
} else {
Ok(false)
}
}

0 comments on commit e804dab

Please sign in to comment.