[pacman-dev] [PATCH 5/5 v3] hooks: pass matched targets to hooks

Andrew Gregory andrew.gregory.8 at gmail.com
Thu Nov 12 00:20:01 UTC 2015


Signed-off-by: Andrew Gregory <andrew.gregory.8 at gmail.com>
---

* added test
* added comment explaining duplicate matches

 doc/alpm-hooks.5.txt                  |   5 ++
 lib/libalpm/hook.c                    | 127 +++++++++++++++++++++++++++++-----
 test/pacman/tests/TESTS               |   1 +
 test/pacman/tests/hook-target-list.py |  40 +++++++++++
 4 files changed, 156 insertions(+), 17 deletions(-)
 create mode 100644 test/pacman/tests/hook-target-list.py

diff --git a/doc/alpm-hooks.5.txt b/doc/alpm-hooks.5.txt
index 770f466..3729387 100644
--- a/doc/alpm-hooks.5.txt
+++ b/doc/alpm-hooks.5.txt
@@ -23,6 +23,7 @@ When = PreTransaction|PostTransaction (Required)
 Exec = <Command> (Required)
 Depends = <PkgName> (Optional)
 AbortOnFail (Optional, PreTransaction only)
+NeedsTargets (Optional)
 --------
 
 DESCRIPTION
@@ -77,6 +78,10 @@ ACTIONS
 	Causes the transaction to be aborted if the hook exits non-zero.  Only
 	applies to PreTransaction hooks.
 
+*NeedsTargets*::
+	Causes the list of matched trigger targets to be passed to the running hook
+	on 'stdin'.
+
 OVERRIDING HOOKS
 ----------------
 
diff --git a/lib/libalpm/hook.c b/lib/libalpm/hook.c
index 6d238d8..f5627af 100644
--- a/lib/libalpm/hook.c
+++ b/lib/libalpm/hook.c
@@ -51,8 +51,9 @@ struct _alpm_hook_t {
 	alpm_list_t *triggers;
 	alpm_list_t *depends;
 	char **cmd;
+	alpm_list_t *matches;
 	enum _alpm_hook_when_t when;
-	int abort_on_fail;
+	int abort_on_fail, needs_targets;
 };
 
 struct _alpm_hook_cb_ctx {
@@ -86,6 +87,7 @@ static void _alpm_hook_free(struct _alpm_hook_t *hook)
 		_alpm_wordsplit_free(hook->cmd);
 		alpm_list_free_inner(hook->triggers, (alpm_list_fn_free) _alpm_trigger_free);
 		alpm_list_free(hook->triggers);
+		alpm_list_free(hook->matches);
 		FREELIST(hook->depends);
 		free(hook);
 	}
