Я написал и поддерживаю программу rlwrap, которая использует псевдотерминал для связи с дочерним процессом. Pseudo-terminal (ptys) находятся во всех Unix-подобных системах, но они ведут себя по-разному на разных платформах.
Пример: в rlwrap
родительский процесс сохраняет открытую рабочую часть pty для сохранения вкладок в настройках дочернего терминала (например, в Linux и FreeBSD можно использовать мастер для этого, но не в Solaris, например)
В FreeBSD (8.2) (но не в Linux) это приводит к потере конечного результата для ребенка. Например:
#include <stdio.h>
/* save as test.c and compile with gcc -o test test.c -lutil */
#define BUFSIZE 255
int main(void) {
int master, slave;
char buf[BUFSIZE];
int nread;
openpty(&master, &slave, NULL, NULL, NULL);
if (fork()) { /* parent: */
close(slave); /* leave this out and lose slave final words ... WHY? */
do {
nread = read(master, buf, BUFSIZE);
write(STDOUT_FILENO, buf, nread); /* echo child output to stdout */
} while (nread > 0);
} else { /* child: */
login_tty(slave); /* this makes child a session leader and slave a controlling */
/* terminal for it, then dup()s std{in,out,err} to slave */
printf("Feeling OK :-)\n");
sleep(1);
printf("Feeling unwell ... Arghhh!\n"); /* this line may get lost */
}
return 0;
}
Родительский процесс будет эхо выводить дочерний вывод, как и ожидалось, но когда я опускаю close(slave)
(сохраняя его открытым, как в rlwrap
):
- на FreeBSD, родитель не видит конечную выходную строку, вместо этого он считывает EOF. (Во всяком случае, я бы ожидал, что противоположное - сохранение открытого конца ведомого приведет к потере вывода)
- В Linux, с другой стороны, EOF никогда не видели, даже после того, как ребенок умер (закрываем ли мы раб или нет)
Является ли это поведение документированным где-то? Есть ли для этого обоснование? Могу ли я обойти это без закрытия подчиненного устройства в родительском процессе?
Я узнал, что не сделать ведомое устройство управляющим терминалом - замена вызова login_tty
несколькими простыми вызовами dup()
- вылечит проблему. Однако это не решение для rlwrap
: для нескольких команд требуется командный терминал (/dev/tty
), поэтому rlwrap
должен предоставить один для них.