* The following hook functions are called if they exist:
        pre_remove(pkgname, version)
        post_remove(pkgname, version)
        pre_upgrade(pkgname, ver, oldver)
        post_upgrade(pkgname, ver, oldver)
        pre_install(pkgname, ver)
        post_install(pkgname, ver)

    * The hook directory is configurable.

Signed-off-by: Daniel Mendler <[email protected]>
---
 etc/pacman.conf.in     |    1 +
 lib/libalpm/add.c      |   28 +++++++++++++++++++++
 lib/libalpm/alpm.h     |    5 +++-
 lib/libalpm/handle.c   |   50 ++++++++++++++++++++++++++++++++++++++
 lib/libalpm/handle.h   |    1 +
 lib/libalpm/remove.c   |   12 +++++++++
 lib/libalpm/trans.c    |   63 ++++++++++++++++++++++++++++++++++++++++++++++++
 lib/libalpm/trans.h    |    1 +
 src/pacman/Makefile.am |    2 +
 src/pacman/conf.h      |    3 ++
 src/pacman/pacman.c    |   30 ++++++++++++++++++++++
 11 files changed, 195 insertions(+), 1 deletions(-)

diff --git a/etc/pacman.conf.in b/etc/pacman.conf.in
index 1105db9..95f6c5a 100644
--- a/etc/pacman.conf.in
+++ b/etc/pacman.conf.in
@@ -13,6 +13,7 @@
 #DBPath      = @localstatedir@/lib/pacman/
 #CacheDir    = @localstatedir@/cache/pacman/pkg/
 #LogFile     = @localstatedir@/log/pacman.log
+#HookDir     = @sysconfdir@/pacman.d/hooks/
 HoldPkg     = pacman glibc
 # If upgrades are available for these packages they will be asked for first
 SyncFirst   = pacman
diff --git a/lib/libalpm/add.c b/lib/libalpm/add.c
index c37198c..498f6f2 100644
--- a/lib/libalpm/add.c
+++ b/lib/libalpm/add.c
@@ -519,6 +519,12 @@ static int commit_single_pkg(pmpkg_t *newpkg, size_t 
pkg_current,
                        _alpm_runscriptlet(handle->root, 
newpkg->origin_data.file,
                                        "pre_upgrade", newpkg->version, 
oldpkg->version);
                }
+
+               /* pre_upgrade hooks */
+               if(!(trans->flags & PM_TRANS_FLAG_NOHOOKS)) {
+                       _alpm_runhooks(handle->root, handle->hookdir, 
"pre_upgrade",
+                               newpkg->name, newpkg->version, oldpkg->version, 
NULL);
+               }
        } else {
                is_upgrade = 0;
 
@@ -531,6 +537,12 @@ static int commit_single_pkg(pmpkg_t *newpkg, size_t 
pkg_current,
                        _alpm_runscriptlet(handle->root, 
newpkg->origin_data.file,
                                        "pre_install", newpkg->version, NULL);
                }
+
+               /* pre_install hooks */
+               if(!(trans->flags & PM_TRANS_FLAG_NOHOOKS)) {
+                       _alpm_runhooks(handle->root, handle->hookdir, 
"pre_install",
+                               newpkg->name, newpkg->version, NULL);
+               }
        }
 
        /* we override any pre-set reason if we have alldeps or allexplicit set 
*/
@@ -705,6 +717,22 @@ static int commit_single_pkg(pmpkg_t *newpkg, size_t 
pkg_current,
                }
        }
 
