GCC в HP-UX, множество проблем с poll (), pipe() и файлами

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

  •  06-07-2019
  •  | 
  •  

Вопрос

У меня возникли большие проблемы с созданием регистратора "посредника" - цель состоит в том, чтобы разместить его на пути над элементом в /usr /bin и фиксировать все, что поступает в приложение и из него.(Стороннее приложение Black box по какой-то причине не работает с 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 ВЕЧЕРА

После исправлений я попытался запустить "баннер" через эту программу, и вот результат, который я получаю...

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() возвращает просто отлично;это результат опроса [1].revents, который вернулся с ошибкой, что означает, что childOutPipe[0] опрошен как имеющий ошибку.logChar(), насколько я могу судить, никогда не вызывается.

Я собираюсь попробовать разделить функцию poll() на два разных вызова.


Хорошо, в тот момент, когда я выполняю функцию poll() - даже в stdin, которая не возвращается с сообщением об ошибке, - это лишает меня возможности записывать данные в файл журнала.Кроме того, я обнаружил, что цикл while () выполняется несколько раз, прежде чем выходной опрос возвращается с ошибкой в канале.Я все больше убеждаюсь, что poll() - это просто гиблое дело.
Каждая попытка записи в logFile завершается неудачей после poll(), даже при успешном poll(), для errno установлено значение "Неверный номер файла".Этого действительно не должно было происходить.Честно говоря, я не понимаю, как это повлияет на мой дескриптор файла.
Ладно, значит, очевидно, что я идиот.Спасибо, что разъяснили мне суть;Я предполагал, что nfds - это размер в байтах, а не размер массива.Это исправлено, и вуаля!Это больше не убивает дескриптор моего файла журнала.

Это было полезно?

Решение

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

первая (но незначительная) Проблема

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-разрядных).Вам нужен размер массива опроса (который равен 2).Вот почему весь ад вырывается на свободу;система просматривает мусор и приходит в замешательство.

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

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

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

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

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

Исправить:

    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() "повреждает" набор опрошенных дескрипторов, и вам приходится перестраивать его в каждом цикле. (Проверив страницу руководства по адресу Открытая Группа, похоже , что poll() не имеет тех проблем, которые select() страдает от.) Это, безусловно, проблема, связанная с select() системный вызов.

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

Аналогичные комментарии могут быть применимы и к родительскому элементу.

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

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


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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top