---

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(-)

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;
-- 
2.21.0

Reply via email to