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

Назад | Содержание | Вперед