Discussion:
Чтение stdout и stderr процесса-потомка.
(слишком старое сообщение для ответа)
Gennadij Pastuhov
2014-03-27 08:27:10 UTC
Permalink
Рад всех приветствовать! А особенно - All!

Что-то я не соображу, можно ли в линухе сделать такое: есть некая программа,
она делает свое дело и выдает строки в stdout и stderr. Мне нужно запустить ее
(из С) и прочесть эти строки.

... Jonny wanna live
Rinat H. Sadretdinow
2014-03-27 09:58:32 UTC
Permalink
On 03/27/2014 12:27 PM, Gennadij Pastuhov wrote:
GP> Что-то я не соображу, можно ли в линухе сделать такое: есть некая
GP> программа, она делает свое дело и выдает строки в stdout и stderr.
GP> Мне нужно запустить ее (из С) и прочесть эти строки.

man 3 popen
--
Пока!
Gennadij Pastuhov
2014-03-27 10:29:42 UTC
Permalink
Рад всех приветствовать! А особенно - Rinat!

Четверг марта 27 14 13:58 Rinat H. Sadretdinow писал к Gennadij Pastuhov:

GP>> Что-то я не соображу, можно ли в линухе сделать такое: есть некая
GP>> программа, она делает свое дело и выдает строки в stdout и stderr.
GP>> Мне нужно запустить ее (из С) и прочесть эти строки.
RS> man 3 popen

Вроде бы не подходит, мне же два потока данных нужны. Пока дошел до такого
способа: делаю 2 пайпа, форкаюсь, цепляю стдоут и стдерр к пайпам и в родителе
по селекту жду данные. Предполагается, что потомок будет висеть вечно и слушать
сетевой интерфейс, а мне возвращать прочтенное оттуда.

... Jonny wanna live
Valentin Nechayev
2014-03-27 10:45:54 UTC
Permalink
GP>>> Что-то я не соображу, можно ли в линухе сделать такое: есть некая
GP>>> программа, она делает свое дело и выдает строки в stdout и stderr.
GP>>> Мне нужно запустить ее (из С) и прочесть эти строки.
RS>> man 3 popen
GP> Вроде бы не подходит, мне же два потока данных нужны. Пока дошел до такого
GP> способа: делаю 2 пайпа, форкаюсь, цепляю стдоут и стдерр к пайпам и в родителе
GP> по селекту жду данные. Предполагается, что потомок будет висеть вечно и слушать
GP> сетевой интерфейс, а мне возвращать прочтенное оттуда.

Hу в общем так оно и есть. Может, где-то есть библиотека для этого,
я не видел.

P.S. Я такое писал, отдать в паблик не могу, но могу ответить на
вопросы.
Одна из самых больших тонкостей, которые надо предусмотреть
концептуально - это соотношение между фактами завершения дочернего
процесса, закрытия потоков с той стороны и таймаута действий.
В случае интерактивного общения с чем-то на той стороне надо тщательно
продумать политику в этом вопросе.


--netch--
Gennadij Pastuhov
2014-03-27 11:17:52 UTC
Permalink
Рад всех приветствовать! А особенно - Valentin!

Четверг марта 27 14 14:45 Valentin Nechayev писал к Gennadij Pastuhov:

VN> Hу в общем так оно и есть. Может, где-то есть библиотека для этого,
VN> я не видел.
VN> P.S. Я такое писал, отдать в паблик не могу, но могу ответить на
VN> вопросы.

Спасибо!

VN> Одна из самых больших тонкостей, которые надо предусмотреть
VN> концептуально - это соотношение между фактами завершения дочернего
VN> процесса, закрытия потоков с той стороны и таймаута действий.
VN> В случае интерактивного общения с чем-то на той стороне надо тщательно
VN> продумать политику в этом вопросе.

Нет, мне нужно только читать. И, если вдруг потомок умер, то в системе явно
что-то не то и можно тоже смело умирать.

... Jonny wanna live
Gennadij Pastuhov
2014-03-31 08:02:16 UTC
Permalink
Рад всех приветствовать! А особенно - Valentin!

Четверг марта 27 14 14:45 Valentin Nechayev писал к Gennadij Pastuhov:

