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

treefile: Add ignore-devices #5114

Merged
merged 1 commit into from
Oct 14, 2024
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 @@ -40,6 +40,10 @@ It supports the following parameters:
* `selinux`: boolean, optional: Defaults to `true`. If `false`, then
no SELinux labeling will be performed on the server side.

* `ignore-devices`: boolean, optional: Defaults to `true`. If `true`, then
all character and block device files found in the target root (except overlayfs
whiteouts, which are automatically "quoted") will be ignored.

* `ima`: boolean, optional: Defaults to `false`. Propagate any
IMA signatures in input RPMs into the final OSTree commit.

Expand Down
11 changes: 7 additions & 4 deletions rpmostree-cxxrs.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ class Slice final : private detail::copy_assignable_if<std::is_const<T>::value>
Slice () noexcept;
Slice (T *, std::size_t count) noexcept;

template <typename C> explicit Slice (C &c) : Slice (c.data (), c.size ()) {}

Slice &operator= (const Slice<T> &) &noexcept = default;
Slice &operator= (Slice<T> &&) &noexcept = default;

Expand Down Expand Up @@ -2206,8 +2208,8 @@ extern "C"
::std::int32_t rootfs_dfd, ::rpmostreecxx::Treefile &treefile, ::rust::Str next_version,
bool unified_core) noexcept;

::rust::repr::PtrLen
rpmostreecxx$cxxbridge1$compose_postprocess_final_pre (::std::int32_t rootfs_dfd) noexcept;
::rust::repr::PtrLen rpmostreecxx$cxxbridge1$compose_postprocess_final_pre (
::std::int32_t rootfs_dfd, ::rpmostreecxx::Treefile const &treefile) noexcept;

::rust::repr::PtrLen rpmostreecxx$cxxbridge1$compose_postprocess_final (
::std::int32_t rootfs_dfd, ::rpmostreecxx::Treefile const &treefile) noexcept;
Expand Down Expand Up @@ -4011,9 +4013,10 @@ compose_postprocess (::std::int32_t rootfs_dfd, ::rpmostreecxx::Treefile &treefi
}

