From 0049dbdd91cc0c1900132374645c5114063db04d Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Thu, 29 Sep 2022 14:01:07 -0400 Subject: [PATCH] compose: Handle embedded whiteouts This pairs with https://github.com/ostreedev/ostree/pull/2722/commits/0085494e350c72599fc5c0e00422885d80b3c660 Basically, while I think it'd make sense actually for the baseline `ostree commit` process to handle this, for now let's put it here in rpm-ostree (where it can be in Rust too). Though a definite negative is that we're now traversing the whole filesystem again. (cherry picked from commit fea26bdd5db59fcf0853a9bb4ab6dcb340be9470) --- rust/src/composepost.rs | 63 ++++++++++++++++++++++++++++++- tests/compose/test-installroot.sh | 8 ++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/rust/src/composepost.rs b/rust/src/composepost.rs index ab5cc176..70a01497 100644 --- a/rust/src/composepost.rs +++ b/rust/src/composepost.rs @@ -202,6 +202,47 @@ fn postprocess_presets(rootfs_dfd: &Dir) -> Result<()> { Ok(()) } +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<()> { + const OSTREE_WHITEOUT_PREFIX: &str = ".ostree-wh."; + fn recurse(root: &Dir, path: &Utf8Path) -> Result { + let mut n = 0; + 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))?; + continue; + } + if !is_overlay_whiteout(&meta) { + 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; + } + Ok(n) + } + let n = recurse(root, ".".into())?; + if n > 0 { + println!("Processed {n} embedded whiteouts"); + } else { + println!("No embedded whiteouts found"); + } + Ok(()) +} + /// Write an RPM macro file to ensure the rpmdb path is set on the client side. pub fn compose_postprocess_rpm_macro(rootfs_dfd: i32) -> CxxResult<()> { let rootfs = unsafe { &crate::ffiutil::ffi_dirfd(rootfs_dfd)? }; @@ -290,13 +331,17 @@ pub(crate) fn postprocess_cleanup_rpmdb(rootfs_dfd: i32) -> CxxResult<()> { /// on the target host. pub fn compose_postprocess_final(rootfs_dfd: i32) -> 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 = [ postprocess_useradd, postprocess_presets, postprocess_subs_dist, postprocess_rpm_macro, ]; - Ok(tasks.par_iter().try_for_each(|f| f(rootfs_dfd))?) + 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)?; + Ok(()) } #[context("Handling treefile 'units'")] @@ -1290,6 +1335,22 @@ OSTREE_VERSION='33.4' Ok(()) } + #[test] + fn test_overlay() -> Result<()> { + // We don't actually test creating whiteout devices here as that + // may not work. + let td = cap_tempfile::tempdir(cap_std::ambient_authority())?; + // Verify no-op case + postprocess_embedded_ovl_whiteouts(&td).unwrap(); + td.create("foo")?; + td.symlink("foo", "bar")?; + postprocess_embedded_ovl_whiteouts(&td).unwrap(); + assert!(td.try_exists("foo")?); + assert!(td.try_exists("bar")?); + + Ok(()) + } + #[test] fn test_tmpfiles_d_translation() { use nix::sys::stat::{umask, Mode}; diff --git a/tests/compose/test-installroot.sh b/tests/compose/test-installroot.sh index 1ac09b9a..3e40f679 100755 --- a/tests/compose/test-installroot.sh +++ b/tests/compose/test-installroot.sh @@ -54,6 +54,8 @@ echo "ok postprocess with treefile" 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 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 @@ -61,6 +63,12 @@ ostree --repo=${repo} ls ${treeref} /usr/bin/bash if ostree --repo=${repo} ls ${treeref} /var/lib/rpm >/dev/null; then echo found /var/lib/rpm in commit 1>&2; exit 1 fi + +# Verify whiteout renames +ostree --repo=${repo} ls ${treeref} /usr/share +ostree --repo=${repo} ls ${treeref} /usr/share/.ostree-wh.foowhiteout >out.txt +grep -Ee '^-00000' out.txt + ostree --repo=${repo} cat ${treeref} /usr/share/rpm-ostree-composetest-split.txt >out.txt grep \"${testdate}\" out.txt ostree --repo=${repo} cat ${treeref} /${integrationconf} -- 2.38.1