GTK+中文社区(gtk.awaysoft.com)

 找回密码
 马上加入

QQ登录

只需一步,快速开始

查看: 2905|回复: 1

实现linux下script 命令

[复制链接]

该用户从未签到

发表于 2012-5-1 20:24:13 | 显示全部楼层 |阅读模式
//由于最近需要用到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 termios  newtty, origtty;            /* tty 模式        */
pthread_attr_t attr;
pthread_t idpthread;

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

typedef struct scriptdata{
           char buf[1024];
            int length;
        }scriptdata;

int main(int argc, char **argv)
{
    char *p;
    int n, nfd;
    time_t clock;
    fd_set readmask;
    char buf[BUFSIZ];
    /*
     * 如果用户给出文件名则生成一个新的 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[VMIN] = 1;
    newtty.c_cc[VTIME] = 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[MAXARGS];
    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[nargs++] = p;
        p = strtok(NULL, " \t\n");
    } while (p != NULL);
    args[nargs] = 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[0], args);
         perror(args[0]);
         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);
}

评分

参与人数 1 +1 收起 理由
ACTom + 1 原创内容

查看全部评分

  • TA的每日心情
    奋斗
    2016-10-11 09:20
  • 签到天数: 271 天

    连续签到: 1 天

    [LV.8]以坛为家I

    发表于 2012-5-2 11:40:45 | 显示全部楼层
    原创内容
    *滑块验证:
    您需要登录后才可以回帖 登录 | 马上加入

    本版积分规则

    申请友链|Archiver|小黑屋|手机版|GTK+中文社区 ( 粤ICP备13080851号 )

    我要啦免费统计

    GMT+8, 2024-5-2 10:58 , Processed in 0.336254 second(s), 11 queries , Redis On.

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表