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