root/usr.sbin/nsd/popen3.c
#include "config.h"
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/time.h>

#include "popen3.h"

static void close_pipe(int fds[2])
{
        if(fds[0] != -1) {
                close(fds[0]);
                fds[0] = -1;
        }
        if(fds[1] != -1) {
                close(fds[1]);
                fds[1] = -1;
        }
}

pid_t popen3(char *const *command,
             int *fdinptr,
             int *fdoutptr,
             int *fderrptr)
{
        int err = 0;
        int fdin[] = { -1, -1 };
        int fdout[] = { -1, -1 };
        int fderr[] = { -1, -1 };
        int fdsig[] = { -1, -1 };
        pid_t pid;
        ssize_t discard;

        if(command == NULL || *command == NULL) {
                errno = EINVAL;
                return -1;
        }

        if(fdinptr != NULL && pipe(fdin) == -1) {
                goto error;
        }
        if(fdoutptr != NULL && pipe(fdout) == -1) {
                goto error;
        }
        if(fderrptr != NULL && pipe(fderr) == -1) {
                goto error;
        }
        if(pipe(fdsig) == -1 ||
           fcntl(fdsig[0], F_SETFD, FD_CLOEXEC) == -1 ||
           fcntl(fdsig[1], F_SETFD, FD_CLOEXEC) == -1)
        {
                goto error;
        }

        pid = fork();
        switch(pid) {
        case -1: /* error */
                goto error;
        case 0: /* child */
                if(fderrptr != NULL) {
                        if(dup2(fderr[1], 2) == -1) {
                                goto error_dup2;
                        }
                        close_pipe(fderr);
                } else {
                        close(2);
                }
                if(fdoutptr != NULL) {
                        if(dup2(fdout[1], 1) == -1) {
                                goto error_dup2;
                        }
                        close_pipe(fdout);
                } else {
                        close(1);
                }
                if(fdinptr != NULL) {
                        if(dup2(fdin[0], 0) == -1) {
                                goto error_dup2;
                        }
                        close_pipe(fdin);
                } else {
                        close(0);
                }

                execvp(*command, command);
error_dup2:
                err = errno;
                close(fdsig[0]);
                discard = write(fdsig[1], &err, sizeof(err));
                (void)discard;
                close(fdsig[1]);
                exit(-1);
        default: /* parent */
        {
                /* wait for signal pipe to close */
                int ret;
                fd_set rfds;

                close(fdsig[1]);
                fdsig[1] = -1;
                do {
                        FD_ZERO(&rfds);
                        FD_SET(fdsig[0], &rfds);
                        ret = select(fdsig[0] + 1, &rfds, NULL, NULL, NULL);
                } while(ret == -1 && errno == EINTR);

                if(ret == -1) {
                        goto error;
                }

                if((ret = read(fdsig[0], &err, sizeof(err))) != 0) {
                        if(ret != -1) {
                                assert(ret == sizeof(err));
                                errno = err;
                        }
                        goto error;
                }
                close(fdsig[0]);
                fdsig[0] = -1;
        }
                break;
        }

        if(fdinptr != NULL) {
                close(fdin[0]);
                *fdinptr = fdin[1];
        }
        if(fdoutptr != NULL) {
                close(fdout[1]);
                *fdoutptr = fdout[0];
        }
        if(fderrptr != NULL) {
                close(fderr[1]);
                *fderrptr = fderr[0];
        }

        return pid;

error:
        err = errno;

        close_pipe(fdin);
        close_pipe(fdout);
        close_pipe(fderr);
        close_pipe(fdsig);

        errno = err;

        return -1;
}