diff --git a/Makefile-libpriv.am b/Makefile-libpriv.am index 12ae509c..dacd7d45 100644 --- a/Makefile-libpriv.am +++ b/Makefile-libpriv.am @@ -30,6 +30,8 @@ librpmostreepriv_la_SOURCES = \ src/libpriv/rpmostree-refts.c \ src/libpriv/rpmostree-core.c \ src/libpriv/rpmostree-core.h \ + src/libpriv/rpmostree-scripts.c \ + src/libpriv/rpmostree-scripts.h \ src/libpriv/rpmostree-refsack.h \ src/libpriv/rpmostree-refsack.c \ src/libpriv/rpmostree-cleanup.h \ @@ -57,3 +59,17 @@ librpmostreepriv_la_LIBADD = \ libglnx.la \ $(CAP_LIBS) \ $(NULL) + +gperf_gperf_sources = src/libpriv/rpmostree-script-gperf.gperf +BUILT_SOURCES += $(gperf_gperf_sources:-gperf.gperf=-gperf.c) +CLEANFILES += $(gperf_gperf_sources:-gperf.gperf=-gperf.c) + +nodist_librpmostreepriv_la_SOURCES = src/libpriv/rpmostree-script-gperf.c + +AM_V_GPERF = $(AM_V_GPERF_$(V)) +AM_V_GPERF_ = $(AM_V_GPERF_$(AM_DEFAULT_VERBOSITY)) +AM_V_GPERF_0 = @echo " GPERF " $@; + +src/%.c: src/%.gperf Makefile + $(AM_V_at)$(MKDIR_P) $(dir $@) + $(AM_V_GPERF)$(GPERF) < $< > $@.tmp && mv $@.tmp $@ diff --git a/configure.ac b/configure.ac index 12f1f49a..ad5eb2af 100644 --- a/configure.ac +++ b/configure.ac @@ -63,6 +63,7 @@ AC_SEARCH_LIBS([rpmsqSetInterruptSafety], [rpmio], PKG_CHECK_MODULES(PKGDEP_GIO_UNIX, [gio-unix-2.0]) PKG_CHECK_MODULES(PKGDEP_RPMOSTREE, [gio-unix-2.0 >= 2.40.0 json-glib-1.0 ostree-1 >= 2015.1 libgsystem >= 2015.1 + libsystemd rpm libhif librepo libarchive]) save_LIBS=$LIBS @@ -74,6 +75,11 @@ AC_PATH_PROG([XSLTPROC], [xsltproc]) GLIB_TESTS +AC_CHECK_TOOL(GPERF, gperf) +AS_IF([test -z "$GPERF"], + AC_MSG_ERROR([*** gperf not found]) +) + m4_ifdef([GOBJECT_INTROSPECTION_CHECK], [ GOBJECT_INTROSPECTION_CHECK([1.34.0]) ]) diff --git a/src/app/rpmostree-container-builtins.c b/src/app/rpmostree-container-builtins.c index bcec662c..355206c1 100644 --- a/src/app/rpmostree-container-builtins.c +++ b/src/app/rpmostree-container-builtins.c @@ -320,7 +320,7 @@ rpmostree_container_builtin_assemble (int argc, goto out; if (!rpmostree_context_assemble_commit (rocctx->ctx, tmprootfs_dfd, NULL, - NULL, TRUE, &commit, cancellable, error)) + NULL, FALSE, &commit, cancellable, error)) goto out; glnx_shutil_rm_rf_at (rocctx->userroot_dfd, tmprootfs, cancellable, NULL); diff --git a/src/app/rpmostree-pkg-builtins.c b/src/app/rpmostree-pkg-builtins.c index 2e353e5f..2339b87b 100644 --- a/src/app/rpmostree-pkg-builtins.c +++ b/src/app/rpmostree-pkg-builtins.c @@ -30,13 +30,22 @@ static char *opt_osname; static gboolean opt_reboot; static gboolean opt_dry_run; +/* Turn off the noscripts stuff for now, since we aren't persisting + * it, and I hope we can mostly get away with not needing it. + */ +#if 0 static gboolean opt_no_scripts; +static char **opt_ignore_script; +#endif static GOptionEntry option_entries[] = { { "os", 0, 0, G_OPTION_ARG_STRING, &opt_osname, "Operate on provided OSNAME", "OSNAME" }, { "reboot", 'r', 0, G_OPTION_ARG_NONE, &opt_reboot, "Initiate a reboot after upgrade is prepared", NULL }, { "dry-run", 'n', 0, G_OPTION_ARG_NONE, &opt_dry_run, "Exit after printing the transaction", NULL }, +#if 0 { "noscripts", 0, 0, G_OPTION_ARG_NONE, &opt_no_scripts, "Do not run scripts", NULL }, + { "ignore-script", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_ignore_script, "Ignore a script for RPM", NULL }, +#endif { NULL } }; @@ -47,8 +56,12 @@ get_args_variant (void) g_variant_dict_init (&dict, NULL); g_variant_dict_insert (&dict, "reboot", "b", opt_reboot); g_variant_dict_insert (&dict, "dry-run", "b", opt_dry_run); +#if 0 if (opt_no_scripts) g_variant_dict_insert (&dict, "noscripts", "b", TRUE); + if (opt_ignore_script) + g_variant_dict_insert (&dict, "ignore-scripts", "^as", opt_ignore_script); +#endif return g_variant_dict_end (&dict); } diff --git a/src/daemon/rpmostree-sysroot-upgrader.c b/src/daemon/rpmostree-sysroot-upgrader.c index be6f5bfc..d724ae49 100644 --- a/src/daemon/rpmostree-sysroot-upgrader.c +++ b/src/daemon/rpmostree-sysroot-upgrader.c @@ -57,6 +57,7 @@ struct RpmOstreeSysrootUpgrader { GKeyFile *origin; char *origin_refspec; char **requested_packages; + GHashTable *ignore_scripts; GHashTable *packages_to_add; GHashTable *packages_to_delete; char *override_csum; @@ -460,6 +461,14 @@ rpmostree_sysroot_upgrader_set_origin_override (RpmOstreeSysrootUpgrader *self, self->override_csum = g_strdup (override_commit); } +void +rpmostree_sysroot_upgrader_set_ignore_scripts (RpmOstreeSysrootUpgrader *self, + GHashTable* ignore_scripts) +{ + g_clear_pointer (&self->ignore_scripts, g_hash_table_unref); + self->ignore_scripts = g_hash_table_ref (ignore_scripts); +} + const char * rpmostree_sysroot_upgrader_get_refspec (RpmOstreeSysrootUpgrader *self) { @@ -1116,6 +1125,9 @@ overlay_final_pkgset (RpmOstreeSysrootUpgrader *self, cancellable, error)) goto out; + if (self->ignore_scripts) + rpmostree_context_set_ignore_scripts (ctx, self->ignore_scripts); + if (!get_pkgcache_repo (repo, &pkgcache_repo, cancellable, error)) goto out; diff --git a/src/daemon/rpmostree-sysroot-upgrader.h b/src/daemon/rpmostree-sysroot-upgrader.h index f5d10027..5cfd6941 100644 --- a/src/daemon/rpmostree-sysroot-upgrader.h +++ b/src/daemon/rpmostree-sysroot-upgrader.h @@ -80,6 +80,8 @@ gboolean rpmostree_sysroot_upgrader_set_origin_rebase (RpmOstreeSysrootUpgrader void rpmostree_sysroot_upgrader_set_origin_override (RpmOstreeSysrootUpgrader *self, const char *override_commit); +void rpmostree_sysroot_upgrader_set_ignore_scripts (RpmOstreeSysrootUpgrader *self, + GHashTable* ignore_scripts); gboolean rpmostree_sysroot_upgrader_add_packages (RpmOstreeSysrootUpgrader *self, char **new_packages, diff --git a/src/daemon/rpmostreed-os.c b/src/daemon/rpmostreed-os.c index e5d77f0f..2166211d 100644 --- a/src/daemon/rpmostreed-os.c +++ b/src/daemon/rpmostreed-os.c @@ -715,36 +715,6 @@ out: return TRUE; } -static RpmOstreeTransactionPkgFlags -pkg_opts_to_flags (GVariant *options) -{ - gboolean v; - RpmOstreeTransactionPkgFlags flags = 0; - GVariantDict dict; - - g_variant_dict_init (&dict, options); - - v = FALSE; - /* XXX Fail if option type is wrong? */ - g_variant_dict_lookup (&dict, "reboot", "b", &v); - if (v) - flags |= RPMOSTREE_TRANSACTION_PKG_FLAG_REBOOT; - - v = FALSE; - g_variant_dict_lookup (&dict, "dry-run", "b", &v); - if (v) - flags |= RPMOSTREE_TRANSACTION_PKG_FLAG_DRY_RUN; - - v = FALSE; - g_variant_dict_lookup (&dict, "noscripts", "b", &v); - if (v) - flags |= RPMOSTREE_TRANSACTION_PKG_FLAG_NOSCRIPTS; - - g_variant_dict_clear (&dict); - - return flags; -} - static gboolean os_handle_pkg_change (RPMOSTreeOS *interface, GDBusMethodInvocation *invocation, @@ -758,6 +728,10 @@ os_handle_pkg_change (RPMOSTreeOS *interface, glnx_unref_object GCancellable *cancellable = NULL; const char *osname; GError *local_error = NULL; + gboolean v; + GVariantDict dict; + RpmOstreeTransactionPkgFlags flags = 0; + const char *const *ignore_scripts; /* If a compatible transaction is in progress, share its bus address. */ transaction = rpmostreed_transaction_monitor_ref_active_transaction (self->transaction_monitor); @@ -780,14 +754,37 @@ os_handle_pkg_change (RPMOSTreeOS *interface, osname = rpmostree_os_get_name (interface); + g_variant_dict_init (&dict, arg_options); + v = FALSE; + /* XXX Fail if option type is wrong? */ + g_variant_dict_lookup (&dict, "reboot", "b", &v); + if (v) + flags |= RPMOSTREE_TRANSACTION_PKG_FLAG_REBOOT; + + v = FALSE; + g_variant_dict_lookup (&dict, "dry-run", "b", &v); + if (v) + flags |= RPMOSTREE_TRANSACTION_PKG_FLAG_DRY_RUN; + + v = FALSE; + g_variant_dict_lookup (&dict, "noscripts", "b", &v); + if (v) + flags |= RPMOSTREE_TRANSACTION_PKG_FLAG_NOSCRIPTS; + + ignore_scripts = NULL; + g_variant_dict_lookup (&dict, "ignore-scripts", "^a&s", (char***)&ignore_scripts); + transaction = rpmostreed_transaction_new_pkg_change (invocation, ot_sysroot, osname, arg_packages_added, arg_packages_removed, - pkg_opts_to_flags (arg_options), + ignore_scripts, + flags, cancellable, &local_error); + g_variant_dict_clear (&dict); + if (transaction == NULL) goto out; diff --git a/src/daemon/rpmostreed-transaction-pkg-change.c b/src/daemon/rpmostreed-transaction-pkg-change.c index 74c7ea95..b4ee1394 100644 --- a/src/daemon/rpmostreed-transaction-pkg-change.c +++ b/src/daemon/rpmostreed-transaction-pkg-change.c @@ -35,6 +35,7 @@ #include "rpmostreed-utils.h" #include "rpmostree-postprocess.h" #include "rpmostree-rpm-util.h" +#include "rpmostree-scripts.h" #include "rpmostree-core.h" typedef struct { @@ -42,6 +43,7 @@ typedef struct { char *osname; char **packages_added; char **packages_removed; + GHashTable *ignore_scripts; RpmOstreeTransactionPkgFlags flags; } PkgChangeTransaction; @@ -62,6 +64,7 @@ pkg_change_transaction_finalize (GObject *object) g_free (self->osname); g_strfreev (self->packages_added); g_strfreev (self->packages_removed); + g_clear_pointer (&self->ignore_scripts, g_hash_table_unref); G_OBJECT_CLASS (pkg_change_transaction_parent_class)->finalize (object); } @@ -95,6 +98,9 @@ pkg_change_transaction_execute (RpmostreedTransaction *transaction, goto out; } + if (self->ignore_scripts) + rpmostree_sysroot_upgrader_set_ignore_scripts (upgrader, self->ignore_scripts); + if (self->packages_removed) { if (!rpmostree_sysroot_upgrader_delete_packages (upgrader, self->packages_removed, @@ -150,11 +156,12 @@ rpmostreed_transaction_new_pkg_change (GDBusMethodInvocation *invocation, const char *osname, const char * const *packages_added, const char * const *packages_removed, + const char * const *ignore_scripts, RpmOstreeTransactionPkgFlags flags, GCancellable *cancellable, GError **error) { - PkgChangeTransaction *self; + glnx_unref_object PkgChangeTransaction *self = NULL; g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), NULL); g_return_val_if_fail (OSTREE_IS_SYSROOT (sysroot), NULL); @@ -172,8 +179,10 @@ rpmostreed_transaction_new_pkg_change (GDBusMethodInvocation *invocation, self->osname = g_strdup (osname); self->packages_added = strdupv_canonicalize (packages_added); self->packages_removed = strdupv_canonicalize (packages_removed); + if (!rpmostree_script_ignore_hash_from_strv (ignore_scripts, &self->ignore_scripts, error)) + return NULL; self->flags = flags; } - return (RpmostreedTransaction *) self; + return (RpmostreedTransaction *) g_steal_pointer (&self); } diff --git a/src/daemon/rpmostreed-transaction-types.h b/src/daemon/rpmostreed-transaction-types.h index f2590115..8c6895bd 100644 --- a/src/daemon/rpmostreed-transaction-types.h +++ b/src/daemon/rpmostreed-transaction-types.h @@ -85,6 +85,7 @@ RpmostreedTransaction * const char *osname, const char *const *packages_added, const char *const *packages_removed, + const char *const *ignore_scripts, RpmOstreeTransactionPkgFlags flags, GCancellable *cancellable, GError **error); diff --git a/src/libpriv/rpmostree-core.c b/src/libpriv/rpmostree-core.c index 9a942f67..eea13b13 100644 --- a/src/libpriv/rpmostree-core.c +++ b/src/libpriv/rpmostree-core.c @@ -34,6 +34,7 @@ #include "rpmostree-core.h" #include "rpmostree-postprocess.h" #include "rpmostree-rpm-util.h" +#include "rpmostree-scripts.h" #include "rpmostree-unpacker.h" #include "rpmostree-output.h" @@ -168,6 +169,9 @@ rpmostree_treespec_new_from_keyfile (GKeyFile *keyfile, if (!add_canonicalized_string_array (&builder, "instlangs", "instlangs-all", keyfile, error)) return NULL; + if (!add_canonicalized_string_array (&builder, "ignore-scripts", "", keyfile, error)) + return NULL; + { gboolean documentation = TRUE; g_autofree char *value = g_key_file_get_value (keyfile, "tree", "documentation", NULL); @@ -274,6 +278,7 @@ struct _RpmOstreeContext { RpmOstreeTreespec *spec; HifContext *hifctx; + GHashTable *ignore_scripts; OstreeRepo *ostreerepo; gboolean unprivileged; char *dummy_instroot_path; @@ -447,6 +452,15 @@ rpmostree_context_set_sepolicy (RpmOstreeContext *self, g_set_object (&self->sepolicy, sepolicy); } +void +rpmostree_context_set_ignore_scripts (RpmOstreeContext *self, + GHashTable *ignore_scripts) +{ + g_clear_pointer (&self->ignore_scripts, g_hash_table_unref); + if (ignore_scripts) + self->ignore_scripts = g_hash_table_ref (ignore_scripts); +} + HifContext * rpmostree_context_get_hif (RpmOstreeContext *self) { @@ -615,6 +629,17 @@ rpmostree_context_setup (RpmOstreeContext *self, HIF_TRANSACTION_FLAG_NODOCS); } + { const char *const *ignore_scripts = NULL; + if (g_variant_dict_lookup (self->spec->dict, "ignore-scripts", "^a&s", &ignore_scripts)) + { + g_autoptr(GHashTable) ignore_hash = NULL; + + if (!rpmostree_script_ignore_hash_from_strv (ignore_scripts, &ignore_hash, error)) + goto out; + rpmostree_context_set_ignore_scripts (self, ignore_hash); + } + } + ret = TRUE; out: return ret; @@ -1897,102 +1922,48 @@ ts_callback (const void * h, return NULL; } -typedef struct { - const char *desc; - rpmsenseFlags sense; - rpmTagVal tag; - rpmTagVal progtag; - rpmTagVal flagtag; -} KnownRpmScriptKind; - -static const KnownRpmScriptKind known_scripts[] = { - { "%prein", 0, - RPMTAG_PREIN, RPMTAG_PREINPROG, RPMTAG_PREINFLAGS }, - { "%preun", 0, - RPMTAG_PREUN, RPMTAG_PREUNPROG, RPMTAG_PREUNFLAGS }, - { "%post", 0, - RPMTAG_POSTIN, RPMTAG_POSTINPROG, RPMTAG_POSTINFLAGS }, - { "%postun", 0, - RPMTAG_POSTUN, RPMTAG_POSTUNPROG, RPMTAG_POSTUNFLAGS }, - { "%pretrans", 0, - RPMTAG_PRETRANS, RPMTAG_PRETRANSPROG, RPMTAG_PRETRANSFLAGS }, - { "%posttrans", 0, - RPMTAG_POSTTRANS, RPMTAG_POSTTRANSPROG, RPMTAG_POSTTRANSFLAGS }, - { "%triggerprein", RPMSENSE_TRIGGERPREIN, - RPMTAG_TRIGGERPREIN, 0, 0 }, - { "%triggerun", RPMSENSE_TRIGGERUN, - RPMTAG_TRIGGERUN, 0, 0 }, - { "%triggerin", RPMSENSE_TRIGGERIN, - RPMTAG_TRIGGERIN, 0, 0 }, - { "%triggerpostun", RPMSENSE_TRIGGERPOSTUN, - RPMTAG_TRIGGERPOSTUN, 0, 0 }, - { "%verify", 0, - RPMTAG_VERIFYSCRIPT, RPMTAG_VERIFYSCRIPTPROG, RPMTAG_VERIFYSCRIPTFLAGS}, -}; - -/* - * We aren't yet running %posts, so let's not lie and say we support - * it. - */ -#if 0 -static gboolean -check_package_is_post_posts (Header hdr, - const char *name, - GError **error) +static Header +get_header_for_package (int tmp_metadata_dfd, + HifPackage *pkg, + GError **error) { - gboolean ret = FALSE; - guint i; - - for (i = 0; i < G_N_ELEMENTS (known_scripts); i++) - { - rpmTagVal tagval = known_scripts[i].tag; - rpmTagVal progtagval = known_scripts[i].progtag; - - if (headerIsEntry (hdr, tagval) || headerIsEntry (hdr, progtagval)) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Package '%s' has (currently) unsupported script of type '%s'", - name, known_scripts[i].desc); - goto out; - } - } - - ret = TRUE; - out: - return ret; -} -#endif - -static gboolean -add_to_transaction (rpmts ts, - HifPackage *pkg, - int tmp_metadata_dfd, - gboolean noscripts, - GError **error) -{ - gboolean ret = FALSE; - int r; Header hdr = NULL; glnx_fd_close int metadata_fd = -1; if ((metadata_fd = openat (tmp_metadata_dfd, hif_package_get_nevra (pkg), O_RDONLY | O_CLOEXEC)) < 0) { glnx_set_error_from_errno (error); - goto out; + return NULL; } if (!rpmostree_unpacker_read_metainfo (metadata_fd, &hdr, NULL, NULL, error)) + return NULL; + + return hdr; +} + +static gboolean +add_to_transaction (rpmts ts, + HifPackage *pkg, + int tmp_metadata_dfd, + gboolean noscripts, + GHashTable *ignore_scripts, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + Header hdr = NULL; + int r; + + hdr = get_header_for_package (tmp_metadata_dfd, pkg, error); + if (!hdr) goto out; - /* TODO uncomment once upgrade understands this or we implement post - handling better */ -#if 0 if (!noscripts) { - if (!check_package_is_post_posts (hdr, hif_package_get_nevra (pkg), error)) + if (!rpmostree_script_txn_validate (pkg, hdr, ignore_scripts, cancellable, error)) goto out; } -#endif r = rpmtsAddInstallElement (ts, hdr, (char*)hif_package_get_nevra (pkg), TRUE, NULL); if (r != 0) @@ -2012,6 +1983,32 @@ add_to_transaction (rpmts ts, return ret; } +static gboolean +run_posttrans_sync (int tmp_metadata_dfd, + int rootfs_dfd, + HifPackage *pkg, + GHashTable *ignore_scripts, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + Header hdr; + + hdr = get_header_for_package (tmp_metadata_dfd, pkg, error); + if (!hdr) + goto out; + + if (!rpmostree_posttrans_run_sync (pkg, hdr, ignore_scripts, rootfs_dfd, + cancellable, error)) + goto out; + + ret = TRUE; + out: + if (hdr) + headerFree (hdr); + return ret; +} + /* FIXME: This is a copy of ot_admin_checksum_version */ static char * checksum_version (GVariant *checksum) @@ -2131,7 +2128,9 @@ rpmostree_context_assemble_commit (RpmOstreeContext *self, cancellable, error)) goto out; - if (!add_to_transaction (ordering_ts, pkg, tmp_metadata_dfd, noscripts, error)) + if (!add_to_transaction (ordering_ts, pkg, tmp_metadata_dfd, noscripts, + self->ignore_scripts, + cancellable, error)) goto out; } @@ -2215,6 +2214,24 @@ rpmostree_context_assemble_commit (RpmOstreeContext *self, rpmostree_output_task_end ("done"); + if (!rpmostree_rootfs_prepare_links (tmprootfs_dfd, cancellable, error)) + goto out; + + if (!noscripts) + { + for (i = 0; i < n_rpmts_elements; i++) + { + rpmte te = rpmtsElement (ordering_ts, i); + const char *tekey = rpmteKey (te); + HifPackage *pkg = g_hash_table_lookup (nevra_to_pkg, tekey); + + if (!run_posttrans_sync (tmp_metadata_dfd, tmprootfs_dfd, pkg, + self->ignore_scripts, + cancellable, error)) + goto out; + } + } + g_clear_pointer (&ordering_ts, rpmtsFree); rpmostree_output_task_begin ("Writing rpmdb"); @@ -2255,7 +2272,9 @@ rpmostree_context_assemble_commit (RpmOstreeContext *self, { HifPackage *pkg = k; - if (!add_to_transaction (rpmdb_ts, pkg, tmp_metadata_dfd, noscripts, error)) + /* Set noscripts since we already validated them above */ + if (!add_to_transaction (rpmdb_ts, pkg, tmp_metadata_dfd, TRUE, NULL, + cancellable, error)) goto out; } } diff --git a/src/libpriv/rpmostree-core.h b/src/libpriv/rpmostree-core.h index d37b05fd..26011898 100644 --- a/src/libpriv/rpmostree-core.h +++ b/src/libpriv/rpmostree-core.h @@ -68,6 +68,8 @@ void rpmostree_context_set_repo (RpmOstreeContext *self, OstreeRepo *repo); void rpmostree_context_set_sepolicy (RpmOstreeContext *self, OstreeSePolicy *sepolicy); +void rpmostree_context_set_ignore_scripts (RpmOstreeContext *self, + GHashTable *ignore_scripts); void rpmostree_hif_add_checksum_goal (GChecksum *checksum, HyGoal goal); char *rpmostree_context_get_state_sha512 (RpmOstreeContext *self); diff --git a/src/libpriv/rpmostree-postprocess.c b/src/libpriv/rpmostree-postprocess.c index 16f8a15d..bda87658 100644 --- a/src/libpriv/rpmostree-postprocess.c +++ b/src/libpriv/rpmostree-postprocess.c @@ -866,6 +866,8 @@ create_rootfs_from_yumroot_content (GFile *targetroot, goto out; } + if (!rpmostree_rootfs_prepare_links (target_root_dfd, cancellable, error)) + goto out; if (!rpmostree_rootfs_postprocess_common (target_root_dfd, cancellable, error)) goto out; @@ -1104,6 +1106,87 @@ rename_if_exists (int dfd, return ret; } +gboolean +rpmostree_rootfs_symlink_emptydir_at (int rootfs_fd, + const char *dest, + const char *src, + GError **error) +{ + const char *parent = dirname (strdupa (src)); + struct stat stbuf; + gboolean make_symlink = TRUE; + + /* For maximum compatibility, create parent directories too. This + * is necessary when we're doing layering on top of a base commit, + * and the /var will be empty. We should probably consider running + * systemd-tmpfiles to setup the temporary /var. + */ + if (parent && strcmp (parent, ".") != 0) + { + if (!glnx_shutil_mkdir_p_at (rootfs_fd, parent, 0755, NULL, error)) + return FALSE; + } + + if (fstatat (rootfs_fd, src, &stbuf, AT_SYMLINK_NOFOLLOW) < 0) + { + if (errno != ENOENT) + { + glnx_set_error_from_errno (error); + return FALSE; + } + } + else + { + if (S_ISLNK (stbuf.st_mode)) + make_symlink = FALSE; + else if (S_ISDIR (stbuf.st_mode)) + { + if (unlinkat (rootfs_fd, src, AT_REMOVEDIR) < 0) + { + glnx_set_prefix_error_from_errno (error, "Removing %s", src); + return FALSE; + } + } + } + + if (make_symlink) + { + if (symlinkat (dest, rootfs_fd, src) < 0) + { + glnx_set_prefix_error_from_errno (error, "Symlinking %s", src); + return FALSE; + } + } + return TRUE; +} + +/** + * rpmostree_rootfs_prepare_links: + * + * Walk over the root filesystem and perform some core conversions + * from RPM conventions to OSTree conventions. For example: + * + * - Symlink /usr/local -> /var/usrlocal + * - Symlink /var/lib/alternatives -> /usr/lib/alternatives + */ +gboolean +rpmostree_rootfs_prepare_links (int rootfs_fd, + GCancellable *cancellable, + GError **error) +{ + if (!glnx_shutil_rm_rf_at (rootfs_fd, "usr/local", cancellable, error)) + return FALSE; + if (!rpmostree_rootfs_symlink_emptydir_at (rootfs_fd, "../var/usrlocal", "usr/local", error)) + return FALSE; + + if (!glnx_shutil_mkdir_p_at (rootfs_fd, "usr/lib/alternatives", 0755, cancellable, error)) + return FALSE; + if (!rpmostree_rootfs_symlink_emptydir_at (rootfs_fd, "../../usr/lib/alternatives", "var/lib/alternatives", error)) + return FALSE; + + return TRUE; +} + /** * rpmostree_rootfs_postprocess_common: * @@ -1111,8 +1194,7 @@ rename_if_exists (int dfd, * from RPM conventions to OSTree conventions. For example: * * - Move /etc to /usr/etc - * - Symlink /usr/local -> /var/usrlocal - * - Clean up RPM database leftovers and lock files + * - Clean up RPM db leftovers */ gboolean rpmostree_rootfs_postprocess_common (int rootfs_fd, @@ -1121,17 +1203,7 @@ rpmostree_rootfs_postprocess_common (int rootfs_fd, { gboolean ret = FALSE; g_auto(GLnxDirFdIterator) dfd_iter = { 0, }; - - if (!glnx_shutil_rm_rf_at (rootfs_fd, "usr/local", cancellable, error)) - goto out; - - if (symlinkat ("../var/usrlocal", rootfs_fd, "usr/local") < 0) - { - glnx_set_error_from_errno (error); - g_prefix_error (error, "Creating usr/local symlink: "); - goto out; - } - + if (!rename_if_exists (rootfs_fd, "etc", "usr/etc", error)) goto out; diff --git a/src/libpriv/rpmostree-postprocess.h b/src/libpriv/rpmostree-postprocess.h index b8cc29e2..c658b10b 100644 --- a/src/libpriv/rpmostree-postprocess.h +++ b/src/libpriv/rpmostree-postprocess.h @@ -32,6 +32,16 @@ rpmostree_treefile_postprocessing (GFile *rootfs, GError **error); gboolean +rpmostree_rootfs_symlink_emptydir_at (int rootfs_fd, + const char *dest, + const char *src, + GError **error); + +gboolean +rpmostree_rootfs_prepare_links (int rootfs_fd, + GCancellable *cancellable, + GError **error); +gboolean rpmostree_rootfs_postprocess_common (int rootfs_fd, GCancellable *cancellable, GError **error); diff --git a/src/libpriv/rpmostree-script-gperf.gperf b/src/libpriv/rpmostree-script-gperf.gperf new file mode 100644 index 00000000..f3f6ae61 --- /dev/null +++ b/src/libpriv/rpmostree-script-gperf.gperf @@ -0,0 +1,27 @@ +%{ +#include "config.h" +#include "rpmostree-scripts.h" +%} +struct RpmOstreePackageScriptHandler; +%language=ANSI-C +%define slot-name package_script +%define hash-function-name rpmostree_script_gperf_hash +%define lookup-function-name rpmostree_script_gperf_lookup +%readonly-tables +%omit-struct-type +%struct-type +%includes +%% +glibc.prein, RPMOSTREE_SCRIPT_ACTION_IGNORE +coreutils.prein, RPMOSTREE_SCRIPT_ACTION_IGNORE /* workaround for old bug? */ +ca-certificates.prein, RPMOSTREE_SCRIPT_ACTION_IGNORE /* Looks like legacy... */ +filesystem.pretrans, RPMOSTREE_SCRIPT_ACTION_IGNORE +libgcc.post, RPMOSTREE_SCRIPT_ACTION_IGNORE +setup.post, RPMOSTREE_SCRIPT_ACTION_IGNORE +pinentry.prein, RPMOSTREE_SCRIPT_ACTION_IGNORE +fedora-release.post, RPMOSTREE_SCRIPT_ACTION_IGNORE +fedora-release.posttrans, RPMOSTREE_SCRIPT_ACTION_IGNORE +bash.post, RPMOSTREE_SCRIPT_ACTION_TODO_SHELL_POSTTRANS +glibc-common.post, RPMOSTREE_SCRIPT_ACTION_TODO_SHELL_POSTTRANS +/* Seems to be another case of legacy workaround */ +gdb.prein, RPMOSTREE_SCRIPT_ACTION_IGNORE diff --git a/src/libpriv/rpmostree-scripts.c b/src/libpriv/rpmostree-scripts.c new file mode 100644 index 00000000..7912ade3 --- /dev/null +++ b/src/libpriv/rpmostree-scripts.c @@ -0,0 +1,402 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2016 Red Hat, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2 of the licence or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include "rpmostree-output.h" +#include +#include "libglnx.h" + +#include "rpmostree-scripts.h" + +typedef struct { + const char *desc; + rpmsenseFlags sense; + rpmTagVal tag; + rpmTagVal progtag; + rpmTagVal flagtag; +} KnownRpmScriptKind; + +#if 0 +static const KnownRpmScriptKind ignored_scripts[] = { + /* Ignore all of the *un variants since we never uninstall + * anything in the RPM sense. + */ + { "%preun", 0, + RPMTAG_PREUN, RPMTAG_PREUNPROG, RPMTAG_PREUNFLAGS }, + { "%postun", 0, + RPMTAG_POSTUN, RPMTAG_POSTUNPROG, RPMTAG_POSTUNFLAGS }, + { "%triggerun", RPMSENSE_TRIGGERUN, + RPMTAG_TRIGGERUN, 0, 0 }, + { "%triggerpostun", RPMSENSE_TRIGGERPOSTUN, + RPMTAG_TRIGGERPOSTUN, 0, 0 }, +}; +#endif + +static const KnownRpmScriptKind posttrans_scripts[] = { + /* For now, we treat %post as equivalent to %posttrans */ + { "%post", 0, + RPMTAG_POSTIN, RPMTAG_POSTINPROG, RPMTAG_POSTINFLAGS }, + { "%posttrans", 0, + RPMTAG_POSTTRANS, RPMTAG_POSTTRANSPROG, RPMTAG_POSTTRANSFLAGS }, +}; + +static const KnownRpmScriptKind unsupported_scripts[] = { + { "%prein", 0, + RPMTAG_PREIN, RPMTAG_PREINPROG, RPMTAG_PREINFLAGS }, + { "%pretrans", 0, + RPMTAG_PRETRANS, RPMTAG_PRETRANSPROG, RPMTAG_PRETRANSFLAGS }, + { "%triggerprein", RPMSENSE_TRIGGERPREIN, + RPMTAG_TRIGGERPREIN, 0, 0 }, + { "%triggerin", RPMSENSE_TRIGGERIN, + RPMTAG_TRIGGERIN, 0, 0 }, + { "%verify", 0, + RPMTAG_VERIFYSCRIPT, RPMTAG_VERIFYSCRIPTPROG, RPMTAG_VERIFYSCRIPTFLAGS}, +}; + +static void +child_setup_fchdir (gpointer user_data) +{ + int fd = GPOINTER_TO_INT (user_data); + if (fchdir (fd) < 0) + err (1, "fchdir"); +} + +static void +add_const_args (GPtrArray *argv_array, ...) +{ + va_list args; + char *arg; + + va_start (args, argv_array); + while ((arg = va_arg (args, char *))) + g_ptr_array_add (argv_array, arg); + va_end (args); +} + +static void +fusermount_cleanup (const char *mountpoint) +{ + g_autoptr(GError) tmp_error = NULL; + const char *fusermount_argv[] = { "fusermount", "-u", mountpoint, NULL}; + int estatus; + + if (!g_spawn_sync (NULL, (char**)fusermount_argv, NULL, G_SPAWN_SEARCH_PATH, + NULL, NULL, NULL, NULL, &estatus, &tmp_error)) + { + g_prefix_error (&tmp_error, "Executing fusermount: "); + goto out; + } + if (!g_spawn_check_exit_status (estatus, &tmp_error)) + { + g_prefix_error (&tmp_error, "Executing fusermount: "); + goto out; + } + + out: + /* We don't want a failure to unmount to be fatal, so all we do here + * is log. Though in practice what we *really* want is for the + * fusermount to be in the bwrap namespace, and hence tied by the + * kernel to the lifecycle of the container. This would require + * special casing for somehow doing FUSE mounts in bwrap. Which + * would be hard because NO_NEW_PRIVS turns off the setuid bits for + * fuse. + */ + if (tmp_error) + sd_journal_print (LOG_WARNING, "%s", tmp_error->message); +} + +static RpmOstreeScriptAction +lookup_script_action (HifPackage *package, + GHashTable *ignored_scripts, + const char *scriptdesc) +{ + const char *pkg_script = glnx_strjoina (hif_package_get_name (package), ".", scriptdesc+1); + const struct RpmOstreePackageScriptHandler *handler = rpmostree_script_gperf_lookup (pkg_script, strlen (pkg_script)); + if (ignored_scripts && g_hash_table_contains (ignored_scripts, pkg_script)) + return RPMOSTREE_SCRIPT_ACTION_IGNORE; + if (!handler) + return RPMOSTREE_SCRIPT_ACTION_DEFAULT; + return handler->action; +} + +gboolean +rpmostree_script_txn_validate (HifPackage *package, + Header hdr, + GHashTable *override_ignored_scripts, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + guint i; + + for (i = 0; i < G_N_ELEMENTS (unsupported_scripts); i++) + { + const char *desc = unsupported_scripts[i].desc; + rpmTagVal tagval = unsupported_scripts[i].tag; + rpmTagVal progtagval = unsupported_scripts[i].progtag; + RpmOstreeScriptAction action; + + if (!(headerIsEntry (hdr, tagval) || headerIsEntry (hdr, progtagval))) + continue; + + action = lookup_script_action (package, override_ignored_scripts, desc); + switch (action) + { + case RPMOSTREE_SCRIPT_ACTION_DEFAULT: + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Package '%s' has (currently) unsupported script of type '%s'", + hif_package_get_name (package), desc); + goto out; + } + case RPMOSTREE_SCRIPT_ACTION_IGNORE: + continue; + } + } + + ret = TRUE; + out: + return ret; +} + +static gboolean +run_script_in_bwrap_container (int rootfs_fd, + const char *name, + const char *scriptdesc, + const char *script, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + int i; + const char *usr_links[] = {"lib", "lib32", "lib64", "bin", "sbin"}; + int estatus; + char *rofiles_mnt = strdupa ("/tmp/rofiles-fuse.XXXXXX"); + const char *rofiles_argv[] = { "rofiles-fuse", "./usr", rofiles_mnt, NULL}; + const char *pkg_script = glnx_strjoina (name, ".", scriptdesc+1); + const char *postscript_name = glnx_strjoina ("/", pkg_script); + const char *postscript_path_container = glnx_strjoina ("/usr/", postscript_name); + const char *postscript_path_host; + gboolean mntpoint_created = FALSE; + gboolean fuse_mounted = FALSE; + g_autoptr(GPtrArray) bwrap_argv = g_ptr_array_new (); + g_autoptr(GPtrArray) bwrap_argv_mallocd = g_ptr_array_new_with_free_func (g_free); + GSpawnFlags bwrap_spawnflags = G_SPAWN_SEARCH_PATH; + gboolean created_var_tmp = FALSE; + + if (!glnx_mkdtempat (AT_FDCWD, rofiles_mnt, 0700, error)) + goto out; + + mntpoint_created = TRUE; + + if (!g_spawn_sync (NULL, (char**)rofiles_argv, NULL, G_SPAWN_SEARCH_PATH, + child_setup_fchdir, GINT_TO_POINTER (rootfs_fd), + NULL, NULL, &estatus, error)) + goto out; + if (!g_spawn_check_exit_status (estatus, error)) + { + g_prefix_error (error, "Executing rofiles-fuse: "); + goto out; + } + + fuse_mounted = TRUE; + + postscript_path_host = glnx_strjoina (rofiles_mnt, "/", postscript_name); + + /* TODO - Create a pipe and send this to bwrap so it's inside the + * tmpfs + */ + if (!g_file_set_contents (postscript_path_host, script, -1, error)) + { + g_prefix_error (error, "Writing script to %s: ", postscript_path_host); + goto out; + } + if (chmod (postscript_path_host, 0755) != 0) + { + g_prefix_error (error, "chmod %s: ", postscript_path_host); + goto out; + } + + /* We need to make the mount point in the case where we're doing + * package layering, since the host `/var` tree is empty. We + * *could* point at the real `/var`...but that seems + * unnecessary/dangerous to me. Daemons that need to perform data + * migrations should do them as part of their systemd units and not + * in %post. + * + * Another alternative would be to make a tmpfs with the compat + * symlinks. + */ + if (mkdirat (rootfs_fd, "var/tmp", 0755) < 0) + { + if (errno == EEXIST) + ; + else + { + glnx_set_error_from_errno (error); + goto out; + } + } + else + created_var_tmp = TRUE; + + add_const_args (bwrap_argv, + "bwrap", + "--bind", rofiles_mnt, "/usr", + "--dev", "/dev", + "--proc", "/proc", + "--dir", "/tmp", + "--chdir", "/", + /* Scripts can see a /var with compat links like alternatives */ + "--ro-bind", "./var", "/var", + /* But no need to access persistent /tmp, so make it /tmp */ + "--bind", "/tmp", "/var/tmp", + /* Allow RPM scripts to change the /etc defaults */ + "--symlink", "usr/etc", "/etc", + "--ro-bind", "/sys/block", "/sys/block", + "--ro-bind", "/sys/bus", "/sys/bus", + "--ro-bind", "/sys/class", "/sys/class", + "--ro-bind", "/sys/dev", "/sys/dev", + "--ro-bind", "/sys/devices", "/sys/devices", + NULL); + + for (i = 0; i < G_N_ELEMENTS (usr_links); i++) + { + const char *subdir = usr_links[i]; + struct stat stbuf; + char *path; + + if (!(fstatat (rootfs_fd, subdir, &stbuf, AT_SYMLINK_NOFOLLOW) == 0 && S_ISLNK (stbuf.st_mode))) + continue; + + g_ptr_array_add (bwrap_argv, "--symlink"); + + path = g_strconcat ("usr/", subdir, NULL); + g_ptr_array_add (bwrap_argv_mallocd, path); + g_ptr_array_add (bwrap_argv, path); + + path = g_strconcat ("/", subdir, NULL); + g_ptr_array_add (bwrap_argv_mallocd, path); + g_ptr_array_add (bwrap_argv, path); + } + + { const char *debugscript = getenv ("RPMOSTREE_DEBUG_SCRIPT"); + if (g_strcmp0 (debugscript, pkg_script) == 0) + { + g_ptr_array_add (bwrap_argv, (char*)"/bin/bash"); + bwrap_spawnflags |= G_SPAWN_CHILD_INHERITS_STDIN; + } + else + g_ptr_array_add (bwrap_argv, (char*)postscript_path_container); + } + g_ptr_array_add (bwrap_argv, NULL); + + if (!g_spawn_sync (NULL, (char**)bwrap_argv->pdata, NULL, bwrap_spawnflags, + child_setup_fchdir, GINT_TO_POINTER (rootfs_fd), + NULL, NULL, &estatus, error)) + { + g_prefix_error (error, "Executing bwrap: "); + goto out; + } + if (!g_spawn_check_exit_status (estatus, error)) + { + g_prefix_error (error, "Executing bwrap: "); + goto out; + } + + ret = TRUE; + out: + if (fuse_mounted) + { + (void) unlink (postscript_path_host); + fusermount_cleanup (rofiles_mnt); + } + if (mntpoint_created) + (void) unlinkat (AT_FDCWD, rofiles_mnt, AT_REMOVEDIR); + if (created_var_tmp) + (void) unlinkat (rootfs_fd, "var/tmp", AT_REMOVEDIR); + return ret; +} + +gboolean +rpmostree_posttrans_run_sync (HifPackage *pkg, + Header hdr, + GHashTable *ignore_scripts, + int rootfs_fd, + GCancellable *cancellable, + GError **error) +{ + for (guint i = 0; i < G_N_ELEMENTS (posttrans_scripts); i++) + { + const char *desc = posttrans_scripts[i].desc; + rpmTagVal tagval = posttrans_scripts[i].tag; + rpmTagVal progtagval = posttrans_scripts[i].progtag; + const char *script; + RpmOstreeScriptAction action; + + if (!(headerIsEntry (hdr, tagval) || headerIsEntry (hdr, progtagval))) + continue; + + script = headerGetString (hdr, tagval); + if (!script) + continue; + + action = lookup_script_action (pkg, ignore_scripts, desc); + switch (action) + { + case RPMOSTREE_SCRIPT_ACTION_DEFAULT: + { + rpmostree_output_task_begin ("Running %s for %s...", desc, hif_package_get_name (pkg)); + if (!run_script_in_bwrap_container (rootfs_fd, hif_package_get_name (pkg), desc, script, + cancellable, error)) + { + g_prefix_error (error, "Running %s for %s: ", desc, hif_package_get_name (pkg)); + return FALSE; + } + rpmostree_output_task_end ("done"); + } + case RPMOSTREE_SCRIPT_ACTION_IGNORE: + continue; + } + } + + return TRUE; +} + +gboolean +rpmostree_script_ignore_hash_from_strv (const char *const *strv, + GHashTable **out_hash, + GError **error) +{ + g_autoptr(GHashTable) ignore_scripts = NULL; + if (!strv) + { + *out_hash = NULL; + return TRUE; + } + ignore_scripts = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + for (const char *const* iter = strv; iter && *iter; iter++) + g_hash_table_add (ignore_scripts, g_strdup (*iter)); + *out_hash = g_steal_pointer (&ignore_scripts); + return TRUE; +} diff --git a/src/libpriv/rpmostree-scripts.h b/src/libpriv/rpmostree-scripts.h new file mode 100644 index 00000000..3ceb97a6 --- /dev/null +++ b/src/libpriv/rpmostree-scripts.h @@ -0,0 +1,65 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2016 Colin Walters + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2 of the licence or (at + * your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libglnx.h" + +typedef enum { + RPMOSTREE_SCRIPT_ACTION_DEFAULT = 0, + RPMOSTREE_SCRIPT_ACTION_IGNORE, + RPMOSTREE_SCRIPT_ACTION_TODO_SHELL_POSTTRANS = RPMOSTREE_SCRIPT_ACTION_IGNORE, +} RpmOstreeScriptAction; + +struct RpmOstreePackageScriptHandler { + const char *package_script; + RpmOstreeScriptAction action; +}; + +const struct RpmOstreePackageScriptHandler* rpmostree_script_gperf_lookup(const char *key, unsigned length); + +gboolean rpmostree_script_ignore_hash_from_strv (const char *const *strv, + GHashTable **out_hash, + GError **error); + +gboolean +rpmostree_script_txn_validate (HifPackage *package, + Header hdr, + GHashTable *ignore_scripts, + GCancellable *cancellable, + GError **error); + +gboolean +rpmostree_posttrans_run_sync (HifPackage *pkg, + Header hdr, + GHashTable *ignore_scripts, + int rootfs_fd, + GCancellable *cancellable, + GError **error);