unchanged: --- /dev/null +++ b/doc/manual/filetriggers @@ -0,0 +1,88 @@ +== Introduction == + +Filetriggers allow to run some scripts when some file has been added or removed. + +The typical use cases are: + +* updating /etc/ld.so.cache when some libraries have been added/removed in /usr/lib or /lib +* running update-menus to update menus of non-XDG compliant desktops when some *.desktop have been added/removed in /usr/share/applications + +== Usage == + +Add the following to your macros to enable filetriggers: + +<pre> +%_filetriggers_dir /var/lib/rpm/filetriggers +</pre> + +Then install your watchers: + +<pre> +% cat /var/lib/rpm/filetriggers/ldconfig.filter +^.(/lib|/usr/lib)/[^/]*\.so\. +% cat /var/lib/rpm/filetriggers/ldconfig.script +#!/bin/sh +ldconfig -X +% cat /var/lib/rpm/filetriggers/gtk-icon-cache-hicolor.filter +^./usr/share/icons/hicolor/ +% cat /var/lib/rpm/filetriggers/gtk-icon-cache-hicolor.script +#!/bin/sh +/usr/bin/gtk-update-icon-cache --force --quiet /usr/share/icons/hicolor +</pre> + +== Implementation == + +=== files-awaiting-filetriggers === + +When a package is successfully installed (or removed), rpm will append the +installed (resp. removed) files to {{file|/var/lib/rpm/files-awaiting-filetriggers}}. + +The format is quite simple: <installed-or-removed> <filename> + +where <installed-or-removed> ::= "+" | "-" + +For example, after installing hexedit: + +<pre> ++/usr/bin/hexedit ++/usr/share/doc/hexedit ++/usr/share/doc/hexedit/COPYING ++/usr/share/doc/hexedit/TODO ++/usr/share/doc/hexedit/hexedit-1.2.12.lsm ++/usr/share/man/man1/hexedit.1.lzma +</pre> + +=== rpmRunFileTriggers === + +This function is called before running {{macro|%posttrans}} scriptlets. It can be +disabled using --noscripts or --notriggers (no special command line option +introduced for now). + +For each {{file|%_filetriggers_dir/*.filter}}, the regexp (POSIX Extended Regular +Expression) on the first line is applied on {{file|files-awaiting-filetriggers}}. If +some files match, the corresponding {{prog|%_filetriggers_dir/<name>.script}} is +called, with matching lines passed in stdin. + +Note that this is done in parallel, so multiple {{prog|<name>.script}} may be running +at the same time. + +When it's done, {{file|/var/lib/rpm/files-awaiting-filetriggers}} is removed. + +== Comments == + +Characteristics of this implementation : +* if the transaction is aborted, the next successful transaction will run on every succesful package installation/removal. This is due to files-awaiting-filetriggers which allows to keep state of what is done or not. +* it needs only a light patch in rpm +* files-awaiting-filetriggers may be getting big (eg: 5MB on a big transaction of 1000 packages) +* not fully integrated in rpm: +** --nofiletriggers and RPMTRANS_FLAG_NOFILETRIGGERS should be added +** a new command {{cmd|rpm --run-filetriggers}} would be useful to force running filetriggers, and only them. + +Some links about filetriggers: +* triggers in dpkg: +** http://www.dpkg.org/dpkg/Triggers +** http://lists.debian.org/debian-devel/2008/03/msg00931.html +** http://lists.debian.org/debian-dpkg/2007/04/msg00076.html +* what may get in rpm.org: +** http://www.mail-archive.com/rpm-maint@lists.rpm.org/msg00789.html +** http://www.mail-archive.com/rpm-maint@lists.rpm.org/msg00788.html unchanged: diff -u b/lib/filetriggers.c rpm-4.6.0/lib/filetriggers.c --- b/lib/filetriggers.c +++ rpm-4.6.0/lib/filetriggers.c 2010-04-16 16:58:09.244840369 +0200 @@ -0,0 +1,266 @@ +#include "system.h" +#include <rpm/rpmlib.h> +#include <rpm/rpmlog.h> +#include <rpm/rpmmacro.h> /* XXX for rpmExpand */ + +#include "rpmfi.h" +#include "misc.h" /* mayAddToFilesAwaitingFiletriggers rpmRunFileTriggers */ + +#include <rpm/rpmfileutil.h> + +#include <regex.h> + +static const char *files_awaiting_filetriggers = "/var/lib/rpm/files-awaiting-filetriggers"; + +#define FILTER_EXTENSION ".filter" + +static char *_filetriggers_dir = NULL; +static char *filetriggers_dir(void) +{ + if (!_filetriggers_dir) _filetriggers_dir = rpmExpand("%{?_filetriggers_dir}", NULL); + return _filetriggers_dir && _filetriggers_dir[0] ? _filetriggers_dir : NULL; +} + +static char *get_filter_name(const char *filter_filename) { + char *p = strrchr(filter_filename, '/'); + return p ? strndup(p+1, strlen(p+1) - strlen(FILTER_EXTENSION)) : NULL; +} + +int mayAddToFilesAwaitingFiletriggers(const char *rootDir, rpmfi fi, int install_or_erase) +{ + int rc = RPMRC_FAIL; + + if (!filetriggers_dir()) return RPMRC_OK; + + fi = rpmfiInit(fi, 0); + if (fi == NULL) return RPMRC_OK; + + char *file = rpmGenPath(rootDir, files_awaiting_filetriggers, NULL); + if (!file) return RPMRC_FAIL; + + FILE *f = fopen(file, "a"); + + if (f == NULL) { + rpmlog(RPMLOG_ERR, _("%s: open failed: %s\n"), file, strerror(errno)); + goto exit; + } + + while (rpmfiNext(fi) >= 0) + { + fputc(install_or_erase ? '+' : '-', f); + fputs(rpmfiFN(fi), f); + fputc('\n', f); + } + fclose(f); + rc = RPMRC_OK; +exit: + _free(file); + return rc; +} + +struct filetrigger_raw { + char *regexp; + char *name; +}; +struct filetrigger { + regex_t regexp; + char *name; + int command_pipe; + int command_pid; +}; + +static int getFiletriggers_raw(const char *rootDir, int *nb, struct filetrigger_raw **list_raw) +{ + char *globstr = rpmGenPath(rootDir, filetriggers_dir(), "*" FILTER_EXTENSION); + ARGV_t filter_files = NULL; + int i; + struct stat st; + + rpmGlob(globstr, nb, &filter_files); + if (*nb == 0) return RPMRC_OK; + + *list_raw = calloc(*nb, sizeof(**list_raw)); + + for (i = 0; i < *nb; i++) { + int filter = open(filter_files[i], O_RDONLY); + if (filter == -1) { + rpmlog(RPMLOG_ERR, "opening %s failed: %s\n", filter_files[i], strerror(errno)); + continue; + } + if (fstat(filter, &st) == 0 && st.st_size > 0) { + char *regexp = malloc(st.st_size+1); + regexp[st.st_size] = '\0'; + if (read(filter, regexp, st.st_size) != st.st_size) { + rpmlog(RPMLOG_ERR, "reading %s failed: %s\n", filter_files[i], strerror(errno)); + } else { + char *p = strchr(regexp, '\n'); + if (p) *p = '\0'; + (*list_raw)[i].regexp = regexp; + (*list_raw)[i].name = get_filter_name(filter_files[i]); + } + } + close(filter); + } + _free(globstr); + argvFree(filter_files); + + return RPMRC_OK; +} + +static char *computeMatchesAnyFilter(int nb, struct filetrigger_raw *list_raw) +{ + int i, regexp_str_size = 0; + + for (i = 0; i < nb; i++) regexp_str_size += strlen(list_raw[i].regexp) + 1; + + char *matches_any = malloc(regexp_str_size); + char *p = stpcpy(matches_any, list_raw[0].regexp); + + for (i = 1; i < nb; i++) { + *p++ = '|'; + p = stpcpy(p, list_raw[i].regexp); + } + rpmlog(RPMLOG_DEBUG, "[filetriggers] matches-any regexp is %s\n", matches_any); + return matches_any; +} + +static void compileFiletriggersRegexp(char *raw, regex_t *regexp) +{ + if (regcomp(regexp, raw, REG_NOSUB | REG_EXTENDED | REG_NEWLINE) != 0) { + rpmlog(RPMLOG_ERR, "failed to compile filetrigger filter: %s\n", raw); + } + free(raw); +} + +static void getFiletriggers(const char *rootDir, regex_t *matches_any, int *nb, struct filetrigger **list) +{ + struct filetrigger_raw *list_raw; + + getFiletriggers_raw(rootDir, nb, &list_raw); + if (*nb == 0) return; + + compileFiletriggersRegexp(computeMatchesAnyFilter(*nb, list_raw), matches_any); + + *list = calloc(*nb, sizeof(**list)); + int i; + for (i = 0; i < *nb; i++) { + (*list)[i].name = list_raw[i].name; + compileFiletriggersRegexp(list_raw[i].regexp, &(*list)[i].regexp); + } + free(list_raw); +} + +static void freeFiletriggers(regex_t *matches_any, int nb, struct filetrigger *list) +{ + regfree(matches_any); + int i; + for (i = 0; i < nb; i++) { + regfree(&list[i].regexp); + free(list[i].name); + } + free(list); +} + +static int is_regexp_matching(regex_t *re, const char *s) +{ + return regexec(re, s, (size_t) 0, NULL, 0) == 0; +} + +static int popen_with_root(const char *rootDir, const char *cmd, int *pid) +{ + int pipes[2]; + long fd_attrs; + + if (pipe(pipes) != 0) return 0; + fd_attrs = fcntl(pipes[1], F_GETFD); + if (fd_attrs == -1) { + rpmlog(RPMLOG_ERR, "failed to set CLOEXEC on pipe write FD"); + return 0; + } + fcntl(pipes[1], F_SETFD, fd_attrs | FD_CLOEXEC); + *pid = fork(); + if (*pid == 0) { + dup2(pipes[0], STDIN_FILENO); + if (pipes[0] != STDIN_FILENO) { + close(pipes[0]); + } + + if (rootDir != NULL && strcmp(rootDir, "/") != 0) { + if (chroot(rootDir) != 0) { + rpmlog(RPMLOG_ERR, "chroot to %s failed\n", rootDir); + _exit(-1); + } + chdir("/"); + } + const char *argv[2]; + argv[0] = cmd; + argv[1] = NULL; + execv(argv[0], (char *const *) argv); + _exit(-1); + } + + close(pipes[0]); + + return pipes[1]; +} + +static void mayStartFiletrigger(const char *rootDir, struct filetrigger *trigger) +{ + if (!trigger->command_pipe) { + char *cmd = NULL; + if (asprintf(&cmd, "%s/%s.script", filetriggers_dir(), trigger->name) != -1) { + rpmlog(RPMLOG_DEBUG, "[filetriggers] spawning %s\n", cmd); + trigger->command_pipe = popen_with_root(rootDir, cmd, &trigger->command_pid); + _free(cmd); + } + } +} + +void rpmRunFileTriggers(const char *rootDir) +{ + regex_t matches_any; + int nb = 0; + struct filetrigger *list = NULL; + + if (!filetriggers_dir()) return; + rpmlog(RPMLOG_DEBUG, _("[filetriggers] starting\n")); + + getFiletriggers(rootDir, &matches_any, &nb, &list); + + char *file = rpmGenPath(rootDir, files_awaiting_filetriggers, NULL); + + FILE *awaiting = NULL; + if (nb > 0) + awaiting = fopen(file, "r"); + if (awaiting) { + char tmp[1024]; + + void *oldhandler = signal(SIGPIPE, SIG_IGN); + + while (fgets(tmp, sizeof(tmp), awaiting)) + if (is_regexp_matching(&matches_any, tmp)) { + rpmlog(RPMLOG_DEBUG, "[filetriggers] matches-any regexp found %s", tmp); + int i; + for (i = 0; i < nb; i++) + if (is_regexp_matching(&list[i].regexp, tmp)) { + mayStartFiletrigger(rootDir, &list[i]); + write(list[i].command_pipe, tmp, strlen(tmp)); + } + } + fclose(awaiting); + + int i; + for (i = 0; i < nb; i++) + if (list[i].command_pipe) { + close(list[i].command_pipe); + int status; + rpmlog(RPMLOG_DEBUG, "[filetriggers] waiting for %s to end\n", list[i].name); + waitpid(list[i].command_pid, &status, 0); + } + freeFiletriggers(&matches_any, nb, list); + + signal(SIGPIPE, oldhandler); + } + unlink(file); + _free(file); +} unchanged: diff --git a/lib/misc.h b/lib/misc.h index e2a5453..0bce564 100644 --- a/lib/misc.h +++ b/lib/misc.h @@ -36,6 +36,11 @@ const char * uidToUname(uid_t uid); RPM_GNUC_INTERNAL const char * gidToGname(gid_t gid); +__attribute__ ((visibility("hidden"))) + int mayAddToFilesAwaitingFiletriggers(const char *rootDir, rpmfi fi, int install_or_erase); + +void rpmRunFileTriggers(const char *rootDir); + #ifdef __cplusplus } #endif --- a/lib/transaction.c +++ b/lib/transaction.c 2011-03-04 19:43:00.998583785 +0100 @@ -1465,11 +1465,19 @@ failed = rpmpsmStage(psm, stage); (void) rpmswExit(rpmtsOp(ts, op), 0); psm = rpmpsmFree(psm); - rpmteClose(p, ts, 1); + rpmteClose(p, ts, 0); } if (failed) { rpmteMarkFailed(p, ts); rc++; + rpmteSetFI(p, NULL); + } else { + if (!(rpmtsFlags(ts) & RPMTRANS_FLAG_TEST)) { + int install_or_erase = (tetype == TR_ADDED)?1:0; + mayAddToFilesAwaitingFiletriggers(rpmtsRootDir(ts), rpmteFI(p), + install_or_erase); + } + rpmteSetFI(p, NULL); } (void) rpmdbSync(rpmtsGetRdb(ts)); } @@ -1529,6 +1537,9 @@ /* Run post-transaction scripts unless disabled */ if (!(rpmtsFlags(ts) & (RPMTRANS_FLAG_TEST|RPMTRANS_FLAG_NOPOST))) { + if ((rpmtsFlags(ts) & _noTransTriggers) != _noTransTriggers) { + rpmRunFileTriggers(rpmtsRootDir(ts)); + } rpmlog(RPMLOG_DEBUG, "running post-transaction scripts\n"); runTransScripts(ts, RPMTAG_POSTTRANS); } --- a/lib/Makefile.am +++ b/lib/Makefile.am 2011-03-27 23:28:43.200135747 +0200 @@ -33,7 +33,7 @@ rpmlead.c rpmlead.h rpmps.c rpmrc.c \ rpmte.c rpmte_internal.h rpmts.c \ rpmvercmp.c signature.c signature.h transaction.c \ - verify.c rpmlock.c rpmlock.h misc.h \ + filetriggers.c verify.c rpmlock.c rpmlock.h misc.h \ legacy.c merge.c \ rpmliblua.c rpmliblua.h if SQLITE3