+       /* run the post-install hook  */
+       if(!(trans->flags & PM_TRANS_FLAG_NOHOOKS)) {
+               if(is_upgrade) {
+                       _alpm_runhooks(handle->root, handle->hookdir, 
"post_upgrade",
+                               alpm_pkg_get_name(newpkg),
+                               alpm_pkg_get_version(newpkg),
+                               oldpkg ? alpm_pkg_get_version(oldpkg) : NULL,
+                               NULL);
+               } else {
+                       _alpm_runhooks(handle->root, handle->hookdir, 
"post_install",
+                               alpm_pkg_get_name(newpkg),
+                               alpm_pkg_get_version(newpkg),
+                               NULL);
+               }
+       }
+
        if(is_upgrade) {
                EVENT(trans, PM_TRANS_EVT_UPGRADE_DONE, newpkg, oldpkg);
        } else {
diff --git a/lib/libalpm/alpm.h b/lib/libalpm/alpm.h
index 48a99d2..ecd295f 100644
--- a/lib/libalpm/alpm.h
+++ b/lib/libalpm/alpm.h
@@ -126,6 +126,9 @@ int alpm_option_set_logfile(const char *logfile);
 const char *alpm_option_get_lockfile(void);
 /* no set_lockfile, path is determined from dbpath */
 
+const char *alpm_option_get_hookdir(void);
+int alpm_option_set_hookdir(const char *hookdir);
+
 int alpm_option_get_usesyslog(void);
 void alpm_option_set_usesyslog(int usesyslog);
 
@@ -282,7 +285,7 @@ typedef enum _pmtransflag_t {
        PM_TRANS_FLAG_DOWNLOADONLY = (1 << 9),
        PM_TRANS_FLAG_NOSCRIPTLET = (1 << 10),
        PM_TRANS_FLAG_NOCONFLICTS = (1 << 11),
-       /* (1 << 12) flag can go here */
+       PM_TRANS_FLAG_NOHOOKS = (1 << 12),
        PM_TRANS_FLAG_NEEDED = (1 << 13),
        PM_TRANS_FLAG_ALLEXPLICIT = (1 << 14),
        PM_TRANS_FLAG_UNNEEDED = (1 << 15),
diff --git a/lib/libalpm/handle.c b/lib/libalpm/handle.c
index 8872ed0..7457849 100644
--- a/lib/libalpm/handle.c
+++ b/lib/libalpm/handle.c
@@ -79,6 +79,7 @@ void _alpm_handle_free(pmhandle_t *handle)
        FREELIST(handle->cachedirs);
        FREE(handle->logfile);
        FREE(handle->lockfile);
+       FREE(handle->hookdir);
        FREE(handle->arch);
        FREELIST(handle->dbs_sync);
        FREELIST(handle->noupgrade);
@@ -169,6 +170,15 @@ const char SYMEXPORT *alpm_option_get_lockfile()
        return handle->lockfile;
 }
 
+const char SYMEXPORT *alpm_option_get_hookdir()
+{
+       if (handle == NULL) {
+               pm_errno = PM_ERR_HANDLE_NULL;
+               return NULL;
+       }
+       return handle->hookdir;
+}
+
 int SYMEXPORT alpm_option_get_usesyslog()
 {
        if (handle == NULL) {
@@ -455,6 +465,46 @@ int SYMEXPORT alpm_option_set_logfile(const char *logfile)
        return(0);
 }
 
+int SYMEXPORT alpm_option_set_hookdir(const char *hookdir)
+{
+       struct stat st;
+       char *realhookdir;
+       size_t hookdirlen;
+
+       ALPM_LOG_FUNC;
+
+       if(!hookdir) {
+               pm_errno = PM_ERR_WRONG_ARGS;
+               return(-1);
+       }
+       if(stat(hookdir, &st) == -1 || !S_ISDIR(st.st_mode)) {
+               pm_errno = PM_ERR_NOT_A_DIR;
+               return(-1);
+       }
+
+       realhookdir = calloc(PATH_MAX+1, sizeof(char));
+       if(!realpath(hookdir, realhookdir)) {
+               FREE(realhookdir);
+               pm_errno = PM_ERR_NOT_A_DIR;
+               return(-1);
+       }
+
+       /* verify hookdir ends in a '/' */
+       hookdirlen = strlen(realhookdir);
+       if(realhookdir[hookdirlen-1] != '/') {
+               hookdirlen += 1;
+       }
+       if(handle->hookdir) {
+               FREE(handle->hookdir);
+       }
+       handle->hookdir = calloc(hookdirlen + 1, sizeof(char));
+       strncpy(handle->hookdir, realhookdir, hookdirlen);
+       handle->hookdir[hookdirlen-1] = '/';
+       FREE(realhookdir);
+       _alpm_log(PM_LOG_DEBUG, "option 'hookdir' = %s\n", handle->hookdir);
+       return(0);
+}
+
 void SYMEXPORT alpm_option_set_usesyslog(int usesyslog)
 {
        handle->usesyslog = usesyslog;
diff --git a/lib/libalpm/handle.h b/lib/libalpm/handle.h
index fa29d11..e728f76 100644
--- a/lib/libalpm/handle.h
+++ b/lib/libalpm/handle.h
@@ -48,6 +48,7 @@ typedef struct _pmhandle_t {
        char *dbpath;            /* Base path to pacman's DBs */
        char *logfile;           /* Name of the log file */
        char *lockfile;          /* Name of the lock file */
+       char *hookdir;           /* Path of the hooks directory */
        alpm_list_t *cachedirs;  /* Paths to pacman cache directories */
 
        /* package lists */
diff --git a/lib/libalpm/remove.c b/lib/libalpm/remove.c
index 0641e43..be86a38 100644
--- a/lib/libalpm/remove.c
+++ b/lib/libalpm/remove.c
@@ -393,6 +393,12 @@ int _alpm_remove_packages(pmtrans_t *trans, pmdb_t *db)
                                        alpm_pkg_get_version(info), NULL);
                }
 
+               /* run the pre-remove hooks  */
+               if(!(trans->flags & PM_TRANS_FLAG_NOHOOKS)) {
+                       _alpm_runhooks(handle->root, handle->hookdir, 
"pre_remove",
+                               pkgname, alpm_pkg_get_version(info), NULL);
+               }
+
                if(!(trans->flags & PM_TRANS_FLAG_DBONLY)) {
                        alpm_list_t *files = alpm_pkg_get_files(info);
                        alpm_list_t *newfiles;
@@ -438,6 +444,12 @@ int _alpm_remove_packages(pmtrans_t *trans, pmdb_t *db)
                                        alpm_pkg_get_version(info), NULL);
                }
 
+               /* run the post-remove hooks */
+               if(!(trans->flags & PM_TRANS_FLAG_NOHOOKS)) {
+                       _alpm_runhooks(handle->root, handle->hookdir, 
"post_remove",
+                               pkgname, alpm_pkg_get_version(info), NULL);
+               }
+
                /* remove the package from the database */
                _alpm_log(PM_LOG_DEBUG, "updating database\n");
                _alpm_log(PM_LOG_DEBUG, "removing database entry '%s'\n", 
pkgname);
diff --git a/lib/libalpm/trans.c b/lib/libalpm/trans.c
index 5b06505..95a6872 100644
--- a/lib/libalpm/trans.c
+++ b/lib/libalpm/trans.c
@@ -32,6 +32,7 @@
 #include <sys/statvfs.h>
 #include <errno.h>
 #include <limits.h>
+#include <dirent.h>
 
 /* libalpm */
 #include "trans.h"
@@ -414,6 +415,68 @@ cleanup:
        return(retval);
 }
 
