0

「ミドルマン」ロガーの構築に多くの問題があります。これは、/ usr / bin内のアイテムの上のパスに配置し、アプリケーションとの間でやり取りされるすべてのものをキャプチャすることを目的としています。(ブラックボックスのサードパーティアプリが何らかの理由でFTPに失敗しています。)実行されると、仲介者はフォークし、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;
}


2009年6月23日12:20PMを編集

修正後、このプログラムで「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].reventsです。これは、childOutPipe[0]がエラーとしてポーリングされたことを意味します。logChar()は、私が知る限り、呼び出されることはありません。

poll()を2つの異なる呼び出しに分割してみます。


さて、私がpoll()を実行した瞬間、エラーメッセージが返されないstdinでも、logFileに書き込む機能が失われます。また、パイプでエラーが発生して出力ポーリングが返される前に、while()ループが数回実行されることを発見しました。私は、poll()が単に失われた原因であるとますます確信するようになっています。
errnoが「不正なファイル番号」に設定されているpoll()が成功した場合でも、logFileへの書き込みはすべて失敗します。これは実際には起こらないはずです。正直なところ、ファイルハンドルにどのような影響があるかわかりません。
さて、どうやら私はバカです。私をまっすぐにしてくれてありがとう。nfdsは配列サイズではなく、バイトサイズであると想定していました。それは修正されました、そして出来上がり!logFileハンドルを強制終了することはもうありません。

4

1 に答える 1

3

本当の問題:

最初の(しかしマイナーな)問題

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

'struct pollfd'の順序と内容について、不当な仮定をしている可能性があります。すべての標準では、(少なくとも)3つのメンバーが含まれているとされています。それらが現れる順序については何も述べていません。

ヘッダーは、少なくとも次のメンバーを含む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標準入力の0をfromに置き換えることができます<fcntl.h>

2番目の(主要な)問題

    nfds_t nfds = sizeof(pollArray);

ポーリング配列のサイズはおそらく16(バイト)です-すべてではありませんがほとんどのマシン(32ビットおよび64ビット)で。ポーリング配列の次元(2)が必要です。これが、すべての地獄が解き放たれる理由です。システムはゴミを見て混乱しています。

コメントへの対応

ローカルファイルまたは関数で定義された配列の次元を見つけるには(ただし、関数に渡された配列パラメーターや別のファイルで定義された配列ではない)、マクロのバリアントを使用します。

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

この名前は、薄暗い遠い過去のBASICの使用を思い起こさせます。私が見た他の名前はNELEMSorARRAY_SIZEまたはDIMENSION(Fortran IVを思い起こさせる)であり、他にもたくさんあると確信しています。

何が起こっているのかというと、2に設定していないためnfds、システムコールは実際のstruct pollfd配列の後にデータを読み取り、ではないものの先頭または末尾を作成しようとしていstruct pollfdます。revents特に、それはおそらく配列内の行のフィールドであるとあなたが言ったことに書き込んでいますstruct pollfdが、実際のスペースはログFILE *であるため、完全に台無しになっています。他のローカル変数についても同様です。言い換えれば、スタックバッファオーバーフロー(別名スタックオーバーフロー)があります。これは、かすかに馴染みのある名前です。しかし、あなたがそれをプログラムしたので、それは起こっています。

修理:

    nfds_t nfds = DIM(pollArray);

3番目(中級)の問題

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

2番目のケースでは#include <errno.h>、ヘッダーリストに''を追加します。の値を保存すると、関数呼び出しによる変更から保護されますが、関数(システムコール)が失敗した場合にerrnoのみ確実にテストできます。成功した関数呼び出しでさえ、ゼロ以外errnoを残す可能性があります。errno(たとえば、一部のシステムでstderrは、端末に接続しない場合、呼び出し全体が成功したとしても、errnoI / O呼び出し後の値はになります。)ENOTTY


以前の反芻

何が問題になるかについてのいくつかの事前の考え。ここにはまだ役立つ情報があると思います。

あなたの問題はpoll()、ポーリングされた記述子のセットに「損傷を与える」ことであり、ループごとにそれを再構築する必要があると思います。( Open Groupのマニュアルページを確認したところpoll()、問題はないようですselect()。)これは確かに関連するselect()システムコールの問題です。

子コードは、必要なときにすべてのファイル記述子を閉じていません。1つの'close()`をコメントアウトしていて、もう1つが完全に欠落しています。子がパイプを標準の入力および出力に接続し終えたときに、複製されていないファイル記述子を開いたままにしたくない場合。プロセスはEOFを正しく検出できません。

親にも同様のコメントが当てはまる場合があります。

また、子の標準出力に何かが表示される前に、送信プロセスで複数のデータパケットを子に送信する必要がある場合があることに注意してください。極端な場合として、' sort'を考えてみましょう。出力を生成する前にすべてのデータを読み取ります。 したがって、方向切り替えコードについては、それが何をするのかを完全には理解していませんが、心配しています。それ自体、方向の切り替えは無害です。前回とは逆の方向に書き込みを開始すると、新しい方向を書き込むだけです。

さらに深刻なことに、単一文字の読み取りと書き込みを使用しないでください。賢明なサイズのバッファがいっぱいです。実用的なサイズは、256から8192の間のほぼ2の累乗です。他のサイズを自由に選択できます(パイプバッファのサイズを選択するのが適切な場合があります)。一度に複数の文字を処理すると、パフォーマンスが大幅に向上します。


私が同様の問題を解決した方法は、2つのプロセスで監視を行うことです。1つは標準入力用で、もう1つは標準出力用です。poll()これは、私が(またはselect())をまったく使用する必要がないことを意味します。標準入力を処理するプロセスは、読み取りを実行し、詳細情報を待機します。何かが到着すると、それをログに記録し、子の標準入力に書き込みます。同様に、標準出力を処理するプロセスの場合。

必要に応じて、パイプで機能するコードを掘り下げることができます(私のプロファイルを参照してください)。私は1、2年前にそれを調べました(うーん、2007年に再コンパイルしましたが、実際には2005年に最後の編集を行いました)、それはまだ正常に機能していました(1989年頃に書かれました)。パイプの代わりにソケットで動作するコードもあります。彼らはあなたの要件に合うようにいくらかの適応を必要とするでしょう。それらはかなり特殊化されていました(特に、パイプバージョンはクライアントサーバーデータベースプロトコルを認識しており、情報の完全なパケットを処理しようとします)。

于 2009-06-23T00:13:53.637 に答える