GP>>>> Что-то я не соображу, можно ли в линухе сделать такое: есть
GP>>>> некая программа, она делает свое дело и выдает строки в stdout и
GP>>>> stderr.
GP>>>> Мне нужно запустить ее (из С) и прочесть эти строки.
RS>>> man 3 popen
GP>> Вроде бы не подходит, мне же два потока данных нужны. Пока дошел
GP>> до такого способа: делаю 2 пайпа, форкаюсь, цепляю стдоут и стдерр к
GP>> пайпам и в родителе по селекту жду данные. Предполагается, что
GP>> потомок будет висеть вечно и слушать сетевой интерфейс, а мне
GP>> возвращать прочтенное оттуда.

VN> Hу в общем так оно и есть. Может, где-то есть библиотека для этого,
VN> я не видел.

Ну вот, оклемался от простуды и написал, работает такой код. Только хорошо бы,
чтобы писатель в stdout почаще делал:

int sofd[2]; //pipe для stdout
int sefd[2]; //pipe для stderr
int rc;
ssize_t ro, re;

char child_stdout_buf[BUF_LEN];
char child_stderr_buf[BUF_LEN];

rc = pipe(sofd);

if (rc == -1) {
// ошибка создания пайпа
} else if (rc == 0) {
rc = pipe(sefd);

if (rc == -1) {
// ошибка создания пайпа
} else if (rc == 0) {
pid_t pid = fork();

if (pid == 0) {
rc = dup2(sofd[1], STDOUT_FILENO);

if (rc == -1) {
// ошибка dup2
} else {
rc = dup2(sefd[1], STDERR_FILENO);

if (rc == -1) {
// ошибка dup2
} else {
rc = execve("child_proc", NULL, NULL);
}
}
} else {
fd_set std;
int nfds = MAX(sofd[0], sefd[0]) + 1;

for (;;) {
FD_ZERO(&std);
FD_SET(sofd[0], &std);
FD_SET(sefd[0], &std);

rc = select(nfds, &std, NULL, NULL, NULL);

if (rc == 0) {
// данных нет
} else if (rc == -1) {
// ошибка при чтении данных
} else {
if (FD_ISSET(sofd[0], &std)) {
// читаем stdout потомка
ro = read(sofd[0],
child_stdout_buf, BUF_LEN);
if (ro == -1) {
// ошибка чтения
} else if (ro == 0) {
// нечего читать
} else {
child_stdout_buf[ro] =
0;
// в буфере прочитанная строка
}
} else if (FD_ISSET(sefd[0], &std)) {
// есть данные из stderr потомка
...
}
}
...

VN> P.S. Я такое писал, отдать в паблик не могу, но могу ответить на
VN> вопросы.

Возможно, для спецов код простой, но я со всеми этими функциями первый раз
столкнулся, пару дней думал, что нужно закрыть, что открыть и в каком порядке.

VN> Одна из самых больших тонкостей, которые надо предусмотреть
VN> концептуально - это соотношение между фактами завершения дочернего
VN> процесса, закрытия потоков с той стороны и таймаута действий.
VN> В случае интерактивного общения с чем-то на той стороне надо тщательно
VN> продумать политику в этом вопросе.

Тут да.

... Jonny wanna live
Pavel Gulchouck
2014-03-31 16:54:18 UTC
Permalink
Hi Gennadij!

31 Mar 14, Gennadij Pastuhov ==> Valentin Nechayev:

GP> Четверг марта 27 14 14:45 Valentin Nechayev писал к Gennadij Pastuhov:

GP>>>>> Что-то я не соображу, можно ли в линухе сделать такое: есть
GP>>>>> некая программа, она делает свое дело и выдает строки в stdout и
GP>>>>> stderr.
GP>>>>> Мне нужно запустить ее (из С) и прочесть эти строки.

RS>>>> man 3 popen

GP>>> Вроде бы не подходит, мне же два потока данных нужны. Пока дошел
GP>>> до такого способа: делаю 2 пайпа, форкаюсь, цепляю стдоут и стдерр к
GP>>> пайпам и в родителе по селекту жду данные. Предполагается, что
GP>>> потомок будет висеть вечно и слушать сетевой интерфейс, а мне
GP>>> возвращать прочтенное оттуда.

VN>> Hу в общем так оно и есть. Может, где-то есть библиотека для этого,
VN>> я не видел.

GP> Ну вот, оклемался от простуды и написал, работает такой код. Только хорошо
GP> бы, чтобы писатель в stdout почаще делал:

Сделаю типа review, если не возражаешь. :)

GP> int sofd[2]; //pipe для stdout
GP> int sefd[2]; //pipe для stderr
GP> int rc;
GP> ssize_t ro, re;

GP> char child_stdout_buf[BUF_LEN];
GP> char child_stderr_buf[BUF_LEN];

GP> rc = pipe(sofd);

GP> if (rc == -1) {
GP> // ошибка создания пайпа
GP> } else if (rc == 0) {
GP> rc = pipe(sefd);

GP> if (rc == -1) {
GP> // ошибка создания пайпа
GP> } else if (rc == 0) {
GP> pid_t pid = fork();

GP> if (pid == 0) {
GP> rc = dup2(sofd[1], STDOUT_FILENO);

GP> if (rc == -1) {
GP> // ошибка dup2
GP> } else {
GP> rc = dup2(sefd[1], STDERR_FILENO);

GP> if (rc == -1) {
GP> // ошибка dup2
GP> } else {

Здесь хорошо бы закрыть sofd[1] и sefd[1].
А можно заодно и sofd[0] и sefd[0] - они для child тоже не нужны.

GP> rc = execve("child_proc", NULL,
GP> NULL);
GP> }
GP> }
GP> } else {

Здесь тоже надо бы закрыть sofd[1] и sefd[1], иначе завершение потомка не
приведёт к закрытию пайпа - он ведь останется у родителя.

GP> fd_set std;
GP> int nfds = MAX(sofd[0], sefd[0]) + 1;

GP> for (;;) {
GP> FD_ZERO(&std);
GP> FD_SET(sofd[0], &std);
GP> FD_SET(sefd[0], &std);

GP> rc = select(nfds, &std, NULL, NULL, NULL);

Точно не нужен таймаут?

GP> if (rc == 0) {
GP> // данных нет
GP> } else if (rc == -1) {
GP> // ошибка при чтении данных
GP> } else {
GP> if (FD_ISSET(sofd[0], &std)) {
GP> // читаем stdout потомка
GP> ro = read(sofd[0],
GP> child_stdout_buf,
GP> BUF_LEN);
GP> if (ro == -1) {
GP> // ошибка чтения
GP> } else if (ro == 0) {
GP> // нечего читать

Насколько я помню, если select() установил флажок, а read() вернул ноль, это
EOF, т.е. пайп закрыт с той стороны.
Это значит, что нужно похоронить дочерний процесс (waitpid()) и закрыть свой
конец пайпа. В твоём случае - обоих пайпов.

GP> } else {
GP> child_stdout_buf[ro]
GP> = 0;
GP> // в буфере прочитанная
GP> строка
GP> }

Совершенно не факт, что там будет строчная буферизация.
Может быть прочитано полстроки, может быть несколько строк. Может и нулевой
байт быть.
И, что особенно интересно, может быть прочитано ровно BUF_LEN байт, и тогда ты
запишешь нолик за границу массива.

GP> } else if (FD_ISSET(sefd[0],
GP> &std)) {
GP> // есть данные из stderr потомка
GP> ...
GP> }
GP> }
GP> ...

VN>> P.S. Я такое писал, отдать в паблик не могу, но могу ответить на
VN>> вопросы.

GP> Возможно, для спецов код простой, но я со всеми этими функциями первый раз
GP> столкнулся, пару дней думал, что нужно закрыть,
GP> что открыть и в каком порядке.

VN>> Одна из самых больших тонкостей, которые надо предусмотреть
VN>> концептуально - это соотношение между фактами завершения дочернего
VN>> процесса, закрытия потоков с той стороны и таймаута действий.
VN>> В случае интерактивного общения с чем-то на той стороне надо тщательно
VN>> продумать политику в этом вопросе.

GP> Тут да.

Lucky carrier,
Паша
aka ***@gul.kiev.ua
Gennadij Pastuhov
2014-04-01 05:25:10 UTC
Permalink
Рад всех приветствовать! А особенно - Pavel!

Понедельник марта 31 14 20:54 Pavel Gulchouck писал к Gennadij Pastuhov:

PG> Сделаю типа review, если не возражаешь. :)

Большое за него спасибо!

PG> Точно не нужен таймаут?

Мне сейчас - нет, а так - на любителя.

PG> Совершенно не факт, что там будет строчная буферизация.
PG> Может быть прочитано полстроки, может быть несколько строк. Может и
PG> нулевой байт быть. И, что особенно интересно, может быть прочитано
PG> ровно BUF_LEN байт, и тогда ты запишешь нолик за границу массива.

Точно, косячок :)

... Jonny wanna live
Valentin Nechayev
2014-04-01 07:20:58 UTC
Permalink
GP> if (rc == -1) {
GP> // ошибка создания пайпа

Мне с ходу не нравится вложенность условий "когда всё ещё идёт по
плану" и финальные отступы более чем дофига.
Лучше было бы переделать это в любой из вариантов - return, break (из
псевдо-цикла только для структурированности) или даже goto.

GP> rc = dup2(sofd[1], STDOUT_FILENO);
GP> if (rc == -1) {

Честно говоря, я не представляю себе реально ситуации, когда dup2 в
уже существующий дескриптор обломится. Это настолько странно, что в
подобном случае лучше делать abort().

GP> rc = execve("child_proc", NULL, NULL);

Hе закрыты читающие концы пайпов при передаче в потомка.
Hадо было или явно их закрыть при fork, или лучше поставить
FD_CLOEXEC. Если жёстко ограничено Linux, и программа многонитевая, то
звать pipe2() с O_CLOEXEC вместо pipe().

GP> } else {

А тут закрыть, наоборот, пишущий конец, ибо иначе никогда не
закончится ввод.

GP> for (;;) {
GP> FD_ZERO(&std);
GP> FD_SET(sofd[0], &std);
GP> FD_SET(sefd[0], &std);

GP> rc = select(nfds, &std, NULL, NULL, NULL);

GP> if (rc == 0) {
GP> // данных нет
GP> } else if (rc == -1) {
GP> // ошибка при чтении данных

Тут любые ошибки, кроме EINTR, надо как минимум логгировать, а лучше
выходить, потому что это значит существенно нештатную ситуацию.

Ещё желательно изначально поставить O_NONBLOCK на читающий конец
пайпов.

GP> } else {
GP> if (FD_ISSET(sofd[0], &std)) {
GP> // читаем stdout потомка
GP> ro = read(sofd[0],
GP> child_stdout_buf, BUF_LEN);
GP> if (ro == -1) {
GP> // ошибка чтения

1. Тут, аналогично, EAGAIN и EINTR должны игнорироваться (если
одиночные), а другие ошибки считаться критическими.

2. Hадо учитывать, когда чтение начнёт отвечать 0, и в этом случае
больше из такого дескриптора не читать. Когда оба ответили 0, можно
завершать потомка. Я помню Ваш рассказ, что завершение потомка
критично, но безумное кручение в цикле в этом случае тоже не лучший
вариант. Как минимум надо фиксировать факт EOF в логе.


--netch--
Gennadij Pastuhov
2014-04-01 08:26:50 UTC
Permalink
Рад всех приветствовать! А особенно - Valentin!

Вторник апреля 01 14 11:20 Valentin Nechayev писал к Gennadij Pastuhov:

GP>> if (rc == -1) {
GP>> // ошибка создания пайпа

VN> Мне с ходу не нравится вложенность условий "когда всё ещё идёт по
VN> плану" и финальные отступы более чем дофига.
VN> Лучше было бы переделать это в любой из вариантов - return, break (из
VN> псевдо-цикла только для структурированности) или даже goto.

С одной стороны - да. С другой - четко видно, в каком месте кода что
происходит. Ну нету тут исключений, что сделаешь :)

GP>> rc = dup2(sofd[1], STDOUT_FILENO);
GP>> if (rc == -1) {

VN> Честно говоря, я не представляю себе реально ситуации, когда dup2 в
VN> уже существующий дескриптор обломится. Это настолько странно, что в
VN> подобном случае лучше делать abort().

В описании указаны возможные ошибки, я как-то привык прежде всего их
обрабатывать.

VN> 2. Hадо учитывать, когда чтение начнёт отвечать 0, и в этом случае
VN> больше из такого дескриптора не читать. Когда оба ответили 0, можно
VN> завершать потомка. Я помню Ваш рассказ, что завершение потомка
VN> критично, но безумное кручение в цикле в этом случае тоже не лучший
VN> вариант. Как минимум надо фиксировать факт EOF в логе.

Ага, спасибо :)

... Jonny wanna live

Loading...