【问题标题】:GCC on HP-UX, lots of poll(), pipe(), and file issuesHP-UX 上的 GCC,大量 poll()、pipe() 和文件问题
【发布时间】:2012-08-06 04:30:57
【问题描述】:

我在构建“中间人”记录器时遇到了很多麻烦 - 目的是将其放置在 /usr/bin 中项目上方的路径上,并捕获进出应用程序的所有内容。 (黑盒第 3 方应用程序由于某种原因无法通过 FTP。)一旦运行,中间人将分叉,将标准输出和标准输入重定向到/从父级控制的管道,然后执行 /usr/bin 中的程序。 (硬编码;是的,我知道,我很糟糕。)

但是,一旦我运行 poll(),事情就会变得很奇怪。我丢失了我的日志文件的句柄,孩子的输出管道上的投票引发了错误,猫和狗开始生活在一起,等等。

有人能解释一下吗?

这是我目前拥有的... 有问题的 poll() 标有非缩进 cmets 以便于定位。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <poll.h>
#include <time.h>
#include <sys/types.h>
#include <fcntl.h>

#define MAX_STR_LEN 1024
static int directionFlag; /* 0 = input, 1 = output */
static int eofFlag;

/* Splits the next char from the stream inFile, with extra
information logged if directionFlag swaps */
void logChar(int inFilDes, int outFilDes, FILE *logFile, int direction)
{
    char inChar = 0;
    if(read(inFilDes, &inChar, sizeof(char)) > 0)
    {

        if(direction != directionFlag)
        {
            directionFlag = direction;
            if(direction)
            {
                fprintf(logFile, "\nOUTPUT: ");
            } else {
                fprintf(logFile, "\nINPUT: ");
            }
        }

        write(outFilDes, &inChar, sizeof(char));
        fputc(inChar, stderr);
        fputc(inChar, logFile);
    } else {
        eofFlag = 1;
    }
    return;
}

int main(int argc, char* argv[])
{
    pid_t pid;

    int childInPipe[2];
    int childOutPipe[2];

    eofFlag = 0;

    /* [0] is input, [1] is output*/

    if(pipe(childInPipe) < 0 || pipe(childOutPipe) < 0) {
        fprintf(stderr,"Pipe error; aborting\n");
            exit(1);
    }

    if((pid = fork()) == -1){
        fprintf(stderr,"Fork error; aborting\n");
        exit(1);
    }

    if(pid)
    {
        /*Parent process*/

        int i;
        int errcode;
        time_t rawtime;
        struct tm * timeinfo;
        time(&rawtime);
        timeinfo=localtime(&rawtime);

        struct pollfd pollArray[2] = {
            { .fd = 0, .events = POLLIN, .revents = 0 },
            { .fd = childOutPipe[0], .events = POLLIN, .revents = 0 }
        };
        /* Yet again, 0 = input, 1 = output */

        nfds_t nfds = sizeof(struct pollfd[2]);

        close(childInPipe[0]);
        close(childOutPipe[1]);

        /* We don't want to change around the streams for this one,
        as we will be logging everything - and I do mean everything */

        FILE *logFile;
        if(!(logFile = fopen("/opt/middleman/logfile.txt", "a"))) {
            fprintf(stderr, "fopen fail on /opt/middleman/logfile.txt\n");
            exit(1);
        }

        fprintf(logFile, "Commandline: ");

        for(i=0; i < argc; i++)
        {
            fprintf(logFile, "%s ", argv[i]);
        }
        fprintf(logFile, "\nTIMESTAMP: %s\n", asctime(timeinfo));

        while(!eofFlag)
        {

// RIGHT HERE is where things go to pot
            errcode = poll(pollArray, nfds, 1);
// All following fprintf(logfile)s do nothing
            if(errcode < 0) {
                fprintf(stderr, "POLL returned with error %d!", errcode);
                eofFlag = 1;
            }
            if((pollArray[0].revents && POLLERR) & errno != EAGAIN ) {
                fprintf(stderr, "POLL on input has thrown an exception!\n");
                fprintf(stderr, "ERRNO value: %d\n", errno);
                fprintf(logFile, "POLL on input has thrown an exception!\n");
                eofFlag = 1;
            } else if(pollArray[0].revents && POLLIN) {
                logChar(pollArray[0].fd, childInPipe[1], logFile, 0);
            } else if((pollArray[1].revents && POLLERR) & errno != EAGAIN ) {
                fprintf(stderr, "POLL on output has thrown an exception!\n");
                fprintf(stderr, "ERRNO value: %d\n", errno);
                fprintf(logFile, "POLL on output has thrown an exception!\n");
                eofFlag = 1;
            } else if(pollArray[1].revents && POLLIN) {
                logChar(pollArray[1].fd, 1, logFile, 1);
            }

        }

        fclose(logFile);

    }
    else
    {
        /*Child process; switch streams and execute application*/
        int i;
        int catcherr = 0;
        char stmt[MAX_STR_LEN] = "/usr/bin/";

        close(childInPipe[1]);
        close(childOutPipe[0]);

        strcat(stmt, argv[0]);

        if(dup2(childInPipe[0],0) < 0) {
            fprintf(stderr, "dup2 threw error %d on childInPipe[0] to stdin!\n", errno);
        }
//      close(childInPipe[0]);

        if(dup2(childOutPipe[1],1) < 0)
        {
            fprintf(stderr, "dup2 threw error %d on childInPipe[1] to stdout!\n", errno);
        }

        /* Arguments need to be in a different format for execv */
        char* args[argc+1];
        for(i = 0; i < argc; i++)
        {
            args[i] = argv[i];
        }
        args[i] = (char *)0;

        fprintf(stderr, "Child setup complete, executing %s\n", stmt);
        fprintf(stdout, "Child setup complete, executing %s\n", stmt);

        if(execv(stmt, args) == -1) {
            fprintf(stderr, "execvP error!\n");
            exit(1);
        }
    }
    return 0;
}


