Подтвердить что ты не робот

Окончательный вывод на slave pty теряется, если он не был закрыт родителем. Зачем?

Я написал и поддерживаю программу 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 должен предоставить один для них.

4b9b3361

Ответ 1

Я думаю, что существует уникальное отдельное поведение для Pty.

  • Система завершается, если записаны последние данные
  • Система завершается, если дочерний элемент выйдет (сломанный канал?)

Код опирается на существующий канал, достаточный для передачи данных, но выход ребенка может привести к удалению виртуального канала до получения данных.

Это будет уникально для Pty и не существует для реального терминала.

Ответ 2

В FreeBSD 10-STABLE я получаю обе выходные строки.

(Вы можете заменить openpty и fork на forkpty, который в основном позаботится и о login_tty.)

В FreeBSD 8.0 старый pty(4) драйвер был заменен на pts(4). Новый pty(4) ведет себя иначе, чем старый. Из руководства;

В отличие от предыдущих реализаций узлы главного и подчиненного устройств уничтожаются, когда PTY не используется. Вызов stat (2) на несуществующем ведущем устройстве уже приведет к созданию нового главного устройства node. Мастер-устройство может быть уничтожено только путем его открытия и закрытия.

Возможно, произошли значительные изменения между 8.0-RELEASE и 10.0-RELEASE

Вы также можете посмотреть patch, который применяется в дереве портов FreeBSD.

Ответ 3

printf выполняет буферизацию вывода в зависимости от класса устройства вывода. Просто попробуйте поставить fflush (stdout); после последнего printf, чтобы увидеть, есть ли проблема с буферизованным выходом.

Ответ 4

вот что я нашел на ubuntu linux   Примечание: всегда проверяйте наличие ошибок

#include <stdio.h>
#include <stdlib.h>
#include <pty.h>    // openpty(), 
#include <utmp.h>   // login_tty()
#include <unistd.h> // read(), write()

/* 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;
    pid_t pid;

    if( -1 == openpty(&master, &slave, NULL, NULL, NULL) )
    { // then openpty failed 
        perror( "openpty failed" );
        exit( EXIT_FAILURE );
    }

    // implied else, openpty successful

    pid = fork();
    if( -1 == pid ) 
    { // then fork failed
        perror( "fork failed" );
        exit( EXIT_FAILURE );
    }

    // implied else, fork successful

    if( pid ) 
    {    /* parent:                                                      */
        close(slave);     /* leave this out and lose slave final words ... WHY?         */
        do 
        {
            if( -1 == (nread = read(master, buf, BUFSIZE) ) )
            {// then, error occurred
                perror( "read failed" );
                exit( EXIT_FAILURE );
            }

            // implied else, read successful

            if ( nread )
            {   
                write(STDOUT_FILENO, buf, nread); /* echo child output to stdout  */
            }
        } while (nread);    /* nread == 0 indicates EOF */     
    } 

    else // pid == 0
    {    /* child:                                                      */
        if( -1 == 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       */ 
        { // then login_tty failed
            perror( "login_tty failed" );
            exit( EXIT_FAILURE );
        }

        // implied else, login_tty successful

        printf("Feeling OK :-)\n");
        sleep(1);
        printf("Feeling unwell ... Arghhh!\n"); /* this line may get lost */
    } // end if

    return 0;
} // end function: main

когда оператор close() закомментирован то родительский элемент никогда не выходит из-за того, что оператор read() блокирует

когда оператор close() является частью источника затем родитель выходит с ошибкой чтения от попытки чтения с терминала, который "отсутствует", когда ребенок выходит из

вот результат, когда close() commentedpout

Feeling OK :-)
Feeling unwell ... Arghhh!

then the parent hangs on the read() statement

вот результат, когда close() не закомментирован

Feeling OK :-)
Feeling unwell ... Arghhh!
read failed: Input/output error

Ответ 5

Я не уверен, что я прав: независимо от pty или нет, если один процесс имеет канал открытым, ОС не должна передавать EOF читателю (потому что все еще есть писатель). (после вилки есть два открытых канала), только если вы закроете родителя, закрытие на подчиненном устройстве должно переслать EOF.

На PTY вы уверены, что NL обрабатываются правильно, так как обычно CR должен запускать новую строку.

(просто мысль: если это контроль tty, все может измениться, так как OS обрабатывает синхронные поставки по-разному, и закрытие chanel будет нормальным завершать все дочерние процессы ребенка. Может ли это быть проблемой, если у родителя все еще есть ручка? )