Скачиваний:
56
Добавлен:
08.01.2014
Размер:
2.6 Mб
Скачать

9.5. Пример управления терминалом: программа tscript

Программа tscript устроена следующим образом: при старте она выполняет вызовы fork и ехес для запуска пользовательской оболочки. Далее все данные, записываемые на терминал оболочкой, сохраняются в файле, при этом оболочка ничего об этом не знает и продолжает вести себя так, как будто она полностью управляет дисциплиной линии связи и, следовательно, терминалом. Логическая структура программы tscript показана на рис. 9.6.

Рис. 9.6. Использование псевдотерминала в программе tscript

Основные элементы схемы:

tscript

Первый запускаемый процесс. После инициализации псевдотерминала и дисциплины линии связи этот процесс использует вызовы fork и ехес для создания оболочки shell. Теперь программа tscript играет две роли. Первая состоит в чтении из настоящего терминала и записи всех данных в порт ведущего устройства псевдотерминала. (Все данные, записываемые в ведущее устройство псевдотерминала, непосредственно передаются на ведомое устройство псевдотерминала.) Вторая роль состоит в чтении вывода программы оболочки shell при помощи псевдотерминала и копировании этих данных на настоящий терминал и в выходной файл

shell

Пользовательская оболочка. Перед запуском процесса shell модули дисциплины линии связи STREAM вставляются в ведомое устройство. Стандартный ввод, стандартный вывод и стандартный вывод диагностики оболочки перенаправляются в ведомое устройство псевдотерминала

Первая задача программы tscript состоит в установке обработчика сигнала SIGCHLD и в открытии псевдотерминала. Затем программа создает процесс shell. И, наконец, вызывается процедура script. Эта процедура отслеживает два потока данных: ввод с клавиатуры (стандартный ввод), который она передает ведущему устройству псевдотерминала, и ввод с ведущего устройства псевдотерминала, передаваемый на стандартный вывод и записываемый в выходной файл.

(* Программа tscript управление терминалом *)

(* Хотя в Linux этот пример и не работает... *)

uses linux,stdio;

var

dattr:termios;

var

act:sigactionrec;

mfd, sfd:longint;

err:integer;

buf:array [0..511] of char;

mask:sigset_t;

begin

(* Сохранить текущие установки терминала *)

tcgetattr (0, dattr);

(* Открыть псевдотерминал *)

err := pttyopen (mfd, sfd);

if err <> 1 then

begin

writeln (stderr, 'pttyopen: ', err);

perror ('Ошибка при открытии псевдотерминала');

halt (1);

end;

(* Установить обработчик сигнала SIGCHLD *)

act.handler.sh := @catch_child;

sigfillset (@mask);

act.sa_mask:=mask.__val[0];

sigaction (SIGCHLD, @act, nil);

(* Создать процесс оболочки *)

case fork of

-1: (* ошибка *)

begin

perror ('Ошибка вызова оболочки');

halt (2);

end;

0: (* дочерний процесс *)

begin

fdclose (mfd);

runshell (sfd);

end;

else (* родительский процесс *)

begin

fdclose (sfd);

script (mfd);

end;

end;

end.

Основная программа использует четыре процедуры. Первая из них называется catch_child. Это обработчик сигнала SIGCHLD. При получении сигнала SIGCHLD процедура catch_child восстанавливает атрибуты терминала и завершает работу.

procedure catch_child (signo:integer);cdecl;

begin

tcsetattr (0, TCSAFLUSH, dattr);

halt (0);

end;

Вторая процедура, pttyopen, открывает псевдотерминал.

function pttyopen (var masterfd, slavefd:longint):integer;

var

slavenm:pchar;

begin

(* Открыть псевдотерминал -

* получить дескриптор файла главного устройства *)

masterfd := fdopen ('/dev/ptmx', Open_RDWR);

if masterfd = -1 then

begin

pttyopen:=-1;

exit;

end;

(* Изменить права доступа для ведомого устройства *)

if grantpt (masterfd) = -1 then

begin

fdclose (masterfd);

pttyopen:=-2;

exit;

end;

(* Разблокировать ведомое устройство, связанное с mfd *)

if unlockpt (masterfd) = -1 then

begin

fdclose (masterfd);

pttyopen:=-3;

exit;

end;

(* Получить имя ведомого устройства и затем открыть его *)

slavenm := ptsname (masterfd);

if slavenm = nil then

begin

fdclose (masterfd);

pttyopen:=-4;

exit;

end;

slavefd := fdopen (slavenm, Open_RDWR);

if slavefd = -1 then

begin

fdclose (masterfd);

