Skip to content

Instantly share code, notes, and snippets.

@mike-bourgeous
Forked from nitrogenlogic/00_popen3_moved.md
Last active May 13, 2022 06:35
Show Gist options
  • Save mike-bourgeous/2be6c8900bf624887fe5fee4f28552ef to your computer and use it in GitHub Desktop.
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 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 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;
}
@phdias79
Copy link

Thanks for this code.
Line 127 of popen_2011.c should be "set_cloexec(err_pipe[0]);"

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