Технологические интерфейсы
Функции для работы с псевдотерминалами
В курсе [ 1 ] мы уже останавливались на понятии псевдотерминала. Опишем теперь соответствующие функции.
Первым действием при работе с псевдотерминалами является открытие главного устройства, осуществляемое функцией posix_openpt() (см. листинг 9.49).
#include <stdlib.h> #include <fcntl.h> int posix_openpt (int oflag);Листинг 9.49. Описание функции открытия главного устройства псевдотерминала.
В значении аргумента oflag есть смысл устанавливать флаги O_RDWR и O_NOCTTY (последний означает, что терминал не будет управляющим для процесса).
Разумеется, нормальным результатом функции posix_openpt()> служит файловый дескриптор ; в случае ошибки возвращается -1.
Считается, что после открытия главного устройства подчиненное устройство псевдотерминала блокируется, и эту блокировку необходимо явным образом снять, вызвав функцию unlockpt() (см. листинг 9.50).
#include <stdlib.h> int unlockpt (int masterfd);Листинг 9.50. Описание функции разблокирования подчиненного устройства псевдотерминала.
Здесь masterfd – файловый дескриптор главного устройства псевдотерминала, полученный в результате вызова функции posix_openpt().
Нормальным для unlockpt() является нулевой результат.
Функция grantpt() (см. листинг 9.51) позволяет сформировать нужные права доступа к подчиненному устройству псевдотерминала. Владелец устройства устанавливается в соответствии с реальным идентификатором пользователя вызывающего процесса; ему (владельцу) предоставляются права на чтение и запись.
#include <stdlib.h> int grantpt (int masterfd);Листинг 9.51. Описание функции формирования прав доступа к подчиненному устройству псевдотерминала.
Применительно к функции grantpt() в стандарте POSIX-2001 оговаривается одна тонкость: вызывающий процесс не должен обрабатывать сигнал SIGCHLD. Это можно понять (и оправдать), поскольку grantpt() выполняет суперпользовательское действие, реализация которого в некоторых ОС может быть сопряжена с порождением процессов.
Чтобы узнать имя подчиненного устройства псевдотерминала, следует обратиться к функции ptsname() (см. листинг 9.52).
#include <stdlib.h> char *ptsname (int masterfd);Листинг 9.52. Описание функции получения имени подчиненного устройства псевдотерминала.
Разумеется, результирующий указатель может ссылаться на статическую область, перезаписываемую при каждом вызове ptsname(), поэтому рекомендуется полученное имя скопировать или поскорее использовать. Использование очевидно – посредством обычной функции open() открыть подчиненное устройство псевдотерминала и получить его файловый дескриптор.
Таким образом, в стандарте POSIX-2001 выстроена пятиэтапная модель получения доступа к псевдотерминалу:
- открытие главного устройства псевдотерминала, получение его файлового дескриптора (осуществляется функцией posix_openpt() );
- разблокирование подчиненного устройства псевдотерминала (функция unlockpt() );
- формирование прав доступа к подчиненному устройству псевдотерминала ( grantpt() );
- получение имени подчиненного устройства псевдотерминала ( ptsname() );
- открытие подчиненного устройства псевдотерминала, получение его файлового дескриптора ( open() ).
Применение описанных функций иллюстрируется переработанным вариантом программы из курса [ 1 ] , запускающей командный интерпретатор на псевдотерминале (см. листинг 9.53).
/* * * * * * * * * * * * * * * * * * * * * * * * */ /* Программа запускает shell на псевдотерминале */ /* * * * * * * * * * * * * * * * * * * * * * * * */ #define _XOPEN_SOURCE 600 #include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <fcntl.h> #include <termios.h> #include <signal.h> #include <poll.h> #include <sys/resource.h> #include <curses.h> /* * * * * * * * * * * * * * * * * * */ /* Действия при завершении процесса */ /* * * * * * * * * * * * * * * * * * */ static void termination (int errcode) { endwin (); exit (errcode); } /* * * * * * * * * * * * * * * * * * */ /* Функция обработки сигнала SIGCHLD */ /* * * * * * * * * * * * * * * * * * */ static void chldied (int dummy) { /* Просто кончимся */ termination (34); } /* * * * * * * * * * * * * * * * * * * * */ /* Организация работы с псевдотерминалом */ /* * * * * * * * * * * * * * * * * * * * */ int main (void) { WINDOW *win1, *win2; /* win1 – окно только для рамки */ /* win2 – окно для shell'а */ int pty, tty; /* Дескрипторы обеих сторон */ /* псевдотерминала */ int fr; /* Результат fork'а */ unsigned char ch; /* Прочитанный символ */ struct termios pt; /* Структура характеристик */ /* псевдотерминала */ struct pollfd fds [2]; /* Массив параметров для */ /* вызова poll */ int w2lines, w2cols; /* Размер создаваемого окна */ int x, y; /* Координаты в окне */ struct sigaction sact; int i; initscr (); cbreak (); noecho (); win1 = newwin (LINES, COLS, 0, 0); box (win1, 0, 0); wrefresh (win1); w2lines = LINES – 2; w2cols = COLS – 4; win2 = newwin (w2lines, w2cols, 1, 2); scrollok (win2, TRUE); /* Откроем псевдотерминал */ if (((pty = posix_openpt (O_RDWR | O_NOCTTY)) < 0) || (unlockpt (pty) == -1) || (grantpt (pty) == -1) || ((tty = open (ptsname (pty), O_RDWR)) < 0)) { fprintf (stderr, "Не удалось открыть псевдотерминал\n"); perror ("POSIX_OPENPT"); return (1); } /* Установим подходящие характеристики псевдотерминала */ if (tcgetattr (pty, &pt) < 0) { perror ("PTY TERMIOS GET ERROR"); return (2); } pt.c_iflag = 0; pt.c_oflag = ONLCR; pt.c_cflag = CS8 | HUPCL; pt.c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK; pt.c_cc [VINTR] = 3; /* CTRL+C */ pt.c_cc [VEOF] = 4; /* CTRL+D */ if (tcsetattr (pty, TCSADRAIN, &pt) < 0) { perror ("PTY TERMIOS SET ERROR"); return (3); } /* То же – для стандартного ввода */ (void) tcgetattr (0, &pt); pt.c_lflag &= ~ISIG; (void) tcsetattr (0, TCSADRAIN, &pt); /* Установим обработку сигнала о завершении потомка */ sact.sa_handler = chldied; (void) sigemptyset (&sact.sa_mask); sact.sa_flags = 0; (void) sigaction (SIGCHLD, &sact, (struct sigaction *) NULL); /* Раздвоимся на процесс чтения с клавиатуры */ /* и вывода на экран и на процесс, */ /* в рамках которого запустим shell */ if ((fr = fork ()) < 0) { perror ("FORK1 ERROR"); termination (-1); } else if (fr) { /* Это процесс, читающий с клавиатуры */ /* и выводящий на экран */ close (tty); /* Будем ждать ввода с клавиатуры или псевдотерминала */ fds [0].fd = 0; fds [0].events = POLLIN; fds [1].fd = pty; fds [1].events = POLLIN; while (1) { if (poll (fds, 2, -1) < 0) { perror ("POLL ERROR"); termination (0); } if (fds [0].revents & POLLIN) { /* Пришел символ со стандартного ввода */ read (0, &ch, 1); write (pty, &ch, 1); } if (fds [1].revents & POLLIN) { /* Пришел символ с псевдотерминала */ read (pty, &ch, 1); switch (ch) { case '\n': { /* Проинтерпретируем перевод строки */ getyx (win2, y, x); if (y == (w2lines – 1)) { wmove (win2, y, w2cols – 1); waddch (win2, (chtype) ch); } else { wmove (win2, y + 1, 0); } break; } default: { /* Символ не интерпретируется */ waddch (win2, (chtype) ch); break; } } wrefresh (win2); } } /* Просто кончимся */ termination (0); } else { /* Порожденный процесс – запустим в нем shell */ /* Закроем все файлы, кроме псевдотерминала */ for (i = 0; i < RLIMIT_NOFILE; i++) { if (i != tty) { (void) close (i); } } /* Сделаем процесс лидером сеанса */ (void) setsid (); /* Свяжем стандартные ввод, вывод и протокол */ /* с псевдотерминалом */ (void) fcntl (tty, F_DUPFD, 0); (void) fcntl (tty, F_DUPFD, 0); (void) fcntl (tty, F_DUPFD, 0); close (tty); /* Поместим в окружение параметры псевдотерминала */ { char lnbuf [20]; char clbuf [20]; sprintf (lnbuf, "LINES=%2d", w2lines); sprintf (clbuf, "COLUMNS=%2d", w2cols); putenv (lnbuf); putenv (clbuf); } if (execl ("/bin/sh", "sh", (char *) NULL) < 0) { perror ("EXECL ERROR"); exit (-1); } } return 0; }Листинг 9.53. Пример программы, использующей псевдотерминалы.
Здесь все пять упоминавшихся подготовительных этапов вошли в состав одного условного оператора. Обратим внимание на применение флага O_NOCTTY при вызове posix_openpt(), а также на то, что обращение к grantpt() выполнено до установки функции обработки сигнала SIGCHLD.