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
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
32 * $FreeBSD: head/usr.bin/script/script.c 326025 2017-11-20 19:49:47Z pfg $
35 #include <sys/param.h>
38 #include <sys/ioctl.h>
41 #include <sys/endian.h>
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) */
65 static int master
, slave
;
67 static const char *fname
;
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);
83 main(int argc
, char *argv
[])
86 struct termios rtt
, stt
;
88 struct timeval tv
, *tvp
;
93 int aflg
, Fflg
, kflg
, pflg
, ch
, k
, n
;
94 int flushtime
, readstdin
;
96 aflg
= Fflg
= kflg
= pflg
= 0;
102 while ((ch
= getopt(argc
, argv
, "adFfkpqrt:")) != -1)
126 flushtime
= atoi(optarg
);
128 err(1, "invalid flush time %d", flushtime
);
142 fname
= "typescript";
144 if ((fscript
= fopen(fname
, pflg
? "r" : aflg
? "a" : "w")) == NULL
)
150 if ((ttyflg
= isatty(STDIN_FILENO
)) != 0) {
151 if (tcgetattr(STDIN_FILENO
, &tt
) == -1)
153 if (ioctl(STDIN_FILENO
, TIOCGWINSZ
, &win
) == -1)
155 if (openpty(&master
, &slave
, NULL
, &tt
, &win
) == -1)
158 if (openpty(&master
, &slave
, NULL
, NULL
, NULL
) == -1)
163 record(fscript
, NULL
, 0, 's');
167 printf("Script started, output file is %s\n", fname
);
169 fprintf(fscript
, "Script started on %s",
173 fprintf(fscript
, "Command: ");
174 for (k
= 0 ; argv
[k
] ; ++k
)
175 fprintf(fscript
, "%s%s", k
? " " : "",
177 fprintf(fscript
, "\n");
185 rtt
.c_lflag
&= ~ECHO
;
186 tcsetattr(STDIN_FILENO
, TCSAFLUSH
, &rtt
);
199 start
= tvec
= time(0);
203 FD_SET(master
, &rfd
);
205 FD_SET(STDIN_FILENO
, &rfd
);
206 if (!readstdin
&& ttyflg
) {
211 } else if (flushtime
> 0) {
212 tv
.tv_sec
= flushtime
- (tvec
- start
);
218 n
= select(master
+ 1, &rfd
, 0, 0, tvp
);
219 if (n
< 0 && errno
!= EINTR
)
221 if (n
> 0 && FD_ISSET(STDIN_FILENO
, &rfd
)) {
222 cc
= read(STDIN_FILENO
, ibuf
, BUFSIZ
);
226 if (tcgetattr(master
, &stt
) == 0 &&
227 (stt
.c_lflag
& ICANON
) != 0) {
228 write(master
, &stt
.c_cc
[VEOF
], 1);
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
));
246 write(STDOUT_FILENO
, obuf
, cc
);
248 record(fscript
, obuf
, cc
, 'o');
250 fwrite(obuf
, 1, cc
, fscript
);
253 if (tvec
- start
>= flushtime
) {
268 "usage: script [-adkpqr] [-t time] [file [command ...]]\n");
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 */
293 shell
= getenv("SHELL");
295 shell
= _PATH_BSHELL
;
301 setenv("SCRIPT", fname
, 1);
306 execl(shell
, shell
, "-i", (char *)NULL
);
318 tcsetattr(STDIN_FILENO
, TCSAFLUSH
, &tt
);
321 record(fscript
, NULL
, 0, 'e');
325 fprintf(fscript
, "\nCommand exit status:"
327 fprintf(fscript
,"\nScript done on %s",
330 printf("\nScript done, output file is %s\n", fname
);
338 record(FILE *fp
, char *buf
, size_t cc
, int direction
)
344 gettimeofday(&tv
, NULL
);
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
;
352 iov
[1].iov_base
= buf
;
353 if (writev(fileno(fp
), &iov
[0], 2) == -1)
358 consume(FILE *fp
, off_t len
, char *buf
, int reg
)
363 if (fseeko(fp
, len
, SEEK_CUR
) == -1)
368 l
= MIN(DEF_BUF
, len
);
369 if (fread(buf
, sizeof(char), l
, fp
) != l
)
370 err(1, "cannot read buffer");
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*/)
388 struct timespec tsi
, tso
;
392 off_t nread
, save_len
;
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) {
405 err(1, "reading playback header");
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
) {
424 printf("Script started on %s",
427 consume(fp
, stamp
.scr_len
, buf
, reg
);
431 printf("\nScript done on %s",
433 consume(fp
, stamp
.scr_len
, buf
, reg
);
436 /* throw input away */
437 consume(fp
, stamp
.scr_len
, buf
, reg
);
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) {
444 tsi
.tv_nsec
+= 1000000000;
447 nanosleep(&tsi
, NULL
);
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
);
459 errx(1, "invalid direction");