Является ли вывод прочитанным из popen () ed FILE * завершенным до pclose ()? - PullRequest
0 голосов
/ 06 сентября 2018
Страница пользователя

pclose() гласит:

Функция pclose () ожидает завершения связанного процесса и возвращает состояние выхода команды, возвращенное wait4 (2).

Мне кажется, это означает, что если связанный FILE*, созданный popen(), был открыт с типом "r" для чтения вывода command, то вы не уверены, что вывод завершен, пока после звонка на pclose(). Но после pclose() закрытый FILE* обязательно должен быть недействительным, так как вы можете быть уверены, что прочитали весь вывод command?

Чтобы проиллюстрировать мой вопрос на примере, рассмотрим следующий код:

// main.cpp

#include <iostream>
#include <cstdio>
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <sys/wait.h>

int main( int argc, char* argv[] )
{
  FILE* fp = popen( "someExecutableThatTakesALongTime", "r" );
  if ( ! fp )
  {
    std::cout << "popen failed: " << errno << " " << strerror( errno )
              << std::endl;
    return 1;
  }

  char buf[512] = { 0 };
  fread( buf, sizeof buf, 1, fp );
  std::cout << buf << std::endl;

  // If we're only certain the output-producing process has terminated after the
  // following pclose(), how do we know the content retrieved above with fread()
  // is complete?
  int r = pclose( fp );

  // But if we wait until after the above pclose(), fp is invalid, so
  // there's nowhere from which we could retrieve the command's output anymore,
  // right?

  std::cout << "exit status: " << WEXITSTATUS( r ) << std::endl;

  return 0;
}

Мои вопросы, как указано выше: если мы уверены, что дочерний процесс, производящий выходные данные, завершился после pclose(), как мы узнаем, что содержимое, полученное с помощью fread(), завершено? Но если мы подождем до тех пор, пока pclose(), fp будет недействительным, то больше некуда будет извлекать вывод команды, верно?

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

Ответы [ 4 ]

0 голосов
/ 07 сентября 2018

TL; Краткий обзор DR: как мы узнаем, что содержимое, полученное с помощью fread (), завершено? - у нас есть EOF.

Вы получаете EOF, когда дочерний процесс закрывает свой конец канала. Это может произойти, когда он вызывает close явно или выходы. Ничто не может выйти из твоего конца трубы после этого. После получения EOF вы не знаете, завершился ли процесс, но вы точно знаете, что он никогда ничего не запишет в канал.

Позвонив по номеру pclose, вы закрываете конец канала и ожидаете завершения дочернего процесса. Когда pclose возвращается, вы знаете, что ребенок закончил.

Если вы позвоните pclose без получения EOF, и ребенок попытается записать материал в конец канала, он потерпит неудачу (фактически он получит SIGPIPE и, вероятно, умрет).

Здесь абсолютно нет места для ситуации с курицей и яйцом.

0 голосов
/ 06 сентября 2018

popen () - это просто ярлык для серии fork, dup2, execv, fdopen и т. Д. Он даст нам доступ к дочернему STDOUT, STDIN через потоковую операцию с файлами.

После popen ()родительский и дочерний процессы выполняются независимо.pclose () не является функцией «kill», она просто ждет завершения дочернего процесса.Поскольку это блокирующая функция, выходные данные, сгенерированные во время выполнения pclose (), могут быть потеряны.

Чтобы избежать потери этих данных, мы будем вызывать pclose () только тогда, когда мы знаем, что дочерний процесс уже завершен: a fgetsВызов () вернет NULL или fread () вернется из блокировки, общий поток достигнет конца, а EOF () вернет true.

Вот пример использования popen () с fread ().Эта функция возвращает -1, если выполняющийся процесс завершился неудачей, 0, если Ok.Дочерние выходные данные возвращаются в szResult.

