From b3f6f25637ee4397dd71a7dc0d85b68bd2aab0ab Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Mon, 9 Apr 2018 14:10:33 -0400 Subject: [PATCH] core,scripts: When no cachedir+unified-core, disable rofiles-fuse This is prep for running inside (unprivileged) Kube containers as they exist today: https://github.com/projectatomic/rpm-ostree/issues/1329 Sadly FUSE today uses a suid binary that ends up wanting CAP_SYS_ADMIN. I think there's some work on FUSE-in-containers but I'm not sure of the current status. What rofiles-fuse here is doing here is protecting is the hardlinked repo imports. But if `--cachedir` isn't specified, that repository gets thrown away anyways. So there's no real value to using FUSE here. Also since nothing is cached, disable the devino cache. We also make use of --force-copy-zerosized that just landed in libostree: https://github.com/ostreedev/ostree/pull/1752 Down the line ideally we gain the capability to detect if either unprivileged overlayfs/FUSE are available. Then if `--cachedir` is specified we can make things work. Closes: #1591 Approved by: jlebon --- src/app/rpmostree-compose-builtin-tree.c | 37 ++++++++++++++++++++--- src/libpriv/rpmostree-core-private.h | 1 + src/libpriv/rpmostree-core.c | 24 ++++++++++++--- src/libpriv/rpmostree-core.h | 1 + src/libpriv/rpmostree-scripts.c | 18 ++++++++--- src/libpriv/rpmostree-scripts.h | 2 ++ tests/common/libtest.sh | 13 ++++++++ tests/compose-tests/test-basic-unified.sh | 15 +++++++++ 8 files changed, 97 insertions(+), 14 deletions(-) diff --git a/src/app/rpmostree-compose-builtin-tree.c b/src/app/rpmostree-compose-builtin-tree.c index 5f88513f..349f9228 100644 --- a/src/app/rpmostree-compose-builtin-tree.c +++ b/src/app/rpmostree-compose-builtin-tree.c @@ -113,6 +113,7 @@ typedef struct { int workdir_dfd; int rootfs_dfd; int cachedir_dfd; + gboolean unified_core_and_fuse; OstreeRepo *repo; OstreeRepo *pkgcache_repo; OstreeRepoDevInoCache *devino_cache; @@ -340,9 +341,37 @@ install_packages_in_root (RpmOstreeTreeComposeContext *self, cancellable, error); if (!self->pkgcache_repo) return FALSE; + + if (!opt_cachedir) + { + /* This is part of enabling rpm-ostree inside Docker/Kubernetes/OpenShift; + * in this case we probably don't have access to FUSE as today it uses a + * suid binary which doesn't have the capabilities it needs. + * + * So this magical bit tells the core to disable FUSE, which we only do + * if --cachedir isn't specified. Another way to say this is that + * running inside an unprivileged container today requires turning off + * some of the rpm-ostree intelligence around caching. + * + * We don't make this actually conditional somehow on running in a + * container since if you're not using a persistent cache there's no + * real advantage to taking the overhead of FUSE. If the hardlinks are + * corrupted, it doesn't matter as they're going to be deleted + * anyways. + */ + rpmostree_context_disable_rofiles (self->corectx); + } + else + { + self->unified_core_and_fuse = TRUE; + /* We also only enable the devino cache if we know we have the FUSE protection + * against mutation of the underlying files. + */ + self->devino_cache = ostree_repo_devino_cache_new (); + rpmostree_context_set_devino_cache (self->corectx, self->devino_cache); + } + rpmostree_context_set_repos (self->corectx, self->repo, self->pkgcache_repo); - self->devino_cache = ostree_repo_devino_cache_new (); - rpmostree_context_set_devino_cache (self->corectx, self->devino_cache); /* Ensure that the imported packages are labeled with *a* policy if * possible, even if it's not the final one. This helps avoid duplicating @@ -886,7 +915,7 @@ impl_install_tree (RpmOstreeTreeComposeContext *self, /* Start postprocessing */ if (!rpmostree_treefile_postprocessing (self->rootfs_dfd, self->treefile_rs, self->treefile, - next_version, opt_unified_core, + next_version, self->unified_core_and_fuse, cancellable, error)) return glnx_prefix_error (error, "Postprocessing"); @@ -999,7 +1028,7 @@ impl_commit_tree (RpmOstreeTreeComposeContext *self, if (!rpmostree_rootfs_postprocess_common (self->rootfs_dfd, cancellable, error)) return FALSE; - if (!rpmostree_postprocess_final (self->rootfs_dfd, self->treefile, opt_unified_core, + if (!rpmostree_postprocess_final (self->rootfs_dfd, self->treefile, self->unified_core_and_fuse, cancellable, error)) return FALSE; diff --git a/src/libpriv/rpmostree-core-private.h b/src/libpriv/rpmostree-core-private.h index e3d35a70..56144a36 100644 --- a/src/libpriv/rpmostree-core-private.h +++ b/src/libpriv/rpmostree-core-private.h @@ -42,6 +42,7 @@ struct _RpmOstreeContext { RpmOstreeContextDnfCachePolicy dnf_cache_policy; OstreeRepo *ostreerepo; OstreeRepo *pkgcache_repo; + gboolean enable_rofiles; OstreeRepoDevInoCache *devino_cache; gboolean unprivileged; OstreeSePolicy *sepolicy; diff --git a/src/libpriv/rpmostree-core.c b/src/libpriv/rpmostree-core.c index c55c48f5..165b3992 100644 --- a/src/libpriv/rpmostree-core.c +++ b/src/libpriv/rpmostree-core.c @@ -381,6 +381,7 @@ rpmostree_context_init (RpmOstreeContext *self) { self->tmprootfs_dfd = -1; self->dnf_cache_policy = RPMOSTREE_CONTEXT_DNF_CACHE_DEFAULT; + self->enable_rofiles = TRUE; } static void @@ -555,11 +556,19 @@ void rpmostree_context_set_devino_cache (RpmOstreeContext *self, OstreeRepoDevInoCache *devino_cache) { + g_assert (self->enable_rofiles); if (self->devino_cache) ostree_repo_devino_cache_unref (self->devino_cache); self->devino_cache = devino_cache ? ostree_repo_devino_cache_ref (devino_cache) : NULL; } +void +rpmostree_context_disable_rofiles (RpmOstreeContext *self) +{ + g_assert (!self->devino_cache); + self->enable_rofiles = FALSE; +} + DnfContext * rpmostree_context_get_dnf (RpmOstreeContext *self) { @@ -2577,6 +2586,7 @@ checkout_package (OstreeRepo *repo, const char *pkg_commit, GHashTable *files_skip, OstreeRepoCheckoutOverwriteMode ovwmode, + gboolean force_copy_zerosized, GCancellable *cancellable, GError **error) { @@ -2591,6 +2601,8 @@ checkout_package (OstreeRepo *repo, /* Always want hardlinks */ opts.no_copy_fallback = TRUE; + /* Used in the no-rofiles-fuse path */ + opts.force_copy_zerosized = force_copy_zerosized; if (files_skip && g_hash_table_size (files_skip) > 0) { @@ -2632,6 +2644,7 @@ checkout_package_into_root (RpmOstreeContext *self, if (!checkout_package (pkgcache_repo, dfd, path, devino_cache, pkg_commit, files_skip, ovwmode, + !self->enable_rofiles, cancellable, error)) return glnx_prefix_error (error, "Checkout %s", dnf_package_get_nevra (pkg)); @@ -2887,7 +2900,7 @@ relabel_in_thread_impl (RpmOstreeContext *self, g_autoptr(OstreeRepoDevInoCache) cache = ostree_repo_devino_cache_new (); if (!checkout_package (repo, tmpdir_dfd, pkg_dirname, cache, - commit_csum, NULL, OSTREE_REPO_CHECKOUT_OVERWRITE_NONE, + commit_csum, NULL, OSTREE_REPO_CHECKOUT_OVERWRITE_NONE, FALSE, cancellable, error)) return FALSE; @@ -3256,7 +3269,7 @@ run_script_sync (RpmOstreeContext *self, return FALSE; if (!rpmostree_script_run_sync (pkg, hdr, kind, rootfs_dfd, var_lib_rpm_statedir, - out_n_run, cancellable, error)) + self->enable_rofiles, out_n_run, cancellable, error)) return FALSE; return TRUE; @@ -3530,7 +3543,8 @@ run_all_transfiletriggers (RpmOstreeContext *self, Header hdr; while ((hdr = rpmdbNextIterator (mi)) != NULL) { - if (!rpmostree_transfiletriggers_run_sync (hdr, rootfs_dfd, out_n_run, + if (!rpmostree_transfiletriggers_run_sync (hdr, rootfs_dfd, self->enable_rofiles, + out_n_run, cancellable, error)) return FALSE; } @@ -3549,8 +3563,8 @@ run_all_transfiletriggers (RpmOstreeContext *self, if (!get_package_metainfo (self, path, &hdr, NULL, error)) return FALSE; - if (!rpmostree_transfiletriggers_run_sync (hdr, rootfs_dfd, out_n_run, - cancellable, error)) + if (!rpmostree_transfiletriggers_run_sync (hdr, rootfs_dfd, self->enable_rofiles, + out_n_run, cancellable, error)) return FALSE; } return TRUE; diff --git a/src/libpriv/rpmostree-core.h b/src/libpriv/rpmostree-core.h index 288a723e..0586067f 100644 --- a/src/libpriv/rpmostree-core.h +++ b/src/libpriv/rpmostree-core.h @@ -121,6 +121,7 @@ void rpmostree_context_set_repos (RpmOstreeContext *self, OstreeRepo *pkgcache_repo); void rpmostree_context_set_devino_cache (RpmOstreeContext *self, OstreeRepoDevInoCache *devino_cache); +void rpmostree_context_disable_rofiles (RpmOstreeContext *self); void rpmostree_context_set_sepolicy (RpmOstreeContext *self, OstreeSePolicy *sepolicy); diff --git a/src/libpriv/rpmostree-scripts.c b/src/libpriv/rpmostree-scripts.c index 29a084c3..f0c27e45 100644 --- a/src/libpriv/rpmostree-scripts.c +++ b/src/libpriv/rpmostree-scripts.c @@ -296,6 +296,7 @@ dump_buffered_output_noerr (const char *prefix, static gboolean run_script_in_bwrap_container (int rootfs_fd, GLnxTmpDir *var_lib_rpm_statedir, + gboolean enable_fuse, const char *name, const char *scriptdesc, const char *interp, @@ -354,8 +355,10 @@ run_script_in_bwrap_container (int rootfs_fd, * See above for why we special case glibc. */ const gboolean is_glibc_locales = strcmp (pkg_script, "glibc-all-langpacks.posttrans") == 0; + RpmOstreeBwrapMutability mutability = + (is_glibc_locales || !enable_fuse) ? RPMOSTREE_BWRAP_MUTATE_FREELY : RPMOSTREE_BWRAP_MUTATE_ROFILES; bwrap = rpmostree_bwrap_new (rootfs_fd, - is_glibc_locales ? RPMOSTREE_BWRAP_MUTATE_FREELY : RPMOSTREE_BWRAP_MUTATE_ROFILES, + mutability, error); if (!bwrap) goto out; @@ -473,6 +476,7 @@ impl_run_rpm_script (const KnownRpmScriptKind *rpmscript, Header hdr, int rootfs_fd, GLnxTmpDir *var_lib_rpm_statedir, + gboolean enable_fuse, GCancellable *cancellable, GError **error) { @@ -541,7 +545,8 @@ impl_run_rpm_script (const KnownRpmScriptKind *rpmscript, } guint64 start_time_ms = g_get_monotonic_time () / 1000; - if (!run_script_in_bwrap_container (rootfs_fd, var_lib_rpm_statedir, dnf_package_get_name (pkg), + if (!run_script_in_bwrap_container (rootfs_fd, var_lib_rpm_statedir, enable_fuse, + dnf_package_get_name (pkg), rpmscript->desc, interp, script, script_arg, -1, cancellable, error)) return glnx_prefix_error (error, "Running %s for %s", rpmscript->desc, dnf_package_get_name (pkg)); @@ -567,6 +572,7 @@ run_script (const KnownRpmScriptKind *rpmscript, Header hdr, int rootfs_fd, GLnxTmpDir *var_lib_rpm_statedir, + gboolean enable_fuse, gboolean *out_did_run, GCancellable *cancellable, GError **error) @@ -595,7 +601,7 @@ run_script (const KnownRpmScriptKind *rpmscript, *out_did_run = TRUE; return impl_run_rpm_script (rpmscript, pkg, hdr, rootfs_fd, var_lib_rpm_statedir, - cancellable, error); + enable_fuse, cancellable, error); } #ifdef BUILDOPT_HAVE_RPM_FILETRIGGERS @@ -715,6 +721,7 @@ rpmostree_script_run_sync (DnfPackage *pkg, RpmOstreeScriptKind kind, int rootfs_fd, GLnxTmpDir *var_lib_rpm_statedir, + gboolean enable_fuse, guint *out_n_run, GCancellable *cancellable, GError **error) @@ -737,7 +744,7 @@ rpmostree_script_run_sync (DnfPackage *pkg, gboolean did_run = FALSE; if (!run_script (scriptkind, pkg, hdr, rootfs_fd, - var_lib_rpm_statedir, + var_lib_rpm_statedir, enable_fuse, &did_run, cancellable, error)) return FALSE; @@ -752,6 +759,7 @@ rpmostree_script_run_sync (DnfPackage *pkg, gboolean rpmostree_transfiletriggers_run_sync (Header hdr, int rootfs_fd, + gboolean enable_fuse, guint *out_n_run, GCancellable *cancellable, GError **error) @@ -906,7 +914,7 @@ rpmostree_transfiletriggers_run_sync (Header hdr, /* Run it, and log the result */ guint64 start_time_ms = g_get_monotonic_time () / 1000; - if (!run_script_in_bwrap_container (rootfs_fd, NULL, pkg_name, + if (!run_script_in_bwrap_container (rootfs_fd, NULL, enable_fuse, pkg_name, "%transfiletriggerin", interp, script, NULL, fileno (tmpf_file), cancellable, error)) return FALSE; diff --git a/src/libpriv/rpmostree-scripts.h b/src/libpriv/rpmostree-scripts.h index 87039b35..c95c2369 100644 --- a/src/libpriv/rpmostree-scripts.h +++ b/src/libpriv/rpmostree-scripts.h @@ -63,6 +63,7 @@ rpmostree_script_run_sync (DnfPackage *pkg, RpmOstreeScriptKind kind, int rootfs_fd, GLnxTmpDir *var_lib_rpm_statedir, + gboolean enable_rofiles, guint *out_n_run, GCancellable *cancellable, GError **error); @@ -70,6 +71,7 @@ rpmostree_script_run_sync (DnfPackage *pkg, gboolean rpmostree_transfiletriggers_run_sync (Header hdr, int rootfs_fd, + gboolean enable_rofiles, guint *out_n_run, GCancellable *cancellable, GError **error); diff --git a/tests/common/libtest.sh b/tests/common/libtest.sh index 711724ee..2404334a 100644 --- a/tests/common/libtest.sh +++ b/tests/common/libtest.sh @@ -549,3 +549,16 @@ EOF post "semodule -n -i ${install_dir}/${name}.pp" \ files "${install_dir}/${name}.pp" } + +files_are_hardlinked() { + inode1=$(stat -c %i $1) + inode2=$(stat -c %i $2) + test -n "${inode1}" && test -n "${inode2}" + [ "${inode1}" == "${inode2}" ] +} + +assert_files_hardlinked() { + if ! files_are_hardlinked "$1" "$2"; then + fatal "Files '$1' and '$2' are not hardlinked" + fi +} diff --git a/tests/compose-tests/test-basic-unified.sh b/tests/compose-tests/test-basic-unified.sh index 17358ec3..50784ec5 100755 --- a/tests/compose-tests/test-basic-unified.sh +++ b/tests/compose-tests/test-basic-unified.sh @@ -35,6 +35,21 @@ if ostree --repo=${repobuild} cat ${treeref} /usr/lib/tmpfiles.d/pkg-rpm.conf > fi echo "ok autovar" +# And verify we're not hardlinking zero-sized files since this path isn't using +# rofiles-fuse +co=${repobuild}/tmp/usr-etc +ostree --repo=${repobuild} checkout -UHz --subpath=/usr/etc ${treeref} ${co} +# Verify the files exist and are zero-sized +for f in ${co}/sub{u,g}id; do + test -f "$f" + test '!' -s "$f" +done +if files_are_hardlinked ${co}/sub{u,g}id; then + fatal "Hardlinked zero-sized files without cachedir" +fi +rm ${co} -rf +echo "ok no cachedir zero-sized hardlinks" + # And redo it to trigger relabeling origrev=$(ostree --repo=${repobuild} rev-parse ${treeref}) runcompose --force-nocache --ex-unified-core