+int _alpm_runhooks(const char* root, const char *hookdir, const char* script, 
...)
+{
+       char buf[PATH_MAX];
+       char argstr[PATH_MAX] = "";
+       char *argv[] = { "sh", "-c", buf, NULL };
+
+       ALPM_LOG_FUNC;
+
+       if(hookdir == NULL || access(hookdir, R_OK)) {
+               return(0);
+       }
+
+       /* join arguments */
+       va_list args;
+       va_start(args, script);
+       const char *arg;
+       char *p = argstr, *end = argstr + PATH_MAX - 1;
+       while ((arg = va_arg(args, const char*)) != NULL) {
+               size_t len = strlen(arg);
+               if (end - p < len + 1) {
+                       break;
+               }
+               *p++ = ' ';
+               strcpy(p, arg);
+               p += len;
+       }
+       va_end(args);
+
+       /* read all directory entries (sorted) */
+       struct dirent **ent;
+       int num_ents = scandir(hookdir, &ent, NULL, alphasort);
+       if(num_ents < 0) {
+               _alpm_log(PM_LOG_ERROR, _("could not access hooks 
directory\n"));
+               return(1);
+       }
+
+       /* step through the directory entries */
+       int retval = 0, i;
+       for (i = 0; i < num_ents; ++i) {
+               if(strcmp(ent[i]->d_name, ".") != 0 && strcmp(ent[i]->d_name, 
"..") != 0) {
+                       /* build the full filepath */
+                       snprintf(buf, PATH_MAX, "%s%s", hookdir, 
ent[i]->d_name);
+
+                       /* script found in file */
+                       if(grep(buf, script)) {
+                               snprintf(buf, PATH_MAX, ". %s%s; %s%s",
+                                               hookdir, ent[i]->d_name, 
script, argstr);
+
+                               _alpm_log(PM_LOG_DEBUG, "executing \"%s\"\n", 
buf);
+                               int ret = _alpm_run_chroot(root, "/bin/sh", 
argv);
+                               if (ret != 0) {
+                                       retval = ret;
+                               }
+                       }
+               }
+               free(ent[i]);
+       }
+
+       free(ent);
+       return(retval);
+}
+
 int SYMEXPORT alpm_trans_get_flags()
 {
        /* Sanity checks */
diff --git a/lib/libalpm/trans.h b/lib/libalpm/trans.h
index afe0ed7..832e919 100644
--- a/lib/libalpm/trans.h
+++ b/lib/libalpm/trans.h
@@ -75,6 +75,7 @@ int _alpm_trans_init(pmtrans_t *trans, pmtransflag_t flags,
 int _alpm_runscriptlet(const char *root, const char *installfn,
                        const char *script, const char *ver,
                        const char *oldver);
+int _alpm_runhooks(const char* hooks, const char *hookdir, const char *script, 
...);
 
 #endif /* _ALPM_TRANS_H */
 
diff --git a/src/pacman/Makefile.am b/src/pacman/Makefile.am
index 31e8b13..335deee 100644
--- a/src/pacman/Makefile.am
+++ b/src/pacman/Makefile.am
@@ -1,5 +1,6 @@
 # paths set at make time
 conffile  = ${sysconfdir}/pacman.conf
+hookdir   = ${sysconfdir}/pacman.d/hooks/
 dbpath    = ${localstatedir}/lib/pacman/
 cachedir  = ${localstatedir}/cache/pacman/pkg/
 logfile   = ${localstatedir}/log/pacman.log
@@ -12,6 +13,7 @@ DEFS = -DLOCALEDIR=\"@localedir@\" \
        -DDBPATH=\"$(dbpath)\" \
        -DCACHEDIR=\"$(cachedir)\" \
        -DLOGFILE=\"$(logfile)\" \
+       -DHOOKDIR=\"$(hookdir)\" \
        @DEFS@
 INCLUDES = -I$(top_srcdir)/lib/libalpm
 
diff --git a/src/pacman/conf.h b/src/pacman/conf.h
index ff7a9c7..03fe424 100644
--- a/src/pacman/conf.h
+++ b/src/pacman/conf.h
@@ -40,6 +40,7 @@ typedef struct __config_t {
        char *rootdir;
        char *dbpath;
        char *logfile;
+       char *hookdir;
        /* TODO how to handle cachedirs? */
 
        unsigned short op_q_isfile;
@@ -98,10 +99,12 @@ enum {
        OP_DEBUG,
        OP_NOPROGRESSBAR,
        OP_NOSCRIPTLET,
+       OP_NOHOOKS,
        OP_ASK,
        OP_CACHEDIR,
        OP_ASDEPS,
        OP_LOGFILE,
+       OP_HOOKDIR,
        OP_IGNOREGROUP,
        OP_NEEDED,
        OP_ASEXPLICIT,
diff --git a/src/pacman/pacman.c b/src/pacman/pacman.c
index 45500cf..c0a4e36 100644
--- a/src/pacman/pacman.c
+++ b/src/pacman/pacman.c
@@ -189,6 +189,7 @@ static void usage(int op, const char * const myname)
                                addlist(_("  -k, --dbonly         only modify 
database entries, not package files\n"));
                                addlist(_("      --noprogressbar  do not show a 
progress bar when downloading files\n"));
                                addlist(_("      --noscriptlet    do not 
execute the install scriptlet if one exists\n"));
+                               addlist(_("      --nohooks        do not 
execute the global hooks\n"));
                                addlist(_("      --print          only print 
the targets instead of performing the operation\n"));
                                addlist(_("      --print-format <string>\n"
                                         "                       specify how 
the targets should be printed\n"));
@@ -197,6 +198,7 @@ static void usage(int op, const char * const myname)
 
                addlist(_("  -b, --dbpath <path>  set an alternate database 
location\n"));
                addlist(_("  -r, --root <path>    set an alternate installation 
root\n"));
+               addlist(_("      --hookdir <path> set an alternate hooks 
directory\n"));
                addlist(_("  -v, --verbose        be verbose\n"));
                addlist(_("      --arch <arch>    set an alternate 
architecture\n"));
                addlist(_("      --cachedir <dir> set an alternate package 
cache location\n"));
@@ -364,6 +366,11 @@ static void setlibpaths(void)
                                snprintf(path, PATH_MAX, "%s%s", 
alpm_option_get_root(), LOGFILE + 1);
                                config->logfile = strdup(path);
                        }
+                       if(!config->hookdir) {
+                               /* omit leading slash from our static LOGFILE 
path, root handles it */
+                               snprintf(path, PATH_MAX, "%s%s", 
alpm_option_get_root(), HOOKDIR + 1);
+                               config->hookdir = strdup(path);
+                       }
                }
                /* Set other paths if they were configured. Note that unless 
rootdir
                 * was left undefined, these two paths (dbpath and logfile) 
will have
@@ -384,6 +391,14 @@ static void setlibpaths(void)
                                cleanup(ret);
                        }
                }
+               if(config->hookdir) {
+                       ret = alpm_option_set_hookdir(config->hookdir);
+                       if(ret != 0) {
+                               pm_printf(PM_LOG_ERROR, _("problem setting 
hookdir '%s' (%s)\n"),
+                                               config->hookdir, 
alpm_strerrorlast());
+                               cleanup(ret);
+                       }
+               }
 
                /* add a default cachedir if one wasn't specified */
                if(alpm_option_get_cachedirs() == NULL) {
@@ -505,6 +520,10 @@ static int parsearg_global(int opt)
                        config->logfile = strndup(optarg, PATH_MAX);
                        break;
                case OP_NOCONFIRM: config->noconfirm = 1; break;
+               case OP_HOOKDIR:
+                       check_optarg();
+                       config->hookdir = strdup(optarg);
+                       break;
                case 'b':
                        check_optarg();
                        config->dbpath = strdup(optarg);
@@ -556,6 +575,7 @@ static int parsearg_trans(int opt)
                case 'k': config->flags |= PM_TRANS_FLAG_DBONLY; break;
                case OP_NOPROGRESSBAR: config->noprogressbar = 1; break;
                case OP_NOSCRIPTLET: config->flags |= 
PM_TRANS_FLAG_NOSCRIPTLET; break;
+               case OP_NOHOOKS: config->flags |= PM_TRANS_FLAG_NOHOOKS; break;
                case 'p': config->print = 1; break;
                case OP_PRINTFORMAT:
                        check_optarg();
@@ -680,12 +700,14 @@ static int parseargs(int argc, char *argv[])
                {"verbose",    no_argument,       0, 'v'},
                {"downloadonly", no_argument,     0, 'w'},
                {"refresh",    no_argument,       0, 'y'},
+               {"hookdir",    required_argument, 0, OP_HOOKDIR},
                {"noconfirm",  no_argument,       0, OP_NOCONFIRM},
                {"config",     required_argument, 0, OP_CONFIG},
                {"ignore",     required_argument, 0, OP_IGNORE},
                {"debug",      optional_argument, 0, OP_DEBUG},
                {"noprogressbar", no_argument,    0, OP_NOPROGRESSBAR},
                {"noscriptlet", no_argument,      0, OP_NOSCRIPTLET},
+               {"nohooks",     no_argument,      0, OP_NOHOOKS},
                {"ask",        required_argument, 0, OP_ASK},
                {"cachedir",   required_argument, 0, OP_CACHEDIR},
                {"asdeps",     no_argument,       0, OP_ASDEPS},
@@ -1003,6 +1025,12 @@ static int _parse_options(char *key, char *value)
                                config->logfile = strdup(value);
                                pm_printf(PM_LOG_DEBUG, "config: logfile: 
%s\n", value);
                        }
+               } else if(strcmp(key, "HookDir") == 0) {
+                       /* don't overwrite a path specified on the command line 
*/
+                       if(!config->hookdir) {
+                               config->hookdir = strdup(value);
+                               pm_printf(PM_LOG_DEBUG, "config: hookdir: 
%s\n", value);
+                       }
                } else if (strcmp(key, "XferCommand") == 0) {
                        config->xfercommand = strdup(value);
                        alpm_option_set_fetchcb(download_with_xfercommand);
@@ -1334,6 +1362,7 @@ int main(int argc, char *argv[])
        /* define paths to reasonable defaults */
        alpm_option_set_root(ROOTDIR);
        alpm_option_set_dbpath(DBPATH);
+       alpm_option_set_hookdir(HOOKDIR);
        alpm_option_set_logfile(LOGFILE);
 
        /* Priority of options:
@@ -1429,6 +1458,7 @@ int main(int argc, char *argv[])
                printf("\n");
                printf("Lock File : %s\n", alpm_option_get_lockfile());
                printf("Log File  : %s\n", alpm_option_get_logfile());
+               printf("Hook Dir  : %s\n", alpm_option_get_hookdir());
                list_display("Targets   :", pm_targets);
        }
 
-- 
1.7.3.5


Reply via email to