Дизайн программы
Родительский процесс должен будет прочитать ответ от потомка и организовать запись этой информации дважды, один раз в файл журнала и один раз в терминал. Или вам нужно будет организовать вывод от дочернего элемента до go в программу наподобие tee
, которая записывает копии своего ввода в несколько мест назначения.
Дополнительные каналы не помогут, если вы вы используете один для перенаправления вывода дочернего объекта на tee
.
Закрытие дескрипторов файлов конвейера
Вы не закрываете достаточно файловых дескрипторов в дочернем процессе.
Практическое правило : Если вы dup2()
на одном конце канала подключены к стандартному вводу или стандартному выводу, закройте оба дескриптора исходного файла, возвращаемые pipe()
как можно скорее. В частности, вы должны закрыть их перед использованием любого из семейства функций exec*()
.
Правило также применяется, если вы дублируете дескрипторы с dup()
или fcntl()
с F_DUPFD
или F_DUPFD_CLOEXEC
.
Если родительский процесс не будет связываться ни с одним из своих дочерних элементов через канал, он должен убедиться, что он закрывает оба конца канала достаточно рано (например, перед ожиданием), чтобы его дочерние элементы могли получать показания EOF при чтении (или получать сигналы SIGPIPE или ошибки записи при записи), а не блокировать бесконечно. Даже если родительский канал использует канал без использования dup2()
, он обычно должен закрывать по крайней мере один конец канала - программа очень редко может читать и писать на обоих концах одного канала.
Обратите внимание, что опция O_CLOEXEC
для open()
и опции FD_CLOEXEC
и F_DUPFD_CLOEXEC
для fcntl()
также могут учитывать это обсуждение.
Если вы используете posix_spawn()
и его обширное семейство вспомогательных функций (всего 21 функция), вам нужно будет рассмотреть, как закрывать файловые дескрипторы в порожденном процессе (posix_spawn_file_actions_addclose()
, et c .).
Обратите внимание, что использование dup2(a, b)
безопаснее, чем использование close(b); dup(a);
по ряду причин. Во-первых, если вы хотите, чтобы дескриптор файла был больше, чем обычно, dup2()
- единственный разумный способ сделать это. Другое состоит в том, что если a
совпадает с b
(например, оба 0
), то dup2()
обрабатывает его правильно (он не закрывается b
до дублирования a
), тогда как отдельный close()
и dup()
ужасно терпит неудачу. Это маловероятное, но не невозможное обстоятельство.
Достаточно исправленный код
Вот исправленная версия вашей программы. Логика c в log_stderr()
странная; единственное, что он не делает, это пишет в стандартную ошибку. Я изменил его, чтобы он читал из канала и записывал как в стандартный вывод, так и в файл журнала. Обратите внимание, что для репликации информации требуется одно чтение, но две записи. Проверка ошибок скудна или не существует. Функция bury()
теперь сообщает о смерти ребенка. Код был сохранен в pipe71.c
и скомпилирован в pipe71
.
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
static
int prepare_log(void)
{
int fd = open("error.log",
O_CREAT | O_RDWR | O_APPEND,
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH );
return fd;
}
static
void log_stderr(int pipe_ends[], int outfile)
{
char buffer[512];
int nbytes;
close(pipe_ends[1]);
while ((nbytes = read(pipe_ends[0], buffer, sizeof(buffer))) > 0)
{
write(STDOUT_FILENO, buffer, nbytes);
write(outfile, buffer, nbytes);
}
close(pipe_ends[0]);
close(outfile);
}
static
void child(int pipe_ends[], char *argv[])
{
dup2(pipe_ends[1], 1);
close(pipe_ends[0]);
close(pipe_ends[1]);
execvp(argv[0], argv);
fprintf(stderr, "failed to execute %s\n", argv[0]);
exit(EXIT_FAILURE);
}
static
void bury(void)
{
int status;
int corpse;
while ((corpse = wait(&status)) > 0)
printf("%d: child %d exited with status 0x%.4X\n", (int)getpid(), corpse, status);
}
int main(void)
{
int fd = prepare_log();
char *argv[] = {"seq", "10", NULL};
int pipe1[2];
pipe(pipe1);
pid_t pid = fork();
if (pid > 0)
{
log_stderr(pipe1, fd);
bury();
}
else if (pid == 0)
{
child(pipe1, argv);
}
return 0;
}
Однако он работает:
$ rm error.log
$ ./pipe71
1
2
3
4
5
6
7
8
9
10
99989: child 99990 exited with status 0x0000
$ cat error.log
1
2
3
4
5
6
7
8
9
10
$