Seek Addo
Seek Addo

Reputation: 1903

How to duplicate a child descriptor onto STDOUT_FILENO and STDERR_FILENO

I am trying to implement the GNU popen library but having issues with dup2. I want to duplicate the child pipe to both STDOUT_FILENO and STDERR_FILENO. I am little confuse here with the dup2, because it says on the man page that dup2 closes the old descriptor, how how can use duplicate pipe onto STDERR_FILENO after doing this dup2(pipe_list[child_fd], child_fd). Because from what i do understand upon a call to dup2 the oldfp will be closed and this dup2(pipe_list[child_fd], STDERR_FILENO) will not work because pipe_list[child_fd] was already closed. Should i use dup and after that close fd or there is a way to achieve this with dup2?

#define tpsr(a,b) (*type == 'r' ? (b) : (a))
#define tpsw(a,b) (*type == 'w' ? (b) : (a))


static FILE *fp_stream = NULL;
static pid_t popen_pid = -1;
static const char * const shell_path = "/bin/sh";


FILE *mypopen(const char *command, const char *type) {

    int pipe_list[2];
    int parent_fd, child_fd;


    if (type == NULL || type[1] != 0) {
        errno = EINVAL;
        return NULL;
    }

    if (type[0] != 'w' && type[0] != 'r') {
        errno = EINVAL;
        return NULL;
    }


    if (pipe(pipe_list) == -1) {
        return NULL;  //errno will be set
    }

    child_fd = tpsr(STDIN_FILENO,STDOUT_FILENO);
    parent_fd = tpsw(STDIN_FILENO,STDOUT_FILENO);
    /*The above (tpsr and tpsw) are the same as this
        if type[0] == 'r'
        child_fd = STDOUT_FILENO; //1 child will be doing the writing
        parent_fd = STDIN_FILENO; //0 parent read

       if type[0] == 'w'
        child_fd = STDIN_FILENO; //0 child doing the reading
        parent_fd = STDOUT_FILENO;//1 parent do the writing
    }*/


    if ((popen_pid = fork()) == -1) {
        close(pipe_list[0]);
        close(pipe_list[1]);
        return NULL;
    }

    if (popen_pid == 0) {
        // we got a child here

        if (pipe_list[child_fd] != child_fd) {

            if (dup2(pipe_list[child_fd], child_fd) == -1) {
                (void) close(pipe_list[child_fd]);
                _exit(EXIT_FAILURE);
            }
              //is this the right way after the oldfd is closed by dup2
            if (child_fd == STDOUT_FILENO) {
                if (dup2(pipe_list[child_fd], STDERR_FILENO) == -1){
                    (void) close(pipe_list[child_fd]);
                    _exit(EXIT_FAILURE);
                }

            }

            (void) close(pipe_list[child_fd]);
        }
        (void) pipe_list[parent_fd];


        (void) execl(shell_path, "sh", "-c", command, (char *) NULL);
        _exit(EXIT_FAILURE); //exit(127) required by man page

    } else {

        (void) close(pipe_list[child_fd]);
        if ((fp_stream = fdopen(pipe_list[parent_fd], type)) == NULL) {
            (void) close(pipe_list[parent_fd]);
            return NULL;
        }


    }

    return fp_stream;
}

Upvotes: 0

Views: 1732

Answers (2)

legale
legale

Reputation: 702

A bit more detailed example with stdout and stderr at the same time.

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

