Skip to content

Instantly share code, notes, and snippets.

@mike-bourgeous
Forked from nitrogenlogic/00_popen3_moved.md
Last active December 9, 2024 09:17

Revisions

  1. mike-bourgeous revised this gist Oct 23, 2016. No changes.
  2. mike-bourgeous revised this gist Oct 23, 2016. 2 changed files with 5 additions and 3 deletions.
    3 changes: 2 additions & 1 deletion popen3_2007.c
    Original file line number Diff line number Diff line change
    @@ -7,7 +7,8 @@
    * but if used in an open source application, attribution would be appreciated.
    *
    * Mike Bourgeous
    * https://github.com/nitrogenlogic
    * https://github.com/mike-bourgeous
    * http://www.mikebourgeous.com/
    */
    #include <stdio.h>
    #include <stdlib.h>
    5 changes: 3 additions & 2 deletions popen3_2011.c
    Original file line number Diff line number Diff line change
    @@ -8,11 +8,12 @@
    * 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
    * 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/nitrogenlogic
    * https://github.com/mike-bourgeous
    * http://www.mikebourgeous.com/
    */
    #include <stdio.h>
    #include <stdlib.h>
  3. Mike Bourgeous revised this gist Oct 23, 2016. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion popen3_2007.c
    Original file line number Diff line number Diff line change
    @@ -42,7 +42,7 @@ pid_t popen3(const char *command, int *fp_in, int *fp_out, int *fp_err, int nonb
    return -1;
    }

    /* Set the pipes to nonblocking IO if rhttps://gist.github.com/1022231equested (a good idea for single-threaded apps) */
    /* 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);
    }
  4. Mike revised this gist Jun 13, 2011. 2 changed files with 3 additions and 1 deletion.
    3 changes: 2 additions & 1 deletion popen3_2007.c
    Original file line number Diff line number Diff line change
    @@ -31,6 +31,7 @@ pid_t popen3(const char *command, int *fp_in, int *fp_out, int *fp_err, int nonb

    // 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;
    @@ -41,7 +42,7 @@ pid_t popen3(const char *command, int *fp_in, int *fp_out, int *fp_err, int nonb
    return -1;
    }

    /* Set the pipes to nonblocking IO if requested (a good idea for single-threaded apps) */
    /* Set the pipes to nonblocking IO if rhttps://gist.github.com/1022231equested (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);
    }
    1 change: 1 addition & 0 deletions popen3_2011.c
    Original file line number Diff line number Diff line change
    @@ -49,6 +49,7 @@ pid_t popen3(char *command, int *writefd, int *readfd, int *errfd)
    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");
  5. Mike created this gist Jun 13, 2011.
    105 changes: 105 additions & 0 deletions popen3_2007.c
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,105 @@
    /*
    * 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.

    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
    } /* }}} */
    152 changes: 152 additions & 0 deletions popen3_2011.c
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,152 @@
    /*
    * 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

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