1 /* $NetBSD: script.c,v 1.17 2009/04/13 07:15:32 lukem 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.17 2009/04/13 07:15:32 lukem 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 FILE *fscript; 77 int master, slave; 78 int child, subchild; 79 int outcc; 80 int usesleep, rawout; 81 const char *fname; 82 83 struct termios tt; 84 85 void done(void); 86 void dooutput(void); 87 void doshell(void); 88 void fail(void); 89 void finish(int); 90 int main(int, char **); 91 void scriptflush(int); 92 void record(FILE *, char *, size_t, int); 93 void consume(FILE *, off_t, char *, int); 94 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 105 aflg = 0; 106 pflg = 0; 107 usesleep = 1; 108 rawout = 0; 109 while ((ch = getopt(argc, argv, "adpr")) != -1) 110 switch(ch) { 111 case 'a': 112 aflg = 1; 113 break; 114 case 'd': 115 usesleep = 0; 116 break; 117 case 'p': 118 pflg = 1; 119 break; 120 case 'r': 121 rawout = 1; 122 break; 123 case '?': 124 default: 125 (void)fprintf(stderr, "usage: %s [-adpr] [file]\n", 126 getprogname()); 127 exit(1); 128 } 129 argc -= optind; 130 argv += optind; 131 132 if (argc > 0) 133 fname = argv[0]; 134 else 135 fname = "typescript"; 136 137 if ((fscript = fopen(fname, pflg ? "r" : aflg ? "a" : "w")) == NULL) 138 err(1, "fopen %s", fname); 139 140 if (pflg) 141 playback(fscript); 142 143 (void)tcgetattr(STDIN_FILENO, &tt); 144 (void)ioctl(STDIN_FILENO, TIOCGWINSZ, &win); 145 if (openpty(&master, &slave, NULL, &tt, &win) == -1) 146 err(1, "openpty"); 147 148 (void)printf("Script started, output file is %s\n", fname); 149 rtt = tt; 150 cfmakeraw(&rtt); 151 rtt.c_lflag &= ~ECHO; 152 (void)tcsetattr(STDIN_FILENO, TCSAFLUSH, &rtt); 153 154 (void)signal(SIGCHLD, finish); 155 child = fork(); 156 if (child < 0) { 157 warn("fork"); 158 fail(); 159 } 160 if (child == 0) { 161 subchild = child = fork(); 162 if (child < 0) { 163 warn("fork"); 164 fail(); 165 } 166 if (child) 167 dooutput(); 168 else 169 doshell(); 170 } 171 172 if (!rawout) 173 (void)fclose(fscript); 174 while ((cc = read(STDIN_FILENO, ibuf, BUFSIZ)) > 0) { 175 if (rawout) 176 record(fscript, ibuf, cc, 'i'); 177 (void)write(master, ibuf, cc); 178 } 179 done(); 180 /* NOTREACHED */ 181 return (0); 182 } 183 184 void 185 finish(int signo) 186 { 187 int die, pid, status; 188 189 die = 0; 190 while ((pid = wait3(&status, WNOHANG, 0)) > 0) 191 if (pid == child) 192 die = 1; 193 194 if (die) 195 done(); 196 } 197 198 void 199 dooutput() 200 { 201 struct itimerval value; 202 int cc; 203 time_t tvec; 204 char obuf[BUFSIZ]; 205 206 (void)close(STDIN_FILENO); 207 tvec = time(NULL); 208 if (rawout) 209 record(fscript, NULL, 0, 's'); 210 else 211 (void)fprintf(fscript, "Script started on %s", ctime(&tvec)); 212 213 (void)signal(SIGALRM, scriptflush); 214 value.it_interval.tv_sec = SECSPERMIN / 2; 215 value.it_interval.tv_usec = 0; 216 value.it_value = value.it_interval; 217 (void)setitimer(ITIMER_REAL, &value, NULL); 218 for (;;) { 219 cc = read(master, obuf, sizeof (obuf)); 220 if (cc <= 0) 221 break; 222 (void)write(1, obuf, cc); 223 if (rawout) 224 record(fscript, obuf, cc, 'o'); 225 else 226 (void)fwrite(obuf, 1, cc, fscript); 227 outcc += cc; 228 } 229 done(); 230 } 231 232 void 233 scriptflush(int signo) 234 { 235 if (outcc) { 236 (void)fflush(fscript); 237 outcc = 0; 238 } 239 } 240 241 void 242 doshell() 243 { 244 const char *shell; 245 246 shell = getenv("SHELL"); 247 if (shell == NULL) 248 shell = _PATH_BSHELL; 249 250 (void)close(master); 251 (void)fclose(fscript); 252 login_tty(slave); 253 execl(shell, shell, "-i", NULL); 254 warn("execl %s", shell); 255 fail(); 256 } 257 258 void 259 fail() 260 { 261 262 (void)kill(0, SIGTERM); 263 done(); 264 } 265 266 void 267 done() 268 { 269 time_t tvec; 270 271 if (subchild) { 272 tvec = time(NULL); 273 if (rawout) 274 record(fscript, NULL, 0, 'e'); 275 else 276 (void)fprintf(fscript,"\nScript done on %s", 277 ctime(&tvec)); 278 (void)fclose(fscript); 279 (void)close(master); 280 } else { 281 (void)tcsetattr(STDIN_FILENO, TCSAFLUSH, &tt); 282 (void)printf("Script done, output file is %s\n", fname); 283 } 284 exit(0); 285 } 286 287 void 288 record(FILE *fp, char *buf, size_t cc, int direction) 289 { 290 struct iovec iov[2]; 291 struct stamp stamp; 292 struct timeval tv; 293 294 (void)gettimeofday(&tv, NULL); 295 stamp.scr_len = cc; 296 stamp.scr_sec = tv.tv_sec; 297 stamp.scr_usec = tv.tv_usec; 298 stamp.scr_direction = direction; 299 iov[0].iov_len = sizeof(stamp); 300 iov[0].iov_base = &stamp; 301 iov[1].iov_len = cc; 302 iov[1].iov_base = buf; 303 if (writev(fileno(fp), &iov[0], 2) == -1) 304 err(1, "writev"); 305 } 306 307 void 308 consume(FILE *fp, off_t len, char *buf, int reg) 309 { 310 size_t l; 311 312 if (reg) { 313 if (fseeko(fp, len, SEEK_CUR) == -1) 314 err(1, NULL); 315 } 316 else { 317 while (len > 0) { 318 l = MIN(DEF_BUF, len); 319 if (fread(buf, sizeof(char), l, fp) != l) 320 err(1, "cannot read buffer"); 321 len -= l; 322 } 323 } 324 } 325 326 #define swapstamp(stamp) do { \ 327 if (stamp.scr_direction > 0xff) { \ 328 stamp.scr_len = bswap64(stamp.scr_len); \ 329 stamp.scr_sec = bswap64(stamp.scr_sec); \ 330 stamp.scr_usec = bswap32(stamp.scr_usec); \ 331 stamp.scr_direction = bswap32(stamp.scr_direction); \ 332 } \ 333 } while (0/*CONSTCOND*/) 334 335 void 336 playback(FILE *fp) 337 { 338 struct timespec tsi, tso; 339 struct stamp stamp; 340 struct stat pst; 341 char buf[DEF_BUF]; 342 off_t nread, save_len; 343 size_t l; 344 time_t tclock; 345 int reg; 346 347 if (fstat(fileno(fp), &pst) == -1) 348 err(1, "fstat failed"); 349 350 reg = S_ISREG(pst.st_mode); 351 352 for (nread = 0; !reg || nread < pst.st_size; nread += save_len) { 353 if (fread(&stamp, sizeof(stamp), 1, fp) != 1) { 354 if (reg) 355 err(1, "reading playback header"); 356 else 357 break; 358 } 359 swapstamp(stamp); 360 save_len = sizeof(stamp); 361 362 if (reg && stamp.scr_len > 363 (uint64_t)(pst.st_size - save_len) - nread) 364 err(1, "invalid stamp"); 365 366 save_len += stamp.scr_len; 367 tclock = stamp.scr_sec; 368 tso.tv_sec = stamp.scr_sec; 369 tso.tv_nsec = stamp.scr_usec * 1000; 370 371 switch (stamp.scr_direction) { 372 case 's': 373 (void)printf("Script started on %s", ctime(&tclock)); 374 tsi = tso; 375 (void)consume(fp, stamp.scr_len, buf, reg); 376 break; 377 case 'e': 378 (void)printf("\nScript done on %s", ctime(&tclock)); 379 (void)consume(fp, stamp.scr_len, buf, reg); 380 break; 381 case 'i': 382 /* throw input away */ 383 (void)consume(fp, stamp.scr_len, buf, reg); 384 break; 385 case 'o': 386 tsi.tv_sec = tso.tv_sec - tsi.tv_sec; 387 tsi.tv_nsec = tso.tv_nsec - tsi.tv_nsec; 388 if (tsi.tv_nsec < 0) { 389 tsi.tv_sec -= 1; 390 tsi.tv_nsec += 1000000000; 391 } 392 if (usesleep) 393 (void)nanosleep(&tsi, NULL); 394 tsi = tso; 395 while (stamp.scr_len > 0) { 396 l = MIN(DEF_BUF, stamp.scr_len); 397 if (fread(buf, sizeof(char), l, fp) != l) 398 err(1, "cannot read buffer"); 399 400 (void)write(STDOUT_FILENO, buf, l); 401 stamp.scr_len -= l; 402 } 403 break; 404 default: 405 err(1, "invalid direction"); 406 } 407 } 408 (void)fclose(fp); 409 exit(0); 410 } 411