2 * Copyright (C) 1980 Regents of the University of California.
3 * Copyright (C) 2013-2019 Karel Zak <kzak@redhat.com>
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. All advertising materials mentioning features or use of this software
16 * must display the following acknowledgement:
17 * This product includes software developed by the University of
18 * California, Berkeley and its contributors.
19 * 4. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
41 #include <sys/ioctl.h>
54 #include <sys/signalfd.h>
58 #include "closestream.h"
63 #include "monotonic.h"
64 #include "timeutils.h"
69 #include "pty-session.h"
72 static UL_DEBUG_DEFINE_MASK(script
);
73 UL_DEBUG_DEFINE_MASKNAMES(script
) = UL_DEBUG_EMPTY_MASKNAMES
;
75 #define SCRIPT_DEBUG_INIT (1 << 1)
76 #define SCRIPT_DEBUG_PTY (1 << 2)
77 #define SCRIPT_DEBUG_IO (1 << 3)
78 #define SCRIPT_DEBUG_SIGNAL (1 << 4)
79 #define SCRIPT_DEBUG_MISC (1 << 5)
80 #define SCRIPT_DEBUG_ALL 0xFFFF
82 #define DBG(m, x) __UL_DBG(script, SCRIPT_DEBUG_, m, x)
83 #define ON_DBG(m, x) __UL_DBG_CALL(script, SCRIPT_DEBUG_, m, x)
85 #ifdef HAVE_LIBUTEMPTER
86 # include <utempter.h>
89 #define DEFAULT_TYPESCRIPT_FILENAME "typescript"
92 * Script is driven by stream (stdout/stdin) activity. It's possible to
93 * associate arbitrary number of log files with the stream. We have two basic
94 * types of log files: "timing file" (simple or multistream) and "data file"
97 * The same log file maybe be shared between both streams. For example
98 * multi-stream timing file is possible to use for stdin as well as for stdout.
101 SCRIPT_FMT_RAW
= 1, /* raw slave/master data */
102 SCRIPT_FMT_TIMING_SIMPLE
, /* (classic) in format "<delta> <offset>" */
103 SCRIPT_FMT_TIMING_MULTI
, /* (advanced) multiple streams in format "<type> <delta> <offset|etc> */
107 FILE *fp
; /* file pointer (handler) */
108 int format
; /* SCRIPT_FMT_* */
109 char *filename
; /* on command line specified name */
110 struct timeval oldtime
; /* previous entry log time (SCRIPT_FMT_TIMING_* only) */
111 struct timeval starttime
;
113 unsigned int initialized
: 1;
116 struct script_stream
{
117 struct script_log
**logs
; /* logs where to write data from stream */
118 size_t nlogs
; /* number of logs */
119 char ident
; /* stream identifier */
122 struct script_control
{
123 uint64_t outsz
; /* current output files size */
124 uint64_t maxsz
; /* maximum output files size */
126 struct script_stream out
; /* output */
127 struct script_stream in
; /* input */
129 struct script_log
*siglog
; /* log for signal entries */
130 struct script_log
*infolog
; /* log for info entries */
137 struct ul_pty
*pty
; /* pseudo-terminal */
138 pid_t child
; /* child pid */
139 int childstatus
; /* child process exit value */
142 append
:1, /* append output */
143 rc_wanted
:1, /* return child exit value */
144 flush
:1, /* flush after each write */
145 quiet
:1, /* suppress most output */
146 force
:1, /* write output to links */
147 isterm
:1; /* is child process running as terminal */
150 static ssize_t
log_info(struct script_control
*ctl
, const char *name
, const char *msgfmt
, ...);
152 static void script_init_debug(void)
154 __UL_INIT_DEBUG_FROM_ENV(script
, SCRIPT_DEBUG_
, 0, SCRIPT_DEBUG
);
157 static void init_terminal_info(struct script_control
*ctl
)
159 if (ctl
->ttyname
|| !ctl
->isterm
)
160 return; /* already initialized */
162 get_terminal_dimension(&ctl
->ttycols
, &ctl
->ttylines
);
163 get_terminal_name(&ctl
->ttyname
, NULL
, NULL
);
164 get_terminal_type(&ctl
->ttytype
);
168 * For tests we want to be able to control time output
171 static inline time_t script_time(time_t *t
)
173 const char *str
= getenv("SCRIPT_TEST_SECOND_SINCE_EPOCH");
176 if (!str
|| sscanf(str
, "%"SCNi64
, &sec
) != 1)
182 #else /* !TEST_SCRIPT */
183 # define script_time(x) time(x)
186 static void __attribute__((__noreturn__
)) usage(void)
189 fputs(USAGE_HEADER
, out
);
190 fprintf(out
, _(" %s [options] [file]\n"), program_invocation_short_name
);
192 fputs(USAGE_SEPARATOR
, out
);
193 fputs(_("Make a typescript of a terminal session.\n"), out
);
195 fputs(USAGE_OPTIONS
, out
);
196 fputs(_(" -I, --log-in <file> log stdin to file\n"), out
);
197 fputs(_(" -O, --log-out <file> log stdout to file (default)\n"), out
);
198 fputs(_(" -B, --log-io <file> log stdin and stdout to file\n"), out
);
199 fputs(USAGE_SEPARATOR
, out
);
201 fputs(_(" -T, --log-timing <file> log timing information to file\n"), out
);
202 fputs(_(" -t[<file>], --timing[=<file>] deprecated alias to -T (default file is stderr)\n"), out
);
203 fputs(_(" -m, --logging-format <name> force to 'classic' or 'advanced' format\n"), out
);
204 fputs(USAGE_SEPARATOR
, out
);
206 fputs(_(" -a, --append append to the log file\n"), out
);
207 fputs(_(" -c, --command <command> run command rather than interactive shell\n"), out
);
208 fputs(_(" -e, --return return exit code of the child process\n"), out
);
209 fputs(_(" -f, --flush run flush after each write\n"), out
);
210 fputs(_(" --force use output file even when it is a link\n"), out
);
211 fputs(_(" -E, --echo <when> echo input (auto, always or never)\n"), out
);
212 fputs(_(" -o, --output-limit <size> terminate if output files exceed size\n"), out
);
213 fputs(_(" -q, --quiet be quiet\n"), out
);
215 fputs(USAGE_SEPARATOR
, out
);
216 printf(USAGE_HELP_OPTIONS(31));
217 printf(USAGE_MAN_TAIL("script(1)"));
222 static struct script_log
*get_log_by_name(struct script_stream
*stream
,
227 for (i
= 0; i
< stream
->nlogs
; i
++) {
228 struct script_log
*log
= stream
->logs
[i
];
229 if (strcmp(log
->filename
, name
) == 0)
235 static struct script_log
*log_associate(struct script_control
*ctl
,
236 struct script_stream
*stream
,
237 const char *filename
, int format
)
239 struct script_log
*log
;
241 DBG(MISC
, ul_debug("associate %s with stream", filename
));
247 log
= get_log_by_name(stream
, filename
);
249 return log
; /* already defined */
251 log
= get_log_by_name(stream
== &ctl
->out
? &ctl
->in
: &ctl
->out
, filename
);
253 /* create a new log */
254 log
= xcalloc(1, sizeof(*log
));
255 log
->filename
= xstrdup(filename
);
256 log
->format
= format
;
259 /* add log to the stream */
260 stream
->logs
= xrealloc(stream
->logs
,
261 (stream
->nlogs
+ 1) * sizeof(log
));
262 stream
->logs
[stream
->nlogs
] = log
;
265 /* remember where to write info about signals */
266 if (format
== SCRIPT_FMT_TIMING_MULTI
) {
276 static int log_close(struct script_control
*ctl
,
277 struct script_log
*log
,
283 if (!log
|| !log
->initialized
)
286 DBG(MISC
, ul_debug("closing %s", log
->filename
));
288 switch (log
->format
) {
291 char buf
[FORMAT_TIMESTAMP_MAX
];
292 time_t tvec
= script_time((time_t *)NULL
);
294 strtime_iso(&tvec
, ISO_TIMESTAMP
, buf
, sizeof(buf
));
296 fprintf(log
->fp
, _("\nScript done on %s [<%s>]\n"), buf
, msg
);
298 fprintf(log
->fp
, _("\nScript done on %s [COMMAND_EXIT_CODE=\"%d\"]\n"), buf
, status
);
301 case SCRIPT_FMT_TIMING_MULTI
:
303 struct timeval now
, delta
;
305 gettime_monotonic(&now
);
306 timersub(&now
, &log
->starttime
, &delta
);
308 log_info(ctl
, "DURATION", "%ld.%06ld",
309 (long)delta
.tv_sec
, (long)delta
.tv_usec
);
310 log_info(ctl
, "EXIT_CODE", "%d", status
);
313 case SCRIPT_FMT_TIMING_SIMPLE
:
317 if (close_stream(log
->fp
) != 0) {
318 warn(_("write failed: %s"), log
->filename
);
323 memset(log
, 0, sizeof(*log
));
328 static int log_flush(struct script_control
*ctl
__attribute__((__unused__
)), struct script_log
*log
)
331 if (!log
|| !log
->initialized
)
334 DBG(MISC
, ul_debug("flushing %s", log
->filename
));
340 static void log_free(struct script_control
*ctl
, struct script_log
*log
)
347 /* the same log is possible to reference from more places, remove all
348 * (TODO: maybe use include/list.h to make it more elegant)
350 if (ctl
->siglog
== log
)
352 else if (ctl
->infolog
== log
)
355 for (i
= 0; i
< ctl
->out
.nlogs
; i
++) {
356 if (ctl
->out
.logs
[i
] == log
)
357 ctl
->out
.logs
[i
] = NULL
;
359 for (i
= 0; i
< ctl
->in
.nlogs
; i
++) {
360 if (ctl
->in
.logs
[i
] == log
)
361 ctl
->in
.logs
[i
] = NULL
;
366 static int log_start(struct script_control
*ctl
,
367 struct script_log
*log
)
369 if (log
->initialized
)
372 DBG(MISC
, ul_debug("opening %s", log
->filename
));
374 assert(log
->fp
== NULL
);
377 log
->fp
= fopen(log
->filename
,
378 ctl
->append
&& log
->format
== SCRIPT_FMT_RAW
?
382 warn(_("cannot open %s"), log
->filename
);
386 /* write header, etc. */
387 switch (log
->format
) {
390 char buf
[FORMAT_TIMESTAMP_MAX
];
391 time_t tvec
= script_time((time_t *)NULL
);
393 strtime_iso(&tvec
, ISO_TIMESTAMP
, buf
, sizeof(buf
));
394 fprintf(log
->fp
, _("Script started on %s ["), buf
);
397 init_terminal_info(ctl
);
400 fprintf(log
->fp
, "TERM=\"%s\" ", ctl
->ttytype
);
402 fprintf(log
->fp
, "TTY=\"%s\" ", ctl
->ttyname
);
404 fprintf(log
->fp
, "COLUMNS=\"%d\" LINES=\"%d\"", ctl
->ttycols
, ctl
->ttylines
);
406 fprintf(log
->fp
, _("<not executed on terminal>"));
408 fputs("]\n", log
->fp
);
411 case SCRIPT_FMT_TIMING_SIMPLE
:
412 case SCRIPT_FMT_TIMING_MULTI
:
413 gettime_monotonic(&log
->oldtime
);
414 gettime_monotonic(&log
->starttime
);
418 log
->initialized
= 1;
422 static int logging_start(struct script_control
*ctl
)
426 /* start all output logs */
427 for (i
= 0; i
< ctl
->out
.nlogs
; i
++) {
428 int rc
= log_start(ctl
, ctl
->out
.logs
[i
]);
433 /* start all input logs */
434 for (i
= 0; i
< ctl
->in
.nlogs
; i
++) {
435 int rc
= log_start(ctl
, ctl
->in
.logs
[i
]);
442 static ssize_t
log_write(struct script_control
*ctl
,
443 struct script_stream
*stream
,
444 struct script_log
*log
,
445 char *obuf
, size_t bytes
)
449 struct timeval now
, delta
;
454 DBG(IO
, ul_debug(" writing [file=%s]", log
->filename
));
456 switch (log
->format
) {
458 DBG(IO
, ul_debug(" log raw data"));
459 rc
= fwrite_all(obuf
, 1, bytes
, log
->fp
);
461 warn(_("cannot write %s"), log
->filename
);
467 case SCRIPT_FMT_TIMING_SIMPLE
:
468 DBG(IO
, ul_debug(" log timing info"));
470 gettime_monotonic(&now
);
471 timersub(&now
, &log
->oldtime
, &delta
);
472 ssz
= fprintf(log
->fp
, "%ld.%06ld %zd\n",
473 (long)delta
.tv_sec
, (long)delta
.tv_usec
, bytes
);
480 case SCRIPT_FMT_TIMING_MULTI
:
481 DBG(IO
, ul_debug(" log multi-stream timing info"));
483 gettime_monotonic(&now
);
484 timersub(&now
, &log
->oldtime
, &delta
);
485 ssz
= fprintf(log
->fp
, "%c %ld.%06ld %zd\n",
487 (long)delta
.tv_sec
, (long)delta
.tv_usec
, bytes
);
502 static ssize_t
log_stream_activity(
503 struct script_control
*ctl
,
504 struct script_stream
*stream
,
505 char *buf
, size_t bytes
)
510 for (i
= 0; i
< stream
->nlogs
; i
++) {
511 ssize_t ssz
= log_write(ctl
, stream
, stream
->logs
[i
], buf
, bytes
);
521 static ssize_t
log_signal(struct script_control
*ctl
, int signum
, char *msgfmt
, ...)
523 struct script_log
*log
;
524 struct timeval now
, delta
;
525 char msg
[BUFSIZ
] = {0};
535 assert(log
->format
== SCRIPT_FMT_TIMING_MULTI
);
536 DBG(IO
, ul_debug(" writing signal to multi-stream timing"));
538 gettime_monotonic(&now
);
539 timersub(&now
, &log
->oldtime
, &delta
);
543 va_start(ap
, msgfmt
);
544 rc
= vsnprintf(msg
, sizeof(msg
), msgfmt
, ap
);
551 sz
= fprintf(log
->fp
, "S %ld.%06ld SIG%s %s\n",
552 (long)delta
.tv_sec
, (long)delta
.tv_usec
,
553 signum_to_signame(signum
), msg
);
555 sz
= fprintf(log
->fp
, "S %ld.%06ld SIG%s\n",
556 (long)delta
.tv_sec
, (long)delta
.tv_usec
,
557 signum_to_signame(signum
));
563 static ssize_t
log_info(struct script_control
*ctl
, const char *name
, const char *msgfmt
, ...)
565 struct script_log
*log
;
566 char msg
[BUFSIZ
] = {0};
576 assert(log
->format
== SCRIPT_FMT_TIMING_MULTI
);
577 DBG(IO
, ul_debug(" writing info to multi-stream log"));
581 va_start(ap
, msgfmt
);
582 rc
= vsnprintf(msg
, sizeof(msg
), msgfmt
, ap
);
589 sz
= fprintf(log
->fp
, "H %f %s %s\n", 0.0, name
, msg
);
591 sz
= fprintf(log
->fp
, "H %f %s\n", 0.0, name
);
597 static void logging_done(struct script_control
*ctl
, const char *msg
)
602 DBG(MISC
, ul_debug("stop logging"));
604 if (WIFSIGNALED(ctl
->childstatus
))
605 status
= WTERMSIG(ctl
->childstatus
) + 0x80;
607 status
= WEXITSTATUS(ctl
->childstatus
);
609 DBG(MISC
, ul_debug(" status=%d", status
));
611 /* close all output logs */
612 for (i
= 0; i
< ctl
->out
.nlogs
; i
++) {
613 struct script_log
*log
= ctl
->out
.logs
[i
];
614 log_close(ctl
, log
, msg
, status
);
618 ctl
->out
.logs
= NULL
;
621 /* close all input logs */
622 for (i
= 0; i
< ctl
->in
.nlogs
; i
++) {
623 struct script_log
*log
= ctl
->in
.logs
[i
];
624 log_close(ctl
, log
, msg
, status
);
632 static void callback_child_die(
634 pid_t child
__attribute__((__unused__
)),
637 struct script_control
*ctl
= (struct script_control
*) data
;
639 ctl
->child
= (pid_t
) -1;
640 ctl
->childstatus
= status
;
643 static void callback_child_sigstop(
644 void *data
__attribute__((__unused__
)),
647 DBG(SIGNAL
, ul_debug(" child stop by SIGSTOP -- stop parent too"));
648 kill(getpid(), SIGSTOP
);
649 DBG(SIGNAL
, ul_debug(" resume"));
650 kill(child
, SIGCONT
);
653 static int callback_log_stream_activity(void *data
, int fd
, char *buf
, size_t bufsz
)
655 struct script_control
*ctl
= (struct script_control
*) data
;
658 DBG(IO
, ul_debug("stream activity callback"));
660 /* from stdin (user) to command */
661 if (fd
== STDIN_FILENO
)
662 ssz
= log_stream_activity(ctl
, &ctl
->in
, buf
, (size_t) bufsz
);
664 /* from command (master) to stdout and log */
665 else if (fd
== ul_pty_get_childfd(ctl
->pty
))
666 ssz
= log_stream_activity(ctl
, &ctl
->out
, buf
, (size_t) bufsz
);
671 DBG(IO
, ul_debug(" append %ld bytes [summary=%zu, max=%zu]", ssz
,
672 ctl
->outsz
, ctl
->maxsz
));
677 /* check output limit */
678 if (ctl
->maxsz
!= 0 && ctl
->outsz
>= ctl
->maxsz
) {
680 printf(_("Script terminated, max output files size %"PRIu64
" exceeded.\n"), ctl
->maxsz
);
681 DBG(IO
, ul_debug("output size %"PRIu64
", exceeded limit %"PRIu64
, ctl
->outsz
, ctl
->maxsz
));
682 logging_done(ctl
, _("max output size exceeded"));
688 static int callback_log_signal(void *data
, struct signalfd_siginfo
*info
, void *sigdata
)
690 struct script_control
*ctl
= (struct script_control
*) data
;
693 switch (info
->ssi_signo
) {
696 struct winsize
*win
= (struct winsize
*) sigdata
;
697 ssz
= log_signal(ctl
, info
->ssi_signo
, "ROWS=%d COLS=%d",
698 win
->ws_row
, win
->ws_col
);
706 ssz
= log_signal(ctl
, info
->ssi_signo
, NULL
);
713 return ssz
< 0 ? ssz
: 0;
716 static int callback_flush_logs(void *data
)
718 struct script_control
*ctl
= (struct script_control
*) data
;
721 for (i
= 0; i
< ctl
->out
.nlogs
; i
++) {
722 int rc
= log_flush(ctl
, ctl
->out
.logs
[i
]);
727 for (i
= 0; i
< ctl
->in
.nlogs
; i
++) {
728 int rc
= log_flush(ctl
, ctl
->in
.logs
[i
]);
735 static void die_if_link(struct script_control
*ctl
, const char *filename
)
741 if (lstat(filename
, &s
) == 0 && (S_ISLNK(s
.st_mode
) || s
.st_nlink
> 1))
743 _("output file `%s' is a link\n"
744 "Use --force if you really want to use it.\n"
745 "Program not started."), filename
);
748 int main(int argc
, char **argv
)
750 struct script_control ctl
= {
751 .out
= { .ident
= 'O' },
752 .in
= { .ident
= 'I' },
754 struct ul_pty_callbacks
*cb
;
755 int ch
, format
= 0, caught_signal
= 0, rc
= 0, echo
= 0;
756 const char *outfile
= NULL
, *infile
= NULL
;
757 const char *timingfile
= NULL
, *shell
= NULL
, *command
= NULL
;
759 enum { FORCE_OPTION
= CHAR_MAX
+ 1 };
761 static const struct option longopts
[] = {
762 {"append", no_argument
, NULL
, 'a'},
763 {"command", required_argument
, NULL
, 'c'},
764 {"echo", required_argument
, NULL
, 'E'},
765 {"return", no_argument
, NULL
, 'e'},
766 {"flush", no_argument
, NULL
, 'f'},
767 {"force", no_argument
, NULL
, FORCE_OPTION
,},
768 {"log-in", required_argument
, NULL
, 'I'},
769 {"log-out", required_argument
, NULL
, 'O'},
770 {"log-io", required_argument
, NULL
, 'B'},
771 {"log-timing", required_argument
, NULL
, 'T'},
772 {"logging-format", required_argument
, NULL
, 'm'},
773 {"output-limit", required_argument
, NULL
, 'o'},
774 {"quiet", no_argument
, NULL
, 'q'},
775 {"timing", optional_argument
, NULL
, 't'},
776 {"version", no_argument
, NULL
, 'V'},
777 {"help", no_argument
, NULL
, 'h'},
780 static const ul_excl_t excl
[] = { /* rows and cols in ASCII order */
784 int excl_st
[ARRAY_SIZE(excl
)] = UL_EXCL_STATUS_INIT
;
785 setlocale(LC_ALL
, "");
787 * script -t prints time delays as floating point numbers. The example
788 * program (scriptreplay) that we provide to handle this timing output
789 * is a perl script, and does not handle numbers in locale format (not
790 * even when "use locale;" is added). So, since these numbers are not
791 * for human consumption, it seems easiest to set LC_NUMERIC here.
793 setlocale(LC_NUMERIC
, "C");
794 bindtextdomain(PACKAGE
, LOCALEDIR
);
796 close_stdout_atexit();
799 ON_DBG(PTY
, ul_pty_init_debug(0xFFFF));
801 /* The default is to keep ECHO flag when stdin is not terminal. We need
802 * it to make stdin (in case of "echo foo | script") log-able and
803 * visible on terminal, and for backward compatibility.
805 ctl
.isterm
= isatty(STDIN_FILENO
);
806 echo
= ctl
.isterm
? 0 : 1;
808 while ((ch
= getopt_long(argc
, argv
, "aB:c:eE:fI:O:o:qm:T:t::Vh", longopts
, NULL
)) != -1) {
810 err_exclusive_options(ch
, longopts
, excl
, excl_st
);
820 if (strcmp(optarg
, "auto") == 0)
822 else if (strcmp(optarg
, "never") == 0)
824 else if (strcmp(optarg
, "always") == 0)
827 errx(EXIT_FAILURE
, _("unssuported echo mode: '%s'"), optarg
);
839 log_associate(&ctl
, &ctl
.in
, optarg
, SCRIPT_FMT_RAW
);
840 log_associate(&ctl
, &ctl
.out
, optarg
, SCRIPT_FMT_RAW
);
841 infile
= outfile
= optarg
;
844 log_associate(&ctl
, &ctl
.in
, optarg
, SCRIPT_FMT_RAW
);
848 log_associate(&ctl
, &ctl
.out
, optarg
, SCRIPT_FMT_RAW
);
852 ctl
.maxsz
= strtosize_or_err(optarg
, _("failed to parse output limit size"));
858 if (strcasecmp(optarg
, "classic") == 0)
859 format
= SCRIPT_FMT_TIMING_SIMPLE
;
860 else if (strcasecmp(optarg
, "advanced") == 0)
861 format
= SCRIPT_FMT_TIMING_MULTI
;
863 errx(EXIT_FAILURE
, _("unssuported logging format: '%s'"), optarg
);
866 if (optarg
&& *optarg
== '=')
868 timingfile
= optarg
? optarg
: "/dev/stderr";
874 print_version(EXIT_SUCCESS
);
878 errtryhelp(EXIT_FAILURE
);
884 /* default if no --log-* specified */
885 if (!outfile
&& !infile
) {
889 die_if_link(&ctl
, DEFAULT_TYPESCRIPT_FILENAME
);
890 outfile
= DEFAULT_TYPESCRIPT_FILENAME
;
893 /* associate stdout with typescript file */
894 log_associate(&ctl
, &ctl
.out
, outfile
, SCRIPT_FMT_RAW
);
898 /* the old SCRIPT_FMT_TIMING_SIMPLE should be used when
899 * recoding output only (just for backward compatibility),
900 * otherwise switch to new format. */
902 format
= infile
|| (outfile
&& infile
) ?
903 SCRIPT_FMT_TIMING_MULTI
:
904 SCRIPT_FMT_TIMING_SIMPLE
;
906 else if (format
== SCRIPT_FMT_TIMING_SIMPLE
&& outfile
&& infile
)
907 errx(EXIT_FAILURE
, _("log multiple streams is mutually "
908 "exclusive with 'classic' format"));
910 log_associate(&ctl
, &ctl
.out
, timingfile
, format
);
912 log_associate(&ctl
, &ctl
.in
, timingfile
, format
);
915 shell
= getenv("SHELL");
917 shell
= _PATH_BSHELL
;
919 ctl
.pty
= ul_new_pty(ctl
.isterm
);
921 err(EXIT_FAILURE
, "failed to allocate PTY handler");
923 ul_pty_slave_echo(ctl
.pty
, echo
);
925 ul_pty_set_callback_data(ctl
.pty
, (void *) &ctl
);
926 cb
= ul_pty_get_callbacks(ctl
.pty
);
927 cb
->child_die
= callback_child_die
;
928 cb
->child_sigstop
= callback_child_sigstop
;
929 cb
->log_stream_activity
= callback_log_stream_activity
;
930 cb
->log_signal
= callback_log_signal
;
931 cb
->flush_logs
= callback_flush_logs
;
934 printf(_("Script started"));
936 printf(_(", output log file is '%s'"), outfile
);
938 printf(_(", input log file is '%s'"), infile
);
940 printf(_(", timing file is '%s'"), timingfile
);
944 #ifdef HAVE_LIBUTEMPTER
945 utempter_add_record(ul_pty_get_childfd(ctl
.pty
), NULL
);
948 if (ul_pty_setup(ctl
.pty
))
949 err(EXIT_FAILURE
, _("failed to create pseudo-terminal"));
954 * We have terminal, do not use err() from now, use "goto done"
957 switch ((int) (ctl
.child
= fork())) {
959 warn(_("cannot create child process"));
967 ul_pty_init_slave(ctl
.pty
);
969 signal(SIGTERM
, SIG_DFL
); /* because /etc/csh.login */
971 shname
= strrchr(shell
, '/');
972 shname
= shname
? shname
+ 1 : shell
;
974 if (access(shell
, X_OK
) == 0) {
976 execl(shell
, shname
, "-c", command
, NULL
);
978 execl(shell
, shname
, "-i", NULL
);
981 execlp(shname
, "-c", command
, NULL
);
983 execlp(shname
, "-i", NULL
);
986 err(EXIT_FAILURE
, "failed to execute %s", shell
);
994 ul_pty_set_child(ctl
.pty
, ctl
.child
);
996 rc
= logging_start(&ctl
);
1000 /* add extra info to advanced timing file */
1001 if (timingfile
&& format
== SCRIPT_FMT_TIMING_MULTI
) {
1002 char buf
[FORMAT_TIMESTAMP_MAX
];
1003 time_t tvec
= script_time((time_t *)NULL
);
1005 strtime_iso(&tvec
, ISO_TIMESTAMP
, buf
, sizeof(buf
));
1006 log_info(&ctl
, "START_TIME", buf
);
1009 init_terminal_info(&ctl
);
1010 log_info(&ctl
, "TERM", ctl
.ttytype
);
1011 log_info(&ctl
, "TTY", ctl
.ttyname
);
1012 log_info(&ctl
, "COLUMNS", "%d", ctl
.ttycols
);
1013 log_info(&ctl
, "LINES", "%d", ctl
.ttylines
);
1015 log_info(&ctl
, "SHELL", shell
);
1017 log_info(&ctl
, "COMMAND", command
);
1018 log_info(&ctl
, "TIMING_LOG", timingfile
);
1020 log_info(&ctl
, "OUTPUT_LOG", outfile
);
1022 log_info(&ctl
, "INPUT_LOG", infile
);
1025 /* this is the main loop */
1026 rc
= ul_pty_proxy_master(ctl
.pty
);
1028 /* all done; cleanup and kill */
1029 caught_signal
= ul_pty_get_delivered_signal(ctl
.pty
);
1031 if (!caught_signal
&& ctl
.child
!= (pid_t
)-1)
1032 ul_pty_wait_for_child(ctl
.pty
); /* final wait */
1034 if (caught_signal
&& ctl
.child
!= (pid_t
)-1) {
1035 fprintf(stderr
, "\nSession terminated, killing shell...");
1036 kill(ctl
.child
, SIGTERM
);
1038 kill(ctl
.child
, SIGKILL
);
1039 fprintf(stderr
, " ...killed.\n");
1043 ul_pty_cleanup(ctl
.pty
);
1044 logging_done(&ctl
, NULL
);
1047 printf(_("Script done.\n"));
1049 #ifdef HAVE_LIBUTEMPTER
1050 if (ul_pty_get_childfd(ctl
.pty
) >= 0)
1051 utempter_remove_record(ul_pty_get_childfd(ctl
.pty
));
1053 ul_free_pty(ctl
.pty
);
1055 /* default exit code */
1056 rc
= rc
? EXIT_FAILURE
: EXIT_SUCCESS
;
1058 /* exit code based on child status */
1059 if (ctl
.rc_wanted
&& rc
== EXIT_SUCCESS
) {
1060 if (WIFSIGNALED(ctl
.childstatus
))
1061 rc
= WTERMSIG(ctl
.childstatus
) + 0x80;
1063 rc
= WEXITSTATUS(ctl
.childstatus
);
1066 DBG(MISC
, ul_debug("done [rc=%d]", rc
));