[pacman-dev] [PATCH 2/3] Add support for global hooks in /etc/pacman.d/hooks/*

Daniel Mendler mail at daniel-mendler.de
Sun Jan 30 18:16:10 EST 2011


    * 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 at 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



More information about the pacman-dev mailing list