The current version will be found at https://gist.github.com/mike-bourgeous/2be6c8900bf624887fe5fee4f28552ef
Please make all comments, stars, forks, etc. there.
The current version will be found at https://gist.github.com/mike-bourgeous/2be6c8900bf624887fe5fee4f28552ef
Please make all comments, stars, forks, etc. there.
/* | |
* This implementation of popen3() was created in 2007 for an experimental | |
* mpg123 frontend and is based on a popen2() snippet found online. This | |
* implementation may behave in unexpected ways if stdin/stdout/stderr have | |
* been closed or modified. No warranty of its correctness, security, or | |
* usability is given. My modifications are released into the public domain, | |
* but if used in an open source application, attribution would be appreciated. | |
* | |
* Mike Bourgeous | |
* https://github.com/nitrogenlogic | |
*/ | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <unistd.h> | |
#include <fcntl.h> | |
/* | |
* popen3() | |
* | |
* Runs the specified command line using a shell (SECURITY WARNING: don't put | |
* user-provided input here, to avoid shell expansion), opening pipes for | |
* stdin, stdout, and stderr. If NULL is specified for a given fd, that stream | |
* will be discarded. | |
* | |
* It is recommended to set nonblock_in to zero, nonblock_outerr to nonzero. | |
*/ | |
pid_t popen3(const char *command, int *fp_in, int *fp_out, int *fp_err, int nonblock_in, int nonblock_outerr) /* {{{ */ | |
{ | |
#define POPEN3_READ 0 | |
#define POPEN3_WRITE 1 | |
// Based on popen2() from http://snippets.dzone.com/posts/show/1134, | |
// where it was listed as freeware. Modifications by Mike Bourgeous. | |
// https://gist.github.com/1022231 | |
int p_stdin[2], p_stdout[2], p_stderr[2]; | |
pid_t pid; | |
/* Open the three pipes */ | |
if(pipe(p_stdin) != 0 || pipe(p_stdout) != 0 || pipe(p_stderr) != 0) { | |
perror("popen3: unable to open pipes for I/O"); | |
return -1; | |
} | |
/* Set the pipes to nonblocking IO if requested (a good idea for single-threaded apps) */ | |
if(nonblock_in) { | |
fcntl(p_stdin[POPEN3_WRITE], F_SETFL, fcntl(p_stdin[POPEN3_WRITE], F_GETFL) | O_NONBLOCK); | |
} | |
if(nonblock_outerr) { | |
fcntl(p_stdout[POPEN3_READ], F_SETFL, fcntl(p_stdout[POPEN3_READ], F_GETFL) | O_NONBLOCK); | |
fcntl(p_stderr[POPEN3_READ], F_SETFL, fcntl(p_stderr[POPEN3_READ], F_GETFL) | O_NONBLOCK); | |
} | |
pid = fork(); | |
if (pid < 0) { | |
/* An error occurred */ | |
return pid; | |
} else if (pid == 0) { | |
/* This is the child process */ | |
// Close the parent-side half of the pipes | |
close(p_stdin[POPEN3_WRITE]); | |
close(p_stderr[POPEN3_READ]); | |
close(p_stdout[POPEN3_READ]); | |
// Replace the real stdin/stdout/stderr with the pipes created above | |
dup2(p_stdin[POPEN3_READ], fileno(stdin)); | |
dup2(p_stderr[POPEN3_WRITE], fileno(stderr)); // stderr is first in case err is redirected to out (i.e. with 2>&1) | |
dup2(p_stdout[POPEN3_WRITE], fileno(stdout)); | |
execl("/bin/sh", "sh", "-c", command, (char *)NULL); | |
perror("popen3: execl failed"); | |
exit(1); | |
} | |
/* This is the parent process */ | |
if(fp_in == NULL) { | |
/* fp_in was null - the caller doesn't want to write to stdin */ | |
close(p_stdin[POPEN3_WRITE]); | |
} else { | |
/* Give the caller the file number for stdin */ | |
*fp_in = p_stdin[POPEN3_WRITE]; | |
} | |
if(fp_out == NULL) { | |
/* fp_out was null - the caller doesn't want to read from stdout */ | |
close(p_stdout[POPEN3_READ]); | |
} else { | |
/* Give the caller the file number for stdout */ | |
*fp_out = p_stdout[POPEN3_READ]; | |
} | |
if(fp_err == NULL) { | |
/* fp_err was null - the caller doesn't want to read from stderr */ | |
close(p_stderr[POPEN3_READ]); | |
} else { | |
/* Give the caller the file number for stderr */ | |
*fp_err = p_stderr[POPEN3_READ]; | |
} | |
return pid; | |
#undef POPEN3_READ | |
#undef POPEN3_WRITE | |
} /* }}} */ |
/* | |
* This implementation of popen3() was created from scratch in June of 2011. It | |
* is less likely to leak file descriptors if an error occurs than the 2007 | |
* version and has been tested under valgrind. It also differs from the 2007 | |
* version in its behavior if one of the file descriptor parameters is NULL. | |
* Instead of closing the corresponding stream, it is left unmodified (typically | |
* sharing the same terminal as the parent process). It also lacks the | |
* non-blocking option present in the 2007 version. | |
* | |
* No warranty of correctness, safety, performance, security, or usability is | |
* given. This implementation is released into the public domain, but if used | |
* in an open source application, attribution would be appreciated. | |
* | |
* Mike Bourgeous | |
* https://github.com/nitrogenlogic | |
*/ | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <unistd.h> | |
#include <fcntl.h> | |
/* | |
* Sets the FD_CLOEXEC flag. Returns 0 on success, -1 on error. | |
*/ | |
static int set_cloexec(int fd) | |
{ | |
if(fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC) == -1) { | |
perror("Error setting FD_CLOEXEC flag"); | |
return -1; | |
} | |
return 0; | |
} | |
/* | |
* Runs command in another process, with full remote interaction capabilities. | |
* Be aware that command is passed to sh -c, so shell expansion will occur. | |
* Writing to *writefd will write to the command's stdin. Reading from *readfd | |
* will read from the command's stdout. Reading from *errfd will read from the | |
* command's stderr. If NULL is passed for writefd, readfd, or errfd, then the | |
* command's stdin, stdout, or stderr will not be changed. Returns the child | |
* PID on success, -1 on error. | |
*/ | |
pid_t popen3(char *command, int *writefd, int *readfd, int *errfd) | |
{ | |
int in_pipe[2] = {-1, -1}; | |
int out_pipe[2] = {-1, -1}; | |
int err_pipe[2] = {-1, -1}; | |
pid_t pid; | |
// 2011 implementation of popen3() by Mike Bourgeous | |
// https://gist.github.com/1022231 | |
if(command == NULL) { | |
fprintf(stderr, "Cannot popen3() a NULL command.\n"); | |
goto error; | |
} | |
if(writefd && pipe(in_pipe)) { | |
perror("Error creating pipe for stdin"); | |
goto error; | |
} | |
if(readfd && pipe(out_pipe)) { | |
perror("Error creating pipe for stdout"); | |
goto error; | |
} | |
if(errfd && pipe(err_pipe)) { | |
perror("Error creating pipe for stderr"); | |
goto error; | |
} | |
pid = fork(); | |
switch(pid) { | |
case -1: | |
// Error | |
perror("Error creating child process"); | |
goto error; | |
case 0: | |
// Child | |
if(writefd) { | |
close(in_pipe[1]); | |
if(dup2(in_pipe[0], 0) == -1) { | |
perror("Error assigning stdin in child process"); | |
exit(-1); | |
} | |
close(in_pipe[0]); | |
} | |
if(readfd) { | |
close(out_pipe[0]); | |
if(dup2(out_pipe[1], 1) == -1) { | |
perror("Error assigning stdout in child process"); | |
exit(-1); | |
} | |
close(out_pipe[1]); | |
} | |
if(errfd) { | |
close(err_pipe[0]); | |
if(dup2(err_pipe[1], 2) == -1) { | |
perror("Error assigning stderr in child process"); | |
exit(-1); | |
} | |
close(err_pipe[1]); | |
} | |
execl("/bin/sh", "/bin/sh", "-c", command, (char *)NULL); | |
perror("Error executing command in child process"); | |
exit(-1); | |
default: | |
// Parent | |
break; | |
} | |
if(writefd) { | |
close(in_pipe[0]); | |
set_cloexec(in_pipe[1]); | |
*writefd = in_pipe[1]; | |
} | |
if(readfd) { | |
close(out_pipe[1]); | |
set_cloexec(out_pipe[0]); | |
*readfd = out_pipe[0]; | |
} | |
if(errfd) { | |
close(err_pipe[1]); | |
set_cloexec(out_pipe[0]); | |
*errfd = err_pipe[0]; | |
} | |
return pid; | |
error: | |
if(in_pipe[0] >= 0) { | |
close(in_pipe[0]); | |
} | |
if(in_pipe[1] >= 0) { | |
close(in_pipe[1]); | |
} | |
if(out_pipe[0] >= 0) { | |
close(out_pipe[0]); | |
} | |
if(out_pipe[1] >= 0) { | |
close(out_pipe[1]); | |
} | |
if(err_pipe[0] >= 0) { | |
close(err_pipe[0]); | |
} | |
if(err_pipe[1] >= 0) { | |
close(err_pipe[1]); | |
} | |
return -1; | |
} |