[pacman-dev,2/2,WIP] run XferCommand via exec

Message ID 20190609171355.5615-2-andrew.gregory.8@gmail.com
State New
Headers show
Series
  • [pacman-dev,1/2,WIP] move wordsplit into ini for sharing
Related show

Commit Message

Andrew Gregory June 9, 2019, 5:13 p.m. UTC
---

systemvp should pretty much be a drop-in replacement for system with
the exception that it takes an argv array and uses exec.  If anybody
wants to play with it to stress test it a little, I have
a self-contained copy and test program at:
https://github.com/andrewgregory/snippets/blob/systemv/c/systemv.c

TODO:
    * update docs
    * fix debug logging
    * should the command be run with PATH lookup (execv vs execvp)?
    * Is the use of mmap with MAP_ANONYMOUS okay?  MAP_ANONYMOUS is
      not POSIX but "most systems also support MAP_ANONYMOUS (or its
      synonym MAP_ANON)" (mmap(2)).
    * should we reset signals prior to exec'ing like we do with
      hooks/scripts?

 src/pacman/conf.c | 95 ++++++++++++++++++++++++++++++++++++++---------
 src/pacman/conf.h |  2 +
 2 files changed, 80 insertions(+), 17 deletions(-)

Patch

diff --git a/src/pacman/conf.c b/src/pacman/conf.c
index 2d8518c4..faf446dc 100644
--- a/src/pacman/conf.c
+++ b/src/pacman/conf.c
@@ -26,9 +26,11 @@ 
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h> /* strdup */
+#include <sys/mman.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <sys/utsname.h> /* uname */
+#include <sys/wait.h>
 #include <unistd.h>
 
 /* pacman */
@@ -153,6 +155,7 @@  int config_free(config_t *oldconfig)
 	free(oldconfig->print_format);
 	free(oldconfig->arch);
 	free(oldconfig);
+	_alpm_wordsplit_free(oldconfig->xfercommand_argv);
 
 	return 0;
 }
@@ -201,18 +204,59 @@  static char *get_tempfile(const char *path, const char *filename)
 	return tempfile;
 }
 
+static int systemvp(const char *file, char *const argv[])
+{
+	int pid, *err;
+
+	err = mmap(NULL, sizeof(*err), PROT_READ | PROT_WRITE,
+			MAP_SHARED | MAP_ANONYMOUS, -1, 0);
+	if(err == NULL) {
+		return -1;
+	}
+
+	pid = fork();
+	if(pid == -1) {
+		/* error */
+		munmap(err, sizeof(*err));
+		return -1;
+	} else if(pid == 0) {
+		/* child */
+		*err = 0;
+		execvp(file, argv);
+		*err = errno;
+		munmap(err, sizeof(*err));
+		_Exit(1);
+	} else {
+		/* parent */
+		int status;
+		while(waitpid(pid, &status, 0) == -1) {
+			if(errno != EINTR) {
+				munmap(err, sizeof(*err));
+				return -1;
+			}
+		}
+		if(*err != 0) {
+			errno = *err;
+			status = -1;
+		}
+		munmap(err, sizeof(*err));
+		return status;
+	}
+}
+
 /** External fetch callback */
 static int download_with_xfercommand(const char *url, const char *localpath,
 		int force)
 {
 	int ret = 0, retval;
 	int usepart = 0;
-	int cwdfd;
+	int cwdfd = -1;
 	struct stat st;
-	char *parsedcmd, *tempcmd;
 	char *destfile, *tempfile, *filename;
+	const char **argv;
+	size_t i;
 
-	if(!config->xfercommand) {
+	if(!config->xfercommand_argv) {
 		return -1;
 	}
 
@@ -230,17 +274,20 @@  static int download_with_xfercommand(const char *url, const char *localpath,
 		unlink(destfile);
 	}
 
-	tempcmd = strdup(config->xfercommand);
-	/* replace all occurrences of %o with fn.part */
-	if(strstr(tempcmd, "%o")) {
-		usepart = 1;
-		parsedcmd = strreplace(tempcmd, "%o", tempfile);
-		free(tempcmd);
-		tempcmd = parsedcmd;
+	if((argv = calloc(config->xfercommand_argc + 1, sizeof(char*))) == NULL) {
+		pm_printf(ALPM_LOG_ERROR, _("could not get current working directory\n"));
+		goto cleanup;
+	}
+
+	for(i = 0; i <= config->xfercommand_argc; i++) {
+		const char *val = config->xfercommand_argv[i];
+		if(val && strcmp(val, "%o") == 0) {
+			val = tempfile;
+		} else if(val && strcmp(val, "%u") == 0) {
+			val = url;
+		}
+		argv[i] = val;
 	}
-	/* replace all occurrences of %u with the download URL */
-	parsedcmd = strreplace(tempcmd, "%u", url);
-	free(tempcmd);
 
 	/* save the cwd so we can restore it later */
 	do {
@@ -256,9 +303,13 @@  static int download_with_xfercommand(const char *url, const char *localpath,
 		ret = -1;
 		goto cleanup;
 	}
-	/* execute the parsed command via /bin/sh -c */
-	pm_printf(ALPM_LOG_DEBUG, "running command: %s\n", parsedcmd);
-	retval = system(parsedcmd);
+
+	printf("running command: %s", config->xfercommand_argv[0]);
+	for(i = 1; argv[i]; i++) {
+		printf(" '%s'", argv[i]);
+	}
+	printf("\n");
+	retval = systemvp(argv[0], (char**)argv);
 
 	if(retval == -1) {
 		pm_printf(ALPM_LOG_WARNING, _("running XferCommand: fork failed!\n"));
@@ -296,7 +347,6 @@  cleanup:
 	}
 	free(destfile);
 	free(tempfile);
-	free(parsedcmd);
 
 	return ret;
 }
@@ -544,6 +594,17 @@  static int _parse_options(const char *key, char *value,
 				pm_printf(ALPM_LOG_DEBUG, "config: logfile: %s\n", value);
 			}
 		} else if(strcmp(key, "XferCommand") == 0) {
+			char **c;
+			if((config->xfercommand_argv = _alpm_wordsplit(value)) == NULL) {
+				pm_printf(ALPM_LOG_WARNING,
+						_("config file %s, line %d: invalid value for '%s' : '%s'\n"),
+						file, linenum, "XferCommand", value);
+				return 1;
+			}
+			config->xfercommand_argc = 0;
+			for(c = config->xfercommand_argv; *c; c++) {
+				config->xfercommand_argc++;
+			}
 			config->xfercommand = strdup(value);
 			pm_printf(ALPM_LOG_DEBUG, "config: xfercommand: %s\n", value);
 		} else if(strcmp(key, "CleanMethod") == 0) {
diff --git a/src/pacman/conf.h b/src/pacman/conf.h
index f45ed436..47df979d 100644
--- a/src/pacman/conf.h
+++ b/src/pacman/conf.h
@@ -125,6 +125,8 @@  typedef struct __config_t {
 	alpm_list_t *noextract;
 	alpm_list_t *overwrite_files;
 	char *xfercommand;
+	char **xfercommand_argv;
+	size_t xfercommand_argc;
 
 	/* our connection to libalpm */
 	alpm_handle_t *handle;