script.c revision 1.30 1 /* $NetBSD: script.c,v 1.30 2022/01/20 19:49:51 christos 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.30 2022/01/20 19:49:51 christos 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 size_t outcc;
80 static int usesleep, rawout;
81 static int quiet, flush;
82 static const char *fname;
83
84 static int eflag;
85 static int isterm;
86 static struct termios tt;
87
88 __dead static void done(int);
89 __dead static void doshell(const char *);
90 __dead static void fail(void);
91 static void dooutput(void);
92 static void finish(int);
93 static void scriptflush(int);
94 static void record(FILE *, char *, size_t, int);
95 static void consume(FILE *, off_t, char *, int);
96 __dead static void playback(FILE *);
97
98 int
99 main(int argc, char *argv[])
100 {
101 ssize_t scc;
102 size_t cc;
103 struct termios rtt;
104 struct winsize win;
105 int aflg, pflg, ch;
106 char ibuf[BUFSIZ];
107 const char *command;
108
109 aflg = 0;
110 pflg = 0;
111 usesleep = 1;
112 rawout = 0;
113 quiet = 0;
114 flush = 0;
115 command = NULL;
116 while ((ch = getopt(argc, argv, "ac:defpqr")) != -1)
117 switch(ch) {
118 case 'a':
119 aflg = 1;
120 break;
121 case 'c':
122 command = optarg;
123 break;
124 case 'd':
125 usesleep = 0;
126 break;
127 case 'e':
128 eflag = 1;
129 break;
130 case 'f':
131 flush = 1;
132 break;
133 case 'p':
134 pflg = 1;
135 break;
136 case 'q':
137 quiet = 1;
138 break;
139 case 'r':
140 rawout = 1;
141 break;
142 case '?':
143 default:
144 (void)fprintf(stderr,
145 "Usage: %s [-c <command>][-adefpqr] [file]\n",
146 getprogname());
147 exit(EXIT_FAILURE);
148 }
149 argc -= optind;
150 argv += optind;
151
152 if (argc > 0)
153 fname = argv[0];
154 else
155 fname = "typescript";
156
157 if ((fscript = fopen(fname, pflg ? "r" : aflg ? "a" : "w")) == NULL)
158 err(EXIT_FAILURE, "fopen %s", fname);
159
160 if (pflg)
161 playback(fscript);
162
163 if (tcgetattr(STDIN_FILENO, &tt) == -1 ||
164 ioctl(STDIN_FILENO, TIOCGWINSZ, &win) == -1) {
165 if (errno != ENOTTY) /* For debugger. */
166 err(EXIT_FAILURE, "tcgetattr/ioctl");
167 if (openpty(&master, &slave, NULL, NULL, NULL) == -1)
168 err(EXIT_FAILURE, "openpty");
169 } else {
170 if (openpty(&master, &slave, NULL, &tt, &win) == -1)
171 err(EXIT_FAILURE, "openpty");
172 isterm = 1;
173 }
174
175 if (!quiet)
176 (void)printf("Script started, output file is %s\n", fname);
177
178 if (isterm) {
179 rtt = tt;
180 cfmakeraw(&rtt);
181 rtt.c_lflag &= ~ECHO;
182 (void)tcsetattr(STDIN_FILENO, TCSAFLUSH, &rtt);
183 }
184
185 (void)signal(SIGCHLD, finish);
186 child = fork();
187 if (child == -1) {
188 warn("fork");
189 fail();
190 }
191 if (child == 0) {
192 subchild = child = fork();
193 if (child == -1) {
194 warn("fork");
195 fail();
196 }
197 if (child)
198 dooutput();
199 else
200 doshell(command);
201 }
202
203 if (!rawout)
204 (void)fclose(fscript);
205 while ((scc = read(STDIN_FILENO, ibuf, BUFSIZ)) > 0) {
206 cc = (size_t)scc;
207 if (rawout)
208 record(fscript, ibuf, cc, 'i');
209 (void)write(master, ibuf, cc);
210 }
211 finish(-1);
212 }
213
214 static void
215 finish(int signo)
216 {
217 int die, pid, status, cstat;
218
219 die = 0;
220 while ((pid = wait(&status)) > 0)
221 if (pid == child) {
222 cstat = status;
223 die = 1;
224 }
225
226 if (!die)
227 return;
228 if (!eflag)
229 done(EXIT_SUCCESS);
230 else if (WIFEXITED(cstat))
231 done(WEXITSTATUS(cstat));
232 else
233 done(128 + WTERMSIG(cstat));
234 }
235
236 static void
237 dooutput(void)
238 {
239 struct itimerval value;
240 ssize_t scc;
241 size_t cc;
242 time_t tvec;
243 char obuf[BUFSIZ];
244
245 (void)close(STDIN_FILENO);
246 tvec = time(NULL);
247 if (rawout)
248 record(fscript, NULL, 0, 's');
249 else if (!quiet)
250 (void)fprintf(fscript, "Script started on %s", ctime(&tvec));
251
252 (void)signal(SIGALRM, scriptflush);
253 value.it_interval.tv_sec = SECSPERMIN / 2;
254 value.it_interval.tv_usec = 0;
255 value.it_value = value.it_interval;
256 (void)setitimer(ITIMER_REAL, &value, NULL);
257 for (;;) {
258 scc = read(master, obuf, sizeof(obuf));
259 if (scc <= 0)
260 break;
261 cc = (size_t)scc;
262 (void)write(STDOUT_FILENO, obuf, cc);
263 if (rawout)
264 record(fscript, obuf, cc, 'o');
265 else
266 (void)fwrite(obuf, 1, cc, fscript);
267 outcc += cc;
268 if (flush)
269 (void)fflush(fscript);
270 }
271 finish(-1);
272 }
273
274 static void
275 scriptflush(int signo)
276 {
277 if (outcc) {
278 (void)fflush(fscript);
279 outcc = 0;
280 }
281 }
282
283 static void
284 doshell(const char *command)
285 {
286 const char *shell;
287
288 (void)close(master);
289 (void)fclose(fscript);
290 login_tty(slave);
291 if (command == NULL) {
292 shell = getenv("SHELL");
293 if (shell == NULL)
294 shell = _PATH_BSHELL;
295 execl(shell, shell, "-i", NULL);
296 warn("execl `%s'", shell);
297 } else {
298 if (system(command) == -1)
299 warn("system `%s'", command);
300 }
301
302 fail();
303 }
304
305 static void
306 fail(void)
307 {
308
309 (void)kill(0, SIGTERM);
310 done(EXIT_FAILURE);
311 }
312
313 static void
314 done(int status)
315 {
316 time_t tvec;
317
318 if (subchild) {
319 tvec = time(NULL);
320 if (rawout)
321 record(fscript, NULL, 0, 'e');
322 else if (!quiet)
323 (void)fprintf(fscript,"\nScript done on %s",
324 ctime(&tvec));
325 (void)fclose(fscript);
326 (void)close(master);
327 } else {
328 if (isterm)
329 (void)tcsetattr(STDIN_FILENO, TCSAFLUSH, &tt);
330 if (!quiet)
331 (void)printf("Script done, output file is %s\n", fname);
332 }
333 exit(status);
334 }
335
336 static void
337 record(FILE *fp, char *buf, size_t cc, int direction)
338 {
339 struct iovec iov[2];
340 struct stamp stamp;
341 struct timeval tv;
342
343 (void)gettimeofday(&tv, NULL);
344 stamp.scr_len = cc;
345 stamp.scr_sec = tv.tv_sec;
346 stamp.scr_usec = tv.tv_usec;
347 stamp.scr_direction = direction;
348 iov[0].iov_len = sizeof(stamp);
349 iov[0].iov_base = &stamp;
350 iov[1].iov_len = cc;
351 iov[1].iov_base = buf;
352 if (writev(fileno(fp), &iov[0], 2) == -1)
353 err(EXIT_FAILURE, "writev");
354 }
355
356 static void
357 consume(FILE *fp, off_t len, char *buf, int reg)
358 {
359 size_t l;
360
361 if (reg) {
362 if (fseeko(fp, len, SEEK_CUR) == -1)
363 err(EXIT_FAILURE, NULL);
364 }
365 else {
366 while (len > 0) {
367 l = MIN(DEF_BUF, len);
368 if (fread(buf, sizeof(char), l, fp) != l)
369 err(EXIT_FAILURE, "cannot read buffer");
370 len -= l;
371 }
372 }
373 }
374
375 #define swapstamp(stamp) do { \
376 if (stamp.scr_direction > 0xff) { \
377 stamp.scr_len = bswap64(stamp.scr_len); \
378 stamp.scr_sec = bswap64(stamp.scr_sec); \
379 stamp.scr_usec = bswap32(stamp.scr_usec); \
380 stamp.scr_direction = bswap32(stamp.scr_direction); \
381 } \
382 } while (0/*CONSTCOND*/)
383
384 static void
385 termset(void)
386 {
387 struct termios traw;
388
389 if (tcgetattr(STDOUT_FILENO, &tt) == -1) {
390 if (errno != ENOTTY) /* For debugger. */
391 err(EXIT_FAILURE, "tcgetattr");
392 return;
393 }
394 isterm = 1;
395 traw = tt;
396 cfmakeraw(&traw);
397 traw.c_lflag |= ISIG;
398 (void)tcsetattr(STDOUT_FILENO, TCSANOW, &traw);
399 }
400
401 static void
402 termreset(void)
403 {
404 if (isterm)
405 (void)tcsetattr(STDOUT_FILENO, TCSADRAIN, &tt);
406
407 isterm = 0;
408 }
409
410 static void
411 playback(FILE *fp)
412 {
413 struct timespec tsi, tso;
414 struct stamp stamp;
415 struct stat pst;
416 char buf[DEF_BUF];
417 off_t nread, save_len;
418 size_t l;
419 time_t tclock;
420 int reg;
421
422 if (fstat(fileno(fp), &pst) == -1)
423 err(EXIT_FAILURE, "fstat failed");
424
425 reg = S_ISREG(pst.st_mode);
426
427 for (nread = 0; !reg || nread < pst.st_size; nread += save_len) {
428 if (fread(&stamp, sizeof(stamp), 1, fp) != 1) {
429 if (reg)
430 err(EXIT_FAILURE, "reading playback header");
431 else
432 break;
433 }
434 swapstamp(stamp);
435 save_len = sizeof(stamp);
436
437 if (reg && stamp.scr_len >
438 (uint64_t)(pst.st_size - save_len) - nread)
439 errx(EXIT_FAILURE, "invalid stamp");
440
441 save_len += stamp.scr_len;
442 tclock = stamp.scr_sec;
443 tso.tv_sec = stamp.scr_sec;
444 tso.tv_nsec = stamp.scr_usec * 1000;
445
446 switch (stamp.scr_direction) {
447 case 's':
448 if (!quiet)
449 (void)printf("Script started on %s",
450 ctime(&tclock));
451 tsi = tso;
452 (void)consume(fp, stamp.scr_len, buf, reg);
453 termset();
454 atexit(termreset);
455 break;
456 case 'e':
457 termreset();
458 if (!quiet)
459 (void)printf("\nScript done on %s",
460 ctime(&tclock));
461 (void)consume(fp, stamp.scr_len, buf, reg);
462 break;
463 case 'i':
464 /* throw input away */
465 (void)consume(fp, stamp.scr_len, buf, reg);
466 break;
467 case 'o':
468 tsi.tv_sec = tso.tv_sec - tsi.tv_sec;
469 tsi.tv_nsec = tso.tv_nsec - tsi.tv_nsec;
470 if (tsi.tv_nsec < 0) {
471 tsi.tv_sec -= 1;
472 tsi.tv_nsec += 1000000000;
473 }
474 if (usesleep)
475 (void)nanosleep(&tsi, NULL);
476 tsi = tso;
477 while (stamp.scr_len > 0) {
478 l = MIN(DEF_BUF, stamp.scr_len);
479 if (fread(buf, sizeof(char), l, fp) != l)
480 err(EXIT_FAILURE, "cannot read buffer");
481
482 (void)write(STDOUT_FILENO, buf, l);
483 stamp.scr_len -= l;
484 }
485 break;
486 default:
487 errx(EXIT_FAILURE, "invalid direction %u",
488 stamp.scr_direction);
489 }
490 }
491 (void)fclose(fp);
492 exit(EXIT_SUCCESS);
493 }
494