int exec_command( const char * szCmd, std::string & szResult ){

    printf("Execute commande : [%s]\n", szCmd );

    FILE * pFile = popen( szCmd, "r");
    if(!pFile){
            printf("Execute commande : [%s] FAILED !\n", szCmd );
            return -1;
    }

    char buf[256];

    //check if the output stream is ended.
    while( !feof(pFile) ){

        //try to read 255 bytes from the stream, this operation is BLOCKING ...
        int nRead = fread(buf, 1, 255, pFile);

        //there are something or nothing to read because the stream is closed or the program catch an error signal
        if( nRead > 0 ){
            buf[nRead] = '\0';
            szResult += buf;
        }
    }

    //the child process is already terminated. Clean it up or we have an other zoombie in the process table.
    pclose(pFile); 

    printf("Exec command [%s] return : \n[%s]\n",  szCmd, szResult.c_str() );
    return 0;
}

Обратите внимание, что все операции с файлами в обратном потоке работают в режиме BLOCKING, поток открыт без флагов O_NONBLOCK.Fread () может быть заблокирован навсегда, когда дочерний процесс зависает и завершается, поэтому используйте popen () только с доверенной программой.

Чтобы получить больше контроля над дочерним процессом и избежать операции блокировки файлов, мы должны использоватьс помощью fork / vfork / execlv и т. д. измените атрибуты открытых каналов с помощью флагов O_NONBLOCK, время от времени используйте poll () или select (), чтобы определить, есть ли какие-либо данные, затем используйте функцию read () для чтения из канала.,

Периодически используйте waitpid () с WNOHANG, чтобы увидеть, был ли завершен дочерний процесс.

0 голосов
/ 07 сентября 2018

Я изучил пару вещей, продолжая исследовать эту проблему, и я думаю, что ответ на мой вопрос:

По существу: да, безопасно fread из FILE*, возвращенного popen до pclose. Предполагая, что буфер, заданный для fread, достаточно велик, вы не пропустите вывод, сгенерированный command, переданным popen.

Возвращаясь назад и тщательно обдумывая, что делает fread: он эффективно блокирует, пока (size * nmemb) байтов не будет прочитано или не будет обнаружен конец файла (или ошибка).

Благодаря C - трубе без использования popen , я лучше понимаю, что popen делает под капотом: он делает dup2, чтобы перенаправить его stdout на конец записи канала оно использует. Важно: он выполняет некоторую форму exec для выполнения указанного command в разветвленном процессе, , и после завершения этого дочернего процесса дескрипторы его открытых файлов, включая 1 (stdout), закрываются . То есть завершение указанного command - это условие, при котором дочерний процесс 'stdout закрывается.

Затем я вернулся и подумал, что на самом деле EOF в этом контексте. Вначале у меня возникло ошибочное и ошибочное впечатление, что «fread пытается читать из FILE* так быстро, как может, и возвращает / разблокирует после считывания последнего байта ». Это не совсем так: как отмечалось выше: fread будет считывать / блокировать до тех пор, пока не будет прочитано целевое число байтов или пока не будет обнаружена EOF или ошибка. FILE*, возвращаемое popen, происходит от fdopen конца чтения канала, используемого popen, поэтому его EOF происходит, когда дочерний процесс 'stdout - который был dup2 ed с записью конца трубы - закрыто .

Итак, в итоге мы имеем: popen создание канала, конец записи которого получает выходные данные дочернего процесса, выполняющего указанный command, и конец чтения которого, если fdopen преобразовано в FILE* передано fread. (Предполагая, что буфер fread достаточно большой), fread будет блокироваться до тех пор, пока не произойдет EOF, что соответствует закрытию конца записи канала popen в результате завершения выполнения command. То есть поскольку fread блокируется до тех пор, пока не встретится EOF, а EOF не произойдет после command - выполнение в дочернем процессе popen - завершится, можно использовать fread (с достаточно большим буфером) для захвата полный вывод command, переданного popen.

Благодарен, если кто-нибудь может проверить мои выводы и выводы.

0 голосов
/ 06 сентября 2018

Прочитайте документацию для popen более внимательно:

Функция pclose() закрывает поток, открытый с помощью popen(), , ожидает завершения команды и возвращает состояние завершения процесса, который выполнял интерпретатор языка команд.

Блокирует и ждет.

...