[pacman-dev] [PATCH 4/6v2] allow arguments in hook Exec fields

Andrew Gregory andrew.gregory.8 at gmail.com
Tue Oct 27 04:47:33 UTC 2015


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

Replaced wordexp with a custom function that only does word splitting with
quote removal.

 doc/alpm-hooks.5.txt                          |   5 +-
 lib/libalpm/hook.c                            | 128 +++++++++++++++++++++++++-
 test/pacman/tests/TESTS                       |   1 +
 test/pacman/tests/hook-exec-with-arguments.py |  22 +++++
 4 files changed, 149 insertions(+), 7 deletions(-)
 create mode 100644 test/pacman/tests/hook-exec-with-arguments.py

diff --git a/doc/alpm-hooks.5.txt b/doc/alpm-hooks.5.txt
index 2986abf..770f466 100644
--- a/doc/alpm-hooks.5.txt
+++ b/doc/alpm-hooks.5.txt
@@ -62,8 +62,9 @@ defined the hook will run if the transaction matches *any* of the triggers.
 ACTIONS
 -------
 
-*Exec =* /path/to/executable::
-	Executable to run.  Required.
+*Exec =* <command>::
+	Command to run.  Command arguments are split on whitespace.  Values
+	containing whitespace should be enclosed in quotes.  Required.
 
 *When =* PreTransaction|PostTransaction::
 	When to run the hook.  Required.
diff --git a/lib/libalpm/hook.c b/lib/libalpm/hook.c
index 463b76a..45c10e1 100644
--- a/lib/libalpm/hook.c
+++ b/lib/libalpm/hook.c
@@ -17,6 +17,7 @@
  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <ctype.h>
 #include <dirent.h>
 #include <errno.h>
 #include <string.h>
@@ -49,7 +50,7 @@ struct _alpm_hook_t {
 	char *name;
 	alpm_list_t *triggers;
 	alpm_list_t *depends;
-	char *cmd;
+	char **cmd;
 	enum _alpm_hook_when_t when;
 	int abort_on_fail;
 };
@@ -67,11 +68,22 @@ static void _alpm_trigger_free(struct _alpm_trigger_t *trigger)
 	}
 }
 
