C 在fork()调用';拉波本

C 在fork()调用';拉波本,c,linux,C,Linux,在我的系统上执行此操作时: FILE* pipe = popen("something_that_doesnt_exist", "r"); printf("pipe: %p\n", pipe); 我得到了管道的有效指针,尽管该程序不存在。我想编写一个popen,它可以检测到这一点,并返回一个指示启动失败的空指针。但是,我不知道如何在保持/bin/sh调用进行解释的同时实现这一点。是否有人知道我如何检查呼叫的返回状态,如: execl("/bin/sh", "sh", "-c", "someth

在我的系统上执行此操作时:

FILE* pipe = popen("something_that_doesnt_exist", "r");
printf("pipe: %p\n", pipe);
我得到了管道的有效指针,尽管该程序不存在。我想编写一个popen,它可以检测到这一点,并返回一个指示启动失败的空指针。但是,我不知道如何在保持/bin/sh调用进行解释的同时实现这一点。是否有人知道我如何检查呼叫的返回状态,如:

execl("/bin/sh", "sh", "-c", "something_that_doesnt_exist");
#包括
#包括
#包括
int main()
{
int fd=open(“/dev/null”,仅限O_wr);
/*备份*/
int f1=dup(1);
int f2=dup(2);
dup2(fd,1);
dup2(fd,2);
int res=系统(“不存在的东西”);
/*恢复*/
dup2(f1,1);
dup2(f2,2);
FILE*pipe=popen(“不存在的东西”,“r”);
如果(res!=0)
{
管道=空;
}
}

重定向stdout和stderr以避免未接受的输出。

如果popen()将成功执行命令,则无法判断。这是因为进程首先调用fork(),因此将始终创建管道和子进程。但是,如果fork()之后的execv()调用失败,那么子进程将死亡,父进程将无法判断这是由execv()失败引起的,还是您希望在没有任何输出的情况下刚刚完成的命令引起的。

如果您的进程没有其他子进程,那么您可以使用waitpid

int stat;
File* fp = popen("something_that_doesnt_exist", "r");
waitpid(-1, &stat, 0);

然后您可以确定stat的值,如果popen成功,stat=0。对这种方式不是很确定,需要有人确认才能这样做,你需要使用低级设施。您需要创建一个额外的管道,即close on exec,当exec失败时,子级将使用该管道编写错误代码

由于exec上的管道是关闭的,因此内核将在新二进制文件开始执行时关闭管道。(我们实际上不知道该命令是否正在运行;我们只知道exec没有失败。因此,不要假设关闭的管道意味着该命令已经在运行。它只意味着它还没有失败。)

父进程关闭不必要的管道端点,并从控制管道读取数据。如果读取成功,则子进程无法执行命令,并且读取的数据描述了错误。如果管道关闭(读取返回0),命令执行将开始(禁止执行时没有错误)

之后,我们可以像往常一样继续从管道中读取数据。当子进程关闭管道时,我们应该使用

考虑下面的示例程序。它执行在命令行上指定的命令——如果您想要与
system()
popen()相同的行为,请使用
sh-c'command'
。(即,
pathname==“sh”
argv=={“sh”、“-c”、“command”、NULL}
) 它逐个字符读取命令的输出,并对它们进行计数,直到子命令结束(通过关闭管道)。之后,我们获取子进程,并报告状态。 如果命令无法执行,也会报告原因。(由于非可执行文件报告为
enoint
(“没有这样的文件或目录”),因此
exec\u suboc()
将该情况修改为
eaprocess
(“权限被拒绝”)

并运行例如

./example date
./example ./example date -u
./example /bin/sh -c 'date | tr A-Za-z a-zA-Z'

pclose()
为您提供所需的退出状态。这还不够好吗?那不行,因为我想在决定是否关闭管道之前需要状态。首先,如果要调用命令,请不要使用
sh-c
。只有在使用POSIX外壳特性(如替换或作业控制)时才需要它。其次,为了从子进程检索
exec*()
状态,可以使用额外的关闭exec管道,该管道仅在
exec*()
失败时使用。我下面的回答详细说明了这种方法。如果你需要
sh
shell行为,那么你可以将你的
不存在的东西
包装在错误报告shell脚本片段中,而你不需要我的答案,只需要POSIX shell脚本。不幸的是,在这种情况下,我会有很多孩子,我不能阻止和等待。我喜欢它,但不幸的是,我的命令要么是长时间运行的,要么不是幂等的,所以我不能真正运行它们两次……不幸的是,我认为我需要posix shell语义来完成我的工作。我正在编写一个库,它提供了超级popen功能,包括可以有多个读卡器的命名管道。在linux中似乎真的没有什么好方法可以做到这一点,这既令人惊讶又令人失望。@gct:别傻了:)问题是“真的没有好方法可以做到这一点”,因为您将自己限制在语义上,不允许为基础问题提供健壮、有用的解决方案。例如,考虑Python模块,它旨在解决潜在问题的子集。如果您想要一个具有多个管道(甚至套接字)的类似popen的设施来互连进程(包括父进程),请使用结构来描述每个进程,而不是字符串!然后您也可以轻松地获取所有错误消息。
#define  _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <string.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>

int reap_subproc(pid_t pid)
{
    int   status;
    pid_t p;

    if (pid < 1) {
        errno = EINVAL;
        return -1;
    }

    do {
        status = 0;
        p = waitpid(pid, &status, 0);
    } while (p == -1 && errno == EINTR);
    if (p == -1)
        return -1;

    errno = 0;
    return status;
}

