about summary refs log tree commit diff
path: root/lib/libsystem.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/libsystem.c')
-rw-r--r--lib/libsystem.c298
1 files changed, 208 insertions, 90 deletions
diff --git a/lib/libsystem.c b/lib/libsystem.c
index b2f78ed4..92495fe5 100644
--- a/lib/libsystem.c
+++ b/lib/libsystem.c
@@ -14,6 +14,7 @@
 =============================================================================*/
 #define _XOPEN_SOURCE
 
+#include <stdarg.h>
 #include <unistd.h>
 #include <stdlib.h>
 #include <stdio.h>
@@ -22,6 +23,8 @@
 #include <signal.h>
 #include <sys/wait.h>
 
+#include "pm_c_util.h"
+#include "mallocvar.h"
 #include "pm.h"
 #include "pm_system.h"
 
@@ -30,35 +33,69 @@
 
 
 static void
-execProgram(const char * const shellCommand,
-            int          const stdinFd,
-            int          const stdoutFd) {
+closeUninheritableFds(int const stdinFd,
+                      int const stdoutFd) {
 /*----------------------------------------------------------------------------
-   Run the shell command 'shellCommand', supplying to the shell
-   'inputPipeFd' as its Standard Input and 'outputPipeFd' as its 
-   Standard Output.
+  Close all the file descriptors that we declare uninheritable -- files Parent
+  has open that Child has no business accessing.
 
-   But leave Standard Input and Standard Output as we found them.
+  Closing an extra file descriptor is essential to allow the file to close
+  when Parent closes it.
+
+  We define uninheritable as less than 64 and not Standard Input, Output,
+  or Error, or 'stdinFd' or 'stdoutFd'.
+-----------------------------------------------------------------------------*/
+    int fd;
+
+    for (fd = 0; fd < 64; ++fd) {
+        if (fd == stdinFd) {
+        } else if (fd == stdoutFd) {
+        } else if (fd == STDIN_FILENO) {
+        } else if (fd == STDOUT_FILENO) {
+        } else if (fd == STDERR_FILENO) {
+        } else {
+            close(fd);
+        }
+    }
+}
+
+
+
+static void
+execProgram(const char *  const progName,
+            const char ** const argArray,
+            int           const stdinFd,
+            int           const stdoutFd) {
+/*----------------------------------------------------------------------------
+   Exec the program 'progName' with arguments argArray[], with 'stdinFd' as
+   its Standard Input and 'stdoutFd' as its Standard Output.
+
+   But if the exec fails, leave all file descriptors as we found them.
+
+   Note that stdinFd or stdoutFd may actually be Standard Input and
+   Standard Output already.
 -----------------------------------------------------------------------------*/
     int stdinSaveFd, stdoutSaveFd;
     int rc;
     int execErrno;
 
-    /* Make inputPipeFd Standard Input.
-       Make outputPipeFd Standard Output.
+    /* Make stdinFd Standard Input.
+       Make stdoutFd Standard Output.
     */
     if (stdinFd != STDIN) {
         stdinSaveFd  = dup(STDIN);
         close(STDIN);
         dup2(stdinFd, STDIN);
+        close(stdinFd);
     }
     if (stdoutFd != STDOUT) {
         stdoutSaveFd = dup(STDOUT);
         close(STDOUT);
         dup2(stdoutFd, STDOUT);
+        close(stdoutFd);
     }
 
-    rc = execl("/bin/sh", "sh", "-c", shellCommand, NULL);
+    rc = execvp(progName, (char **)argArray);
 
     execErrno = errno;
 
@@ -73,10 +110,12 @@ execProgram(const char * const shellCommand,
         close(stdoutSaveFd);
     }
     if (rc < 0)
-        pm_error("Unable to exec the shell.  Errno=%d (%s)",
-                 execErrno, strerror(execErrno));
+        pm_error("Unable to exec '%s' "
+                 "(i.e. the program did not run at all).  "
+                 "execvp() errno=%d (%s)",
+                 progName, execErrno, strerror(execErrno));
     else
-        pm_error("INTERNAL ERROR.  execl() returns, but does not fail.");
+        pm_error("INTERNAL ERROR.  execvp() returns, but does not fail.");
 }
 
 
@@ -92,21 +131,21 @@ createPipeFeeder(void          pipeFeederRtn(int, void *),
    other end of the pipe as *fdP.
 -----------------------------------------------------------------------------*/
     int pipeToFeed[2];
-    pid_t feederPid;
+    pid_t rc;
 
     pipe(pipeToFeed);
-    feederPid = fork();
-    if (feederPid < 0) {
+    rc = fork();
+    if (rc < 0) {
         pm_error("fork() of stdin feeder failed.  errno=%d (%s)", 
                  errno, strerror(errno));
-    } else if (feederPid == 0) {
+    } else if (rc == 0) {
         /* This is the child -- the stdin feeder process */
         close(pipeToFeed[0]);
         (*pipeFeederRtn)(pipeToFeed[1], feederParm);
         exit(0);
-    }
-    else {
+    } else {
         /* This is the parent */
+        pid_t const feederPid = rc;
         close(pipeToFeed[1]);
         *fdP = pipeToFeed[0];
         *pidP = feederPid;
@@ -116,39 +155,62 @@ createPipeFeeder(void          pipeFeederRtn(int, void *),
 
 
 static void
-spawnProcessor(const char * const shellCommand, 
-               int          const stdinFd,
-               int *        const stdoutFdP,
-               pid_t *      const pidP) {
+spawnProcessor(const char *  const progName,
+               const char ** const argArray,
+               int           const stdinFd,
+               int *         const stdoutFdP,
+               pid_t *       const pidP) {
 /*----------------------------------------------------------------------------
-   Create a process to run a shell that runs command 'shellCommand'.
-   Pass file descriptor 'stdinFd' to the shell as Standard Input.
-   Set up a pipe and pass it to the shell as Standard Output.  Return
-   as *stdoutFdP the file descriptor of the other end of that pipe,
-   from which Caller can suck the shell's Standard Output.
+   Create a process to run program 'progName' with arguments
+   argArray[] (terminated by NULL element).  Pass file descriptor
+   'stdinFd' to the shell as Standard Input.
+
+   if 'stdoutFdP' is NULL, have that process write its Standard Output to
+   the current process' Standard Output.
+
+   If 'stdoutFdP' is non-NULL, set up a pipe and pass it to the new
+   process as Standard Output.  Return as *stdoutFdP the file
+   descriptor of the other end of that pipe, from which Caller can
+   suck the program's Standard Output.
 -----------------------------------------------------------------------------*/
+    bool const pipeStdout = !!stdoutFdP;
+
     int stdoutpipe[2];
-    pid_t processorpid;
-        
-    pipe(stdoutpipe);
+    pid_t rc;
 
-    processorpid = fork();
-    if (processorpid < 0) {
+    if (pipeStdout)
+        pipe(stdoutpipe);
+
+    rc = fork();
+    if (rc < 0) {
         pm_error("fork() of processor process failed.  errno=%d (%s)", 
                  errno, strerror(errno));
-    } else if (processorpid == 0) {
-        /* The second child */
-        close(stdoutpipe[0]);
+    } else if (rc == 0) {
+        /* The program child */
+
+        int stdoutFd;
+        
+        if (pipeStdout) {
+            close(stdoutpipe[0]);
+            stdoutFd = stdoutpipe[1];
+        } else
+            stdoutFd = STDOUT;
 
-        execProgram(shellCommand, stdinFd, stdoutpipe[1]);
+        closeUninheritableFds(stdinFd, stdoutFd);
+
+        execProgram(progName, argArray, stdinFd, stdoutFd);
 
         close(stdinFd);
         close(stdoutpipe[1]);
         pm_error("INTERNAL ERROR: execProgram() returns.");
     } else {
         /* The parent */
-        close(stdoutpipe[1]);
-        *stdoutFdP = stdoutpipe[0];
+        pid_t const processorpid = rc;
+
+        if (pipeStdout) {
+            close(stdoutpipe[1]);
+            *stdoutFdP = stdoutpipe[0];
+        }
         *pidP = processorpid;
     }
 }
@@ -235,7 +297,7 @@ cleanupProcessorProcess(pid_t const processorPid) {
         int const exitStatus = WEXITSTATUS(terminationStatus);
 
         if (exitStatus != 0)
-            pm_message("Shell process exited with abnormal exist status %u.  ",
+            pm_message("Shell process exited with abnormal exit status %u.  ",
                        exitStatus);
     } else if (WIFSIGNALED(terminationStatus)) {
         pm_message("Shell process was killed by a Class %u (%s) signal.",
@@ -281,18 +343,19 @@ cleanupFeederProcess(pid_t const feederPid) {
 
 
 void
-pm_system(void stdinFeeder(int, void *),
-          void *          const feederParm,
-          void stdoutAccepter(int, void *),
-          void *          const accepterParm,
-          const char *    const shellCommand) {
+pm_system_vp(const char *    const progName,
+             const char **   const argArray,
+             void stdinFeeder(int, void *),
+             void *          const feederParm,
+             void stdoutAccepter(int, void *),
+             void *          const accepterParm) {
 /*----------------------------------------------------------------------------
-   Run a shell and have it run command 'shellCommand'.  Feed its
-   Standard Input with a pipe, which is fed by the routine
-   'stdinFeeder' with parameter 'feederParm'.  Process its Standard
-   Output with the routine 'stdoutAccepter' with parameter 'accepterParm'.
+   Run a program in a child process.  Feed its Standard Input with a
+   pipe, which is fed by the routine 'stdinFeeder' with parameter
+   'feederParm'.  Process its Standard Output with the routine
+   'stdoutAccepter' with parameter 'accepterParm'.
 
-   But if 'stdinFeeder' is NULL, just feed the shell our own Standard
+   But if 'stdinFeeder' is NULL, just feed the program our own Standard
    Input.  And if 'stdoutFeeder' is NULL, just send its Standard Output
    to our own Standard Output.
 
@@ -300,77 +363,132 @@ pm_system(void stdinFeeder(int, void *),
    element).  That includes arg0.
 -----------------------------------------------------------------------------*/
     /* If 'stdinFeeder' is non-NULL, we create a child process to run
-       'stdinFeeder' and create a pipe between from that process as the
-       shell's Standard Input.
+       'stdinFeeder' and create a pipe from that process as the
+       program's Standard Input.
+
+       We create another child process to run the program.
 
-       If 'stdoutFeeder' is non-NULL, we create a child process to run
-       the shell and create a pipe between the shell's Standard Output
-       and this process, and then this process runs 'stdoutAccepter'
-       to read the data from that pipe.
+       If 'stdoutFeeder' is non-NULL, we create a pipe between the
+       program process and the current process and have the program
+       write its Standard Output to that pipe.  The current process
+       runs 'stdoutAccepter' to read the data from that pipe.
        
-       But if 'stdoutFeeder' is NULL, we just run the shell in this
-       process.
+       But if 'stdoutFeeder' is NULL, we just tell the program process
+       to write to the current process' Standard Output.
 
-       So there can be 1, 2, or 3 processes involved depending on 
-       parameters.
+       So there are two processes when stdinFeeder is NULL and three when
+       stdinFeeder is non-null.
     */
     
-    int shellStdinFd;
+    int progStdinFd;
     pid_t feederPid;
+    pid_t processorPid;
 
     if (stdinFeeder) 
-        createPipeFeeder(stdinFeeder, feederParm, &shellStdinFd, &feederPid);
+        createPipeFeeder(stdinFeeder, feederParm, &progStdinFd, &feederPid);
     else {
-        shellStdinFd = STDIN;
+        progStdinFd = STDIN;
         feederPid = 0;
     }
 
     if (stdoutAccepter) {
-        int shellStdoutFd;
-        pid_t processorPid;
+        int progStdoutFd;
 
-        /* Make a child process to run the shell and pipe back to us its
+        /* Make a child process to run the program and pipe back to us its
            Standard Output 
         */
-        spawnProcessor(shellCommand, shellStdinFd, 
-                       &shellStdoutFd, &processorPid);
+        spawnProcessor(progName, argArray, progStdinFd, 
+                       &progStdoutFd, &processorPid);
 
-        /* The shell process has cloned our 'shellStdinFd'; we have no
+        /* The child process has cloned our 'progStdinFd'; we have no
            more use for our copy.
         */
-        close(shellStdinFd);
-        /* Dispose of the stdout from that shell */
-        (*stdoutAccepter)(shellStdoutFd, accepterParm);
-        close(shellStdoutFd);
-
-        cleanupProcessorProcess(processorPid);
+        close(progStdinFd);
+        /* Dispose of the stdout from that child */
+        (*stdoutAccepter)(progStdoutFd, accepterParm);
+        close(progStdoutFd);
     } else {
-        /* Run a child process for the shell that sends its Standard Output
+        /* Run a child process for the program that sends its Standard Output
            to our Standard Output
         */
-        int const stdinSaveFd = dup(STDIN);
-        int rc;
+        spawnProcessor(progName, argArray, progStdinFd, NULL, &processorPid);
+    }
 
-        dup2(shellStdinFd, STDIN);
-        
-        rc = system(shellCommand);
+    cleanupProcessorProcess(processorPid);
 
-        close(STDIN);
-        dup2(stdinSaveFd, STDIN);
+    if (feederPid) 
+        cleanupFeederProcess(feederPid);
+}
+
+
+
+void
+pm_system_lp(const char *    const progName,
+             void stdinFeeder(int, void *),
+             void *          const feederParm,
+             void stdoutAccepter(int, void *),
+             void *          const accepterParm,
+             ...) {
+/*----------------------------------------------------------------------------
+  same as pm_system_vp() except with arguments as variable arguments
+  instead of an array.
+-----------------------------------------------------------------------------*/
+    va_list args;
+    bool endOfArgs;
+    const char ** argArray;
+    unsigned int n;
+
+    va_start(args, accepterParm);
+
+    endOfArgs = FALSE;
+    argArray = NULL;
+
+    for (endOfArgs = FALSE, argArray = NULL, n = 0;
+         !endOfArgs;
+        ) {
+        const char * const arg = va_arg(args, const char *);
         
-        if (rc < 0)
-            pm_error("Unable to invoke the shell.  Errno=%d (%s)",
-                     errno, strerror(errno));
-        else if (rc != 0)
-            pm_message("WARNING: Shell process completion code = %d", rc);
+        REALLOCARRAY(argArray, n+1);
+
+        argArray[n++] = arg;
+
+        if (!arg)
+            endOfArgs = TRUE;
     }
 
-    if (feederPid) 
-        cleanupFeederProcess(feederPid);
+    va_end(args);
+
+    pm_system_vp(progName, argArray,
+                 stdinFeeder, feederParm, stdoutAccepter, accepterParm);
+
+    free(argArray);
 }
 
 
 
+void
+pm_system(void stdinFeeder(int, void *),
+          void *          const feederParm,
+          void stdoutAccepter(int, void *),
+          void *          const accepterParm,
+          const char *    const shellCommand) {
+/*----------------------------------------------------------------------------
+   Run a shell and have it run command 'shellCommand'.  Feed its
+   Standard Input with a pipe, which is fed by the routine
+   'stdinFeeder' with parameter 'feederParm'.  Process its Standard
+   Output with the routine 'stdoutAccepter' with parameter 'accepterParm'.
+
+   But if 'stdinFeeder' is NULL, just feed the shell our own Standard
+   Input.  And if 'stdoutFeeder' is NULL, just send its Standard Output
+   to our own Standard Output.
+-----------------------------------------------------------------------------*/
+
+    pm_system_lp("/bin/sh", 
+                 stdinFeeder, feederParm, stdoutAccepter, accepterParm,
+                 "sh", "-c", shellCommand, NULL);
+}
+
+
 
 void
 pm_feed_from_memory(int    const pipeToFeedFd,