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

然而,一旦我运行poll(),事情变得奇怪。我丢失了我的日志文件的句柄,来自孩子的输出管道上的轮询引发了错误,猫和狗开始一起生活,等等。

任何人都可以对此有所了解吗?

以下是我目前所拥有的...有问题的poll()标有非缩进注释,以便于定位。

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


编辑6/23/09 12:20 PM

在修复之后,我试图通过这个程序运行'banner',这是我得到的输出......

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]。以及错误返回的事件,这意味着childOutPipe [0]被调查为有错误。据我所知,logChar()永远不会被调用。

我打算尝试将poll()分成两个不同的调用。


好吧,我在poll()的那一刻 - 甚至在没有返回错误信息的stdin上 - 它会杀死我写入logFile的能力。另外,我发现while()循环在输出轮询返回之前运行了几次,并且管道上有错误。我越来越相信poll()只是一个失败的原因。
在poll()之后,每次尝试写入logFile都会失败,即使是成功的poll(),也将errno设置为<!>“;错误的文件号<!>”;这确实不应该发生。老实说,我看不出它会如何影响我的文件句柄。
好吧,显然我是个白痴。感谢设置我直;我假设nfds是字节大小,而不是数组大小。这是固定的,瞧!它不再杀死我的logFile句柄了。

有帮助吗?

解决方案

真正的问题:

第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 },
    };

您可以使用FILENO_STDIN<fcntl.h>替换标准输入的0。

第二(主要)问题

    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数组之后读取数据,并试图制作不是revents的东西的头或尾。特别是,它可能写入你告诉它的是FILE *数组中行的poll()字段,但实际空间是日志errcode,因此完全搞砸了。其他局部变量也是如此。换句话说,你有一个堆栈缓冲区溢出 - 也就是Stack Overflow,一个应该非常熟悉的名称。但它正在发生,因为你编程了它。

修正:

    nfds_t nfds = DIM(pollArray);

第3(中级)问题

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

#include <errno.h>的结果未保存,并且变量errno永远不会分配值,但您会在之后立即检查该值。更正后的代码可能是:

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

在第二种情况下,您需要在标题列表中添加“stderr”。保存ENOTTY的值可以防止函数调用更改 - 但是只能在函数(系统调用)失败时可靠地测试select()。即使成功的函数调用也可能会sort非零。 (例如,在某些系统上,如果<=>未转到终端,则I / O调用后<=>的值为<=>,即使整个调用成功也是如此。)


以前的反刍

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

我怀疑你的问题是<=>'损坏'轮询描述符集,你必须在每个循环上重建它。(已经检查了 Open Group ,似乎<=>没有<=>遭受的问题。)这肯定是相关的<=>系统调用的问题。

你的子代码没有关闭所有的文件描述符 - 你已经注释掉一个'close()`并且还有另一个丢失了。当孩子将管道连接到标准输入和输出时,您不希望未重复的文件描述符仍然打开;这些过程无法正确检测EOF。

类似的评论可能适用于父母。

另外,请注意,在孩子的标准输出上出现任何内容之前,发送过程可能需要向孩子发送多个数据包。作为极端情况,请考虑'<=>';在生成任何输出之前读取其所有数据。 我担心方向切换代码,因此,虽然我并没有完全消化它的作用。本身,方向切换是无害的 - 它只是在它开始以与上次相反的方向书写时写出新的方向。

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


我解决类似问题的方法是让两个进程进行监控,一个用于标准输入,另一个用于标准输出 - 或等价物。这意味着我根本不需要使用<=>(或<=>)。处理标准输入的进程读取和阻塞等待更多信息;当某些东西到来时,它会记录它并将其写入childs标准输入。类似地,对于处理标准输出的过程。

如果需要,我可以挖出适用于管道的代码(参见我的个人资料)。我在一两年前看过它(嗯;实际上是2005年的最后一次编辑,虽然我在2007年重新编译了它)并且它仍然处于正常工作状态(它大约写于1989年)。我也有代码适用于套接字而不是管道。他们需要一些适应你的要求;它们非常专业(特别是管道版本,它知道客户端 - 服务器数据库协议,并尝试处理完整的信息包)。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top