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 int rpos; 77 int 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 int cc; 111 struct termios rtt, stt; 112 struct winsize win; 113 struct timeval tv, *tvp; 114 time_t tvec, start; 115 char obuf[BUFSIZ]; 116 char ibuf[BUFSIZ]; 117 fd_set rfd, wfd; 118 struct buf_elm *be; 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 [-adfkpqr] [-t time] [file [command ...]]\n"); 370 exit(1); 371 } 372 373 static void 374 finish(void) 375 { 376 int e, status; 377 378 if (waitpid(child, &status, 0) == child) { 379 if (WIFEXITED(status)) 380 e = WEXITSTATUS(status); 381 else if (WIFSIGNALED(status)) 382 e = WTERMSIG(status); 383 else /* can't happen */ 384 e = 1; 385 done(e); 386 } 387 } 388 389 static void 390 doshell(char **av) 391 { 392 const char *shell; 393 394 shell = getenv("SHELL"); 395 if (shell == NULL) 396 shell = _PATH_BSHELL; 397 398 (void)close(master); 399 (void)fclose(fscript); 400 free(fmfname); 401 login_tty(slave); 402 setenv("SCRIPT", fname, 1); 403 if (av[0]) { 404 execvp(av[0], av); 405 warn("%s", av[0]); 406 } else { 407 execl(shell, shell, "-i", (char *)NULL); 408 warn("%s", shell); 409 } 410 exit(1); 411 } 412 413 static void 414 done(int eno) 415 { 416 time_t tvec; 417 418 if (ttyflg) 419 (void)tcsetattr(STDIN_FILENO, TCSAFLUSH, &tt); 420 tvec = time(NULL); 421 if (rawout) 422 record(fscript, NULL, 0, 'e'); 423 if (!qflg) { 424 if (!rawout) { 425 if (showexit) 426 (void)fprintf(fscript, "\nCommand exit status:" 427 " %d", eno); 428 (void)fprintf(fscript,"\nScript done on %s", 429 ctime(&tvec)); 430 } 431 (void)printf("\nScript done, output file is %s\n", fname); 432 if (fflg) { 433 (void)printf("Filemon done, output file is %s\n", 434 fmfname); 435 } 436 } 437 (void)fclose(fscript); 438 (void)close(master); 439 exit(eno); 440 } 441 442 static void 443 record(FILE *fp, char *buf, size_t cc, int direction) 444 { 445 struct iovec iov[2]; 446 struct stamp stamp; 447 struct timeval tv; 448 449 (void)gettimeofday(&tv, NULL); 450 stamp.scr_len = cc; 451 stamp.scr_sec = tv.tv_sec; 452 stamp.scr_usec = tv.tv_usec; 453 stamp.scr_direction = direction; 454 iov[0].iov_len = sizeof(stamp); 455 iov[0].iov_base = &stamp; 456 iov[1].iov_len = cc; 457 iov[1].iov_base = buf; 458 if (writev(fileno(fp), &iov[0], 2) == -1) 459 err(1, "writev"); 460 } 461 462 static void 463 consume(FILE *fp, off_t len, char *buf, int reg) 464 { 465 size_t l; 466 467 if (reg) { 468 if (fseeko(fp, len, SEEK_CUR) == -1) 469 err(1, NULL); 470 } 471 else { 472 while (len > 0) { 473 l = MIN(DEF_BUF, len); 474 if (fread(buf, sizeof(char), l, fp) != l) 475 err(1, "cannot read buffer"); 476 len -= l; 477 } 478 } 479 } 480 481 #define swapstamp(stamp) do { \ 482 if (stamp.scr_direction > 0xff) { \ 483 stamp.scr_len = bswap64(stamp.scr_len); \ 484 stamp.scr_sec = bswap64(stamp.scr_sec); \ 485 stamp.scr_usec = bswap32(stamp.scr_usec); \ 486 stamp.scr_direction = bswap32(stamp.scr_direction); \ 487 } \ 488 } while (0/*CONSTCOND*/) 489 490 static void 491 termset(void) 492 { 493 struct termios traw; 494 495 if (tcgetattr(STDOUT_FILENO, &tt) == -1) { 496 if (errno != ENOTTY) /* For debugger. */ 497 err(1, "tcgetattr"); 498 return; 499 } 500 ttyflg = 1; 501 traw = tt; 502 cfmakeraw(&traw); 503 traw.c_lflag |= ISIG; 504 (void)tcsetattr(STDOUT_FILENO, TCSANOW, &traw); 505 } 506 507 static void 508 termreset(void) 509 { 510 if (ttyflg) { 511 tcsetattr(STDOUT_FILENO, TCSADRAIN, &tt); 512 ttyflg = 0; 513 } 514 } 515 516 static void 517 playback(FILE *fp) 518 { 519 struct timespec tsi, tso; 520 struct stamp stamp; 521 struct stat pst; 522 char buf[DEF_BUF]; 523 off_t nread, save_len; 524 size_t l; 525 time_t tclock; 526 time_t lclock; 527 int reg; 528 529 if (fstat(fileno(fp), &pst) == -1) 530 err(1, "fstat failed"); 531 532 reg = S_ISREG(pst.st_mode); 533 lclock = 0; 534 535 for (nread = 0; !reg || nread < pst.st_size; nread += save_len) { 536 if (fread(&stamp, sizeof(stamp), 1, fp) != 1) { 537 if (reg) 538 err(1, "reading playback header"); 539 else 540 break; 541 } 542 swapstamp(stamp); 543 save_len = sizeof(stamp); 544 545 if (reg && stamp.scr_len > 546 (uint64_t)(pst.st_size - save_len) - nread) 547 errx(1, "invalid stamp"); 548 549 save_len += stamp.scr_len; 550 tclock = stamp.scr_sec; 551 tso.tv_sec = stamp.scr_sec; 552 tso.tv_nsec = stamp.scr_usec * 1000; 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