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
pclose()
returns"exit code"*256
. So divide that number by256
to get the real exit 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;
}