int main(int argc, char *argv[]) {
  int fdout[2];     /* pipe fd array for the stdout */
  int fderr[2];     /* pipe fd array for the stderr */
  pid_t pid;        /* process id */
  char buf[4096];   /* stdout and stderr buffer */
  int status;       /* place to store child process status */

  if (pipe(fdout)==-1)  /* first pipe for the stdout */
    perror("pipe out");    /* exit with error if something wrong */

  if (pipe(fderr)==-1)  /* second pipe for the stderr */
    perror("pipe err");    
    /*
    *   On success, the PID of the child process is returned in the
    *   parent, and 0 is returned in the child.  On failure, -1 is
    *   returned in the parent, no child process is created, and errno is
    *   set to indicate the error.
    */
  if ((pid = fork()) == -1) /* fork current process and store pid */
    perror("fork error");

  if(pid == 0) { /* if pid == 0 than this is a child process */

    dup2 (fdout[1], STDOUT_FILENO); /* send stdout to the pipe fdout */
    dup2 (fderr[1], STDERR_FILENO); /* send stderr to the pipe fderr */
    
    /* close both sides of the pipe in the child */
    close(fdout[0]);
    close(fdout[1]);

    /* close both sides of the pipe in the child */
    close(fderr[0]);
    close(fderr[1]);


    /* execvp is called in the child ps 
     * argv[1] is a ptr to the first arg
     * &argv[1] is a ptr to a ptr to the first arg
     */
    execvp(argv[1], &argv[1]); 
    
    /* this part is never reached if execvp success */
    return errno;

  } else { /* parent */    
    while(wait(&status) > 0){} /* wait for the child processes to finish */
    
    /* close write ends of the pipes */
    close(fdout[1]);
    close(fderr[1]);
    
    /* if child process finished with errorprint error 
     * and return error code from parent process
     */
    if (WIFEXITED(status) && WEXITSTATUS(status)){
      printf("%s\n", strerror(WEXITSTATUS(status)));
      return WEXITSTATUS(status);
    }

    ssize_t size;
    size = read(fdout[0], buf, sizeof(buf));
    printf("OUT: \"%.*s\"\n", (int)size, buf);

    size = read(fderr[0], buf, sizeof(buf));
    printf("ERR: \"%.*s\"\n", (int)size, buf);

    /* close read ends of the pipes */
    close(fdout[0]);
    close(fderr[0]);
  }

  return 0;
}

compile and test:

ruslan@localhost:~/exec$ gcc main.c -o a && ./a /bin/ls 
OUT: "a
main.c
"
ERR: ""
ruslan@localhost:~/exec$ gcc main.c -o a && ./a /bin/ls ---sdfadsfsdf
OUT: ""
ERR: "/bin/ls: unrecognized option '---sdfadsfsdf'
Try '/bin/ls --help' for more information.
"
ruslan@localhost:~/exec$ 

Upvotes: 0

Nominal Animal
Nominal Animal

Reputation: 39406

When you call dup2(fd1, fd2):

  • If fd1 is not an open file descriptor, the function will return -1 and set errno to EBADF. fd2 is not closed.

  • If fd2 is outside the allowed range, or if it is not open but the process already has the maximum number of open file descriptors (RLIMIT_NOFILE), the function will return -1 and set errno to EBADF.

  • If any other problem occurs, the function will return -1 with errno set to some error code. It is not specified whether fd2 is untouched or closed in this case.

    The rest of the cases below assume the operation is successful.

  • If fd1 == fd2, the function will return fd1 (which is the same as d2).

  • If fd2 is not an open descriptor, then fd1 is duplicated to it. The two descriptors then refer to the same file description (the stuff like file position that the kernel keeps).

  • If fd2 is an open file descriptor, it gets closed when fd1 is duplicated to it. The closing is silent, meaning that any error due to the closing is silently ignored.

The main point is, that only fd2 may be closed, and only if fd2 != fd1 and if fd2 was previously open. In no case is fd1 closed.


I cannot really follow your logic with regards to your code. In particular, using the parent_fd and client_fd as indexes to pipe_list[] seems suspect to me. Furthermore, the function should return both a file handle, as well as the child process PID.

#define _POSIX_C_SOURCE  200809L
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

typedef struct {
    FILE *child_pipe;
    pid_t child_pid;
    int   exit_status;
} cmd;

/* open() analog, except moves the descriptor to the
   number specified. You cannot use O_CREAT flag.
   Returns 0 if success, -1 if an error occurred,
   with reason in errno. */
