Linux script and scriptreplay(三)
2016-05-04 08:02
513 查看
前言
在上面两篇博客中,介绍了script和scriptreplay的使用以及原理,这节中就来分析一下script的原理。在分析script之前需要了解终端的概念
终端是一种字符设备,它有多种类型,通常使用tty来简称各种类型的终端设备。 设备名放在特殊目录/dev/下,终端特殊设备文件一般有以下几种:1.串行端口终端(/dev/ttySn)
串行端口终端是使用计算机串行端口连接的终端设备。计算机把每个串行端口看做是一个字符设备,有段时间这些串行端口设备通常称为终端设备,那是它们的最大用途就是用来连接终端。2.伪终端
伪终端(Pseudo Terminal)是成对的逻辑终端设备,即伪终端是由Master和Slave共同组成的,通常用于实现网络登陆功能,如ssh,telnet等。伪终端一般分为两类:(1)BSD 伪终端
BSD伪终端中,定义/dev/pty[p-za-e][0-9a-f] 是Master; /dev/tty[p-za-e][0-9a-f] 是Slave,他们都是配对好的,即/dev/ptyp1对应/dev/ttyp1,共同伪终端。这种看似简单的定义使得编程实现很困难,因为我们需要遍历/dev/目录,一个一个的尝试才能找到一对合适的终端(2)Unix 98伪终端
与BSD伪终端不同,始终以/dev/ptmx作为Master的复制设备,每次打开/dev/ptmx才能得到一个master设备的fd,同时在/dev/pts目录下得到一个Slave设备,这种方式下,编程就变得简单了许多。3.控制终端
如果当前进程有控制终端的话,那么/dev/tty就是当前进程的控制终端的特殊文件。可以使用ps -ef来查看进程与哪个控制终端相连。对于你登陆的shell,/dev/tty就是你使用的终端。你可以使用
tty命令查看它具体对应哪个实际终端设备
4.控制台终端
在linux系统中,计算机显示器通常被称为控制台终端。它仿真了类型为linux的一种终端(TERM=Linux),并且有一些设备特殊文件与之相关联:tty[1-6]使用Alt+[F1—F6]组合键时,我们就可以切换到tty2、tty3等上面去。tty1–tty6等称为虚拟终端,而tty0则是当前所使用虚拟终端的一个别名,系统所产生的信息会发送到该终端上。因此不管当前正在使用哪个虚拟终端,系统信息都会发送到控制台终端上。你可以登录到不同的虚拟终端上去,因而可以让系统同时有几个不同的会话期存在。script关键代码分析
getmaster
void getmaster() { //此种方式为Unix98伪终端,直接调用openpty函数获取Master和Slave #if HAVE_LIBUTIL && HAVE_PTY_H tcgetattr(STDIN_FILENO, &tt); ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&win); if (openpty(&master, &slave, NULL, &tt, &win) < 0) { warn(_("openpty failed")); fail(); } #else //BSD伪终端,需要便利/dev目录,找到一对可用的Master和Slave char *pty, *bank, *cp; struct stat stb; pty = &line[strlen("/dev/ptyp")]; for (bank = "pqrs"; *bank; bank++) { line[strlen("/dev/pty")] = *bank; *pty = '0'; if (stat(line, &stb) < 0) break; for (cp = "0123456789abcdef"; *cp; cp++) { *pty = *cp; master = open(line, O_RDWR); if (master >= 0) { char *tp = &line[strlen("/dev/")]; int ok; /* verify slave side is usable */ *tp = 't'; ok = access(line, R_OK|W_OK) == 0; *tp = 'p'; if (ok) { tcgetattr(STDIN_FILENO, &tt); ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&win); return; } close(master); master = -1; } } } master = -1; warn(_("out of pty's")); fail(); #endif /* not HAVE_LIBUTIL */ }
dooutput
void dooutput(FILE *timingfd) { ssize_t cc; time_t tvec; char obuf[BUFSIZ]; struct timeval tv; double oldtime=time(NULL), newtime; int flgs = 0; ssize_t wrt; ssize_t fwrt; close(STDIN_FILENO); #ifdef HAVE_LIBUTIL close(slave); #endif tvec = time((time_t *)NULL); my_strftime(obuf, sizeof obuf, "%c\n", localtime(&tvec)); //输出开始信息到typescript文件 fprintf(fscript, _("Script started on %s"), obuf); do { if (die && flgs == 0) { /* ..child is dead, but it doesn't mean that there is * nothing in buffers. */ flgs = fcntl(master, F_GETFL, 0); if (fcntl(master, F_SETFL, (flgs | O_NONBLOCK)) == -1) break; } if (tflg) gettimeofday(&tv, NULL); errno = 0; //从master中读取数据 cc = read(master, obuf, sizeof (obuf)); if (die && errno == EINTR && cc <= 0) /* read() has been interrupted by SIGCHLD, try it again * with O_NONBLOCK */ continue; if (cc <= 0) break; //计算上次输出到本次输出的时间间隔 if (tflg) { newtime = tv.tv_sec + (double) tv.tv_usec / 1000000; fprintf(timingfd, "%f %zd\n", newtime - oldtime, cc); oldtime = newtime; } //将从master中读到的数据输出到stdout wrt = write(STDOUT_FILENO, obuf, cc); if (wrt < 0) { warn (_("write failed")); fail(); } //将数据写入到typescript文件中 fwrt = fwrite(obuf, 1, cc, fscript); if (fwrt < cc) { warn (_("cannot write script file 10291 ")); fail(); } if (fflg) fflush(fscript); } while(1); if (flgs) fcntl(master, F_SETFL, flgs); done(); }
doinput
void doinput() { ssize_t cc; char ibuf[BUFSIZ]; fclose(fscript); //从stdin中读取输入,写入到master中 while (die == 0) { if ((cc = read(STDIN_FILENO, ibuf, BUFSIZ)) > 0) { ssize_t wrt = write(master, ibuf, cc); if (wrt < 0) { warn (_("write failed")); fail(); } } else if (cc < 0 && errno == EINTR && resized) resized = 0; else break; } done(); }
script程序流程梳理
Created with Raphaël 2.1.0开始读取参数获取主设备fork子进程是否是子进程?fork子进程是否是子进程dooutput(记录数据以及输出)结束doshell(execl启动一个shell)doinput(循环读取输入)yesnoyesnogolang实现一个Unix98伪终端的script程序
package main import ( "bufio" "flag" "fmt" "os" "os/exec" "syscall" "time" "unsafe" ) // #include <termios.h> import "C" const ( PtyMaster = "/dev/ptmx" ) var ( TimingFile = flag.String("t", "timing", "timing file") ) func main() { flag.Parse() typeScript := "typescript" fmt.Println(os.Args, len(os.Args), *TimingFile) args := flag.Args() if len(args) != 1 { fmt.Printf("[usage]\n\n%s -t timingfile typescript\n", os.Args[0]) os.Exit(1) } typeScript = args[0] script, err := os.OpenFile(typeScript, os.O_RDWR|os.O_CREATE, 0664) if err != nil { fmt.Printf("Open Typescript %s Error:%v", typeScript, err) os.Exit(1) } timing, err := os.OpenFile(*TimingFile, os.O_RDWR|os.O_CREATE, 0664) if err != nil { fmt.Printf("Open Timing %s Error:%v", TimingFile, err) os.Exit(1) } cmd := exec.Command("/bin/bash") p, err := start(cmd) if err != nil { fmt.Printf("start error:%v", err) os.Exit(1) } err = fixtty() if err != nil { fmt.Printf("fixtty error:%v", err) os.Exit(1) } // 循环从master中读取输出 go func(p, timing, script *os.File) { fmt.Fprintf(script, "Script started on %s\n", time.Now()) oldTime := time.Now() for { // fmt.Println("2") buf := make([]byte, 1024) n, err := p.Read(buf) if err != nil { break } newTime := time.Now() delay := newTime.Sub(oldTime) oldTime = newTime //print data to stdout fmt.Print(string(buf[:n])) //save data to file fmt.Fprintf(timing, "%f %d\n", float64(delay)/1e9, n) fmt.Fprint(script, string(buf[:n])) } done(script) os.Exit(0) }(p, timing, script) //循环从标准输入中读取输入 for { buf := make([]byte, 1024) rd := bufio.NewReader(os.Stdin) n, err := rd.Read(buf) if err != nil { fmt.Printf("outside:%v\n", err) // close master p.Close() break } p.Write(buf[:n]) } } func done(f *os.File) { fmt.Fprintf(f, "Script done on %s\n", time.Now()) fmt.Printf("Script done, file is %s", f.Name()) } // fixtty 将stdin对应的终端设备设置为不回显,非规范方式 func fixtty() error { var term Termios e := tcget(os.Stdin.Fd(), &term) if e != 0 { return e } term.Lflag &^= (syscall.ECHO | syscall.ICANON) e = tcset(os.Stdin.Fd(), &term) if e != 0 { return e } return nil } //start 启动子进程 func start(cmd *exec.Cmd) (pty *os.File, err error) { m, s, e := getPty() if e != nil { return nil, e } defer s.Close() cmd.Stdin = s cmd.Stdout = s cmd.Stderr = s cmd.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true} err = cmd.Start() if err != nil { m.Close() return nil, err } return m, err } //getPty 获取一对Pty设备 func getPty() (master, slave *os.File, err error) { m, e := os.OpenFile(PtyMaster, os.O_RDWR, 0) if e != nil { return nil, nil, e } sname, e := ptsName(m) if e != nil { return nil, nil, e } e = unlockpt(m) if e != nil { return nil, nil, e } s, e := os.OpenFile(sname, os.O_RDWR, 0) if e != nil { return nil, nil, e } return m, s, nil } //ioctl 模拟c中的ioctl函数 func ioctl(fd, cmd, ptr uintptr) error { _, _, e := syscall.Syscall(syscall.SYS_IOCTL, fd, cmd, ptr) if e != 0 { return e } return nil } //ptsName 获取master对应的slave func ptsName(f *os.File) (string, error) { var n int if err := ioctl(f.Fd(), syscall.TIOCGPTN, uintptr(unsafe.Pointer(&n))); err != nil { return "", err } return fmt.Sprintf("/dev/pts/%d", n), nil } //unlockpt 释放pty锁 func unlockpt(f *os.File) error { var n int return ioctl(f.Fd(), syscall.TIOCSPTLCK, uintptr(unsafe.Pointer(&n))) } // Termios is the Unix API for terminal I/O. // It is passthrough for syscall.Termios in order to make it portable with // other platforms where it is not available or handled differently. type Termios syscall.Termios // func cfmakeraw() func tcget(fd uintptr, p *Termios) syscall.Errno { ret, err := C.tcgetattr(C.int(fd), (*C.struct_termios)(unsafe.Pointer(p))) if ret != 0 { return err.(syscall.Errno) } return 0 } func tcset(fd uintptr, p *Termios) syscall.Errno { ret, err := C.tcsetattr(C.int(fd), C.TCSANOW, (*C.struct_termios)(unsafe.Pointer(p))) if ret != 0 { return err.(syscall.Errno) } return 0 }
相关文章推荐
- Linux socket 初步
- android wifi 无线调试
- Linux Kernel 4.0 RC5 发布!
- linux lsof详解
- linux 文件权限
- Linux 执行数学运算
- 10 篇对初学者和专家都有用的 Linux 命令教程
- Linux 与 Windows 对UNICODE 的处理方式
- Ubuntu12.04下QQ完美走起啊!走起啊!有木有啊!
- 解決Linux下Android开发真机调试设备不被识别问题
- 运维入门
- 运维提升
- Linux 自检和 SystemTap
- Ubuntu Linux使用体验
- c语言实现hashmap(转载)
- Linux 信号signal处理机制
- linux下mysql添加用户
- Scientific Linux 5.5 图形安装教程