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

       

Функция runshell из файла stdio,


uses stdio;
function runshell(comstring:pchar):longint;
uses linux;
function shell(comstring:pchar):longint;
Функция runshell из файла stdio, как и shell из linux, выполняет команду, заданную строкой comstring. Вначале она создает дочерний процесс, который, в свою очередь, осуществляет вызов exec для запуска стандартного командного интерпретатора UNIX с командной строкой comstring. В это время процедура runshell в первом процессе выполняет вызов wait, гарантируя тем самым, что выполнение продолжится только после того, как запущенная команда завершится. Возвращаемое после этого значение retval содержит статус выхода командного интерпретатора, по которому можно определить, было ли выполнение программы успешным или нет. В случае неудачи любого из вызовов fork или exec значение переменной retval будет равно –1.
Поскольку в качестве посредника выступает командный интерпретатор, строка comstring может содержать любую команду, которую можно набрать на терминале. Это позволяет программисту воспользоваться такими преимуществами командного интерпретатора, как перенаправление ввода/вывода, поиск файлов в пути и т.д. Следующий оператор использует процедуру runshell для создания подкаталога при помощи программы mkdir:
retval := runshell('mkdir workdir');
if retval <> 0 then
  writeln(stderr, 'Процедура runshell вернула значение ', retval);
Остановимся на некоторых важных моментах. Во-первых, процедура runshell в вызывающем процессе будет игнорировать сигналы SIGINT и SIGQUIT. Это позволяет пользователю прерывать выполнение команды, не затрагивая родительский процесс. Во-вторых, команда, выполняемая процедурой runshell, будет наследовать из вызывающего процесса некоторые открытые дескрипторы файлов. В частности, стандартный ввод команды будет получен из того же источника, что и в родительском процессе. При вводе из файла могут возникнуть проблемы, если процедура
runshell используется для запуска интерактивной программы, так как ввод программы также будет производиться из файла.
Процедура runshell имеет один серьезный недостаток. Он не позволяет программе получать доступ к выводу запускаемой программы. Для этого можно использовать две другие процедуры из стандартной библиотеки ввода/вывода: pipeopen/popen и pipeclose/pclose.




uses stdio;
function pipeopen(comstring, _type:pchar):pfile;
function pipeclose(strm:pfile):integer;
Procedure POpen(Var F:FileType; comstring:pathstr; _type:char);
Function PClose(Var F:FileType):longint;
Как и процедура runshell, процедуры popen и pipeopen создает дочерний процесс командного интерпретатора для запуска команды, заданной параметром comstring. Но, в отличие от процедуры runshell, она также создает канал между вызывающим процессом и командой. При этом pipeopen возвращает структуру TFILE, связанную с этим каналом, а popen – переменную файлового типа. Если значение параметра _type равно w, то программа может выполнять запись в стандартный ввод при помощи структуры TFILE. Если же значение параметра _type равно r, то программа сможет выполнять чтение из стандартного вывода программы. Таким образом, процедуры popen и pipeopen представляют простой и понятный метод взаимодействия с другой программой.
Для закрытия потока, открытого при помощи процедуры popen, должна всегда использоваться процедура pclose. Она будет ожидать завершения команды, после чего вернет статус ее завершения.
Пример использования POpen:
uses linux;
var f : text;
    i : longint;
   
begin
  writeln ('Creating a shell script to which echoes its arguments');
  writeln ('and input back to stdout');
  assign (f,'test21a');
  rewrite (f);
  writeln (f,'#!/bin/sh');
  writeln (f,'echo this is the child speaking.... ');
  writeln (f,'echo got arguments \*"$*"\*');
  writeln (f,'cat');
  writeln (f,'exit 2');
  writeln (f);
  close (f);
  chmod ('test21a',octal (755));
  popen (f,'./test21a arg1 arg2','W');
  if linuxerror<>0 then
     writeln ('error from POpen : Linuxerror : ', Linuxerror);
  for i:=1 to 10 do
    writeln (f,'This is written to the pipe, and should appear on stdout.');
  Flush(f);
  Writeln ('The script exited with status : ',PClose (f));
  writeln;
  writeln ('Press <return> to remove shell script.');


  readln;
  assign (f,'test21a');
  erase (f)
end.
Следующий пример, процедура getlist, использует процедуру popen и команду ls для вывода списка элементов каталога. Каждое имя файла затем помещается в двухмерный массив символов, адрес которого передается процедуре getlist в качестве параметра.
(* getlist - процедура для получения списка файлов в каталоге *)
uses stdio, strings;
const
  MAXLEN=255; (* Максимальная длина имени файла *)
  MAXCMD=100; (* Максимальная длина команды *)
  ERROR=-1;
  SUCCESS=0;
type
  sarray=array [0..MAXLEN] of char;
  darray=array [0..MAXCMD] of sarray;
 
function getlist(namepart:pchar; var dirnames:darray;
                 maxnames:integer):integer;
var
  cmd:array [0..MAXCMD] of char;
  in_line:array [0..MAXLEN+1] of char;
  i:integer;
  lsf:pfile;
begin
  (* Основная команда *)
  strcopy(cmd, 'ls ');
  (* Дополнительные параметры команды *)
  if namepart <> nil then
    strlcat(cmd, namepart, MAXCMD - strlen(cmd));
  lsf := pipeopen(cmd, 'r'); (* Запускаем команду *)
  if lsf = nil then
  begin
    getlist:=ERROR;
    exit;
  end;
  for i:=0 to maxnames-1 do
  begin
    if fgets(in_line, MAXLEN+2, lsf) = nil then
      break;
    (* Удаляем символ перевода строки *)
    if in_line[strlen(in_line)-1] = #$a then
      in_line[strlen(in_line)-1] := #0;
    strcopy(dirnames[i], in_line);
  end;
  if i < maxnames then
    dirnames[i][0] := #0;
  pipeclose (lsf);
  getlist:=SUCCESS;
end;
var
  namebuf:darray;
  i:integer;
begin
  getlist('*.pas', namebuf, 100);
  i:=0;
  while namebuf[i][0]<>#0 do
  begin
    writeln(namebuf[i]);
    inc(i);
  end;
end.
Процедура getlist может быть вызвана следующим образом:
getlist('*.pas', namebuf, 100);
при этом в переменную namebuf будут помещены имена всех Паскаль-программ в текущем каталоге.
Следующий пример разрешает обычную проблему, с который часто сталкиваются администраторы UNIX: как быстро «освободить» терминал, который был заблокирован какой-либо программой, например, неотлаженной программой, работающей с экраном. Программа unfreeze принимает в качестве аргументов имz терминала и список программ. Затем она запускает команду вывода списка процессов ps при помощи процедуры popen для получения списка связанных с терминалом процессов и выполняет поиск указанных программ в этом списке процессов. Далее программа unfreeze запрашивает разрешение пользователя на завершение работы каждого из процессов, удовлетворяющих критерию.


Программа ps
сильно привязана к конкретной системе. Это связано с тем, что она напрямую обращается к ядру (через специальный файл, представляющий образ системной памяти) для получения системной таблицы процессов. На системе, использованной при разработке этого примера, команда ps
имеет синтаксис
$ ps -t ttyname
где ttyname является именем специального файла терминала в каталоге /dev, например, tty1, console, pts/8 и др. Выполнение этой команды ps
дает следующий вывод:
PID TTY TIME  COMMAND
29  со  0:04  sh
39  со  0:49  vi
42  со  0:00  sh
43  со  0:01  ps
Первый столбец содержит идентификатор процесса. Второй – имя терминала, в данном случае со
соответствует консоли. В третьем столбце выводится суммарное время выполнения процесса. В последнем, четвертом, столбце выводится имя выполняемой программы. Обратите внимание на первую строку, которая является заголовком. В программе unfreeze, текст которой приведен ниже, нам потребуется ее пропустить.
(* Программа unfreeze - освобождение терминала *)
uses stdio,linux,strings;
const
  LINESZ =150;
  SUCCESS=0;
  ERROR  =(-1);
const
  killflag:integer=0;
  (* Инициализация этой переменной зависит от вашей системы *)
  pspart:pchar = 'ps t ';
  fmt:pchar = '%d %*s %*s %*s %s';
var
  comline, inbuf, header, name:array [0..LINESZ-1] of char;
  f:pfile; 
  j:integer;
  pid:longint;
begin
  if paramcount <2 then
  begin
    writeln (stderr, 'синтаксис: ',paramstr(0),' терминал программа ...');
    halt (1);
  end;
  (* Сборка командной строки *)
  strcopy (comline, pspart);
  strcat (comline, argv[1]);
  (* Запуск команды ps *)
  f := pipeopen (comline, 'r');
  if f = nil then
  begin
    writeln (stderr, paramstr(0),': не могу запустить команду ps ');
    halt (2);
  end;
  (* Получить первую строку от ps и игнорировать ее *)
  if fgets (header, LINESZ, f) = nil then
  begin
    writeln (stderr, paramstr(0),': нет вывода от ps?');
    halt (3);


  end;
  (* Поиск программы, которую нужно завершить *)
  while fgets (inbuf, LINESZ, f) <> nil do
  begin
    if sscanf (inbuf, fmt, [@pid, pchar(name)]) < 2 then
      break;
    for j := 2 to argc-1 do
    begin
      if strcomp (name, argv[j]) = 0 then
      begin
        if dokill (pid, inbuf, header) = SUCCESS then
          inc(killflag);
      end;
    end;
  end;
  (* Это предупреждение, а не ошибка *)
  if killflag=0 then
    writeln(stderr, paramstr(0),': работа программы не завершена ',
            paramstr(1));
  pipeclose(f);
  halt (0);
end.
Ниже приведена реализация процедуры dokill, вызываемой программой unfreeze. Обратите внимание на использование процедуры readln для чтений первого не пробельного символа (вместо нее можно было бы использовать и функцию
yesno, представленную в разделе 11.8).
(* Получить подтверждение, затем завершить работу программы *)
function dokill(procid:longint;line,hd:pchar):integer;
var
  c:char;
begin
  writeln (#$a'Найден процесс, выполняющий заданную программу :');
  writeln (#9,hd,#9,line);
  writeln ('Нажмите `y` для завершения процесса ', procid);
  write (#$a'Yes\No? > ');
  (* Введите следующий не пробельный символ *)
  readln (c);
  if (c = 'y') or (c = 'Y') then
  begin
    kill (procid, SIGKILL);
    dokill:=SUCCESS;
    exit;
  end;
  dokill:=ERROR;
end;
Упражнение 11.9. Напишите свою версию процедуры getcwd, которая возвращает строку с именем текущего рабочего каталога. Назовите вашу программу wdir. Совет: используйте стандартную команду pwd.
Упражнение 11.10. Напишите программу arrived, которая запускает программу who при помощи процедуры popen для проверки (с 60-секундными интервалами), находится ли в системе пользователи из заданного списка. Список пользователей должен передаваться программе arrived в качестве аргумента командной строки. При обнаружении кого-нибудь из перечисленных в списке пользователей, программа arrived должна выводить сообщение. Программа должна быть достаточно эффективной, для этого используйте вызов sleep между выполнением проверок. Программа who должна быть описана в справочном руководстве системы.

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