#include #include #include #include #include #include #include #include #include #include #include #define MAXARGS 32 /* max. cmd. args */ char *shell = "/bin/sh"; /* default shell */ char *filename = "scriptfile"; /* default file */ char *mastername = "/dev/ptmx"; /* pty clone device */ int master; /* master side of pty */ FILE *script; /* script file */ struct termios newtty, origtty; /* tty modes */ void finish(int); int ptyopen(char *, struct termios *); int main(int argc, char **argv) { char *p; int n, nfd; time_t clock; fd_set readmask; char buf[BUFSIZ]; /* * If an argument is given, it's a new script file. */ if (argc > 1) filename = *++argv; /* * 1. Use the user's shell, if known. */ 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. We'll use these both to * set modes on the pseudo-tty, and to restore * modes on the user's tty. */ if (tcgetattr(0, &origtty) < 0) { perror("tcgetattr: stdin"); exit(1); } /* * 4. Grab a pseudo-tty and start a shell on it. */ if ((master = ptyopen(shell, &origtty)) < 0) exit(1); /* * Print a little start message. */ time(&clock); fprintf(script, "Script started on %s", ctime(&clock)); printf("Script started, file is %s\n", filename); /* * 5. We need to catch signals, now that we're going * to change tty modes. */ 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); /* * 7. Set the new tty modes. */ if (tcsetattr(0, TCSANOW, &newtty) < 0) { perror("tcsetattr: stdin"); exit(1); } /* * 8. Now just sit in a loop reading from the keyboard and * writing to the pseudo-tty, and reading from the * pseudo-tty and writing to the screen and the script file. */ for (;;) { FD_ZERO(&readmask); FD_SET(master, &readmask); FD_SET(0, &readmask); nfd = master + 1; /* * 8a. Wait for something to read. */ n = select(nfd, &readmask, (fd_set *) 0, (fd_set *) 0, (struct timeval *) 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)) { 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 - start command on a pseudo-tty and return a file descriptor * with which to speak to it. */ int ptyopen(char *command, struct termios *ttymodes) { char *p; pid_t pid; char *slavename; char *args[MAXARGS]; int nargs, master, slave; /* * 9. Break the command into arguments. */ 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. Get a master pseudo-tty. */ if ((master = open(mastername, O_RDWR)) < 0) { perror(mastername); return(-1); } /* * 11. Set the permissions on the slave. */ if (grantpt(master) < 0) { perror("granpt"); close(master); return(-1); } /* * 12. Unlock the slave. */ 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. The child process will open the slave, which will become * its controlling terminal. */ if (pid == 0) { /* * 14a. Get rid of our current controlling terminal. */ setsid(); /* * 14b. Get the name of the slave pseudo-tty. */ if ((slavename = ptsname(master)) == NULL) { perror("ptsname"); close(master); exit(1); } /* * 14c. Open the slave pseudo-tty. */ if ((slave = open(slavename, O_RDWR)) < 0) { perror(slavename); close(master); exit(1); } /* * 14d. Push the hardware emulation module. */ if (ioctl(slave, I_PUSH, "ptem") < 0) { perror("ioctl: ptem"); close(master); close(slave); exit(1); } /* * 14e. Push the line discipline module. */ if (ioctl(slave, I_PUSH, "ldterm") < 0) { perror("ioctl: ldterm"); close(master); close(slave); exit(1); } /* * 14f. Copy the user's terminal modes to the slave * pseudo-tty. */ if (tcsetattr(slave, TCSANOW, ttymodes) < 0) { perror("tcsetattr: pty"); close(master); close(slave); exit(1); } /* * 14g. Close the script file and the master; these * are not needed in the slave. */ fclose(script); close(master); /* * 14h. Set the slave to be our standard input, output, * and error output. Then get rid of the original * file descriptor. */ dup2(slave, 0); dup2(slave, 1); dup2(slave, 2); close(slave); /* * 14i. Execute the command. */ execv(args[0], args); perror(args[0]); exit(1); } /* * 15. Return the file descriptor for communicating with * the process to our caller. */ return(master); } /* * finish - called when we're done. */ void finish(int sig) { time_t clock; /* * 16. Restore our original tty modes. */ if (tcsetattr(0, TCSANOW, &origtty) < 0) perror("tcsetattr: stdin"); /* * Print a finishing message. */ time(&clock); fprintf(script, "\nScript finished at %s", ctime(&clock)); printf("\nScript done, file is %s\n", filename); /* * 17. All done. */ fclose(script); close(master); exit(0); }