FILE *exec_subproc(pid_t *pidptr, const char *pathname, char **argv)
{
    char    buffer[1];
    int     datafd[2], controlfd[2], result;
    FILE   *out;
    ssize_t n;
    pid_t   pid, p;

    if (pidptr)
        *pidptr = (pid_t)0;

    if (!pidptr || !pathname || !*pathname || !argv || !argv[0]) {
        errno = EINVAL;
        return NULL;
    }

    if (pipe(datafd) == -1)
        return NULL;
    if (pipe(controlfd) == -1) {
        const int saved_errno = errno;
        close(datafd[0]);
        close(datafd[1]);
        errno = saved_errno;
        return NULL;
    }

    if (fcntl(datafd[0], F_SETFD, FD_CLOEXEC) == -1 ||
        fcntl(controlfd[1], F_SETFD, FD_CLOEXEC) == -1) {
        const int saved_errno = errno;
        close(datafd[0]);
        close(datafd[1]);
        close(controlfd[0]);
        close(controlfd[1]);
        errno = saved_errno;
        return NULL;
    }

    pid = fork();
    if (pid == (pid_t)-1) {
        const int saved_errno = errno;
        close(datafd[0]);
        close(datafd[1]);
        close(controlfd[0]);
        close(controlfd[1]);
        errno = saved_errno;
        return NULL;
    }

    if (!pid) {
        /* Child process. */

        close(datafd[0]);
        close(controlfd[0]);

        if (datafd[1] != STDOUT_FILENO) {
            do {
                result = dup2(datafd[1], STDOUT_FILENO);
            } while (result == -1 && errno == EINTR);
            if (result == -1) {
                buffer[0] = errno;
                close(datafd[1]);
                do {
                    n = write(controlfd[1], buffer, 1);
                } while (n == -1 && errno == EINTR);
                exit(127);
            }
            close(datafd[1]);
        }

        if (pathname[0] == '/')
            execv(pathname, argv);
        else
            execvp(pathname, argv);

        buffer[0] = errno;
        close(datafd[1]);

        /* In case it exists, we return EACCES instead of ENOENT. */
        if (buffer[0] == ENOENT)
            if (access(pathname, R_OK) == 0)
                buffer[0] = EACCES;

        do {
            n = write(controlfd[1], buffer, 1);
        } while (n == -1 && errno == EINTR);
        exit(127);
    }

    *pidptr = pid;

    close(datafd[1]);
    close(controlfd[1]);

    do {
        n = read(controlfd[0], buffer, 1);
    } while (n == -1 && errno == EINTR);
    if (n == -1) {
        close(datafd[0]);
        close(controlfd[0]);
        kill(pid, SIGKILL);
        do {
            p = waitpid(pid, NULL, 0);
        } while (p == (pid_t)-1 && errno == EINTR);
        errno = EIO;
        return NULL;
    } else
    if (n == 1) {
        close(datafd[0]);
        close(controlfd[0]);
        do {
            p = waitpid(pid, NULL, 0);
        } while (p == (pid_t)-1 && errno == EINTR);
        errno = (int)buffer[0];
        return NULL;
    }

    close(controlfd[0]);

    out = fdopen(datafd[0], "r");
    if (!out) {
        close(datafd[0]);
        kill(pid, SIGKILL);
        do {
            p = waitpid(pid, NULL, 0);
        } while (p == (pid_t)-1 && errno == EINTR);
        errno = EIO;
        return NULL;
    }

    errno = 0;
    return out;
}

int main(int argc, char *argv[])
{
    FILE   *cmd;
    pid_t   pid;
    int     c;
    unsigned long bytes = 0UL;

    if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
        fprintf(stderr, "       %s command [ arguments ... ]\n", argv[0]);
        fprintf(stderr, "\n");
        return EXIT_SUCCESS;
    }

    cmd = exec_subproc(&pid, argv[1], argv + 1);
    if (!cmd) {
        fprintf(stderr, "%s: %s.\n", argv[1], strerror(errno));
        return EXIT_FAILURE;
    }

    while ((c = getc(cmd)) != EOF) {
        bytes++;
        putchar(c);
    }
    fflush(stdout);

    fclose(cmd);
    c = reap_subproc(pid);
    if (errno) {
        fprintf(stderr, "%s: %s.\n", argv[1], strerror(errno));
        return EXIT_FAILURE;
    }

    if (WIFEXITED(c) && !WEXITSTATUS(c)) {
        fprintf(stderr, "%s: %lu bytes; success.\n", argv[1], bytes);
        return 0;
    }
    if (WIFEXITED(c)) {
        fprintf(stderr, "%s: %lu bytes; failed (exit status %d)\n", argv[1], bytes, WEXITSTATUS(c));
        return WEXITSTATUS(c);
    }
    if (WIFSIGNALED(c)) {
        fprintf(stderr, "%s: %lu bytes; killed by signal %d (%s)\n", argv[1], bytes, WTERMSIG(c), strsignal(WTERMSIG(c)));
        return 128 + WTERMSIG(c);
    }

    fprintf(stderr, "%s: %lu bytes; child lost.\n", argv[1], bytes);
    return EXIT_FAILURE;
}
gcc -Wall -Wextra -O2 example.c -o example
./example date
./example ./example date -u
./example /bin/sh -c 'date | tr A-Za-z a-zA-Z'