usr.sbin/makefs: Sync with sys/vfs/hammer2
[dragonfly.git] / usr.bin / script / script.c
blob8fa672cfbb69bf4043d3129a42b0e95d1ffa78b2
1 /*
2 * SPDX-License-Identifier: BSD-3-Clause
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.
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.
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.
32 * $FreeBSD: head/usr.bin/script/script.c 326025 2017-11-20 19:49:47Z pfg $
35 #include <sys/param.h>
36 #include <sys/wait.h>
37 #include <sys/stat.h>
38 #include <sys/ioctl.h>
39 #include <sys/time.h>
40 #include <sys/uio.h>
41 #include <sys/endian.h>
43 #include <err.h>
44 #include <errno.h>
45 #include <fcntl.h>
46 #include <libutil.h>
47 #include <paths.h>
48 #include <signal.h>
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include <termios.h>
53 #include <unistd.h>
55 #define DEF_BUF 65536
57 struct stamp {
58 uint64_t scr_len; /* amount of data */
59 uint64_t scr_sec; /* time it arrived in seconds... */
60 uint32_t scr_usec; /* ...and microseconds */
61 uint32_t scr_direction; /* 'i', 'o', etc (also indicates endianness) */
64 static FILE *fscript;
65 static int master, slave;
66 static int child;
67 static const char *fname;
68 static char *fmfname;
69 static int qflg, ttyflg;
70 static int usesleep, rawout, showexit;
72 static struct termios tt;
74 static void done(int) __dead2;
75 static void doshell(char **);
76 static void finish(void);
77 static void record(FILE *, char *, size_t, int);
78 static void consume(FILE *, off_t, char *, int);
79 static void playback(FILE *) __dead2;
80 static void usage(void);
82 int
83 main(int argc, char *argv[])
85 int cc;
86 struct termios rtt, stt;
87 struct winsize win;
88 struct timeval tv, *tvp;
89 time_t tvec, start;
90 char obuf[BUFSIZ];
91 char ibuf[BUFSIZ];
92 fd_set rfd;
93 int aflg, Fflg, kflg, pflg, ch, k, n;
94 int flushtime, readstdin;
96 aflg = Fflg = kflg = pflg = 0;
97 usesleep = 1;
98 rawout = 0;
99 flushtime = 30;
100 showexit = 0;
102 while ((ch = getopt(argc, argv, "adFfkpqrt:")) != -1)
103 switch(ch) {
104 case 'a':
105 aflg = 1;
106 break;
107 case 'd':
108 usesleep = 0;
109 break;
110 case 'F':
111 Fflg = 1;
112 break;
113 case 'k':
114 kflg = 1;
115 break;
116 case 'p':
117 pflg = 1;
118 break;
119 case 'q':
120 qflg = 1;
121 break;
122 case 'r':
123 rawout = 1;
124 break;
125 case 't':
126 flushtime = atoi(optarg);
127 if (flushtime < 0)
128 err(1, "invalid flush time %d", flushtime);
129 break;
130 case '?':
131 default:
132 usage();
134 argc -= optind;
135 argv += optind;
137 if (argc > 0) {
138 fname = argv[0];
139 argv++;
140 argc--;
141 } else
142 fname = "typescript";
144 if ((fscript = fopen(fname, pflg ? "r" : aflg ? "a" : "w")) == NULL)
145 err(1, "%s", fname);
147 if (pflg)
148 playback(fscript);
150 if ((ttyflg = isatty(STDIN_FILENO)) != 0) {
151 if (tcgetattr(STDIN_FILENO, &tt) == -1)
152 err(1, "tcgetattr");
153 if (ioctl(STDIN_FILENO, TIOCGWINSZ, &win) == -1)
154 err(1, "ioctl");
155 if (openpty(&master, &slave, NULL, &tt, &win) == -1)
156 err(1, "openpty");
157 } else {
158 if (openpty(&master, &slave, NULL, NULL, NULL) == -1)
159 err(1, "openpty");
162 if (rawout)
163 record(fscript, NULL, 0, 's');
165 if (!qflg) {
166 tvec = time(NULL);
167 printf("Script started, output file is %s\n", fname);
168 if (!rawout) {
169 fprintf(fscript, "Script started on %s",
170 ctime(&tvec));
171 if (argv[0]) {
172 showexit = 1;
173 fprintf(fscript, "Command: ");
174 for (k = 0 ; argv[k] ; ++k)
175 fprintf(fscript, "%s%s", k ? " " : "",
176 argv[k]);
177 fprintf(fscript, "\n");
180 fflush(fscript);
182 if (ttyflg) {
183 rtt = tt;
184 cfmakeraw(&rtt);
185 rtt.c_lflag &= ~ECHO;
186 tcsetattr(STDIN_FILENO, TCSAFLUSH, &rtt);
189 child = fork();
190 if (child < 0) {
191 warn("fork");
192 done(1);
194 if (child == 0)
195 doshell(argv);
197 close(slave);
199 start = tvec = time(0);
200 readstdin = 1;
201 for (;;) {
202 FD_ZERO(&rfd);
203 FD_SET(master, &rfd);
204 if (readstdin)
205 FD_SET(STDIN_FILENO, &rfd);
206 if (!readstdin && ttyflg) {
207 tv.tv_sec = 1;
208 tv.tv_usec = 0;
209 tvp = &tv;
210 readstdin = 1;
211 } else if (flushtime > 0) {
212 tv.tv_sec = flushtime - (tvec - start);
213 tv.tv_usec = 0;
214 tvp = &tv;
215 } else {
216 tvp = NULL;
218 n = select(master + 1, &rfd, 0, 0, tvp);
219 if (n < 0 && errno != EINTR)
220 break;
221 if (n > 0 && FD_ISSET(STDIN_FILENO, &rfd)) {
222 cc = read(STDIN_FILENO, ibuf, BUFSIZ);
223 if (cc < 0)
224 break;
225 if (cc == 0) {
226 if (tcgetattr(master, &stt) == 0 &&
227 (stt.c_lflag & ICANON) != 0) {
228 write(master, &stt.c_cc[VEOF], 1);
230 readstdin = 0;
232 if (cc > 0) {
233 if (rawout)
234 record(fscript, ibuf, cc, 'i');
235 write(master, ibuf, cc);
236 if (kflg && tcgetattr(master, &stt) >= 0 &&
237 ((stt.c_lflag & ECHO) == 0)) {
238 fwrite(ibuf, 1, cc, fscript);
242 if (n > 0 && FD_ISSET(master, &rfd)) {
243 cc = read(master, obuf, sizeof (obuf));
244 if (cc <= 0)
245 break;
246 write(STDOUT_FILENO, obuf, cc);
247 if (rawout)
248 record(fscript, obuf, cc, 'o');
249 else
250 fwrite(obuf, 1, cc, fscript);
252 tvec = time(0);
253 if (tvec - start >= flushtime) {
254 fflush(fscript);
255 start = tvec;
257 if (Fflg)
258 fflush(fscript);
260 finish();
261 done(0);
264 static void
265 usage(void)
267 fprintf(stderr,
268 "usage: script [-adkpqr] [-t time] [file [command ...]]\n");
269 exit(1);
272 static void
273 finish(void)
275 int e, status;
277 if (waitpid(child, &status, 0) == child) {
278 if (WIFEXITED(status))
279 e = WEXITSTATUS(status);
280 else if (WIFSIGNALED(status))
281 e = WTERMSIG(status);
282 else /* can't happen */
283 e = 1;
284 done(e);
288 static void
289 doshell(char **av)
291 const char *shell;
293 shell = getenv("SHELL");
294 if (shell == NULL)
295 shell = _PATH_BSHELL;
297 close(master);
298 fclose(fscript);
299 free(fmfname);
300 login_tty(slave);
301 setenv("SCRIPT", fname, 1);
302 if (av[0]) {
303 execvp(av[0], av);
304 warn("%s", av[0]);
305 } else {
306 execl(shell, shell, "-i", (char *)NULL);
307 warn("%s", shell);
309 exit(1);
312 static void
313 done(int eno)
315 time_t tvec;
317 if (ttyflg)
318 tcsetattr(STDIN_FILENO, TCSAFLUSH, &tt);
319 tvec = time(NULL);
320 if (rawout)
321 record(fscript, NULL, 0, 'e');
322 if (!qflg) {
323 if (!rawout) {
324 if (showexit)
325 fprintf(fscript, "\nCommand exit status:"
326 " %d", eno);
327 fprintf(fscript,"\nScript done on %s",
328 ctime(&tvec));
330 printf("\nScript done, output file is %s\n", fname);
332 fclose(fscript);
333 close(master);
334 exit(eno);
337 static void
338 record(FILE *fp, char *buf, size_t cc, int direction)
340 struct iovec iov[2];
341 struct stamp stamp;
342 struct timeval tv;
344 gettimeofday(&tv, NULL);
345 stamp.scr_len = cc;
346 stamp.scr_sec = tv.tv_sec;
347 stamp.scr_usec = tv.tv_usec;
348 stamp.scr_direction = direction;
349 iov[0].iov_len = sizeof(stamp);
350 iov[0].iov_base = &stamp;
351 iov[1].iov_len = cc;
352 iov[1].iov_base = buf;
353 if (writev(fileno(fp), &iov[0], 2) == -1)
354 err(1, "writev");
357 static void
358 consume(FILE *fp, off_t len, char *buf, int reg)
360 size_t l;
362 if (reg) {
363 if (fseeko(fp, len, SEEK_CUR) == -1)
364 err(1, NULL);
366 else {
367 while (len > 0) {
368 l = MIN(DEF_BUF, len);
369 if (fread(buf, sizeof(char), l, fp) != l)
370 err(1, "cannot read buffer");
371 len -= l;
376 #define swapstamp(stamp) do { \
377 if (stamp.scr_direction > 0xff) { \
378 stamp.scr_len = bswap64(stamp.scr_len); \
379 stamp.scr_sec = bswap64(stamp.scr_sec); \
380 stamp.scr_usec = bswap32(stamp.scr_usec); \
381 stamp.scr_direction = bswap32(stamp.scr_direction); \
383 } while (0/*CONSTCOND*/)
385 static void
386 playback(FILE *fp)
388 struct timespec tsi, tso;
389 struct stamp stamp;
390 struct stat pst;
391 char buf[DEF_BUF];
392 off_t nread, save_len;
393 size_t l;
394 time_t tclock;
395 int reg;
397 if (fstat(fileno(fp), &pst) == -1)
398 err(1, "fstat failed");
400 reg = S_ISREG(pst.st_mode);
402 for (nread = 0; !reg || nread < pst.st_size; nread += save_len) {
403 if (fread(&stamp, sizeof(stamp), 1, fp) != 1) {
404 if (reg)
405 err(1, "reading playback header");
406 else
407 break;
409 swapstamp(stamp);
410 save_len = sizeof(stamp);
412 if (reg && stamp.scr_len >
413 (uint64_t)(pst.st_size - save_len) - nread)
414 errx(1, "invalid stamp");
416 save_len += stamp.scr_len;
417 tclock = stamp.scr_sec;
418 tso.tv_sec = stamp.scr_sec;
419 tso.tv_nsec = stamp.scr_usec * 1000;
421 switch (stamp.scr_direction) {
422 case 's':
423 if (!qflg)
424 printf("Script started on %s",
425 ctime(&tclock));
426 tsi = tso;
427 consume(fp, stamp.scr_len, buf, reg);
428 break;
429 case 'e':
430 if (!qflg)
431 printf("\nScript done on %s",
432 ctime(&tclock));
433 consume(fp, stamp.scr_len, buf, reg);
434 break;
435 case 'i':
436 /* throw input away */
437 consume(fp, stamp.scr_len, buf, reg);
438 break;
439 case 'o':
440 tsi.tv_sec = tso.tv_sec - tsi.tv_sec;
441 tsi.tv_nsec = tso.tv_nsec - tsi.tv_nsec;
442 if (tsi.tv_nsec < 0) {
443 tsi.tv_sec -= 1;
444 tsi.tv_nsec += 1000000000;
446 if (usesleep)
447 nanosleep(&tsi, NULL);
448 tsi = tso;
449 while (stamp.scr_len > 0) {
450 l = MIN(DEF_BUF, stamp.scr_len);
451 if (fread(buf, sizeof(char), l, fp) != l)
452 err(1, "cannot read buffer");
454 write(STDOUT_FILENO, buf, l);
455 stamp.scr_len -= l;
457 break;
458 default:
459 errx(1, "invalid direction");
462 fclose(fp);
463 exit(0);