编辑 09 年 6 月 23 日下午 12:20

修复后,我尝试通过该程序运行“横幅”,这是我得到的输出...

Child setup complete, executing /usr/bin/banner
POLL on output has thrown an exception!
ERRNO value: 0

日志文件有以下内容:

Commandline: banner testing 
TIMESTAMP: Tue Jun 23 11:21:00 2009

ERRNO 中有一个 0 的原因是因为 poll() 返回得很好;是 pollArray[1].revents 返回错误,这意味着 childOutPipe[0] 被轮询为有错误。 logChar(),据我所知,永远不会被调用。

我将尝试将 poll() 拆分为两个不同的调用。


好的,当我 poll() 时——即使在标准输入上,它也不会返回错误消息——它会扼杀我写入日志文件的能力。此外,我发现 while() 循环在输出轮询返回管道错误之前运行了几次。我越来越相信 poll() 只是一个失败的原因。
在 poll() 之后,每次写入 logFile 的尝试都会失败,即使是成功的 poll(),也会将 errno 设置为“错误文件号”。这真的不应该发生。老实说,我看不出它会如何影响我的文件句柄。
好吧,看来我是个白痴。谢谢你让我直截了当;我假设 nfds 是字节大小,而不是数组大小。这是固定的,瞧!它不再杀死我的 logFile 句柄。

