core: Initial implementation of %posttrans using bwrap+rofiles-fuse

In order to make many things work, we need to run scripts.  Short version:
For now, we:

 - Run `%posttrans`
 - Treat most `%post` as the same as `%posttrans`
 - Ignore `%preun` and such since we never uninstall

Most importantly though, we start to build up an "override" list
for script handling.  Currently it's just a blacklist of scripts
we don't need.

Significant work here would be needed to run Lua scripts, so far I've
been able to just skip them.

Closes: #338
Approved by: jlebon
This commit is contained in:
Colin Walters 2016-06-17 15:14:24 -04:00 committed by Atomic Bot
parent 4738c6eb60
commit f35f5cf468
16 changed files with 782 additions and 129 deletions

View File

@ -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 $@

View File

@ -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])
])

View File

@ -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);

View File

@ -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);
}

View File

@ -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;

View File

@ -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,

View File

@ -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;

View File

@ -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);
}

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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

View File

@ -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 <gio/gio.h>
#include <systemd/sd-journal.h>
#include "rpmostree-output.h"
#include <err.h>
#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;
}

View File

@ -0,0 +1,65 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright (C) 2016 Colin Walters <walters@verbum.org>
*
* 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 <gio/gio.h>
#include <ostree.h>
#include <rpm/rpmsq.h>
#include <rpm/rpmlib.h>
#include <rpm/rpmlog.h>
#include <rpm/rpmfi.h>
#include <rpm/rpmmacro.h>
#include <rpm/rpmts.h>
#include <libhif/libhif.h>
#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);