1/*
2 * SPDX-License-Identifier: BSD-3-Clause
3 *
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.
7 *
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.
19 *
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.
31 */
32
33#include <sys/param.h>
34#include <sys/wait.h>
35#include <sys/stat.h>
36#include <sys/ioctl.h>
37#include <sys/time.h>
38#include <sys/queue.h>
39#include <sys/uio.h>
40#include <sys/endian.h>
41#include <dev/filemon/filemon.h>
42
43#include <assert.h>
44#include <err.h>
45#include <errno.h>
46#include <fcntl.h>
47#include <libutil.h>
48#include <paths.h>
49#include <signal.h>
50#include <stdio.h>
51#include <stdlib.h>
52#include <string.h>
53#include <termios.h>
54#include <unistd.h>
55
56#define DEF_BUF 65536
57
58struct stamp {
59	uint64_t scr_len;	/* amount of data */
60	uint64_t scr_sec;	/* time it arrived in seconds... */
61	uint32_t scr_usec;	/* ...and microseconds */
62	uint32_t scr_direction; /* 'i', 'o', etc (also indicates endianness) */
63};
64
65struct buf_elm {
66	TAILQ_ENTRY(buf_elm) link;
67	size_t rpos;
68	size_t len;
69	char ibuf[];
70};
71
72static FILE *fscript;
73static int master, slave;
74static int child;
75static const char *fname;
76static char *fmfname;
77static int fflg, qflg, ttyflg;
78static int usesleep, rawout, showexit;
79static TAILQ_HEAD(, buf_elm) obuf_list = TAILQ_HEAD_INITIALIZER(obuf_list);
80static volatile sig_atomic_t doresize;
81
82static struct termios tt;
83
84#ifndef TSTAMP_FMT
85/* useful for tool and human reading */
86# define TSTAMP_FMT "%n@ %s [%Y-%m-%d %T]%n"
87#endif
88static const char *tstamp_fmt = TSTAMP_FMT;
89static int tflg;
90
91static void done(int) __dead2;
92static void doshell(char **);
93static void finish(void);
94static void record(FILE *, char *, size_t, int);
95static void consume(FILE *, off_t, char *, int);
96static void playback(FILE *) __dead2;
97static void usage(void) __dead2;
98static void resizeit(int);
99
100int
101main(int argc, char *argv[])
102{
103	struct termios rtt, stt;
104	struct winsize win;
105	struct timespec tv, *tvp;
106	time_t tvec, start;
107	char obuf[BUFSIZ];
108	char ibuf[BUFSIZ];
109	sigset_t *pselmask, selmask;
110	fd_set rfd, wfd;
111	struct buf_elm *be;
112	ssize_t cc;
113	int aflg, Fflg, kflg, pflg, wflg, ch, k, n, fcm;
114	int flushtime, readstdin;
115	int fm_fd, fm_log;
116
117	aflg = Fflg = kflg = pflg = wflg = 0;
118	doresize = 0;
119	usesleep = 1;
120	rawout = 0;
121	flushtime = 30;
122	fm_fd = -1;
123	showexit = 0;
124
125	/*
126	 * For normal operation, we'll leave pselmask == NULL so that pselect(2)
127	 * leaves the signal mask alone.  If -w is specified, we'll restore the
128	 * process signal mask upon entry with SIGWINCH unblocked so that we can
129	 * forward resize events properly.
130	 */
131	sigemptyset(&selmask);
132	pselmask = NULL;
133
134	while ((ch = getopt(argc, argv, "adeFfkpqrT:t:w")) != -1)
135		switch (ch) {
136		case 'a':
137			aflg = 1;
138			break;
139		case 'd':
140			usesleep = 0;
141			break;
142		case 'e':
143			/* Default behavior, accepted for linux compat. */
144			break;
145		case 'F':
146			Fflg = 1;
147			break;
148		case 'f':
149			fflg = 1;
150			break;
151		case 'k':
152			kflg = 1;
153			break;
154		case 'p':
155			pflg = 1;
156			break;
157		case 'q':
158			qflg = 1;
159			break;
160		case 'r':
161			rawout = 1;
162			break;
163		case 't':
164			flushtime = atoi(optarg);
165			if (flushtime < 0)
166				err(1, "invalid flush time %d", flushtime);
167			break;
168		case 'T':
169			tflg = pflg = 1;
170			if (strchr(optarg, '%'))
171				tstamp_fmt = optarg;
172			break;
173		case 'w':
174			wflg = 1;
175			break;
176		case '?':
177		default:
178			usage();
179		}
180	argc -= optind;
181	argv += optind;
182
183	if (argc > 0) {
184		fname = argv[0];
185		argv++;
186		argc--;
187	} else
188		fname = "typescript";
189
190	if ((fscript = fopen(fname, pflg ? "r" : aflg ? "a" : "w")) == NULL)
191		err(1, "%s", fname);
192
193	if (fflg) {
194		asprintf(&fmfname, "%s.filemon", fname);
195		if (!fmfname)
196			err(1, "%s.filemon", fname);
197		if ((fm_fd = open("/dev/filemon", O_RDWR | O_CLOEXEC)) == -1)
198			err(1, "open(\"/dev/filemon\", O_RDWR)");
199		if ((fm_log = open(fmfname,
200		    O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC,
201		    S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1)
202			err(1, "open(%s)", fmfname);
203		if (ioctl(fm_fd, FILEMON_SET_FD, &fm_log) < 0)
204			err(1, "Cannot set filemon log file descriptor");
205	}
206
207	if (pflg)
208		playback(fscript);
209
210	if (tcgetattr(STDIN_FILENO, &tt) == -1 ||
211	    ioctl(STDIN_FILENO, TIOCGWINSZ, &win) == -1) {
212		if (errno != ENOTTY) /* For debugger. */
213			err(1, "tcgetattr/ioctl");
214		if (openpty(&master, &slave, NULL, NULL, NULL) == -1)
215			err(1, "openpty");
216	} else {
217		if (openpty(&master, &slave, NULL, &tt, &win) == -1)
218			err(1, "openpty");
219		ttyflg = 1;
220	}
221	fcm = fcntl(master, F_GETFL);
222	if (fcm == -1)
223		err(1, "master F_GETFL");
224	fcm |= O_NONBLOCK;
225	if (fcntl(master, F_SETFL, fcm) == -1)
226		err(1, "master F_SETFL");
227
228	if (rawout)
229		record(fscript, NULL, 0, 's');
230
231	if (!qflg) {
232		tvec = time(NULL);
233		(void)printf("Script started, output file is %s\n", fname);
234		if (!rawout) {
235			(void)fprintf(fscript, "Script started on %s",
236			    ctime(&tvec));
237			if (argv[0]) {
238				showexit = 1;
239				fprintf(fscript, "Command: ");
240				for (k = 0 ; argv[k] ; ++k)
241					fprintf(fscript, "%s%s", k ? " " : "",
242						argv[k]);
243				fprintf(fscript, "\n");
244			}
245		}
246		fflush(fscript);
247		if (fflg) {
248			(void)printf("Filemon started, output file is %s\n",
249			    fmfname);
250		}
251	}
252	if (ttyflg) {
253		rtt = tt;
254		cfmakeraw(&rtt);
255		rtt.c_lflag &= ~ECHO;
256		(void)tcsetattr(STDIN_FILENO, TCSAFLUSH, &rtt);
257	}
258
259	assert(fflg ? fm_fd >= 0 : fm_fd < 0);
260
261	child = fork();
262	if (child < 0) {
263		warn("fork");
264		done(1);
265	}
266	if (child == 0) {
267		if (fflg) {
268			int pid;
269
270			pid = getpid();
271			if (ioctl(fm_fd, FILEMON_SET_PID, &pid) < 0)
272				err(1, "Cannot set filemon PID");
273		}
274
275		doshell(argv);
276	}
277	close(slave);
278
279	if (wflg) {
280		struct sigaction sa = { .sa_handler = resizeit };
281		sigset_t smask;
282
283		sigaction(SIGWINCH, &sa, NULL);
284
285		sigemptyset(&smask);
286		sigaddset(&smask, SIGWINCH);
287
288		if (sigprocmask(SIG_BLOCK, &smask, &selmask) != 0)
289			err(1, "Failed to block SIGWINCH");
290
291		/* Just in case SIGWINCH was blocked before we came in. */
292		sigdelset(&selmask, SIGWINCH);
293		pselmask = &selmask;
294	}
295
296	start = tvec = time(0);
297	readstdin = 1;
298	for (;;) {
299		FD_ZERO(&rfd);
300		FD_ZERO(&wfd);
301		FD_SET(master, &rfd);
302		if (readstdin)
303			FD_SET(STDIN_FILENO, &rfd);
304		if (!TAILQ_EMPTY(&obuf_list))
305			FD_SET(master, &wfd);
306		if (!readstdin && ttyflg) {
307			tv.tv_sec = 1;
308			tv.tv_nsec = 0;
309			tvp = &tv;
310			readstdin = 1;
311		} else if (flushtime > 0) {
312			tv.tv_sec = flushtime - (tvec - start);
313			tv.tv_nsec = 0;
314			tvp = &tv;
315		} else {
316			tvp = NULL;
317		}
318		n = pselect(master + 1, &rfd, &wfd, NULL, tvp, pselmask);
319		if (n < 0 && errno != EINTR)
320			break;
321
322		if (doresize) {
323			if (ioctl(STDIN_FILENO, TIOCGWINSZ, &win) != -1)
324				ioctl(master, TIOCSWINSZ, &win);
325			doresize = 0;
326		}
327
328		if (n > 0 && FD_ISSET(STDIN_FILENO, &rfd)) {
329			cc = read(STDIN_FILENO, ibuf, BUFSIZ);
330			if (cc < 0)
331				break;
332			if (cc == 0) {
333				if (tcgetattr(master, &stt) == 0 &&
334				    (stt.c_lflag & ICANON) != 0) {
335					(void)write(master, &stt.c_cc[VEOF], 1);
336				}
337				readstdin = 0;
338			}
339			if (cc > 0) {
340				if (rawout)
341					record(fscript, ibuf, cc, 'i');
342				be = malloc(sizeof(*be) + cc);
343				be->rpos = 0;
344				be->len = cc;
345				memcpy(be->ibuf, ibuf, cc);
346				TAILQ_INSERT_TAIL(&obuf_list, be, link);
347			}
348		}
349		if (n > 0 && FD_ISSET(master, &wfd)) {
350			while ((be = TAILQ_FIRST(&obuf_list)) != NULL) {
351				cc = write(master, be->ibuf + be->rpos,
352				    be->len);
353				if (cc == -1) {
354					if (errno == EWOULDBLOCK ||
355					    errno == EINTR)
356						break;
357					warn("write master");
358					done(1);
359				}
360				if (cc == 0)
361					break;		/* retry later ? */
362				if (kflg && tcgetattr(master, &stt) >= 0 &&
363				    ((stt.c_lflag & ECHO) == 0)) {
364					(void)fwrite(be->ibuf + be->rpos,
365					    1, cc, fscript);
366				}
367				be->len -= cc;
368				if (be->len == 0) {
369					TAILQ_REMOVE(&obuf_list, be, link);
370					free(be);
371				} else {
372					be->rpos += cc;
373				}
374			}
375		}
376		if (n > 0 && FD_ISSET(master, &rfd)) {
377			cc = read(master, obuf, sizeof(obuf));
378			if (cc <= 0)
379				break;
380			(void)write(STDOUT_FILENO, obuf, cc);
381			if (rawout)
382				record(fscript, obuf, cc, 'o');
383			else
384				(void)fwrite(obuf, 1, cc, fscript);
385		}
386		tvec = time(0);
387		if (tvec - start >= flushtime) {
388			fflush(fscript);
389			start = tvec;
390		}
391		if (Fflg)
392			fflush(fscript);
393	}
394	finish();
395	done(0);
396}
397
398static void
399usage(void)
400{
401	(void)fprintf(stderr,
402	    "usage: script [-aeFfkpqrw] [-t time] [file [command ...]]\n");
403	(void)fprintf(stderr,
404	    "       script -p [-deq] [-T fmt] [file]\n");
405	exit(1);
406}
407
408static void
409finish(void)
410{
411	int e, status;
412
413	if (waitpid(child, &status, 0) == child) {
414		if (WIFEXITED(status))
415			e = WEXITSTATUS(status);
416		else if (WIFSIGNALED(status))
417			e = WTERMSIG(status);
418		else /* can't happen */
419			e = 1;
420		done(e);
421	}
422}
423
424static void
425doshell(char **av)
426{
427	const char *shell;
428
429	shell = getenv("SHELL");
430	if (shell == NULL)
431		shell = _PATH_BSHELL;
432
433	(void)close(master);
434	(void)fclose(fscript);
435	free(fmfname);
436	login_tty(slave);
437	setenv("SCRIPT", fname, 1);
438	if (av[0]) {
439		execvp(av[0], av);
440		warn("%s", av[0]);
441	} else {
442		execl(shell, shell, "-i", (char *)NULL);
443		warn("%s", shell);
444	}
445	exit(1);
446}
447
448static void
449done(int eno)
450{
451	time_t tvec;
452
453	if (ttyflg)
454		(void)tcsetattr(STDIN_FILENO, TCSAFLUSH, &tt);
455	tvec = time(NULL);
456	if (rawout)
457		record(fscript, NULL, 0, 'e');
458	if (!qflg) {
459		if (!rawout) {
460			if (showexit)
461				(void)fprintf(fscript, "\nCommand exit status:"
462				    " %d", eno);
463			(void)fprintf(fscript, "\nScript done on %s",
464			    ctime(&tvec));
465		}
466		(void)printf("\nScript done, output file is %s\n", fname);
467		if (fflg) {
468			(void)printf("Filemon done, output file is %s\n",
469			    fmfname);
470		}
471	}
472	(void)fclose(fscript);
473	(void)close(master);
474	exit(eno);
475}
476
477static void
478record(FILE *fp, char *buf, size_t cc, int direction)
479{
480	struct iovec iov[2];
481	struct stamp stamp;
482	struct timeval tv;
483
484	(void)gettimeofday(&tv, NULL);
485	stamp.scr_len = cc;
486	stamp.scr_sec = tv.tv_sec;
487	stamp.scr_usec = tv.tv_usec;
488	stamp.scr_direction = direction;
489	iov[0].iov_len = sizeof(stamp);
490	iov[0].iov_base = &stamp;
491	iov[1].iov_len = cc;
492	iov[1].iov_base = buf;
493	if (writev(fileno(fp), &iov[0], 2) == -1)
494		err(1, "writev");
495}
496
497static void
498consume(FILE *fp, off_t len, char *buf, int reg)
499{
500	size_t l;
501
502	if (reg) {
503		if (fseeko(fp, len, SEEK_CUR) == -1)
504			err(1, NULL);
505	} else {
506		while (len > 0) {
507			l = MIN(DEF_BUF, len);
508			if (fread(buf, sizeof(char), l, fp) != l)
509				err(1, "cannot read buffer");
510			len -= l;
511		}
512	}
513}
514
515#define swapstamp(stamp) do { \
516	if (stamp.scr_direction > 0xff) { \
517		stamp.scr_len = bswap64(stamp.scr_len); \
518		stamp.scr_sec = bswap64(stamp.scr_sec); \
519		stamp.scr_usec = bswap32(stamp.scr_usec); \
520		stamp.scr_direction = bswap32(stamp.scr_direction); \
521	} \
522} while (0/*CONSTCOND*/)
523
524static void
525termset(void)
526{
527	struct termios traw;
528
529	if (tcgetattr(STDOUT_FILENO, &tt) == -1) {
530		if (errno != ENOTTY) /* For debugger. */
531			err(1, "tcgetattr");
532		return;
533	}
534	ttyflg = 1;
535	traw = tt;
536	cfmakeraw(&traw);
537	traw.c_lflag |= ISIG;
538	(void)tcsetattr(STDOUT_FILENO, TCSANOW, &traw);
539}
540
541static void
542termreset(void)
543{
544	if (ttyflg) {
545		tcsetattr(STDOUT_FILENO, TCSADRAIN, &tt);
546		ttyflg = 0;
547	}
548}
549
550static void
551playback(FILE *fp)
552{
553	struct timespec tsi, tso;
554	struct stamp stamp;
555	struct stat pst;
556	char buf[DEF_BUF];
557	off_t nread, save_len;
558	size_t l;
559	time_t tclock;
560	time_t lclock;
561	int reg;
562
563	if (fstat(fileno(fp), &pst) == -1)
564		err(1, "fstat failed");
565
566	reg = S_ISREG(pst.st_mode);
567	lclock = 0;
568
569	for (nread = 0; !reg || nread < pst.st_size; nread += save_len) {
570		if (fread(&stamp, sizeof(stamp), 1, fp) != 1) {
571			if (reg)
572				err(1, "reading playback header");
573			else
574				break;
575		}
576		swapstamp(stamp);
577		save_len = sizeof(stamp);
578
579		if (reg && stamp.scr_len >
580		    (uint64_t)(pst.st_size - save_len) - nread)
581			errx(1, "invalid stamp");
582
583		save_len += stamp.scr_len;
584		tclock = stamp.scr_sec;
585		tso.tv_sec = stamp.scr_sec;
586		tso.tv_nsec = stamp.scr_usec * 1000;
587		if (nread == 0)
588			tsi = tso;
589
590		switch (stamp.scr_direction) {
591		case 's':
592			if (!qflg)
593			    (void)printf("Script started on %s",
594				ctime(&tclock));
595			tsi = tso;
596			(void)consume(fp, stamp.scr_len, buf, reg);
597			termset();
598			atexit(termreset);
599			break;
600		case 'e':
601			termreset();
602			if (!qflg)
603				(void)printf("\nScript done on %s",
604				    ctime(&tclock));
605			(void)consume(fp, stamp.scr_len, buf, reg);
606			break;
607		case 'i':
608			/* throw input away */
609			(void)consume(fp, stamp.scr_len, buf, reg);
610			break;
611		case 'o':
612			if (tflg) {
613				if (stamp.scr_len == 0)
614					continue;
615				if (tclock - lclock > 0) {
616				    l = strftime(buf, sizeof buf, tstamp_fmt,
617					localtime(&tclock));
618				    (void)write(STDOUT_FILENO, buf, l);
619				}
620				lclock = tclock;
621			} else {
622				tsi.tv_sec = tso.tv_sec - tsi.tv_sec;
623				tsi.tv_nsec = tso.tv_nsec - tsi.tv_nsec;
624				if (tsi.tv_nsec < 0) {
625					tsi.tv_sec -= 1;
626					tsi.tv_nsec += 1000000000;
627				}
628				if (usesleep)
629					(void)nanosleep(&tsi, NULL);
630				tsi = tso;
631			}
632			while (stamp.scr_len > 0) {
633				l = MIN(DEF_BUF, stamp.scr_len);
634				if (fread(buf, sizeof(char), l, fp) != l)
635					err(1, "cannot read buffer");
636
637				(void)write(STDOUT_FILENO, buf, l);
638				stamp.scr_len -= l;
639			}
640			break;
641		default:
642			errx(1, "invalid direction");
643		}
644	}
645	(void)fclose(fp);
646	exit(0);
647}
648
649static void
650resizeit(int signo __unused)
651{
652	doresize = 1;
653}
654