pttyopen:=-5;

exit;

end;

(* Создать дисциплину линии связи *)

ioctl (slavefd, I_PUSH, pchar('ptem'));

if linuxerror>0 then

begin

fdclose (masterfd);

fdclose (slavefd);

pttyopen:=-6;

exit;

end;

ioctl (slavefd, I_PUSH, pchar('ldterm'));

if linuxerror>0 then

begin

fdclose (masterfd);

fdclose (slavefd);

pttyopen:=-7;

exit;

end;

pttyopen:=1;

end;

Третья процедура – процедура runshell. Она выполняет следующие задачи:

  • вызывает setpgrp, чтобы оболочка выполнялась в своей группе процессов. Это позволяет оболочке полностью управлять обработкой сигналов, в особенности в отношении управления заданиями;

  • вызывает системный вызов dup2 для перенаправления дескрипторов stdin, stdout и stderr на дескриптор файла ведомого устройства. Это особенно важный шаг;

  • запускает оболочку при помощи вызова ехес, которая выполняется до тех пор, пока не будет прервана пользователем.

procedure runshell (sfd:longint);

begin

setpgrp;

dup2 (sfd, 0);

dup2 (sfd, 1);

dup2 (sfd, 2);

execl ('/bin/sh -i');

end;

Теперь рассмотрим саму процедуру script. Первым действием процедуры script является изменение дисциплины линии связи так, чтобы она работала в режиме прямого доступа. Это достигается получением текущих атрибутов терминала и изменением их при помощи вызова tcsetattr. Затем процедура script открывает файл output и использует системный вызов select (обсуждавшийся в главе 7) для обеспечения одновременного ввода со своего стандартного ввода и ведущего устройства псевдотерминала. Если данные поступают со стандартного ввода, то процедура script передает их без изменений ведущему устройству псевдотерминала. При поступлении же данных с ведущего устройства псевдотерминала процедура script записывает эти данные в терминал пользователя и в файл output.

procedure script(mfd:longint);

var

nread, ofile:longint;

_set, master:fdset;

attr:termios;

buf:array [0..511] of char;

begin

(* Перевести дисциплину линии связи в режим прямого доступа *)

tcgetattr (0, attr);

attr.c_cc[VMIN] := 1;

attr.c_cc[VTIME] := 0;

attr.c_lflag := attr.c_lflag and not (ISIG or ECHO or ICANON);

tcsetattr (0, TCSAFLUSH, attr);

(* Открыть выходной файл *)

ofile := fdopen ('output', Open_CREAT or Open_WRONLY or Open_TRUNC, octal(0666));

(* Задать битовые маски для системного вызова select *)

FD_ZERO (master);

FD_SET (0, master);

FD_SET (mfd, master);

(* Вызов select осуществляется без таймаута,

* и будет заблокирован до наступления события. *)

_set := master;

while select (mfd + 1, @_set, nil, nil, nil) > 0 do

begin

(* Проверить стандартный ввод *)

if FD_ISSET (0, _set) then

begin

nread := fdread (0, buf, 512);

fdwrite (mfd, buf, nread);

end;

(* Проверить главное устройство *)

if FD_ISSET (mfd, _set) then

begin

nread := fdread (mfd, buf, 512);

write (ofile, buf, nread);

write (1, buf, nread);

end;

_set := master;

end;

end;

Следующий сеанс демонстрирует работу программы tscript. Комментарии, обозначенные символом #, показывают, какая из оболочек выполняется в данный момент.

$ ./tscript

$ ls -l tscript # работает новая оболочка

-rwxr-xr-x 1 spate fcf 6984 Jan 22 21:57 tscript

$ head -2 /etc/passwd # выполняется в новой оболочке

root:х:0:1:0000-Admin(0000):/:/bin/ksh

daemon:x:1:1:0000-Admin(0000):/:

$ exit # выход из новой оболочки

$ cat output # работает исходная оболочка

-rwxr-xr-x 1 spate fcf 6984 Jan 22 21:57 tscript

root:х:0:1:0000-Admin(0000):/:/bin/ksh

daemon:x:1:1:0000-Admin(0000):/:

Упражнение 9.5. Добавьте к программе обработку ошибок и возможность задания в качестве параметра имени выходного файла. Если имя не задано, используйте по умолчанию имя output.

Упражнение 9.6. Эквивалентная стандартная программа UNIX script позволяет задать параметр , который указывает на необходимость дополнения файла output (содержимое файла не уничтожается). Реализуйте аналогичную возможность в программе tscript.

Соседние файлы в папке Полищук, Семериков. Системное программирование в UNIX средствами Free Pascal