GCC в HP-UX, множество проблем с poll (), pipe () и файлами - PullRequest
0 голосов
/ 23 июня 2009

У меня много проблем с созданием логгера «посредника» - намерение поместить его в путь над элементом в / usr / bin и записать все, что идет в приложение и из приложения. (Стороннее приложение черного ящика по какой-то причине не работает по протоколу FTP.) После запуска посредник разветвляется, перенаправляет stdout и stdin в / из каналов, которыми родительский контроль управляет, а затем выполняет программу в / usr / bin. (Закодировано; да, я знаю, я плохой.)

Однако, как только я запускаю 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;
}

<ч /> РЕДАКТИРОВАТЬ 23.06.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 () на два разных вызова. <ч /> Хорошо, в тот момент, когда я опрашиваю () - даже на stdin, который не возвращается с сообщением об ошибке - это убивает мою возможность записи в файл журнала. Кроме того, я обнаружил, что цикл while () выполняется несколько раз, прежде чем выходной опрос возвращается с ошибкой в ​​канале. Я все больше убеждаюсь, что poll () - просто безнадежное дело. <ч /> Каждая попытка записи в logFile завершается неудачей после poll (), даже успешного poll (), с errno, установленным в «Bad file number». Это действительно не должно происходить. Честно говоря, я не вижу, как это повлияет на мой дескриптор файла. <ч /> Ладно, видимо я идиот. Спасибо, что поправил меня; Я предполагал, что nfds был размером в байтах, а не размером массива. Это исправлено, и вуаля! Это больше не убивает мой дескриптор logFile.

1 Ответ

3 голосов
/ 23 июня 2009

Реальные проблемы:

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 для стандартного ввода на FILENO_STDIN из <fcntl.h>.

2-я (главная) задача

    nfds_t nfds = sizeof(pollArray);

Размер массива опроса, вероятно, составляет 16 (байтов) - на большинстве, но не на всех машинах (32-битных и 64-битных). Вам нужно измерение массива poll (которое равно 2). Вот почему весь ад распадается; система смотрит на мусор и запутывается.

Обращаясь к комментарию :

Чтобы найти размерность массива, определенного в локальном файле или функции (но не параметр массива, переданный в функцию, или массив, определенный в другом файле), используйте вариант макроса:

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

Это имя восходит к использованию Бейсика в смутном, далеком прошлом; другие имена, которые я видел, были NELEMS или ARRAY_SIZE или DIMENSION (возвращаясь к Фортрану IV), и я уверен, что есть много других.

Что происходит, так как вы не устанавливаете nfds в 2, системный вызов читает данные после фактического массива struct pollfd и пытается создать заголовок или хвост материала, который не является struct pollfd. В частности, он, вероятно, записывает в то, что вы сказали, что это поле revents строки в массиве struct pollfd, но фактическим пространством является журнал FILE *, поэтому он полностью облажался. Аналогично для других локальных переменных. Другими словами, у вас есть переполнение буфера стека - он же переполнение стека, имя, которое должно быть слегка знакомым. Но это происходит потому, что вы это запрограммировали.

Fix:

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

Во втором случае вы добавили бы '#include <errno.h>' в список заголовков. Сохранение значения errno сохраняет его от изменения при вызове функций, но вы можете надежно проверить errno только в случае сбоя функции (системного вызова). Даже успешные вызовы функций могут оставить errno ненулевым. (Например, в некоторых системах, если stderr не идет к терминалу, значение errno после вызова ввода / вывода равно ENOTTY, даже если вызов в целом завершился успешно.)


Предыдущие размышления

Некоторые предварительные мысли о том, в чем может быть проблема; Я думаю, что здесь есть еще некоторая полезная информация.

Я подозреваю, что ваша проблема в том, что poll() «повреждает» набор опрошенных дескрипторов, и вы должны перестраивать его в каждом цикле. (После проверки страницы руководства в Open Group , похоже, что poll() не имеет проблем, от которых select() страдает.) Это, безусловно, проблема с системным вызовом select().

Ваш дочерний код не закрывает все файловые дескрипторы, когда это необходимо - вы закомментировали один 'close ()', а другого вообще не хватает. Когда дочерний процесс завершил подключение каналов к стандартному вводу и выводу, вы не хотите, чтобы дескрипторы файлов не были по-прежнему открыты; процессы не могут правильно определить EOF.

Подобные комментарии могут относиться к родителю.

Также обратите внимание, что процессу отправки может понадобиться отправить несколько пакетов данных дочернему элементу, прежде чем что-либо появится в стандартном выводе дочернего элемента. В качестве крайнего случая рассмотрим 'sort'; который читает все свои данные перед генерацией любого вывода. Я беспокоюсь о коде переключения направления, поэтому, хотя я не полностью усвоил то, что он делает. Само по себе переключение направления безвредно - оно просто записывает новое направление, когда начинает писать в противоположном направлении. с прошлого раза.

Более серьезно, не используйте односимвольные операции чтения и записи; читать буферы разумного размера. Разумный размер может быть почти любой степенью двойки от 256 до 8192; Вы можете выбрать другие размеры по своему усмотрению (размер буфера канала может быть хорошим выбором). Обработка нескольких символов одновременно значительно улучшит производительность.


Я решил аналогичные проблемы, используя два процесса, выполняющих мониторинг: один для стандартного ввода, а другой для стандартного вывода - или эквиваленты. Это означает, что мне вообще не нужно использовать poll() (или select()). Процесс, обрабатывающий стандартный ввод, читает и блокирует ожидание дополнительной информации; когда что-то прибывает, это регистрирует это и записывает это в стандартный ввод childs. Аналогично для процесса обработки стандартного вывода.

Я могу выкопать код, который работает с каналами, если вам это нужно (см. Мой профиль). Я посмотрел на него год или два назад (хммм; последние изменения фактически были в 2005 году, хотя я перекомпилировал его в 2007 году), и он все еще был в рабочем состоянии (он был написан около 1989 года). У меня также есть код, который работает на сокетах вместо каналов. Им потребуется некоторая адаптация, чтобы удовлетворить ваши требования; они были довольно специализированными (и, в частности, версия канала знает о протоколе клиент-серверной базы данных и пытается обрабатывать полные пакеты информации).

...