如何将子描述符复制到STDOUT_FILENO和STDERR_FILENO

时间:2017-04-08 15:08:28

标签: c

我正在尝试实现GNU popen库但遇到dup2问题。我想将子管道复制到STDOUT_FILENOSTDERR_FILENO。  我在这里与dup2有点混淆,因为它在man页面上说dup2关闭旧描述符,如何在执行此STDERR_FILENO之后使用重复管道到dup2(pipe_list[child_fd], child_fd)。因为根据我对dup2的调用理解,oldfp将被关闭,而dup2(pipe_list[child_fd], STDERR_FILENO)将无效,因为pipe_list[child_fd]已经关闭。我应该使用dup并在关闭fd之后使用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;
}

1 个答案:

答案 0 :(得分:1)

致电dup2(fd1, fd2)时:

  • 如果fd1不是开放文件描述符,则该函数将返回-1并将errno设置为EBADFfd2未关闭。

  • 如果fd2超出允许范围,或者如果它未打开但进程已经具有最大打开文件描述符数(RLIMIT_NOFILE),则该函数将返回{{ 1}}并将-1设置为errno

  • 如果出现任何其他问题,该函数将返回EBADF并将-1设置为某个错误代码。在这种情况下,未指定errno是否未被触及或关闭。

    以下其他案例假设操作成功。

  • 如果fd2,该函数将返回fd1 == fd2(与fd1相同)。

  • 如果d2不是开放描述符,则fd2与其重复。然后,这两个描述符引用相同的文件描述(内核保存的文件位置等内容)。

  • 如果fd1是一个打开的文件描述符,则在fd2复制到它时它会被关闭。结束是无声,这意味着由于结束而导致的任何错误都会被忽略。

重点是,只有fd1fd2之前打开时才会关闭fd2 != fd1。在任何情况下fd2都不会关闭。

关于您的代码,我无法真正遵循您的逻辑。特别是,使用fd1parent_fd作为client_fd的索引似乎对我很怀疑。此外,该函数应返回文件句柄以及子进程PID。

pipe_list[]

如果将上述内容保存为 pipe_example.c ,则可以使用例如编译它。

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

并通过运行例如

进行测试
gcc -Wall -O2 pipe_example.c -o pipe_ex

正如您可以看到的,上面的代码非常复杂 - 总的来说,它只是一个./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 $$' 模拟,但功能较少。

但是,它会非常谨慎地执行所有操作(除非将exec从子级发生故障的原因传递给父级 - 我只是假设写入成功)。因此,作为在实际多处理程序中存在多少细节(以及处理这些细节的至少一种方式)的示例可能是有用的。当然,标准POSIX.1 popen()是多么有用。