我正在尝试实现GNU popen
库但遇到dup2
问题。我想将子管道复制到STDOUT_FILENO
和STDERR_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;
}
答案 0 :(得分:1)
致电dup2(fd1, fd2)
时:
如果fd1
不是开放文件描述符,则该函数将返回-1
并将errno
设置为EBADF
。 fd2
未关闭。
如果fd2
超出允许范围,或者如果它未打开但进程已经具有最大打开文件描述符数(RLIMIT_NOFILE
),则该函数将返回{{ 1}}并将-1
设置为errno
。
如果出现任何其他问题,该函数将返回EBADF
并将-1
设置为某个错误代码。在这种情况下,未指定errno
是否未被触及或关闭。
以下其他案例假设操作成功。
如果fd2
,该函数将返回fd1 == fd2
(与fd1
相同)。
如果d2
不是开放描述符,则fd2
与其重复。然后,这两个描述符引用相同的文件描述(内核保存的文件位置等内容)。
如果fd1
是一个打开的文件描述符,则在fd2
复制到它时它会被关闭。结束是无声,这意味着由于结束而导致的任何错误都会被忽略。
重点是,只有fd1
和fd2
之前打开时才会关闭fd2 != fd1
。在任何情况下fd2
都不会关闭。
关于您的代码,我无法真正遵循您的逻辑。特别是,使用fd1
和parent_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()
是多么有用。