myfatelxy 发表于 2012-5-1 20:24:13

实现linux下script 命令

//由于最近需要用到script命令,且此命令在系统调起的时候系统为此命令开启子进程,故此在对调用script命令时退出命令和其产生的文件进行操作时会带来诸多的不便,
//故实/现了此命令。代码如下:

#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <stropts.h>
#include <termios.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <stdio.h>
#include <pthread.h>
#define MAXARGS 32                        /*shell 最长的命令限制*/
char      *shell = "/bin/sh";             /* 默认 shell      */
char      *filename = "mopon.log";       /* 默认文件名      */
char      *mastername = "/dev/ptmx";      /* pty clone device   */
int   master;                           /* master side of pty */
FILE      *script;                        
struct termiosnewtty, origtty;            /* tty 模式      */
pthread_attr_t attr;
pthread_t idpthread;

void    finish(int);
int ptyopen(char *, struct termios *);
void readfile();

typedef struct scriptdata{
         char buf;
            int length;
      }scriptdata;

int main(int argc, char **argv)
{
    char *p;
    int n, nfd;
    time_t clock;
    fd_set readmask;
    char buf;
    /*
   * 如果用户给出文件名则生成一个新的 script file.
   *默认将生产mopon.log文件
   */
    if (argc > 1)
      filename = *++argv;
    /*
   * 1. 得到用户shell
   */
    if ((p = getenv("SHELL")) != NULL)
      shell = p;
    /*
   * 2. Open the script file.
   */
    if ((script = fopen(filename, "w")) == NULL)
    {
      perror(filename);
      exit(1);
    }
    /*
   * 3. Get the tty modes.将设置2个TTY 主设备和从设备,
   *   在从设备运行命令,并在从设备退出时恢复主设备状态
   *   
   *   
   * tcgetattr 获取与终端相关的参数,返回的结果保存在termios(origtty)结构体中
   */
    if (tcgetattr(0, &origtty) < 0)
    {
      perror("tcgetattr: stdin");
      exit(1);
    }
    /*
   * 4. 得到从设备-tty 文件描述符,开启一个新的 shell 会话.
   */
    if ((master = ptyopen(shell, &origtty)) < 0)
      exit(1);

    /*
   * 打印script开启信息.
   */
    time(&clock);
    fprintf(script, "Script started on %s", ctime(&clock));
    printf("Script started, file is %s\n", filename);
    /*
   * 5. 抓取信号,通过信号退出程序.
   *每当进程接收到某个已处理的信号时,就会调用通过 sigset 分配的函数
   */
    sigset(SIGINT, finish);
    sigset(SIGQUIT, finish);
    /*
   * 6. Change the user's tty modes such that pretty
   *    much everything gets passed through to the
   *    pseudo-tty.Set "raw" mode so that we can pass
   *    characters as they're typed, etc.
   */
    newtty = origtty;
    newtty.c_cc = 1;
    newtty.c_cc = 0;
    newtty.c_oflag &= ~OPOST;
    newtty.c_lflag &= ~(ICANON|ISIG|ECHO);
    newtty.c_iflag &= ~(INLCR|IGNCR|ICRNL|IUCLC|IXON);
    /*
   * 7a. 设置这新的TTY的模式.
   *int tcsetattr(int fd, int optional_actions, const struct termios *termios_p);用于设置终端参数
   *fd -->打开的终端文件描述符
   *optional_actions-->取值如下:
   * TCSANOW:不等数据传输完毕就立即改变属性。
   *TCSADRAIN:等待所有数据传输结束才改变属性。
   *TCSAFLUSH:清空输入输出缓冲区才改变属性。
   *termios结构体中保存了要修改的参数。
   */
    if (tcsetattr(0, TCSANOW, &newtty) < 0)
    {
      perror("tcsetattr: stdin");
      exit(1);
    }
/*7b:
   *在主进程进入死循环前
   *可以在此处起一线程死循环专门用于读取文件
   *和清除文件
*/
// pthread_create(&idpthread,&attr,(void *)readfile,NULL);
    /*
   * 8.
   *通过循环将键盘输入在新会话显示
   *并将此输入和新会话下的输出全部copy到文件
   */
    for (;;)
    {
      FD_ZERO(&readmask);
      FD_SET(master, &readmask);
      FD_SET(0, &readmask);
//FD_SET(int fd, fd_set *fdset):建立文件句柄fd与fdset的联系
      nfd = master + 1;
      /*
         * 8a.read.
         *int select(nfds, readfds, writefds, exceptfds, timeout)
         *ndfs:select监视的文件句柄数,视进程中打开的文件数而定,一般设为你要监视各文件
         *readfds:select监视的可读文件句柄集合
         *writefds: select监视的可写文件句柄集合
         *exceptfds:select监视的异常文件句柄集合
         *timeout:本次select()的超时结束时间。(见/usr/sys/select.h
         */
      n = select(nfd, &readmask, (fd_set *) 0, (fd_set *) 0,(struct timeval *) 0);/*设置为0 的君为不设置监视*/
      if (n < 0)
{
            perror("select");
            exit(1);
      }
      /*
         * 8b. The user typed something... read it and pass
         *      it on to the pseudo-tty.
         */
      if (FD_ISSET(0, &readmask))
/*int FD_ISSET(int fd,fd_set *fdset) 在调用select()函数后,用FD_ISSET来检测fdset中文件fd有无发生变化
*
*返回整型,当fd是fdset的子集的时候,返回真,否则返回假。
*/
{
            if ((n = read(0, buf, sizeof(buf))) < 0)
      {
                perror("read: stdin");
                exit(1);
            }
            /*
             * The user typed end-of-file; we're
             * done.
             */
            if (n == 0)
                finish(0);
            if (write(master, buf, n) != n)
      {
                perror("write: pty");
                exit(1);
            }
      }
      /*
         * 8c. There's output on the pseudo-tty... read it and
         *   pass it on to the screen and the script file.
         */
      if (FD_ISSET(master, &readmask))
{
            /*
             * The process died.
             */
            if ((n = read(master, buf, sizeof(buf))) <= 0)
                finish(0);
            fwrite(buf, sizeof(char), n, script);
            write(1, buf, n);
   
      }
    }
}
/*
* ptyopen - 运行命令在从设备-tty 返回新文件描述符
*进入新的会话      
*/
int ptyopen(char *command, struct termios *ttymodes)
{
    char *p;
    pid_t pid;
    char *slavename;
    char *args;
    int nargs, master, slave;
    /*
   * 9. 对命令参数的处理
   */
    nargs = 0;
    p = strtok(command, " \t\n");
    do
   {
      if (nargs == MAXARGS)
{
            fprintf(stderr, "too many arguments.\n");
            return(-1);
      }
      
      args = p;
      p = strtok(NULL, " \t\n");
    } while (p != NULL);
    args = NULL;
    /*
   * 10. 取得主终端设备流
   *      后续将通过fork取得一个子终端
   */
    if ((master = open(mastername, O_RDWR)) < 0)
    {
      perror(mastername);
      return(-1);
    }
    /*
   * 11. 设置权限
   *grantpt来改变从设备的权限
   */
    if (grantpt(master) < 0)
    {
      perror("granpt");
      close(master);
      return(-1);
    }
    /*
   * 12.
   *unlockpt用来清除从设备的内部锁。在打开从设备前我们必须做这件事情
   */
    if (unlockpt(master) < 0)
    {
      perror("unlockpt");
      close(master);
      return(-1);
    }
    /*
   * 13. Start a child process.
   */
    if ((pid = fork()) < 0)
   {
      perror("fork");
      close(master);
      return(-1);
    }
    /*
   * 14. 子进程将开启一个新的会话为(这个就是script的从终端设备操作界面)
   *   
   */
    if (pid == 0)
    {
         /*
          * 14a. Get rid of our current controlling terminal.
          */
         setsid();// setsid runs a program in a new session.
         /*
          * 14b.
          *调用ptsname来得到从设备的名称
          */
         if ((slavename = ptsname(master)) == NULL)
{
             perror("ptsname");
             close(master);
             exit(1);
         }
         /*
          * 14c. 打开从设备
          */
         if ((slave = open(slavename, O_RDWR)) < 0)
{
             perror(slavename);
             close(master);
             exit(1);
         }

         /*
          * 14d. Copy主终端设备的模式到从终端设备
          *      
          */
         if (tcsetattr(slave, TCSANOW, ttymodes) < 0) /*设置终端参数*/
{
             perror("tcsetattr: pty");
             close(master);
             close(slave);
             exit(1);
         }
         /*
          * 14e. 关掉fork带来的文件描述符,不在新会话中对此文件操作
          *    并关闭主程序打开的TTY设备,不需要
          */
         fclose(script);
         close(master);
         /*
          * 14f. 将 input, output,and error output重定向到从设备
          *
          */
         dup2(slave, 0);
         dup2(slave, 1);
         dup2(slave, 2);
         close(slave);
         /*
          * 14h. 在从设备执行命令
          */
         execv(args, args);
         perror(args);
         exit(1);
    }
    /*
   * 15. 返回从设备文件描述符
   *   
   */
    return(master);
}
      
/*
* finish - called when the "exit" input
*/
void finish(int sig)
{
    time_t clock;
    /*
   * 16. 恢复最初终端模式.
   */
    if (tcsetattr(0, TCSANOW, &origtty) < 0)
      perror("tcsetattr: stdin");
    /*
   * 打印程序结束信息.
   */
    time(&clock);
    fprintf(script, "\nScript finished at %s", ctime(&clock));
    printf("\nScript done, file is %s\n", filename);
    /*
   * 17. the end
   */
    fclose(script);
    close(master);
    exit(0);
}

ACTom 发表于 2012-5-2 11:40:45

原创内容
页: [1]
查看完整版本: 实现linux下script 命令