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)
|
AC_SUBST(CAP_LIBS)
|
||||||
LIBS="$save_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.
|
# 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_GIO_UNIX, [gio-unix-2.0])
|
||||||
PKG_CHECK_MODULES(PKGDEP_RPMOSTREE, [gio-unix-2.0 >= 2.40.0 json-glib-1.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_CFLAGS="-I $(pwd)/libdnf -I $(pwd)/libdnf-build $PKGDEP_RPMOSTREE_CFLAGS"
|
||||||
PKGDEP_RPMOSTREE_LIBS="-L$(pwd)/libdnf-build/libdnf -ldnf $PKGDEP_RPMOSTREE_LIBS"
|
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])
|
AC_PATH_PROG([XSLTPROC], [xsltproc])
|
||||||
|
|
||||||
GLIB_TESTS
|
GLIB_TESTS
|
||||||
@ -209,9 +206,10 @@ AC_OUTPUT
|
|||||||
echo "
|
echo "
|
||||||
$PACKAGE $VERSION
|
$PACKAGE $VERSION
|
||||||
|
|
||||||
nts name: $enable_new_name
|
built with modern RPM (e.g. Fedora 25+): $have_modern_rpm
|
||||||
compose tooling: $enable_compose_tooling
|
nts name: $enable_new_name
|
||||||
introspection: $found_introspection
|
compose tooling: $enable_compose_tooling
|
||||||
bubblewrap: $with_bubblewrap
|
introspection: $found_introspection
|
||||||
gtk-doc: $enable_gtk_doc
|
bubblewrap: $with_bubblewrap
|
||||||
|
gtk-doc: $enable_gtk_doc
|
||||||
"
|
"
|
||||||
|
@ -2708,6 +2708,45 @@ add_install (RpmOstreeContext *self,
|
|||||||
return TRUE;
|
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
|
gboolean
|
||||||
rpmostree_context_assemble_tmprootfs (RpmOstreeContext *self,
|
rpmostree_context_assemble_tmprootfs (RpmOstreeContext *self,
|
||||||
int tmprootfs_dfd,
|
int tmprootfs_dfd,
|
||||||
@ -2987,10 +3026,13 @@ rpmostree_context_assemble_tmprootfs (RpmOstreeContext *self,
|
|||||||
return FALSE;
|
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
|
/* 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
|
* with a script; see https://github.com/projectatomic/rpm-ostree/pull/888
|
||||||
*/
|
*/
|
||||||
|
|
||||||
gboolean skip_sanity_check = FALSE;
|
gboolean skip_sanity_check = FALSE;
|
||||||
g_variant_dict_lookup (self->spec->dict, "skip-sanity-check", "b", &skip_sanity_check);
|
g_variant_dict_lookup (self->spec->dict, "skip-sanity-check", "b", &skip_sanity_check);
|
||||||
if (!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
|
glibc-common.post, RPMOSTREE_SCRIPT_ACTION_TODO_SHELL_POSTTRANS
|
||||||
/* Seems to be another case of legacy workaround */
|
/* Seems to be another case of legacy workaround */
|
||||||
gdb.prein, RPMOSTREE_SCRIPT_ACTION_IGNORE
|
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-output.h"
|
||||||
#include "rpmostree-bwrap.h"
|
#include "rpmostree-bwrap.h"
|
||||||
#include <err.h>
|
#include <err.h>
|
||||||
|
#include <systemd/sd-journal.h>
|
||||||
#include "libglnx.h"
|
#include "libglnx.h"
|
||||||
|
|
||||||
#include "rpmostree-scripts.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 */
|
/* This bit is currently private in librpm */
|
||||||
enum rpmscriptFlags_e {
|
enum rpmscriptFlags_e {
|
||||||
RPMSCRIPT_FLAG_NONE = 0,
|
RPMSCRIPT_FLAG_NONE = 0,
|
||||||
@ -90,11 +93,25 @@ static const KnownRpmScriptKind unsupported_scripts[] = {
|
|||||||
RPMTAG_VERIFYSCRIPT, RPMTAG_VERIFYSCRIPTPROG, RPMTAG_VERIFYSCRIPTFLAGS},
|
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
|
static RpmOstreeScriptAction
|
||||||
lookup_script_action (DnfPackage *package,
|
lookup_script_action (const char *pkg_name,
|
||||||
const char *scriptdesc)
|
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));
|
const struct RpmOstreePackageScriptHandler *handler = rpmostree_script_gperf_lookup (pkg_script, strlen (pkg_script));
|
||||||
if (!handler)
|
if (!handler)
|
||||||
return RPMOSTREE_SCRIPT_ACTION_DEFAULT;
|
return RPMOSTREE_SCRIPT_ACTION_DEFAULT;
|
||||||
@ -116,7 +133,7 @@ rpmostree_script_txn_validate (DnfPackage *package,
|
|||||||
if (!(headerIsEntry (hdr, tagval) || headerIsEntry (hdr, progtagval)))
|
if (!(headerIsEntry (hdr, tagval) || headerIsEntry (hdr, progtagval)))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
RpmOstreeScriptAction action = lookup_script_action (package, desc);
|
RpmOstreeScriptAction action = lookup_script_action (dnf_package_get_name (package), desc);
|
||||||
switch (action)
|
switch (action)
|
||||||
{
|
{
|
||||||
case RPMOSTREE_SCRIPT_ACTION_DEFAULT:
|
case RPMOSTREE_SCRIPT_ACTION_DEFAULT:
|
||||||
@ -130,6 +147,15 @@ rpmostree_script_txn_validate (DnfPackage *package,
|
|||||||
return TRUE;
|
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
|
/* Lowest level script handler in this file; create a bwrap instance and run it
|
||||||
* synchronously.
|
* synchronously.
|
||||||
*/
|
*/
|
||||||
@ -140,6 +166,7 @@ run_script_in_bwrap_container (int rootfs_fd,
|
|||||||
const char *interp,
|
const char *interp,
|
||||||
const char *script,
|
const char *script,
|
||||||
const char *script_arg,
|
const char *script_arg,
|
||||||
|
int stdin_fd,
|
||||||
GCancellable *cancellable,
|
GCancellable *cancellable,
|
||||||
GError **error)
|
GError **error)
|
||||||
{
|
{
|
||||||
@ -203,6 +230,9 @@ run_script_in_bwrap_container (int rootfs_fd,
|
|||||||
if (!bwrap)
|
if (!bwrap)
|
||||||
goto out;
|
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,
|
rpmostree_bwrap_append_child_argv (bwrap,
|
||||||
interp,
|
interp,
|
||||||
postscript_path_container,
|
postscript_path_container,
|
||||||
@ -246,11 +276,8 @@ impl_run_rpm_script (const KnownRpmScriptKind *rpmscript,
|
|||||||
|
|
||||||
const char *interp = (args && args[0]) ? args[0] : "/bin/sh";
|
const char *interp = (args && args[0]) ? args[0] : "/bin/sh";
|
||||||
/* Check for lua; see also https://github.com/projectatomic/rpm-ostree/issues/749 */
|
/* Check for lua; see also https://github.com/projectatomic/rpm-ostree/issues/749 */
|
||||||
static const char lua_builtin[] = "<lua>";
|
if (!fail_if_interp_is_lua (interp, dnf_package_get_name (pkg), rpmscript->desc, error))
|
||||||
if (g_strcmp0 (interp, lua_builtin) == 0)
|
return FALSE;
|
||||||
return glnx_throw (error, "Package '%s' has (currently) unsupported %s script in '%s'",
|
|
||||||
dnf_package_get_name (pkg), lua_builtin, rpmscript->desc);
|
|
||||||
|
|
||||||
/* http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html#S2-RPM-INSIDE-ERASE-TIME-SCRIPTS */
|
/* http://ftp.rpm.org/max-rpm/s1-rpm-inside-scripts.html#S2-RPM-INSIDE-ERASE-TIME-SCRIPTS */
|
||||||
const char *script_arg = NULL;
|
const char *script_arg = NULL;
|
||||||
switch (dnf_package_get_action (pkg))
|
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),
|
if (!run_script_in_bwrap_container (rootfs_fd, dnf_package_get_name (pkg),
|
||||||
rpmscript->desc, interp, script, script_arg,
|
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 glnx_prefix_error (error, "Running %s for %s", rpmscript->desc, dnf_package_get_name (pkg));
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@ -305,7 +332,7 @@ run_script (const KnownRpmScriptKind *rpmscript,
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
|
|
||||||
const char *desc = rpmscript->desc;
|
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)
|
switch (action)
|
||||||
{
|
{
|
||||||
case RPMOSTREE_SCRIPT_ACTION_IGNORE:
|
case RPMOSTREE_SCRIPT_ACTION_IGNORE:
|
||||||
@ -318,6 +345,115 @@ run_script (const KnownRpmScriptKind *rpmscript,
|
|||||||
cancellable, error);
|
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
|
gboolean
|
||||||
rpmostree_posttrans_run_sync (DnfPackage *pkg,
|
rpmostree_posttrans_run_sync (DnfPackage *pkg,
|
||||||
Header hdr,
|
Header hdr,
|
||||||
@ -325,16 +461,192 @@ rpmostree_posttrans_run_sync (DnfPackage *pkg,
|
|||||||
GCancellable *cancellable,
|
GCancellable *cancellable,
|
||||||
GError **error)
|
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++)
|
for (guint i = 0; i < G_N_ELEMENTS (posttrans_scripts); i++)
|
||||||
{
|
{
|
||||||
if (!run_script (&posttrans_scripts[i], pkg, hdr, rootfs_fd,
|
if (!run_script (&posttrans_scripts[i], pkg, hdr, rootfs_fd,
|
||||||
cancellable, error))
|
cancellable, error))
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
return TRUE;
|
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
|
gboolean
|
||||||
rpmostree_pre_run_sync (DnfPackage *pkg,
|
rpmostree_pre_run_sync (DnfPackage *pkg,
|
||||||
Header hdr,
|
Header hdr,
|
||||||
|
@ -58,6 +58,12 @@ rpmostree_posttrans_run_sync (DnfPackage *pkg,
|
|||||||
GCancellable *cancellable,
|
GCancellable *cancellable,
|
||||||
GError **error);
|
GError **error);
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
rpmostree_transfiletriggers_run_sync (Header hdr,
|
||||||
|
int rootfs_fd,
|
||||||
|
GCancellable *cancellable,
|
||||||
|
GError **error);
|
||||||
|
|
||||||
gboolean
|
gboolean
|
||||||
rpmostree_pre_run_sync (DnfPackage *pkg,
|
rpmostree_pre_run_sync (DnfPackage *pkg,
|
||||||
Header hdr,
|
Header hdr,
|
||||||
|
@ -378,6 +378,9 @@ License: GPLv2+
|
|||||||
EOF
|
EOF
|
||||||
|
|
||||||
local build= install= files= pretrans= pre= post= posttrans= post_args=
|
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
|
while [ $# -ne 0 ]; do
|
||||||
local section=$1; shift
|
local section=$1; shift
|
||||||
local arg=$1; shift
|
local arg=$1; shift
|
||||||
@ -392,6 +395,15 @@ EOF
|
|||||||
post_args="$arg";;
|
post_args="$arg";;
|
||||||
version|release|arch|build|install|files|pretrans|pre|post|posttrans)
|
version|release|arch|build|install|files|pretrans|pre|post|posttrans)
|
||||||
declare $section="$arg";;
|
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";;
|
assert_not_reached "unhandled section $section";;
|
||||||
esac
|
esac
|
||||||
@ -423,6 +435,15 @@ $post
|
|||||||
${posttrans:+%posttrans}
|
${posttrans:+%posttrans}
|
||||||
$posttrans
|
$posttrans
|
||||||
|
|
||||||
|
${transfiletriggerin:+%transfiletriggerin -- ${transfiletriggerin_patterns}}
|
||||||
|
$transfiletriggerin
|
||||||
|
|
||||||
|
${transfiletriggerin2:+%transfiletriggerin -- ${transfiletriggerin2_patterns}}
|
||||||
|
$transfiletriggerin2
|
||||||
|
|
||||||
|
${transfiletriggerun:+%transfiletriggerun -- ${transfiletriggerun_patterns}}
|
||||||
|
$transfiletriggerun
|
||||||
|
|
||||||
%install
|
%install
|
||||||
mkdir -p %{buildroot}/usr/bin
|
mkdir -p %{buildroot}/usr/bin
|
||||||
install $name %{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"
|
assert_file_has_content prefixtest.txt "/usr"
|
||||||
echo "ok script expansion"
|
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
|
# And now, things that should fail
|
||||||
vm_build_rpm rofiles-violation \
|
vm_build_rpm rofiles-violation \
|
||||||
post "echo should fail >> /usr/share/licenses/glibc/COPYING"
|
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!"
|
assert_not_reached "installed test-post-rofiles-violation!"
|
||||||
fi
|
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 'useradd testuser || true'
|
||||||
vm_cmd touch /home/testuser/somedata /tmp/sometmpfile /var/tmp/sometmpfile
|
vm_cmd touch /home/testuser/somedata /tmp/sometmpfile /var/tmp/sometmpfile
|
||||||
vm_build_rpm rmrf post "rm --no-preserve-root -rf / &>/dev/null || true"
|
vm_build_rpm rmrf post "rm --no-preserve-root -rf / &>/dev/null || true"
|
||||||
|
Loading…
Reference in New Issue
Block a user