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