HP-UX上のGCC、多くのpoll()、pipe()、およびファイルの問題

StackOverflow https://stackoverflow.com/questions/1029910

  •  06-07-2019
  •  | 
  •  

質問

「ミドルマン」ロガーの構築に多くの問題があります-意図は、/ 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;
}


編集6/23/09 12:20 PM

修正後、このプログラムで「バナー」を実行しようとしましたが、出力は次のとおりです...

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()は単に失われた原因であると私はますます確信しています。
poll()が成功した後でも、logFileへの書き込みはすべて失敗し、errnoが<!> quot; Bad file number <!> quot;に設定されます。これは実際には発生しないはずです。正直なところ、ファイルハンドルにどのように影響するかわかりません。
さて、どうやら私はバカです。私をまっすぐにしてくれてありがとう。 nfdsは配列サイズではなくバイトサイズであると想定していました。それは修正され、出来上がりです! logFileハンドルがそれ以上殺されない。

役に立ちましたか?

解決

実際の問題:

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

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

標準入力の0をFILENO_STDIN<fcntl.h>に置き換えることができます。

2番目の(主要な)問題

    nfds_t nfds = sizeof(pollArray);

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

コメントのアドレス指定

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

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

この名前は、薄暗い遠い過去のBASICの使用に似ています。私が見た他の名前はNELEMSまたはARRAY_SIZEまたはDIMENSION(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;
}

2番目のケースでは、ヘッダーリストに「stderr」を追加します。 ENOTTYの値を保存すると、関数呼び出しによる変更に対して値が保持されますが、関数(システムコール)が失敗した場合にのみselect()を確実にテストできます。関数呼び出しが成功しても、sortがゼロ以外になる場合があります。 (たとえば、一部のシステムでは、<=>が端末に送信されない場合、I / O呼び出し後の<=>の値は、呼び出し全体が成功したとしても<=>です。)


過去の反mination

問題の可能性に関する事前の考え。ここにはまだ有用な情報があると思います。

あなたの問題は、<=>ポーリングされた記述子のセットを「破損」しているため、各ループで再構築する必要があると思われます。グループを開く、<=>には<=>の問題はないようです。)これは、関連する<=>システムコールの問題です。

子コードは、必要なときにすべてのファイル記述子を閉じていません-1つの 'close() `をコメントアウトし、別の行方不明が完全にあります。子が標準入出力へのパイプの接続を終了したとき、重複していないファイル記述子を開いたままにしたくないでしょう。プロセスはEOFを適切に検出できません。

親でも同様のコメントが適用される場合があります。

また、送信プロセスは、子の標準出力に何かが現れる前に、データの複数のパケットを子に送信する必要があるかもしれないことに注意してください。極端な場合として、「<=>」を検討してください。出力を生成する前にすべてのデータを読み取ります。 方向転換コードが心配なので、私はそれ自体は、方向の切り替えは無害です-前回から反対方向に書き込みを開始すると、単に新しい方向を書き込みます。

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


同様の問題を解決したのは、監視を行う2つのプロセスを使用することです。1つは標準入力用、もう1つは標準出力用、または同等のものです。つまり、<=>(または<=>)を使用する必要はまったくありません。標準入力を処理するプロセスは、詳細な情報を待機してブロックします。何かが到着すると、それをログに記録し、それを子の標準入力に書き込みます。標準出力を処理するプロセスについても同様です。

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

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top