|
//由于最近需要用到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);
} |
评分
-
查看全部评分
|