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
#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;
/* Эта функция вызывается при окончании трассируемого процесса * по сигналу 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);
}
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" );
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 );
}
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
/* перенаправить каналы, связанные с терминалом */
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);
} else { /* FATHER */
PP.pid = child;
PP.command = av[0];
time( &PP.t_start ); PP.t_stop = PP.t_start;
signal( SIGHUP, wm_done );
signal( SIGINT, wm_done );
signal( SIGQUIT, wm_done );
signal( SIGTERM, wm_done );
signal( SIGILL, wm_done );
signal( SIGBUS, wm_done );
signal( SIGSEGV, wm_done );
}
}
char buf[ BSIZE ]; /* буфер для передачи данных */
/* /dev/pty? /dev/ttyp?
экран *--------* *--------*
/||| | | PP.pfd | |
|||||<-STDOUT--| мой |<---------| псевдо |<-STDOUT---|
\||| |терминал| |терминал|<-STDERR---|трассируемый
|(базовый) | | |процесс
------- | | STDIN | | |
|.....|-STDIN--> |----------> |--STDIN--->|
|_____| | | | |
клавиатура *--------* *--------*
master slave
*/
/* Опрос дескрипторов */
void wm_select(){
int nready;
int nfds;
int maxfd;
int nopen; /* число опрашиваемых дескрипторов */
register f;
fd_set set, rset; /* маски */
struct timeval timeout, rtimeout;
FD_ZERO(&set); nopen = 0; /* очистка маски */
FD_SET (PP.pfd, &set); nopen++; /* учесть в маске */
FD_SET (STDIN, &set); nopen++;
maxfd = max(PP.pfd, STDIN);
timeout.tv_sec = 3600; /* секунд */
timeout.tv_usec = 0; /* миллисекунд */
nfds = maxfd + 1;
while( nopen ){
rset = set;
rtimeout = timeout;
/* опросить дескрипторы */
if((nready = select( nfds, &rset, NULL, NULL, &rtimeout )) <= 0)
continue;
for(f=0; f < nfds; f++ )
if( FD_ISSET(f, &rset)){ /* дескриптор f готов */
int n;
if((n = read(f, buf, sizeof buf)) <= 0 ){
FD_CLR(f, &set); nopen--; /* исключить */
close(f);
} else {
int fdout;
/* учет и контроль */
if( f == PP.pfd ){
fdout = STDOUT;
PP.out_bytes += n;
if( fpscript )
fwrite(buf, 1, n, fpscript);
} else if( f == STDIN ) {
fdout = PP.pfd;
PP.in_bytes += n;
if( halfflag && fpscript )
fwrite(buf, 1, n, fpscript);
if( autoecho )
write(STDOUT, buf, n);
}
write(fdout, buf, n);
}
}
}
}
int main(ac, av) char **av;
{
while( ac > 1 && *av[1] == '-' ){
switch(av[1][1]){
case 's':
scriptflg++;
break;
case 'f':
av++; ac--;
protocol = av[1];
scriptflg++;
break;
case 'h':
halfflag++;
break;
case 'a':
autoecho++;
break;
default:
fprintf(stderr, "Bad key %s\n", av[1]);
break;
}
ac--; av++;
}
if( scriptflg ){
fpscript = fopen( protocol, "w" );
}
ac--; av++;
wm_init();
PP = wm_ptypair();
if( PP.pfd < 0 ){
fprintf(stderr, "Cannot get pty. Please wait and try again.\n");
return 1;
}
wm_fixtty();
wm_startshell(ac, av);
go++;
wm_select();
wm_done(0);
/* NOTREACHED */
return 0;
}
© Copyright А. Богатырев, 1992-95
Си в UNIX
Назад | Содержание | Вперед