Системное программирование в UNIX средствами Free Pascal

         

Пример управления терминалом: программа 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.


Содержание раздела