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