Implement file triggers (%transfiletriggerin) for layered pkgs
File triggers are a post-RHEL7 thing; more information at http://rpm.org/user_doc/file_triggers.html There are two notable users I've been testing this with; `glib2` and `vagrant`. The `vagrant` one is more immediately urgent, since it makes `vagrant-libvirt` work, which I currently rely on for my workstation dev. I've tested things successfully with `vagrant`, and I did verify that we run the `glib2` ones when doing `rpm-ostree ex container`. Long term, more transaction file triggers are likely to live in "base" packages like `glib2`. We don't implement those yet, but extending this to do that shouldn't be too hard. There was *significant* what I'd call reverse engineering of the implementation in librpm. The file triggers code there is spread out and abstracted in a few different places in the code. I found trying to understand what header values were involved to be quite tricky. There are some corner cases like multiple patterns that I *think* this does correctly, but could use more validation. The main question I had was - is it required that the patterns for e.g. `%transfiletriggerin` and `%transfiletriggerun` be identical? Closes: https://github.com/projectatomic/rpm-ostree/issues/648 Closes: #869 Approved by: jlebon
This commit is contained in:
parent
d4effe8f47
commit
0d4d6be94f
30
configure.ac
30
configure.ac
@ -83,17 +83,6 @@ CAP_LIBS="$LIBS"
|
||||
AC_SUBST(CAP_LIBS)
|
||||
LIBS="$save_LIBS"
|
||||
|
||||
AC_SEARCH_LIBS([rpmsqSetInterruptSafety], [rpmio],
|
||||
AC_DEFINE([BUILDOPT_HAVE_RPMSQ_SET_INTERRUPT_SAFETY], 1, [Set to 1 if we have interrupt safety API]),
|
||||
AC_DEFINE([BUILDOPT_HAVE_RPMSQ_SET_INTERRUPT_SAFETY], 0, [Set to 1 if we have interrupt safety API])
|
||||
)
|
||||
|
||||
# rpmfiles was made public in rpm >= 4.12, el7 is still on 4.11
|
||||
AC_SEARCH_LIBS([rpmfilesNew], [rpm],
|
||||
AC_DEFINE([BUILDOPT_HAVE_RPMFILES], 1, [Set to 1 if we have rpmfiles API]),
|
||||
AC_DEFINE([BUILDOPT_HAVE_RPMFILES], 0, [Set to 1 if we have rpmfiles API])
|
||||
)
|
||||
|
||||
# Remember to update AM_CPPFLAGS in Makefile.am when bumping GIO req.
|
||||
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
|
||||
@ -106,6 +95,14 @@ dnl bundled libdnf
|
||||
PKGDEP_RPMOSTREE_CFLAGS="-I $(pwd)/libdnf -I $(pwd)/libdnf-build $PKGDEP_RPMOSTREE_CFLAGS"
|
||||
PKGDEP_RPMOSTREE_LIBS="-L$(pwd)/libdnf-build/libdnf -ldnf $PKGDEP_RPMOSTREE_LIBS"
|
||||
|
||||
dnl This is the current version in Fedora 25.
|
||||
AS_IF([pkg-config --atleast-version=4.13.0.1 rpm], [
|
||||
have_modern_rpm=true
|
||||
AC_DEFINE([BUILDOPT_HAVE_RPMSQ_SET_INTERRUPT_SAFETY], 1, [Set to 1 if we have interrupt safety API])
|
||||
AC_DEFINE([BUILDOPT_HAVE_RPM_FILETRIGGERS], 1, [Set to 1 if we have file triggers])
|
||||
AC_DEFINE([BUILDOPT_HAVE_RPMFILES], 1, [Set to 1 if we have rpmfiles API]),
|
||||
], [have_modern_rpm=false])
|
||||
|
||||
AC_PATH_PROG([XSLTPROC], [xsltproc])
|
||||
|
||||
GLIB_TESTS
|
||||
@ -209,9 +206,10 @@ AC_OUTPUT
|
||||
echo "
|
||||
$PACKAGE $VERSION
|
||||
|
||||
nts name: $enable_new_name
|
||||
compose tooling: $enable_compose_tooling
|
||||
introspection: $found_introspection
|
||||
bubblewrap: $with_bubblewrap
|
||||
gtk-doc: $enable_gtk_doc
|
||||
built with modern RPM (e.g. Fedora 25+): $have_modern_rpm
|
||||
nts name: $enable_new_name
|
||||
compose tooling: $enable_compose_tooling
|
||||
introspection: $found_introspection
|
||||
bubblewrap: $with_bubblewrap
|
||||
gtk-doc: $enable_gtk_doc
|
||||
"
|
||||
|
@ -2708,6 +2708,45 @@ add_install (RpmOstreeContext *self,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Run %transfiletriggerin */
|
||||
static gboolean
|
||||
run_all_transfiletriggers (RpmOstreeContext *self,
|
||||
rpmts ts,
|
||||
int rootfs_dfd,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
/* Triggers from base packages */
|
||||
g_auto(rpmdbMatchIterator) mi = rpmtsInitIterator (ts, RPMDBI_PACKAGES, NULL, 0);
|
||||
{ Header hdr;
|
||||
while ((hdr = rpmdbNextIterator (mi)) != NULL)
|
||||
{
|
||||
if (!rpmostree_transfiletriggers_run_sync (hdr, rootfs_dfd,
|
||||
cancellable, error))
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/* Triggers from newly added packages */
|
||||
const guint n = (guint)rpmtsNElements (ts);
|
||||
for (guint i = 0; i < n; i++)
|
||||
{
|
||||
rpmte te = rpmtsElement (ts, i);
|
||||
if (rpmteType (te) != TR_ADDED)
|
||||
continue;
|
||||
DnfPackage *pkg = (void*)rpmteKey (te);
|
||||
g_autofree char *path = get_package_relpath (pkg);
|
||||
g_auto(Header) hdr = NULL;
|
||||
if (!get_package_metainfo (self, path, &hdr, NULL, error))
|
||||
return FALSE;
|
||||
|
||||
if (!rpmostree_transfiletriggers_run_sync (hdr, rootfs_dfd,
|
||||
cancellable, error))
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
rpmostree_context_assemble_tmprootfs (RpmOstreeContext *self,
|
||||
int tmprootfs_dfd,
|
||||
@ -2987,10 +3026,13 @@ rpmostree_context_assemble_tmprootfs (RpmOstreeContext *self,
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!run_all_transfiletriggers (self, ordering_ts, tmprootfs_dfd,
|
||||
cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
/* We want this to be the first error message if something went wrong
|
||||
* with a script; see https://github.com/projectatomic/rpm-ostree/pull/888
|
||||
*/
|
||||
|
||||
gboolean skip_sanity_check = FALSE;
|
||||
g_variant_dict_lookup (self->spec->dict, "skip-sanity-check", "b", &skip_sanity_check);
|
||||
if (!skip_sanity_check &&
|
||||
|
@ -29,3 +29,5 @@ 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
|
||||
systemd.transfiletriggerin, RPMOSTREE_SCRIPT_ACTION_IGNORE /* Just does a daemon-reload which we don't want offline */
|
||||
man-db.transfiletriggerin, RPMOSTREE_SCRIPT_ACTION_IGNORE /* https://bugzilla.redhat.com/show_bug.cgi?id=1473402 */
|
||||
|
@ -25,10 +25,13 @@
|
||||
#include "rpmostree-output.h"
|
||||
#include "rpmostree-bwrap.h"
|
||||
#include <err.h>
|
||||
#include <systemd/sd-journal.h>
|
||||
#include "libglnx.h"
|
||||
|
||||
#include "rpmostree-scripts.h"
|
||||
|
||||
#define RPMOSTREE_MESSAGE_FILETRIGGER SD_ID128_MAKE(ef,dd,0e,4e,79,ca,45,d3,88,76,ac,45,e1,28,23,68)
|
||||
|
||||
/* This bit is currently private in librpm */
|
||||
enum rpmscriptFlags_e {
|
||||
RPMSCRIPT_FLAG_NONE = 0,
|
||||
@ -90,11 +93,25 @@ static const KnownRpmScriptKind unsupported_scripts[] = {
|
||||
RPMTAG_VERIFYSCRIPT, RPMTAG_VERIFYSCRIPTPROG, RPMTAG_VERIFYSCRIPTFLAGS},
|
||||
};
|
||||
|
||||
static gboolean
|
||||
fail_if_interp_is_lua (const char *pkg_name,
|
||||
const char *interp,
|
||||
const char *script_desc,
|
||||
GError **error)
|
||||
{
|
||||
static const char lua_builtin[] = "<lua>";
|
||||
if (g_strcmp0 (interp, lua_builtin) == 0)
|
||||
return glnx_throw (error, "Package '%s' has (currently) unsupported %s script in '%s'",
|
||||
pkg_name, lua_builtin, script_desc);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static RpmOstreeScriptAction
|
||||
lookup_script_action (DnfPackage *package,
|
||||
lookup_script_action (const char *pkg_name,
|
||||
const char *scriptdesc)
|
||||
{
|
||||
const char *pkg_script = glnx_strjoina (dnf_package_get_name (package), ".", scriptdesc+1);
|
||||
const char *pkg_script = glnx_strjoina (pkg_name, ".", scriptdesc+1);
|
||||
const struct RpmOstreePackageScriptHandler *handler = rpmostree_script_gperf_lookup (pkg_script, strlen (pkg_script));
|
||||
if (!handler)
|
||||
return RPMOSTREE_SCRIPT_ACTION_DEFAULT;
|
||||
@ -116,7 +133,7 @@ rpmostree_script_txn_validate (DnfPackage *package,
|
||||
if (!(headerIsEntry (hdr, tagval) || headerIsEntry (hdr, progtagval)))
|
||||
continue;
|
||||
|
||||
RpmOstreeScriptAction action = lookup_script_action (package, desc);
|
||||
RpmOstreeScriptAction action = lookup_script_action (dnf_package_get_name (package), desc);
|
||||
switch (action)
|
||||
{
|
||||
case RPMOSTREE_SCRIPT_ACTION_DEFAULT:
|
||||
@ -130,6 +147,15 @@ rpmostree_script_txn_validate (DnfPackage *package,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
script_child_setup_stdin (gpointer data)
|
||||
{
|
||||
int fd = GPOINTER_TO_INT (data);
|
||||
|
||||
if (dup2 (fd, 0) < 0)
|
||||
err (1, "dup2");
|
||||
}
|
||||
|
||||
/* Lowest level script handler in this file; create a bwrap instance and run it
|
||||
* synchronously.
|
||||
*/
|
||||
@ -140,6 +166,7 @@ run_script_in_bwrap_container (int rootfs_fd,
|
||||
const char *interp,
|
||||
const char *script,
|
||||
const char *script_arg,
|
||||
int stdin_fd,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
@ -203,6 +230,9 @@ run_script_in_bwrap_container (int rootfs_fd,
|
||||
if (!bwrap)
|
||||
goto out;
|
||||
|
||||
if (stdin_fd >= 0)
|
||||
rpmostree_bwrap_set_child_setup (bwrap, script_child_setup_stdin, GINT_TO_POINTER (stdin_fd));
|
||||
|
||||
rpmostree_bwrap_append_child_argv (bwrap,
|
||||
interp,
|
||||
postscript_path_container,
|
||||
@ -246,11 +276,8 @@ impl_run_rpm_script (const KnownRpmScriptKind *rpmscript,
|
||||
|
||||
const char *interp = (args && args[0]) ? args[0] : "/bin/sh";
|
||||
/* Check for lua; see also https://github.com/projectatomic/rpm-ostree/issues/749 */
|
||||
static const char lua_builtin[] = "<lua>";
|
||||
if (g_strcmp0 (interp, lua_builtin) == 0)
|
||||
return glnx_throw (error, "Package '%s' has (currently) unsupported %s script in '%s'",
|
||||
dnf_package_get_name (pkg), lua_builtin, rpmscript->desc);
|
||||
|
||||
if (!fail_if_interp_is_lua (interp, dnf_package_get_name (pkg), rpmscript->desc, error))
|
||||
return FALSE;
|
||||
/* http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html#S2-RPM-INSIDE-ERASE-TIME-SCRIPTS */
|
||||
const char *script_arg = NULL;
|
||||
switch (dnf_package_get_action (pkg))
|
||||
@ -278,7 +305,7 @@ impl_run_rpm_script (const KnownRpmScriptKind *rpmscript,
|
||||
|
||||
if (!run_script_in_bwrap_container (rootfs_fd, dnf_package_get_name (pkg),
|
||||
rpmscript->desc, interp, script, script_arg,
|
||||
cancellable, error))
|
||||
-1, cancellable, error))
|
||||
return glnx_prefix_error (error, "Running %s for %s", rpmscript->desc, dnf_package_get_name (pkg));
|
||||
return TRUE;
|
||||
}
|
||||
@ -305,7 +332,7 @@ run_script (const KnownRpmScriptKind *rpmscript,
|
||||
return TRUE;
|
||||
|
||||
const char *desc = rpmscript->desc;
|
||||
RpmOstreeScriptAction action = lookup_script_action (pkg, desc);
|
||||
RpmOstreeScriptAction action = lookup_script_action (dnf_package_get_name (pkg), desc);
|
||||
switch (action)
|
||||
{
|
||||
case RPMOSTREE_SCRIPT_ACTION_IGNORE:
|
||||
@ -318,6 +345,115 @@ run_script (const KnownRpmScriptKind *rpmscript,
|
||||
cancellable, error);
|
||||
}
|
||||
|
||||
#ifdef BUILDOPT_HAVE_RPM_FILETRIGGERS
|
||||
static gboolean
|
||||
write_filename (FILE *f, GString *prefix,
|
||||
GError **error)
|
||||
{
|
||||
if (fwrite_unlocked (prefix->str, 1, prefix->len, f) != prefix->len)
|
||||
return glnx_throw_errno_prefix (error, "fwrite");
|
||||
if (fputc_unlocked ('\n', f) == EOF)
|
||||
return glnx_throw_errno_prefix (error, "fputc");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Used for %transfiletriggerin - basically an implementation of `find -type f` that
|
||||
* writes the filenames to a tmpfile.
|
||||
*/
|
||||
static gboolean
|
||||
write_subdir (int dfd, const char *path,
|
||||
GString *prefix,
|
||||
FILE *f,
|
||||
guint *inout_n_matched,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
/* This is an inlined copy of ot_dfd_iter_init_allow_noent()...if more users
|
||||
* appear we should probably push to libglnx.
|
||||
*/
|
||||
g_assert (*path != '/');
|
||||
glnx_fd_close int target_dfd = glnx_opendirat_with_errno (dfd, path, TRUE);
|
||||
if (target_dfd < 0)
|
||||
{
|
||||
if (errno != ENOENT)
|
||||
return glnx_throw_errno_prefix (error, "opendirat");
|
||||
/* Not early return */
|
||||
return TRUE;
|
||||
}
|
||||
g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
|
||||
if (!glnx_dirfd_iterator_init_take_fd (&target_dfd, &dfd_iter, error))
|
||||
return FALSE;
|
||||
|
||||
while (TRUE)
|
||||
{
|
||||
struct dirent *dent;
|
||||
|
||||
if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent, cancellable, error))
|
||||
return FALSE;
|
||||
if (dent == NULL)
|
||||
break;
|
||||
|
||||
const size_t origlen = prefix->len;
|
||||
g_string_append_c (prefix, '/');
|
||||
g_string_append (prefix, dent->d_name);
|
||||
if (dent->d_type == DT_DIR)
|
||||
{
|
||||
if (!write_subdir (dfd_iter.fd, dent->d_name, prefix, f,
|
||||
inout_n_matched,
|
||||
cancellable, error))
|
||||
return FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!write_filename (f, prefix, error))
|
||||
return FALSE;
|
||||
(*inout_n_matched)++;
|
||||
}
|
||||
g_string_truncate (prefix, origlen);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Given file trigger @pattern (really a subdirectory), traverse the
|
||||
* filesystem @rootfs_fd and write all matches as file names to @f. Used
|
||||
* for %transfiletriggerin.
|
||||
*/
|
||||
static gboolean
|
||||
find_and_write_matching_files (int rootfs_fd, const char *pattern,
|
||||
FILE *f,
|
||||
guint *out_n_matches,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
GLNX_AUTO_PREFIX_ERROR ("Finding matches", error);
|
||||
|
||||
/* Fontconfig in fedora has /usr/local; we don't support RPM
|
||||
* touching /usr/local. While I'm here, proactively
|
||||
* require /usr as a prefix too.
|
||||
*/
|
||||
if (g_str_has_prefix (pattern, "usr/local") ||
|
||||
!g_str_has_prefix (pattern, "usr/"))
|
||||
return TRUE;
|
||||
|
||||
/* The printed buffer does have a leading / */
|
||||
g_autoptr(GString) buf = g_string_new ("/");
|
||||
g_string_append (buf, pattern);
|
||||
/* Strip trailing '/' in the mutable copy we have here */
|
||||
while (buf->len > 0 && buf->str[buf->len-1] == '/')
|
||||
g_string_truncate (buf, buf->len - 1);
|
||||
|
||||
guint n_pattern_matches = 0;
|
||||
if (!write_subdir (rootfs_fd, pattern, buf, f, &n_pattern_matches,
|
||||
cancellable, error))
|
||||
return glnx_prefix_error (error, "pattern '%s'", pattern);
|
||||
*out_n_matches += n_pattern_matches;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Execute all post/post-transaction scripts for @pkg */
|
||||
gboolean
|
||||
rpmostree_posttrans_run_sync (DnfPackage *pkg,
|
||||
Header hdr,
|
||||
@ -325,16 +461,192 @@ rpmostree_posttrans_run_sync (DnfPackage *pkg,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
/* We treat %post and %posttrans equivalently, so do those in one go */
|
||||
for (guint i = 0; i < G_N_ELEMENTS (posttrans_scripts); i++)
|
||||
{
|
||||
if (!run_script (&posttrans_scripts[i], pkg, hdr, rootfs_fd,
|
||||
cancellable, error))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* File triggers, as used by e.g. glib2.spec and vagrant.spec in Fedora. More
|
||||
* info at <http://rpm.org/user_doc/file_triggers.html>.
|
||||
*/
|
||||
gboolean
|
||||
rpmostree_transfiletriggers_run_sync (Header hdr,
|
||||
int rootfs_fd,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
#ifdef BUILDOPT_HAVE_RPM_FILETRIGGERS
|
||||
const char *pkg_name = headerGetString (hdr, RPMTAG_NAME);
|
||||
g_assert (pkg_name);
|
||||
|
||||
g_autofree char *error_prefix = g_strconcat ("Executing %transfiletriggerin for ", pkg_name, NULL);
|
||||
GLNX_AUTO_PREFIX_ERROR (error_prefix, error);
|
||||
|
||||
RpmOstreeScriptAction action = lookup_script_action (pkg_name, "%transfiletriggerin");
|
||||
switch (action)
|
||||
{
|
||||
case RPMOSTREE_SCRIPT_ACTION_IGNORE:
|
||||
return TRUE; /* Note early return */
|
||||
case RPMOSTREE_SCRIPT_ACTION_DEFAULT:
|
||||
break; /* Continue below */
|
||||
}
|
||||
|
||||
headerGetFlags hgflags = HEADERGET_MINMEM;
|
||||
struct rpmtd_s tname, tscripts, tprogs, tflags, tscriptflags;
|
||||
struct rpmtd_s tindex;
|
||||
headerGet (hdr, RPMTAG_TRANSFILETRIGGERNAME, &tname, hgflags);
|
||||
headerGet (hdr, RPMTAG_TRANSFILETRIGGERSCRIPTS, &tscripts, hgflags);
|
||||
headerGet (hdr, RPMTAG_TRANSFILETRIGGERSCRIPTPROG, &tprogs, hgflags);
|
||||
headerGet (hdr, RPMTAG_TRANSFILETRIGGERFLAGS, &tflags, hgflags);
|
||||
headerGet (hdr, RPMTAG_TRANSFILETRIGGERSCRIPTFLAGS, &tscriptflags, hgflags);
|
||||
headerGet (hdr, RPMTAG_TRANSFILETRIGGERINDEX, &tindex, hgflags);
|
||||
g_debug ("pkg %s transtrigger count %u/%u/%u/%u/%u/%u\n",
|
||||
pkg_name, rpmtdCount (&tname), rpmtdCount (&tscripts),
|
||||
rpmtdCount (&tprogs), rpmtdCount (&tflags), rpmtdCount (&tscriptflags),
|
||||
rpmtdCount (&tindex));
|
||||
|
||||
if (rpmtdCount (&tscripts) == 0)
|
||||
return TRUE;
|
||||
|
||||
const guint n_scripts = rpmtdCount (&tscripts);
|
||||
const guint n_names = rpmtdCount (&tname);
|
||||
/* Given multiple matching patterns, RPM expands it into multiple copies.
|
||||
* The trigger index (AIUI) defines where to find the pattern (and flags) given a
|
||||
* script.
|
||||
*
|
||||
* Some librpm source references:
|
||||
* - tagexts.c:triggercondsTagFor()
|
||||
* - rpmscript.c:rpmScriptFromTriggerTag()
|
||||
*/
|
||||
for (guint i = 0; i < n_scripts; i++)
|
||||
{
|
||||
rpmtdInit (&tname);
|
||||
rpmtdInit (&tflags);
|
||||
|
||||
rpmFlags flags = 0;
|
||||
if (rpmtdSetIndex (&tscriptflags, i) >= 0)
|
||||
flags = rpmtdGetNumber (&tscriptflags);
|
||||
|
||||
g_assert_cmpint (rpmtdSetIndex (&tprogs, i), ==, i);
|
||||
const char *interp = rpmtdGetString (&tprogs);
|
||||
if (!interp)
|
||||
interp = "/bin/sh";
|
||||
if (!fail_if_interp_is_lua (interp, pkg_name, "%transfiletriggerin", error))
|
||||
return FALSE;
|
||||
|
||||
g_autofree char *script_owned = NULL;
|
||||
g_assert_cmpint (rpmtdSetIndex (&tscripts, i), ==, i);
|
||||
const char *script = rpmtdGetString (&tscripts);
|
||||
if (!script)
|
||||
continue;
|
||||
if (flags & RPMSCRIPT_FLAG_EXPAND)
|
||||
script = script_owned = rpmExpand (script, NULL);
|
||||
|
||||
g_autoptr(GPtrArray) patterns = g_ptr_array_new_with_free_func (g_free);
|
||||
|
||||
/* Iterate over the trigger "names" which are file patterns */
|
||||
for (guint j = 0; j < n_names; j++)
|
||||
{
|
||||
g_assert_cmpint (rpmtdSetIndex (&tindex, j), ==, j);
|
||||
guint32 tindex_num = *rpmtdGetUint32 (&tindex);
|
||||
|
||||
if (tindex_num != i)
|
||||
continue;
|
||||
|
||||
rpmFlags sense = 0;
|
||||
if (rpmtdSetIndex (&tflags, j) >= 0)
|
||||
sense = rpmtdGetNumber (&tflags);
|
||||
/* See if this is a triggerin (as opposed to triggerun, which we)
|
||||
* don't execute.
|
||||
*/
|
||||
const gboolean trigger_in = (sense & RPMSENSE_TRIGGERIN) > 0;
|
||||
if (!trigger_in)
|
||||
continue;
|
||||
|
||||
g_assert_cmpint (rpmtdSetIndex (&tname, j), ==, j);
|
||||
const char *pattern = rpmtdGetString (&tname);
|
||||
if (!pattern)
|
||||
continue;
|
||||
/* Skip leading `/`, since we use fd-relative access */
|
||||
pattern += strspn (pattern, "/");
|
||||
/* Silently ignore broken patterns for now */
|
||||
if (!*pattern)
|
||||
continue;
|
||||
|
||||
g_ptr_array_add (patterns, g_strdup (pattern));
|
||||
}
|
||||
|
||||
if (patterns->len == 0)
|
||||
continue;
|
||||
|
||||
/* Build up the list of files matching the patterns. librpm uses a pipe and
|
||||
* doesn't do async writes, and hence is subject to deadlock. We could use
|
||||
* a pipe and do async, but an O_TMPFILE is easier for now. There
|
||||
* shouldn't be megabytes of data here, and the parallelism loss in
|
||||
* practice I think is going to be small.
|
||||
*/
|
||||
g_auto(GLnxTmpfile) matching_files_tmpf = { 0, };
|
||||
if (!glnx_open_anonymous_tmpfile (O_RDWR | O_CLOEXEC, &matching_files_tmpf, error))
|
||||
return FALSE;
|
||||
g_autoptr(FILE) tmpf_file = fdopen (matching_files_tmpf.fd, "w");
|
||||
if (!tmpf_file)
|
||||
return glnx_throw_errno_prefix (error, "fdopen");
|
||||
|
||||
g_autoptr(GString) patterns_joined = g_string_new ("");
|
||||
guint n_total_matched = 0;
|
||||
for (guint j = 0; j < patterns->len; j++)
|
||||
{
|
||||
guint n_matched = 0;
|
||||
const char *pattern = patterns->pdata[j];
|
||||
if (j > 0)
|
||||
g_string_append (patterns_joined, ", ");
|
||||
g_string_append (patterns_joined, pattern);
|
||||
if (!find_and_write_matching_files (rootfs_fd, pattern, tmpf_file, &n_matched,
|
||||
cancellable, error))
|
||||
return FALSE;
|
||||
if (n_matched == 0)
|
||||
{
|
||||
/* This is probably a bug...let's log it */
|
||||
sd_journal_print (LOG_INFO, "No files matched %%transfiletriggerin(%s) for %s", pattern, pkg_name);
|
||||
}
|
||||
n_total_matched += n_matched;
|
||||
}
|
||||
|
||||
if (n_total_matched == 0)
|
||||
continue;
|
||||
|
||||
if (!glnx_stdio_file_flush (tmpf_file, error))
|
||||
return FALSE;
|
||||
/* Now, point back to the beginning so the script reads it from the start
|
||||
as stdin */
|
||||
if (lseek (matching_files_tmpf.fd, 0, SEEK_SET) < 0)
|
||||
return glnx_throw_errno_prefix (error, "lseek");
|
||||
|
||||
/* Run it, and log the result */
|
||||
if (!run_script_in_bwrap_container (rootfs_fd, pkg_name,
|
||||
"%transfiletriggerin", interp, script, NULL,
|
||||
matching_files_tmpf.fd, cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
sd_journal_send ("MESSAGE_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(RPMOSTREE_MESSAGE_FILETRIGGER),
|
||||
"MESSAGE=Executed %%transfiletriggerin(%s) for %s; %u matched files",
|
||||
pkg_name, patterns_joined->str, n_total_matched,
|
||||
"SCRIPT_TYPE=%%transfiletriggerin"
|
||||
"PKG=%s", pkg_name,
|
||||
"PATTERNS=%s", patterns_joined->str,
|
||||
"TRIGGER_N_MATCHES=%u", n_total_matched,
|
||||
NULL);
|
||||
}
|
||||
#endif
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Execute all pre-install scripts for @pkg */
|
||||
gboolean
|
||||
rpmostree_pre_run_sync (DnfPackage *pkg,
|
||||
Header hdr,
|
||||
|
@ -58,6 +58,12 @@ rpmostree_posttrans_run_sync (DnfPackage *pkg,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
|
||||
gboolean
|
||||
rpmostree_transfiletriggers_run_sync (Header hdr,
|
||||
int rootfs_fd,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
|
||||
gboolean
|
||||
rpmostree_pre_run_sync (DnfPackage *pkg,
|
||||
Header hdr,
|
||||
|
@ -378,6 +378,9 @@ License: GPLv2+
|
||||
EOF
|
||||
|
||||
local build= install= files= pretrans= pre= post= posttrans= post_args=
|
||||
local transfiletriggerin= transfiletriggerin_patterns=
|
||||
local transfiletriggerin2= transfiletriggerin2_patterns=
|
||||
local transfiletriggerun= transfiletriggerun_patterns=
|
||||
while [ $# -ne 0 ]; do
|
||||
local section=$1; shift
|
||||
local arg=$1; shift
|
||||
@ -392,6 +395,15 @@ EOF
|
||||
post_args="$arg";;
|
||||
version|release|arch|build|install|files|pretrans|pre|post|posttrans)
|
||||
declare $section="$arg";;
|
||||
transfiletriggerin)
|
||||
transfiletriggerin_patterns="$arg";
|
||||
declare $section="$1"; shift;;
|
||||
transfiletriggerin2)
|
||||
transfiletriggerin2_patterns="$arg";
|
||||
declare $section="$1"; shift;;
|
||||
transfiletriggerun)
|
||||
transfiletriggerun_patterns="$arg";
|
||||
declare $section="$1"; shift;;
|
||||
*)
|
||||
assert_not_reached "unhandled section $section";;
|
||||
esac
|
||||
@ -423,6 +435,15 @@ $post
|
||||
${posttrans:+%posttrans}
|
||||
$posttrans
|
||||
|
||||
${transfiletriggerin:+%transfiletriggerin -- ${transfiletriggerin_patterns}}
|
||||
$transfiletriggerin
|
||||
|
||||
${transfiletriggerin2:+%transfiletriggerin -- ${transfiletriggerin2_patterns}}
|
||||
$transfiletriggerin2
|
||||
|
||||
${transfiletriggerun:+%transfiletriggerun -- ${transfiletriggerun_patterns}}
|
||||
$transfiletriggerun
|
||||
|
||||
%install
|
||||
mkdir -p %{buildroot}/usr/bin
|
||||
install $name %{buildroot}/usr/bin
|
||||
|
@ -77,6 +77,39 @@ vm_cmd cat /usr/lib/prefixtest.txt > prefixtest.txt
|
||||
assert_file_has_content prefixtest.txt "/usr"
|
||||
echo "ok script expansion"
|
||||
|
||||
vm_rpmostree rollback
|
||||
vm_reboot
|
||||
vm_rpmostree cleanup -p
|
||||
# File triggers are Fedora+
|
||||
if ! vm_cmd grep -q 'ID=.*centos' /etc/os-release; then
|
||||
# We use /usr/share/licenses since it's small predictable content
|
||||
license_combos="zlib-rpm systemd-tar-rpm sed-tzdata"
|
||||
license_un_combos="zlib systemd-rpm"
|
||||
vm_build_rpm scriptpkg4 \
|
||||
transfiletriggerin "/usr/share/licenses/zlib /usr/share/licenses/rpm" 'sort >/usr/share/transfiletriggerin-license-zlib-rpm.txt' \
|
||||
transfiletriggerun "/usr/share/licenses/zlib" 'sort >/usr/share/transfiletriggerun-license-zlib.txt'
|
||||
vm_build_rpm scriptpkg5 \
|
||||
transfiletriggerin "/usr/share/licenses/systemd /usr/share/licenses/rpm /usr/share/licenses/tar" 'sort >/usr/share/transfiletriggerin-license-systemd-tar-rpm.txt' \
|
||||
transfiletriggerun "/usr/share/licenses/systemd /usr/share/licenses/rpm" 'sort >/usr/share/transfiletriggerun-license-systemd-rpm.txt' \
|
||||
transfiletriggerin2 "/usr/share/licenses/sed /usr/share/licenses/tzdata" 'sort >/usr/share/transfiletriggerin-license-sed-tzdata.txt'
|
||||
vm_rpmostree pkg-add scriptpkg{4,5}
|
||||
vm_rpmostree ex livefs
|
||||
for combo in ${license_combos}; do
|
||||
vm_cmd cat /usr/share/transfiletriggerin-license-${combo}.txt > transfiletriggerin-license-${combo}.txt
|
||||
rm -f transfiletriggerin-fs-${combo}.txt.tmp
|
||||
(for path in $(echo ${combo} | sed -e 's,-, ,g'); do
|
||||
vm_cmd find /usr/share/licenses/${path} -type f
|
||||
done) | sort > transfiletriggerin-fs-license-${combo}.txt
|
||||
diff -u transfiletriggerin-license-${combo}.txt transfiletriggerin-fs-license-${combo}.txt
|
||||
done
|
||||
for combo in ${license_un_combos}; do
|
||||
vm_cmd test '!' -f /usr/share/licenses/transfiletriggerun-license-${combo}.txt
|
||||
done
|
||||
# We really need a reset command to go back to the base layer
|
||||
vm_rpmostree uninstall scriptpkg{4,5}
|
||||
echo "ok transfiletriggerin"
|
||||
fi
|
||||
|
||||
# And now, things that should fail
|
||||
vm_build_rpm rofiles-violation \
|
||||
post "echo should fail >> /usr/share/licenses/glibc/COPYING"
|
||||
@ -84,8 +117,6 @@ if vm_rpmostree install rofiles-violation; then
|
||||
assert_not_reached "installed test-post-rofiles-violation!"
|
||||
fi
|
||||
|
||||
# We really need a reset command to go back to the base layer
|
||||
vm_rpmostree uninstall scriptpkg{1,2,3}
|
||||
vm_cmd 'useradd testuser || true'
|
||||
vm_cmd touch /home/testuser/somedata /tmp/sometmpfile /var/tmp/sometmpfile
|
||||
vm_build_rpm rmrf post "rm --no-preserve-root -rf / &>/dev/null || true"
|
||||
|
Loading…
Reference in New Issue
Block a user