буфера внутри ядра.





А. Богатырев, 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 )