1 /* 2 * SPDX-License-Identifier: BSD-3-Clause 3 * 4 * Copyright (c) 2010, 2012 David E. O'Brien 5 * Copyright (c) 1980, 1992, 1993 6 * The Regents of the University of California. All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 3. Neither the name of the University nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 * SUCH DAMAGE. 31 */ 32 33 #include <sys/param.h> 34 #ifndef lint 35 static const char copyright[] = 36 "@(#) Copyright (c) 1980, 1992, 1993\n\ 37 The Regents of the University of California. All rights reserved.\n"; 38 #endif 39 40 #include <sys/wait.h> 41 #include <sys/stat.h> 42 #include <sys/ioctl.h> 43 #include <sys/time.h> 44 #include <sys/queue.h> 45 #include <sys/uio.h> 46 #include <sys/endian.h> 47 #include <dev/filemon/filemon.h> 48 49 #include <err.h> 50 #include <errno.h> 51 #include <fcntl.h> 52 #include <libutil.h> 53 #include <paths.h> 54 #include <signal.h> 55 #include <stdio.h> 56 #include <stdlib.h> 57 #include <string.h> 58 #include <termios.h> 59 #include <unistd.h> 60 61 #define DEF_BUF 65536 62 63 struct stamp { 64 uint64_t scr_len; /* amount of data */ 65 uint64_t scr_sec; /* time it arrived in seconds... */ 66 uint32_t scr_usec; /* ...and microseconds */ 67 uint32_t scr_direction; /* 'i', 'o', etc (also indicates endianness) */ 68 }; 69 70 struct buf_elm { 71 TAILQ_ENTRY(buf_elm) link; 72 size_t rpos; 73 size_t len; 74 char ibuf[]; 75 }; 76 77 static FILE *fscript; 78 static int master, slave; 79 static int child; 80 static const char *fname; 81 static char *fmfname; 82 static int fflg, qflg, ttyflg; 83 static int usesleep, rawout, showexit; 84 static TAILQ_HEAD(, buf_elm) obuf_list = TAILQ_HEAD_INITIALIZER(obuf_list); 85 86 static struct termios tt; 87 88 #ifndef TSTAMP_FMT 89 /* useful for tool and human reading */ 90 # define TSTAMP_FMT "%n@ %s [%Y-%m-%d %T]%n" 91 #endif 92 static const char *tstamp_fmt = TSTAMP_FMT; 93 static int tflg; 94 95 static void done(int) __dead2; 96 static void doshell(char **); 97 static void finish(void); 98 static void record(FILE *, char *, size_t, int); 99 static void consume(FILE *, off_t, char *, int); 100 static void playback(FILE *) __dead2; 101 static void usage(void) __dead2; 102 103 int 104 main(int argc, char *argv[]) 105 { 106 struct termios rtt, stt; 107 struct winsize win; 108 struct timeval tv, *tvp; 109 time_t tvec, start; 110 char obuf[BUFSIZ]; 111 char ibuf[BUFSIZ]; 112 fd_set rfd, wfd; 113 struct buf_elm *be; 114 ssize_t cc; 115 int aflg, Fflg, kflg, pflg, ch, k, n, fcm; 116 int flushtime, readstdin; 117 int fm_fd, fm_log; 118 119 aflg = Fflg = kflg = pflg = 0; 120 usesleep = 1; 121 rawout = 0; 122 flushtime = 30; 123 fm_fd = -1; /* Shut up stupid "may be used uninitialized" GCC 124 warning. (not needed w/clang) */ 125 showexit = 0; 126 127 while ((ch = getopt(argc, argv, "adeFfkpqrT:t:")) != -1) 128 switch(ch) { 129 case 'a': 130 aflg = 1; 131 break; 132 case 'd': 133 usesleep = 0; 134 break; 135 case 'e': /* Default behavior, accepted for linux compat */ 136 break; 137 case 'F': 138 Fflg = 1; 139 break; 140 case 'f': 141 fflg = 1; 142 break; 143 case 'k': 144 kflg = 1; 145 break; 146 case 'p': 147 pflg = 1; 148 break; 149 case 'q': 150 qflg = 1; 151 break; 152 case 'r': 153 rawout = 1; 154 break; 155 case 't': 156 flushtime = atoi(optarg); 157 if (flushtime < 0) 158 err(1, "invalid flush time %d", flushtime); 159 break; 160 case 'T': 161 tflg = pflg = 1; 162 if (strchr(optarg, '%')) 163 tstamp_fmt = optarg; 164 break; 165 case '?': 166 default: 167 usage(); 168 } 169 argc -= optind; 170 argv += optind; 171 172 if (argc > 0) { 173 fname = argv[0]; 174 argv++; 175 argc--; 176 } else 177 fname = "typescript"; 178 179 if ((fscript = fopen(fname, pflg ? "r" : aflg ? "a" : "w")) == NULL) 180 err(1, "%s", fname); 181 182 if (fflg) { 183 asprintf(&fmfname, "%s.filemon", fname); 184 if (!fmfname) 185 err(1, "%s.filemon", fname); 186 if ((fm_fd = open("/dev/filemon", O_RDWR | O_CLOEXEC)) == -1) 187 err(1, "open(\"/dev/filemon\", O_RDWR)"); 188 if ((fm_log = open(fmfname, 189 O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 190 S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1) 191 err(1, "open(%s)", fmfname); 192 if (ioctl(fm_fd, FILEMON_SET_FD, &fm_log) < 0) 193 err(1, "Cannot set filemon log file descriptor"); 194 } 195 196 if (pflg) 197 playback(fscript); 198 199 if (tcgetattr(STDIN_FILENO, &tt) == -1 || 200 ioctl(STDIN_FILENO, TIOCGWINSZ, &win) == -1) { 201 if (errno != ENOTTY) /* For debugger. */ 202 err(1, "tcgetattr/ioctl"); 203 if (openpty(&master, &slave, NULL, NULL, NULL) == -1) 204 err(1, "openpty"); 205 } else { 206 if (openpty(&master, &slave, NULL, &tt, &win) == -1) 207 err(1, "openpty"); 208 ttyflg = 1; 209 } 210 fcm = fcntl(master, F_GETFL); 211 if (fcm == -1) 212 err(1, "master F_GETFL"); 213 fcm |= O_NONBLOCK; 214 if (fcntl(master, F_SETFL, fcm) == -1) 215 err(1, "master F_SETFL"); 216 217 if (rawout) 218 record(fscript, NULL, 0, 's'); 219 220 if (!qflg) { 221 tvec = time(NULL); 222 (void)printf("Script started, output file is %s\n", fname); 223 if (!rawout) { 224 (void)fprintf(fscript, "Script started on %s", 225 ctime(&tvec)); 226 if (argv[0]) { 227 showexit = 1; 228 fprintf(fscript, "Command: "); 229 for (k = 0 ; argv[k] ; ++k) 230 fprintf(fscript, "%s%s", k ? " " : "", 231 argv[k]); 232 fprintf(fscript, "\n"); 233 } 234 } 235 fflush(fscript); 236 if (fflg) { 237 (void)printf("Filemon started, output file is %s\n", 238 fmfname); 239 } 240 } 241 if (ttyflg) { 242 rtt = tt; 243 cfmakeraw(&rtt); 244 rtt.c_lflag &= ~ECHO; 245 (void)tcsetattr(STDIN_FILENO, TCSAFLUSH, &rtt); 246 } 247 248 child = fork(); 249 if (child < 0) { 250 warn("fork"); 251 done(1); 252 } 253 if (child == 0) { 254 if (fflg) { 255 int pid; 256 257 pid = getpid(); 258 if (ioctl(fm_fd, FILEMON_SET_PID, &pid) < 0) 259 err(1, "Cannot set filemon PID"); 260 } 261 262 doshell(argv); 263 } 264 close(slave); 265 266 start = tvec = time(0); 267 readstdin = 1; 268 for (;;) { 269 FD_ZERO(&rfd); 270 FD_ZERO(&wfd); 271 FD_SET(master, &rfd); 272 if (readstdin) 273 FD_SET(STDIN_FILENO, &rfd); 274 if (!TAILQ_EMPTY(&obuf_list)) 275 FD_SET(master, &wfd); 276 if (!readstdin && ttyflg) { 277 tv.tv_sec = 1; 278 tv.tv_usec = 0; 279 tvp = &tv; 280 readstdin = 1; 281 } else if (flushtime > 0) { 282 tv.tv_sec = flushtime - (tvec - start); 283 tv.tv_usec = 0; 284 tvp = &tv; 285 } else { 286 tvp = NULL; 287 } 288 n = select(master + 1, &rfd, &wfd, NULL, tvp); 289 if (n < 0 && errno != EINTR) 290 break; 291 if (n > 0 && FD_ISSET(STDIN_FILENO, &rfd)) { 292 cc = read(STDIN_FILENO, ibuf, BUFSIZ); 293 if (cc < 0) 294 break; 295 if (cc == 0) { 296 if (tcgetattr(master, &stt) == 0 && 297 (stt.c_lflag & ICANON) != 0) { 298 (void)write(master, &stt.c_cc[VEOF], 1); 299 } 300 readstdin = 0; 301 } 302 if (cc > 0) { 303 if (rawout) 304 record(fscript, ibuf, cc, 'i'); 305 be = malloc(sizeof(*be) + cc); 306 be->rpos = 0; 307 be->len = cc; 308 memcpy(be->ibuf, ibuf, cc); 309 TAILQ_INSERT_TAIL(&obuf_list, be, link); 310 } 311 } 312 if (n > 0 && FD_ISSET(master, &wfd)) { 313 while ((be = TAILQ_FIRST(&obuf_list)) != NULL) { 314 cc = write(master, be->ibuf + be->rpos, 315 be->len); 316 if (cc == -1) { 317 if (errno == EWOULDBLOCK || 318 errno == EINTR) 319 break; 320 warn("write master"); 321 done(1); 322 } 323 if (cc == 0) 324 break; /* retry later ? */ 325 if (kflg && tcgetattr(master, &stt) >= 0 && 326 ((stt.c_lflag & ECHO) == 0)) { 327 (void)fwrite(be->ibuf + be->rpos, 328 1, cc, fscript); 329 } 330 be->len -= cc; 331 if (be->len == 0) { 332 TAILQ_REMOVE(&obuf_list, be, link); 333 free(be); 334 } else { 335 be->rpos += cc; 336 } 337 } 338 } 339 if (n > 0 && FD_ISSET(master, &rfd)) { 340 cc = read(master, obuf, sizeof (obuf)); 341 if (cc <= 0) 342 break; 343 (void)write(STDOUT_FILENO, obuf, cc); 344 if (rawout) 345 record(fscript, obuf, cc, 'o'); 346 else 347 (void)fwrite(obuf, 1, cc, fscript); 348 } 349 tvec = time(0); 350 if (tvec - start >= flushtime) { 351 fflush(fscript); 352 start = tvec; 353 } 354 if (Fflg) 355 fflush(fscript); 356 } 357 finish(); 358 done(0); 359 } 360 361 static void 362 usage(void) 363 { 364 (void)fprintf(stderr, 365 "usage: script [-aeFfkpqr] [-t time] [file [command ...]]\n"); 366 (void)fprintf(stderr, 367 " script -p [-deq] [-T fmt] [file]\n"); 368 exit(1); 369 } 370 371 static void 372 finish(void) 373 { 374 int e, status; 375 376 if (waitpid(child, &status, 0) == child) { 377 if (WIFEXITED(status)) 378 e = WEXITSTATUS(status); 379 else if (WIFSIGNALED(status)) 380 e = WTERMSIG(status); 381 else /* can't happen */ 382 e = 1; 383 done(e); 384 } 385 } 386 387 static void 388 doshell(char **av) 389 { 390 const char *shell; 391 392 shell = getenv("SHELL"); 393 if (shell == NULL) 394 shell = _PATH_BSHELL; 395 396 (void)close(master); 397 (void)fclose(fscript); 398 free(fmfname); 399 login_tty(slave); 400 setenv("SCRIPT", fname, 1); 401 if (av[0]) { 402 execvp(av[0], av); 403 warn("%s", av[0]); 404 } else { 405 execl(shell, shell, "-i", (char *)NULL); 406 warn("%s", shell); 407 } 408 exit(1); 409 } 410 411 static void 412 done(int eno) 413 { 414 time_t tvec; 415 416 if (ttyflg) 417 (void)tcsetattr(STDIN_FILENO, TCSAFLUSH, &tt); 418 tvec = time(NULL); 419 if (rawout) 420 record(fscript, NULL, 0, 'e'); 421 if (!qflg) { 422 if (!rawout) { 423 if (showexit) 424 (void)fprintf(fscript, "\nCommand exit status:" 425 " %d", eno); 426 (void)fprintf(fscript,"\nScript done on %s", 427 ctime(&tvec)); 428 } 429 (void)printf("\nScript done, output file is %s\n", fname); 430 if (fflg) { 431 (void)printf("Filemon done, output file is %s\n", 432 fmfname); 433 } 434 } 435 (void)fclose(fscript); 436 (void)close(master); 437 exit(eno); 438 } 439 440 static void 441 record(FILE *fp, char *buf, size_t cc, int direction) 442 { 443 struct iovec iov[2]; 444 struct stamp stamp; 445 struct timeval tv; 446 447 (void)gettimeofday(&tv, NULL); 448 stamp.scr_len = cc; 449 stamp.scr_sec = tv.tv_sec; 450 stamp.scr_usec = tv.tv_usec; 451 stamp.scr_direction = direction; 452 iov[0].iov_len = sizeof(stamp); 453 iov[0].iov_base = &stamp; 454 iov[1].iov_len = cc; 455 iov[1].iov_base = buf; 456 if (writev(fileno(fp), &iov[0], 2) == -1) 457 err(1, "writev"); 458 } 459 460 static void 461 consume(FILE *fp, off_t len, char *buf, int reg) 462 { 463 size_t l; 464 465 if (reg) { 466 if (fseeko(fp, len, SEEK_CUR) == -1) 467 err(1, NULL); 468 } 469 else { 470 while (len > 0) { 471 l = MIN(DEF_BUF, len); 472 if (fread(buf, sizeof(char), l, fp) != l) 473 err(1, "cannot read buffer"); 474 len -= l; 475 } 476 } 477 } 478 479 #define swapstamp(stamp) do { \ 480 if (stamp.scr_direction > 0xff) { \ 481 stamp.scr_len = bswap64(stamp.scr_len); \ 482 stamp.scr_sec = bswap64(stamp.scr_sec); \ 483 stamp.scr_usec = bswap32(stamp.scr_usec); \ 484 stamp.scr_direction = bswap32(stamp.scr_direction); \ 485 } \ 486 } while (0/*CONSTCOND*/) 487 488 static void 489 termset(void) 490 { 491 struct termios traw; 492 493 if (tcgetattr(STDOUT_FILENO, &tt) == -1) { 494 if (errno != ENOTTY) /* For debugger. */ 495 err(1, "tcgetattr"); 496 return; 497 } 498 ttyflg = 1; 499 traw = tt; 500 cfmakeraw(&traw); 501 traw.c_lflag |= ISIG; 502 (void)tcsetattr(STDOUT_FILENO, TCSANOW, &traw); 503 } 504 505 static void 506 termreset(void) 507 { 508 if (ttyflg) { 509 tcsetattr(STDOUT_FILENO, TCSADRAIN, &tt); 510 ttyflg = 0; 511 } 512 } 513 514 static void 515 playback(FILE *fp) 516 { 517 struct timespec tsi, tso; 518 struct stamp stamp; 519 struct stat pst; 520 char buf[DEF_BUF]; 521 off_t nread, save_len; 522 size_t l; 523 time_t tclock; 524 time_t lclock; 525 int reg; 526 527 if (fstat(fileno(fp), &pst) == -1) 528 err(1, "fstat failed"); 529 530 reg = S_ISREG(pst.st_mode); 531 lclock = 0; 532 533 for (nread = 0; !reg || nread < pst.st_size; nread += save_len) { 534 if (fread(&stamp, sizeof(stamp), 1, fp) != 1) { 535 if (reg) 536 err(1, "reading playback header"); 537 else 538 break; 539 } 540 swapstamp(stamp); 541 save_len = sizeof(stamp); 542 543 if (reg && stamp.scr_len > 544 (uint64_t)(pst.st_size - save_len) - nread) 545 errx(1, "invalid stamp"); 546 547 save_len += stamp.scr_len; 548 tclock = stamp.scr_sec; 549 tso.tv_sec = stamp.scr_sec; 550 tso.tv_nsec = stamp.scr_usec * 1000; 551 if (nread == 0) 552 tsi = tso; 553 554 switch (stamp.scr_direction) { 555 case 's': 556 if (!qflg) 557 (void)printf("Script started on %s", 558 ctime(&tclock)); 559 tsi = tso; 560 (void)consume(fp, stamp.scr_len, buf, reg); 561 termset(); 562 atexit(termreset); 563 break; 564 case 'e': 565 termreset(); 566 if (!qflg) 567 (void)printf("\nScript done on %s", 568 ctime(&tclock)); 569 (void)consume(fp, stamp.scr_len, buf, reg); 570 break; 571 case 'i': 572 /* throw input away */ 573 (void)consume(fp, stamp.scr_len, buf, reg); 574 break; 575 case 'o': 576 if (tflg) { 577 if (stamp.scr_len == 0) 578 continue; 579 if (tclock - lclock > 0) { 580 l = strftime(buf, sizeof buf, tstamp_fmt, 581 localtime(&tclock)); 582 (void)write(STDOUT_FILENO, buf, l); 583 } 584 lclock = tclock; 585 } else { 586 tsi.tv_sec = tso.tv_sec - tsi.tv_sec; 587 tsi.tv_nsec = tso.tv_nsec - tsi.tv_nsec; 588 if (tsi.tv_nsec < 0) { 589 tsi.tv_sec -= 1; 590 tsi.tv_nsec += 1000000000; 591 } 592 if (usesleep) 593 (void)nanosleep(&tsi, NULL); 594 tsi = tso; 595 } 596 while (stamp.scr_len > 0) { 597 l = MIN(DEF_BUF, stamp.scr_len); 598 if (fread(buf, sizeof(char), l, fp) != l) 599 err(1, "cannot read buffer"); 600 601 (void)write(STDOUT_FILENO, buf, l); 602 stamp.scr_len -= l; 603 } 604 break; 605 default: 606 errx(1, "invalid direction"); 607 } 608 } 609 (void)fclose(fp); 610 exit(0); 611 } 612