static int open_fd(const int fd, const char *path, const int flags)
{
    int tempfd;

    if (fd == -1 || !path || !*path || (flags & O_CREAT)) {
        errno = EINVAL;
        return -1;
    }

    tempfd = open(path, flags);
    if (tempfd == -1)
        return -1;

    if (tempfd != fd) {
        if (dup2(tempfd, fd) == -1) {
            const int failure = errno;
            close(tempfd);
            errno = failure;
            return -1;
        }
        if (close(tempfd)) {
            const int failure = errno;
            close(fd);
            errno = failure;
            return -1;
        }
    }

    return 0;
}

/* pipe[] analog, except ensures neither endpoint
   matches STDIN_FILENO, STDOUT_FILENO, or STDERR_FILENO.
*/
static int create_pipe(int fd[2])
{
    /* I like to initialize return parameters.. */
    fd[0] = -1;
    fd[1] = -1;

    if (pipe(fd) == -1)
        return -1;
    else {
        const int  close_stdin  = (fd[0] == STDIN_FILENO) || (fd[1] == STDIN_FILENO);
        const int  close_stdout = (fd[0] == STDOUT_FILENO) || (fd[1] == STDOUT_FILENO);
        const int  close_stderr = (fd[0] == STDERR_FILENO) || (fd[1] == STDERR_FILENO);
        int        failure = 0;

        do {

            while (fd[0] == STDIN_FILENO ||
                   fd[0] == STDOUT_FILENO ||
                   fd[0] == STDERR_FILENO) {
                fd[0] = dup(fd[0]);
                if (fd[0] == -1) {
                    failure = -1;
                    break;
                }
            }
            if (failure)
                break;

            while (fd[1] == STDIN_FILENO ||
                   fd[1] == STDOUT_FILENO ||
                   fd[1] == STDERR_FILENO) {
                fd[1] = dup(fd[1]);
                if (fd[1] == -1) {
                    failure = -1;
                    break;
                }
            }
            if (failure)
                break;

            if (close_stdin)
                close(STDIN_FILENO);
            if (close_stdout)
                close(STDOUT_FILENO);
            if (close_stderr)
                close(STDERR_FILENO);

            return 0;
        } while (0);

        /* Whoops, failed: cleanup time. */
        failure = errno;

        if (fd[0] != STDIN_FILENO &&
            fd[0] != STDOUT_FILENO &&
            fd[0] != STDERR_FILENO)
            close(fd[0]);
        if (fd[1] != STDIN_FILENO &&
            fd[1] != STDOUT_FILENO &&
            fd[1] != STDERR_FILENO)
            close(fd[1]);
        if (close_stdin)
            close(STDIN_FILENO);
        if (close_stdout)
            close(STDOUT_FILENO);
        if (close_stderr)
            close(STDERR_FILENO);

        errno = failure;
        return -1;
    }
}
#define  CMD_PASS     0
#define  CMD_READ     1
#define  CMD_DISCARD  2