void
compose_postprocess_final_pre (::std::int32_t rootfs_dfd)
compose_postprocess_final_pre (::std::int32_t rootfs_dfd, ::rpmostreecxx::Treefile const &treefile)
{
::rust::repr::PtrLen error$ = rpmostreecxx$cxxbridge1$compose_postprocess_final_pre (rootfs_dfd);
::rust::repr::PtrLen error$
= rpmostreecxx$cxxbridge1$compose_postprocess_final_pre (rootfs_dfd, treefile);
if (error$.ptr)
{
throw ::rust::impl< ::rust::Error>::error (error$);
Expand Down
5 changes: 4 additions & 1 deletion rpmostree-cxxrs.h
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,8 @@ class Slice final : private detail::copy_assignable_if<std::is_const<T>::value>
Slice () noexcept;
Slice (T *, std::size_t count) noexcept;

template <typename C> explicit Slice (C &c) : Slice (c.data (), c.size ()) {}

Slice &operator= (const Slice<T> &) &noexcept = default;
Slice &operator= (Slice<T> &&) &noexcept = default;

Expand Down Expand Up @@ -1867,7 +1869,8 @@ void composepost_nsswitch_altfiles (::std::int32_t rootfs_dfd);
void compose_postprocess (::std::int32_t rootfs_dfd, ::rpmostreecxx::Treefile &treefile,
::rust::Str next_version, bool unified_core);

void compose_postprocess_final_pre (::std::int32_t rootfs_dfd);
void compose_postprocess_final_pre (::std::int32_t rootfs_dfd,
::rpmostreecxx::Treefile const &treefile);

void compose_postprocess_final (::std::int32_t rootfs_dfd,
::rpmostreecxx::Treefile const &treefile);
Expand Down
74 changes: 52 additions & 22 deletions rust/src/composepost.rs
Original file line number Diff line number Diff line change
Expand Up @@ -295,40 +295,69 @@ fn is_overlay_whiteout(meta: &cap_std::fs::Metadata) -> bool {
(meta.mode() & libc::S_IFMT) == libc::S_IFCHR && meta.rdev() == 0
}

/// Auto-synthesize embedded overlayfs whiteouts; for more information
/// see https://github.com/ostreedev/ostree/pull/2722/commits/0085494e350c72599fc5c0e00422885d80b3c660
#[context("Postprocessing embedded overlayfs")]
fn postprocess_embedded_ovl_whiteouts(root: &Dir) -> Result<()> {
/// Automatically "quote" embeded overlayfs whiteouts as regular files, and
/// if configured error out on devices or ignore them.
/// For more on overlayfs, see https://github.com/ostreedev/ostree/pull/2722/commits/0085494e350c72599fc5c0e00422885d80b3c660
#[context("Postprocessing devices")]
fn postprocess_devices(root: &Dir, treefile: &Treefile) -> Result<()> {
const OSTREE_WHITEOUT_PREFIX: &str = ".ostree-wh.";
fn recurse(root: &Dir, path: &Utf8Path) -> Result<u32> {
let mut n = 0;
let mut n_overlay = 0u64;
let mut n_devices = 0u64;
fn recurse(
root: &Dir,
path: &Utf8Path,
ignore_devices: bool,
n_overlay: &mut u64,
n_devices: &mut u64,
) -> Result<()> {
for entry in root.read_dir(path)? {
let entry = entry?;
let meta = entry.metadata()?;
let name = PathBuf::from(entry.file_name());
let name: Utf8PathBuf = name.try_into()?;
if meta.is_dir() {
n += recurse(root, &path.join(name))?;
recurse(root, &path.join(name), ignore_devices, n_overlay, n_devices)?;
continue;
}
if !is_overlay_whiteout(&meta) {
let is_device = matches!(meta.mode() & libc::S_IFMT, libc::S_IFCHR | libc::S_IFBLK);
if !is_device {
continue;
};
}
let srcpath = path.join(&name);
let targetname = format!("{OSTREE_WHITEOUT_PREFIX}{name}");
let destpath = path.join(&targetname);
root.remove_file(srcpath)?;
root.atomic_write_with_perms(destpath, "", meta.permissions())?;
n += 1;
if is_overlay_whiteout(&meta) {
let targetname = format!("{OSTREE_WHITEOUT_PREFIX}{name}");
let destpath = path.join(&targetname);
root.remove_file(srcpath)?;
root.atomic_write_with_perms(destpath, "", meta.permissions())?;
*n_overlay += 1;
continue;
}
if ignore_devices {
root.remove_file(srcpath)?;
*n_devices += 1;
} else {
anyhow::bail!("Unsupported device file: {srcpath}")
}
}
Ok(n)
Ok(())
}
let n = recurse(root, ".".into())?;
if n > 0 {
println!("Processed {n} embedded whiteouts");
recurse(
root,
".".into(),
treefile.get_ignore_devices(),
&mut n_overlay,
&mut n_devices,
)?;
if n_overlay > 0 {
println!("Processed {n_overlay} embedded whiteouts");
} else {
println!("No embedded whiteouts found");
}
if n_devices > 0 {
println!("Ignored {n_devices} device files");
} else {
println!("No device files found");
}
Ok(())
}

Expand Down Expand Up @@ -420,7 +449,7 @@ pub(crate) fn postprocess_cleanup_rpmdb(rootfs_dfd: i32) -> CxxResult<()> {
/// it as the bits of that function that we've chosen to implement in Rust.
/// It takes care of all things that are really required to use rpm-ostree
/// on the target host.
pub fn compose_postprocess_final_pre(rootfs_dfd: i32) -> CxxResult<()> {
pub fn compose_postprocess_final_pre(rootfs_dfd: i32, treefile: &Treefile) -> CxxResult<()> {
let rootfs_dfd = unsafe { &crate::ffiutil::ffi_dirfd(rootfs_dfd)? };
// These tasks can safely run in parallel, so just for fun we do so via rayon.
let tasks = [
Expand All @@ -430,7 +459,7 @@ pub fn compose_postprocess_final_pre(rootfs_dfd: i32) -> CxxResult<()> {
];
tasks.par_iter().try_for_each(|f| f(rootfs_dfd))?;
// This task recursively traverses the filesystem and hence should be serial.
postprocess_embedded_ovl_whiteouts(rootfs_dfd)?;
postprocess_devices(rootfs_dfd, treefile)?;
Ok(())
}

Expand Down Expand Up @@ -1536,11 +1565,12 @@ OSTREE_VERSION='33.4'
// We don't actually test creating whiteout devices here as that
// may not work.
let td = cap_tempfile::tempdir(cap_std::ambient_authority())?;
let tf = crate::treefile::tests::new_test_tf_basic("")?;
// Verify no-op case
postprocess_embedded_ovl_whiteouts(&td).unwrap();
postprocess_devices(&td, &tf).unwrap();
td.create("foo")?;
td.symlink("foo", "bar")?;
postprocess_embedded_ovl_whiteouts(&td).unwrap();
postprocess_devices(&td, &tf).unwrap();
assert!(td.try_exists("foo")?);
assert!(td.try_exists("bar")?);

Expand Down
2 changes: 1 addition & 1 deletion rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ pub mod ffi {
next_version: &str,
unified_core: bool,
) -> Result<()>;
fn compose_postprocess_final_pre(rootfs_dfd: i32) -> Result<()>;
fn compose_postprocess_final_pre(rootfs_dfd: i32, treefile: &Treefile) -> Result<()>;
fn compose_postprocess_final(rootfs_dfd: i32, treefile: &Treefile) -> Result<()>;
fn convert_var_to_tmpfiles_d(rootfs_dfd: i32, cancellable: &GCancellable) -> Result<()>;
fn rootfs_prepare_links(
Expand Down
7 changes: 7 additions & 0 deletions rust/src/treefile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,7 @@ fn treefile_merge(dest: &mut TreeComposeConfig, src: &mut TreeComposeConfig) {
rojig,
selinux,
selinux_label_version,
ignore_devices,
ima,
gpg_key,
include,
Expand Down Expand Up @@ -1320,6 +1321,10 @@ impl Treefile {
self.parsed.base.selinux.unwrap_or(true)
}

pub(crate) fn get_ignore_devices(&self) -> bool {
self.parsed.base.ignore_devices.unwrap_or(true)
}

pub(crate) fn get_selinux_label_version(&self) -> u32 {
self.parsed.base.selinux_label_version.unwrap_or_default()
}
Expand Down Expand Up @@ -2505,6 +2510,8 @@ pub(crate) struct BaseComposeConfigFields {
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) selinux: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) ignore_devices: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) selinux_label_version: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) ima: Option<bool>,
Expand Down
2 changes: 1 addition & 1 deletion src/libpriv/rpmostree-postprocess.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ postprocess_final (int rootfs_dfd, rpmostreecxx::Treefile &treefile, gboolean un

auto selinux = treefile.get_selinux ();

ROSCXX_TRY (compose_postprocess_final_pre (rootfs_dfd), error);
ROSCXX_TRY (compose_postprocess_final_pre (rootfs_dfd, treefile), error);

if (selinux)
{
Expand Down
8 changes: 8 additions & 0 deletions tests/compose/test-installroot.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ dn=$(cd "$(dirname "$0")" && pwd)

# This is used to test postprocessing with treefile vs not
treefile_set "boot-location" '"new"'
# On by default now:
# treefile_set "ignore-devices" 'True'

# This test is a bit of a degenerative case of the supermin abstration. We need
# to be able to interact with the compose output directly, feed it back to
Expand Down Expand Up @@ -56,6 +58,7 @@ testdate=$(date)
runasroot sh -xec "
# https://github.com/ostreedev/ostree/pull/2717/commits/e234b630f85b97e48ecf45d5aaba9b1aa64e6b54
mknod -m 000 ${instroot}-directcommit/usr/share/foowhiteout c 0 0
mknod -m 000 ${instroot}-directcommit/usr/share/devzero c 1 5
echo \"${testdate}\" > ${instroot}-directcommit/usr/share/rpm-ostree-composetest-split.txt
! test -f ${instroot}-directcommit/${integrationconf}
rpm-ostree compose commit --repo=${repo} ${treefile} ${instroot}-directcommit
Expand All @@ -69,6 +72,11 @@ ostree --repo=${repo} ls ${treeref} /usr/share
ostree --repo=${repo} ls ${treeref} /usr/share/.ostree-wh.foowhiteout >out.txt
grep -Ee '^-00000' out.txt

# And the devzero should have been ignored
if ostree --repo=${repo} ls ${treeref} /usr/share/devzero; then
echo \"found devzero\" 1>&2; exit 1
fi

ostree --repo=${repo} cat ${treeref} /usr/share/rpm-ostree-composetest-split.txt >out.txt
grep \"${testdate}\" out.txt
ostree --repo=${repo} cat ${treeref} /${integrationconf}
Expand Down
Loading