Пакетный режим не предусмотрен стандартом SUSv3, и некоторые детали его реализации варьируются в зависимости от системы. Больше подробностей о нем, в том числе и о значениях битовой маски, описывающей изменения состояния, можно найти на странице tty_ioctl(4) руководства).
Теперь мы готовы к написанию простой версии стандартной программы script(1). Эта программа создает новую сессию командной строки и записывает весь ее ввод и вывод в файл. Большинство сессий командной строки, представленных в настоящей книге, было записано с ее помощью.
В обычной сессии командная оболочка соединяется непосредственно с пользовательским терминалом. После запуска программа script становится промежуточным звеном между оболочкой и терминалом пользователя, используя псевдотерминал для общения с оболочкой (см. рис. 60.4). Командная оболочка подключается ко вторичному устройству псевдотерминала, а программа script — к первичному. Процесс script выполняет для пользователя роль прокси, принимая данные, введенные в терминале, и записывая их в первичный конец псевдотерминала, а также направляя вывод из данного конца обратно в терминал.
Кроме того, script создает исходящий файл (который по умолчанию называется typescript), содержащий копию всех данных, выведенных на первичное устройство псевдотерминала. Это касается не только вывода, сгенерированного сессией командной оболочки, но и ее ввода. Ввод записывается, потому что, как и в случае с обычным терминалом, ядро экранирует вводимые символы, копируя их в исходящую очередь (см. рис. 58.1). Но если выключить эхо-контроль, как делается в программах, считывающих пароли, ввод вторичного устройства псевдотерминала не будет копироваться в исходящую очередь и, следовательно, не попадет в исходящий файл программы script.
Наша реализация программы script представлена в листинге 60.3. Она выполняет следующие действия.
1. Получает размер окна и атрибуты терминала, в котором запущена программа
2. Вызывает нашу функцию ptyFork() (см. листинг 60.2), чтобы создать дочерний процесс, соединенный с родителем путем псевдотерминала
3. После вызова ptyFork() потомок запускает командную оболочку
Рис. 60.4.
4. После вызова ptyFork() родитель выполняет следующие шаги.
1) Открывает файл вывода
2) Переключает терминал в режим без обработки (используя функцию ttySetRaw(), представленную в листинге 58.3), чтобы вводимые символы направлялись непосредственно в программу script и не изменялись драйвером терминала
Факт нахождения терминала в режиме без обработки вовсе не означает, что командная оболочка (или любая другая группа процессов, которая является активной по отношению ко вторичному устройству псевдотерминала) будет получать управляющие символы в их исходном виде или что эти символы дойдут неизмененными до пользовательского терминала. Интерпретация специальных символов происходит во вторичном устройстве (если только оно тоже не было переключено в режим без обработки). Используя режим без обработки в терминале пользователя, мы отключаем второй уровень интерпретации входящих и исходящих символов.
3) Вызывает функцию atexit() с целью установить обработчик, который при завершении программы возвращает терминал в его исходный режим
4) Выполняет цикл, передающий данные от терминала к первичному устройству псевдотерминала и наоборот
Листинг 60.3. Простая реализация программы script(1)
pty/script.c
#include
#include
#include
#include
#include
#include "pty_fork.h" /* Объявление ptyFork() */
#include "tty_functions.h" /* Объявление ttySetRaw() */
#include "tlpi_hdr.h"
#define BUF_SIZE 256
#define MAX_SNAME 1000
struct termios ttyOrig;
static void /* Сбрасываем режим терминала при выходе */
ttyReset(void)
{
if (tcsetattr(STDIN_FILENO, TCSANOW, &ttyOrig) == -1)
errExit("tcsetattr");
}
int
main(int argc, char *argv[])
{
char slaveName[MAX_SNAME];
char *shell;
int masterFd, scriptFd;