Skip to content

Instantly share code, notes, and snippets.

@aponxi
Last active April 25, 2018 10:51
Show Gist options
  • Save aponxi/5053843 to your computer and use it in GitHub Desktop.
Save aponxi/5053843 to your computer and use it in GitHub Desktop.
Popen Stuff/C++

A popen wrapper which also listens to the stderr of child process and a utility to process the stdout and stderr of child

Ref: http://naidutrk.blogspot.com/2012/06/popen-wrapper-which-also-listens-to.html

Notes:

  • pclose() returns "exit code"*256. So divide that number by 256 to get the real exit code.

Code

#include <iostream>
#include <unistd.h>
#include <thread>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <string.h>
#include <assert.h>
#include <iostream>
#include <thread>
#include <future>
#include <vector>
#include <errno.h>
#include <stdio.h>
#include <stdint.h>

//Macro for checking and reissuing system call if interrupted in the middle of blocking,


#define _eintr(syscall) ({ int _rc; while(( _rc = (syscall)) < 0x0 && (errno == EINTR)); (_rc); })
int popenCustom( int *popenPipe, pid_t *childPid, const char *command, char **args) 
{
 pid_t child;
 int in[2]; 
 int out[2];
 int err[2];

 int rc;
 rc = _eintr(pipe(in));
 if (rc < 0) { perror("Unable to open the input pipe");  goto in_error; }

 rc = _eintr(pipe(out));
 if (rc < 0) { perror("Unable to open the output pipe");  goto out_error; }

 rc = _eintr(pipe(err));
 if (rc < 0) { perror("Unable to open the error pipe");  goto err_error; }

 child = fork();
 if (child) { //parent
  _eintr(close(in[0]));
  _eintr(close(out[1]));
  _eintr(close(err[1]));

  popenPipe[0] = in[1];
  popenPipe[1] = out[0];
  popenPipe[2] = err[0];

  *childPid = child;

  //since the fds will be used with select, its better we set them
  //nonblocking mode. 
  rc = _eintr(fcntl(popenPipe[1], F_SETFL, O_NONBLOCK));
  if (rc < 0) { perror("Unable to set the popenPipe[1] to nonblocking ");  goto fork_error; }
  rc = _eintr(fcntl(popenPipe[2], F_SETFL, O_NONBLOCK));
  if (rc < 0) { perror("Unable to set the popenPipe[2] to nonblocking ");  goto fork_error; }

 } else if(child == 0) { //child
  _eintr(close(in[1]));
  _eintr(close(out[0]));
  _eintr(close(err[0]));

  int rc;
  _eintr(close(0)); 
  rc = _eintr(dup(in[0]));
  if (rc < 0) { perror("dup failed on stdin");  goto execv_error; }
  _eintr(close(1)); 
  rc = _eintr(dup(out[1]));
  if (rc < 0) { perror("dup failed on stdout");  goto execv_error; }
  _eintr(close(2)); 
  rc = _eintr(dup(err[1]));
  if (rc < 0) { perror("dup failed on stderr");  goto execv_error; }
  
  rc = execv(command, args); //finally spawn the command 
  if (rc < 0) {
   perror("execvp call failure "); 
   goto execv_error; 
  }
 } else {
  perror("fork failure:"); 
  goto fork_error; 
 }

 return 0; 

execv_error:
fork_error:
 _eintr(close(err[1]));
 _eintr(close(err[0]));

err_error:
 _eintr(close(out[1]));
 _eintr(close(out[0]));

out_error:
 _eintr(close(in[1]));
 _eintr(close(in[0]));

in_error:
 return -1;
}


