#include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_PTY_H #include #endif #ifdef HAVE_UTIL_H #include #endif #include #include #include "auto_fd.hh" using namespace std; class child_term { public: class error : public std::exception { public: error(int err) : e_err(err) { }; int e_err; }; child_term() { struct winsize ws; auto_fd slave; if (isatty(STDIN_FILENO) && tcgetattr(STDIN_FILENO, &this->ct_termios) == -1) { throw error(errno); } if (isatty(STDOUT_FILENO) && ioctl(STDOUT_FILENO, TIOCGWINSZ, &this->ct_winsize) == -1) { throw error(errno); } ws.ws_col = 80; ws.ws_row = 24; if (openpty(this->ct_master.out(), slave.out(), NULL, NULL, &ws) < 0) { throw error(errno); } if ((this->ct_child = fork()) == -1) throw error(errno); if (this->ct_child == 0) { this->ct_master.reset(); dup2(slave, STDIN_FILENO); dup2(slave, STDOUT_FILENO); setenv("TERM", "xterm-color", 1); } else { slave.reset(); } }; virtual ~child_term() { (void)this->wait_for_child(); if (isatty(STDIN_FILENO) && tcsetattr(STDIN_FILENO, TCSANOW, &this->ct_termios) == -1) { perror("tcsetattr"); } if (isatty(STDOUT_FILENO) && ioctl(STDOUT_FILENO, TIOCSWINSZ, &this->ct_winsize) == -1) { perror("ioctl"); } }; int wait_for_child(void) { int retval = -1; if (this->ct_child > 0) { kill(this->ct_child, SIGTERM); this->ct_child = -1; while (wait(&retval) < 0 && (errno == EINTR)); } return retval; }; bool is_child() { return this->ct_child == 0; }; pid_t get_child_pid() { return this->ct_child; }; int get_fd() { return this->ct_master; }; protected: pid_t ct_child; auto_fd ct_master; struct termios ct_termios; struct winsize ct_winsize; }; static int tty_raw(int fd) { struct termios attr[1]; if (tcgetattr(fd, attr) == -1) return -1; attr->c_lflag &= ~(ECHO | ICANON | IEXTEN); attr->c_iflag &= ~(ICRNL | INPCK | ISTRIP | IXON); attr->c_cflag &= ~(CSIZE | PARENB); attr->c_cflag |= (CS8); attr->c_oflag &= ~(OPOST); attr->c_cc[VMIN] = 1; attr->c_cc[VTIME] = 0; return tcsetattr(fd, TCSANOW, attr); } typedef enum { ET_READ, } expect_type_t; struct expect { expect_type_t e_type; union { char *b; } e_arg; }; typedef enum { CT_SLEEP, CT_WRITE, } command_type_t; struct command { command_type_t c_type; union { char *b; } c_arg; }; static struct { const char *sd_program_name; sig_atomic_t sd_looping; pid_t sd_child_pid; const char *sd_to_child_name; FILE *sd_to_child; const char *sd_from_child_name; FILE *sd_from_child; queue sd_replay; queue sd_expected; } scripty_data; static void dump_memory(FILE *dst, char *src, int len) { int lpc; for (lpc = 0; lpc < len; lpc++) { fprintf(dst, "%02x", src[lpc]); } } static char *hex2bits(const char *src) { int len, pos = sizeof(int); char *retval; len = strlen(src) / 2; retval = new char[sizeof(int) + len]; *((int *)retval) = len; while (src[0]) { int val; sscanf(src, "%2x", &val); src += 2; retval[pos] = (char)val; pos += 1; } return retval; } static void sigchld(int sig) { scripty_data.sd_looping = false; } static void sigpass(int sig) { kill(scripty_data.sd_child_pid, sig); } static void usage(void) { const char *usage_msg = "usage: %s [-h] [-t to_child] [-f from_child] -- \n" "\n" "Recorder for TTY I/O from a child process." "\n" "Options:\n" " -h Print this message, then exit.\n" " -t The file where any input send to the child process\n" " should be stored.\n" " -f The file where any output from the child process\n" " should be stored.\n"; fprintf(stderr, usage_msg, scripty_data.sd_program_name); } int main(int argc, char *argv[]) { int c, fd, retval = EXIT_SUCCESS; bool passout = true; FILE *file; scripty_data.sd_program_name = argv[0]; scripty_data.sd_looping = true; while ((c = getopt(argc, argv, "ht:f:r:e:n")) != -1) { switch (c) { case 'h': usage(); exit(retval); break; case 't': scripty_data.sd_to_child_name = optarg; break; case 'f': scripty_data.sd_from_child_name = optarg; break; case 'e': if ((file = fopen(optarg, "r")) == NULL) { fprintf(stderr, "error: cannot open %s\n", optarg); retval = EXIT_FAILURE; } else { char line[32 * 1024]; while (fgets(line, sizeof(line), file)) { char *sp; if (line[0] == '#' || (sp = strchr(line, ' ')) == NULL) { } else { struct expect exp; *sp = '\0'; sp += 1; if (strcmp(line, "read") == 0) { exp.e_type = ET_READ; exp.e_arg.b = hex2bits(sp); } else { fprintf(stderr, "error: unknown command -- %s\n", line); retval = EXIT_FAILURE; } scripty_data.sd_expected.push(exp); } } fclose(file); file = NULL; } break; case 'r': if ((file = fopen(optarg, "r")) == NULL) { fprintf(stderr, "error: cannot open %s\n", optarg); retval = EXIT_FAILURE; } else { char line[32 * 1024]; while (fgets(line, sizeof(line), file)) { char *sp; if (line[0] == '#' || (sp = strchr(line, ' ')) == NULL) { } else { struct command cmd; *sp = '\0'; sp += 1; if (strcmp(line, "sleep") == 0) { cmd.c_type = CT_SLEEP; } else if (strcmp(line, "write") == 0) { cmd.c_type = CT_WRITE; cmd.c_arg.b = hex2bits(sp); scripty_data.sd_replay.push(cmd); } else { fprintf(stderr, "error: unknown command -- %s\n", line); retval = EXIT_FAILURE; } } } fclose(file); file = NULL; } break; case 'n': passout = false; break; default: retval = EXIT_FAILURE; break; } } argc -= optind; argv += optind; if ((scripty_data.sd_to_child_name != NULL) && (scripty_data.sd_to_child = fopen(scripty_data.sd_to_child_name, "w")) == NULL) { fprintf(stderr, "error: unable to open %s -- %s\n", scripty_data.sd_to_child_name, strerror(errno)); retval = EXIT_FAILURE; } if (scripty_data.sd_from_child_name != NULL) { if (strcmp(scripty_data.sd_from_child_name, "-") == 0) { scripty_data.sd_from_child = stdout; } else if ((scripty_data.sd_from_child = fopen(scripty_data.sd_from_child_name, "w")) == NULL) { fprintf(stderr, "error: unable from open %s -- %s\n", scripty_data.sd_from_child_name, strerror(errno)); retval = EXIT_FAILURE; } } fd = open("/tmp/scripty.err", O_WRONLY|O_CREAT|O_APPEND, 0666); dup2(fd, STDERR_FILENO); close(fd); fprintf(stderr, "startup\n"); if (scripty_data.sd_to_child != NULL) fcntl(fileno(scripty_data.sd_to_child), F_SETFD, 1); if (scripty_data.sd_from_child != NULL) fcntl(fileno(scripty_data.sd_from_child), F_SETFD, 1); if (retval != EXIT_FAILURE) { child_term ct; if (ct.is_child()) { execvp(argv[0], argv); perror("execvp"); exit(-1); } else { int maxfd, out_len = 0, exp_pos = 0, exp_len = 0; bool got_expected = true; struct timeval last, now; char out_buffer[8192]; fd_set read_fds; char *exp_data = NULL; scripty_data.sd_child_pid = ct.get_child_pid(); signal(SIGINT, sigpass); signal(SIGTERM, sigpass); signal(SIGCHLD, sigchld); gettimeofday(&now, NULL); last = now; FD_ZERO(&read_fds); FD_SET(STDIN_FILENO, &read_fds); FD_SET(ct.get_fd(), &read_fds); fprintf(stderr, "goin in the loop\n"); tty_raw(STDIN_FILENO); if (!scripty_data.sd_expected.empty()) { struct expect exp = scripty_data.sd_expected.front(); scripty_data.sd_expected.pop(); switch (exp.e_type) { case ET_READ: exp_pos = sizeof(int); exp_len = *((int *)exp.e_arg.b) + sizeof(int); exp_data = exp.e_arg.b; break; } got_expected = false; } maxfd = max(STDIN_FILENO, ct.get_fd()); while (scripty_data.sd_looping) { fd_set ready_rfds = read_fds; struct timeval diff, to; int rc; to.tv_sec = 0; to.tv_usec = 10000; rc = select(maxfd + 1, &ready_rfds, NULL, NULL, &to); if (rc == 0) { if (exp_data != NULL && exp_pos == exp_len && !got_expected) { exp_data = NULL; if (!scripty_data.sd_expected.empty()) { struct expect exp = scripty_data.sd_expected.front(); delete [] exp_data; scripty_data.sd_expected.pop(); switch (exp.e_type) { case ET_READ: exp_pos = sizeof(int); exp_len = *((int *)exp.e_arg.b) + sizeof(int); exp_data = exp.e_arg.b; break; } } got_expected = true; } if (!scripty_data.sd_replay.empty() && got_expected) { struct command cmd = scripty_data.sd_replay.front(); int len; scripty_data.sd_replay.pop(); fprintf(stderr, "replay %d\n", scripty_data.sd_replay.size()); switch (cmd.c_type) { case CT_SLEEP: break; case CT_WRITE: len = *((int *)cmd.c_arg.b); write(ct.get_fd(), cmd.c_arg.b + sizeof(int), len); delete [] cmd.c_arg.b; break; } got_expected = false; } } else if (rc < 0) { switch (errno) { case EINTR: break; default: fprintf(stderr, "select %s\n", strerror(errno)); scripty_data.sd_looping = false; break; } } else { char buffer[1024]; gettimeofday(&now, NULL); timersub(&now, &last, &diff); if (FD_ISSET(STDIN_FILENO, &ready_rfds)) { rc = read(STDIN_FILENO, buffer, sizeof(buffer)); if (rc < 0) { scripty_data.sd_looping = false; } else if (rc == 0) { FD_CLR(STDIN_FILENO, &read_fds); } else { write(ct.get_fd(), buffer, rc); if (scripty_data.sd_to_child != NULL) { fprintf(scripty_data.sd_to_child, "sleep %ld.%06d\n" "write ", diff.tv_sec, diff.tv_usec); dump_memory(scripty_data.sd_to_child, buffer, rc); fprintf(scripty_data.sd_to_child, "\n"); } if (scripty_data.sd_from_child != NULL) { fprintf(stderr, "do write %d\n", out_len); fprintf(scripty_data.sd_from_child, "read "); dump_memory(scripty_data.sd_from_child, out_buffer, out_len); fprintf(scripty_data.sd_from_child, "\n"); fprintf(scripty_data.sd_from_child, "# write "); dump_memory(scripty_data.sd_from_child, buffer, rc); fprintf(scripty_data.sd_from_child, "\n"); out_len = 0; } } } if (FD_ISSET(ct.get_fd(), &ready_rfds)) { rc = read(ct.get_fd(), buffer, sizeof(buffer)); if (rc <= 0) { scripty_data.sd_looping = false; } else { if (passout) write(STDOUT_FILENO, buffer, rc); if (scripty_data.sd_from_child != NULL) { fprintf(stderr, "got out %d\n", rc); memcpy(&out_buffer[out_len], buffer, rc); out_len += rc; } if (exp_data != NULL) { int clen = min((exp_len - exp_pos), rc); fprintf(stderr, "cmp %d %d %d %d\n", exp_len, exp_pos, rc, clen); if (memcmp(&exp_data[exp_pos], buffer, clen) == 0) { exp_pos += clen; fprintf(stderr, "exp %d %d\n", exp_pos, clen); if (exp_pos == exp_len) { exp_data = NULL; if (!scripty_data.sd_expected.empty()) { struct expect exp = scripty_data.sd_expected.front(); delete [] exp_data; scripty_data.sd_expected.pop(); switch (exp.e_type) { case ET_READ: exp_pos = sizeof(int); exp_len = *((int *)exp.e_arg.b) + sizeof(int); exp_data = exp.e_arg.b; break; } } got_expected = true; } } else { scripty_data.sd_looping = false; retval = EXIT_FAILURE; } } } } } last = now; } } if (!scripty_data.sd_expected.empty()) retval = EXIT_FAILURE; retval = ct.wait_for_child() || retval; } if (scripty_data.sd_to_child != NULL) { fclose(scripty_data.sd_to_child); scripty_data.sd_to_child = NULL; } if (scripty_data.sd_from_child != NULL) { fclose(scripty_data.sd_from_child); scripty_data.sd_from_child = NULL; } return retval; }