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

标签 c

我正在尝试实现 GNU popen 库,但遇到 dup2 问题。我想将子管道复制到 STDOUT_FILENOSTDERR_FILENO。 我在这里对 dup2 有点困惑,因为它在手册页上说 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;
}

最佳答案

当您调用dup2(fd1, fd2)时:

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

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

  • 如果发生任何其他问题,该函数将返回 -1,并将 errno 设置为某个错误代码。未指定在这种情况下 fd2 是保持不变还是关闭。

    以下其余情况假设操作成功。

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

  • 如果fd2不是一个打开的描述符,则将fd1复制到它。然后,这两个描述符引用相同的文件描述(内核保留的文件位置之类的东西)。

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

要点是,只有 fd2 可以关闭,并且仅当 fd2 != fd1 并且 fd2 之前已打开。 fd1 在任何情况下都不会关闭。

<小时/>

对于您的代码,我无法真正遵循您的逻辑。特别是,使用 parent_fdclient_fd 作为 pipe_list[] 的索引对我来说似乎很可疑。此外,该函数应返回文件句柄以及子进程 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;
}

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

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 $$'

如您所见,上面的代码相当复杂——总的来说,它只是一个 popen() 模拟,但功能较少。

但是,它做所有事情都非常仔细(除了可能将执行失败的原因从子级传递给父级 - 我只是假设写入成功)。因此,作为实际多进程程序中有多少细节(以及至少一种处理这些细节的方法)的示例,它可能很有用。当然,标准 POSIX.1 多么有用 popen()是。

关于c - 如何将子描述符复制到 STDOUT_FILENO 和 STDERR_FILENO,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43295721/

相关文章:

c++ - 函数参数中的按位或 (|)

c - 为什么我不能从 Golang 中正确读取 C 常量?

c - 是否可以返回 (*ptr)[2] 类型?

c - 如何比较C中两个不同的 union

c - 如何从文件重定向中获取命令?

c - 如何在C>中生成8位随机数

c - GTK 状态栏如何工作?我的代码有什么问题?

c++ - ffmpeg命令行的c/c++程序

地穴类型识别/etc/shadow

C - 检查字符串是否相等