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