ttyrec.c (12693B)
1 /* 2 * Copyright (c) 1980 Regents of the University of California. 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. All advertising materials mentioning features or use of this software 14 * must display the following acknowledgement: 15 * This product includes software developed by the University of 16 * California, Berkeley and its contributors. 17 * 4. Neither the name of the University nor the names of its contributors 18 * may be used to endorse or promote products derived from this software 19 * without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 * SUCH DAMAGE. 32 */ 33 34 /* 1999-02-22 Arkadiusz Mi¶kiewicz <misiek@misiek.eu.org> 35 * - added Native Language Support 36 */ 37 38 /* 2000-12-27 Satoru Takabayashi <satoru@namazu.org> 39 * - modify `script' to create `ttyrec'. 40 */ 41 42 /* 2012-10-03 Antoine Amarilli <a3nm@a3nm.net> 43 * - add -f and -l 44 */ 45 46 /* 47 * script 48 */ 49 #include <sys/types.h> 50 #include <sys/stat.h> 51 #include <termios.h> 52 #include <sys/ioctl.h> 53 #include <sys/time.h> 54 #include <sys/file.h> 55 #include <sys/signal.h> 56 #include <stdio.h> 57 #include <time.h> 58 #include <unistd.h> 59 #include <string.h> 60 #include <stdlib.h> 61 62 #if defined(SVR4) 63 #include <fcntl.h> 64 #include <stropts.h> 65 #endif /* SVR4 */ 66 67 #include <sys/time.h> 68 #include "ttyrec.h" 69 #include "io.h" 70 71 #define HAVE_inet_aton 72 #define HAVE_scsi_h 73 #define HAVE_kd_h 74 75 #define _(FOO) FOO 76 77 #ifdef HAVE_openpty 78 #include <libutil.h> 79 #endif 80 81 #if defined(SVR4) && !defined(CDEL) 82 #if defined(_POSIX_VDISABLE) 83 #define CDEL _POSIX_VDISABLE 84 #elif defined(CDISABLE) 85 #define CDEL CDISABLE 86 #else /* not _POSIX_VISIBLE && not CDISABLE */ 87 #define CDEL 255 88 #endif /* not _POSIX_VISIBLE && not CDISABLE */ 89 #endif /* SVR4 && ! CDEL */ 90 91 void done(void); 92 void fail(void); 93 void fixtty(void); 94 void getmaster(void); 95 void getslave(void); 96 void doinput(void); 97 void dooutput(void); 98 void doshell(const char*); 99 void spawnshell(); 100 void dowrites(Header, int, char*); 101 102 char *shell; 103 FILE *fscript; 104 int master; 105 int slave; 106 int child; 107 int subchild; 108 char *fname; 109 110 struct termios tt; 111 struct winsize win; 112 int lb; 113 int l; 114 #if !defined(SVR4) 115 #ifndef HAVE_openpty 116 char line[] = "/dev/ptyXX"; 117 #endif 118 #endif /* !SVR4 */ 119 int aflg; 120 int uflg; 121 int force; 122 int limit; 123 char *command = NULL; 124 125 static void 126 resize(int dummy) { 127 /* transmit window change information to the child */ 128 (void) ioctl(0, TIOCGWINSZ, (char *)&win); 129 (void) ioctl(master, TIOCSWINSZ, (char *)&win); 130 } 131 132 int 133 main(argc, argv) 134 int argc; 135 char *argv[]; 136 { 137 struct sigaction sa; 138 extern int optind; 139 int ch; 140 void finish(); 141 char *getenv(); 142 143 while ((ch = getopt(argc, argv, "auefl:h?")) != EOF) 144 switch((char)ch) { 145 case 'a': 146 aflg++; 147 break; 148 case 'u': 149 uflg++; 150 break; 151 case 'e': 152 command = strdup(optarg); 153 break; 154 case 'l': 155 limit = atoi(optarg); 156 break; 157 case 'f': 158 force = 1; 159 break; 160 case 'h': 161 case '?': 162 default: 163 fprintf(stderr, _("usage: ttyrec [-u] [-e command] [-a] [-f] [-l limit] [file]\n")); 164 exit(1); 165 } 166 argc -= optind; 167 argv += optind; 168 169 if (argc > 0) 170 fname = argv[0]; 171 else 172 fname = "ttyrecord"; 173 174 shell = getenv("SHELL"); 175 if (shell == NULL) 176 shell = "/bin/sh"; 177 178 if ((fscript = fopen(fname, aflg ? "a" : "w")) == NULL) { 179 if (!force) perror(fname); 180 fail(); 181 } 182 setbuf(fscript, NULL); 183 184 getmaster(); 185 fixtty(); 186 187 sigemptyset(&sa.sa_mask); 188 sa.sa_flags = 0; 189 sa.sa_handler = finish; 190 sigaction(SIGCHLD, &sa, NULL); 191 child = fork(); 192 if (child < 0) { 193 if (!force) perror("fork"); 194 fail(); 195 } 196 if (child == 0) { 197 subchild = child = fork(); 198 if (child < 0) { 199 if (!force) perror("fork"); 200 fail(); 201 } 202 if (child) { 203 sa.sa_flags = SA_RESTART; 204 sigaction(SIGCHLD, &sa, NULL); 205 dooutput(); 206 } else 207 doshell(command); 208 } 209 sa.sa_handler = resize; 210 sa.sa_flags = SA_RESTART; 211 sigaction(SIGWINCH, &sa, NULL); 212 doinput(); 213 214 return 0; 215 } 216 217 void 218 doinput() 219 { 220 register int cc; 221 char ibuf[BUFSIZ]; 222 223 (void) fclose(fscript); 224 #ifdef HAVE_openpty 225 (void) close(slave); 226 #endif 227 while ((cc = read(0, ibuf, BUFSIZ)) > 0) 228 (void) write(master, ibuf, cc); 229 usleep(10); // TODO ugly way to avoid a race condition 230 done(); 231 } 232 233 #include <sys/wait.h> 234 235 void 236 finish() 237 { 238 #if defined(SVR4) 239 int status; 240 #else /* !SVR4 */ 241 union wait status; 242 #endif /* !SVR4 */ 243 register int pid; 244 245 while ((pid = wait3((int *)&status, WNOHANG, 0)) > 0) 246 if (pid == child) 247 break; 248 } 249 250 struct linebuf { 251 char str[BUFSIZ + 1]; /* + 1 for an additional NULL character.*/ 252 int len; 253 }; 254 255 256 void 257 check_line (const char *line) 258 { 259 static int uuencode_mode = 0; 260 static FILE *uudecode; 261 262 if (uuencode_mode == 1) { 263 fprintf(uudecode, "%s", line); 264 if (strcmp(line, "end\n") == 0) { 265 pclose(uudecode); 266 uuencode_mode = 0; 267 } 268 } else { 269 int dummy; char dummy2[BUFSIZ]; 270 if (sscanf(line, "begin %o %s", &dummy, dummy2) == 2) { 271 /* 272 * uuencode line found! 273 */ 274 uudecode = popen("uudecode", "w"); 275 fprintf(uudecode, "%s", line); 276 uuencode_mode = 1; 277 } 278 } 279 } 280 281 static void 282 uu_check_output(const char *str, int len) 283 { 284 static struct linebuf lbuf = {"", 0}; 285 int i; 286 287 for (i = 0; i < len; i++) { 288 if (lbuf.len < BUFSIZ) { 289 lbuf.str[lbuf.len] = str[i]; 290 if (lbuf.str[lbuf.len] == '\r') { 291 lbuf.str[lbuf.len] = '\n'; 292 } 293 lbuf.len++; 294 if (lbuf.str[lbuf.len - 1] == '\n') { 295 if (lbuf.len > 1) { /* skip a blank line. */ 296 lbuf.str[lbuf.len] = '\0'; 297 check_line(lbuf.str); 298 } 299 lbuf.len = 0; 300 } 301 } else {/* buffer overflow */ 302 lbuf.len = 0; 303 } 304 } 305 } 306 307 static int 308 check_output(char *str, int len) 309 { 310 char *p; 311 312 /* If we see query string, remove it */ 313 /* ESC [ > 0 c : Send Device Attributes */ 314 if (len >= 5 && (p = strstr(str, "\e[>0c")) != NULL) { 315 if (len == 5) 316 return 0; 317 memmove(p, p+5, len-5+1-(p-str)); 318 return len-5; 319 } 320 321 return len; 322 } 323 324 void 325 dowrites(Header h, int cc, char *obuf) { 326 if ((cc = check_output(obuf, cc))) { 327 (void) write_header(fscript, &h); 328 (void) fwrite(obuf, 1, cc, fscript); 329 } 330 } 331 332 void 333 dooutput() 334 { 335 int cc; 336 int lcc = 0; 337 char obuf[BUFSIZ]; 338 char lobuf[BUFSIZ]; 339 time_t lastsec = 0; 340 int readsec = 0; 341 Header h; 342 Header lh; 343 344 setbuf(stdout, NULL); 345 (void) close(0); 346 #ifdef HAVE_openpty 347 (void) close(slave); 348 #endif 349 for (;;) { 350 351 cc = read(master, obuf, BUFSIZ); 352 if (cc <= 0) { 353 if (lcc) { 354 // do the last chunk of ignored data 355 dowrites(lh, lcc, lobuf); 356 lcc = 0; 357 } 358 break; 359 } 360 if (uflg) 361 uu_check_output(obuf, cc); 362 h.len = cc; 363 gettimeofday(&h.tv, NULL); 364 (void) write(1, obuf, cc); 365 if (limit) { 366 if (h.tv.tv_sec == lastsec) { 367 if (readsec >= limit) { 368 // TODO: avoid this by cycling between two buffers 369 strncpy(lobuf, obuf, BUFSIZ); 370 lcc = cc; 371 lh = h; 372 continue; 373 } 374 readsec += cc; 375 } else { 376 if (lcc) { 377 // do the last chunk of ignored data 378 dowrites(lh, lcc, lobuf); 379 lcc = 0; 380 } 381 lastsec = h.tv.tv_sec; 382 readsec = cc; 383 } 384 } 385 dowrites(h, cc, obuf); 386 } 387 done(); 388 } 389 390 void 391 doshell(const char* command) 392 { 393 /*** 394 int t; 395 396 t = open(_PATH_TTY, O_RDWR); 397 if (t >= 0) { 398 (void) ioctl(t, TIOCNOTTY, (char *)0); 399 (void) close(t); 400 } 401 ***/ 402 getslave(); 403 (void) close(master); 404 (void) fclose(fscript); 405 (void) dup2(slave, 0); 406 (void) dup2(slave, 1); 407 (void) dup2(slave, 2); 408 (void) close(slave); 409 410 spawnshell(); 411 perror(shell); 412 fail(); 413 } 414 415 void 416 fixtty() 417 { 418 struct termios rtt; 419 420 rtt = tt; 421 #if defined(SVR4) 422 #if !defined(XCASE) 423 #define XCASE 0 424 #endif 425 rtt.c_iflag = 0; 426 rtt.c_lflag &= ~(ISIG|ICANON|XCASE|ECHO|ECHOE|ECHOK|ECHONL); 427 rtt.c_oflag = OPOST; 428 rtt.c_cc[VINTR] = CDEL; 429 rtt.c_cc[VQUIT] = CDEL; 430 rtt.c_cc[VERASE] = CDEL; 431 rtt.c_cc[VKILL] = CDEL; 432 rtt.c_cc[VEOF] = 1; 433 rtt.c_cc[VEOL] = 0; 434 #else /* !SVR4 */ 435 cfmakeraw(&rtt); 436 rtt.c_lflag &= ~ECHO; 437 #endif /* !SVR4 */ 438 (void) tcsetattr(0, TCSAFLUSH, &rtt); 439 } 440 441 void 442 fail() 443 { 444 if (force) { 445 spawnshell(); 446 perror(shell); 447 } 448 (void) kill(0, SIGTERM); 449 done(); 450 } 451 452 void 453 done() 454 { 455 if (subchild) { 456 (void) fclose(fscript); 457 (void) close(master); 458 } else { 459 (void) tcsetattr(0, TCSAFLUSH, &tt); 460 } 461 exit(0); 462 } 463 464 void 465 getmaster() 466 { 467 #if defined(SVR4) 468 (void) tcgetattr(0, &tt); 469 (void) ioctl(0, TIOCGWINSZ, (char *)&win); 470 if ((master = open("/dev/ptmx", O_RDWR)) < 0) { 471 if (!force) perror("open(\"/dev/ptmx\", O_RDWR)"); 472 fail(); 473 } 474 #else /* !SVR4 */ 475 #ifdef HAVE_openpty 476 (void) tcgetattr(0, &tt); 477 (void) ioctl(0, TIOCGWINSZ, (char *)&win); 478 if (openpty(&master, &slave, NULL, &tt, &win) < 0) { 479 fprintf(stderr, _("openpty failed\n")); 480 fail(); 481 } 482 #else 483 #ifdef HAVE_getpt 484 if ((master = getpt()) < 0) { 485 if (!force) perror("getpt()"); 486 fail(); 487 } 488 #else 489 char *pty, *bank, *cp; 490 struct stat stb; 491 492 pty = &line[strlen("/dev/ptyp")]; 493 for (bank = "pqrs"; *bank; bank++) { 494 line[strlen("/dev/pty")] = *bank; 495 *pty = '0'; 496 if (stat(line, &stb) < 0) 497 break; 498 for (cp = "0123456789abcdef"; *cp; cp++) { 499 *pty = *cp; 500 master = open(line, O_RDWR); 501 if (master >= 0) { 502 char *tp = &line[strlen("/dev/")]; 503 int ok; 504 505 /* verify slave side is usable */ 506 *tp = 't'; 507 ok = access(line, R_OK|W_OK) == 0; 508 *tp = 'p'; 509 if (ok) { 510 (void) tcgetattr(0, &tt); 511 (void) ioctl(0, TIOCGWINSZ, 512 (char *)&win); 513 return; 514 } 515 (void) close(master); 516 } 517 } 518 } 519 fprintf(stderr, _("Out of pty's\n")); 520 fail(); 521 #endif /* not HAVE_getpt */ 522 #endif /* not HAVE_openpty */ 523 #endif /* !SVR4 */ 524 } 525 526 void 527 getslave() 528 { 529 #if defined(SVR4) 530 (void) setsid(); 531 grantpt( master); 532 unlockpt(master); 533 if ((slave = open((const char *)ptsname(master), O_RDWR)) < 0) { 534 if (!force) perror("open(fd, O_RDWR)"); 535 fail(); 536 } 537 if (isastream(slave)) { 538 if (ioctl(slave, I_PUSH, "ptem") < 0) { 539 if (!force) perror("ioctl(fd, I_PUSH, ptem)"); 540 fail(); 541 } 542 if (ioctl(slave, I_PUSH, "ldterm") < 0) { 543 if (!force) perror("ioctl(fd, I_PUSH, ldterm)"); 544 fail(); 545 } 546 #ifndef _HPUX_SOURCE 547 if (ioctl(slave, I_PUSH, "ttcompat") < 0) { 548 if (!force) perror("ioctl(fd, I_PUSH, ttcompat)"); 549 fail(); 550 } 551 #endif 552 } 553 (void) tcsetattr(slave, TCSAFLUSH, &tt); 554 (void) ioctl(slave, TIOCSWINSZ, (char *)&win); 555 (void) ioctl(slave, TIOCSCTTY, 0); 556 #else /* !SVR4 */ 557 #ifndef HAVE_openpty 558 line[strlen("/dev/")] = 't'; 559 slave = open(line, O_RDWR); 560 if (slave < 0) { 561 if (!force) perror(line); 562 fail(); 563 } 564 (void) tcsetattr(slave, TCSAFLUSH, &tt); 565 (void) ioctl(slave, TIOCSWINSZ, (char *)&win); 566 #endif 567 (void) setsid(); 568 (void) ioctl(slave, TIOCSCTTY, 0); 569 #endif /* SVR4 */ 570 } 571 572 void 573 spawnshell() 574 { 575 force = 0; 576 577 if (!command) { 578 execl(shell, strrchr(shell, '/') + 1, "-i", NULL); 579 } else { 580 execl(shell, strrchr(shell, '/') + 1, "-c", command, NULL); 581 } 582 }