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 __FBSDID("$FreeBSD$"); 35 #ifndef lint 36 static const char copyright[] = 37 "@(#) Copyright (c) 1980, 1992, 1993\n\ 38 The Regents of the University of California. All rights reserved.\n"; 39 #endif 40 #ifndef lint 41 static const char sccsid[] = "@(#)script.c 8.1 (Berkeley) 6/6/93"; 42 #endif 43 44 #include <sys/wait.h> 45 #include <sys/stat.h> 46 #include <sys/ioctl.h> 47 #include <sys/time.h> 48 #include <sys/queue.h> 49 #include <sys/uio.h> 50 #include <sys/endian.h> 51 #include <dev/filemon/filemon.h> 52 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); 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; /* Shut up stupid "may be used uninitialized" GCC 128 warning. (not needed w/clang) */ 129 showexit = 0; 130 131 while ((ch = getopt(argc, argv, "adeFfkpqrT:t:")) != -1) 132 switch(ch) { 133 case 'a': 134 aflg = 1; 135 break; 136 case 'd': 137 usesleep = 0; 138 break; 139 case 'e': /* 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 child = fork(); 253 if (child < 0) { 254 warn("fork"); 255 done(1); 256 } 257 if (child == 0) { 258 if (fflg) { 259 int pid; 260 261 pid = getpid(); 262 if (ioctl(fm_fd, FILEMON_SET_PID, &pid) < 0) 263 err(1, "Cannot set filemon PID"); 264 } 265 266 doshell(argv); 267 } 268 close(slave); 269 270 start = tvec = time(0); 271 readstdin = 1; 272 for (;;) { 273 FD_ZERO(&rfd); 274 FD_ZERO(&wfd); 275 FD_SET(master, &rfd); 276 if (readstdin) 277 FD_SET(STDIN_FILENO, &rfd); 278 if (!TAILQ_EMPTY(&obuf_list)) 279 FD_SET(master, &wfd); 280 if (!readstdin && ttyflg) { 281 tv.tv_sec = 1; 282 tv.tv_usec = 0; 283 tvp = &tv; 284 readstdin = 1; 285 } else if (flushtime > 0) { 286 tv.tv_sec = flushtime - (tvec - start); 287 tv.tv_usec = 0; 288 tvp = &tv; 289 } else { 290 tvp = NULL; 291 } 292 n = select(master + 1, &rfd, &wfd, NULL, tvp); 293 if (n < 0 && errno != EINTR) 294 break; 295 if (n > 0 && FD_ISSET(STDIN_FILENO, &rfd)) { 296 cc = read(STDIN_FILENO, ibuf, BUFSIZ); 297 if (cc < 0) 298 break; 299 if (cc == 0) { 300 if (tcgetattr(master, &stt) == 0 && 301 (stt.c_lflag & ICANON) != 0) { 302 (void)write(master, &stt.c_cc[VEOF], 1); 303 } 304 readstdin = 0; 305 } 306 if (cc > 0) { 307 if (rawout) 308 record(fscript, ibuf, cc, 'i'); 309 be = malloc(sizeof(*be) + cc); 310 be->rpos = 0; 311 be->len = cc; 312 memcpy(be->ibuf, ibuf, cc); 313 TAILQ_INSERT_TAIL(&obuf_list, be, link); 314 } 315 } 316 if (n > 0 && FD_ISSET(master, &wfd)) { 317 while ((be = TAILQ_FIRST(&obuf_list)) != NULL) { 318 cc = write(master, be->ibuf + be->rpos, 319 be->len); 320 if (cc == -1) { 321 if (errno == EWOULDBLOCK || 322 errno == EINTR) 323 break; 324 warn("write master"); 325 done(1); 326 } 327 if (cc == 0) 328 break; /* retry later ? */ 329 if (kflg && tcgetattr(master, &stt) >= 0 && 330 ((stt.c_lflag & ECHO) == 0)) { 331 (void)fwrite(be->ibuf + be->rpos, 332 1, cc, fscript); 333 } 334 be->len -= cc; 335 if (be->len == 0) { 336 TAILQ_REMOVE(&obuf_list, be, link); 337 free(be); 338 } else { 339 be->rpos += cc; 340 } 341 } 342 } 343 if (n > 0 && FD_ISSET(master, &rfd)) { 344 cc = read(master, obuf, sizeof (obuf)); 345 if (cc <= 0) 346 break; 347 (void)write(STDOUT_FILENO, obuf, cc); 348 if (rawout) 349 record(fscript, obuf, cc, 'o'); 350 else 351 (void)fwrite(obuf, 1, cc, fscript); 352 } 353 tvec = time(0); 354 if (tvec - start >= flushtime) { 355 fflush(fscript); 356 start = tvec; 357 } 358 if (Fflg) 359 fflush(fscript); 360 } 361 finish(); 362 done(0); 363 } 364 365 static void 366 usage(void) 367 { 368 (void)fprintf(stderr, 369 "usage: script [-aeFfkpqr] [-t time] [file [command ...]]\n"); 370 (void)fprintf(stderr, 371 " script -p [-deq] [-T fmt] [file]\n"); 372 exit(1); 373 } 374 375 static void 376 finish(void) 377 { 378 int e, status; 379 380 if (waitpid(child, &status, 0) == child) { 381 if (WIFEXITED(status)) 382 e = WEXITSTATUS(status); 383 else if (WIFSIGNALED(status)) 384 e = WTERMSIG(status); 385 else /* can't happen */ 386 e = 1; 387 done(e); 388 } 389 } 390 391 static void 392 doshell(char **av) 393 { 394 const char *shell; 395 396 shell = getenv("SHELL"); 397 if (shell == NULL) 398 shell = _PATH_BSHELL; 399 400 (void)close(master); 401 (void)fclose(fscript); 402 free(fmfname); 403 login_tty(slave); 404 setenv("SCRIPT", fname, 1); 405 if (av[0]) { 406 execvp(av[0], av); 407 warn("%s", av[0]); 408 } else { 409 execl(shell, shell, "-i", (char *)NULL); 410 warn("%s", shell); 411 } 412 exit(1); 413 } 414 415 static void 416 done(int eno) 417 { 418 time_t tvec; 419 420 if (ttyflg) 421 (void)tcsetattr(STDIN_FILENO, TCSAFLUSH, &tt); 422 tvec = time(NULL); 423 if (rawout) 424 record(fscript, NULL, 0, 'e'); 425 if (!qflg) { 426 if (!rawout) { 427 if (showexit) 428 (void)fprintf(fscript, "\nCommand exit status:" 429 " %d", eno); 430 (void)fprintf(fscript,"\nScript done on %s", 431 ctime(&tvec)); 432 } 433 (void)printf("\nScript done, output file is %s\n", fname); 434 if (fflg) { 435 (void)printf("Filemon done, output file is %s\n", 436 fmfname); 437 } 438 } 439 (void)fclose(fscript); 440 (void)close(master); 441 exit(eno); 442 } 443 444 static void 445 record(FILE *fp, char *buf, size_t cc, int direction) 446 { 447 struct iovec iov[2]; 448 struct stamp stamp; 449 struct timeval tv; 450 451 (void)gettimeofday(&tv, NULL); 452 stamp.scr_len = cc; 453 stamp.scr_sec = tv.tv_sec; 454 stamp.scr_usec = tv.tv_usec; 455 stamp.scr_direction = direction; 456 iov[0].iov_len = sizeof(stamp); 457 iov[0].iov_base = &stamp; 458 iov[1].iov_len = cc; 459 iov[1].iov_base = buf; 460 if (writev(fileno(fp), &iov[0], 2) == -1) 461 err(1, "writev"); 462 } 463 464 static void 465 consume(FILE *fp, off_t len, char *buf, int reg) 466 { 467 size_t l; 468 469 if (reg) { 470 if (fseeko(fp, len, SEEK_CUR) == -1) 471 err(1, NULL); 472 } 473 else { 474 while (len > 0) { 475 l = MIN(DEF_BUF, len); 476 if (fread(buf, sizeof(char), l, fp) != l) 477 err(1, "cannot read buffer"); 478 len -= l; 479 } 480 } 481 } 482 483 #define swapstamp(stamp) do { \ 484 if (stamp.scr_direction > 0xff) { \ 485 stamp.scr_len = bswap64(stamp.scr_len); \ 486 stamp.scr_sec = bswap64(stamp.scr_sec); \ 487 stamp.scr_usec = bswap32(stamp.scr_usec); \ 488 stamp.scr_direction = bswap32(stamp.scr_direction); \ 489 } \ 490 } while (0/*CONSTCOND*/) 491 492 static void 493 termset(void) 494 { 495 struct termios traw; 496 497 if (tcgetattr(STDOUT_FILENO, &tt) == -1) { 498 if (errno != ENOTTY) /* For debugger. */ 499 err(1, "tcgetattr"); 500 return; 501 } 502 ttyflg = 1; 503 traw = tt; 504 cfmakeraw(&traw); 505 traw.c_lflag |= ISIG; 506 (void)tcsetattr(STDOUT_FILENO, TCSANOW, &traw); 507 } 508 509 static void 510 termreset(void) 511 { 512 if (ttyflg) { 513 tcsetattr(STDOUT_FILENO, TCSADRAIN, &tt); 514 ttyflg = 0; 515 } 516 } 517 518 static void 519 playback(FILE *fp) 520 { 521 struct timespec tsi, tso; 522 struct stamp stamp; 523 struct stat pst; 524 char buf[DEF_BUF]; 525 off_t nread, save_len; 526 size_t l; 527 time_t tclock; 528 time_t lclock; 529 int reg; 530 531 if (fstat(fileno(fp), &pst) == -1) 532 err(1, "fstat failed"); 533 534 reg = S_ISREG(pst.st_mode); 535 lclock = 0; 536 537 for (nread = 0; !reg || nread < pst.st_size; nread += save_len) { 538 if (fread(&stamp, sizeof(stamp), 1, fp) != 1) { 539 if (reg) 540 err(1, "reading playback header"); 541 else 542 break; 543 } 544 swapstamp(stamp); 545 save_len = sizeof(stamp); 546 547 if (reg && stamp.scr_len > 548 (uint64_t)(pst.st_size - save_len) - nread) 549 errx(1, "invalid stamp"); 550 551 save_len += stamp.scr_len; 552 tclock = stamp.scr_sec; 553 tso.tv_sec = stamp.scr_sec; 554 tso.tv_nsec = stamp.scr_usec * 1000; 555 if (nread == 0) 556 tsi = tso; 557 558 switch (stamp.scr_direction) { 559 case 's': 560 if (!qflg) 561 (void)printf("Script started on %s", 562 ctime(&tclock)); 563 tsi = tso; 564 (void)consume(fp, stamp.scr_len, buf, reg); 565 termset(); 566 atexit(termreset); 567 break; 568 case 'e': 569 termreset(); 570 if (!qflg) 571 (void)printf("\nScript done on %s", 572 ctime(&tclock)); 573 (void)consume(fp, stamp.scr_len, buf, reg); 574 break; 575 case 'i': 576 /* throw input away */ 577 (void)consume(fp, stamp.scr_len, buf, reg); 578 break; 579 case 'o': 580 if (tflg) { 581 if (stamp.scr_len == 0) 582 continue; 583 if (tclock - lclock > 0) { 584 l = strftime(buf, sizeof buf, tstamp_fmt, 585 localtime(&tclock)); 586 (void)write(STDOUT_FILENO, buf, l); 587 } 588 lclock = tclock; 589 } else { 590 tsi.tv_sec = tso.tv_sec - tsi.tv_sec; 591 tsi.tv_nsec = tso.tv_nsec - tsi.tv_nsec; 592 if (tsi.tv_nsec < 0) { 593 tsi.tv_sec -= 1; 594 tsi.tv_nsec += 1000000000; 595 } 596 if (usesleep) 597 (void)nanosleep(&tsi, NULL); 598 tsi = tso; 599 } 600 while (stamp.scr_len > 0) { 601 l = MIN(DEF_BUF, stamp.scr_len); 602 if (fread(buf, sizeof(char), l, fp) != l) 603 err(1, "cannot read buffer"); 604 605 (void)write(STDOUT_FILENO, buf, l); 606 stamp.scr_len -= l; 607 } 608 break; 609 default: 610 errx(1, "invalid direction"); 611 } 612 } 613 (void)fclose(fp); 614 exit(0); 615 } 616