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; | |
| } |