//call the command with the arguments
//stdout_callback is invoked with the contents put on stdout by child 
//stderr_callback is invoked with the contents put on stderr by child 
//both the callbacks are passed the fd to the child_stdin so that they 
//can write to the child input basing on the contents of child's stdout
//or stderr.
int spawnCommand(int *popenPipe, const char *command, char **args, int *childExitCode,
  void(*stdout_callback)(int child_stdin, const char *buf, size_t size),
  void(*stderr_callback)(int child_stdin, const char *buf, size_t size)
  )
{
 int childStatus;
 int rc = 0;

 pid_t child;
 if(popenCustom(popenPipe, &child, command, args) < 0)  return -1;

 //listen on the stdout and stderr of the child
 fd_set childOutFds;
 FD_ZERO(&childOutFds);
 FD_SET(popenPipe[1], &childOutFds);
 FD_SET(popenPipe[2], &childOutFds);
 int max = popenPipe[1] > popenPipe[2] ? popenPipe[1] : popenPipe[2];

 while (1) {
  int rc = 0;
  struct timeval tv = {120, 0};
  rc = _eintr(select(max+1, &childOutFds, NULL, NULL, &tv));
  if (rc < 0) {
   perror("select failed:");
   break;
  } else if (rc) {
   if(FD_ISSET(popenPipe[1], &childOutFds)) {
    int rc = 0;
    //Try to drain the pipe 
    do {
     char childOutput[2048] = {'\0'};
     rc = _eintr(read(popenPipe[1], childOutput, sizeof(childOutput)));
     if (rc < 0) {
      if ((errno != EAGAIN) || (errno != EWOULDBLOCK)) {
       perror("read on childout failed"); 
       return -1;
      }
     }
     else if (rc) {
      if (stdout_callback)
       stdout_callback(popenPipe[0], childOutput, rc); 
     }
     //other end has exited and read returned 0 bytes here 
     //just close the pipes and get the fuck out of here 
     else goto collect_wait_status;
    } while(rc > 0);
   }

   if(FD_ISSET(popenPipe[2], &childOutFds)) {
    int rc = 0;
    //Try to drain the pipe
    do {
     char childOutput[2048] = {'\0'};
     rc = _eintr(read(popenPipe[2], childOutput, sizeof(childOutput)));
     if (rc < 0) {
      if ((errno != EAGAIN) || (errno != EWOULDBLOCK)) {
       perror("read on childerr failed");
       return -1;
      }
     }
     else if (rc) {
      if (stderr_callback)
       stderr_callback(popenPipe[0], childOutput, rc); 
     }
     //other end has exited and read returned 0 bytes here 
     //just close the pipes and get the fuck out of here 
     else goto collect_wait_status;
    } while(rc > 0);
   }
  } else {
   int rc = 0;
   rc = _eintr(waitpid(child, &childStatus, WNOHANG));
   if (rc == 0) continue;  //move on with select
   else {
    if (rc < 0) perror("waitpid failed");
    goto closefds_and_exit;
   }
  }
 }

 //close the pipe descriptors and return
collect_wait_status:
 rc = _eintr(waitpid(child, &childStatus, 0));
 assert(rc); //we are here coz of the child exit 
    //so the return value of waitpid has 
    //to be nonzero.
closefds_and_exit:
 _eintr(close(popenPipe[0]));
 _eintr(close(popenPipe[1]));
 _eintr(close(popenPipe[2]));
 *childExitCode = WEXITSTATUS(childStatus);
 return 0;
}

How to capture stdin, stdout and stderr of child program!

Ref: http://jineshkj.wordpress.com/2006/12/22/how-to-capture-stdin-stdout-and-stderr-of-child-program/

Description:

This is an article that I had posted in my livejournal on 21st-Apr-2006 05:28 pm. I’m trying to migrate the most valuable stuff to wordpress.

Hi everyone,

I have been asking this particular question to everyone for some time, the solution of which I found just now. What I wanted was to capture the standard file streams of a child process. Also that, I wanted not to use any named pipes for this purpose. One of the many ways I found was using a popen() system call. But, it seem to give to the parent either of stdin or stdout, but not both, and most importantly never stderr.

I used to wonder how various IDE’s used to execute compilers and debuggers in the background providing them with proper input and interpret their output. Anyway, I could find a solution to my problem. The basic fact is that “the child process always inherits the parent’s file descriptors”. ie, if a child can be made to inherit the file descriptors 0, 1 and 2, which actually points to some pipes with their other end in the parent process, the the parent effectively captures the child’s standard file descriptors.

So, what should be done is that, before fork(), the parent has to create the required number of pipes(one pipe for each stream) and make its own standard descriptors point to the file descriptors of the child end(supposed) of the pipes. After doing the fork(), the child will have its standard descriptors as what the parent had till the fork is called(ie, one of the ends of pipes).

Suppose you have to write something to the stdin of a child program. The steps to follow are as given below:

  1. Create a pipe – use pipe() – the pipe will have a read end and a write end – two descriptors
  2. Duplicate parent’s stdin – use oldstream=dup(). this is just like to save our original stream
  3. Close stdin – So that we can assign the read end(child’s stdin) of the pipe to file descriptor 0
  4. Make read end of pipe as stdin – use dup2() to duplicate the read end fd as 0
  5. do the fork() – From now on whatever the parent and child reads from stdin will come from the read end of pipe.
  6. Close unnecessary fds – Close the read and write end file descriptors from the child – it’ll use only stdin from now on. You may also close read end from parent since the parent is only going to write to the pipe.
  7. Restore parent’s stdin – close the fd 0, use dup2() to use the saved(oldstream) copy of the parent’s stdin.
  8. Start writing to child – Now start writing to the write end of the pipe to write to the stdin of child.

The above steps may be confusing. But if spent little time to think, you’ll surely get the idea. For more help see the code given below which captures both stdin and stdout of the child which is here the ‘bc’ calculator:

Code

#include <unistd.h>
#include <stdio.h>

main()
{

int outfd[2];
int infd[2];

int oldstdin, oldstdout;

pipe(outfd); // Where the parent is going to write to
pipe(infd); // From where parent is going to read

oldstdin = dup(0); // Save current stdin
oldstdout = dup(1); // Save stdout

close(0);
close(1);

dup2(outfd[0], 0); // Make the read end of outfd pipe as stdin
dup2(infd[1],1); // Make the write end of infd as stdout

if(!fork())
{

char *argv[]={"/usr/bin/bc",
"-q",
0};close(outfd[0]); // Not required for the child
close(outfd[1]);
close(infd[0]);
close(infd[1]);

execv(argv[0],argv);

}
else
{

char input[100];close(0); // Restore the original std fds of parent
close(1);
dup2(oldstdin, 0);
dup2(oldstdout, 1);

close(outfd[0]); // These are being used by the child
close(infd[1]);

write(outfd[1],"2^32\n",5); // Write to child’s stdin

input[read(infd[0],input,100)] = 0; // Read from child’s stdout

printf("%s",input);

}

}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment