Online Documentation Server
 ПОИСК
ods.com.ua Web
 КАТЕГОРИИ
Home
Programming
Net technology
Unixes
Security
RFC, HOWTO
Web technology
Data bases
Other docs

 

 ПОДПИСКА

 О КОПИРАЙТАХ
Вся предоставленная на этом сервере информация собрана нами из разных источников. Если Вам кажется, что публикация каких-то документов нарушает чьи-либо авторские права, сообщите нам об этом.




6.  Системные вызовы и взаимодействие с UNIX.
     В этой главе речь пойдет о процессах.  Скомпилированная  программа  хранится  на
диске  как  обычный нетекстовый файл. Когда она будет загружена в память компьютера и
начнет выполняться - она станет процессом.
     UNIX - многозадачная система (мультипрограммная).  Это  означает,  что  одновре-
менно может быть запущено много процессов. Процессор выполняет их в режиме разделения
времени  -  выделяя  по  очереди  квант  времени  одному  процессу,  затем   другому,
третьему...  В результате создается впечатление параллельного выполнения всех процес-
сов (на многопроцессорных  машинах  параллельность  истинная).  Процессам,  ожидающим
некоторого  события,  время  процессора  не  выделяется. Более того, "спящий" процесс
может быть временно откачан (т.е. скопирован из памяти машины) на диск, чтобы освобо-
дить  память для других процессов.  Когда "спящий" процесс дождется события, он будет
"разбужен" системой, переведен в ранг "готовых к выполнению" и, если  был  откачан  -
будет  возвращен  с  диска  в память (но, может быть, на другое место в памяти!). Эта
процедура носит название "своппинг" (swapping).
     Можно запустить несколько процессов, выполняющих программу из одного и  того  же
файла;  при  этом  все они будут (если только специально не было предусмотрено иначе)
независимыми друг от друга. Так, у каждого пользователя, работающего в системе,  име-
ется  свой  собственный  процесс-интерпретатор команд (своя копия), выполняющий прог-
рамму из файла /bin/csh (или /bin/sh).
     Процесс представляет собой изолированный "мир", общающийся с другими "мирами" во
Вселенной при помощи:
a)   Аргументов функции main:
       void main(int argc, char *argv[], char *envp[]);
     Если мы наберем команду
       $ a.out a1 a2 a3
     то функция main программы из файла a.out вызовется с

           argc    = 4  /* количество аргументов */
           argv[0] = "a.out"       argv[1] = "a1"
           argv[2] = "a2"          argv[3] = "a3"
           argv[4] = NULL

     По соглашению argv[0] содержит имя выполняемого файла из которого загружена  эта
     программа[*].
b)   Так называемого "окружения" (или "среды") char *envp[], продублированного  также
     в предопределенной переменной
       extern char **environ;
     Окружение состоит из строк вида
       "ИМЯПЕРЕМЕННОЙ=значение"
     Массив этих строк завершается NULL (как и argv).  Для получения  значения  пере-
     менной с именем ИМЯ существует стандартная функция
       char *getenv( char *ИМЯ );
     Она выдает либо значение, либо NULL если переменной с таким именем нет.
c)   Открытых файлов. По умолчанию (неявно) всегда открыты 3 канала:
                        ВВОД         В Ы В О Д
     FILE *             stdin     stdout   stderr
     соответствует fd     0         1        2
     связан с        клавиатурой     дисплеем


____________________
   [*] Именно это имя показывает команда ps -ef

    #include <stdio.h>
    main(ac, av) char **av; {
      execl("/bin/sleep", "Take it easy", "1000", NULL);
    }






А. Богатырев, 1992-95                  - 187 -                              Си в UNIX

     Эти каналы достаются процессу "в наследство" от запускающего процесса и  связаны
     с  дисплеем и клавиатурой, если только не были перенаправлены. Кроме того, прог-
     рамма может сама явно открывать файлы (при помощи  open,  creat,  pipe,  fopen).
     Всего  программа  может  одновременно  открыть  до 20 файлов (считая стандартные
     каналы), а в некоторых системах и больше (например, 64).  В MS DOS  есть  еще  2
     предопределенных  канала  вывода:  stdaux  - в последовательный коммуникационный
     порт, stdprn - на принтер.
d)   Процесс имеет уникальный номер, который он может узнать вызовом
       int pid = getpid();
     а также узнать номер "родителя" вызовом
       int ppid = getppid();
     Процессы могут по этому номеру посылать друг другу сигналы:
       kill(pid /* кому */, sig /* номер сигнала */);
     и реагировать на них
       signal (sig /*по сигналу*/, f /*вызывать f(sig)*/);
e)   Существуют и другие средства коммуникации процессов: семафоры, сообщения,  общая
     память, сетевые коммуникации.
f)   Существуют некоторые другие параметры (контекст) процесса: например, его текущий
     каталог,  который  достается  в  наследство от процесса-"родителя", и может быть
     затем изменен системным вызовом
       chdir(char *имя_нового_каталога);
     У каждого процесса есть свой собственный текущий рабочий каталог (в  отличие  от
     MS  DOS, где текущий каталог одинаков для всех задач).  К "прочим" характеристи-
     кам отнесем также: управляющий терминал; группу процессов (pgrp);  идентификатор
     (номер)  владельца процесса (uid), идентификатор группы владельца (gid), реакции
     и маски, заданные на различные сигналы; и.т.п.
g)   Издания других запросов (системных вызовов) к операционной системе ("богу")  для
     выполнения различных "внешних" операций.
h)   Все остальные действия происходят внутри процесса и никак не  влияют  на  другие
     процессы  и  устройства ("миры"). В частности, один процесс НИКАК не может полу-
     чить доступ к памяти другого процесса, если тот не позволил ему это явно  (меха-
     низм  shared  memory);  адресные пространства процессов независимы и изолированы
     (равно и пространство ядра изолировано от памяти процессов).

     Операционная система выступает в качестве  коммуникационной  среды,  связывающей
"миры"-процессы, "миры"-внешние устройства (включая терминал пользователя); а также в
качестве распорядителя ресурсов "Вселенной", в частности - времени (по очереди  выде-
ляемого активным процессам) и пространства (в памяти компьютера и на дисках).
     Мы уже неоднократно упоминали "системные вызовы". Что же  это  такое?   С  точки
зрения  Си-программиста  - это обычные функции. В них передают аргументы, они возвра-
щают значения.  Внешне они ничем не отличаются от написанных  нами  или  библиотечных
функций и вызываются из программ одинаковым с ними способом.
     С точки же зрения реализации - есть глубокое различие.   Тело  функции-сисвызова
расположено  не  в  нашей  программе,  а  в резидентной (т.е. постоянно находящейся в
памяти компьютера) управляющей программе,  называемой  ядром  операционной  системы[*].
____________________
   [*] Собственно, операционная система характеризуется набором предоставляемых ею сис-
темных  вызовов,  поскольку  все концепции, заложенные в системе, доступны нам только
через них.  Если мы имеем две реализации  системы  с  разным  внутренним  устройством
ядер,  но  предоставляющие  одинаковый интерфейс системных вызовов (их набор, смысл и
поведение), то это все-таки одна и та же система!  Ядра могут не  просто  отличаться,
но и быть построенными на совершенно различных принципах: так обстоит дело с UNIX-ами
на однопроцессорных и многопроцессорных машинах.  Но  для  нас  ядро  -  это  "черный
ящик",  полностью  определяемый его поведением, т.е. своим интерфейсом с программами,
но не внутренним устройством.  Вторым параметром, характеризующим ОС,  являются  фор-
маты данных, используемые системой: форматы данных для сисвызовов и формат информации
в различных файлах, в том числе формат оформления выполняемых файлов (формат данных в
физической памяти машины в этот список не входит - он зависим от реализации и от про-
цессора).  Как правило, программа пишется так, чтобы использовать соглашения,  приня-
тые  в  данной системе, для чего она просто включает ряд стандартных include-файлов с
описанием этих форматов.  Имена этих файлов также можно отнести к интерфейсу системы.



А. Богатырев, 1992-95                  - 188 -                              Си в UNIX

Сам термин "системный вызов" как раз означает "вызов системы  для  выполнения  дейст-
вия",  т.е.  вызов функции в ядре системы.  Ядро работает в привелегированном режиме,
в котором имеет доступ к некоторым системным таблицам[**], регистрам  и  портам  внешних
устройств и диспетчера памяти, к которым обычным программам доступ аппаратно запрещен
(в отличие от MS DOS, где все таблицы ядра доступны пользовательским программам,  что
создает раздолье для вирусов).  Системный вызов происходит в 2 этапа: сначала в поль-
зовательской программе вызывается библиотечная функция-"корешок", тело которой  напи-
сано на ассемблере и содержит команду генерации программного прерывания.  Это - глав-
ное отличие от нормальных Си-функций - вызов по прерыванию.  Вторым  этапом  является
реакция ядра на прерывание:
1.   переход в привелегированный режим;
2.   разбирательство, КТО обратился к ядру, и подключение  u-area  этого  процесса  к
     адресному пространству ядра (context switching);
3.   извлечение аргументов из памяти запросившего процесса;
4.   выяснение, ЧТО же хотят от ядра (один из аргументов, невидимый нам -  это  номер
     системного вызова);
5.   проверка корректности остальных аргументов;
6.   проверка прав процесса на допустимость выполнения такого запроса;
7.   вызов тела требуемого системного вызова - это обычная Си-функция в ядре;
8.   возврат ответа в память процесса;
9.   выключение привелегированного режима;
10.  возврат из прерывания.

     Во время системного вызова (шаг 7) процесс может "заснуть", дожидаясь некоторого
события (например, нажатия кнопки на клавиатуре).  В это время ядро передаст управле-
ние другому процессу. Когда наш процесс будет "разбужен"  (событие  произошло)  -  он
продолжит выполнение шагов системного вызова.
     Большинство системных вызовов возвращают в программу в качестве своего  значения
признак  успеха: 0 - все сделано, (-1) - сисвызов завершился неудачей; либо некоторое
содержательное значение при успехе (вроде дескриптора файла в open(), и (-1) при неу-
даче.   В  случае неудачного завершения в предопределенную переменную errno заносится
номер ошибки, описывающий причину неудачи  (коды  ошибок  предопределены,  описаны  в
include-файле  <errno.h>  и имеют вид Eчтото).  Заметим, что при УДАЧЕ эта переменная
просто не изменяется и может содержать любой мусор, поэтому проверять ее имеет  смысл
лишь в случае, если ошибка действительно произошла:

    #include <errno.h>      /* коды ошибок */
    extern int errno;
    extern char *sys_errlist[];
    int value;
    if((value = sys_call(...)) < 0 ){
       printf("Error:%s(%d)\n", sys_errlist[errno],
                                errno );
       exit(errno); /* принудительное завершение программы */
    }

____________________
     Поведение всех программ в системе вытекает из поведения системных вызовов, кото-
рыми  они  пользуются. Даже то, что UNIX является многозадачной системой, непосредст-
венно вытекает из наличия системных вызовов fork, exec, wait и спецификации их  функ-
ционирования!
     То же можно сказать про язык Си - мобильность программы зависит  в  основном  от
набора используемых в ней библиотечных функций (и, в меньшей степени, от диалекта са-
мого языка, который должен удовлетворять стандарту на язык Си).  Если две разные сис-
темы  предоставляют  все  эти  функции (которые могут быть по-разному реализованы, но
должны делать одно и то же), то программа будет компилироваться и  работать  в  обоих
системах, более того, работать в них одинаково.
   [**] Таким как таблица процессов, таблица открытых файлов (всех вместе и для  каждого
процесса), и.т.п.





А. Богатырев, 1992-95                  - 189 -                              Си в UNIX

Предопределенный массив sys_errlist, хранящийся в  стандартной  библиотеке,  содержит
строки-расшифровку  смысла  ошибок  (по-английски).  Посмотрите описание функции per-
ror().

6.1.  Файлы и каталоги.

6.1.1.  Используя системный вызов stat, напишите программу, определяющую  тип  файла:
обычный файл, каталог, устройство, FIFO-файл.  Ответ:

    #include <sys/types.h>
    #include <sys/stat.h>

    typeOf( name ) char *name;
    {  int type; struct stat st;
       if( stat( name, &st ) < 0 ){
               printf( "%s не существует\n", name );
               return 0;
       }
       printf("Файл имеет %d имен\n", st.st_nlink);

       switch(type = (st.st_mode & S_IFMT)){
       case S_IFREG:
            printf( "Обычный файл размером %ld байт\n",
                       st.st_size ); break;
       case S_IFDIR:
               printf( "Каталог\n" );      break;
       case S_IFCHR:   /* байтоориентированное  */
       case S_IFBLK:   /* блочноориентированное */
               printf( "Устройство\n" );   break;
       case S_IFIFO:
               printf( "FIFO-файл\n" );    break;
       default:
               printf( "Другой тип\n" );   break;
       }       return type;
     }


6.1.2.  Напишите программу, печатающую: свои аргументы, переменные окружения,  инфор-
мацию  о  всех  открытых  ею файлах и используемых трубах.  Для этой цели используйте
системный вызов

    struct stat st; int used, fd;
    for(fd=0; fd < NOFILE; fd++ ){
      used = fstat(fd, &st) < 0 ? 0 : 1;
      ...
    }

Программа может  использовать  дескрипторы  файлов  с  номерами  0..NOFILE-1  (обычно
0..19).  Если fstat для какого-то fd вернул код ошибки (<0), это означает, что данный
дескриптор не связан с открытым файлом (т.е. не используется).  NOFILE  определено  в
include-файле <sys/param.h>, содержащем разнообразные параметры данной системы.

6.1.3.  Напишите упрощенный аналог команды ls,  распечатывающий  содержимое  текущего
каталога  (файла с именем ".") без сортировки имен по алфавиту.  Предусмотрите чтение
каталога, чье имя задается как аргумент программы.  Имена "." и ".." не выдавать.
     Формат каталога описан в header-файле <sys/dir.h> и в "канонической" версии выг-
лядит  так:  каталог  - это файл, состоящий из структур direct, каждая описывает одно
имя файла, входящего в каталог:






А. Богатырев, 1992-95                  - 190 -                              Си в UNIX

    struct  direct {
       unsigned short d_ino;   /* 2 байта: номер I-узла */
       char    d_name[DIRSIZ]; /* имя файла             */
    };

В семействе BSD формат каталога несколько иной - там записи имеют разную длину, зави-
сящую от длины имени файла, которое может иметь длину от 1 до 256 символов.
     Имя файла может состоять из любых  символов,  кроме  '\0',  служащего  признаком
конца  имени  и  '/', служащего разделителем.  В имени допустимы пробелы, управляющие
символы (но не рекомендуются!), любое число точек (в отличие от MS DOS, где допустима
единственная  точка,  отделяющая  собственно имя от суффикса (расширения)), разрешены
даже непечатные (т.е. управляющие) символы!  Если имя файла имеет длину  14  (DIRSIZ)
символов,  то  оно  не оканчивается байтом '\0'. В этом случае для печати имени файла
возможны три подхода:
1.   Выводить символы при помощи putchar()-а в цикле. Цикл прерывать по индексу  рав-
     ному DIRSIZ, либо по достижению байта '\0'.
2.   Скопировать поле d_name в другое место:

         char buf[ DIRSIZ + 1 ];
         strncpy(buf, d.d_name, DIRSIZ);
         buf[ DIRSIZ ] = '\0';

     Этот способ лучший, если имя файла надо не просто напечатать, но и запомнить  на
     будущее, чтобы использовать в своей программе.
3.   Использовать такую особенность функции printf():

         #include <sys/types.h>
         #include <sys/dir.h>

         struct direct d;
            ...
         printf( "%*.*s\n", DIRSIZ, DIRSIZ, d.d_name );

     Если файл был стерт, то в поле d_ino записи каталога будет содержаться 0 (именно
поэтому  I-узлы нумеруются начиная с 1, а не с 0).  При удалении файла содержимое его
(блоки) уничтожается, I-узел освобождается, но имя в  каталоге  не  затирается  физи-
чески,  а  просто помечается как стертое: d_ino=0; Каталог при этом никак не уплотня-
ется и не укорачивается!  Поэтому имена с d_ino==0 выдавать не следует  -  это  имена
уже уничтоженных файлов.
     При создании нового имени (creat, link, mknod) система просматривает  каталог  и
переиспользует  первый от начала свободный слот (ячейку каталога) где d_ino==0, запи-
сывая новое имя в него (только в этот момент старое имя-призрак окончательно исчезнет
физически).  Если пустых мест нет - каталог удлиняется.
     Любой каталог всегда содержит два стандартных имени: "."  - ссылка  на  этот  же
каталог  (на  его  собственный  I-node),  ".." - на вышележащий каталог.  У корневого
каталога "/" оба этих имени ссылаются на него же самого (т.е. содержат d_ino==2).
     Имя каталога не содержится в нем самом. Оно содержится в "родительском" каталоге
...
     Каталог в UNIX - это обычный дисковый файл. Вы можете читать его из своих  прог-
рамм. Однако никто (включая суперпользователя[**]) не может записывать что-либо в  ката-
лог при помощи write.  Изменения содержимого каталогов выполняет только ядро, отвечая
на запросы в виде системных вызовов creat, unlink, link, mkdir, rmdir, rename, mknod.
Коды  доступа для каталога интерпретируются следующим образом:
w запись
     S_IWRITE.  Означает право создавать и уничтожать в  каталоге  имена  файлов  при
____________________
   [**] Суперпользователь (superuser) имеет uid==0.  Это  "привелегированный"  пользова-
тель,  который имеет право делать ВСЕ. Ему доступны любые сисвызовы и файлы, несмотря
на коды доступа и.т.п.





А. Богатырев, 1992-95                  - 191 -                              Си в UNIX

     помощи этих вызовов. То есть: право создавать, удалять и переименовывать файлы в
     каталоге.   Отметим,  что для переименования или удаления файла вам не требуется
     иметь доступ по записи к самому файлу - достаточно  иметь  доступ  по  записи  к
     каталогу, содержащему его имя!
r чтение
     S_IREAD.  Право читать каталог как обычный файл (право  выполнять  opendir,  см.
     ниже):  благодаря  этому  мы  можем  получить список имен файлов, содержащихся в
     каталоге.  Однако, если мы ЗАРАНЕЕ знаем имена файлов в каталоге, мы МОЖЕМ рабо-
     тать с ними - если имеем право доступа "выполнение" для этого каталога!
x выполнение
     S_IEXEC.  Разрешает поиск в  каталоге.  Для  открытия  файла,  создания/удаления
     файла,  перехода  в другой каталог (chdir), система выполняет следующие действия
     (осуществляемые функцией namei() в ядре): чтение каталога и поиск в нем  указан-
     ного  имени  файла  или  каталога;  найденному  имени соответствует номер I-узла
     d_ino; по номеру узла система считывает с диска сам I-узел нужного  файла  и  по
     нему добирается до содержимого файла.  Код "выполнение" - это как раз разрешение
     такого просмотра каталога системой.  Если каталог имеет доступ на  чтение  -  мы
     можем получить список файлов (т.е. применить команду ls); но если он при этом не
     имеет кода доступа "выполнение" - мы не сможем получить доступа ни к  одному  из
     файлов каталога (ни открыть, ни удалить, ни создать, ни сделать stat, ни chdir).
     Т.е. "чтение" разрешает применение вызова read, а "выполнение"  -  функции  ядра
     namei.   Фактически  "выполнение"  означает "доступ к файлам в данном каталоге";
     еще более точно - к I-nodам файлов этого каталога.
t sticky bit
     S_ISVTX - для каталога он означает, что удалить или переименовать некий  файл  в
     данном  каталоге могут только: владелец каталога, владелец данного файла, супер-
     пользователь.  И никто другой. Это исключает удаление файлов чужими.
Совет: для каталога полезно иметь такие коды доступа:

    chmod o-w,+t каталог

В системах BSD используется, как уже было упомянуто,  формат  каталога  с  переменной
длиной  записей. Чтобы иметь удобный доступ к именам в каталоге, возникли специальные
функции чтения каталога: opendir, closedir, readdir. Покажем, как простейшая  команда
ls реализуется через эти функции.





























А. Богатырев, 1992-95                  - 192 -                              Си в UNIX

    #include <stdio.h>
    #include <sys/types.h>
    #include <dirent.h>

    int listdir(char *dirname){
        register struct dirent *dirbuf;
        DIR *fddir;
        ino_t dot_ino = 0, dotdot_ino = 0;

        if((fddir = opendir (dirname)) == NULL){
            fprintf(stderr, "Can't read %s\n", dirname);
            return 1;
        }
        /* Без сортировки по алфавиту */
        while ((dirbuf = readdir (fddir)) != NULL ) {
            if (dirbuf->d_ino == 0) continue;
            if (strcmp (dirbuf->d_name, "." ) == 0){
                    dot_ino = dirbuf->d_ino;
                    continue;
            } else if(strcmp (dirbuf->d_name, "..") == 0){
                    dotdot_ino = dirbuf->d_ino;
                    continue;
            } else printf("%s\n", dirbuf->d_name);
        }
        closedir (fddir);

        if(dot_ino    == 0) printf("Поврежденный каталог: нет имени \".\"\n");
        if(dotdot_ino == 0) printf("Поврежденный каталог: нет имени \"..\"\n");
        if(dot_ino && dot_ino == dotdot_ino)  printf("Это корневой каталог диска\n");

        return 0;
    }

    int main(int ac, char *av[]){
        int i;

        if(ac > 1) for(i=1; i < ac; i++) listdir(av[i]);
        else                             listdir(".");

        return 0;
    }

Обратите внимание, что тут не требуется добавление '\0' в  конец  поля  d_name,  пос-
кольку его предоставляет нам сама функция readdir().

6.1.4.  Напишите программу удаления файлов и каталогов,  заданных  в  argv.   Делайте
stat,  чтобы  определить тип файла (файл/каталог). Программа должна отказываться уда-
лять файлы устройств.
     Для удаления пустого каталога (не содержащего иных имен, кроме "." и "..")  сле-
дует использовать сисвызов
    rmdir(имя_каталога);
(если каталог не пуст - errno получит значение EEXIST); а для удаления обычных файлов
(не каталогов)
    unlink(имя_файла);
Программа должна запрашивать подтверждение на удаление  каждого  файла,  выдавая  его
имя, тип, размер в килобайтах и вопрос "удалить ?".

6.1.5.  Напишите функцию рекурсивного обхода дерева подкаталогов и печати  имен  всех
файлов в нем. Ключ U42 означает файловую систему с длинными именами файлов (BSD 4.2).





А. Богатырев, 1992-95                  - 193 -                              Си в UNIX

    /*#!/bin/cc -DFIND -DU42 -DMATCHONLY treemk.c match.c -o tree -lx
     * Обход поддерева каталогов (по мотивам Керниган & Ритчи).
     *              Ключи компиляции:
     * BSD-4.2 BSD-4.3                     -DU42
     * XENIX с канонической файл.сист.      ничего
     * XENIX с библиотекой  -lx            -DU42
     *      программа поиска файлов                          -DFIND
     *      программа рекурсивного удаления                  -DRM_REC
     *      программа подсчета используемого места на диске  БЕЗ_КЛЮЧА
     */
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <sys/param.h>         /* для MAXPATHLEN */


    #if defined(M_XENIX) && defined(U42)
    # include <sys/ndir.h>  /* XENIX + U42 эмуляция */
    #else
    # include <dirent.h>
    # define stat(f,s) lstat(f,s)  /* не проходить по символьным ссылкам */
    # define d_namlen d_reclen
    #endif


    /* проверка: каталог ли это */
    #define  isdir(st) ((st.st_mode & S_IFMT) == S_IFDIR)
    struct   stat st;               /* для сисвызова stat() */
    char     buf[MAXPATHLEN+1];     /* буфер для имени файла */

    #define FAILURE (-1)            /* код неудачи */
    #define SUCCESS   1             /* код успеха  */
    #define WARNING   0             /* нефатальная ошибка */
    /* Сообщения об ошибках во время обхода дерева: */
    #ifndef ERR_CANT_READ
    # define ERR_CANT_READ(name)  \
             fprintf( stderr, "\tНе могу читать \"%s\"\n", name), WARNING
    # define ERR_NAME_TOO_LONG()  \
             fprintf( stderr, "\tСлишком длинное полное имя\n" ), WARNING
    #endif


    /* Прототипы для предварительного объявления функций. */
    extern char *strrchr(char *, char);
    int directory (char *name, int level,
        int (*enter)(char *full, int level, struct stat *st),
        int (*leave)(char *full, int level),
        int (*touch)(char *full, int level, struct stat *st));
    /* Функции-обработчики enter, leave, touch должны
     * возвращать (-1) для прерывания просмотра дерева,
     * либо значение >= 0 для продолжения. */













А. Богатырев, 1992-95                  - 194 -                              Си в UNIX

    /* Обойти дерево с корнем в rootdir */
    int walktree (
        char *rootdir,      /* корень дерева */
        int (*enter)(char *full, int level, struct stat *st),
        int (*leave)(char *full, int level),
        int (*touch)(char *full, int level, struct stat *st)
    ){
        /* проверка корректности корня */
        if( stat(rootdir, &st) < 0 || !isdir(st)){
            fprintf( stderr, "\tПлохой корень дерева \"%s\"\n", rootdir );
            return   FAILURE;  /* неудача */
        }
        strcpy     (buf, rootdir);
        return act (buf, 0, enter, leave, touch);
    }


    /* Оценка файла с именем name.
     */
    int act (char *name, int level,
        int (*enter)(char *full, int level, struct stat *st),
        int (*leave)(char *full, int level),
        int (*touch)(char *full, int level, struct stat *st))
    {
        if (stat (name, &st) < 0)
            return WARNING; /* ошибка, но не фатальная      */
        if(isdir(st)){      /* позвать обработчик каталогов */
           if(enter)
              if( enter(name, level, &st) == FAILURE ) return FAILURE;
           return directory (name, level+1, enter, leave, touch);

        } else {            /* позвать обработчик файлов    */
           if(touch) return touch (name, level, &st);
           else      return SUCCESS;
        }
    }




























А. Богатырев, 1992-95                  - 195 -                              Си в UNIX

    /* Обработать каталог: прочитать его и найти подкаталоги */
    int directory (char *name, int level,
        int (*enter)(char *full, int level, struct stat *st),
        int (*leave)(char *full, int level),
        int (*touch)(char *full, int level, struct stat *st))
    {
    #ifndef U42
        struct direct   dirbuf;
        int        fd;
    #else
        register struct dirent *dirbuf;
        DIR    *fd;
        extern DIR *opendir();
    #endif
        char   *nbp, *tail, *nep;
        int     i, retcode = SUCCESS;

    #ifndef U42
        if ((fd = open (name, 0)) < 0) {
    #else
        if ((fd = opendir (name)) == NULL) {
    #endif
            return ERR_CANT_READ(name);
        }

        tail = nbp = name + strlen (name);  /* указатель на закрывающий \0 */
        if( strcmp( name, "/" ))  /* если не "/" */
            *nbp++ = '/';
        *nbp = '\0';

    #ifndef U42
        if (nbp + DIRSIZ + 2 >= name + MAXPATHLEN) {
            *tail = '\0';
            return ERR_NAME_TOO_LONG();
        }
    #endif

    #ifndef U42
        while (read(fd, (char *) &dirbuf, sizeof(dirbuf)) == sizeof(dirbuf)){
            if (dirbuf.d_ino == 0)  /* стертый файл */
                continue;
            if (strcmp (dirbuf.d_name, "." ) == 0  ||
                strcmp (dirbuf.d_name, "..") == 0)  /* не интересуют */
                continue;
            for (i = 0, nep = nbp; i < DIRSIZ; i++)
                *nep++ = dirbuf.d_name[i];

    # else /*U42*/
        while ((dirbuf = readdir (fd)) != NULL ) {
            if (dirbuf->d_ino == 0)
                continue;
            if (strcmp (dirbuf->d_name, "." ) == 0  ||
                strcmp (dirbuf->d_name, "..") == 0)
                continue;
            for (i = 0, nep = nbp; i < dirbuf->d_namlen ; i++)
                *nep++ = dirbuf->d_name[i];
    #endif /*U42*/
            *nep = '\0';
            if( act(name, level,  enter, leave, touch) == FAILURE) {
                retcode = FAILURE; break;                          }
        }



А. Богатырев, 1992-95                  - 196 -                              Си в UNIX

    #ifndef U42
        close (fd);
    #else
        closedir(fd);
    #endif
        *tail = '\0';       /* восстановить старое name */

        if(retcode != FAILURE   &&   leave)
           if( leave(name, level) == FAILURE) retcode = FAILURE;
        return retcode;
    }


    /* -------------------------------------------------------------- */
    /* Disk Usage -- Оценка места, занимаемого файлами поддерева      */
    /* -------------------------------------------------------------- */
    /* Пересчет байтов в килобайты */
    #define KB(s)  (((s)/1024L) + ((s)%1024L ? 1L:0L))
    /* или #define KB(s)   (((s) + 1024L - 1) / 1024L)  */
    long size;                      /* общий размер     */
    long nfiles;                    /* всего файлов     */
    long ndirs;                     /* из них каталогов */
    #define WARNING_LIMIT 150L      /* подозрительно большой файл */

    static int du_touch (char *name, int level, struct stat *st){
         long sz;
         size += (sz = KB(st->st_size));  /* размер файла в Кб. */
         nfiles++;
    #ifndef TREEONLY
         if( sz >= WARNING_LIMIT )
            fprintf(stderr,"\tВнимание! \"%s\" очень большой: %ld Кб.\n",
                                          name,               sz);
    #endif /*TREEONLY*/
         return SUCCESS;
    }
    static int du_enter (char *name, int level, struct stat *st){
    #ifndef TREEONLY
         fprintf( stderr, "Каталог \"%s\"\n", name );
    #endif
         size += KB(st->st_size);  /* размер каталога в Кб. */
         nfiles++; ++ndirs; return SUCCESS;
    }
    long du (char *name){
         size = nfiles = ndirs = 0L;
         walktree(name, du_enter, NULL, du_touch );
         return size;
    }

















А. Богатырев, 1992-95                  - 197 -                              Си в UNIX

    /* -------------------------------------------------------------- */
    /* Рекурсивное удаление файлов и каталогов                        */
    /* -------------------------------------------------------------- */
    int  deleted;    /* сколько файлов и каталогов удалено */
    static int recrm_dir (char *name, int level){
         if( rmdir(name) >= 0){ deleted++; return SUCCESS; }
         fprintf(stderr, "Не могу rmdir '%s'\n", name); return WARNING;
    }

    static int recrm_file(char *name, int level, struct stat *st){
         if( unlink(name) >= 0){ deleted++; return SUCCESS; }
         fprintf(stderr, "Не могу rm    '%s'\n", name); return WARNING;
    }
    int recrmdir(char *name){
        int ok_code; deleted = 0;
        ok_code = walktree(name, NULL, recrm_dir, recrm_file);
        printf("Удалено %d файлов и каталогов в %s\n", deleted, name);
        return ok_code;
    }


    /* -------------------------------------------------------------- */
    /* Поиск файлов с подходящим именем (по шаблону имени)            */
    /* -------------------------------------------------------------- */
    char *find_PATTERN;
    static int find_check(char *fullname, int level, struct stat *st){
        char *basename = strrchr(fullname, '/');
        if(basename) basename++;
        else         basename = fullname;
        if( match(basename, find_PATTERN))
            printf("Level#%02d %s\n", level, fullname);
        if( !strcmp( basename, "core")){
            printf("Найден дамп %s, поиск прекращен.\n", fullname);
            return FAILURE;
        }
        return SUCCESS;
    }
    void find (char *root, char *pattern){
         find_PATTERN = pattern;
         walktree(root, find_check, NULL, find_check);
    }























А. Богатырев, 1992-95                  - 198 -                              Си в UNIX

    /* -------------------------------------------------------------- */
    #ifndef TREEONLY
    void main(int argc, char *argv[]){
    #ifdef FIND
         if(argc != 3){ fprintf(stderr, "Arg count\n"); exit(1); }
         find(argv[1], argv[2]);
    #else
    # ifdef RM_REC
         for(argv++; *argv; argv++)
             recrmdir(*argv);
    # else
         du( argc == 1 ? "." : argv[1] );
         printf( "%ld килобайт в %ld файлах.\n", size, nfiles );
         printf( "%ld каталогов.\n", ndirs );
    # endif
    #endif
         exit(0);
    }
    #endif /*TREEONLY*/


6.1.6.  Используя предыдущий алгоритм, напишите  программу  рекурсивного  копирования
поддерева  каталогов в другое место. Для создания новых каталогов используйте систем-
ный вызов
    mkdir(имя_каталога, коды_доступа);

6.1.7.  Используя тот же алгоритм, напишите программу удаления каталога, которая уда-
ляет  все  файлы  в  нем и, рекурсивно, все его подкаталоги. Таким образом, удаляется
дерево каталогов.  В UNIX подобную операцию выполняет команда
    rm -r имя_каталога_корня_дерева

6.1.8.  Используя все тот же алгоритм обхода, напишите аналог команды  find,  который
будет позволять:
-    находить все файлы, чьи имена удовлетворяют заданному шаблону (используйте функ-
     цию match() из главы "Текстовая обработка");
-    находить все выполняемые файлы: обычные файлы S_IFREG, у которых

            (st.st_mode & 0111) != 0

     Как уже ясно, следует пользоваться вызовом stat для проверки каждого файла.

6.2.  Время в UNIX.

6.2.1.  Напишите функцию, переводящую год, месяц, день,  часы,  минуты  и  секунды  в
число  секунд, прошедшее до указанного момента с 00 часов 00 минут 00 секунд 1 Января
1970 года.  Внимание: результат должен иметь тип long (точнее time_t).
     Эта функция облегчит вам сравнение двух моментов времени, заданных в  общеприня-
том  "человеческом"   формате,  поскольку  сравнить два long числа гораздо проще, чем
сравнивать по очереди годы, затем, если они равны - месяцы, если месяцы равны - даты,
и.т.д.;  а  также облегчит измерение интервала между двумя событиями - он вычисляется
просто как разность двух чисел.  В  системе  UNIX  время  обрабатывается  и  хранится
именно  в  виде  числа секунд; в частности текущее астрономическое время можно узнать
системным вызовом

    #include <sys/types.h>
    #include <time.h>
    time_t t = time(NULL);  /* time(&t); */

Функция

    struct tm *tm = localtime( &t );



А. Богатырев, 1992-95                  - 199 -                              Си в UNIX

разлагает число секунд на отдельные составляющие, содержащиеся в int-полях структуры:

    tm_year  год           (надо прибавлять 1900)
    tm_yday  день в году   0..365
    tm_mon   номер месяца  0..11 (0 - Январь)
    tm_mday  дата месяца   1..31
    tm_wday  день недели   0..6  (0 - Воскресенье)
    tm_hour  часы          0..23
    tm_min   минуты        0..59
    tm_sec   секунды       0..59

Номера месяца и дня недели начинаются с  нуля,  чтобы  вы  могли  использовать  их  в
качестве индексов:

    char *months[] = { "Январь", "Февраль", ..., "Декабрь" };
    printf( "%s\n", months[ tm->tm_mon ] );

Пример использования этих функций есть в приложении.
     Установить время в системе может суперпользователь вызовом
     stime(&t);

6.2.2.  Напишите  функцию  печати  текущего  времени  в  формате  ЧЧ:ММ:СС ДД-МЕС-ГГ.
Используйте системный вызов time() и функцию localtime().
     Существует стандартная функция ctime(), которая печатает время в формате:

    /* Mon Mar 25 18:56:36 1991 */
    #include <stdio.h>
    #include <time.h>
    main(){ /* команда date */
            time_t t = time(NULL);
            char *s  = ctime(&t);
            printf("%s", s);
    }

Обратите внимание, что строка s уже содержит на конце символ '\n'.

6.2.3.  Структура stat, заполняемая системным  вызовом  stat(),  кроме  прочих  полей
содержит  поля типа time_t st_ctime, st_mtime и st_atime - время последнего изменения
содержимого I-узла файла, время последнего изменения файла и время последнего доступа
к файлу.
-    Поле st_ctime изменяется (устанавливается равным текущему астрономическому  вре-
     мени)  при  применении к файлу вызовов creat, chmod, chown, link, unlink, mknod,
     utime[*], write (т.к. изменяется длина файла); Это поле следует рассматривать  как
     время модификации прав доступа к файлу;
-    st_mtime - write, creat, mknod, utime; Это поле следует рассматривать как  время
     модификации содержимого файла (данных);
-    st_atime - read, creat, mknod, utime; Это поле следует рассматривать  как  время
     чтения содержимого файла (данных).
Модифицируйте функцию typeOf(), чтобы она печатала еще и эти даты.


____________________
   [*] Время модификации файла можно изменить на текущее  астрономическое  время  и  не
производя записи в файл.  Для этого используется вызов

    utime(имяФайла, NULL);

Он используется для взаимодействия с программой make -  в  команде  touch.   Изменить
время можно только своему файлу.





А. Богатырев, 1992-95                  - 200 -                              Си в UNIX

6.2.4.  Напишите аналог команды ls -tm, выдающей список имен  файлов  текущего  ката-
лога,  отсортированный  по  убыванию  поля st_mtime, то есть недавно модифицированные
файлы выдаются первыми.  Для каждого прочитанного  из  каталога  имени  надо  сделать
stat; имена файлов и времена следует сохранить в массиве структур, а затем отсортиро-
вать его.

6.2.5.  Напишите аналогичную программу, сортирующую файлы в  порядке  возрастания  их
размера (st_size).

6.2.6.  Напишите аналог команды ls -l, выдающий имена файлов каталога и их коды  дос-
тупа в формате rwxrw-r--.  Для получения кодов доступа используйте вызов stat

    stat( имяФайла, &st);
    кодыДоступа = st.st_mode & 0777;

Для изменения кодов доступа используется вызов

    chmod(имя_файла, новые_коды);

Можно изменять коды доступа, соответствующие битовой маске

    0777 | S_ISUID | S_ISGID | S_ISVTX

(смотри <sys/stat.h>).  Тип файла (см. функцию typeOf) не может быть изменен.   Изме-
нить коды доступа к файлу может только его владелец.
     Печатайте еще номер I-узла файла: поле d_ino каталога либо поле st_ino структуры
stat.

6.2.7.  Вот программа, которая каждые 2 секунды проверяет - не изменилось ли содержи-
мое текущего каталога:

    #include <sys/types.h>
    #include <sys/stat.h>
    extern char *ctime();
    main(){
       time_t last; struct stat st;
       for( stat(".", &st), last=st.st_mtime; ; sleep(2)){
            stat(".", &st);
            if(last != st.st_mtime){
               last  = st.st_mtime;
    printf("Был создан или удален какой-то файл: %s",
                       ctime(&last));
            }
       }
    }

Модифицируйте ее, чтобы она сообщала какое имя (имена) было удалено или создано  (для
этого  надо  при  запуске  программы прочитать и запомнить содержимое каталога, а при
обнаружении модификации - перечитать каталог и сравнить его с прежним содержимым).

6.2.8.  Напишите по аналогии программу, которая выдает сообщение, если указанный вами
файл был кем-то прочитан, записан или удален. Вам следует отслеживать изменение полей
st_atime, st_mtime и значение stat() < 0 соответственно. Если файл удален - программа
завершается.

6.2.9.  Современные UNIX-машины имеют встроенные таймеры (как  правило  несколько)  с
довольно  высоким разрешением. Некоторые из них могут использоваться как "будильники"
с обратным отсчетом времени: в таймер загружается некоторое  значение;  таймер  ведет
обратный  отсчет, уменьшая загруженный счетчик; как только это время истекает - посы-
лается сигнал процессу, загрузившему таймер.




А. Богатырев, 1992-95                  - 201 -                              Си в UNIX

     Вот как, к примеру, выглядит функция задержки в микросекундах (миллионных  долях
секунды).   Примечание:  эту  функцию  не следует использовать вперемежку с функциями
sleep и alarm (смотри статью про них ниже, в главе про сигналы).

    #include <sys/types.h>
    #include <signal.h>
    #include <sys/time.h>

    void do_nothing() {}

    /* Задержка на usec миллионных долей секунды (микросекунд) */
    void usleep(unsigned int usec) {

            struct itimerval        new, old;
            /*  struct itimerval  содержит поля:
                struct timeval    it_interval;
                struct timeval    it_value;

                Где struct timeval содержит поля:
                long    tv_sec;    -- число целых секунд
                long    tv_usec;   -- число микросекунд
             */
            struct sigaction        new_vec, old_vec;


            if (usec == 0) return;

            /* Поле tv_sec  содержит число целых секунд.
               Поле tv_usec содержит число микросекунд.

               it_value    - это время, через которое В ПЕРВЫЙ раз
                             таймер "прозвонит",
                             то есть пошлет нашему процессу
                             сигнал SIGALRM.

                             Время, равное нулю, немедленно остановит таймер.

               it_interval - это интервал времени, который будет загружаться
                             в таймер после каждого "звонка"
                             (но не в первый раз).

                             Время, равное нулю, остановит таймер
                             после его первого "звонка".
             */
            new.it_interval.tv_sec  = 0;
            new.it_interval.tv_usec = 0;
            new.it_value.tv_sec  = usec / 1000000;
            new.it_value.tv_usec = usec % 1000000;
















А. Богатырев, 1992-95                  - 202 -                              Си в UNIX

            /* Сохраняем прежнюю реакцию на сигнал SIGALRM в old_vec,
               заносим в качестве новой реакции do_nothing()
             */
            new_vec.sa_handler = do_nothing;
            sigemptyset(&new_vec.sa_mask);
            new_vec.sa_flags = 0;

            sighold(SIGALRM);
            sigaction(SIGALRM, &new_vec, &old_vec);

            /* Загрузка интервального таймера значением new, начало отсчета.
             * Прежнее значение спасти в old.
             * Вместо &old можно также NULL - не спасать.
             */
            setitimer(ITIMER_REAL, &new, &old);

            /* Ждать прихода сигнала SIGALRM */
            sigpause(SIGALRM);


            /* Восстановить реакцию на SIGALRM */
            sigaction(SIGALRM, &old_vec, (struct sigaction *) 0);
            sigrelse(SIGALRM);

            /* Восстановить прежние параметры таймера */
            setitimer(ITIMER_REAL, &old, (struct itimerval *) 0);
    }


6.2.10.  Второй пример использования таймера  -  это  таймер,  отсчитывающий  текущее
время суток (а также дату).  Чтобы получить значение этого таймера используется вызов
функции gettimeofday

    #include <time.h>

    void main(){
            struct timeval timenow;

            gettimeofday(&timenow, NULL);
            printf("%u sec, %u msec\n",
                    timenow.tv_sec,
                    timenow.tv_usec
            );
            printf("%s", ctime(&timenow.tv_sec));
            exit(0);
    }

Поле tv_sec содержит число секунд, прошедшее с полуночи 1 января 1970 года до данного
момента;  в  чем  полностью соответствует системному вызову time.  Однако плюс к тому
поле tv_usec содержит число миллионных долей текущей  секунды  (значение  этого  поля
всегда меньше 1000000).

6.2.11.  К данному параграфу вернитесь, изучив раздел про fork()  и  exit().   Каждый
процесс  может пребывать в двух фазах: системной (внутри тела системного вызова - его
выполняет для нас ядро операционной системы) и пользовательской  (внутри  кода  самой
программы). Время, затраченное процессом в каждой фазе, может быть измеряно системным
вызовом times(). Кроме того, этот вызов позволяет узнать суммарное время, затраченное
порожденными  процессами  (порожденными  при помощи fork).  Системный вызов заполняет
структуру





А. Богатырев, 1992-95                  - 203 -                              Си в UNIX

    struct tms {
            clock_t tms_utime;
            clock_t tms_stime;
            clock_t tms_cutime;
            clock_t tms_cstime;
    };

и возвращает значение

    #include <sys/times.h>

    struct tms time_buf;
    clock_t real_time = times(&time_buf);

Все времена измеряются в "тиках" - некоторых долях секунды.  Число  тиков  в  секунде
можно узнать таким системным вызовом (в системе Solaris):

    #include <unistd.h>
    clock_t HZ = sysconf(_SC_CLK_TCK);

В старых системах, где таймер работал от сети переменного тока, это число  получалось
равным 60 (60 Герц - частота сети переменного тока).  В современных системах это 100.
Поля структуры содержат:
tms_utime
     время, затраченное вызывающим процессом  в пользовательской фазе.
tms_stime
     время, затраченное вызывающим процессом  в системной фазе.
tms_cutime
     время, затраченное порожденными процессами  в пользовательской фазе:  оно  равно
     сумме  всех tms_utime и tms_cutime порожденных процессов (рекурсивное суммирова-
     ние).
tms_cstime
     время, затраченное порожденными процессами  в системной фазе:  оно  равно  сумме
     всех tms_stime и tms_cstime порожденных процессов (рекурсивное суммирование).
real_time
     время, соответствующее астрономическому времени  системы.   Имеет  смысл  мерять
     только их разность.
Вот пример программы:

    #include <stdio.h>
    #include <unistd.h>     /* _SC_CLK_TCK */
    #include <signal.h>     /* SIGALRM */
    #include <sys/time.h>   /* не используется */
    #include <sys/times.h>  /* struct tms */

    struct tms tms_stop,  tms_start;
    clock_t    real_stop, real_start;

    clock_t HZ;     /* число ticks в секунде */















А. Богатырев, 1992-95                  - 204 -                              Си в UNIX

    /* Засечь время момента старта процесса */
    void hello(void){
            real_start = times(&tms_start);
    }
    /* Засечь время окончания процесса */
    void bye(int n){
            real_stop = times(&tms_stop);
    #ifdef CRONO
            /* Разность времен */
            tms_stop.tms_utime -= tms_start.tms_utime;
            tms_stop.tms_stime -= tms_start.tms_stime;
    #endif


            /* Распечатать времена */
            printf("User   time          = %g seconds [%lu ticks]\n",
              tms_stop.tms_utime / (double)HZ, tms_stop.tms_utime);
            printf("System time          = %g seconds [%lu ticks]\n",
              tms_stop.tms_stime / (double)HZ, tms_stop.tms_stime);
            printf("Children user   time = %g seconds [%lu ticks]\n",
              tms_stop.tms_cutime / (double)HZ, tms_stop.tms_cutime);
            printf("Children system time = %g seconds [%lu ticks]\n",
              tms_stop.tms_cstime / (double)HZ, tms_stop.tms_cstime);
            printf("Real time            = %g seconds [%lu ticks]\n",
              (real_stop - real_start) / (double)HZ, real_stop - real_start);
            exit(n);
    }


    /* По сигналу SIGALRM - завершить процесс */
    void onalarm(int nsig){
            printf("Выход #%d ================\n", getpid());
            bye(0);
    }
    /* Порожденный процесс */
    void dochild(int n){
            hello();
            printf("Старт #%d ================\n", getpid());
            signal(SIGALRM, onalarm);

            /* Заказать сигнал SIGALRM через 1 + n*3 секунд */
            alarm(1 + n*3);

            for(;;){}       /* зациклиться в user mode */
    }



















А. Богатырев, 1992-95                  - 205 -                              Си в UNIX

    #define NCHLD 4
    int main(int ac, char *av[]){
            int i;

            /* Узнать число тиков в секунде */
            HZ = sysconf(_SC_CLK_TCK);
            setbuf(stdout, NULL);

            hello();
            for(i=0; i < NCHLD; i++)
                    if(fork() == 0)
                            dochild(i);
            while(wait(NULL) > 0);
            printf("Выход MAIN =================\n");
            bye(0);
            return 0;
    }

и ее выдача:

    Старт #3883 ================
    Старт #3884 ================
    Старт #3885 ================
    Старт #3886 ================
    Выход #3883 ================
    User   time          = 0.72 seconds [72 ticks]
    System time          = 0.01 seconds [1 ticks]
    Children user   time = 0 seconds [0 ticks]
    Children system time = 0 seconds [0 ticks]
    Real time            = 1.01 seconds [101 ticks]
    Выход #3884 ================
    User   time          = 1.88 seconds [188 ticks]
    System time          = 0.01 seconds [1 ticks]
    Children user   time = 0 seconds [0 ticks]
    Children system time = 0 seconds [0 ticks]
    Real time            = 4.09 seconds [409 ticks]
    Выход #3885 ================
    User   time          = 4.41 seconds [441 ticks]
    System time          = 0.01 seconds [1 ticks]
    Children user   time = 0 seconds [0 ticks]
    Children system time = 0 seconds [0 ticks]
    Real time            = 7.01 seconds [701 ticks]
    Выход #3886 ================
    User   time          = 8.9 seconds [890 ticks]
    System time          = 0 seconds [0 ticks]
    Children user   time = 0 seconds [0 ticks]
    Children system time = 0 seconds [0 ticks]
    Real time            = 10.01 seconds [1001 ticks]
    Выход MAIN =================
    User   time          = 0.01 seconds [1 ticks]
    System time          = 0.04 seconds [4 ticks]
    Children user   time = 15.91 seconds [1591 ticks]
    Children system time = 0.03 seconds [3 ticks]
    Real time            = 10.41 seconds [1041 ticks]

Обратите внимание, что 72+188+441+890=1591 (поле tms_cutime для main).

6.2.12.  Еще одна программа: хронометрирование выполнения другой программы.   Пример:
timer ls -l





А. Богатырев, 1992-95                  - 206 -                              Си в UNIX

    /* Хронометрирование выполнения программы */
    #include <stdio.h>
    #include <unistd.h>
    #include <sys/times.h>

    extern errno;

    typedef struct _timeStamp {
            clock_t real_time;
            clock_t cpu_time;
            clock_t child_time;
            clock_t child_sys, child_user;
    } TimeStamp;


    TimeStamp TIME(){
            struct tms tms;
            TimeStamp  st;

            st.real_time  = times(&tms);
            st.cpu_time   = tms.tms_utime +
                            tms.tms_stime +
                            tms.tms_cutime +
                            tms.tms_cstime;
            st.child_time = tms.tms_cutime +
                            tms.tms_cstime;
            st.child_sys  = tms.tms_cstime;
            st.child_user = tms.tms_cutime;
            return st;
    }


    void PRTIME(TimeStamp start, TimeStamp stop){
            clock_t HZ = sysconf(_SC_CLK_TCK);
            clock_t real_time  = stop.real_time  - start.real_time;
            clock_t cpu_time   = stop.cpu_time   - start.cpu_time;
            clock_t child_time = stop.child_time - start.child_time;

            printf("%g real, %g cpu, %g child (%g user, %g sys), %ld%%\n",
                    real_time       / (double)HZ,
                    cpu_time        / (double)HZ,
                    child_time      / (double)HZ,
                    stop.child_user / (double)HZ,
                    stop.child_sys  / (double)HZ,
                    (child_time * 100L) / (real_time ? real_time : 1)
            );
    }

















А. Богатырев, 1992-95                  - 207 -                              Си в UNIX

    TimeStamp start, stop;

    int main(int ac, char *av[]){
            char *prog = *av++;
            if(*av == NULL){
                    fprintf(stderr, "Usage: %s command [args...]\n", prog);
                    return(1);
            }
            start = TIME();
            if(fork() == 0){
                    execvp(av[0], av);
                    perror(av[0]);
                    exit(errno);
            }
            while(wait(NULL) > 0);
            stop = TIME();
            PRTIME(start, stop);
            return(0);
    }


6.3.  Свободное место на диске.

6.3.1.  Системный вызов ustat() позволяет узнать количество свободного места в файло-
вой системе, содержащей заданный файл (в примере ниже - текущий каталог):

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <ustat.h>
    struct stat st; struct ustat ust;
    void main(int ac, char *av[]){
         char *file = (ac==1 ? "." : av[1]);
         if( stat(file, &st) < 0) exit(1);
         ustat(st.st_dev, &ust);
         printf("На диске %*.*s\n"
           "%ld свободных блоков (%ld Кб)\n"
           "%d свободных I-узлов\n",
         sizeof ust.f_fname, sizeof ust.f_fname,
         ust.f_fname, /* название файловой системы (метка) */
         ust.f_tfree, /* блоки по 512 байт */
        (ust.f_tfree * 512L) / 1024,
         ust.f_tinode );
    }

Обратите внимание на запись длинной строки в printf: строки, перечисленные последова-
тельно, склеиваются ANSI C компилятором в одну длинную строку:

    char s[] = "This is"  " a line "  "of words";
            совпадает с
    char s[] = "This is a line of words";


6.3.2.  Более правильно, однако, пользоваться сисвызовом statvfs - статистика по вир-
туальной  файловой  системе.  Рассмотрим его в следующем примере: копирование файла с
проверкой на наличие свободного места.









А. Богатырев, 1992-95                  - 208 -                              Си в UNIX

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <stdarg.h>
    #include <fcntl.h>              /* O_RDONLY */
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <sys/statvfs.h>
    #include <sys/param.h>          /* MAXPATHLEN */

    char *progname;                 /* имя программы */

    void error(char *fmt, ...){
            va_list args;

            va_start(args, fmt);
            fprintf(stderr, "%s: ", progname);
            vfprintf(stderr, fmt, args);
            fputc('\n', stderr);
            va_end(args);
    }


    int copyFile(char *to, char *from){       /* куда, откуда */
            char newname[MAXPATHLEN+1];
            char answer[20];
            struct stat stf, stt;
            int fdin, fdout;
            int n, code = 0;
            char iobuf[64 * 1024];
            char *dirname = NULL, *s;

            if((fdin = open(from, O_RDONLY)) < 0){
                    error("Cannot read %s", from);
                    return (-1);
            }
            fstat(fdin, &stf);
            if((stf.st_mode & S_IFMT) == S_IFDIR){
                    close(fdin);
                    error("%s is a directory", from);
                    return (-2);
            }






















А. Богатырев, 1992-95                  - 209 -                              Си в UNIX

            if(stat(to, &stt) >= 0){
                    /* Файл уже существует */

                    if((stt.st_mode & S_IFMT) == S_IFDIR){
                            /* И это каталог */

                            /* Выделить последнюю компоненту пути from */
                            if((s = strrchr(from, '/')) && s[1])
                                    s++;
                            else    s = from;

                            dirname = to;

                            /* Целевой файл - файл в этом каталоге */
                            sprintf(newname, "%s/%s", to, s);
                            to = newname;

                            if(stat(to, &stt) < 0)
                                    goto not_exist;
                    }


                    if(stt.st_dev == stf.st_dev && stt.st_ino == stf.st_ino){
                            error("%s: cannot copy file to itself", from);
                            return (-3);
                    }
                    switch(stt.st_mode & S_IFMT){
                    case S_IFBLK:
                    case S_IFCHR:
                    case S_IFIFO:
                            break;


                    default:
                            printf("%s already exists, overwrite ? ", to);
                            fflush(stdout);

                            *answer = '\0';
                            gets(answer);

                            if(*answer != 'y'){     /* NO */
                                    close(fdin);
                                    return (-4);
                            }
                            break;
                    }
            }

















А. Богатырев, 1992-95                  - 210 -                              Си в UNIX

    not_exist:
            printf("COPY %s TO %s\n", from, to);

            if((stf.st_mode & S_IFMT) == S_IFREG){
                    /* Проверка наличия свободного места в каталоге dirname */
                    struct statvfs fs;
                    char tmpbuf[MAXPATHLEN+1];

                    if(dirname == NULL){
                            /* То 'to' - это имя файла, а не каталога */
                            strcpy(tmpbuf, to);
                            if(s = strrchr(tmpbuf, '/')){
                                    if(*tmpbuf != '/' || s != tmpbuf){
                                            /* Имена "../xxx"
                                             * и второй случай:
                                             * абсолютные имена не в корне,
                                             * то есть не "/" и не "/xxx"
                                             */
                                            *s = '\0';
                                    }else{
                                            /* "/" или "/xxx" */
                                            if(s[1]) s[1] = '\0';
                                    }
                                    dirname = tmpbuf;
                            } else  dirname = ".";
                    }


                    if(statvfs(dirname, &fs) >= 0){
                            size_t size = (geteuid() == 0 ) ?
                                    /* Доступно суперпользователю: байт */
                                    fs.f_frsize * fs.f_bfree :
                                    /* Доступно обычному пользователю: байт */
                                    fs.f_frsize * fs.f_bavail;

                            if(size < stf.st_size){
                               error("Not enough free space on %s: have %lu, need %lu",
                                      dirname, size, stf.st_size);
                               close(fdin);
                               return (-5);
                            }
                    }
            }


            if((fdout = creat(to, stf.st_mode)) < 0){
                    error("Can't create %s", to);
                    close(fdin);
                    return (-6);
            } else {
                    fchmod(fdout, stf.st_mode);
                    fchown(fdout, stf.st_uid, stf.st_gid);
            }











А. Богатырев, 1992-95                  - 211 -                              Си в UNIX

            while (n = read (fdin, iobuf, sizeof iobuf)) {
                    if(n < 0){
                            error ("read error");
                            code = (-7);
                            goto done;
                    }
                    if(write (fdout, iobuf, n) != n) {
                            error ("write error");
                            code = (-8);
                            goto done;
                    }
            }


    done:
            close (fdin);
            close (fdout);

            /* Проверить: соответствует ли результат ожиданиям */
            if(stat(to, &stt) >= 0 && (stt.st_mode & S_IFMT) == S_IFREG){
                    if(stf.st_size < stt.st_size){
                            error("File has grown at the time of copying");
                    } else if(stf.st_size > stt.st_size){
                            error("File too short, target %s removed", to);
                            unlink(to);
                            code = (-9);
                    }
            }
            return code;
    }


    int main(int argc, char *argv[]){
            int i, code = 0;

            progname = argv[0];

            if(argc < 3){
                    error("Usage: %s from... to", argv[0]);
                    return 1;
            }
            for(i=1; i < argc-1; i++)
                    code |= copyFile(argv[argc-1], argv[i]) < 0 ? 1 : 0;
            return code;
    }

Возвращаемая структура struct statvfs содержит такие поля (в частности):

    Типа long:
    f_frsize                размер блока
    f_blocks                размер файловой системы в блоках
    f_bfree                 свободных блоков (для суперпользователя)
    f_bavail                свободных блоков (для всех остальных)

    f_files                 число I-nodes в файловой системе
    f_ffree                 свободных I-nodes (для суперпользователя)
    f_favail                свободных I-nodes (для всех остальных)

    Типа char *
    f_basetype              тип файловой системы: ufs, nfs, ...




А. Богатырев, 1992-95                  - 212 -                              Си в UNIX

По два значения дано потому, что операционная система резервирует часть файловой сис-
темы  для использования ТОЛЬКО суперпользователем (чтобы администратор смог распихать
файлы в случае переполнения диска, и имел резерв на это). ufs - это UNIX file  system
из BSD 4.x

6.4.  Сигналы.
     Процессы в UNIX используют много разных механизмов взаимодействия. Одним из  них
являются сигналы.
     Сигналы - это асинхронные события. Что это значит?  Сначала объясним, что  такое
синхронные события: я два раза в день подхожу к почтовому ящику и проверяю - нет ли в
нем почты (событий).  Во-первых, я произвожу опрос - "нет ли для  меня  события?",  в
программе  это выглядело бы как вызов функции опроса и, может быть, ожидания события.
Во-вторых, я знаю, что почта может ко мне прийти, поскольку я подписался на  какие-то
газеты. То есть я предварительно заказывал эти события.
     Схема с синхронными событиями очень распространена.  Кассир сидит у кассы и ожи-
дает,  пока  к нему в окошечко не заглянет клиент.  Поезд периодически проезжает мимо
светофора и останавливается, если горит красный.  Функция Си пассивно "спит"  до  тех
пор,  пока  ее  не вызовут; однако она всегда готова выполнить свою работу (обслужить
клиента).  Такое ожидающее  заказа  (события)  действующее  лицо  называется  сервер.
После  выполнения  заказа  сервер вновь переходит в состояние ожидания вызова.  Итак,
если событие ожидается в специальном месте и в определенные моменты времени (издается
некий  вызов  для  ОПРОСА)  -  это синхронные события.  Канонический пример - функция
gets, которая задержит выполнение программы,  пока  с  клавиатуры  не  будет  введена
строка.   Большинство  ожиданий внутри системных вызовов - синхронны.  Ядро ОС высту-
пает для программ пользователей в роли сервера, выполняющего  сисвызовы  (хотя  и  не
только  в этой роли - ядро иногда предпринимает и активные действия: передача процес-
сора другому процессу через определенное время (режим разделения  времени),  убивание
процесса при ошибке, и.т.п.).
     Сигналы - это асинхронные события. Они приходят неожиданно, в любой момент  вре-
мени  -  вроде  телефонного  звонка.  Кроме того, их не требуется заказывать - сигнал
процессу может поступить совсем без повода.  Аналогия из жизни такова: человек  сидит
и  пишет  письмо. Вдруг его окликают посреди фразы - он отвлекается, отвечает на воп-
рос, и вновь продолжает прерванное занятие. Человек  не  ожидал  этого  оклика  (быть
может,  он готов к нему, но он не озирался по сторонам специально).  Кроме того, сиг-
нал мог поступить когда он писал 5-ое предложение, а мог - когда 34-ое.  Момент  вре-
мени, в который произойдет прерывание, не фиксирован.
     Сигналы имеют номера, причем их количество ограничено - есть определенный список
допустимых  сигналов.  Номера  и  мнемонические имена сигналов перечислены в include-
файле <signal.h> и имеют вид SIGнечто.  Допустимы сигналы с номерами  1..NSIG-1,  где
NSIG  определено  в  этом  файле.   При  получении сигнала мы узнаем его номер, но не
узнаем никакой иной информации: ни от кого поступил сигнал,  ни  что  от  нас  хотят.
Просто "звонит телефон". Чтобы получить дополнительную информацию, наш процесс должен
взять ее из другого известного места; например - прочесть заказ из некоторого  файла,
об  имени которого все наши программы заранее "договорились".  Сигналы процессу могут
поступать тремя путями:
-    От другого процесса, который явно посылает его нам вызовом
         kill(pid, sig);
     где pid - идентификатор (номер) процесса-получателя,  а  sig  -  номер  сигнала.
     Послать сигнал можно только родственному процессу - запущенному тем же пользова-
     телем.
-    От операционной системы. Система может посылать процессу ряд сигналов,  сигнали-
     зирующих  об ошибках, например при обращении программы по несуществующему адресу
     или при ошибочном номере системного вызова. Такие сигналы обычно прекращают  наш
     процесс.
-    От пользователя - с клавиатуры терминала можно нажимом некоторых клавиш  послать
     сигналы SIGINT и SIGQUIT.  Собственно, сигнал посылается драйвером терминала при
     получении им с клавиатуры определенных символов. Так можно прервать  зациклившу-
     юся или надоевшую программу.

Процесс-получатель должен как-то отреагировать на сигнал.  Программа может:




А. Богатырев, 1992-95                  - 213 -                              Си в UNIX

-    проигнорировать сигнал (не ответить на звонок);
-    перехватить сигнал (снять трубку), выполнить какие-то действия, затем продолжить
     прерванное занятие;
-    быть убитой сигналом (звонок был подкреплен броском гранаты в окно);

В большинстве случаев сигнал по умолчанию убивает процесс-получатель.  Однако процесс
может изменить это умолчание и задать свою реакцию явно. Это делается вызовом signal:

    #include <signal.h>
    void (*signal(int sig, void (*react)() )) ();

Параметр react может иметь значение:
SIG_IGN
     сигнал sig будет отныне игнорироваться.  Некоторые  сигналы  (например  SIGKILL)
     невозможно перехватить или проигнорировать.
SIG_DFL
     восстановить реакцию по умолчанию (обычно - смерть получателя).
имя_функции
     Например

          void fr(gotsig){ ..... }  /* обработчик */
          ... signal (sig, fr); ... /* задание реакции */

     Тогда при получении сигнала sig будет вызвана функция fr, в которую  в  качестве
     аргумента  системой  будет  передан номер сигнала, действительно вызвавшего ее -
     gotsig==sig.  Это полезно, т.к. можно задать одну и ту  же  функцию  в  качестве
     реакции для нескольких сигналов:

          ... signal (sig1, fr); signal(sig2, fr); ...

     После возврата из функции fr() программа продолжится с прерванного места.  Перед
     вызовом  функции-обработчика  реакция  автоматически  сбрасывается  в реакцию по
     умолчанию SIG_DFL, а после выхода из обработчика снова восстанавливается  в  fr.
     Это значит, что во время работы функции-обработчика может прийти сигнал, который
     убьет программу.

Приведем список  некоторых  сигналов;  полное  описание  посмотрите  в  документации.
Колонки  таблицы:  G  -  может быть перехвачен; D - по умолчанию убивает процесс (k),
игнорируется (i); C - образуется дамп памяти процесса: файл core, который затем может
быть  исследован  отладчиком  adb; F - реакция на сигнал сбрасывается; S - посылается
обычно системой, а не явно.

    сигнал         G   D   C   F   S  смысл

    SIGTERM        +   k   -   +   -  завершить процесс
    SIGKILL        -   k   -   +   -  убить процесс
    SIGINT         +   k   -   +   -  прерывание с клавиш
    SIGQUIT        +   k   +   +   -  прерывание с клавиш
    SIGALRM        +   k   -   +   +  будильник
    SIGILL         +   k   +   -   +  запрещенная команда
    SIGBUS         +   k   +   +   +  обращение по неверному
    SIGSEGV        +   k   +   +   +     адресу
    SIGUSR1, USR2  +   i   -   +   -  пользовательские
    SIGCLD         +   i   -   +   +  смерть потомка

-    Сигнал SIGILL используется иногда для эмуляции команд с  плавающей  точкой,  что
     происходит  примерно  так: при обнаружении "запрещенной" команды для отсутствую-
     щего процессора "плавающей" арифметики  аппаратура  дает  прерывание  и  система
     посылает процессу сигнал SIGILL.  По сигналу вызывается функция-эмулятор плаваю-
     щей арифметики (подключаемая к  выполняемому  файлу  автоматически),  которая  и
     обрабатывает  требуемую команду. Это может происходить много раз, именно поэтому



А. Богатырев, 1992-95                  - 214 -                              Си в UNIX

     реакция на этот сигнал не сбрасывается.
-    SIGALRM посылается в результате его заказа вызовом alarm() (см. ниже).
-    Сигнал SIGCLD посылается  процессу-родителю  при  выполнении  процессом-потомком
     сисвызова  exit  (или при смерти вследствие получения сигнала).  Обычно процесс-
     родитель при получении такого сигнала (если он его заказывал) реагирует,  выпол-
     няя  в обработчике сигнала вызов wait (см. ниже). По-умолчанию этот сигнал игно-
     рируется.
-    Реакция SIG_IGN не сбрасывается в SIG_DFL при приходе сигнала, т.е. сигнал игно-
     рируется постоянно.
-    Вызов signal возвращает старое значение реакции, которое может быть запомнено  в
     переменную вида void (*f)(); а потом восстановлено.
-    Синхронное ожидание (сисвызов) может иногда быть прервано  асинхронным  событием
     (сигналом), но об этом ниже.

     Некоторые версии UNIX предоставляют более развитые средства работы с  сигналами.
Опишем некоторые из средств, имеющихся в BSD (в других системах они могут быть смоде-
лированы другими способами).
     Пусть у нас в программе есть "критическая секция", во время  выполнения  которой
приход  сигналов  нежелателен.  Мы можем "заморозить" (заблокировать) сигнал, отложив
момент его поступления до "разморозки":

       |
    sighold(sig);  заблокировать сигнал
       |           :
     КРИТИЧЕСКАЯ   :<---процессу послан сигнал sig,
     СЕКЦИЯ        :  но он не вызывает реакцию немедленно,
       |           :  а "висит", ожидая разрешения.
       |           :
    sigrelse(sig); разблокировать
       |<----------- sig
       |    накопившиеся сигналы доходят,
       |    вызывается реакция.

Если во время блокировки процессу было послано несколько одинаковых сигналов sig,  то
при  разблокировании  поступит  только один. Поступление сигналов во время блокировки
просто отмечается в специальной битовой шкале в паспорте процесса (примерно так):

    mask |= (1 << (sig - 1));

и при разблокировании сигнала sig, если соответствующий бит  выставлен,  то  приходит
один такой сигнал (система вызывает функцию реакции).
То есть sighold заставляет приходящие сигналы "накапливаться"  в  специальной  маске,
вместо  того,  чтобы немедленно вызывать реакцию на них.  А sigrelse разрешает "нако-
пившимся" сигналам (если они есть) прийти и вызывает реакцию на них.
Функция
    sigset(sig, react);
аналогична функции signal, за исключением того, что на время работы обработчика  сиг-
нала  react, приход сигнала sig блокируется; то есть перед вызовом react как бы дела-
ется sighold, а при выходе из обработчика - sigrelse.  Это значит, что если во  время
работы  обработчика  сигнала  придет  такой же сигнал, то программа не будет убита, а
"запомнит" пришедший сигнал, и обработчик  будет  вызван  повторно  (когда  сработает
sigrelse).
Функция
    sigpause(sig);
вызывается внутри "рамки"

    sighold(sig);
            ...
        sigpause(sig);
            ...
    sigrelse(sig);



А. Богатырев, 1992-95                  - 215 -                              Си в UNIX

и вызывает задержку выполнения процесса до прихода сигнала  sig.   Функция  разрешает
приход  сигнала  sig (обычно на него должна быть задана реакция при помощи sigset), и
"засыпает" до прихода сигнала sig.
     В UNIX стандарта POSIX для управления сигналами есть вызовы sigaction,  sigproc-
mask, sigpending, sigsuspend. Посмотрите в документацию!

6.4.1.  Напишите программу, выдающую на экран файл /etc/termcap.  Перехватывайте сиг-
нал   SIGINT, при получении сигнала запрашивайте "Продолжать?".  По ответу 'y' - про-
должить выдачу; по 'n' - завершить программу; по 'r' - начать выдавать файл с начала:
lseek(fd,0L,0).  Не забудьте заново переустановить реакцию на SIGINT, поскольку после
получения сигнала реакция автоматически сбрасывается.

    #include <signal.h>
    void onintr(sig){       /* sig - номер сигнала  */
      signal (sig, onintr); /* восстановить реакцию */
      ... запрос и действия ...
    }
    main(){ signal (SIGINT, onintr); ... }

Сигнал прерывания можно игнорировать. Это делается так:

    signal (SIGINT, SIG_IGN);

Такую программу нельзя прервать с клавиатуры.  Напомним, что реакция SIG_IGN сохраня-
ется при приходе сигнала.

6.4.2.  Системный вызов, находящийся в состоянии  ожидания  какого-то  события  (read
ждущий нажатия кнопки на клавиатуре, wait ждущий окончания процесса-потомка, и.т.п.),
может быть прерван сигналом. При этом сисвызов вернет значение "ошибка" (-1) и  errno
станет  равно  EINTR. Это позволяет нам писать системные вызовы с выставлением тайма-
ута: если событие не происходит в течение заданного времени, то завершить ожидание  и
прервать сисвызов.  Для этой цели используется вызов alarm(sec), заказывающий посылку
сигнала SIGALRM нашей программе через целое число sec секунд (0 - отменяет заказ):

    #include <signal.h>
    void (*oldaction)(); int alarmed;
    /* прозвонил будильник */
    void onalarm(nsig){ alarmed++; }
        ...
    /* установить реакцию на сигнал */
    oldaction = signal (SIGALRM, onalarm);
    /* заказать будильник через TIMEOUT сек. */
    alarmed = 0; alarm ( TIMEOUT /* sec */ );

         sys_call(...);  /* ждет события */
      // если нас сбил сигнал, то по сигналу будет
      // еще вызвана реакция на него - onalarm

    if(alarmed){
      // событие так и не произошло.
      // вызов прерван сигналом т.к. истекло время.
    }else{
      alarm(0); /* отменить заказ сигнала */
      // событие произошло, сисвызов успел
      // завершиться до истечения времени.
    }
    signal (SIGALRM, oldaction);

Напишите программу, которая ожидает ввода с клавиатуры  в  течение  10  секунд.  Если
ничего  не  введено  -  печатает  "Нет ввода", иначе - печатает "Спасибо".  Для ввода
можно использовать как вызов read,  так  и  функцию  gets  (или  getchar),  поскольку



А. Богатырев, 1992-95                  - 216 -                              Си в UNIX

функция  эта  все  равно  внутри себя издает системный вызов read.  Исследуйте, какое
значение возвращает fgets (gets) в случае прерывания ее системным вызовом.

    /* Копирование стандартного ввода на стандартный вывод
     * с установленным тайм-аутом.
     * Это позволяет использовать программу для чтения из FIFO-файлов
     * и с клавиатуры.
     * Небольшая модификация позволяет использовать программу
     * для копирования "растущего" файла (т.е. такого, который в
     * настоящий момент еще продолжает записываться).
     * Замечание:
     *       В ДЕМОС-2.2 сигнал НЕ сбивает чтение из FIFO-файла,
     *       а получение сигнала откладывается до выхода из read()
     *       по успешному чтению информации. Пользуйтесь open()-ом
     *       с флагом O_NDELAY, чтобы получить требуемый эффект.
     *
     *      Вызов: a.out /dev/tty
     *
     * По мотивам книги М.Дансмура и Г.Дейвиса.
     */


    #define WAIT_TIME 5 /* ждать 5 секунд */
    #define MAX_TRYS  5 /* максимум 5 попыток */
    #define BSIZE     256
    #define STDIN     0 /* дескриптор стандартного ввода  */
    #define STDOUT    1 /* дескриптор стандартного вывода */


    #include <signal.h>
    #include <errno.h>
    #include <stdio.h>
    #include <fcntl.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    char buffer [ BSIZE ];
    extern int errno;       /* код ошибки */


    void timeout(nsig){ signal( SIGALRM, timeout ); }
    void main(argc, argv) char **argv;{
       int fd, n, trys = 0;  struct stat stin, stout;

       if( argc != 2 ){
           fprintf(stderr, "Вызов: %s файл\n", argv[0]); exit(1);
       }
       if((fd = !strcmp(argv[1],"-")? STDIN : open(argv[1],O_RDONLY)) < 0){
           fprintf(stderr, "Не могу читать %s\n", argv[1]); exit(2);
       }
       /* Проверить, что ввод не совпадает с выводом,
        *     hardcat aFile >> aFile
        * кроме случая, когда вывод - терминал.
        * Такая проверка полезна для программ-фильтров (STDIN->STDOUT),
        * чтобы исключить порчу исходной информации */
       fstat(fd, &stin); fstat(STDOUT, &stout);
       if( !isatty(STDOUT) && stin.st_ino == stout.st_ino &&
                              stin.st_dev == stout.st_dev
       ){ fprintf(stderr,
          "\aВвод == выводу, возможно потеряна информация в %s.\n",argv[1]);
          exit(33);
       }



А. Богатырев, 1992-95                  - 217 -                              Си в UNIX

       signal( SIGALRM, timeout );
       while( trys < MAX_TRYS ){
               alarm( WAIT_TIME ); /* заказать сигнал через 5 сек */

               /* и ждем ввода ... */
               n = read( fd, buffer, BSIZE );

               alarm(0);       /* отменили заказ сигнала */
                     /* (хотя, возможно, он уже получен) */

               /* проверяем: почему мы слезли с вызова read() ? */
               if( n < 0 && errno == EINTR ){
                   /* Мы были сбиты сигналом SIGALRM,
                    * код ошибки EINTR - сисвызов прерван
                    * неким сигналом.
                    */
                   fprintf( stderr, "\7timed out (%d раз)\n", ++trys );
                   continue;
               }


               if( n < 0 ){
                   /* ошибка чтения */
                   fprintf( stderr, "read error.\n" ); exit(4);
               }
               if( n == 0 ){
                   /* достигнут конец файла */
                   fprintf( stderr, "Достигнут EOF.\n\n" ); exit(0);
               }
               /* копируем прочитанную информацию */
               write( STDOUT, buffer, n );
               trys = 0;
       }
       fprintf( stderr, "Все попытки провалились.\n" ); exit(5);
    }

Если мы хотим, чтобы сисвызов не мог прерываться сигналом, мы должны защитить его:

    #include <signal.h>
    void (*fsaved)();
            ...
    fsaved = signal (sig, SIG_IGN);
       sys_call(...);
    signal (sig, fsaved);

или так:

    sighold(sig);
       sys_call(...);
    sigrelse(sig);

Сигналами могут быть прерваны не все системные вызовы и не при всех обстоятельствах.

6.4.3.  Напишите функцию sleep(n), задерживающую выполнение программы  на  n  секунд.
Воспользуйтесь  системным  вызовом  alarm(n)  (будильник)  и вызовом pause(), который
задерживает программу до получения любого сигнала.  Предусмотрите рестарт при получе-
нии  во  время ожидания другого сигнала, нежели SIGALRM. Сохраняйте заказ alarm, сде-
ланный до вызова sleep (alarm выдает число секунд, оставшееся до завершения  предыду-
щего заказа).  На самом деле есть такая СТАНДАРТНАЯ функция.  Ответ:





А. Богатырев, 1992-95                  - 218 -                              Си в UNIX

    #include <sys/types.h>
    #include <stdio.h>
    #include <signal.h>

    int got;   /* пришел ли сигнал */

    void onalarm(int sig)
    { printf( "Будильник\n" ); got++; } /* сигнал получен */


    void sleep(int n){
       time_t time(), start = time(NULL);
       void (*save)();
       int oldalarm, during = n;

       if( n <= 0 ) return;
       got = 0;
       save = signal(SIGALRM, onalarm);
       oldalarm = alarm(3600); /* Узнать старый заказ */
       if( oldalarm ){
           printf( "Был заказан сигнал, который придет через %d сек.\n",
                    oldalarm );
           if(oldalarm > n) oldalarm -= n;
           else { during = n = oldalarm; oldalarm = 1; }
       }
       printf( "n=%d oldalarm=%d\n", n, oldalarm );
       while( n > 0 ){
         printf( "alarm(%d)\n", n );
         alarm(n);  /* заказать SIGALRM через n секунд */

         pause();

         if(got) break;
         /* иначе мы сбиты с pause другим сигналом */
         n = during - (time(NULL) - start); /* прошло времени */
       }
       printf( "alarm(%d) при выходе\n", oldalarm );
       alarm(oldalarm);  /* alarm(0) - отмена заказа сигнала */
       signal(SIGALRM, save); /* восстановить реакцию */
    }


    void onintr(int nsig){
       printf( "Сигнал SIGINT\n"); signal(SIGINT, onintr);
    }

    void onOldAlarm(int nsig){
       printf( "Звонит старый будильник\n");
    }

    void main(){
            int time1 = 0;  /* 5, 10, 20 */
            setbuf(stdout, NULL);
            signal(SIGINT, onintr);
            signal(SIGALRM, onOldAlarm); alarm(time1);
                        sleep(10);
            if(time1) pause();
            printf("Чао!\n");
    }





А. Богатырев, 1992-95                  - 219 -                              Си в UNIX

6.4.4.  Напишите "часы", выдающие текущее время каждые 3 секунды.

    #include <signal.h>
    #include <time.h>
    #include <stdio.h>
    void tick(nsig){
       time_t tim; char *s;
       signal (SIGALRM, tick);
       alarm(3); time(&tim);
       s = ctime(&tim);
       s[ strlen(s)-1 ] = '\0'; /* обрубить '\n' */
       fprintf(stderr, "\r%s", s);
    }
    main(){ tick(0);
            for(;;) pause();
    }


6.5.  Жизнь процессов.

6.5.1.  Какие классы памяти имеют данные, в каких сегментах  программы  они  располо-
жены?

            char x[] = "hello";
            int y[25];
            char *p;
            main(){
                    int z = 12;
                    int v;
                    static int w = 25;
                    static int q;
                    char s[20];
                    char *pp;
                    ...
                    v = w + z;      /* #1 */
            }

Ответ:

    Переменная  Класс памяти     Сегмент   Начальное значение
         x        static        data/DATA      "hello"
         y        static        data/BSS     {0, ..., 0}
         p        static        data/BSS        NULL
         z        auto          stack            12
         v        auto          stack        не определено
         w        static        data/DATA        25
         q        static        data/BSS          0
         s        auto          stack        не определено
         pp       auto          stack        не определено
         main     static        text/TEXT

Большими буквами обозначены сегменты, хранимые в выполняемом файле:
DATA - это инициализированные статические данные (которым присвоены начальные  значе-
     ния).  Они помещаются компилятором в файл в виде готовых констант, а при запуске
     программы (при ее загрузке в память  машины),  просто  копируются  в  память  из
     файла.
BSS (Block Started by Symbol)
     - неинициализированные статические данные. Они по умолчанию имеют начальное зна-
     чение  0  (NULL,  "",  '\0').  Эта память расписывается нулями при запуске прог-
     раммы, а в файле хранится лишь ее размер.




А. Богатырев, 1992-95                  - 220 -                              Си в UNIX

TEXT - сегмент, содержащий машинные команды (код).

Хранящаяся в файле выполняемая программа имеет также заголовок - в  нем  в  частности
содержатся  размеры  перечисленных  сегментов  и их местоположение в файле; и еще - в
самом конце файла - таблицу имен.  В ней содержатся имена всех функций и  переменных,
используемых  в  программе,  и  их адреса. Эта таблица используется отладчиками adb и
sdb, а также при сборке программы  из  нескольких  объектных  файлов  программой  ld.
Просмотреть ее можно командой
     nm имяФайла
Для экономии дискового пространства эту таблицу часто удаляют, что делается командой
     strip имяФайла
Размеры сегментов можно узнать командой
     size имяФайла
Программа, загруженная в память компьютера (т.е. процесс), состоит из  3x  сегментов,
относящихся непосредственно к программе:
stack
     - стек для локальных переменных функций (автоматических переменных).  Этот  сег-
     мент  существует  только у выполняющейся программы, поскольку отведение памяти в
     стеке производится выполнением некоторых машинных команд (поэтому описание авто-
     матических  переменных в Си - это на самом деле выполняемые операторы, хотя и не
     с точки зрения языка).  Сегмент стека автоматически растет  по  мере  надобности
     (если мы вызываем новые и новые функции, отводящие переменные в стеке).  За этим
     следит аппаратура диспетчера памяти.
data - сегмент, в который склеены сегменты статических данных DATA и BSS, загруженные
     из  файла.   Этот  сегмент  также может изменять свой размер, но делать это надо
     явно - системными вызовами sbrk или brk. В частности, функция malloc() для  раз-
     мещения динамически отводимых данных увеличивает размер этого сегмента.
text - это выполняемые команды, копия сегмента TEXT из файла.  Так строка с меткой #1
     содержится в виде машинных команд именно в этом сегменте.

Кроме того, каждый процесс имеет еще:

proc - это резидентная часть паспорта процесса в таблице процессов в ядре  операцион-
     ной системы;
user - это 4-ый сегмент процесса - нерезидентная часть паспорта  (u-area).   К  этому
     сегменту имеет доступ только ядро, но не сама программа.

Паспорт процесса был поделен на 2 части только из соображений экономии памяти в ядре:
контекст  процесса (таблица открытых файлов, ссылка на I-узел текущего каталога, таб-
лица реакций на сигналы, ссылка на I-узел управляющего терминала, и.т.п.) нужен  ядру
только  при обслуживании текущего активного процесса.  Когда активен другой процесс -
эта информация в памяти ядра не нужна.  Более того, если процесс из-за нехватки места
в памяти машины был откачан на диск, эта информация также может быть откачана на диск
и подкачана назад лишь вместе с процессом.  Поэтому контекст был выделен в  отдельный
сегмент,  и сегмент этот подключается к адресному пространству ядра лишь при выполне-
нии процессом какого-либо системного вызова (это подключение называется "переключение
контекста"  - context switch).  Четыре сегмента процесса могут располагаться в памяти
машины не обязательно подряд - между ними могут лежать сегменты других процессов.
     Схема составных частей процесса:

            П  Р  О  Ц  Е  С  С
      таблица процессов:
      паспорт  в ядре       сегменты в памяти

       struct proc[]
            ####---------------> stack      1
            ####                 data       2
                                 text       3
                   контекст:   struct user  4





А. Богатырев, 1992-95                  - 221 -                              Си в UNIX

Каждый процесс имеет уникальный номер, хранящийся в поле p_pid в структуре proc[*].   В
ней  также  хранятся:  адреса  сегментов процесса в памяти машины (или на диске, если
процесс откачан); p_uid - номер владельца процесса; p_ppid - номер процесса-родителя;
p_pri,  p_nice  -  приоритеты процесса; p_pgrp - группа процесса; p_wchan - ожидаемое
процессом событие; p_flag и p_stat - состояние процесса; и многое  другое.  Структура
proc определена в include-файле <sys/proc.h>, а структура user - в <sys/user.h>.

6.5.2.  Системный вызов fork() (вилка) создает новый процесс: копию процесса,  издав-
шего вызов.  Отличие этих процессов состоит только в возвращаемом fork-ом значении:

    0                   - в новом процессе.
    pid нового процесса - в исходном.

Вызов fork может завершиться неудачей если таблица процессов переполнена.  Простейший
способ сделать это:

    main(){
          while(1)
            if( ! fork()) pause();
    }

Одно гнездо таблицы процессов зарезервировано - его может использовать только  супер-
пользователь  (в  целях  жизнеспособности  системы: хотя бы для того, чтобы запустить
программу, убивающую все эти процессы-варвары).
     Вызов fork создает копию всех 4х сегментов процесса и выделяет порожденному про-
цессу  новый паспорт и номер.  Иногда сегмент text не копируется, а используется про-
цессами совместно ("разделяемый сегмент") в целях экономии  памяти.  При  копировании
сегмента  user  контекст порождающего процесса наследуется порожденным процессом (см.
ниже).
     Проведите опыт, доказывающий что порожденный системным вызовом fork() процесс  и
породивший его - равноправны. Повторите несколько раз программу:

    #include <stdio.h>
    int pid, i, fd; char c;
    main(){
       fd = creat( "TEST", 0644);
       if( !(pid = fork())){ /* сын: порожденный процесс */
               c = 'a';
               for(i=0; i < 5; i++){
                 write(fd, &c, 1); c++; sleep(1);
               }
               printf("Сын %d окончен\n", getpid());
               exit(0);
       }
       /* else процесс-отец */
       c = 'A';
       for(i=0; i < 5; i++){
               write(fd, &c, 1); c++; sleep(1);
       }
       printf("Родитель %d процесса %d окончен\n",
               getpid(), pid );
    }

В файле TEST мы будем от случая к случаю получать строки вида

    aABbCcDdEe  или  AaBbcdCDEe

что говорит о том, что первым "проснуться" после fork() может любой из  двух  процес-
сов. Если же опыт дает устойчиво строки, начинающиеся с одной и той же буквы - значит
____________________
   [*] Процесс может узнать его вызовом pid=getpid();



А. Богатырев, 1992-95                  - 222 -                              Си в UNIX

в данной реализации системы один из процессов все же запускается раньше. Но не  стоит
использовать этот эффект - при переносе на другую систему его может не быть!
     Данный опыт основан на следующем свойстве системы  UNIX:  при  системном  вызове
fork()  порожденный процесс получает все открытые порождающим процессом файлы "в нас-
ледство" - это соответствует тому, что таблица открытых процессом файлов копируется в
процесс-потомок.   Именно  так,  в  частности,  передаются от отца к сыну стандартные
каналы 0, 1, 2: порожденному процессу не нужно открывать стандартные  ввод,  вывод  и
вывод  ошибок  явно.  Изначально  же они открываются специальной программой при вашем
входе в систему.

             до вызова fork();

     таблица открытых
     файлов  процесса
        0   ## ---<--- клавиатура
        1   ## --->--- дисплей
        2   ## --->--- дисплей
        ... ##
        fd  ## --->--- файл TEST
        ... ##


             после fork();

     ПРОЦЕСС-ПАПА                   ПРОЦЕСС-СЫН
     0   ## ---<--- клавиатура --->--- ## 0
     1   ## --->--- дисплей    ---<--- ## 1
     2   ## --->--- дисплей    ---<--- ## 2
     ... ##                            ## ...
     fd  ## --->--- файл TEST  ---<--- ## fd
     ... ##            |               ## ...
                       *--RWptr-->ФАЙЛ

Ссылки из таблиц открытых файлов в процессах указывают на структуры "открытый файл" в
ядре  (см.  главу  про файлы).  Таким образом, два процесса получают доступ к одной и
той же структуре и, следовательно, имеют  общий  указатель  чтения/записи  для  этого
файла. Поэтому, когда процессы "отец" и "сын" пишут по дескриптору fd, они пользуются
одним и тем же указателем R/W, т.е. информация от обоих процессов записывается после-
довательно.   На  принципе  наследования  и совместного использования открытых файлов
основан также системный вызов pipe.
     Порожденный процесс наследует также: реакции на сигналы (!!!), текущий  каталог,
управляющий терминал, номер владельца процесса и группу владельца, и.т.п.
     При системном вызове exec() (который заменяет программу, выполняемую  процессом,
на  программу  из  указанного файла) все открытые каналы также достаются в наследство
новой программе (а не закрываются).

6.5.3.  Процесс-копия это хорошо, но не совсем то, что нам хотелось бы.  Нам  хочется
запустить  программу,  содержащуюся  в выполняемом файле (например a.out).  Для этого
существует системный вызов exec, который имеет несколько  разновидностей.  Рассмотрим
только две:

    char *path;
    char *argv[], *envp[], *arg0, ..., *argn;
    execle(path, arg0, arg1, ..., argn, NULL, envp);
    execve(path, argv, envp);

Системный вызов exec заменяет программу, выполняемую данным процессом, на  программу,
загружаемую  из  файла path. В данном случае path должно быть полным именем файла или
именем файла от текущего каталога:

    /usr/bin/vi   a.out  ../mybin/xkick



А. Богатырев, 1992-95                  - 223 -                              Си в UNIX

Файл должен иметь код доступа "выполнение".  Первые два  байта  файла  (в  его  заго-
ловке),  рассматриваемые  как  short  int, содержат так называемое "магическое число"
(A_MAGIC), свое для каждого типа машин (смотри include-файл <a.out.h>).  Его помещает
в  начало выполняемого файла редактор связей ld при компоновке программы из объектных
файлов.  Это число должно быть правильным, иначе система  откажется  запускать  прог-
рамму  из этого файла.  Бывает несколько разных магических чисел, обозначающих разные
способы организации программы в памяти.  Например, есть вариант, в  котором  сегменты
text  и data склеены вместе (тогда text не разделяем между процессами и не защищен от
модификации программой), а есть - где данные и текст находятся в раздельных  адресных
пространствах и запись в text запрещена (аппаратно).
     Остальные аргументы вызова - arg0, ..., argn - это аргументы функции main  новой
программы. Во второй форме вызова аргументы не перечисляются явно, а заносятся в мас-
сив. Это позволяет формировать произвольный массив строк-аргументов во  время  работы
программы:

    char *argv[20];
    argv[0]="ls"; argv[1]="-l"; argv[2]="-i"; argv[3]=NULL;
    execv( "/bin/ls", argv);
            либо
    execl( "/bin/ls", "ls","-l","-i", NULL):

В результате этого вызова текущая программа завершается (но не процесс!) и вместо нее
запускается программа из заданного файла: сегменты stack, data, text старой программы
уничтожаются; создаются новые сегменты data и text, загружаемые из файла path;  отво-
дится  сегмент stack (первоначально - не очень большого размера); сегмент user сохра-
няется от старой программы (за исключением реакций на сигналы, отличных от SIG_DFL  и
SIG_IGN  -  они  будут  сброшены  в SIG_DFL).  Затем будет вызвана функция main новой
программы с аргументами argv:

    void main( argc, argv )
        int argc; char *argv[]; { ... }

Количество аргументов - argc - подсчитает сама система. Строка  NULL  не  подсчитыва-
ется.
     Процесс остается тем же самым - он имеет тот же паспорт (только адреса сегментов
изменились);  тот же номер (pid); все открытые прежней программой файлы остаются отк-
рытыми (с теми же дескрипторами); текущий каталог также наследуется от  старой  прог-
раммы;  сигналы,  которые  игнорировались  ею,  также будут игнорироваться (остальные
сбрасываются в SIG_DFL).  Зато "сущность" процесса  подвергается  перерождению  -  он
выполняет  теперь  иную  программу.  Таким образом, системный вызов exec осуществляет
вызов функции main, находящейся в другой программе, передавая  ей  свои  аргументы  в
качестве входных.
     Системный вызов exec может не удаться, если указанный файл path  не  существует,
либо  вы  не  имеете  права  его  выполнять (такие коды доступа), либо он не является
выполняемой программой (неверное магическое число), либо  слишком  велик  для  данной
машины  (системы), либо файл открыт каким-нибудь процессом (например еще записывается
компилятором).  В этом случае продолжится  выполнение  прежней  программы.   Если  же
вызов  успешен  - возврата из exec не происходит вообще (поскольку управление переда-
ется в другую программу).
     Аргумент argv[0] обычно полагают равным path.  По нему программа,  имеющая  нес-
колько  имен (в файловой системе), может выбрать ЧТО она должна делать. Так программа
/bin/ls имеет альтернативные имена lr, lf, lx, ll.  Запускается одна и  та  же  прог-
рамма, но в зависимости от argv[0] она далее делает разную работу.
     Аргумент envp - это "окружение" программы (см.  начало этой главы).  Если он  не
задан  -  передается  окружение текущей программы (наследуется содержимое массива, на
который указывает переменная environ); если же задан явно (например, окружение скопи-
ровано  в какой-то массив и часть переменных подправлена или добавлены новые перемен-
ные) - новая программа получит новое окружение.  Напомним, что окружение  можно  про-
честь из предопределенной переменной char **environ, либо из третьего аргумента функ-
ции main (см. начало главы), либо функцией getenv().




А. Богатырев, 1992-95                  - 224 -                              Си в UNIX

     Системные вызовы fork и exec не склеены в один вызов потому, что  между  fork  и
exec  в  процессе-сыне  могут  происходить  некоторые  действия, нарушающие симметрию
процесса-отца и порожденного процесса: установка реакций на сигналы,  перенаправление
ввода/вывода, и.т.п. Смотри пример "интерпретатор команд" в приложении.  В MS DOS, не
имеющей параллельных процессов, вызовы fork, exec и wait склеены в один вызов  spawn.
Зато  при  этом приходится делать перенаправления ввода-вывода в порождающем процессе
перед spawn, а после него - восстанавливать все как было.

6.5.4.  Завершить процесс можно системным вызовом

    void exit( unsigned char retcode );

Из этого вызова не бывает возврата. Процесс завершается: сегменты stack, data,  text,
user  уничтожаются (при этом все открытые процессом файлы закрываются); память, кото-
рую они занимали, считается свободной и в нее  может  быть  помещен  другой  процесс.
Причина  смерти отмечается в паспорте процесса - в структуре proc в таблице процессов
внутри ядра. Но паспорт  еще  не  уничтожается!  Это  состояние  процесса  называется
"зомби" - живой мертвец.
     В паспорт процесса заносится код ответа retcode.  Этот код может  быть  прочитан
процессом-родителем  (тем, кто создал этот процесс вызовом fork).  Принято, что код 0
означает успешное завершение процесса, а любое положительное значение 1..255 означает
неудачное  завершение  с  таким кодом ошибки.  Коды ошибок заранее не предопределены:
это личное дело процессов отца и сына - установить между собой какие-то соглашения по
этому  поводу.   В  старых программах иногда писалось exit(-1); Это некорректно - код
ответа должен быть неотрицателен; код -1 превращается в код 255.  Часто  используется
конструкция exit(errno);
     Программа может завершиться не только явно вызывая exit, но и еще  двумя  спосо-
бами:
-    если происходит возврат управления из функции main(), т.е. она  кончилась  -  то
     вызов exit() делается неявно, но с непредсказуемым значением retcode;
-    процесс может быть убит сигналом. В этом  случае  он  не  выдает  никакого  кода
     ответа в процесс-родитель, а выдает признак "процесс убит".

6.5.5.  В действительности exit() - это еще не  сам  системный  вызов  завершения,  а
стандартная  функция.   Сам системный вызов называется _exit().  Мы можем переопреде-
лить функцию exit() так, чтобы по окончании программы происходили некоторые действия:

    void exit(unsigned code){
      /* Добавленный мной дополнительный оператор: */
      printf("Закончить работу, "
             "код ответа=%u\n", code);

      /* Стандартные операторы: */
      _cleanup();  /* закрыть все открытые файлы.
                    * Это стандартная функция [**] */
      _exit(code); /* собственно сисвызов */
    }

    int f(){ return 17; }
    void main(){
      printf("aaaa\n"); printf("bbbb\n"); f();
      /* потом откомментируйте это:  exit(77); */
    }

Здесь функция exit вызывается неявно по окончании main, ее  подставляет  в  программу
компилятор.  Дело  в  том,  что при запуске программы exec-ом, первым начинает выпол-
няться код так называемого "стартера", подклеенного при  сборке  программы  из  файла
/lib/crt0.o. Он выглядит примерно так (в действительности он написан на ассемблере):

    ... // вычислить argc, настроить некоторые параметры.
    main(argc, argv, envp);
    exit();


А. Богатырев, 1992-95                  - 225 -                              Си в UNIX

или так (взято из проекта GNU[*][*]):

    int errno = 0;
    char **environ;
    _start(int argc, int arga)
    {
    /* OS and Compiler dependent!!!! */
    char **argv = (char **) &arga;
    char **envp = environ = argv + argc + 1;
    /* ... возможно еще какие-то инициализации,
     * наподобие setlocale( LC_ALL, "" ); в SCO UNIX */
    exit (main(argc, argv, envp));
    }

Где должно быть

    int main(int argc, char *argv[], char *envp[]){
              ...
            return 0;  /* вместо exit(0); */
    }

Адрес функции _start() помечается в одном из полей заголовка файла формата a.out  как
адрес,  на  который  система  должна  передать  управление после загрузки программы в
память (точка входа).
     Какой код ответа попадет в exit() в этих примерах (если отсутствует явный  вызов
exit или return) - непредсказуемо.  На IBM PC в вышенаписанном примере этот код равен
17, то есть значению, возвращенному последней вызывавшейся функцией.  Однако  это  не
какое-то  специальное соглашение, а случайный эффект (так уж устроен код, создаваемый
этим компилятором).

6.5.6.  Процесс-отец может дождаться окончания своего потомка.  Это делается  систем-
ным вызовом wait и нужно по следующей причине: пусть отец - это интерпретатор команд.
Если он запустил процесс и продолжил свою работу, то оба процесса будут предпринимать
попытки  читать ввод с клавиатуры терминала - интерпретатор ждет команд, а запущенная
программа ждет данных.  Кому из них будет поступать набираемый нами текст -  непредс-
казуемо!   Вывод:  интерпретатор  команд  должен "заснуть" на то время, пока работает
порожденный им процесс:

    int pid;  unsigned short status;
             ...
    if((pid = fork()) == 0 ){
            /* порожденный процесс */
             ...  // перенаправления ввода-вывода.
             ...  // настройка сигналов.
            exec(....);
            perror("exec не удался"); exit(1);
    }
    /* иначе это породивший процесс */
    while((pid = wait(&status)) > 0 )
      printf("Окончился сын pid=%d с кодом %d\n",
              pid, status >> 8);
    printf( "Больше нет сыновей\n");


____________________
   [**] _cleanup() закрывает файлы, открытые fopen()ом, "вытряхая" при этом данные,  на-
копленные в буферах, в файл.  При аварийном завершении программы файлы все равно зак-
рываются, но уже не явно, а операционной системой (в вызове _exit).  При этом  содер-
жимое недосброшенных буферов будет утеряно.
____________________
   [*][*] GNU - программы, распространяемые в исходных текстах из Free  Software  Founda-



А. Богатырев, 1992-95                  - 226 -                              Си в UNIX

wait приостанавливает[*] выполнение вызвавшего процесса до момента окончания любого  из
порожденных  им  процессов  (ведь  можно  было запустить и нескольких сыновей!).  Как
только какой-то потомок окончится  -  wait  проснется  и  выдаст  номер  (pid)  этого
потомка.   Когда  никого  из живых "сыновей" не осталось - он выдаст (-1).  Ясно, что
процессы могут оканчиваться не в том порядке, в котором их порождали.   В  переменную
status  заносится  в  специальном  виде код ответа окончившегося процесса, либо номер
сигнала, которым он был убит.

    #include <sys/types.h>
    #include <sys/wait.h>
            ...
    int status, pid;
            ...
    while((pid = wait(&status)) > 0){
        if( WIFEXITED(status)){
          printf( "Процесс %d умер с кодом %d\n",
                           pid,            WEXITSTATUS(status));
        } else if( WIFSIGNALED(status)){
          printf( "Процесс %d убит сигналом %d\n",
                           pid,             WTERMSIG(status));
          if(WCOREDUMP(status)) printf( "Образовался core\n" );
          /* core - образ памяти процесса для отладчика adb */
        } else if( WIFSTOPPED(status)){
          printf( "Процесс %d остановлен сигналом %d\n",
                           pid,            WSTOPSIG(status));
        } else if( WIFCONTINUED(status)){
          printf( "Процесс %d продолжен\n",
                           pid);
        }
    }
            ...

Если код ответа нас не интересует, мы можем писать wait(NULL).
     Если у нашего процесса не было или больше нет живых сыновей - вызов wait  ничего
не  ждет, а возвращает значение (-1).  В написанном примере цикл while позволяет дож-
даться окончания всех потомков.
     В тот момент, когда процесс-отец получает информацию о причине  смерти  потомка,
паспорт  умершего  процесса  наконец  вычеркивается из таблицы процессов и может быть
переиспользован новым процессом.  До того, он хранится в таблице процессов в  состоя-
нии  "zombie"  - "живой мертвец".  Только для того, чтобы кто-нибудь мог узать статус
его завершения.
     Если процесс-отец завершился раньше своих сыновей, то  кто  же  сделает  wait  и
вычеркнет  паспорт?   Это  сделает процесс номер 1: /etc/init.  Если отец умер раньше
процессов-сыновей, то система заставляет процесс номер 1  "усыновить"  эти  процессы.
init  обычно  находится  в  цикле,  содержащем в начале вызов wait(), то есть ожидает
____________________
tion  (FSF).  Среди них - C++ компилятор g++ и редактор emacs. Смысл слов GNU - "gen-
erally not UNIX" - проект был основан как противодействие начавшейся коммерциализации
UNIX и закрытию его исходных текстов. "Сделать как в UNIX, но лучше".
   [*] "Живой" процесс может пребывать в одном из нескольких состояний: процесс ожидает
наступления  какого-то события ("спит"), при этом ему не выделяется время процессора,
т.к. он не готов к выполнению; процесс готов к выполнению и стоит в очереди к процес-
сору (поскольку процессор выполняет другой процесс); процесс готов и выполняется про-
цессором в данный момент.  Последнее состояние может происходить  в  двух  режимах  -
пользовательском (выполняются команды сегмента text) и системном (процессом был издан
системный вызов, и сейчас выполняется функция в ядре). Ожидание события бывает только
в  системной фазе - внутри системного вызова (т.е.  это "синхронное" ожидание). Неак-
тивные процессы ("спящие" или ждущие ресурса процессора) могут быть временно откачаны
на диск.





А. Богатырев, 1992-95                  - 227 -                              Си в UNIX

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

        |pid=719,csh
        |
    if(!fork())------->--------* pid=723,csh
        |                      |                  загрузить
     wait(&status)           exec("a.out",...) <-- a.out
        :                    main(...){           с диска
        :                      |
        :pid=719,csh           | pid=723,a.out
      спит(ждет)             работает
        :                      |
        :                    exit(status) умер
        :                    }
     проснулся <---проснись!--RIP
        |
        |pid=719,csh

Заметьте, что номер порожденного процесса не обязан быть следующим за  номером  роди-
теля,  а  только  больше него. Это связано с тем, что другие процессы могли создать в
системе новые процессы до того, как наш процесс издал свой вызов fork.

6.5.7.  Кроме того, wait позволяет отслеживать  остановку  процесса.   Процесс  может
быть  приостановлен  при  помощи  посылки  ему  сигналов  SIGSTOP,  SIGTTIN, SIGTTOU,
SIGTSTP.  Последние три сигнала посылает  при  определенных  обстоятельствах  драйвер
терминала,  к  примеру  SIGTSTP  -  при нажатии клавиши CTRL/Z.  Продолжается процесс
посылкой ему сигнала SIGCONT.
     В данном контексте, однако, нас интересуют не сами эти сигналы, а  другая  схема
манипуляции  с  отслеживанием статуса порожденных процессов.  Если указано явно, сис-
тема может посылать процессу-родителю сигнал SIGCLD в момент изменения статуса любого
из его потомков.  Это позволит процессу-родителю немедленно сделать wait и немедленно
отразить изменение состояние процесса-потомка в  своих  внутренних  списках.   Данная
схема программируется так:

    void pchild(){
            int pid, status;

            sighold(SIGCLD);
            while((pid = waitpid((pid_t) -1, &status, WNOHANG|WUNTRACED)) > 0){
              dorecord:
                    записать_информацию_об_изменениях;
            }
            sigrelse(SIGCLD);

            /* Reset */
            signal(SIGCLD, pchild);
    }
            ...
    main(){
            ...
            /* По сигналу SIGCLD вызывать функцию pchild */
            signal(SIGCLD, pchild);
            ...
            главный_цикл;
    }

Секция  с  вызовом  waitpid  (разновидность  вызова  wait),  прикрыта  парой  функций
sighold-sigrelse,  запрещающих  приход сигнала SIGCLD внутри этой критической секции.



А. Богатырев, 1992-95                  - 228 -                              Си в UNIX

Сделано это вот для чего: если процесс начнет модифицировать  таблицы  или  списки  в
районе  метки  dorecord:,  а  в этот момент придет еще один сигнал, то функция pchild
будет вызвана рекурсивно и тоже попытается модифицировать таблицы и списки, в которых
еще остались незавершенными перестановки ссылок, элементов, счетчиков. Это приведет к
разрушению данных.
     Поэтому сигналы должны приходить последовательно, и  функции  pchild  вызываться
также  последовательно, а не рекурсивно. Функция sighold откладывает доставку сигнала
(если он случится), а sigrelse - разрешает доставить накопившиеся сигналы (но если их
пришло  несколько  одного типа - все они доставляются как один такой сигнал. Отсюда -
цикл вокруг waitpid).
     Флаг WNOHANG - означает "не ждать внутри вызова wait", если ни один из  потомков
не  изменил  своего  состояния;  а  просто вернуть код (-1)".  Это позволяет вызывать
pchild даже без получения сигнала: ничего не произойдет.  Флаг WUNTRACED  -  означает
"выдавать информацию также об остановленных процессах".

6.5.8.  Как уже было сказано, при exec все  открытые  файлы  достаются  в  наследство
новой программе (в частности, если между fork и exec были перенаправлены вызовом dup2
стандартные ввод и вывод, то они останутся перенаправленными и  у  новой  программы).
Что  делать,  если  мы  не  хотим,  чтобы  наследовались все открытые файлы? (Хотя бы
потому, что большинством из них новая программа пользоваться не будет  -  в  основном
она  будет использовать лишь fd 0, 1 и 2; а ячейки в таблице открытых файлов процесса
они занимают).  Во-первых, ненужные дескрипторы можно явно закрыть close в промежутке
между  fork-ом  и  exec-ом.   Однако не всегда мы помним номера дескрипторов для этой
операции.  Более радикальной мерой является тотальная чистка:

    for(f = 3; f < NOFILE; f++)
            close(f);

Есть более элегантный путь.  Можно  пометить  дескриптор  файла  специальным  флагом,
означающим, что во время вызова exec этот дескриптор должен быть автоматически закрыт
(режим file-close-on-exec - fclex):

    #include <fcntl.h>
    int fd = open(.....);
    fcntl (fd, F_SETFD, 1);

Отменить этот режим можно так:

    fcntl (fd, F_SETFD, 0);

Здесь есть одна тонкость: этот флаг устанавливается не для структуры file - "открытый
файл",  а непосредственно для дескриптора в таблице открытых процессом файлов (массив
флагов: char u_pofile[NOFILE]).  Он не сбрасывается при закрытии файла,  поэтому  нас
может ожидать сюрприз:

     ... fcntl (fd, F_SETFD, 1); ... close(fd);
     ...
     int fd1 = open( ... );

Если fd1 окажется равным fd, то дескриптор fd1 будет при exec-е закрыт, чего мы  явно
не ожидали! Поэтому перед close(fd) полезно было бы отменить режим fclex.

6.5.9.  Каждый процесс имеет управляющий терминал (short *u_ttyp).  Он достается про-
цессу  в  наследство от родителя (при fork и exec) и обычно совпадает с терминалом, с
на котором работает данный пользователь.
     Каждый процесс относится к некоторой  группе  процессов  (int  p_pgrp),  которая
также наследуется. Можно послать сигнал всем процессам указанной группы pgrp:
    kill( -pgrp, sig );
Вызов
    kill( 0, sig );
посылает сигнал sig всем  процессам,  чья  группа  совпадает  с  группой  посылающего



А. Богатырев, 1992-95                  - 229 -                              Си в UNIX

процесса.  Процесс может узнать свою группу:
    int pgrp = getpgrp();
а может стать "лидером" новой группы. Вызов
    setpgrp();
делает следующие операции:

    /* У процесса больше нет управл. терминала: */
    if(p_pgrp != p_pid) u_ttyp = NULL;
    /* Группа процесса полагается равной его ид-у: */
    p_pgrp = p_pid;  /* new group */

В свою очередь, управляющий терминал тоже имеет некоторую группу (t_pgrp). Это значе-
ние устанавливается равным группе процесса, первым открывшего этот терминал:

    /* часть процедуры открытия терминала */
    if( p_pid == p_pgrp // лидер группы
     && u_ttyp == NULL  // еще нет упр.терм.
     && t_pgrp == 0 ){  // у терминала нет группы
            u_ttyp = &t_pgrp;
            t_pgrp =  p_pgrp;
    }

Таким процессом обычно является процесс регистрации пользователя в  системе  (который
спрашивает  у вас имя и пароль).  При закрытии терминала всеми процессами (что бывает
при выходе пользователя из системы) терминал теряет группу: t_pgrp=0;
     При нажатии на клавиатуре терминала некоторых клавиш:

    c_cc[ VINTR ]     обычно DEL или CTRL/C
    c_cc[ VQUIT ]     обычно CTRL/\

драйвер терминала посылает соответственно сигналы SIGINT  и  SIGQUIT  всем  процессам
группы терминала, т.е. как бы делает
    kill( -t_pgrp, sig );
Именно поэтому мы можем прервать процесс нажатием клавиши DEL.  Поэтому, если процесс
сделал  setpgrp(),  то сигнал с клавиатуры ему послать невозможно (т.к. он имеет свой
уникальный номер группы != группе терминала).
     Если процесс еще не имеет управляющего терминала (или уже  его  не  имеет  после
setpgrp),  то он может сделать любой терминал (который он имеет право открыть) управ-
ляющим для себя.  Первый же файл-устройство, являющийся интерфейсом драйвера термина-
лов, который будет открыт этим процессом, станет для него управляющим терминалом. Так
процесс может иметь каналы 0, 1, 2 связанные с одним терминалом, а  прерывания  полу-
чать с клавиатуры другого (который он сделал управляющим для себя).
     Процесс регистрации пользователя в системе - /etc/getty (название происходит  от
"get  tty"  -  получить терминал) - запускается процессом номер 1 - /etc/init-ом - на
каждом из терминалов, зарегистрированных в системе, когда
-    система только что была запущена;
-    либо когда пользователь на каком-то терминале вышел  из  системы  (интерпретатор
     команд завершился).
В сильном упрощении getty может быть описан так:

    void main(ac, av) char *av[];
    {   int f; struct termio tmodes;

        for(f=0; f < NOFILE; f++) close(f);

        /* Отказ от управляющего терминала,
         * основание новой группы процессов.
         */
        setpgrp();

        /* Первоначальное явное открытие терминала */



А. Богатырев, 1992-95                  - 230 -                              Си в UNIX

        /* При этом терминал av[1] станет упр. терминалом */
            open( av[1], O_RDONLY ); /* fd = 0 */
            open( av[1], O_RDWR   ); /* fd = 1 */


        // ... Считывание параметров терминала из файла
        // /etc/gettydefs. Тип требуемых параметров линии
        // задается меткой, указываемой в av[2].
        // Заполнение структуры tmodes требуемыми
        // значениями ... и установка мод терминала.
        ioctl (f, TCSETA, &tmodes);

        // ... запрос имени и пароля ...

        chdir (домашний_каталог_пользователя);

        execl ("/bin/csh", "-csh", NULL);
        /* Запуск интерпретатора команд. Группа процессов,
         * управл. терминал, дескрипторы 0,1,2 наследуются.
         */
    }

Здесь последовательные вызовы open занимают последовательные ячейки в таблице  откры-
тых  процессом файлов (поиск каждой новой незанятой ячейки производится с начала таб-
лицы) - в итоге по дескрипторам 0,1,2 открывается файл-терминал.  После  этого  деск-
рипторы 0,1,2 наследуются всеми потомками интерпретатора команд.  Процесс init запус-
кает по одному процессу getty на каждый терминал, как бы делая

            /etc/getty /dev/tty01 m &
            /etc/getty /dev/tty02 m &
                    ...

и ожидает окончания любого из них.  После входа пользователя в  систему  на  каком-то
терминале,  соответствующий  getty  превращается в интерпретатор команд (pid процесса
сохраняется).  Как только кто-то из них умрет - init перезапустит getty на соответст-
вующем  терминале  (все  они - его сыновья, поэтому он знает - на каком именно терми-
нале).

6.6.  Трубы и FIFO-файлы.
     Процессы могут обмениваться между  собой  информацией  через  файлы.  Существуют
файлы с необычным поведением - так называемые FIFO-файлы (first in, first out), веду-
щие себя подобно очереди. У них указатели чтения и записи разделены. Работа  с  таким
файлом напоминает проталкивание шаров через трубу - с одного конца мы вталкиваем дан-
ные, с другого конца - вынимаем их.  Операция чтения из пустой "трубы"  проиостановит
вызов  read  (и  издавший его процесс) до тех пор, пока кто-нибудь не запишет в FIFO-
файл какие-нибудь данные.  Операция позиционирования указателя - lseek()  -  неприме-
нима к FIFO-файлам.  FIFO-файл создается системным вызовом

    #include <sys/types.h>
    #include <sys/stat.h>
       mknod( имяФайла, S_IFIFO | 0666, 0 );

где 0666 - коды доступа к файлу.  При помощи FIFO-файла могут общаться даже  неродст-
венные процессы.
     Разновидностью FIFO-файла является  безымянный  FIFO-файл,  предназначенный  для
обмена информацией между процессом-отцом и процессом-сыном.  Такой файл - канал связи
как раз и называется термином "труба" или pipe.  Он создается вызовом pipe:

    int conn[2];   pipe(conn);

Если бы файл-труба имел имя PIPEFILE, то вызов pipe можно было бы описать как



А. Богатырев, 1992-95                  - 231 -                              Си в UNIX

    mknod("PIPEFILE", S_IFIFO | 0600, 0);
    conn[0] = open("PIPEFILE", O_RDONLY);
    conn[1] = open("PIPEFILE", O_WRONLY);
    unlink("PIPEFILE");

При вызове fork каждому из двух процессов достанется в наследство пара дескрипторов:

                 pipe(conn);
                   fork();

    conn[0]----<----    ----<-----conn[1]
                    FIFO
    conn[1]---->----    ---->-----conn[0]
     процесс A                 процесс B

Пусть процесс A будет посылать информацию в процесс B.  Тогда процесс A сделает:

    close(conn[0]);
    // т.к. не собирается ничего читать
    write(conn[1], ... );

а процесс B

    close(conn[1]);
    // т.к. не собирается ничего писать
    read (conn[0], ... );

Получаем в итоге:

    conn[1]---->----FIFO---->-----conn[0]
     процесс A                 процесс B

Обычно поступают еще более элегантно,  перенаправляя  стандартный  вывод  A  в  канал
conn[1]

    dup2 (conn[1], 1); close(conn[1]);
    write(1, ... );   /* или printf */

а стандартный ввод B - из канала conn[0]

    dup2(conn[0], 0); close(conn[0]);
    read(0, ... );    /* или gets */

Это соответствует конструкции

         $   A | B

записанной на языке СиШелл.
     Файл, выделяемый под pipe, имеет ограниченный размер (и поэтому  обычно  целиком
оседает  в буферах в памяти машины).  Как только он заполнен целиком - процесс, пишу-
щий в трубу вызовом write, приостанавливается до появления свободного места в  трубе.
Это  может привести к возникновению тупиковой ситуации, если писать программу неакку-
ратно.  Пусть процесс A является сыном процесса B, и пусть  процесс  B  издает  вызов
wait,  не  закрыв канал conn[0].  Процесс же A очень много пишет в трубу conn[1].  Мы
получаем ситуацию, когда оба процесса спят:
A    потому что труба переполнена, а процесс B ничего из нее не читает, так как  ждет
     окончания A;
B    потому что процесс-сын A не окончился, а он не может окончиться пока не  допишет
     свое сообщение.
Решением служит запрет процессу B делать вызов wait до тех пор, пока он не  прочитает
ВСЮ  информацию  из трубы (не получит EOF). Только сделав после этого close(conn[0]);



А. Богатырев, 1992-95                  - 232 -                              Си в UNIX

процесс B имеет право сделать wait.
     Если процесс B закроет свою сторону трубы close(conn[0]) прежде, чем  процесс  A
закончит  запись  в нее, то при вызове write в процессе A, система пришлет процессу A
сигнал SIGPIPE - "запись в канал, из которого никто не читает".

6.6.1.  Открытие FIFO файла приведет к блокированию процесса  ("засыпанию"),  если  в
буфере  FIFO файла пусто. Процесс заснет внутри вызова open до тех пор, пока в буфере
что-нибудь не появится.
     Чтобы избежать такой ситуации, а, например, сделать что-нибудь иное  полезное  в
это  время,  нам  надо  было бы опросить файл на предмет того - можно ли его открыть?
Это делается при помощи флага O_NDELAY у вызова open.

    int fd = open(filename, O_RDONLY|O_NDELAY);

Если open ведет к блокировке процесса внутри вызова, вместо  этого  будет  возвращено
значение  (-1).  Если  же файл может быть немедленно открыт - возвращается нормальный
дескриптор со значением >=0, и файл открыт.
     O_NDELAY является зависимым от семантики того файла, который  мы  открываем.   К
примеру,  можно  использовать  его  с файлами устройств, например именами, ведущими к
последовательным портам. Эти файлы устройств  (порты)  обладают  тем  свойством,  что
одновременно  их  может  открыть только один процесс (так устроена реализация функции
open внутри драйвера этих устройств). Поэтому, если один процесс уже работает с  пор-
том,  а  в  это  время второй пытается его же открыть, второй "заснет" внутри open, и
будет дожидаться освобождения порта close первым процессом.  Чтобы не ждать - следует
открывать порт с флагом O_NDELAY.

    #include <stdio.h>
    #include <fcntl.h>

    /* Убрать больше не нужный O_NDELAY */
    void nondelay(int fd){
            fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) & ~O_NDELAY);
    }
    int main(int ac, char *av[]){
            int fd;
            char *port = ac > 1 ? "/dev/term/a" : "/dev/cua/a";


    retry:  if((fd = open(port, O_RDWR|O_NDELAY)) < 0){
                    perror(port);
                    sleep(10);
                    goto retry;
            }
            printf("Порт %s открыт.\n", port);
            nondelay(fd);

            printf("Работа с портом, вызови эту программу еще раз!\n");
            sleep(60);
            printf("Все.\n");
            return 0;
    }

Вот протокол:











А. Богатырев, 1992-95                  - 233 -                              Си в UNIX

    su# a.out & a.out xxx
    [1] 22202
    Порт /dev/term/a открыт.
    Работа с портом, вызови эту программу еще раз!
    /dev/cua/a: Device busy
    /dev/cua/a: Device busy
    /dev/cua/a: Device busy
    /dev/cua/a: Device busy
    /dev/cua/a: Device busy
    /dev/cua/a: Device busy
    Все.
    Порт /dev/cua/a открыт.
    Работа с портом, вызови эту программу еще раз!
    su#


6.7.  Нелокальный переход.
     Теперь поговорим про нелокальный переход. Стандартная функция  setjmp  позволяет
установить в программе "контрольную точку"[*], а функция longjmp осуществляет прыжок  в
эту  точку,  выполняя  за  один раз выход сразу из нескольких вызванных функций (если
надо)[**].  Эти функции не являются системными вызовами, но  поскольку  они  реализуются
машинно-зависимым образом, а используются чаще всего как реакция на некоторый сигнал,
речь о них идет в этом разделе.  Вот как, например,  выглядит  рестарт  программы  по
прерыванию с клавиатуры:

    #include <signal.h>
    #include <setjmp.h>
    jmp_buf jmp;  /* контрольная точка */

    /* прыгнуть в контрольную точку */
    void onintr(nsig){ longjmp(jmp, nsig); }

    main(){
       int n;
       n = setjmp(jmp);  /* установить контрольную точку */
       if( n ) printf( "Рестарт после сигнала %d\n", n);
       signal (SIGINT, onintr);     /* реакция на сигнал */
       printf("Начали\n");
       ...
    }

setjmp возвращает 0 при запоминании контрольной  точки.   При  прыжке  в  контрольную
точку  при помощи longjmp, мы оказываемся снова в функции setjmp, и эта функция возв-
ращает нам значение второго аргумента longjmp, в этом примере - nsig.
     Прыжок в контрольную точку очень удобно использовать  в  алгоритмах  перебора  с
возвратом  (backtracking): либо - если ответ найден - прыжок на печать ответа, либо -
если ветвь перебора зашла в тупик - прыжок в точку ветвления и выбор другой альтерна-
тивы.  При этом можно делать прыжки и в рекурсивных вызовах одной и той же функции: с
более высокого уровня рекурсии в вызов более низкого уровня (в  этом  случае  jmp_buf
лучше делать автоматической переменной - своей для каждого уровня вызова функции).


____________________
   [*] В некотором буфере запоминается текущее состояние  процесса:  положение  вершины
стека  вызовов  функций (stack pointer); состояние всех регистров процессора, включая
регистр адреса текущей машинной команды (instruction pointer).
   [**] Это достигается восстановлением состояния процесса из буфера. Изменения,  проис-
шедшие  за  время между setjmp и longjmp в статических данных не отменяются (т.к. они
не сохранялись).





А. Богатырев, 1992-95                  - 234 -                              Си в UNIX

6.7.1.  Перепишите следующий алгоритм при помощи longjmp.

    #define FOUND    1 /* ответ найден    */
    #define NOTFOUND 0 /* ответ не найден */
    int value;         /* результат */
    main(){    int i;
      for(i=2; i < 10; i++){
          printf( "пробуем i=%d\n", i);
          if( test1(i) == FOUND ){
              printf("ответ %d\n", value); break;
          }
      }
    }
    test1(i){  int j;
      for(j=1; j < 10 ; j++ ){
          printf( "пробуем j=%d\n", j);
          if( test2(i,j) == FOUND ) return FOUND;
          /* "сквозной" return */
      }
      return NOTFOUND;
    }
    test2(i, j){
      printf( "пробуем(%d,%d)\n", i, j);
      if( i * j == 21 ){
          printf( "  Годятся (%d,%d)\n", i,j);
          value = j; return FOUND;
      }
      return NOTFOUND;
    }

Вот ответ, использующий нелокальный переход вместо цепочки return-ов:

    #include <setjmp.h>
    jmp_buf jmp;
    main(){   int i;
      if( i = setjmp(jmp))  /* после прыжка */
            printf("Ответ %d\n", --i);
      else  /* установка точки */
        for(i=2; i < 10; i++)
          printf( "пробуем i=%d\n", i), test1(i);
    }
    test1(i){ int j;
      for(j=1; j < 10 ; j++ )
          printf( "пробуем j=%d\n", j), test2(i,j);
    }
    test2(i, j){
      printf( "пробуем(%d,%d)\n", i, j);
      if( i * j == 21 ){
         printf( "  Годятся (%d,%d)\n", i,j);
         longjmp(jmp, j + 1);
      }
    }

Обратите внимание, что при возврате ответа через второй аргумент longjmp мы прибавили
1,  а  при  печати  ответа  мы эту единицу отняли. Это сделано на случай ответа j==0,
чтобы функция setjmp не вернула бы в этом случае значение 0 (признак установки  конт-
рольной точки).

6.7.2.  В чем ошибка?

    #include <setjmp.h>



А. Богатырев, 1992-95                  - 235 -                              Си в UNIX

    jmp_buf jmp;
    main(){
         g();
         longjmp(jmp,1);
    }
    g(){ printf("Вызвана g\n");
         f();
         printf("Выхожу из g\n");
    }
    f(){
         static n;
         printf( "Вызвана f\n");
         setjmp(jmp);
         printf( "Выхожу из f %d-ый раз\n", ++n);
    }

Ответ: longjmp делает прыжок в функцию f(), из которой уже произошел возврат управле-
ния. При переходе в тело функции в обход ее заголовка не выполняются машинные команды
"пролога" функции - функция остается "неактивированной". При  возврате  из  вызванной
таким  "нелегальным"  путем  функции  возникает ошибка, и программа падает. Мораль: в
функцию, которая НИКЕМ НЕ ВЫЗВАНА, нельзя передавать управление.  Обратный  прыжок  -
из  f()  в main() - был бы законен, поскольку функция main() является активной, когда
управление находится в теле функции f().  Т.е. можно "прыгать" из вызванной функции в
вызывающую: из f() в main() или в g(); и из g() в main();

    --        --
     |   f    |  стек      прыгать
     |   g    |  вызовов   сверху вниз
     |   main |  функций   можно - это соответствует
     ----------            выкидыванию нескольких
                           верхних слоев стека

но нельзя наоборот: из main() в g() или f(); а также  из  g()  в  f().   Можно  также
совершать прыжок в пределах одной и той же функции:

    f(){ ...
            A:   setjmp(jmp);
                 ...
                 longjmp(jmp, ...); ...
                 /* это как бы goto A; */
    }


6.8.  Хозяин файла, процесса, и проверка привелегий.
     UNIX - многопользовательская система. Это значит,  что  одновременно  на  разных
терминалах, подключенных к машине, могут работать разные пользователи (а может и один
на нескольких терминалах). На каждом терминале работает  свой  интерпретатор  команд,
являющийся потомком процесса /etc/init.

6.8.1.  Теперь - про функции, позволяющие узнать некоторые данные про любого  пользо-
вателя  системы.   Каждый  пользователь  в UNIX имеет уникальный номер: идентификатор
пользователя (user id), а также уникальное имя: регистрационное имя, которое он наби-
рает  для  входа  в  систему.   Вся  информация  о  пользователях  хранится  в  файле
/etc/passwd. Существуют функции, позволяющие по номеру пользователя узнать  регистра-
ционное имя и наоборот, а заодно получить еще некоторую информацию из passwd:









А. Богатырев, 1992-95                  - 236 -                              Си в UNIX

    #include <stdio.h>
    #include <pwd.h>
    struct passwd *p;
    int   uid;   /* номер */
    char *uname; /* рег. имя */

    uid = getuid();
    p   = getpwuid( uid   );
            ...
    p   = getpwnam( uname );

Эти функции возвращают указатели на статические структуры, скрытые внутри этих  функ-
ций.  Структуры эти имеют поля:

    p->pw_uid     идентиф. пользователя (int uid);
    p->pw_gid     идентиф. группы пользователя;

            и ряд полей типа char[]
    p->pw_name    регистрационное имя пользователя (uname);
    p->pw_dir     полное имя домашнего каталога
      (каталога, становящегося текущим при входе в систему);
    p->pw_shell   интерпретатор команд
      (если "", то имеется в виду /bin/sh);
    p->pw_comment произвольная учетная информация (не используется);
    p->pw_gecos   произвольная учетная информация (обычно ФИО);
    p->pw_passwd  зашифрованный пароль для входа в
       систему. Истинный пароль нигде не хранится вовсе!

Функции возвращают значение p==NULL, если указанный пользователь не существует  (нап-
ример,  если  задан неверный uid).  uid хозяина данного процесса можно узнать вызовом
getuid, а uid владельца файла - из поля st_uid структуры, заполняемой системным вызо-
вом stat (а идентификатор группы владельца - из поля st_gid).  Задание: модифицируйте
наш аналог программы ls, чтобы он выдавал в  текстовом  виде  имя  владельца  каждого
файла в каталоге.

6.8.2.  Владелец файла может изменить своему файлу идентификаторы владельца и  группы
вызовом

    chown(char *имяФайла, int uid, int gid);

т.е. "подарить" файл другому пользователю.  Забрать чужой файл себе невозможно.   При
этой операции биты S_ISUID и S_ISGID в кодах доступа к файлу (см. ниже) сбрасываются,
поэтому создать "Троянского коня" и, сделав его хозяином суперпользователя,  получить
неограниченные привелегии - не удастся!

6.8.3.  Каждый файл имеет своего владельца (поле di_uid в I-узле на  диске  или  поле
i_uid в копии I-узла в памяти ядра[*]).  Каждый процесс также  имеет  своего  владельца
(поля u_uid и u_ruid в u-area).  Как мы видим, процесс имеет два параметра, обознача-
ющие владельца. Поле ruid называется "реальным идентификатором" пользователя, а uid -
"эффективным  идентификатором".   При вызове exec() заменяется программа, выполняемая
данным процессом:

____________________
   [*] При открытии файла и вообще при любой операции с файлом, в таблицах  ядра  заво-
дится  копия  I-узла  (для ускорения доступа, чтобы постоянно не обращаться к диску).
Если I-узел в памяти будет изменен, то при закрытии файла (а также периодически через
некоторые  промежутки  времени)  эта копия будет записана обратно на диск.  Структура
I-узла в памяти - struct inode - описана в файле <sys/inode.h>, а на диске  -  struct
dinode - в файле <sys/ino.h>.





А. Богатырев, 1992-95                  - 237 -                              Си в UNIX

    старая программа  exec    новая программа


                        |
                   выполняемый файл
                    i_uid (st_uid)

Как видно из этой схемы, реальный идентификатор хозяина процесса наследуется.  Эффек-
тивный  идентификатор  обычно также наследуется, за исключением одного случая: если в
кодах доступа файла (i_mode) выставлен бит S_ISUID (set-uid bit),  то  значение  поля
u_uid в новом процессе станет равно значению i_uid файла с программой:

    /* ... во время exec ... */
    p_suid = u_uid;     /* спасти */
    if( i_mode & S_ISUID ) u_uid = i_uid;
    if( i_mode & S_ISGID ) u_gid = i_gid;

т.е. эффективным владельцем процесса станет владелец файла.  Здесь gid - это  иденти-
фикаторы  группы  владельца  (которые тоже есть и у файла и у процесса, причем у про-
цесса - реальный и эффективный).
     Зачем все это надо? Во-первых затем, что ПРАВА процесса на доступ к  какому-либо
файлу  проверяются  именно  для эффективного владельца процесса.  Т.е. например, если
файл имеет коды доступа

    mode = i_mode & 0777;
                  /* rwx rwx rwx */

и владельца i_uid, то процесс, пытающийся открыть этот файл, будет "проэкзаменован" в
таком порядке:

    if( u_uid == 0 )  /* super user */
         то доступ разрешен;
    else if( u_uid == i_uid )
         проверить коды (mode & 0700);
    else if( u_gid == i_gid )
         проверить коды (mode & 0070);
    else проверить коды (mode & 0007);

Процесс может узнать свои параметры:

    unsigned short uid  = geteuid();  /* u_uid  */
    unsigned short ruid = getuid();   /* u_ruid */
    unsigned short gid  = getegid();  /* u_gid  */
    unsigned short rgid = getuid();   /* u_rgid */

а также установить их:

    setuid(newuid);  setgid(newgid);

Рассмотрим вызов setuid. Он работает так (u_uid -  относится  к  процессу,  издавшему
этот вызов):

    if(      u_uid == 0 /* superuser */ )
             u_uid = u_ruid =    p_suid =  newuid;
    else if( u_ruid == newuid || p_suid == newuid )
             u_uid = newuid;
    else     неудача;

Поле p_suid позволяет  set-uid-ной  программе  восстановить  эффективного  владельца,
который был у нее до exec-а.




А. Богатырев, 1992-95                  - 238 -                              Си в UNIX

     Во-вторых, все это надо для следующего случая: пусть у меня есть некоторый  файл
BASE  с  хранящимися  в нем секретными сведениями. Я являюсь владельцем этого файла и
устанавливаю ему коды доступа 0600 (чтение и запись разрешены  только  мне).  Тем  не
менее,  я  хочу  дать другим пользователям возможность работать с этим файлом, однако
контролируя их деятельность.  Для этого я пишу программу, которая выполняет некоторые
действия  с  файлом  BASE,  при этом проверяя законность этих действий, т.е. позволяя
делать не все что попало, а лишь то, что я в ней предусмотрел, и под жестким  контро-
лем.   Владельцем  файла PROG, в котором хранится эта программа, также являюсь я, и я
задаю этому файлу коды доступа 0711 (rwx--x--x) - всем можно выполнять эту программу.
Все  ли  я  сделал, чтобы позволить другим пользоваться базой BASE через программу (и
только нее) PROG?  Нет!
     Если кто-то другой запустит программу PROG, то  эффективный  идентификатор  про-
цесса  будет  равен  идентификатору этого другого пользователя, и программа не сможет
открыть мой файл BASE.  Чтобы все работало, процесс, выполняющий программу PROG, дол-
жен работать как бы от моего имени. Для этого я должен вызовом chmod либо командой
     chmod u+s PROG
добавить к кодам доступа файла PROG бит S_ISUID.
     После этого, при запуске программы PROG, она будет получать эффективный  иденти-
фикатор,  равный  моему  идентификатору,  и таким образом сможет открыть и работать с
файлом BASE.  Вызов getuid позволяет выяснить, кто вызвал мою  программу  (и  занести
это в протокол, если надо).
     Программы такого типа - не редкость в UNIX, если владельцем программы (файла  ее
содержащего)  является  суперпользователь. В таком случае программа, имеющая бит дос-
тупа S_ISUID работает от имени суперпользователя и может выполнять  некоторые  дейст-
вия, запрещенные обычным пользователям. При этом программа внутри себя делает всячес-
кие проверки и периодически спрашивает пароли, то есть при работе защищает систему от
дураков  и преднамеренных вредителей.  Простейшим примером служит команда ps, которая
считывает таблицу процессов из памяти ядра и распечатывает ее.  Доступ  к  физической
памяти  машины  производится  через файл-псевдоустройство /dev/mem, а к памяти ядра -
/dev/kmem.  Чтение и запись в них позволены только суперпользователю,  поэтому  прог-
раммы "общего пользования", обращающиеся к этим файлам, должны иметь бит set-uid.
     Откуда же изначально берутся значения uid и ruid (а также gid  и  rgid)  у  про-
цесса?  Они берутся из процесса регистрации пользователя в системе: /etc/getty.  Этот
процесс запускается на каждом терминале как процесс, принадлежащий  суперпользователю
(u_uid==0).  Сначала он запрашивает имя и пароль пользователя:

    #include <stdio.h>  /* cc -lc_s */
    #include <pwd.h>
    #include <signal.h>
    struct passwd *p;
    char userName[80], *pass, *crpass;
    extern char *getpass(), *crypt();
      ...
    /* Не прерываться по сигналам с клавиатуры */
    signal (SIGINT, SIG_IGN);
    for(;;){
      /* Запросить имя пользователя: */
      printf("Login: "); gets(userName);
      /* Запросить пароль (без эха): */
      pass = getpass("Password: ");
      /* Проверить имя: */
      if(p = getpwnam(userName)){
         /* есть такой пользователь */
         crpass = (p->pw_passwd[0]) ? /* если есть пароль */
                  crypt(pass, p->pw_passwd) : pass;
         if( !strcmp( crpass, p->pw_passwd))
                  break; /* верный пароль */
      }
      printf("Login incorrect.\a\n");
    }
    signal (SIGINT, SIG_DFL);



А. Богатырев, 1992-95                  - 239 -                              Си в UNIX

Затем он выполняет:

    // ... запись информации о входе пользователя в систему
    // в файлы /etc/utmp (кто работает в системе сейчас)
    // и       /etc/wtmp (список всех входов в систему)
            ...
    setuid( p->pw_uid ); setgid( p->pw_gid );
    chdir ( p->pw_dir ); /* GO HOME! */
    // эти параметры будут унаследованы
    // интерпретатором команд.
            ...
    // настройка некоторых переменных окружения envp:
    // HOME     = p->pw_dir
    // SHELL    = p->pw_shell
    // PATH     = нечто по умолчанию, вроде :/bin:/usr/bin
    // LOGNAME (USER) = p->pw_name
    // TERM     = считывается из файла
    //            /etc/ttytype по имени устройства av[1]
    // Делается это как-то подобно
    //   char *envp[MAXENV], buffer[512]; int envc = 0;
    //   ...
    //   sprintf(buffer, "HOME=%s", p->pw_dir);
    //   envp[envc++] = strdup(buffer);
    //   ...
    //   envp[envc] = NULL;
            ...
    // настройка кодов доступа к терминалу. Имя устройства
    // содержится в параметре av[1] функции main.
    chown (av[1], p->pw_uid, p->pw_gid);
    chmod (av[1], 0600 );  /* -rw------- */
    // теперь доступ к данному терминалу имеют только
    // вошедший в систему пользователь и суперпользователь.
    // В случае смерти интерпретатора команд,
    // которым заменится getty, процесс init сойдет
    // с системного вызова ожидания wait() и выполнит
    //  chown ( этот_терминал, 2 /*bin*/, 15 /*terminal*/ );
    //  chmod ( этот_терминал, 0600 );
    // и, если терминал числится в файле описания линий
    // связи /etc/inittab как активный (метка respawn), то
    // init перезапустит на этом_терминале новый
    // процесс getty при помощи пары вызовов fork() и exec().
            ...
    // запуск интерпретатора команд:
    execle( *p->pw_shell ? p->pw_shell : "/bin/sh",
                      "-", NULL, envp );

В результате он становится процессом пользователя, вошедшего в  систему.  Таковым  же
после exec-а, выполняемого getty, остается и интерпретатор команд p->pw_shell (обычно
/bin/sh или /bin/csh) и все его потомки.
     На самом деле, в описании регистрации пользователя при входе в  систему,  созна-
тельно  было  допущено  упрощение.  Дело в том, что все то, что мы приписали процессу
getty, в действительности выполняется двумя программами: /etc/getty и /bin/login.
     Сначала процесс getty занимается настройкой параметров линии связи (т.е.  терми-
нала) в соответствии с ее описанием в файле /etc/gettydefs.  Затем он запрашивает имя
пользователя и заменяет себя (при помощи сисвызова exec) процессом  login,  передавая
ему в качестве одного из аргументов полученное имя пользователя.
     Затем login запрашивает пароль, настраивает окружение, и.т.п., то есть именно он
производит  все операции, приведенные выше на схеме.  В конце концов он заменяет себя
интерпретатором команд.
     Такое разделение делается, в частности, для того, чтобы считанный пароль в  слу-
чае  опечатки  не  хранился  бы в памяти процесса getty, а уничтожался бы при очистке



А. Богатырев, 1992-95                  - 240 -                              Си в UNIX

памяти завершившегося процесса login. Таким образом пароль в истинном,  незашифрован-
ном  виде  хранится  в  системе  минимальное время, что затрудняет его подсматривание
средствами электронного или программного шпионажа.  Кроме того, это  позволяет  изме-
нять систему проверки паролей не изменяя программу инициализации терминала getty.
     Имя, под которым пользователь вошел в систему на данном терминале, можно  узнать
вызовом стандартной функции
     char *getlogin();
Эта функция не проверяет uid процесса, а просто извлекает запись про данный  терминал
из файла /etc/utmp.
     Наконец отметим, что владелец файла устанавливается  при  создании  этого  файла
(вызовами  creat  или mknod), и полагается равным эффективному идентификатору создаю-
щего процесса.

    di_uid = u_uid;     di_gid = u_gid;


6.8.4.  Напишите программу, узнающую у системы  и  распечатывающую:  номер  процесса,
номер  и  имя своего владельца, номер группы, название и тип терминала на котором она
работает (из переменной окружения TERM).

6.9.  Блокировка доступа к файлам.
     В базах данных нередко встречается ситуация одновременного доступа к одним и тем
же  данным. Допустим, что в некотором файле хранятся данные, которые могут читаться и
записываться произвольным числом процессов.
-    Допустим, что процесс A изменяет некоторую область файла, в то время как процесс
     B  пытается  прочесть  ту же область.  Итогом такого соревнования может быть то,
     что процесс B прочтет неверные данные.
-    Допустим, что процесс A изменяет некоторую область файла, в то время как процесс
     C  также  изменяет  ту  же  самую  область.  В итоге эта область может содержать
     неверные данные (часть - от процесса A, часть - от C).
     Ясно, что требуется механизм синхронизации  процессов,  позволяющий  не  пускать
другой  процесс (процессы) читать и/или записывать данные в указанной области.  Меха-
низмов синхронизации в UNIX существует множество: от семафоров до блокировок областей
файла. О последних мы и будем тут говорить.
     Прежде всего отметим, что блокировки файла носят в UNIX необязательный характер.
То есть, программа не использующая вызовов синхронизации, будет иметь доступ к данным
без каких либо ограничений. Увы.  Таким образом,  программы,  собирающиеся  корректно
пользоваться  общими  данными,  должны  все  использовать - и при том один и тот же -
механизм синхронизации: заключить между собой "джентльменское соглашение".

6.9.1.  Блокировка устанавливается при помощи вызова

    flock_t lock;

    fcntl(fd, operation, &lock);

Здесь operation может быть одним из трех:
F_SETLK
     Устанавливает или снимает замок, описываемый структурой lock.  Структура flock_t
     имеет такие поля:

         short  l_type;
         short  l_whence;
         off_t  l_start;
         size_t l_len;

         long   l_sysid;
         pid_t  l_pid;

l_type
     тип блокировки:



А. Богатырев, 1992-95                  - 241 -                              Си в UNIX

         F_RDLCK - на чтение;
         F_WRLCK - на запись;
         F_UNLCK - снять все замки.

l_whence, l_start, l_len
     описывают   сегмент   файла,   на   который    ставится    замок:    от    точки
     lseek(fd,l_start,l_whence);  длиной  l_len  байт.   Здесь  l_whence  может быть:
     SEEK_SET, SEEK_CUR, SEEK_END.  l_len равное нулю означает "до конца файла".  Так
     если все три параметра равны 0, то будет заблокирован весь файл.
F_SETLKW
     Устанавливает или снимает замок, описываемый структурой lock.   При  этом,  если
     замок  на  область,  пересекающуюся с указанной уже кем-то установлен, то сперва
     дождаться снятия этого замка.

         Пытаемся   | Нет        Уже есть                   уже есть
         поставить  | чужих      замок                      замок
         замок на   | замков     на READ                    на WRITE
         -----------|---------------------------------------------------------------
         READ       | читать     читать                     ждать;запереть;читать
         WRITE      | записать   ждать;запереть;записать    ждать;запереть;записать
         UNLOCK     | отпереть   отпереть                   отпереть

-    Если кто-то читает сегмент файла, то другие тоже могут его читать свободно,  ибо
     чтение не изменяет файла.
-    Если же кто-то записывает файл - то все  остальные  должны  дождаться  окончания
     записи и разблокировки.
-    Если кто-то читает сегмент, а другой процесс собрался изменить  (записать)  этот
     сегмент, то этот другой процесс обязан дождаться окончания чтения первым.
-    В момент, обозначенный как отпереть - будятся процессы, ждущие разблокировки,  и
     ровно один из них получает доступ (может установить свою блокировку).  Порядок -
     кто из них будет первым - вообще говоря не определен.
F_GETLK
     Запрашиваем возможность установить замок, описанный в lock.
-    Если мы можем установить такой замок (не заперто никем),  то  в  структуре  lock
     поле l_type становится равным F_UNLCK и поле l_whence равным SEEK_SET.
-    Если замок уже кем-то установлен (и вызов F_SETLKW заблокировал бы наш  процесс,
     привел  бы  к  ожиданию), мы получаем информацию о чужом замке в структуру lock.
     При этом в поле l_pid заносится идентификатор процесса, создавшего этот замок, а
     в  поле  l_sysid - идентификатор машины (поскольку блокировка файлов поддержива-
     ется через сетевые файловые системы).
     Замки автоматически снимаются при закрытии дескриптора файла.  Замки не наследу-
ются порожденным процессом при вызове fork.

    #include <stdio.h>
    #include <sys/types.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <time.h>
    #include <signal.h>

    char DataFile [] = "data.xxx";
    char info     [] = "abcdefghijklmnopqrstuvwxyz";
    #define OFFSET 5
    #define SIZE   12

    #define PAUSE 2

    int trial = 1;
    int fd, pid;
    char buffer[120], myname[20];
    void writeAccess(), readAccess();



А. Богатырев, 1992-95                  - 242 -                              Си в UNIX

    void fcleanup(int nsig){
            unlink(DataFile);
            printf("cleanup:%s\n", myname);
            if(nsig) exit(0);
    }


    int main(){
            int i;

            fd = creat(DataFile, 0644);
            write(fd, info, strlen(info));
            close(fd);

            signal(SIGINT, fcleanup);

            sprintf(myname, fork() ? "B-%06d" : "A-%06d", pid = getpid());

            srand(time(NULL)+pid);
            printf("%s:started\n", myname);

            fd = open(DataFile, O_RDWR|O_EXCL);
            printf("%s:opened %s\n", myname, DataFile);

            for(i=0; i < 30; i++){
                    if(rand()%2)    readAccess();
                    else            writeAccess();
            }

            close(fd);

            printf("%s:finished\n", myname);

            wait(NULL);
            fcleanup(0);
            return 0;
    }



























А. Богатырев, 1992-95                  - 243 -                              Си в UNIX

    void writeAccess(){
            flock_t lock;

            printf("Write:%s #%d\n", myname, trial);

            lock.l_type   = F_WRLCK;
            lock.l_whence = SEEK_SET;
            lock.l_start  = (off_t)  OFFSET;
            lock.l_len    = (size_t) SIZE;

            if(fcntl(fd, F_SETLKW, &lock) <0)
                    perror("F_SETLKW");
            printf("\twrite:%s locked\n", myname);

            sprintf(buffer, "%s #%02d", myname, trial);
            printf ("\twrite:%s \"%s\"\n", myname, buffer);

            lseek (fd, (off_t) OFFSET, SEEK_SET);
            write (fd, buffer, SIZE);

            sleep (PAUSE);

            lock.l_type   = F_UNLCK;
            if(fcntl(fd, F_SETLKW, &lock) <0)
                    perror("F_SETLKW");

            printf("\twrite:%s unlocked\n", myname);

            trial++;
    }


    void readAccess(){
            flock_t lock;

            printf("Read:%s #%d\n", myname, trial);

            lock.l_type   = F_RDLCK;
            lock.l_whence = SEEK_SET;
            lock.l_start  = (off_t)  OFFSET;
            lock.l_len    = (size_t) SIZE;

            if(fcntl(fd, F_SETLKW, &lock) <0)
                    perror("F_SETLKW");
            printf("\tread:%s locked\n", myname);

            lseek(fd, (off_t) OFFSET, SEEK_SET);
            read (fd, buffer, SIZE);

            printf("\tcontents:%s \"%*.*s\"\n", myname, SIZE, SIZE, buffer);
            sleep (PAUSE);

            lock.l_type   = F_UNLCK;
            if(fcntl(fd, F_SETLKW, &lock) <0)
                    perror("F_SETLKW");

            printf("\tread:%s unlocked\n", myname);

            trial++;
    }




А. Богатырев, 1992-95                  - 244 -                              Си в UNIX

Исследуя выдачу этой программы, вы можете обнаружить, что READ-области могут перекры-
ваться;  но  что никогда не перекрываются области READ и WRITE ни в какой комбинации.
Если идет чтение процессом A - то запись процессом B дождется разблокировки A (чтение
-  не  будет  дожидаться).   Если идет запись процессом A - то и чтение процессом B и
запись процессом B дождутся разблокировки A.

6.9.2.
UNIX SVR4 имеет еще один интерфейс для блокировки файлов: функцию lockf.

    #include <unistd.h>

    int lockf(int fd, int operation, size_t size);

Операция operation:
F_ULOCK
     Разблокировать указанный сегмент файла (это может  снимать  один  или  несколько
     замков).
F_LOCK
F_TLOCK
     Установить замок. При этом,  если  уже  имеется  чужой  замок  на  запрашиваемую
     область, F_LOCK блокирует процесс, F_TLOCK - просто выдает ошибку (функция возв-
     ращает -1, errno устанавливается в EAGAIN).
-    Ожидание отпирания/запирания замка может быть прервано сигналом.
-    Замок устанавливается следующим образом: от текущей  позиции  указателя  чтения-
     записи  в  файле fd (что не похоже на fcntl, где позиция задается явно как пара-
     метр в структуре); длиной size. Отрицательное значение size означает  отсчет  от
     текущей  позиции к началу файла. Нулевое значение - означает "от текущей позиции
     до конца файла".  При этом "конец файла" понимается именно как конец, а  не  как
     текущий  размер  файла.   Если  файл  изменит размер, запертая область все равно
     будет простираться до конца файла (уже нового).
-    Замки, установленные процессом, автоматически  отпираются  при  завершении  про-
     цесса.
F_TEST
     Проверить наличие замка.  Функция возвращает 0, если замка нет; -1  в  противном
     случае (заперто).
Если устанавливается замок, перекрывающийся с уже установленным, то  замки  объединя-
ются.

    было:     ___________#######____######__________

    запрошено:______________##########______________

    стало:    ___________#################__________

Если снимается замок с области,  покрывающей  только  часть  заблокированной  прежде,
остаток области остается как отдельный замок.

    было:     ___________#################__________

    запрошено:______________XXXXXXXXXX______________

    стало:    ___________###__________####__________


6.10.  Файлы устройств.
     Пространство дисковой памяти может состоять из  нескольких  файловых  систем  (в
дальнейшем  FS),  т.е.  логических  и/или физических дисков.  Каждая файловая система
имеет древовидную логическую структуру (каталоги, подкаталоги и файлы) и  имеет  свой
корневой  каталог.  Файлы  в каждой FS имеют свои собственные I-узлы и собственную их
нумерацию с 1.  В начале каждой FS зарезервированы:




А. Богатырев, 1992-95                  - 245 -                              Си в UNIX

-    блок для загрузчика - программы, вызываемой аппаратно при включении машины (заг-
     рузчик  записывает  с диска в память машины программу /boot, которая в свою оче-
     редь загружает в память ядро /unix);
-    суперблок - блок заголовка файловой системы, хранящий размер файловой системы (в
     блоках),  размер  блока (512, 1024, ...), количество I-узлов, начало списка сво-
     бодных блоков, и другие сведения об FS;
-    некоторая непрерывная область диска для хранения I-узлов  - "I-файл".

Файловые системы объединяются в единую древовидную иерархию операцией монтирования  -
подключения  корня  файловой системы к какому-то из каталогов-"листьев" дерева другой
FS.
     Файлы в объединенной иерархии адресуются при помощи двух способов:
-    имен, задающих путь в дереве каталогов:

          /usr/abs/bin/hackIt
          bin/hackIt
          ./../../bin/vi

     (этот способ предназначен для программ, пользующихся файлами, а также  пользова-
     телей);
-    внутренних адресов, используемых программами ядра и некоторыми системными  прог-
     раммами.

Поскольку в каждой FS имеется собственная нумерация I-узлов, то файл  в  объединенной
иерархии должен адресоваться ДВУМЯ параметрами:
-    номером (кодом) устройства, содержащего файловую систему,  в  которой  находится
     искомый файл: dev_t i_dev;
-    номером I-узла файла в этой файловой системе: ino_t i_number;

Преобразование имени файла в объединенной файловой иерархии в  такую   адресную  пару
выполняет  в  ядре уже упоминавшаяся выше функция namei (при помощи просмотра катало-
гов):

    struct inode *ip = namei(...);

Создаваемая ею копия I-узла в памяти ядра содержит поля i_dev и i_number (которые  на
самом диске не хранятся!).
     Рассмотрим некоторые алгоритмы работы ядра с файлами.  Ниже они приведены  чисто
схематично  и  в  сильном  упрощении.  Форматы вызова (и оформление) функций не соот-
ветствуют форматам, используемым на самом деле в ядре; верны лишь  названия  функций.
Опущены  проверки  на  корректность, подсчет ссылок на структуры file и inode, блоки-
ровка I-узлов и кэш-буферов от одновременного доступа, и многое другое.
     Пусть мы хотим открыть файл для чтения и прочитать из него некоторую информацию.
Вызовы открытия и закрытия файла имеют схему (часть ее будет объяснена позже):

    #include <sys/types.h>
    #include <sys/inode.h>
    #include <sys/file.h>
    int fd_read = open(имяФайла, O_RDONLY){

      int fd; struct inode *ip; struct file *fp; dev_t dev;

      u_error = 0;    /* errno в программе */
    // Найти файл по имени. Создается копия I-узла в памяти:
      ip = namei(имяФайла, LOOKUP);
    // namei может выдать ошибку, если нет такого файла
      if(u_error) return(-1);  // ошибка

    // Выделяется структура "открытый файл":
      fp = falloc(ip, FREAD);
      // fp->f_flag = FREAD; открыт на чтение



А. Богатырев, 1992-95                  - 246 -                              Си в UNIX

      // fp->f_offset = 0;   RWptr
      // fp->f_inode  = ip;  ссылка на I-узел

    // Выделить новый дескриптор
      for(fd=0; fd < NOFILE; fd++)
         if(u_ofile[fd] == NULL ) // свободен
             goto done;
      u_error = EMFILE; return (-1);
    done:
      u_ofile[fd] = fp;

    // Если это устройство - инициализировать его.
    // Это функция openi(ip, fp->f_flag);
      dev = ip->i_rdev;
      if((ip->i_mode & IFMT) == IFCHR)
        (*cdevsw[major(dev)].d_open)(minor(dev),fp->f_flag);
      else if((ip->i_mode & IFMT) == IFBLK)
        (*bdevsw[major(dev)].d_open)(minor(dev),fp->f_flag);
      return fd;  // через u_rval1
    }


    close(fd){
      struct file  *fp = u_ofile[fd];
      struct inode *ip = fp->f_inode;
      dev_t dev = ip->i_rdev;

      if((ip->i_mode & IFMT) == IFCHR)
        (*cdevsw[major(dev)].d_close)(minor(dev),fp->f_flag);
      else if((ip->i_mode & IFMT) == IFBLK)
        (*bdevsw[major(dev)].d_close)(minor(dev),fp->f_flag);

      u_ofile[fd] = NULL;
      // и удалить ненужные структуры из ядра.
    }

Теперь рассмотрим функцию преобразования логических блоков файла в номера  физических
блоков в файловой системе. Для этого преобразования в I-узле файла содержится таблица
адресов блоков. Она устроена довольно сложно - ее начало находится в узле, а  продол-
жение  - в нескольких блоках в самой файловой системе (устройство это можно увидеть в
примере "Фрагментированность файловой системы" в приложении).  Мы для простоты  будем
предполагать,  что  это  просто линейный массив i_addr[], в котором n-ому логическому
блоку файла отвечает bno-тый физический блок файловой системы:

    bno = ip->i_addr[n];

Если файл является интерфейсом устройства, то этот файл не хранит информации в  логи-
ческой  файловой  системе.   Поэтому  у  устройств нет таблицы адресов блоков. Вместо
этого, поле i_addr[0] используется для хранения кода устройства, к которому  приводит
этот специальный файл. Это поле носит название i_rdev, т.е. как бы сделано

    #define i_rdev i_addr[0]

(на самом деле используется union).  Устройства бывают байто-ориентированные, обмен с
которыми  производится  по одному байту (как с терминалом или с коммуникационным пор-
том); и блочно-ориентированные, обмен с которыми возможен только большими порциями  -
блоками  (пример  -  диск).   То,  что файл является устройством, помечено в поле тип
файла

    ip->i_mode & IFMT




А. Богатырев, 1992-95                  - 247 -                              Си в UNIX

одним из значений: IFCHR - байтовое; или IFBLK - блочное.  Алгоритм вычисления номера
блока:

    ushort u_pboff;  // смещение от начала блока
    ushort u_pbsize; // сколько байт надо использовать
    // ushort  - это unsigned short, смотри <sys/types.h>
    // daddr_t - это long (disk address)

    daddr_t bmap(struct inode *ip,
                 off_t offset, unsigned count){
      int sz, rem;

      // вычислить логический номер блока по позиции RWptr.
      // BSIZE - это размер блока файловой системы,
      // эта константа определена в <sys/param.h>
      daddr_t bno = offset / BSIZE;
      // если BSIZE == 1 Кб, то можно offset >> 10

          u_pboff = offset % BSIZE;
          // это можно записать как offset & 01777

          sz = BSIZE - u_pboff;
          // столько байт надо взять из этого блока,
          // начиная с позиции u_pboff.

          if(count < sz) sz = count;
          u_pbsize = sz;

Если файл представляет собой устройство, то трансляция логических блоков в физические
не  производится - устройство представляет собой "сырой" диск без файлов и каталогов,
т.е. обращение происходит сразу по физическому номеру блока:

          if((ip->i_mode & IFMT) == IFBLK) // block device
             return bno;       // raw disk
          // иначе провести пересчет:

          rem = ip->i_size /*длина файла*/ - offset;
          // это остаток файла.
          if( rem < 0 )  rem = 0;
          // файл короче, чем заказано нами:
          if( rem < sz ) sz = rem;
          if((u_pbsize = sz) == 0) return (-1); // EOF

          // и, собственно, замена логич. номера на физич.
          return ip->i_addr[bno];
    }

Теперь рассмотрим алгоритм read. Параметры, начинающиеся с u_..., на самом деле пере-
даются как статические через вспомогательные переменные в u-area процесса.

    read(int fd, char *u_base, unsigned u_count){
        unsigned srccount = u_count;
        struct   file  *fp = u_ofile[fd];
        struct   inode *ip = fp->f_inode;
        struct   buf   *bp;
        daddr_t         bno; // очередной блок файла

        // dev - устройство,
        // интерфейсом которого является файл-устройство,
        // или на котором расположен обычный файл.
        dev_t dev = (ip->i_mode & (IFCHR|IFBLK)) ?



А. Богатырев, 1992-95                  - 248 -                              Си в UNIX

              ip->i_rdev : ip->i_dev;

        switch( ip->i_mode & IFMT ){

        case IFCHR:  // байто-ориентированное устройство
          (*cdevsw[major(dev)].d_read)(minor(dev));
          // прочие параметры передаются через u-area
          break;

        case IFREG:  // обычный файл
        case IFDIR:  // каталог
        case IFBLK:  // блочно-ориентированное устройство
          do{
             bno = bmap(ip, fp->f_offset /*RWptr*/, u_count);
             if(u_pbsize==0 || (long)bno < 0) break; // EOF
             bp  = bread(dev, bno);  // block read

             iomove(bp->b_addr + u_pboff, u_pbsize, B_READ);

Функция iomove копирует данные

    bp->b_addr[ u_pboff..u_pboff+u_pbsize-1 ]

из адресного пространства ядра (из буфера в ядре) в адресное пространство процесса по
адресам

    u_base[ 0..u_pbsize-1 ]

то есть пересылает u_pbsize байт между ядром и процессом (u_base  попадает  в  iomove
через  статическую  переменную).  При записи вызовом write(), iomove с флагом B_WRITE
производит обратное копирование - из памяти процесса в память ядра. Продолжим:

             // продвинуть счетчики и указатели:
             u_count      -= u_pbsize;
             u_base       += u_pbsize;
             fp->f_offset += u_pbsize;  // RWptr
          } while( u_count != 0 );
          break;
        ...
        return( srccount - u_count );
    } // end read

Теперь обсудим некоторые места этого алгоритма.  Сначала  посмотрим,  как  происходит
обращение  к  байтовому устройству.  Вместо адресов блоков мы получаем код устройства
i_rdev.  Коды устройств в UNIX (тип dev_t) представляют собой пару двух чисел,  назы-
ваемых мажор и минор, хранимых в старшем и младшем байтах кода устройства:

    #define major(dev)  ((dev >> 8) & 0x7F)
    #define minor(dev)  ( dev       & 0xFF)

Мажор обозначает тип устройства (диск, терминал, и.т.п.) и приводит к одному из драй-
веров  (если  у  нас  есть  8 терминалов, то их обслуживает один и тот же драйвер); а
минор обозначает номер устройства данного типа (... каждый из терминалов имеет миноры
0..7).  Миноры обычно служат индексами в некоторой таблице структур внутри выбранного
драйвера.  Мажор же служит индексом в переключательной таблице устройств.   При  этом
блочно-ориентированные  устройства  выбираются  в  одной таблице - bdevsw[], а байто-
ориентированные - в другой  -  cdevsw[]  (см.  <sys/conf.h>;  имена  таблиц  означают
block/character  device  switch).   Каждая  строка  таблицы  содержит адреса функций,
выполняющих некоторые предопределенные операции способом,  зависимым  от  устройства.
Сами  эти  функции  реализованы  в  драйверах устройств.  Аргументом для этих функций
обычно служит  минор  устройства,  к  которому  производится  обращение.   Функция  в



А. Богатырев, 1992-95                  - 249 -                              Си в UNIX

драйвере  использует  этот  минор  как  индекс для выбора конкретного экземпляра уст-
ройства данного типа; как индекс в массиве управляющих структур  (содержащих  текущее
состояние,  режимы  работы,  адреса функций прерываний, адреса очередей данных и.т.п.
каждого конкретного устройства) для данного типа устройств. Эти управляющие структуры
различны для разных типов устройств (и их драйверов).
     Каждая строка переключательной таблицы содержит адреса функций, выполняющих опе-
рации  open,  close,  read, write, ioctl, select.  open служит для инициализации уст-
ройства при первом его открытии (++ip->i_count==1) - например, для включения  мотора;
close  -  для  выключения  при последнем закрытии (--ip->i_count==0).  У блочных уст-
ройств поля для read и write объединены в функцию strategy, вызываемую  с  параметром
B_READ  или B_WRITE.  Вызов ioctl предназначен для управления параметрами работы уст-
ройства.  Операция select - для опроса: есть ли поступившие в устройство данные (нап-
ример,  есть ли в clist-е ввода с клавиатуры байты? см. главу "Экранные библиотеки").
Вызов select применим только к некоторым байтоориентированным устройствам  и  сетевым
портам  (socket-ам).   Если  данное  устройство не умеет выполнять такую операцию, то
есть запрос к этой операции должен вернуть в  программу  ошибку  (например,  операция
read  неприменима  к  принтеру), то в переключательной таблице содержится специальное
имя функции nodev; если же операция допустима, но является фиктивной (как  write  для
/dev/null)  -  имя  nulldev.  Обе эти функции-заглушки представляют собой "пустышки":
{}.
     Теперь обратимся к блочно-ориентированным устройствам.  UNIX  использует  внутри
ядра дополнительную буферизацию при обменах с такими  устройствами[*].   Использованная
нами  выше функция bp=bread(dev,bno); производит чтение физического блока номер bno с
устройства dev.  Эта операция обращается к драйверу конкретного устройства и вызывает
чтение  блока  в  некоторую  область  памяти в ядре ОС: в один из кэш-буферов (cache,
"запасать").  Заголовки кэш-буферов (struct buf) организованы в список и  имеют  поля
(см. файл <sys/buf.h>):
b_dev
     код устройства, с которого прочитан блок;
b_blkno
     номер физического блока, хранящегося в буфере в данный момент;
b_flags
     флаги блока (см. ниже);
b_addr
     адрес участка памяти (как правило в самом ядре), в котором собственно и хранится
     содержимое блока.

Буферизация блоков позволяет системе экономить число обращений к диску.  При  обраще-
нии  к  bread()  сначала происходит поиск блока (dev,bno) в таблице кэш-буферов. Если
блок уже был ранее прочитан в кэш, то обращения  к  диску  не  происходит,  поскольку
копия  содержимого  дискового  блока уже есть в памяти ядра.  Если же блока еще нет в
кэш-буферах, то в ядре выделяется чистый буфер, в заголовке ему прописываются  нужные
значения полей b_dev и b_blkno, и блок считывается в буфер с диска вызовом функции

    bp->b_flags |= B_READ;  // род работы: прочитать
    (*bdevsw[major(dev)].d_startegy)(bp);
    // bno и минор - берутся из полей *bp

из драйвера конкретного устройства.
     Когда мы что-то изменяем в файле вызовом write(), то  изменения  на  самом  деле
происходят в кэш-буферах в памяти ядра, а не сразу на диске.  При записи в блок буфер
помечается как измененный:

    b_flags |= B_DELWRI;  // отложенная запись

____________________
   [*] Следует отличать эту системную буферизацию от буферизации при помощи  библиотеки
stdio.   Библиотека  создает буфер в самом процессе, тогда как системные вызовы имеют
буфера внутри ядра.





А. Богатырев, 1992-95                  - 250 -                              Си в UNIX

и на диск немедленно не записывается.  Измененные буфера  физически  записываются  на
диск в таких случаях:
-    Был сделан системный вызов sync();
-    Ядру не хватает кэш-буферов (их число ограничено). Тогда самый старый  буфер  (к
     которому  дольше  всего  не  было  обращений) записывается на диск и после этого
     используется для другого блока.
-    Файловая система была отмонтирована вызовом umount;

Понятно, что не измененные блоки обратно на диск из буферов не записываются (т.к.  на
диске  и  так  содержатся  те же самые данные).  Даже если файл уже закрыт close, его
блоки могут быть еще не записаны на диск - запись произойдет лишь  при  вызове  sync.
Это  означает,  что  измененные  блоки записываются на диск "массированно" - по многу
блоков, но не очень часто, что позволяет оптимизировать и саму запись на диск: сорти-
ровкой блоков можно достичь минимизации перемещения магнитных головок над диском.
     Отслеживание самых "старых" буферов  происходит  за  счет  реорганизации  списка
заголовков  кэш-буферов.  В большом упрощении это можно представить так: как только к
блоку происходит обращение, соответствующий заголовок переставляется в начало списка.
В  итоге  самый  "пассивный" блок оказывается в хвосте - он то и переиспользуется при
нужде.
     "Подвисание" файлов в памяти ядра значительно  ускоряет  работу  программ,  т.к.
работа с памятью гораздо быстрее, чем с диском. Если блок надо считать/записать, а он
уже есть в кэше, то реального обращения к диску не происходит.  Зато,  если  случится
сбой питания (или кто-то неаккуратно выключит машину), а некоторые буфера еще не были
сброшены на диск - то часть изменений в файлах будет  потеряна.   Для  принудительной
записи всех измененных кэш-буферов на диск существует сисвызов "синхронизации" содер-
жимого дисков и памяти

    sync();  // synchronize

Вызов sync делается раз в 30  секунд  специальным  служебным  процессом  /etc/update,
запускаемым  при  загрузке  системы.  Для работы с файлами, которые должны гарантиро-
ванно быть корректными на диске, используется открытие файла

    fd = open( имя, O_RDWR | O_SYNC);

которое означает, что при каждом write блок из кэш-буфера немедленно записывается  на
диск.  Это делает работу надежнее, но существенно медленнее.
     Специальные файлы устройств не  могут  быть  созданы  вызовом  creat,  создающим
только обычные файлы.  Файлы устройств создаются вызовом  mknod:

    #include <sys/sysmacros.h>
    dev_t dev = makedev(major, minor);
                    /* (major << 8) | minor */
    mknod( имяФайла, кодыДоступа|тип, dev);

где dev - пара (мажор,минор) создаваемого устройства; кодыДоступа -  коды  доступа  к
файлу (0777)[**]; тип - это одна из констант S_IFIFO, S_IFCHR, S_IFBLK из  include-файла
<sys/stat.h>.
     mknod доступен для выполнения только суперпользователю  (за  исключением  случая
S_IFIFO).  Если бы это было не так, то можно было бы создать файл устройства, связан-
ный с существующим диском, и читать информацию с него напрямую,  в  обход  механизмов
логической файловой системы и защиты файлов кодами доступа.
     Можно создать файл устройства с мажором и/или минором,  не  отвечающим  никакому
реальному  устройству  (нет такого драйвера или минор слишком велик).  Открытие таких
____________________
   [**] Обычно к блочным устройствам (дискам) доступ разрешается  только  суперпользова-
телю,  в противном случае можно прочитать с "сырого" диска (в обход механизмов файло-
вой системы) физические блоки любого файла и весь механизм защиты окажется неработаю-
щим.





А. Богатырев, 1992-95                  - 251 -                              Си в UNIX

устройств выдает код ошибки ENODEV.
     Из нашей программы мы можем вызовом stat() узнать  код  устройства,  на  котором
расположен файл.  Он будет содержаться в поле dev_t st_dev; а если файл является спе-
циальным файлом (интерфейсом драйвера устройства), то  код  самого  этого  устройства
можно узнать из поля dev_t st_rdev; Рассмотрим пример, который выясняет, относятся ли
два имени к одному и тому же файлу:

    #include <sys/types.h>
    #include <sys/stat.h>
    void main(ac, av) char *av[]; {
      struct stat st1, st2; int eq;
      if(ac != 3) exit(13);
      stat(av[1], &st1); stat(av[2], &st2);
      if(eq =
        (st1.st_ino == st2.st_ino && /* номера I-узлов */
         st1.st_dev == st2.st_dev))  /* коды устройств */
    printf("%s и %s - два имени одного файла\n",av[1],av[2]);
      exit( !eq );
    }

Наконец, вернемся к склейке нескольких файловых систем в одну объединенную иерархию:

          ino=2
          *------      корневая файловая система
         / \    /\     на диске /dev/hd0
        /  /\    /\
             \
              *-/mnt/hd1
              :
              * ino=2    FS на диске /dev/hd1
             / \         (removable FS)
            /\  \

Для того, чтобы поместить корневой каталог файловой  системы,  находящейся  на  диске
/dev/hd1, вместо каталога /mnt/hd1 уже "собранной" файловой системы, мы должны издать
сисвызов

    mount("/dev/hd1", "/mnt/hd1", 0);

Для отключения смонтированной файловой системы мы должны вызвать

    umount("/dev/hd1");

(каталог, к которому она смонтирована, уже числится в таблице ядра, поэтому его зада-
вать не надо).  При монтировании все содержимое каталога /mnt/hd1 станет недоступным,
зато при обращении к имени /mnt/hd1 мы на самом деле доберемся до (безымянного)  кор-
невого  каталога на диске /dev/hd1.  Такой каталог носит название mount point и может
быть выявлен по тому признаку, что "." и ".." в нем лежат на разных устройствах:

    struct stat st1, st2;
    stat("/mnt/hd1/.", &st1); stat("/mnt/hd1/..", &st2);
    if( st1.st_dev != st2.st_dev) ... ; /*mount point*/

Для st1 поле st_dev означает код устройства /dev/hd1, а для st2 - устройства,  содер-
жащего  корневую  файловую  систему.  Операции монтирования и отмонтирования файловых
систем доступны только суперпользователю.

     И напоследок - сравнение структур I-узла.

            на диске        в памяти        в вызове stat
            <sys/ino.h>     <sys/inode.h>   <sys/stat.h>



А. Богатырев, 1992-95                  - 252 -                              Си в UNIX

            struct dinode   struct inode    struct stat

         // коды доступа и тип файла
    ushort  di_mode         i_mode          st_mode
         // число имен файла
    short   di_nlink        i_nlink         st_nlink
         // номер I-узла
    ushort   ---            i_number        st_ino
         // идентификатор владельца
    ushort  di_uid          i_uid           st_uid
         // идентификатор группы владельца
    ushort  di_gid          i_gid           st_gid
         // размер файла в байтах
    off_t   di_size         i_size          st_size
         // время создания
    time_t  di_ctime        i_ctime         st_ctime
         // время последнего изменения (write)
    time_t  di_mtime        i_mtime         st_mtime
         // время последнего доступа (read/write)
    time_t  di_atime        i_atime         st_atime
         // устройство, на котором расположен файл
    dev_t     ---           i_dev           st_dev
         // устройство, к которому приводит спец.файл
    dev_t     ---           i_rdev          st_rdev
         // адреса блоков
    char    di_addr[39]     i_addr[]
         // счетчик ссылок на структуру в ядре
    cnt_t                   i_count
         //                 и кое-что еще

Минусы означают, что данное поле не хранится на диске, а вычисляется ядром. В  совре-
менных версиях UNIX могут быть легкие отличия от вышенаписанной таблицы.

6.10.1.  Напишите программу pwd, определяющую полное имя текущего рабочего  каталога.
#define U42  определяет файловую систему с длинными именами, отсутствие этого флага -
с короткими (14 символов).




























А. Богатырев, 1992-95                  - 253 -                              Си в UNIX

    /* Команда pwd.
     * Текст getwd() взят из исходных текстов библиотеки языка Си.
     */
    #include <stdio.h>
    #include <fcntl.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <dirent.h>
    #define  ediag(e,r)      (e)
    /*
     * getwd() возвращает полное имя текущего рабочего каталога.
     * При ошибке возвращается NULL, а в pathname копируется сообщение
     * об ошибке.
     */
    #ifndef MAXPATHLEN
    #define MAXPATHLEN      128
    #endif

    #define CURDIR          "."   /* имя текущего каталога      */
    #define PARENTDIR       ".."  /* имя родительского каталога */
    #define PATHSEP         "/"   /* разделитель компонент пути */
    #define ROOTDIR         "/"   /* корневой каталог           */
    #define GETWDERR(s)     strcpy(pathname, (s));
    #define CP(to,from)     strncpy(to,from.d_name,DIRSIZ),to[DIRSIZ]='\0'

    char *strcpy(char *, char *); char *strncpy(char *, char *, int);
    char *getwd(char *pathname);
    static char *prepend(char *dirname, char *pathname);

    static int pathsize;                    /* длина имени */


    #ifndef U42
    char *getwd(char *pathname)
    {
            char pathbuf[MAXPATHLEN];       /* temporary pathname buffer    */
            char *pnptr = &pathbuf[(sizeof pathbuf)-1]; /* pathname pointer */
            dev_t rdev;                     /* root device number           */
            int fil = (-1);                 /* directory file descriptor    */
            ino_t rino;                     /* root inode number            */
            struct direct dir;              /* directory entry struct       */
            struct stat d ,dd;              /* file status struct           */
                                            /* d - "."    dd - ".." | dname */
            char dname[DIRSIZ+1];           /* an directory entry           */

            pathsize = 0;
            *pnptr = '\0';
            if (stat(ROOTDIR, &d) < 0) {
                    GETWDERR(ediag("getwd: can't stat /",
                                   "getwd: нельзя выполнить stat /"));
                    return (NULL);
            }
            rdev = d.st_dev; /* код устройства, на котором размещен корень */
            rino = d.st_ino; /* номер I-узла, представляющего корневой каталог */










А. Богатырев, 1992-95                  - 254 -                              Си в UNIX

            for (;;) {
                    if (stat(CURDIR, &d) < 0) {
            CantStat:
                            GETWDERR(ediag("getwd: can't stat .",
                                           "getwd: нельзя выполнить stat ."));
                            goto fail;
                    }
                    if (d.st_ino == rino && d.st_dev == rdev)
                            break;          /* достигли корневого каталога */
                    if ((fil = open(PARENTDIR, O_RDONLY)) < 0) {
                            GETWDERR(ediag("getwd: can't open ..",
                                           "getwd: нельзя открыть .."));
                            goto fail;
                    }
                    if (chdir(PARENTDIR) < 0) {
                            GETWDERR(ediag("getwd: can't chdir to ..",
                                           "getwd: нельзя перейти в .."));
                            goto fail;
                    }
                    if (fstat(fil, &dd) < 0)
                            goto CantStat;
                    if (d.st_dev == dd.st_dev) {  /* то же устройство */
                            if (d.st_ino == dd.st_ino) {
                                    /* достигли корня ".." == "." */
                                    close(fil); break;
                            }
                            do {
                                    if (read(fil, (char *) &dir,
                                        sizeof(dir)) < sizeof(dir)
                                    ){
                            ReadErr:
                                      close(fil);
                                      GETWDERR(ediag("getwd: read error in ..",
                                                     "getwd: ошибка чтения .."));
                                            goto fail;
                                    }
                            } while (dir.d_ino != d.st_ino);
                            CP(dname,dir);

                    } else  /* ".." находится на другом диске: mount point */


                            do {
                                    if (read(fil, (char *) &dir,
                                        sizeof(dir)) < sizeof(dir))
                                            goto ReadErr;
                                    if( dir.d_ino == 0 )    /* файл стерт */
                                            continue;
                                    CP(dname,dir);
                                    if (stat(dname, &dd) < 0) {
                                            sprintf (pathname, "getwd: %s %s",
                                                     ediag ("can't stat",
                                                "нельзя выполнить stat"), dname);
                                            goto fail;
                                    }
                            } while(dd.st_ino != d.st_ino ||
                                    dd.st_dev != d.st_dev);
                    close(fil);
                    pnptr = prepend(PATHSEP, prepend(dname, pnptr));
            }




А. Богатырев, 1992-95                  - 255 -                              Си в UNIX

            if (*pnptr == '\0')             /* текущий каталог == корневому */
                    strcpy(pathname, ROOTDIR);
            else {
                    strcpy(pathname, pnptr);
                    if (chdir(pnptr) < 0) {
                            GETWDERR(ediag("getwd: can't change back to .",
                                           "getwd: нельзя вернуться в ."));
                            return (NULL);
                    }
            }
            return (pathname);

    fail:
            close(fil);
            chdir(prepend(CURDIR, pnptr));
            return (NULL);
    }


    #else /* U42 */
    extern char   *strcpy ();
    extern DIR    *opendir();

    char   *getwd (char *pathname)
    {
        char    pathbuf[MAXPATHLEN];/* temporary pathname buffer */
        char   *pnptr = &pathbuf[(sizeof pathbuf) - 1];/* pathname pointer */
        char   *prepend ();         /* prepend dirname to pathname */
        dev_t rdev;                 /* root device number */
        DIR * dirp;                 /* directory stream */
        ino_t rino;                 /* root inode number */
        struct dirent  *dir;        /* directory entry struct */
        struct stat d,
                    dd;             /* file status struct */

        pathsize = 0;
        *pnptr = '\0';
        stat (ROOTDIR, &d);
        rdev = d.st_dev;
        rino = d.st_ino;


        for (;;) {
            stat (CURDIR, &d);

            if (d.st_ino == rino && d.st_dev == rdev)
                break;              /* reached root directory */

            if ((dirp = opendir (PARENTDIR)) == NULL) {
                GETWDERR ("getwd: can't open ..");
                goto fail;
            }
            if (chdir (PARENTDIR) < 0) {
                closedir (dirp);
                GETWDERR ("getwd: can't chdir to ..");
                goto fail;
            }







А. Богатырев, 1992-95                  - 256 -                              Си в UNIX

            fstat (dirp -> dd_fd, &dd);
            if (d.st_dev == dd.st_dev) {
                if (d.st_ino == dd.st_ino) {
                /* reached root directory */
                    closedir (dirp);
                    break;
                }
                do {
                    if ((dir = readdir (dirp)) == NULL) {
                        closedir (dirp);
                        GETWDERR ("getwd: read error in ..");
                        goto fail;
                    }
                } while (dir -> d_ino != d.st_ino);
            }


            else
                do {
                    if ((dir = readdir (dirp)) == NULL) {
                        closedir (dirp);
                        GETWDERR ("getwd: read error in ..");
                        goto fail;
                    }
                    stat (dir -> d_name, &dd);
                } while (dd.st_ino != d.st_ino || dd.st_dev != d.st_dev);
            closedir (dirp);
            pnptr = prepend (PATHSEP, prepend (dir -> d_name, pnptr));
        }


        if (*pnptr == '\0')         /* current dir == root dir */
            strcpy (pathname, ROOTDIR);
        else {
            strcpy (pathname, pnptr);
            if (chdir (pnptr) < 0) {
                GETWDERR ("getwd: can't change back to .");
                return (NULL);
            }
        }
        return (pathname);

    fail:
        chdir (prepend (CURDIR, pnptr));
        return (NULL);
    }
    #endif

















А. Богатырев, 1992-95                  - 257 -                              Си в UNIX

    /*
     * prepend() tacks a directory name onto the front of a pathname.
     */
    static char *prepend (
            register char *dirname,         /* что добавлять    */
            register char *pathname         /* к чему добавлять */
    ) {
            register int i;         /* длина имени каталога */

            for (i = 0; *dirname != '\0'; i++, dirname++)
                    continue;
            if ((pathsize += i) < MAXPATHLEN)
                    while (i-- > 0)
                            *--pathname = *--dirname;
            return (pathname);
    }

    #ifndef CWDONLY
    void main(){
            char buffer[MAXPATHLEN+1];
            char *cwd = getwd(buffer);
            printf( "%s%s\n", cwd ? "": "ERROR:", buffer);
    }
    #endif


6.10.2.  Напишите функцию canon(), канонизирующую имя файла, т.е.  превращающую его в
полное  имя (от корневого каталога), не содержащее компонент "." и "..", а также лиш-
них символов слэш '/'. Пусть, к примеру, текущий  рабочий  каталог  есть  /usr/abs/C-
book. Тогда функция преобразует

      .                    -> /usr/abs/C-book
      ..                   -> /usr/abs
      ../..                -> /usr
      ////..               -> /
      /aa                  -> /aa
      /aa/../bb            -> /bb
      cc//dd/../ee         -> /usr/abs/C-book/cc/ee
      ../a/b/./d           -> /usr/abs/a/b/d

Ответ:

    #include <stdio.h>
    /* слэш, разделитель компонент пути */
    #define SLASH        '/'
    extern char *strchr (char *, char),
                *strrchr(char *, char);
    struct savech{ char *s, c; };
    #define SAVE(sv, str) (sv).s = (str); (sv).c = *(str)
    #define RESTORE(sv) if((sv).s)   *(sv).s = (sv).c
    /* Это структура для использования в таком контексте:
    void main(){
      char *d = "hello"; struct savech ss;
      SAVE(ss, d+3); *(d+3) = '\0'; printf("%s\n", d);
      RESTORE(ss);                  printf("%s\n", d);
    }
    */

    /* ОТСЕЧЬ ПОСЛЕДНЮЮ КОМПОНЕНТУ ПУТИ */
    struct savech parentdir(char *path){
       char *last  = strrchr( path, SLASH );



А. Богатырев, 1992-95                  - 258 -                              Си в UNIX

       char *first = strchr ( path, SLASH );
       struct savech sp; sp.s = NULL; sp.c = '\0';

    if( last    == NULL  ) return sp; /* не полное имя    */
    if( last[1] == '\0'  ) return sp; /* корневой каталог */
    if( last    == first ) /* единственный слэш: /DIR     */
        last++;
       sp.s = last; sp.c = *last; *last = '\0';
       return sp;
    }
    #define isfullpath(s)   (*s == SLASH)
    /* КАНОНИЗИРОВАТЬ ИМЯ ФАЙЛА */
    void canon(
         char *where, /* куда поместить ответ */
         char *cwd,   /* полное имя текущего каталога */
         char *path   /* исходное имя для канонизации */
    ){   char *s, *slash;
       /* Сформировать имя каталога - точки отсчета */
       if( isfullpath(path)){
           s = strchr(path, SLASH);   /* @ */
           strncpy(where, path, s - path + 1);
           where[s - path + 1] = '\0';
           /* или даже просто strcpy(where, "/"); */
           path = s+1; /* остаток пути без '/' в начале */
       } else strcpy(where, cwd);

       /* Покомпонентный просмотр пути */
       do{  if(slash = strchr(path, SLASH)) *slash = '\0';
       /* теперь path содержит очередную компоненту пути */
            if(*path == '\0' || !strcmp(path, ".")) ;
         /* то просто проигнорировать "." и лишние "///" */
            else  if( !strcmp(path, ".."))
                  (void) parentdir(where);
            else{ int len = strlen(where);
               /* добавить в конец разделяющий слэш */
                  if( where[len-1] != SLASH ){
                      where[len] = SLASH;
                      where[len+1] = '\0';
                  }
                  strcat( where+len, path );
                  /* +len чисто для ускорения поиска
                   * конца строки внутри strcat(); */
            }
            if(slash){ *slash = SLASH; /* восстановить */
                       path   = slash + 1;
            }
       } while (slash != NULL);
    }
    char cwd[256], input[256], output[256];
    void main(){
    /* Узнать полное имя текущего каталога.
     * getcwd() - стандартная функция, вызывающая
     * через popen() команду pwd (и потому медленная).
     */
       getcwd(cwd, sizeof cwd);
       while( gets(input)){
         canon(output, cwd, input);
         printf("%-20s -> %s\n", input, output);
       }
    }




А. Богатырев, 1992-95                  - 259 -                              Си в UNIX

В этом примере (изначально писавшемся для MS DOS) есть "странное"  место,  помеченное
/*@*/. Дело в том, что в DOS функция isfullpath была способна распознавать имена фай-
лов вроде C:\aaa\bbb, которые не обязательно начинаются со слэша.

6.11.  Мультиплексирование ввода-вывода.
     Данная глава посвящена системному вызову select, который, однако, мы  предостав-
ляем  вам  исследовать самостоятельно.  Его роль такова: он позволяет опрашивать нес-
колько дескрипторов открытых файлов (или устройств) и как только в  файле  появляется
новая  информация  -  сообщать  об этом нашей программе.  Обычно это бывает связано с
дескрипторами, ведущими к сетевым устройствам.

6.11.1.

    /* Пример использования вызова select() для мультиплексирования
     * нескольких каналов ввода. Этот вызов можно также использовать
     * для получения таймаута.
     *     Вызов: войти на терминалах tty01 tty02 и набрать на каждом
     *            sleep 30000
     *     затем  на tty00 сказать           select /dev/tty01 /dev/tty02
     *     и вводить что-либо на терминалах  tty01 и tty02
     * Сборка:      cc select.c -o select -lsocket
     */
    #include <stdio.h>
    #include <fcntl.h>
    #include <sys/types.h>  /* fd_set, FD_SET, e.t.c. */
    #include <sys/param.h>  /* NOFILE */
    #include <sys/select.h>
    #include <sys/time.h>
    #include <sys/filio.h>  /* для FIONREAD */
    #define max(a,b)        ((a) > (b) ? (a) : (b))


    char buf[512];          /* буфер чтения                      */
    int fdin, fdout;        /* дескрипторы каналов stdin, stdout */
    int nready;             /* число готовых каналов             */
    int nopen;              /* число открытых каналов            */
    int maxfd = 0;          /* максимальный дескриптор           */
    int nfds;               /* сколько первых дескрипторов проверять */
    int f;                  /* текущий дескриптор                */
    fd_set set, rset;       /* маски                             */

    /* таблица открытых нами файлов */
    struct _fds {
            int fd;         /* дескриптор */
            char name[30];  /* имя файла  */
    } fds[ NOFILE ] = { /* NOFILE - макс. число открытых файлов на процесс */
            { 0, "stdin" }, { 1, "stdout" }, { 2, "stderr" }
            /* все остальное - нули */
    };
    struct timeval timeout, rtimeout;


    /* выдать имя файла по дескриптору */
    char *N( int fd ){
            register i;
            for(i=0; i < NOFILE; i++)
                    if(fds[i].fd == fd ) return fds[i].name;
            return "???";
    }





А. Богатырев, 1992-95                  - 260 -                              Си в UNIX

    void main( int ac, char **av ){
            nopen = 3;              /* stdin, stdout, stderr */
            for( f = 3; f < NOFILE; f++ ) fds[f].fd = (-1);
            fdin = fileno(stdin);   fdout = fileno(stdout);
            setbuf(stdout, NULL);   /* отмена буферизации */
            FD_ZERO(&set);          /* очистка маски */

            for(f=1; f < ac; f++ )
                    if((fds[nopen].fd = open(av[f], O_RDONLY)) < 0 ){
                       fprintf(stderr, "Can't read %s\n", av[f] );
                       continue;
                    } else {
                       FD_SET(fds[nopen].fd, &set );   /* учесть в маске */
                          maxfd = max(maxfd, fds[nopen].fd );
                       strncpy(fds[nopen].name, av[f], sizeof(fds[0].name) - 1);
                       nopen++;
                    }

            if( nopen == 3 ){
                    fprintf(stderr, "Nothing is opened\n");
                    exit(1);
            }

            FD_SET(fdin, &set); /* учесть stdin */
               maxfd = max(maxfd, fdin );
            nopen -= 2;         /* stdout и stderr не участвуют в select */
            timeout.tv_sec = 10;    /* секунд */
            timeout.tv_usec = 0;    /* миллисекунд */


        /* nfds - это КОЛИЧЕСТВО первых дескрипторов, которые надо
         * просматривать. Здесь можно использовать
         *          nfds = NOFILE;  (кол-во ВСЕХ дескрипторов   )
         * или      nfds = maxfd+1; (кол-во = номер последнего+1)
         * ( +1 т.к. нумерация fd идет с номера 0, а количество - с 1).
         */
            nfds = maxfd + 1;
            while( nopen ){

               rset = set; rtimeout = timeout; /* копируем, т.к. изменятся */
        /* опрашивать можно FIFO-файлы, терминалы, pty, socket-ы, stream-ы */

               nready = select( nfds, &rset, NULL, NULL, &rtimeout );

        /* Если вместо &rtimeout написать NULL, то ожидание будет
         * бесконечным (пока не собьют сигналом)
         */
               if( nready <= 0 ){  /* ничего не поступило */
                    fprintf(stderr, "Timed out, nopen=%d\n", nopen);
                    continue;
               }













А. Богатырев, 1992-95                  - 261 -                              Си в UNIX

               /* опрос готовых дескрипторов */
               for(f=0; f < nfds; f++ )
                    if( FD_ISSET(f, &rset)){  /* дескриптор f готов */
                            int n;

                            /* Вызов FIONREAD позволяет запросить
                             * число байт готовых к передаче
                             * через дескриптор.
                             */
                            if(ioctl(f, FIONREAD, &n) < 0)
                                    perror("FIONREAD");
                            else printf("%s have %d bytes.\n", N(f), n);

                            if((n = read(f, buf, sizeof buf)) <= 0 ){
                    eof:
                                FD_CLR(f, &set); /* исключить */
                                close(f); nopen--;
                                fprintf(stderr, "EOF in %s\n", N(f));

                            } else {

                                fprintf(stderr, "\n%d bytes from %s:\n", n, N(f));
                                write(fdout, buf, n);
                                if( n == 4 && !strncmp(buf, "end\n", 4))
                                /* ncmp, т.к. buf может не оканчиваться \0 */
                                    goto eof;
                            }
                    }
            }
            exit(0);
    }


6.11.2.  В качестве самостоятельной работы предлагаем вам пример  программы,  ведущей
протокол сеанса работы.  Информацию о псевдотерминалах изучите самостоятельно.





























А. Богатырев, 1992-95                  - 262 -                              Си в UNIX

    /*
     *              script.c
     *      Программа получения трассировки работы других программ.
     *      Используется системный вызов опроса готовности каналов
     *      ввода/вывода select() и псевдотерминал (пара ttyp+ptyp).
     */

    #include <stdio.h>
    #include <stdlib.h>
    #include <fcntl.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <signal.h>
    #include <sys/param.h>  /* NOFILE */
    #include <sys/times.h>
    #include <sys/wait.h>
    #include <errno.h>

    #ifdef TERMIOS
    # include <termios.h>
    # define TERMIO struct termios
    # define GTTY(fd, tadr)  tcgetattr(fd, tadr)
    # define STTY(fd, tadr)  tcsetattr(fd, TCSADRAIN, tadr)
    #else
    # include <termio.h>
    # define TERMIO struct termio
    # define GTTY(fd, tadr) ioctl(fd, TCGETA,  tadr)
    # define STTY(fd, tadr) ioctl(fd, TCSETAW, tadr)
    #endif



































А. Богатырев, 1992-95                  - 263 -                              Си в UNIX

    #ifdef __SVR4
    # include <stropts.h>   /* STREAMS i/o */
    extern char *ptsname();
    #endif

    #if defined(ISC2_2)
    # include <sys/bsdtypes.h>
    #else
    # include <sys/select.h>
    #endif

    #ifndef BSIZE
    # define BSIZE 512
    #endif

    #define LOGFILE         "/usr/spool/scriptlog"
    #define max(a,b)        ((a) > (b) ? (a) : (b))
    extern int errno;
    TERMIO told, tnew, ttypmodes;
    FILE *fpscript = NULL;  /* файл с трассировкой (если надо) */
    int go = 0;

    int scriptflg  = 0;
    int halfflag   = 0;      /* HALF DUPLEX */
    int autoecho   = 0;
    char *protocol = "typescript";

    #define STDIN   0 /* fileno(stdin)   */
    #define STDOUT  1 /* fileno(stdout)  */
    #define STDERR  2 /* fileno(stderr)  */

    /* какие каналы связаны с терминалом? */
    int tty_stdin, tty_stdout, tty_stderr;
    int TTYFD;


    void wm_checkttys(){
            TERMIO t;
            tty_stdin  = ( GTTY(STDIN,  &t) >= 0 );
            tty_stdout = ( GTTY(STDOUT, &t) >= 0 );
            tty_stderr = ( GTTY(STDERR, &t) >= 0 );

            if     ( tty_stdin  )  TTYFD = STDIN;
            else if( tty_stdout )  TTYFD = STDOUT;
            else if( tty_stderr )  TTYFD = STDERR;
            else {
                    fprintf(stderr, "Cannot access tty\n");
                    exit(7);
            }
    }














А. Богатырев, 1992-95                  - 264 -                              Си в UNIX

    /* Описатель трассируемого процесса */
    struct ptypair {
            char line[25];          /* терминальная линия: /dev/ttyp? */
            int pfd;                /* дескриптор master pty          */
            long in_bytes;          /* прочтено байт с клавиатуры     */
            long out_bytes;         /* послано байт на экран          */
            int pid;                /* идентификатор процесса         */
            time_t t_start, t_stop; /* время запуска и окончания      */
            char *command;          /* запущенная команда             */
    } PP;


    /* Эта функция вызывается при окончании трассируемого процесса -
     * по сигналу SIGCLD
     */
    char Reason[128];
    void ondeath(sig){
            int pid;
            extern void wm_done();
            int status;
            int fd;

            /* выявить причину окончания процесса */
            while((pid = wait(&status)) > 0 ){
              if( WIFEXITED(status))
                  sprintf( Reason, "Pid %d died with retcode %d",
                                        pid, WEXITSTATUS(status));
                else if( WIFSIGNALED(status)) {
                  sprintf( Reason, "Pid %d killed by signal #%d",
                                        pid, WTERMSIG(status));
    #ifdef WCOREDUMP
                  if(WCOREDUMP(status)) strcat( Reason, " Core dumped" );
    #endif
                } else if( WIFSTOPPED(status))
                  sprintf( Reason, "Pid %d suspended by signal #%d",
                                        pid, WSTOPSIG(status));
            }
            wm_done(0);
    }


    void wm_init(){
            wm_checkttys();

            GTTY(TTYFD, &told);

            /* Сконструировать "сырой" режим для нашего _базового_ терминала */
            tnew = told;

            tnew.c_cc[VINTR]  = '\0';
            tnew.c_cc[VQUIT]  = '\0';
            tnew.c_cc[VERASE] = '\0';
            tnew.c_cc[VKILL]  = '\0';
    #ifdef VSUSP
            tnew.c_cc[VSUSP]  = '\0';
    #endif








А. Богатырев, 1992-95                  - 265 -                              Си в UNIX

            /* CBREAK */
            tnew.c_cc[VMIN]   = 1;
            tnew.c_cc[VTIME]  = 0;

            tnew.c_cflag &= ~(PARENB|CSIZE);
            tnew.c_cflag |=   CS8;
            tnew.c_iflag &= ~(ISTRIP|ICRNL);
            tnew.c_lflag &= ~(ICANON|ECHO|ECHOK|ECHOE|XCASE);

            tnew.c_oflag &= ~OLCUC;
            /* но оставить c_oflag ONLCR и TAB3, если они были */

            /* моды для псевдотерминала */
            ttypmodes = told;
            /* не выполнять преобразования на выводе:
             * ONLCR:       \n --> \r\n
             * TAB3:        \t --> пробелы
             */
            ttypmodes.c_oflag &= ~(ONLCR|TAB3);

            (void) signal(SIGCLD, ondeath);
    }


    void wm_fixtty(){
            STTY(TTYFD, &tnew);
    }
    void wm_resettty(){
            STTY(TTYFD, &told);
    }


    /* Подобрать свободный псевдотерминал для трассируемого процесса */
    struct ptypair wm_ptypair(){
            struct ptypair p;

    #ifdef __SVR4
            p.pfd = (-1); p.pid = 0;
            p.in_bytes = p.out_bytes = 0;

            /* Открыть master side пары pty (еще есть slave) */
            if((p.pfd = open( "/dev/ptmx", O_RDWR)) < 0 ){
                /* Это клонируемый STREAMS driver.
                 * Поскольку он клонируемый, то есть создающий новое псевдоустройство
                 * при каждом открытии, то на master-стороне может быть только
                 * единственный процесс!
                 */
                perror( "Open /dev/ptmx" );
                goto err;
            }














А. Богатырев, 1992-95                  - 266 -                              Си в UNIX

    # ifdef notdef
            /* Сделать права доступа к slave-стороне моими. */
            if( grantpt (p.pfd) < 0 ){
                    perror( "grantpt");
                    exit(errno);
            }
    # endif
            /* Разблокировать slave-сторону псевдотерминала:
               позволить первый open() для нее */
            if( unlockpt(p.pfd) < 0 ){
                    perror( "unlockpt");
                    exit(errno);
            }

            /* Получить и записать имя нового slave-устройства-файла. */
            strcpy( p.line, ptsname(p.pfd));


    #else
            register i;
            char c;
            struct stat st;

            p.pfd = (-1); p.pid = 0;
            p.in_bytes = p.out_bytes = 0;

            strcpy( p.line, "/dev/ptyXX" );


            for( c = 'p'; c <= 's'; c++ ){
                    p.line[ strlen("/dev/pty") ] = c;
                    p.line[ strlen("/dev/ptyp")] = '0';
                    if( stat(p.line, &st) < 0 )
                            goto err;
                    for(i=0; i < 16; i++){
                            p.line[ strlen("/dev/ptyp") ] =
                                    "0123456789abcdef" [i] ;
                            if((p.pfd = open( p.line, O_RDWR )) >= 0 ){
                                    p.line[ strlen("/dev/") ] = 't';
                                    return p;
                            }
                    }
            }
    #endif
    err:    return p;
    }


















А. Богатырев, 1992-95                  - 267 -                              Си в UNIX

    /* Ведение статистики по вызовам script */
    void write_stat( in_bytes, out_bytes, time_here , name, line, at )
            long in_bytes, out_bytes;
            time_t time_here;
            char *name;
            char *line;
            char *at;
    {
            FILE *fplog;
            struct flock lock;

            if((fplog = fopen( LOGFILE, "a" )) == NULL )
                    return;

            lock.l_type   = F_WRLCK;
            lock.l_whence = 0;
            lock.l_start  = 0;
            lock.l_len    = 0;  /* заблокировать весь файл */
            fcntl  ( fileno(fplog), F_SETLKW, &lock );

            fprintf( fplog, "%s (%s) %ld bytes_in %ld bytes_out %ld secs %s %s %s",
                             PP.command, Reason, in_bytes, out_bytes,
                             time_here, name, line, at );
            fflush ( fplog );

            lock.l_type = F_UNLCK;
            lock.l_whence = 0;
            lock.l_start  = 0;
            lock.l_len    = 0;  /* разблокировать весь файл */
            fcntl  ( fileno(fplog), F_SETLK, &lock );

            fclose ( fplog );
    }


    void wm_done(sig){
            char *getlogin(), *getenv(), *logname = getlogin();
            time( &PP.t_stop );  /* запомнить время окончания */

            wm_resettty();   /* восстановить режим базового терминала */
            if( fpscript )
                    fclose(fpscript);
            if( PP.pid > 0 ) kill( SIGHUP, PP.pid ); /* "обрыв связи" */

            if( go ) write_stat( PP.in_bytes, PP.out_bytes,
                                 PP.t_stop - PP.t_start,
                                 logname ? logname : getenv("LOGNAME"),
                                 PP.line, ctime(&PP.t_stop) );
            printf( "\n" );
            exit(0);
    }













А. Богатырев, 1992-95                  - 268 -                              Си в UNIX

    /* Запуск трассируемого процесса на псевдотерминале */
    void wm_startshell (ac, av)
            char **av;
    {
            int child, fd, sig;

            if( ac == 0 ){
                    static char *avshell[] = { "/bin/sh", "-i", NULL };
                    av = avshell;
            }
            if((child = fork()) < 0 ){
                    perror("fork");
                    wm_done(errno);
            }
            if( child == 0 ){       /* SON */
                    if( tty_stdin )
                            setpgrp(); /* отказ от управляющего терминала */

                    /* получить новый управляющий терминал */
                    if((fd = open( PP.line, O_RDWR )) < 0 ){
                            exit(errno);
                    }

                    /* закрыть лишние каналы */
                    if( fpscript )
                            fclose(fpscript);
                    close( PP.pfd );


    #ifdef __SVR4
                    /* Push pty compatibility modules onto stream */
                    ioctl(fd, I_PUSH, "ptem");     /* pseudo tty module */
                    ioctl(fd, I_PUSH, "ldterm");   /* line discipline module */
                    ioctl(fd, I_PUSH, "ttcompat"); /* BSD ioctls module */
    #endif


                    /* перенаправить каналы, связанные с терминалом */
                    if( fd != STDIN  && tty_stdin  ) dup2(fd, STDIN);
                    if( fd != STDOUT && tty_stdout ) dup2(fd, STDOUT);
                    if( fd != STDERR && tty_stderr ) dup2(fd, STDERR);
                    if( fd > STDERR )
                        (void) close(fd);

                    /* установить моды терминала */
                    STTY(TTYFD, &ttypmodes);

                    /* восстановить реакции на сигналы */
                    for(sig=1; sig < NSIG; sig++)
                            signal( sig, SIG_DFL );

                    execvp(av[0], av);
                    system( "echo OBLOM > HELP.ME");
                    perror("execl");
                    exit(errno);









А. Богатырев, 1992-95                  - 269 -                              Си в UNIX

            } else {                /* FATHER */
                    PP.pid = child;
                    PP.command = av[0];
                    time( &PP.t_start ); PP.t_stop = PP.t_start;

                    signal( SIGHUP,  wm_done );
                    signal( SIGINT,  wm_done );
                    signal( SIGQUIT, wm_done );
                    signal( SIGTERM, wm_done );
                    signal( SIGILL,  wm_done );
                    signal( SIGBUS,  wm_done );
                    signal( SIGSEGV, wm_done );
            }
    }


    char buf[ BSIZE ];   /* буфер для передачи данных */

    /*                               /dev/pty?    /dev/ttyp?
         экран         *--------*          *--------*
         /|||          |        |  PP.pfd  |        |
        |||||<-STDOUT--|  мой   |<---------| псевдо |<-STDOUT---|
         \|||          |терминал|          |терминал|<-STDERR---|трассируемый
                       |(базовый)          |        |           |процесс
        -------        |        |  STDIN   |        |           |
        |.....|-STDIN-->        |---------->        |--STDIN--->|
        |_____|        |        |          |        |
        клавиатура     *--------*          *--------*
                                        master     slave

    */


    /* Опрос дескрипторов */
    void wm_select(){
            int nready;
            int nfds;
            int maxfd;
            int nopen;  /* число опрашиваемых дескрипторов */
            register f;

            fd_set set, rset;       /* маски */
            struct timeval timeout, rtimeout;

            FD_ZERO(&set); nopen = 0;        /* очистка маски */

            FD_SET (PP.pfd, &set); nopen++;  /* учесть в маске */
            FD_SET (STDIN,  &set); nopen++;
            maxfd = max(PP.pfd, STDIN);

            timeout.tv_sec = 3600;      /* секунд */
            timeout.tv_usec = 0;        /* миллисекунд */












А. Богатырев, 1992-95                  - 270 -                              Си в UNIX

            nfds =  maxfd + 1;
            while( nopen ){
               rset = set;
               rtimeout = timeout;

               /* опросить дескрипторы */
               if((nready = select( nfds, &rset, NULL, NULL, &rtimeout )) <= 0)
                    continue;

               for(f=0; f < nfds; f++ )
                    if( FD_ISSET(f, &rset)){  /* дескриптор f готов */
                            int n;

                            if((n = read(f, buf, sizeof buf)) <= 0 ){
                                FD_CLR(f, &set); nopen--; /* исключить */
                                close(f);

                            } else {
                                int fdout;

                                /* учет и контроль */
                                if( f == PP.pfd ){
                                    fdout = STDOUT;
                                    PP.out_bytes += n;
                                    if( fpscript )
                                        fwrite(buf, 1, n, fpscript);


                                } else if( f == STDIN  ) {
                                    fdout = PP.pfd;
                                    PP.in_bytes += n;
                                    if( halfflag && fpscript )
                                        fwrite(buf, 1, n, fpscript);
                                    if( autoecho )
                                        write(STDOUT, buf, n);
                                }
                                write(fdout, buf, n);
                            }
                    }
            }
    }























А. Богатырев, 1992-95                  - 271 -                              Си в UNIX

    int main(ac, av) char **av;
    {
            while( ac > 1 && *av[1] == '-' ){
                    switch(av[1][1]){
                    case 's':
                            scriptflg++;
                            break;
                    case 'f':
                            av++; ac--;
                            protocol = av[1];
                            scriptflg++;
                            break;
                    case 'h':
                            halfflag++;
                            break;
                    case 'a':
                            autoecho++;
                            break;
                    default:
                            fprintf(stderr, "Bad key %s\n", av[1]);
                            break;
                    }
                    ac--; av++;
            }
            if( scriptflg ){
                    fpscript = fopen( protocol, "w" );
            }
            ac--; av++;


            wm_init();
            PP = wm_ptypair();
            if( PP.pfd < 0 ){
                    fprintf(stderr, "Cannot get pty. Please wait and try again.\n");
                    return 1;
            }
            wm_fixtty();
            wm_startshell(ac, av);
            go++;
            wm_select();
            wm_done(0);
            /* NOTREACHED */
            return 0;
    }


6.12.  Простой интерпретатор команд.
     Данный раздел просто приводит исходный  текст  простого  интерпретатора  команд.
Функция match описана в главе "Текстовая обработка".















А. Богатырев, 1992-95                  - 272 -                              Си в UNIX

    /* Примитивный интерпретатор команд. Распознает построчно
     * команды вида: CMD ARG1 ... ARGn <FILE >FILE >>FILE >&FILE >>&FILE
     * Сборка: cc -U42 -DCWDONLY sh.c match.c pwd.c -o sh
     */

    #include <sys/types.h>/* определение типов, используемых системой */
    #include <stdio.h>      /* описание библиотеки ввода/вывода  */
    #include <signal.h>     /* описание сигналов                 */
    #include <fcntl.h>      /* определение O_RDONLY              */
    #include <errno.h>      /* коды системных ошибок             */
    #include <ctype.h>      /* макросы для работы с символами    */
    #include <dirent.h>     /* эмуляция файловой системы BSD 4.2 */
    #include <pwd.h>        /* работа с /etc/passwd              */
    #include <sys/wait.h>   /* описание формата wait()           */


    char cmd[256];          /* буфер для считывания команды */
    #define   MAXARGS 256   /* макс. количество аргументов  */
    char *arg[MAXARGS];     /* аргументы команды */
    char *fin, *fout;       /* имена для перенаправления ввода/вывода */
    int  rout;              /* флаги перенаправления вывода */

    char *firstfound;       /* имя найденной, но невыполняемой программы */
    #define LIM ':'         /* разделитель имен каталогов в path */
    extern char *malloc(), *getenv(), *strcpy(), *getwd();
    extern char *strchr(), *execat();
    extern void callshell(), printenv(), setenv(), dowait(), setcwd();
    extern struct passwd   *getpwuid();
            /* Предопределенные переменные */
    extern char **environ;  /* окружение: изначально смотрит на тот же
                             * массив, что и ev из main() */
    extern int errno;       /* код ошибки системного вызова   */


    char *strdup(s)char *s;
    { char *p; return(p=malloc(strlen(s)+1), strcpy(p,s)); }
            /* strcpy() возвращает свой первый аргумент */
    char *str3spl(s, p, q) char *s, *p, *q;
    { char *n = malloc(strlen(s)+strlen(p)+strlen(q)+1);
      strcpy(n, s); strcat(n, p); strcat(n, q); return n;
    }

    int cmps(s1, s2) char **s1, **s2;
    { return strcmp(*s1, *s2); }




















А. Богатырев, 1992-95                  - 273 -                              Си в UNIX

    /* Перенаправить вывод */
    #define APPEND 0x01
    #define ERRTOO 0x02
    int output (name, append, err_too, created) char *name; int *created;
    {
        int     fd;
        *created = 0;     /* Создан ли файл ? */

        if( append ){                   /* >>file */
            /* Файл name существует? Пробуем открыть на запись */
            if((fd = open (name, O_WRONLY)) < 0) {
                if (errno == ENOENT) /* Файл еще не существовал */
                    goto CREATE;
                else
                    return 0;    /* Не имеем права писать в этот файл */
            }
            /* иначе fd == открытый файл, *created == 0 */
        }else{
    CREATE: /* Пытаемся создать (либо опустошить) файл "name" */
            if((fd = creat (name, 0666)) < 0 )
                        return 0;     /* Не могу создать файл  */
            else        *created = 1; /* Был создан новый файл */
        }
        if (append)
            lseek (fd, 0l, 2);      /* на конец файла */
     /* перенаправить стандартный вывод */
        dup2(fd, 1);
        if( err_too ) dup2(fd, 2);  /* err_too=1 для >& */
        close(fd); return 1;
    }


    /* Перенаправить ввод */
    int input (name) char *name;
    {
        int     fd;
        if((fd = open (name, O_RDONLY)) < 0 ) return 0;/* Не могу читать */
     /* перенаправить стандартный ввод */
        dup2(fd, 0); close(fd); return 1;
    }
























А. Богатырев, 1992-95                  - 274 -                              Си в UNIX

    /* запуск команды */
    int cmdExec(progr, av, envp, inp, outp, outflg)
            char *progr;       /* имя программы */
            char **av;         /* список аргументов */
            char **envp;       /* окружение */
            char *inp, *outp;  /* файлы ввода-вывода (перенаправления) */
            int outflg;        /* режимы перенаправления вывода */
    {
            void (*del)(), (*quit)();
            int pid;
            int cr = 0;

            del = signal(SIGINT, SIG_IGN); quit = signal(SIGQUIT, SIG_IGN);
            if( ! (pid = fork())){     /* ветвление */
               /* порожденный процесс (сын) */
               signal(SIGINT, SIG_DFL); /* восстановить реакции */
               signal(SIGQUIT,SIG_DFL); /* по умолчанию         */
               /* getpid() выдает номер (идентификатор) данного процесса */
               printf( "Процесс pid=%d запущен\n", pid = getpid());

               /* Перенаправить ввод-вывод */
               if( inp ) if(!input( inp )){
                 fprintf(stderr, "Не могу <%s\n", inp ); goto Err;
               }
               if( outp )
                   if(!output (outp, outflg & APPEND, outflg & ERRTOO, &cr)){
                       fprintf(stderr, "Не могу >%s\n", outp ); goto Err;
                   }
     /* Заменить программу: при успехе
      * данная программа завершается, а вместо нее вызывается
      * функция main(ac, av, envp) программы, хранящейся в файле progr.
      * ac вычисляет система.
      */
               execvpe(progr, av, envp);


    Err:
      /* при неудаче печатаем причину и завершаем порожденный процесс */
               perror(firstfound ? firstfound: progr);
               /* Мы не делаем free(firstfound),firstfound = NULL
                * потому что данный процесс завершается (и тем ВСЯ его
                * память освобождается) :
                */
               if( cr && outp )  /* был создан новый файл     */
                   unlink(outp); /* но теперь он нам не нужен */

               exit(errno);
            }
            /* процесс - отец */

            /* Сейчас сигналы игнорируются, wait не может быть оборван
             * прерыванием с клавиатуры */
            dowait();       /* ожидать окончания сына */
            /* восстановить реакции на сигналы от клавиатуры */
            signal(SIGINT, del); signal(SIGQUIT, quit);
            return pid;     /* вернуть идентификатор сына */
    }







А. Богатырев, 1992-95                  - 275 -                              Си в UNIX

    /* Запуск программы с поиском по переменной среды PATH */
    int execvpe(progr, av, envp) char *progr, **av, **envp;
    {
            char *path, *cp;
            int try = 1;
            register eacces = 0;
            char fullpath[256];     /* полное имя программы */

            firstfound = NULL;
            if((path = getenv("PATH")) == NULL )
                path = ".:/bin:/usr/bin:/etc";
            /* имя: короткое или путь уже задан ? */
            cp = strchr(progr, '/') ? "" : path;
            do{     /* пробуем разные варианты */
                    cp = execat(cp, progr, fullpath);
            retry:
                fprintf(stderr, "пробуем \"%s\"\n", fullpath );
                    execve(fullpath, av, envp);
                    /* если программа запустилась, то на этом месте данный
                     * процесс заменился новой программой. Иначе - ошибка. */
                    switch( errno ){  /* какова причина неудачи ? */
                    case ENOEXEC:  /* это командный файл */
                            callshell(fullpath, av, envp);
                            return (-1);
                    case ETXTBSY:   /* файл записывается */
                            if( ++try > 5 ) return (-1);
                            sleep(try); goto retry;
                    case EACCES:    /* не имеете права */
                            if(firstfound == NULL)
                               firstfound = strdup(fullpath);
                            eacces++; break;
                    case ENOMEM:    /* программа не лезет в память */
                    case E2BIG:
                            return (-1);
                    }
            }while( cp );
            if( eacces ) errno = EACCES;
            return (-1);
    }


    /* Склейка очередной компоненты path и имени программы name */
    static char *execat(path, name, buf)
            register char *path, *name;
            char *buf;      /* где будет результат */
    {
            register char *s = buf;
            while(*path && *path != LIM )
                    *s++ = *path++;         /* имя каталога */
            if( s != buf ) *s++ = '/';
            while( *name )
                    *s++ = *name++;         /* имя программы */
            *s = '\0';
            return ( *path ? ++path /* пропустив LIM */ : NULL );
    }









А. Богатырев, 1992-95                  - 276 -                              Си в UNIX

    /* Запуск командного файла при помощи вызова интерпретатора */
    void callshell(progr, av, envp) char *progr, **av, **envp;
    {
            register i; char *sh; char *newav[MAXARGS+2];
            int fd; char first = 0;

            if((fd = open(progr, O_RDONLY)) < 0 )
                    sh = "/bin/sh";
            else{
                    read(fd, &first, 1); close(fd);
                    sh = (first == '#') ? "/bin/csh" : "/bin/sh";
            }
            newav[0] = "Shellscript"; newav[1] = progr;
            for(i=1; av[i]; i++)
                    newav[i+1] = av[i];
            newav[i+1] = NULL;
            printf( "Вызываем %s\n", sh );
            execve(sh, newav, envp);
    }


    /* Ожидать окончания всех процессов, выдать причины смерти. */
    void dowait(){
            int ws; int pid;

            while((pid = wait( &ws)) > 0 ){
              if( WIFEXITED(ws)){
                printf( "Процесс %d умер с кодом %d\n",
                                 pid,            WEXITSTATUS(ws));
              }else if( WIFSIGNALED(ws)){
                printf( "Процесс %d убит сигналом %d\n",
                                 pid,             WTERMSIG(ws));
                if(WCOREDUMP(ws)) printf( "Образовался core\n" );
                /* core - образ памяти процесса для отладчика adb */
              }else if( WIFSTOPPED(ws)){
                printf( "Процесс %d остановлен сигналом %d\n",
                                 pid,            WSTOPSIG(ws));
              }
           }
    }
























А. Богатырев, 1992-95                  - 277 -                              Си в UNIX

    /* Расширение шаблонов имен. Это упрощенная версия, которая
     * расширяет имена только в текущем каталоге.
     */
    void glob(dir, args, indx, str /* что расширять */, quote )
         char *args[], *dir; int *indx; char *str;
         char quote; /* кавычки, в которые заключена строка str */
    {
            static char globchars[] = "*?[";
            char *p; char **start = &args[ *indx ];
            short nglobbed = 0;

            register struct dirent *dirbuf;
            DIR *fd; extern DIR *opendir();

            /* Затычка для отмены глоббинга: */
            if( *str == '\\' ){ str++;    goto noGlob; }

            /* Обработка переменных $NAME    */
            if( *str == '$' && quote != '\'' ){
                    char *s = getenv(str+1);
                    if( s ) str = s;
            }
            /* Анализ: требуется ли глоббинг */
            if( quote ) goto noGlob;
            for( p=str; *p; p++ )  /* Есть ли символы шаблона? */
                    if( strchr(globchars, *p))
                            goto doGlobbing;
    noGlob:
            args[ (*indx)++ ] = strdup(str);
            return;


    doGlobbing:
           if((fd = opendir (dir)) == NULL){
                  fprintf(stderr, "Can't read %s\n", dir); return;
           }
           while ((dirbuf = readdir (fd)) != NULL ) {
                  if (dirbuf->d_ino == 0) continue;
                  if (strcmp (dirbuf->d_name, ".") == 0 ||
                      strcmp (dirbuf->d_name, "..") == 0) continue;
                  if( match( dirbuf->d_name, str)){
                      args[ (*indx)++ ] = strdup(dirbuf->d_name);
                      nglobbed++;
                  }
           }
           closedir(fd);
           if( !nglobbed){
               printf( "%s: no match\n", str);
               goto noGlob;
           }else{   /* отсортировать */
               qsort(start, nglobbed, sizeof (char *), cmps);
           }
    }











А. Богатырев, 1992-95                  - 278 -                              Си в UNIX

    /* Разбор командной строки */
    int parse(s) register char *s;
    {
            int i; register char *p;
            char tmp[80];   /* очередной аргумент */
            char c;

            /* очистка старых аргументов */
            for(i=0; arg[i]; i++) free(arg[i]), arg[i] = NULL;
            if( fin  ) free(fin ), fin  = NULL;
            if( fout ) free(fout), fout = NULL;
            rout = 0;


            /* разбор строки */
            for( i=0 ;; ){
                    char quote = '\0';

                    /* пропуск пробелов - разделителей слов */
                    while((c = *s) && isspace(c)) s++;
                    if( !c ) break;
                    /* очередное слово */
                    p = tmp;
                    if(*s == '\'' || *s == '"' ){
                    /* аргумент в кавычках */
                            quote = *s++;  /* символ кавычки */
                            while((c = *s) != '\0' && c != quote){
                               if( c == '\\' ){ /* заэкранировано */
                                   c = *++s;
                                   if( !c ) break;
                               }
                               *p++ = c; ++s;
                            }
                            if(c == '\0')
                      fprintf(stderr, "Нет закрывающей кавычки %c\n", quote);
                            else s++; /* проигнорировать кавычку на конце */




























А. Богатырев, 1992-95                  - 279 -                              Си в UNIX

                    } else
                            while((c = *s) && !isspace(c)){
                               if(c == '\\') /* заэкранировано */
                                    if( !(c = *++s))
                                         break /* while */;
                               *p++ = c; s++;
                            }
                    *p = '\0';
                    /* Проверить, не есть ли это перенаправление
                     * ввода/вывода. В отличие от sh и csh
                     * здесь надо писать >ФАЙЛ <ФАЙЛ
                     * >< вплотную к имени файла.
                     */
                    p = tmp;        /* очередное слово */
                    if( *p == '>'){ /* перенаправлен вывод */
                        p++;
                        if( fout ) free(fout), rout = 0; /* уже было */
                        if( *p == '>' ){ rout |= APPEND; p++; }
                        if( *p == '&' ){ rout |= ERRTOO; p++; }
                        if( !*p ){
                                fprintf(stderr, "Нет имени для >\n");
                                fout = NULL; rout = 0;
                        } else  fout = strdup(p);
                    } else if( *p == '<' ){  /* перенаправлен ввод */
                        p++;
                        if( fin ) free(fin);        /* уже было */
                        if( !*p ){
                                fprintf(stderr, "Нет имени для <\n");
                                fin = NULL;
                        } else  fin = strdup(p);
                    } else /* добавить имена к аргументам */
                                glob( ".", arg, &i, p, quote );
            }
            arg[i] = NULL; return i;
    }


    /* Установить имя пользователя */
    void setuser(){
            int uid = getuid();     /* номер пользователя, запустившего Шелл */
            char *user = "mr. Nobody";      /* имя пользователя     */
            char *home = "/tmp";            /* его домашний каталог */
            struct passwd *pp = getpwuid( uid );
            if( pp != NULL ){
                    if(pp->pw_name && *pp->pw_name ) user = pp->pw_name;
                    if(               *pp->pw_dir  ) home = pp->pw_dir;
            }
            setenv("USER", user); setenv("HOME", home);
    }


    void setcwd(){ /* Установить имя текущего каталога */
            char cwd[512];
            getwd(cwd); setenv( "CWD", cwd );
    }









А. Богатырев, 1992-95                  - 280 -                              Си в UNIX

    void main(ac, av, ev) char *av[], *ev[]; {
            int argc;              /* количество аргументов */
            char *prompt;          /* приглашение           */

            setuser(); setcwd();
            signal(SIGINT, SIG_IGN);
            setbuf(stdout, NULL);  /* отменить буферизацию */
            for(;;){
                 prompt = getenv( "prompt" ); /* setenv prompt -->\  */
                 printf( prompt ? prompt : "@ ");/* приглашение */
                 if( gets(cmd) == NULL /* at EOF  */ ) exit(0);
                 argc = parse(cmd);
                 if( !argc) continue;
                 if( !strcmp(arg[0], "exit" )) exit(0);


                 if( !strcmp(arg[0], "cd" )){
                     char *d = (argc==1) ? getenv("HOME"):arg[1];
                     if(chdir(d) < 0)
                        printf( "Не могу войти в %s\n", d );
                     else setcwd();
                     continue;
                 }


                 if( !strcmp(arg[0], "echo" )){
                     register i; FILE *fp;
                     if( fout ){
                        if((fp = fopen(fout, rout & APPEND ? "a":"w"))
                               == NULL) continue;
                     }  else fp = stdout;
                     for(i=1; i < argc; i++ )
                        fprintf( fp, "%s%s", arg[i], i == argc-1 ? "\n":"   ");
                     if( fp != stdout ) fclose(fp);
                     continue;
                 }


                 if( !strcmp(arg[0], "setenv" )){
                          if( argc == 1 ) printenv();
                     else if( argc == 2 ) setenv( arg[1], "" );
                     else                 setenv( arg[1], arg[2]);
                     continue;
                 }
                 cmdExec(arg[0], (char **) arg, environ, fin, fout, rout);
            }
    }

















А. Богатырев, 1992-95                  - 281 -                              Си в UNIX

    /* -----------------------------------------------------------*/
    /* Отсортировать и напечатать окружение */
    void printenv(){
            char *e[40]; register i = 0; char *p, **q = e;

            do{
                 p = e[i] = environ[i]; i++;
            } while( p );

    #ifdef SORT
            qsort( e, --i /* сколько */, sizeof(char *), cmps);
    #endif
            while( *q )
                 printf( "%s\n", *q++ );
    }


    /* Сравнение имени переменной окружения с name */
    static char *envcmp(name, evstr) char *name, *evstr;
    {
            char *p; int code;
            if((p = strchr(evstr, '=')) == NULL ) return NULL; /* error ! */
            *p = '\0';      /* временно */
            code = strcmp(name, evstr);
            *p = '=';       /* восстановили */
            return code==0 ? p+1 : NULL;
    }


    /* Установить переменную окружения */
    void setenv( name, value ) char *name, *value;
    {
            static malloced = 0;    /* 1, если environ перемещен */
            char *s, **p, **newenv;
            int len, change_at = (-1), i;

            /* Есть ли переменная name в environ-е ? */
            for(p = environ; *p; p++ )
                    if(s = envcmp(name, *p)){       /* уже есть */
                            if((len = strlen(s)) >= strlen(value)){
                                /* достаточно места */
                                strcpy(s, value); return;
                            }
                            /* Если это новый environ ... */
                            if( malloced ){
                                free( *p ); *p = str3spl(name, "=", value);
                                return;
                            }
                            /* иначе создаем копию environ-а */
                            change_at = p - environ;        /* индекс */
                            break;
                    }












А. Богатырев, 1992-95                  - 282 -                              Си в UNIX

            /* Создаем копию environ-а. Если change_at == (-1), то
             * резервируем новую ячейку для еще не определенной переменной */
            for(p=environ, len=0; *p; p++, len++ );
            /* вычислили количество переменных */
            if( change_at < 0 ) len++;
            if((newenv = (char **) malloc( sizeof(char *) * (len+1)))
                    == (char **) NULL) return;
            for(i=0; i < len+1; i++ ) newenv[i] = NULL;     /* зачистка */
            /* Копируем старый environ в новый */
            if( !malloced ) /* исходный environ в стеке (дан системой) */
                 for(i=0; environ[i]; i++ ) newenv[i] = strdup(environ[i]);
            else for(i=0; environ[i]; i++ ) newenv[i] =        environ[i];
            /* Во втором случае строки уже были спасены, копируем ссылки */


            /* Изменяем, если надо: */
            if( change_at >= 0 ){
                    free( newenv[change_at] );
                    newenv[change_at] = str3spl(name, "=", value);
            } else {        /* добавить в конец новую переменную */
                    newenv[len-1] = str3spl(name, "=", value);
            }
            /* подменить environ */
            if( malloced ) free( environ );
            environ = newenv; malloced++;
            qsort( environ, len, sizeof(char *), cmps);
    }


    /* Допишите команды:
       unsetenv имя_переменной - удаляет переменную среды;
       exit N                  - завершает интерпретатор с
                                 кодом возврата N (это целое число);
    */



With any suggestions or questions please feel free to contact us