Search code examples
c++clinuxposixglibc

Linux: Executing child process with piped stdin/stdout


Using Linux and C++, I would like a function that does the following:

string f(string s)
{
    string r = system("foo < s");
    return r;
}

Obviously the above doesn't work, but you get the idea. I have a string s that I would like to pass as the standard input of a child process execution of application "foo", and then I would like to record its standard output to string r and then return it.

What combination of Linux syscalls or POSIX functions should I use? I'm using Linux 3.0 and do not need the solution to work with older systems.


Solution

  • The code provided by eerpini does not work as written. Note, for example, that the pipe ends that are closed in the parent are used afterwards. Look at

    close(wpipefd[1]); 
    

    and the subsequent write to that closed descriptor. This is just transposition, but it shows this code has never been used. Below is a version that I have tested. Unfortunately, I changed the code style, so this was not accepted as an edit of eerpini's code.

    The only structural change is that I only redirect the I/O in the child (note the dup2 calls are only in the child path.) This is very important, because otherwise the parent's I/O gets messed up. Thanks to eerpini for the initial answer, which I used in developing this one.

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <errno.h>
    
    #define PIPE_READ 0
    #define PIPE_WRITE 1
    
    int createChild(const char* szCommand, char* const aArguments[], char* const aEnvironment[], const char* szMessage) {
      int aStdinPipe[2];
      int aStdoutPipe[2];
      int nChild;
      char nChar;
      int nResult;
    
      if (pipe(aStdinPipe) < 0) {
        perror("allocating pipe for child input redirect");
        return -1;
      }
      if (pipe(aStdoutPipe) < 0) {
        close(aStdinPipe[PIPE_READ]);
        close(aStdinPipe[PIPE_WRITE]);
        perror("allocating pipe for child output redirect");
        return -1;
      }
    
      nChild = fork();
      if (0 == nChild) {
        // child continues here
    
        // redirect stdin
        if (dup2(aStdinPipe[PIPE_READ], STDIN_FILENO) == -1) {
          exit(errno);
        }
    
        // redirect stdout
        if (dup2(aStdoutPipe[PIPE_WRITE], STDOUT_FILENO) == -1) {
          exit(errno);
        }
    
        // redirect stderr
        if (dup2(aStdoutPipe[PIPE_WRITE], STDERR_FILENO) == -1) {
          exit(errno);
        }
    
        // all these are for use by parent only
        close(aStdinPipe[PIPE_READ]);
        close(aStdinPipe[PIPE_WRITE]);
        close(aStdoutPipe[PIPE_READ]);
        close(aStdoutPipe[PIPE_WRITE]); 
    
        // run child process image
        // replace this with any exec* function find easier to use ("man exec")
        nResult = execve(szCommand, aArguments, aEnvironment);
    
        // if we get here at all, an error occurred, but we are in the child
        // process, so just exit
        exit(nResult);
      } else if (nChild > 0) {
        // parent continues here
    
        // close unused file descriptors, these are for child only
        close(aStdinPipe[PIPE_READ]);
        close(aStdoutPipe[PIPE_WRITE]); 
    
        // Include error check here
        if (NULL != szMessage) {
          write(aStdinPipe[PIPE_WRITE], szMessage, strlen(szMessage));
        }
    
        // Just a char by char read here, you can change it accordingly
        while (read(aStdoutPipe[PIPE_READ], &nChar, 1) == 1) {
          write(STDOUT_FILENO, &nChar, 1);
        }
    
        // done with these in this example program, you would normally keep these
        // open of course as long as you want to talk to the child
        close(aStdinPipe[PIPE_WRITE]);
        close(aStdoutPipe[PIPE_READ]);
      } else {
        // failed to create child
        close(aStdinPipe[PIPE_READ]);
        close(aStdinPipe[PIPE_WRITE]);
        close(aStdoutPipe[PIPE_READ]);
        close(aStdoutPipe[PIPE_WRITE]);
      }
      return nChild;
    }