1 /* $NetBSD: script.c,v 1.21 2011/09/06 18:29:56 joerg Exp $ */ 2 3 /* 4 * Copyright (c) 1980, 1992, 1993 5 * The Regents of the University of California. All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. Neither the name of the University nor the names of its contributors 16 * may be used to endorse or promote products derived from this software 17 * without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 */ 31 32 #include <sys/cdefs.h> 33 #ifndef lint 34 __COPYRIGHT("@(#) Copyright (c) 1980, 1992, 1993\ 35 The Regents of the University of California. All rights reserved."); 36 #endif /* not lint */ 37 38 #ifndef lint 39 #if 0 40 static char sccsid[] = "@(#)script.c 8.1 (Berkeley) 6/6/93"; 41 #endif 42 __RCSID("$NetBSD: script.c,v 1.21 2011/09/06 18:29:56 joerg Exp $"); 43 #endif /* not lint */ 44 45 #include <sys/types.h> 46 #include <sys/wait.h> 47 #include <sys/stat.h> 48 #include <sys/ioctl.h> 49 #include <sys/time.h> 50 #include <sys/param.h> 51 #include <sys/uio.h> 52 53 #include <err.h> 54 #include <errno.h> 55 #include <fcntl.h> 56 #include <paths.h> 57 #include <signal.h> 58 #include <stdio.h> 59 #include <stdlib.h> 60 #include <string.h> 61 #include <termios.h> 62 #include <time.h> 63 #include <tzfile.h> 64 #include <unistd.h> 65 #include <util.h> 66 67 #define DEF_BUF 65536 68 69 struct stamp { 70 uint64_t scr_len; /* amount of data */ 71 uint64_t scr_sec; /* time it arrived in seconds... */ 72 uint32_t scr_usec; /* ...and microseconds */ 73 uint32_t scr_direction; /* 'i', 'o', etc (also indicates endianness) */ 74 }; 75 76 static FILE *fscript; 77 static int master, slave; 78 static int child, subchild; 79 static int outcc; 80 static int usesleep, rawout; 81 static int quiet, flush; 82 static const char *fname; 83 84 static struct termios tt; 85 86 __dead static void done(void); 87 __dead static void dooutput(void); 88 __dead static void doshell(const char *); 89 __dead static void fail(void); 90 static void finish(int); 91 static void scriptflush(int); 92 static void record(FILE *, char *, size_t, int); 93 static void consume(FILE *, off_t, char *, int); 94 __dead static void playback(FILE *); 95 96 int 97 main(int argc, char *argv[]) 98 { 99 int cc; 100 struct termios rtt; 101 struct winsize win; 102 int aflg, pflg, ch; 103 char ibuf[BUFSIZ]; 104 const char *command; 105 106 aflg = 0; 107 pflg = 0; 108 usesleep = 1; 109 rawout = 0; 110 quiet = 0; 111 flush = 0; 112 command = NULL; 113 while ((ch = getopt(argc, argv, "ac:dfpqr")) != -1) 114 switch(ch) { 115 case 'a': 116 aflg = 1; 117 break; 118 case 'c': 119 command = optarg; 120 break; 121 case 'd': 122 usesleep = 0; 123 break; 124 case 'f': 125 flush = 1; 126 break; 127 case 'p': 128 pflg = 1; 129 break; 130 case 'q': 131 quiet = 1; 132 break; 133 case 'r': 134 rawout = 1; 135 break; 136 case '?': 137 default: 138 (void)fprintf(stderr, 139 "Usage: %s [-c <command>][-adfpqr] [file]\n", 140 getprogname()); 141 exit(1); 142 } 143 argc -= optind; 144 argv += optind; 145 146 if (argc > 0) 147 fname = argv[0]; 148 else 149 fname = "typescript"; 150 151 if ((fscript = fopen(fname, pflg ? "r" : aflg ? "a" : "w")) == NULL) 152 err(1, "fopen %s", fname); 153 154 if (pflg) 155 playback(fscript); 156 157 (void)tcgetattr(STDIN_FILENO, &tt); 158 (void)ioctl(STDIN_FILENO, TIOCGWINSZ, &win); 159 if (openpty(&master, &slave, NULL, &tt, &win) == -1) 160 err(1, "openpty"); 161 162 if (!quiet) 163 (void)printf("Script started, output file is %s\n", fname); 164 rtt = tt; 165 cfmakeraw(&rtt); 166 rtt.c_lflag &= ~ECHO; 167 (void)tcsetattr(STDIN_FILENO, TCSAFLUSH, &rtt); 168 169 (void)signal(SIGCHLD, finish); 170 child = fork(); 171 if (child < 0) { 172 warn("fork"); 173 fail(); 174 } 175 if (child == 0) { 176 subchild = child = fork(); 177 if (child < 0) { 178 warn("fork"); 179 fail(); 180 } 181 if (child) 182 dooutput(); 183 else 184 doshell(command); 185 } 186 187 if (!rawout) 188 (void)fclose(fscript); 189 while ((cc = read(STDIN_FILENO, ibuf, BUFSIZ)) > 0) { 190 if (rawout) 191 record(fscript, ibuf, cc, 'i'); 192 (void)write(master, ibuf, cc); 193 } 194 done(); 195 /* NOTREACHED */ 196 return (0); 197 } 198 199 static void 200 finish(int signo) 201 { 202 int die, pid, status; 203 204 die = 0; 205 while ((pid = wait3(&status, WNOHANG, 0)) > 0) 206 if (pid == child) 207 die = 1; 208 209 if (die) 210 done(); 211 } 212 213 static void 214 dooutput(void) 215 { 216 struct itimerval value; 217 int cc; 218 time_t tvec; 219 char obuf[BUFSIZ]; 220 221 (void)close(STDIN_FILENO); 222 tvec = time(NULL); 223 if (rawout) 224 record(fscript, NULL, 0, 's'); 225 else if (!quiet) 226 (void)fprintf(fscript, "Script started on %s", ctime(&tvec)); 227 228 (void)signal(SIGALRM, scriptflush); 229 value.it_interval.tv_sec = SECSPERMIN / 2; 230 value.it_interval.tv_usec = 0; 231 value.it_value = value.it_interval; 232 (void)setitimer(ITIMER_REAL, &value, NULL); 233 for (;;) { 234 cc = read(master, obuf, sizeof (obuf)); 235 if (cc <= 0) 236 break; 237 (void)write(1, obuf, cc); 238 if (rawout) 239 record(fscript, obuf, cc, 'o'); 240 else 241 (void)fwrite(obuf, 1, cc, fscript); 242 outcc += cc; 243 if (flush) 244 (void)fflush(fscript); 245 } 246 done(); 247 } 248 249 static void 250 scriptflush(int signo) 251 { 252 if (outcc) { 253 (void)fflush(fscript); 254 outcc = 0; 255 } 256 } 257 258 static void 259 doshell(const char *command) 260 { 261 const char *shell; 262 263 (void)close(master); 264 (void)fclose(fscript); 265 login_tty(slave); 266 if (command == NULL) { 267 shell = getenv("SHELL"); 268 if (shell == NULL) 269 shell = _PATH_BSHELL; 270 execl(shell, shell, "-i", NULL); 271 warn("execl `%s'", shell); 272 } else { 273 if (system(command) == -1) 274 warn("system `%s'", command); 275 } 276 277 fail(); 278 } 279 280 static void 281 fail(void) 282 { 283 284 (void)kill(0, SIGTERM); 285 done(); 286 } 287 288 static void 289 done(void) 290 { 291 time_t tvec; 292 293 if (subchild) { 294 tvec = time(NULL); 295 if (rawout) 296 record(fscript, NULL, 0, 'e'); 297 else if (!quiet) 298 (void)fprintf(fscript,"\nScript done on %s", 299 ctime(&tvec)); 300 (void)fclose(fscript); 301 (void)close(master); 302 } else { 303 (void)tcsetattr(STDIN_FILENO, TCSAFLUSH, &tt); 304 if (!quiet) 305 (void)printf("Script done, output file is %s\n", fname); 306 } 307 exit(0); 308 } 309 310 static void 311 record(FILE *fp, char *buf, size_t cc, int direction) 312 { 313 struct iovec iov[2]; 314 struct stamp stamp; 315 struct timeval tv; 316 317 (void)gettimeofday(&tv, NULL); 318 stamp.scr_len = cc; 319 stamp.scr_sec = tv.tv_sec; 320 stamp.scr_usec = tv.tv_usec; 321 stamp.scr_direction = direction; 322 iov[0].iov_len = sizeof(stamp); 323 iov[0].iov_base = &stamp; 324 iov[1].iov_len = cc; 325 iov[1].iov_base = buf; 326 if (writev(fileno(fp), &iov[0], 2) == -1) 327 err(1, "writev"); 328 } 329 330 static void 331 consume(FILE *fp, off_t len, char *buf, int reg) 332 { 333 size_t l; 334 335 if (reg) { 336 if (fseeko(fp, len, SEEK_CUR) == -1) 337 err(1, NULL); 338 } 339 else { 340 while (len > 0) { 341 l = MIN(DEF_BUF, len); 342 if (fread(buf, sizeof(char), l, fp) != l) 343 err(1, "cannot read buffer"); 344 len -= l; 345 } 346 } 347 } 348 349 #define swapstamp(stamp) do { \ 350 if (stamp.scr_direction > 0xff) { \ 351 stamp.scr_len = bswap64(stamp.scr_len); \ 352 stamp.scr_sec = bswap64(stamp.scr_sec); \ 353 stamp.scr_usec = bswap32(stamp.scr_usec); \ 354 stamp.scr_direction = bswap32(stamp.scr_direction); \ 355 } \ 356 } while (0/*CONSTCOND*/) 357 358 static void 359 playback(FILE *fp) 360 { 361 struct timespec tsi, tso; 362 struct stamp stamp; 363 struct stat pst; 364 char buf[DEF_BUF]; 365 off_t nread, save_len; 366 size_t l; 367 time_t tclock; 368 int reg; 369 370 if (fstat(fileno(fp), &pst) == -1) 371 err(1, "fstat failed"); 372 373 reg = S_ISREG(pst.st_mode); 374 375 for (nread = 0; !reg || nread < pst.st_size; nread += save_len) { 376 if (fread(&stamp, sizeof(stamp), 1, fp) != 1) { 377 if (reg) 378 err(1, "reading playback header"); 379 else 380 break; 381 } 382 swapstamp(stamp); 383 save_len = sizeof(stamp); 384 385 if (reg && stamp.scr_len > 386 (uint64_t)(pst.st_size - save_len) - nread) 387 errx(1, "invalid stamp"); 388 389 save_len += stamp.scr_len; 390 tclock = stamp.scr_sec; 391 tso.tv_sec = stamp.scr_sec; 392 tso.tv_nsec = stamp.scr_usec * 1000; 393 394 switch (stamp.scr_direction) { 395 case 's': 396 if (!quiet) 397 (void)printf("Script started on %s", 398 ctime(&tclock)); 399 tsi = tso; 400 (void)consume(fp, stamp.scr_len, buf, reg); 401 break; 402 case 'e': 403 if (!quiet) 404 (void)printf("\nScript done on %s", 405 ctime(&tclock)); 406 (void)consume(fp, stamp.scr_len, buf, reg); 407 break; 408 case 'i': 409 /* throw input away */ 410 (void)consume(fp, stamp.scr_len, buf, reg); 411 break; 412 case 'o': 413 tsi.tv_sec = tso.tv_sec - tsi.tv_sec; 414 tsi.tv_nsec = tso.tv_nsec - tsi.tv_nsec; 415 if (tsi.tv_nsec < 0) { 416 tsi.tv_sec -= 1; 417 tsi.tv_nsec += 1000000000; 418 } 419 if (usesleep) 420 (void)nanosleep(&tsi, NULL); 421 tsi = tso; 422 while (stamp.scr_len > 0) { 423 l = MIN(DEF_BUF, stamp.scr_len); 424 if (fread(buf, sizeof(char), l, fp) != l) 425 err(1, "cannot read buffer"); 426 427 (void)write(STDOUT_FILENO, buf, l); 428 stamp.scr_len -= l; 429 } 430 break; 431 default: 432 errx(1, "invalid direction"); 433 } 434 } 435 (void)fclose(fp); 436 exit(0); 437 } 438