* 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 <mail@daniel-mendler.de> --- 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