-
-
Save mike-bourgeous/2be6c8900bf624887fe5fee4f28552ef to your computer and use it in GitHub Desktop.
Two implementations of a popen3() function in POSIX/C providing stdin, stdout, and stderr (http://blog.mikebourgeous.com/2011/06/12/programmatic-process-control-in-c-popen3/)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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/mike-bourgeous | |
* http://www.mikebourgeous.com/ | |
*/ | |
#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 file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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/CC0, but if used | |
* in an open source application, attribution would be appreciated. | |
* | |
* Mike Bourgeous | |
* https://github.com/mike-bourgeous | |
* http://www.mikebourgeous.com/ | |
*/ | |
#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; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks for this code.
Line 127 of popen_2011.c should be "set_cloexec(err_pipe[0]);"