int cmd_read(cmd        *pipe,
             const char *path,
             char       *args[],
             const int   stdout_mode,
             const int   stderr_mode)
{
    int   pipefd[2], controlfd[2], cause;
    FILE *handle;
    pid_t child, p;

    /* If pipe is not NULL, initialize it. */
    if (pipe) {
        pipe->child_pipe = NULL;
        pipe->child_pid = 0;
        pipe->exit_status = 0;
    }

    /* Verify the parameters make sense. */
    if (!path || !args || !pipe ||
        stdout_mode < 0 || stdout_mode > 2 ||
        stderr_mode < 0 || stderr_mode > 2) {
        errno = EINVAL;
        return -1;
    }

    /* Do we need the pipe? */
    if (stdout_mode == CMD_READ || stderr_mode == CMD_READ) {
        if (create_pipe(pipefd) == -1)
            return -1;
    } else {
        pipefd[0] = -1;
        pipefd[1] = -1;
    }

    /* We use a control pipe to detect exec errors. */
    if (create_pipe(controlfd) == -1) {
        cause = errno;
        if (pipefd[0] != -1)
            close(pipefd[0]);
        if (pipefd[1] != -1)
            close(pipefd[1]);
        errno = cause;
        return -1;
    }

    /* Parent reads from the control pipe,
       and the child writes to it, but only
       if exec fails. We mark the write end
       close-on-exec, so the parent notices
       if the exec is successful. */
    fcntl(controlfd[1], F_SETFD, O_CLOEXEC);

    /* Fork the child process. */
    child = fork();
    if (child == (pid_t)-1) {
        cause = errno;
        close(controlfd[0]);
        close(controlfd[1]);
        if (pipefd[0] != -1)
            close(pipefd[0]);
        if (pipefd[1] != -1)
            close(pipefd[1]);
        errno = cause;
        return -1;
    }

    if (!child) {
        /* This is the child process. */
        close(controlfd[0]);
        if (pipefd[0] != -1)
            close(pipefd[0]);

        cause = 0;
        do {
            if (stdout_mode == CMD_READ) {
                if (dup2(pipefd[1], STDOUT_FILENO) == -1) {
                    cause = -1;
                    break;
                }
            } else
            if (stdout_mode == CMD_DISCARD) {
                if (open_fd(STDOUT_FILENO, "/dev/null", O_WRONLY) == -1) {
                    cause = -1;
                    break;
                }
            }
            if (cause)
                break;

            if (stderr_mode == CMD_READ) {
                if (dup2(pipefd[1], STDERR_FILENO) == -1) {
                    cause = -1;
                    break;
                }
            } else
            if (stderr_mode == CMD_DISCARD) {
                if (open_fd(STDERR_FILENO, "/dev/null", O_WRONLY) == -1) {
                    cause = -1;
                    break;
                }
            }
            if (cause)
                break;

            if (pipefd[1] != -1)
                close(pipefd[1]);

            if (path[0] == '/')
                execv(path, (char *const *)args);
            else
                execvp(path, (char *const *)args);

            /* Failed. */
        } while (0);

        /* Tell parent, why. */
        cause = errno;
        /* To silence the warn_unused_result warning: */
        if (write(controlfd[1], &cause, sizeof cause))
            ;
        close(controlfd[1]);

        exit(127);
    }

    /* Parent process. */
    close(controlfd[1]);
    if (pipefd[1] != -1)
        close(pipefd[1]);

    do {
        ssize_t  n;

        /* Read from the pipe to see if exec failed. */
        n = read(controlfd[0], &cause, sizeof cause);
        if (n == (ssize_t)sizeof cause)
            break;
        if (n != 0) {
            cause = EIO;
            kill(child, SIGKILL);
            break;
        }
        close(controlfd[0]);

        if (pipefd[0] != -1) {
            handle = fdopen(pipefd[0], "r");
            if (!handle) {
                cause = errno;
                kill(child, SIGKILL);
                break;
            }
        } else
            handle = NULL;

        /* Success! */
        pipe->child_pipe = handle;
        pipe->child_pid = child;

        return 0;
    } while (0);

    /* Failed; reason is in cause. */
    if (pipefd[0] != -1)
        close(pipefd[0]);

    /* Reap child. */
    while (1) {
        p = waitpid(child, NULL, 0);
        if ((p == child) || (p == (pid_t)-1 && errno != EINTR))
            break;
    }

    errno = cause;
    return -1;
}

int cmd_wait(cmd *pipe)
{
    pid_t  p;

    if (!pipe || pipe->child_pid == -1)
        return errno = EINVAL;

    while (1) {
        p = waitpid(pipe->child_pid, &(pipe->exit_status), 0);
        if (p == pipe->child_pid) {
            if (pipe->child_pipe)
                fclose(pipe->child_pipe);
            pipe->child_pipe = NULL;
            pipe->child_pid  = 0;
            return 0;

        } else
        if (p != -1) {
            if (pipe->child_pipe)
                fclose(pipe->child_pipe);
            pipe->child_pipe = NULL;
            pipe->child_pid  = 0;
            errno = EIO;
            return -1;

        } else
        if (errno != EINTR) {
            const int cause = errno;
            if (pipe->child_pipe)
                fclose(pipe->child_pipe);
            pipe->child_pipe = NULL;
            pipe->child_pid  = 0;
            errno = cause;
            return -1;
        }
    }
}

