антиро-
ванно быть корректными на диске, используется открытие файла
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 s