calendar(1): Remove redundant fr_FR.UTF-8/calendar.french
[dragonfly.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  * $FreeBSD: head/usr.bin/script/script.c 326025 2017-11-20 19:49:47Z pfg $
33  */
34
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>
42
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>
54
55 #define DEF_BUF 65536
56
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) */
62 };
63
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;
71
72 static struct termios tt;
73
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);
81
82 int
83 main(int argc, char *argv[])
84 {
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;
95
96         aflg = Fflg = kflg = pflg = 0;
97         usesleep = 1;
98         rawout = 0;
99         flushtime = 30;
100         showexit = 0;
101
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();
133                 }
134         argc -= optind;
135         argv += optind;
136
137         if (argc > 0) {
138                 fname = argv[0];
139                 argv++;
140                 argc--;
141         } else
142                 fname = "typescript";
143
144         if ((fscript = fopen(fname, pflg ? "r" : aflg ? "a" : "w")) == NULL)
145                 err(1, "%s", fname);
146
147         if (pflg)
148                 playback(fscript);
149
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");
160         }
161
162         if (rawout)
163                 record(fscript, NULL, 0, 's');
164
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");
178                         }
179                 }
180                 fflush(fscript);
181         }
182         if (ttyflg) {
183                 rtt = tt;
184                 cfmakeraw(&rtt);
185                 rtt.c_lflag &= ~ECHO;
186                 tcsetattr(STDIN_FILENO, TCSAFLUSH, &rtt);
187         }
188
189         child = fork();
190         if (child < 0) {
191                 warn("fork");
192                 done(1);
193         }
194         if (child == 0)
195                 doshell(argv);
196
197         close(slave);
198
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;
217                 }
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);
229                                 }
230                                 readstdin = 0;
231                         }
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);
239                                 }
240                         }
241                 }
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);
251                 }
252                 tvec = time(0);
253                 if (tvec - start >= flushtime) {
254                         fflush(fscript);
255                         start = tvec;
256                 }
257                 if (Fflg)
258                         fflush(fscript);
259         }
260         finish();
261         done(0);
262 }
263
264 static void
265 usage(void)
266 {
267         fprintf(stderr,
268             "usage: script [-adkpqr] [-t time] [file [command ...]]\n");
269         exit(1);
270 }
271
272 static void
273 finish(void)
274 {
275         int e, status;
276
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);
285         }
286 }
287
288 static void
289 doshell(char **av)
290 {
291         const char *shell;
292
293         shell = getenv("SHELL");
294         if (shell == NULL)
295                 shell = _PATH_BSHELL;
296
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);
308         }
309         exit(1);
310 }
311
312 static void
313 done(int eno)
314 {
315         time_t tvec;
316
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));
329                 }
330                 printf("\nScript done, output file is %s\n", fname);
331         }
332         fclose(fscript);
333         close(master);
334         exit(eno);
335 }
336
337 static void
338 record(FILE *fp, char *buf, size_t cc, int direction)
339 {
340         struct iovec iov[2];
341         struct stamp stamp;
342         struct timeval tv;
343
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");
355 }
356
357 static void
358 consume(FILE *fp, off_t len, char *buf, int reg)
359 {
360         size_t l;
361
362         if (reg) {
363                 if (fseeko(fp, len, SEEK_CUR) == -1)
364                         err(1, NULL);
365         }
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;
372                 }
373         }
374 }
375
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); \
382         } \
383 } while (0/*CONSTCOND*/)
384
385 static void
386 playback(FILE *fp)
387 {
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;
396
397         if (fstat(fileno(fp), &pst) == -1)
398                 err(1, "fstat failed");
399
400         reg = S_ISREG(pst.st_mode);
401
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;
408                 }
409                 swapstamp(stamp);
410                 save_len = sizeof(stamp);
411
412                 if (reg && stamp.scr_len >
413                     (uint64_t)(pst.st_size - save_len) - nread)
414                         errx(1, "invalid stamp");
415
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;
420
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;
445                         }
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");
453
454                                 write(STDOUT_FILENO, buf, l);
455                                 stamp.scr_len -= l;
456                         }
457                         break;
458                 default:
459                         errx(1, "invalid direction");
460                 }
461         }
462         fclose(fp);
463         exit(0);
464 }