@@ -320,6 +322,8 @@ static int _alpm_hook_parse_cb(const char *file, int line,
 			hook->depends = alpm_list_add(hook->depends, val);
 		} else if(strcmp(key, "AbortOnFail") == 0) {
 			hook->abort_on_fail = 1;
+		} else if(strcmp(key, "NeedsTargets") == 0) {
+			hook->needs_targets = 1;
 		} else if(strcmp(key, "Exec") == 0) {
 			if((hook->cmd = _alpm_wordsplit(value)) == NULL) {
 				if(errno == EINVAL) {
@@ -339,7 +343,8 @@ static int _alpm_hook_parse_cb(const char *file, int line,
 	return 0;
 }
 
-static int _alpm_hook_trigger_match_file(alpm_handle_t *handle, struct _alpm_trigger_t *t)
+static int _alpm_hook_trigger_match_file(alpm_handle_t *handle,
+		struct _alpm_hook_t *hook, struct _alpm_trigger_t *t)
 {
 	alpm_list_t *i, *j, *install = NULL, *upgrade = NULL, *remove = NULL;
 	size_t isize = 0, rsize = 0;
@@ -423,15 +428,31 @@ static int _alpm_hook_trigger_match_file(alpm_handle_t *handle, struct _alpm_tri
 			|| (t->op & ALPM_HOOK_OP_UPGRADE && upgrade)
 			|| (t->op & ALPM_HOOK_OP_REMOVE && remove);
 
-	alpm_list_free(install);
-	alpm_list_free(upgrade);
-	alpm_list_free(remove);
+	if(hook->needs_targets) {
+#define _save_matches(_op, _matches) \
+	if(t->op & _op && _matches) { \
+		hook->matches = alpm_list_join(hook->matches, _matches); \
+	} else { \
+		alpm_list_free(_matches); \
+	}
+		_save_matches(ALPM_HOOK_OP_INSTALL, install);
+		_save_matches(ALPM_HOOK_OP_UPGRADE, upgrade);
+		_save_matches(ALPM_HOOK_OP_REMOVE, remove);
+#undef _save_matches
+	} else {
+		alpm_list_free(install);
+		alpm_list_free(upgrade);
+		alpm_list_free(remove);
+	}
 
 	return ret;
 }
 
-static int _alpm_hook_trigger_match_pkg(alpm_handle_t *handle, struct _alpm_trigger_t *t)
+static int _alpm_hook_trigger_match_pkg(alpm_handle_t *handle,
+		struct _alpm_hook_t *hook, struct _alpm_trigger_t *t)
 {
+	alpm_list_t *install = NULL, *upgrade = NULL, *remove = NULL;
+
 	if(t->op & ALPM_HOOK_OP_INSTALL || t->op & ALPM_HOOK_OP_UPGRADE) {
 		alpm_list_t *i;
 		for(i = handle->trans->add; i; i = i->next) {
@@ -439,11 +460,19 @@ static int _alpm_hook_trigger_match_pkg(alpm_handle_t *handle, struct _alpm_trig
 			if(_alpm_fnmatch_patterns(t->targets, pkg->name) == 0) {
 				if(alpm_db_get_pkg(handle->db_local, pkg->name)) {
 					if(t->op & ALPM_HOOK_OP_UPGRADE) {
-						return 1;
+						if(hook->needs_targets) {
+							upgrade = alpm_list_add(upgrade, pkg->name);
+						} else {
+							return 1;
+						}
 					}
 				} else {
 					if(t->op & ALPM_HOOK_OP_INSTALL) {
-						return 1;
+						if(hook->needs_targets) {
+							install = alpm_list_add(install, pkg->name);
+						} else {
+							return 1;
+						}
 					}
 				}
 			}
@@ -456,31 +485,47 @@ static int _alpm_hook_trigger_match_pkg(alpm_handle_t *handle, struct _alpm_trig
 			alpm_pkg_t *pkg = i->data;
 			if(pkg && _alpm_fnmatch_patterns(t->targets, pkg->name) == 0) {
 				if(!alpm_list_find(handle->trans->add, pkg, _alpm_pkg_cmp)) {
-					return 1;
+					if(hook->needs_targets) {
+						remove = alpm_list_add(remove, pkg->name);
+					} else {
+						return 1;
+					}
 				}
 			}
 		}
 	}
 
-	return 0;
+	/* if we reached this point we either need the target lists or we didn't
+	 * match anything and the following calls will all be no-ops */
+	hook->matches = alpm_list_join(hook->matches, install);
+	hook->matches = alpm_list_join(hook->matches, upgrade);
+	hook->matches = alpm_list_join(hook->matches, remove);
+
+	return install || upgrade || remove;
 }
 
-static int _alpm_hook_trigger_match(alpm_handle_t *handle, struct _alpm_trigger_t *t)
+static int _alpm_hook_trigger_match(alpm_handle_t *handle,
+		struct _alpm_hook_t *hook, struct _alpm_trigger_t *t)
 {
 	return t->type == ALPM_HOOK_TYPE_PACKAGE
-		? _alpm_hook_trigger_match_pkg(handle, t)
-		: _alpm_hook_trigger_match_file(handle, t);
+		? _alpm_hook_trigger_match_pkg(handle, hook, t)
+		: _alpm_hook_trigger_match_file(handle, hook, t);
 }
 
 static int _alpm_hook_triggered(alpm_handle_t *handle, struct _alpm_hook_t *hook)
 {
 	alpm_list_t *i;
+	int ret = 0;
 	for(i = hook->triggers; i; i = i->next) {
-		if(_alpm_hook_trigger_match(handle, i->data)) {
-			return 1;
+		if(_alpm_hook_trigger_match(handle, hook, i->data)) {
+			if(!hook->needs_targets) {
+				return 1;
+			} else {
+				ret = 1;
+			}
 		}
 	}
-	return 0;
+	return ret;
 }
 
 static int _alpm_hook_cmp(struct _alpm_hook_t *h1, struct _alpm_hook_t *h2)
@@ -500,6 +545,44 @@ static alpm_list_t *find_hook(alpm_list_t *haystack, const void *needle)
 	return NULL;
 }
 
+static ssize_t _alpm_hook_feed_targets(char *buf, ssize_t needed, alpm_list_t **pos)
+{
+	size_t remaining = needed, written = 0;;
+	size_t len;
+
+	while(*pos && (len = strlen((*pos)->data)) + 1 <= remaining) {
+		memcpy(buf, (*pos)->data, len);
+		buf[len++] = '\n';
+		*pos = (*pos)->next;
+		buf += len;
+		remaining -= len;
+		written += len;
+	}
+
+	if(*pos && remaining) {
+		memcpy(buf, (*pos)->data, remaining);
+		(*pos)->data = (char*) (*pos)->data + remaining;
+		written += remaining;
+	}
+
+	return written;
+}
+
+static alpm_list_t *_alpm_strlist_dedup(alpm_list_t *list)
+{
+	alpm_list_t *i = list;
+	while(i) {
+		alpm_list_t *next = i->next;
+		while(next && strcmp(i->data, next->data) == 0) {
+			list = alpm_list_remove_item(list, next);
+			free(next);
+			next = i->next;
+		}
+		i = next;
+	}
+	return list;
+}
+
 static int _alpm_hook_run_hook(alpm_handle_t *handle, struct _alpm_hook_t *hook)
 {
 	alpm_list_t *i, *pkgs = _alpm_db_get_pkgcache(handle->db_local);
@@ -512,7 +595,17 @@ static int _alpm_hook_run_hook(alpm_handle_t *handle, struct _alpm_hook_t *hook)
 		}
 	}
 
-	return _alpm_run_chroot(handle, hook->cmd[0], hook->cmd, NULL, NULL);
+	if(hook->needs_targets) {
+		alpm_list_t *ctx;
+		hook->matches = alpm_list_msort(hook->matches,
+				alpm_list_count(hook->matches), (alpm_list_fn_cmp)strcmp);
+		/* hooks with multiple triggers could have duplicate matches */
+		ctx = hook->matches = _alpm_strlist_dedup(hook->matches);
+		return _alpm_run_chroot(handle, hook->cmd[0], hook->cmd,
+				(_alpm_cb_io) _alpm_hook_feed_targets, &ctx);
+	} else {
+		return _alpm_run_chroot(handle, hook->cmd[0], hook->cmd, NULL, NULL);
+	}
 }
 
 int _alpm_hook_run(alpm_handle_t *handle, enum _alpm_hook_when_t when)
diff --git a/test/pacman/tests/TESTS b/test/pacman/tests/TESTS
index afd2e69..e330896 100644
--- a/test/pacman/tests/TESTS
+++ b/test/pacman/tests/TESTS
@@ -59,6 +59,7 @@ TESTS += test/pacman/tests/hook-invalid-trigger.py
 TESTS += test/pacman/tests/hook-pkg-install-trigger-match.py
 TESTS += test/pacman/tests/hook-pkg-remove-trigger-match.py
 TESTS += test/pacman/tests/hook-pkg-upgrade-trigger-match.py
+TESTS += test/pacman/tests/hook-target-list.py
 TESTS += test/pacman/tests/hook-upgrade-trigger-no-match.py
 TESTS += test/pacman/tests/ignore001.py
 TESTS += test/pacman/tests/ignore002.py
diff --git a/test/pacman/tests/hook-target-list.py b/test/pacman/tests/hook-target-list.py
new file mode 100644
index 0000000..6dd6c4d
--- /dev/null
+++ b/test/pacman/tests/hook-target-list.py
@@ -0,0 +1,40 @@
+self.description = "Hook with NeedsTargets"
+
+self.add_hook("hook",
+        """
+        [Trigger]
+        Type = Package
+        Operation = Install
+        Target = foo
+
+        # duplicate trigger to check that duplicate targets are removed
+        [Trigger]
+        Type = Package
+        Operation = Install
+        Target = foo
+
+        [Trigger]
+        Type = File
+        Operation = Install
+        # matches files in 'file/' but not 'file/' itself
+        Target = file/?*
+
+        [Action]
+        When = PreTransaction
+        Exec = bin/sh -c 'while read -r tgt; do printf "%s\\n" "$tgt"; done > var/log/hook-output'
+        NeedsTargets
+        """);
+
+p1 = pmpkg("foo")
+p1.files = ["file/foo"]
+self.addpkg(p1)
+
+p2 = pmpkg("bar")
+p2.files = ["file/bar"]
+self.addpkg(p2)
+
+self.args = "-U %s %s" % (p1.filename(), p2.filename())
+
+self.addrule("PACMAN_RETCODE=0")
+self.addrule("PKG_EXIST=foo")
+self.addrule("FILE_CONTENTS=var/log/hook-output|file/bar\nfile/foo\nfoo\n")
-- 
2.6.2


More information about the pacman-dev mailing list