【问题讨论】:

    标签: c pipe polling


    【解决方案1】:

    真正的问题:

    第一个(但很小的)问题

    struct pollfd pollArray[2] = {{0, POLLIN, 0}, {childOutPipe[0], POLLIN, 0}};
    

    您可能对“struct pollfd”的顺序和内容做出了毫无根据的假设。该标准所说的只是它包含(至少)三个成员;它没有说明它们出现的顺序。

    头部应定义 pollfd 结构,该结构应至少包括以下成员:

    int    fd       The following descriptor being polled. 
    short  events   The input event flags (see below). 
    short  revents  The output event flags (see below). 
    

    由于您使用的是 C99,因此请使用安全初始化表示法:

        struct pollfd pollArray[2] =
        {
            { .fd = 0,               .events = POLLIN, .revents = 0 },
            { .fd = childOutPipe[0], .events = POLLIN, .revents = 0 },
        };
    

    您可以将标准输入的 0 替换为来自 &lt;fcntl.h&gt;FILENO_STDIN

    第二个(主要)问题

        nfds_t nfds = sizeof(pollArray);
    

    poll 数组的大小可能是 16(字节)——在大多数但不是所有机器上(32 位和 64 位)。您需要 poll 数组的维度(即 2)。这就是为什么所有的地狱都崩溃了;系统正在查看垃圾并感到困惑。

    发表评论

    要查找在本地文件或函数中定义的数组的维度(但不是传递给函数的数组参数,也不是在另一个文件中定义的数组),请使用宏的变体:

    #define DIM(x) (sizeof(x)/sizeof(*(x)))
    

    这个名字让人回想起在遥远的昏暗过去使用 BASIC;我见过的其他名称是 NELEMSARRAY_SIZEDIMENSION(回到 Fortran IV),我相信还有很多其他名称。

    发生的情况是,因为您没有将 nfds 设置为 2,所以系统调用在实际 struct pollfd 数组之后读取数据,并尝试制作不是 struct pollfd 的内容的头部或尾部。特别是,它可能正在写入你所说的struct pollfd 数组中一行的revents 字段,但实际空间是日志FILE *,所以这完全搞砸了。对于其他局部变量也是如此。换句话说,你有一个堆栈缓冲区溢出——又名堆栈溢出,这个名字应该有点熟悉。但它正在发生,因为您对其进行了编程。

    修复:

        nfds_t nfds = DIM(pollArray);
    

    第三(中级)问题

       poll(pollArray, nfds, 1);
       if (errcode < 0) {
    

    poll() 的结果没有被保存,变量errcode 从来没有被赋值,但是你在之后立即检查这个值是什么。更正后的代码可能是:

    errcode = poll(pollArray, nfds, 1);
    if (errcode < 0)
    {
        fprintf(stderr, "POLL returned with error %d!\n", errcode);
        eofFlag = 1;
    }
    

    注意添加到错误消息中的换行符 - 你需要它。或者:

    if (poll(pollArray, nfds, 1) < 0)
    {
        int errnum = errno;
        fprintf(stderr, "POLL returned with error (%d: %s)\n",
                errnum, strerror(errnum));
        eofFlag = 1;
    }
    

    在第二种情况下,您需要将“#include &lt;errno.h&gt;”添加到标题列表中。保存 errno 的值可以防止函数调用对其进行更改 - 但您只能在函数(系统调用)失败时可靠地测试 errno。即使是成功的函数调用也可能使errno 非零。 (例如,在某些系统上,如果stderr 不去终端,则在I/O 调用之后errno 的值是ENOTTY,即使整个调用成功。)


    以前的沉思

    关于可能是什么问题的一些先前想法;我认为这里还有一些有用的信息。

    我怀疑你的问题是 poll()“损坏”了轮询描述符集,你必须在每个循环中重建它。(查看了 Open Group 的手册页,看来poll() 没有select() 遇到的问题。)这肯定是相关的select() 系统调用的问题。

    您的子代码没有在应该关闭所有文件描述符的时候关闭 - 您已经注释掉了一个 'close()` 并且完全缺少另一个。当孩子完成将管道连接到标准输入和输出时,您不希望未复制的文件描述符仍然打开;进程无法正确检测到 EOF。

    类似的 cmets 可能适用于父级。

    另外,请注意,发送进程可能需要向子进程发送多个数据包,然后子进程的标准输出中才会出现任何内容。作为极端情况,考虑'sort';在生成任何输出之前读取其所有数据。 我担心方向切换代码,因此,虽然我还没有完全理解它的作用。 就其本身而言,方向切换是无害的——它只是在开始写入相反的方向时写入新方向上次的方向。

    更严重的是,不要使用单字符读写;读取合理大小的缓冲区已满。合理的大小可能几乎是 256 到 8192 之间的 2 的任何幂;您可以随意选择其他大小(管道缓冲区的大小可能是一个不错的选择)。一次处理多个字符将大大提高性能。


    我解决类似问题的方法是让两个进程进行监控,一个用于标准输入,另一个用于标准输出 - 或等效的。这意味着我根本不需要使用poll()(或select())。处理标准输入的进程读取并阻塞等待更多信息;当有东西到达时,它会记录它并将其写入孩子的标准输入。处理标准输出的进程也是如此。

    如果您需要,我可以挖掘出适用于管道的代码(请参阅我的个人资料)。我在一两年前看过它(嗯;事实上,最后一次编辑是在 2005 年,尽管我在 2007 年重新编译了它)并且它仍然处于工作状态(它是在 1989 年左右编写的)。我也有适用于套接字而不是管道的代码。他们需要进行一些调整以适应您的要求;它们相当专业(尤其是管道版本,它知道客户端-服务器数据库协议并尝试处理完整的信息包)。

    【讨论】:

    • 天哪。当我昨天回家时,我在想这将是那些“迷失在以太中”的问题之一,然后我今天早上开始讨论这个问题!非常感谢;我会着手解决这些问题,然后告诉你进展如何。
    • 好吧...我已经尝试了几种不同的 sizeof 迭代,以及查找不同的实现 - 而 poll() 仍然让我失去了我的 logFile 句柄。在我 poll() 的那一刻,什么都不能写入日志文件;就像它过早地 fclose()d 一样。我想我可以重构这个东西以使用两个观察者线程,正如你提到的你过去所做的那样。至少这样我就不必再和这东西斗争了。
    • 稍后进行一些编辑,我上面的列表完全过时了...文件句柄不再丢失,这要感谢您指出我应该使用一个项目计数而不是字节计数 sizeof,但同样的问题仍然发生在我的从子轮询返回 POLLERR 时。我想知道管道寡妇是否因为程序终止而仍在等待从中写入数据。
    • 可能是时候写一个新问题了,只有当前问题,主题是“为什么 poll() 在这种情况下会返回 POLLERR?”。
    • 好建议。再次感谢您的帮助!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-02-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-02
    • 1970-01-01
    相关资源
    最近更新 更多