int main(int argc, char *argv[])
{
    off_t   total = 0;
    char   *line  = NULL;
    size_t  size  = 0;
    ssize_t len;
    int     stdout_mode, stderr_mode;
    cmd     run;

    if (argc < 3 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        printf("\n");
        printf("Usage:\n");
        printf("       %s [ -h | --help ]\n", argv[0]);
        printf("       %s MODE COMMAND [ ARGS ... ]\n", argv[0]);
        printf("Where MODE may contain:\n");
        printf("       o  to retain output,\n");
        printf("       O  to discard/hide output,\n");
        printf("       e  to retain errors, and\n");
        printf("       E  to discard/hide errors.\n");
        printf("All other characters are ignored.\n");
        printf("If there is no 'o' or 'O' in MODE, then output\n");
        printf("is passed through to standard output; similarly,\n");
        printf("if there is no 'e' or 'E' in MODE, then errors\n");
        printf("are passed through to standard error.\n");
        printf("\n");
        return EXIT_SUCCESS;
    }

    if (strchr(argv[1], 'o'))
        stdout_mode = CMD_READ;
    else
    if (strchr(argv[1], 'O'))
        stdout_mode = CMD_DISCARD;
    else
        stdout_mode = CMD_PASS;

    if (strchr(argv[1], 'e'))
        stderr_mode = CMD_READ;
    else
    if (strchr(argv[1], 'E'))
        stderr_mode = CMD_DISCARD;
    else
        stderr_mode = CMD_PASS;

    if (cmd_read(&run, argv[2], argv + 2, stdout_mode, stderr_mode) == -1) {
        fprintf(stderr, "%s: %s.\n", argv[2], strerror(errno));
        return EXIT_FAILURE;
    }

    if (run.child_pipe) {
        while (1) {
            len = getline(&line, &size, run.child_pipe);
            if (len == -1)
                break;

            total += (off_t)len;

#ifdef PRINT_PIPE_CONTENTS
            if (len > 0)
                fwrite(line, (size_t)len, 1, stdout);
#endif
        }
        if (ferror(run.child_pipe) || !feof(run.child_pipe)) {
            fprintf(stderr, "%s: Error reading from pipe.\n", argv[2]);
            return EXIT_FAILURE;
        }
    }

    if (cmd_wait(&run) == -1) {
        fprintf(stderr, "%s: Lost child process: %s.\n", argv[2], strerror(errno));
        return EXIT_FAILURE;
    }

    fprintf(stderr, "Read %llu bytes from child process.\n", (unsigned long long)total);

    if (WIFEXITED(run.exit_status))
        fprintf(stderr, "Child exited with status %d.\n", WEXITSTATUS(run.exit_status));
    else
    if (WIFSIGNALED(run.exit_status))
        fprintf(stderr, "Child terminated due to signal %d.\n", WTERMSIG(run.exit_status));
    else
        fprintf(stderr, "Child lost.\n");

    return EXIT_SUCCESS;
}

If you save the above as pipe_example.c, you can compile it using e.g.

gcc -Wall -O2 pipe_example.c -o pipe_ex

and test it by running e.g.

./pipe_ex  oe  sh -c 'printf "OUT\n"; printf "ERR\n" >&2; exit 5'
./pipe_ex  Oe  sh -c 'printf "OUT\n"; printf "ERR\n" >&2;'
./pipe_ex  -E  sh -c 'printf "OUT\n"; printf "ERR\n" >&2; kill -TERM $$'

The above code is, as you can obviously see, quite complicated -- and overall, it is just a popen() analog, but with fewer features.

However, it does everything quite carefully (except perhaps passing the reason the exec failed from the child to the parent -- there I am simply assuming the write succeeds). As such, it might be useful as an example of how many details (and at least one way of dealing with such details) there are in practical multiprocess programs. And, of course, how useful standard POSIX.1 popen() is.

Upvotes: 2

Related Questions