+static void _alpm_wordsplit_free(char **ws)
+{
+    if(ws) {
+        char **c;
+        for(c = ws; *c; c++) {
+            free(*c);
+        }
+        free(ws);
+    }
+}
+
 static void _alpm_hook_free(struct _alpm_hook_t *hook)
 {
 	if(hook) {
 		free(hook->name);
-		free(hook->cmd);
+		_alpm_wordsplit_free(hook->cmd);
 		alpm_list_free_inner(hook->triggers, (alpm_list_fn_free) _alpm_trigger_free);
 		alpm_list_free(hook->triggers);
 		FREELIST(hook->depends);
@@ -141,6 +153,106 @@ static int _alpm_hook_validate(alpm_handle_t *handle,
 	return ret;
 }
 
+static char **_alpm_wordsplit(char *str)
+{
+    char *c = str, *end;
+    char **out = NULL, **outsave;
+    size_t count = 0;
+
+    if(str == NULL) {
+        errno = EINVAL;
+        return NULL;
+    }
+
+	for(c = str; isspace(*c); c++);
+    while(*c) {
+        size_t wordlen = 0;
+
+        /* extend our array */
+        outsave = out;
+        if((out = realloc(out, (count + 1) * sizeof(char*))) == NULL) {
+            out = outsave;
+            goto error;
+        }
+
+        /* calculate word length and check for unbalanced quotes */
+        for(end = c; *end && !isspace(*end); end++) {
+            if(*end == '\'' || *end == '"') {
+                char quote = *end;
+                while(*(++end) && *end != quote) {
+                    if(*end == '\\' && *(end + 1) == quote) {
+                        end++;
+                    }
+                    wordlen++;
+                }
+                if(*end != quote) {
+                    errno = EINVAL;
+                    goto error;
+                }
+            } else {
+                if(*end == '\\' && (end[1] == '\'' || end[1] == '"')) {
+                    end++; /* skip the '\\' */
+                }
+                wordlen++;
+            }
+        }
+
+        if(wordlen == (size_t) (end - c)) {
+            /* no internal quotes or escapes, copy it the easy way */
+            if((out[count++] = strndup(c, wordlen)) == NULL) {
+							goto error;
+						}
+        } else {
+            /* manually copy to remove quotes and escapes */
+            char *dest = out[count++] = malloc(wordlen + 1);
+            if(dest == NULL) { goto error; }
+            while(c < end) {
+                if(*c == '\'' || *c == '"') {
+                    char quote = *c;
+                    /* we know there must be a matching end quote,
+                     * no need to check for '\0' */
+                    for(c++; *c != quote; c++) {
+                        if(*c == '\\' && *(c + 1) == quote) {
+                            c++;
+                        }
+                        *(dest++) = *c;
+                    }
+                    c++;
+                } else {
+                    if(*c == '\\' && (c[1] == '\'' || c[1] == '"')) {
+                        c++; /* skip the '\\' */
+                    }
+                    *(dest++) = *(c++);
+                }
+            }
+            *dest = '\0';
+        }
+
+        if(*end == '\0') {
+            break;
+        } else {
+            for(c = end + 1; isspace(*c); c++);
+        }
+    }
+
+    outsave = out;
+    if((out = realloc(out, (count + 1) * sizeof(char*))) == NULL) {
+        out = outsave;
+        goto error;
+    }
+
+    out[count++] = NULL;
+
+    return out;
+
+error:
+    /* can't use wordsplit_free here because NULL has not been appended */
+    while(count) {
+        free(out[--count]);
+    }
+    free(out);
+    return NULL;
+}
 static int _alpm_hook_parse_cb(const char *file, int line,
 		const char *section, char *key, char *value, void *data)
 {
@@ -208,7 +320,14 @@ static int _alpm_hook_parse_cb(const char *file, int line,
 		} else if(strcmp(key, "AbortOnFail") == 0) {
 			hook->abort_on_fail = 1;
 		} else if(strcmp(key, "Exec") == 0) {
-			STRDUP(hook->cmd, value, return 1);
+			if((hook->cmd = _alpm_wordsplit(value)) == NULL) {
+				if(errno == EINVAL) {
+					error(_("hook %s line %d: invalid value %s\n"), file, line, value);
+				} else {
+					error(_("hook %s line %d: unable to set option (%s)\n"),
+							file, line, strerror(errno));
+				}
+			}
 		} else {
 			error(_("hook %s line %d: invalid option %s\n"), file, line, key);
 		}
@@ -383,7 +502,6 @@ static alpm_list_t *find_hook(alpm_list_t *haystack, const void *needle)
 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);
-	char *const argv[] = { hook->cmd, NULL };
 
 	for(i = hook->depends; i; i = i->next) {
 		if(!alpm_find_satisfier(pkgs, i->data)) {
@@ -393,7 +511,7 @@ static int _alpm_hook_run_hook(alpm_handle_t *handle, struct _alpm_hook_t *hook)
 		}
 	}
 
-	return _alpm_run_chroot(handle, hook->cmd, argv);
+	return _alpm_run_chroot(handle, hook->cmd[0], hook->cmd);
 }
 
 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 8ad1b9c..afd2e69 100644
--- a/test/pacman/tests/TESTS
+++ b/test/pacman/tests/TESTS
@@ -51,6 +51,7 @@ TESTS += test/pacman/tests/fileconflict030.py
 TESTS += test/pacman/tests/fileconflict031.py
 TESTS += test/pacman/tests/fileconflict032.py
 TESTS += test/pacman/tests/hook-abortonfail.py
+TESTS += test/pacman/tests/hook-exec-with-arguments.py
 TESTS += test/pacman/tests/hook-file-change-packages.py
 TESTS += test/pacman/tests/hook-file-remove-trigger-match.py
 TESTS += test/pacman/tests/hook-file-upgrade-nomatch.py
diff --git a/test/pacman/tests/hook-exec-with-arguments.py b/test/pacman/tests/hook-exec-with-arguments.py
new file mode 100644
index 0000000..d3df87b
--- /dev/null
+++ b/test/pacman/tests/hook-exec-with-arguments.py
@@ -0,0 +1,22 @@
+self.description = "Hook with arguments"
+
+self.add_hook("hook",
+        """
+        [Trigger]
+        Type = Package
+        Operation = Install
+        Target = foo
+
+        [Action]
+        When = PreTransaction
+        Exec = bin/sh -c ': > hook-output'
+        """);
+
+sp = pmpkg("foo")
+self.addpkg2db("sync", sp)
+
+self.args = "-S foo"
+
+self.addrule("PACMAN_RETCODE=0")
+self.addrule("PKG_EXIST=foo")
+self.addrule("FILE_EXIST=hook-output")
-- 
2.6.2


More information about the pacman-dev mailing list