Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Программирование Cи / Богатырев_Язык Си в системе Unix

.pdf
Скачиваний:
80
Добавлен:
25.04.2015
Размер:
1.19 Mб
Скачать

А. Богатырёв, 1992-96 - 221 - Си в 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 ); 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';

А. Богатырёв, 1992-96

 

 

- 222 -

Си в UNIX™

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);

}

}

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

6.11. Мультиплексирование ввода-вывода.

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

А. Богатырёв, 1992-96

- 223 -

Си в UNIX™

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-96

- 224 -

Си в 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-96

- 225 -

Си в 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. В качестве самостоятельной работы предлагаем вам пример программы, ведущей протокол сеанса работы. Информацию о псевдотерминалах изучите самостоятельно.

/*

*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-96

- 226 -

Си в 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);

 

 

 

}

 

 

 

 

}

 

 

 

 

/* Описатель трассируемого процесса */

 

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;

 

 

 

 

А. Богатырёв, 1992-96

- 227 -

Си в UNIX™

/* Эта функция вызывается при окончании трассируемого процесса - * по сигналу 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

/* 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);

}

А. Богатырёв, 1992-96

- 228 -

Си в UNIX™

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;

}

# 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" );

А. Богатырёв, 1992-96

- 229 -

Си в UNIX™

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;

}

/* Ведение статистики по вызовам 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 );

}

А. Богатырёв, 1992-96

- 230 -

Си в UNIX™

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);

}

/* Запуск трассируемого процесса на псевдотерминале */ 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