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"
64 #include "monotonic.h"
65 #include "timeutils.h"
71 #include "pty-session.h"
74 static UL_DEBUG_DEFINE_MASK(script
);
75 UL_DEBUG_DEFINE_MASKNAMES(script
) = UL_DEBUG_EMPTY_MASKNAMES
;
77 #define SCRIPT_DEBUG_INIT (1 << 1)
78 #define SCRIPT_DEBUG_PTY (1 << 2)
79 #define SCRIPT_DEBUG_IO (1 << 3)
80 #define SCRIPT_DEBUG_SIGNAL (1 << 4)
81 #define SCRIPT_DEBUG_MISC (1 << 5)
82 #define SCRIPT_DEBUG_ALL 0xFFFF
84 #define DBG(m, x) __UL_DBG(script, SCRIPT_DEBUG_, m, x)
85 #define ON_DBG(m, x) __UL_DBG_CALL(script, SCRIPT_DEBUG_, m, x)
87 #ifdef HAVE_LIBUTEMPTER
88 # include <utempter.h>
91 #define DEFAULT_TYPESCRIPT_FILENAME "typescript"
94 * Script is driven by stream (stdout/stdin) activity. It's possible to
95 * associate arbitrary number of log files with the stream. We have two basic
96 * types of log files: "timing file" (simple or multistream) and "data file"
99 * The same log file maybe be shared between both streams. For example
100 * multi-stream timing file is possible to use for stdin as well as for stdout.
103 SCRIPT_FMT_RAW
= 1, /* raw slave/master data */
104 SCRIPT_FMT_TIMING_SIMPLE
, /* (classic) in format "<delta> <offset>" */
105 SCRIPT_FMT_TIMING_MULTI
, /* (advanced) multiple streams in format "<type> <delta> <offset|etc> */
109 FILE *fp
; /* file pointer (handler) */
110 int format
; /* SCRIPT_FMT_* */
111 char *filename
; /* on command line specified name */
112 struct timeval oldtime
; /* previous entry log time (SCRIPT_FMT_TIMING_* only) */
113 struct timeval starttime
;
115 unsigned int initialized
: 1;
118 struct script_stream
{
119 struct script_log
**logs
; /* logs where to write data from stream */
120 size_t nlogs
; /* number of logs */
121 char ident
; /* stream identifier */
124 struct script_control
{
125 uint64_t outsz
; /* current output files size */
126 uint64_t maxsz
; /* maximum output files size */
128 struct script_stream out
; /* output */
129 struct script_stream in
; /* input */
131 struct script_log
*siglog
; /* log for signal entries */
132 struct script_log
*infolog
; /* log for info entries */
137 char *command_norm
; /* normalized (without \n) */
141 struct ul_pty
*pty
; /* pseudo-terminal */
142 pid_t child
; /* child pid */
143 int childstatus
; /* child process exit value */
145 bool append
, /* append output */
146 rc_wanted
, /* return child exit value */
147 flush
, /* flush after each write */
148 quiet
, /* suppress most output */
149 force
, /* write output to links */
150 isterm
; /* is child process running as terminal */
153 static ssize_t
log_info(struct script_control
*ctl
, const char *name
, const char *msgfmt
, ...)
154 __attribute__((__format__ (__printf__
, 3, 4)));
156 static void script_init_debug(void)
158 __UL_INIT_DEBUG_FROM_ENV(script
, SCRIPT_DEBUG_
, 0, SCRIPT_DEBUG
);
161 static void init_terminal_info(struct script_control
*ctl
)
163 if (ctl
->ttyname
|| !ctl
->isterm
)
164 return; /* already initialized */
166 get_terminal_dimension(&ctl
->ttycols
, &ctl
->ttylines
);
167 get_terminal_name(&ctl
->ttyname
, NULL
, NULL
);
168 get_terminal_type(&ctl
->ttytype
);
172 * For tests we want to be able to control time output
175 static inline time_t script_time(time_t *t
)
177 const char *str
= getenv("SCRIPT_TEST_SECOND_SINCE_EPOCH");
180 if (!str
|| sscanf(str
, "%"SCNi64
, &sec
) != 1)
186 #else /* !TEST_SCRIPT */
187 # define script_time(x) time(x)
190 static void __attribute__((__noreturn__
)) usage(void)
193 fputs(USAGE_HEADER
, out
);
194 fprintf(out
, _(" %s [options] [<file>] [-- <command> [<argument>...]]\n"), program_invocation_short_name
);
196 fputs(USAGE_SEPARATOR
, out
);
197 fputs(_("Make a typescript of a terminal session.\n"), out
);
199 fputs(USAGE_OPTIONS
, out
);
200 fputs(_(" -I, --log-in <file> log stdin to file\n"), out
);
201 fputs(_(" -O, --log-out <file> log stdout to file (default)\n"), out
);
202 fputs(_(" -B, --log-io <file> log stdin and stdout to file\n"), out
);
203 fputs(USAGE_SEPARATOR
, out
);
205 fputs(_(" -T, --log-timing <file> log timing information to file\n"), out
);
206 fputs(_(" -t[<file>], --timing[=<file>] deprecated alias to -T (default file is stderr)\n"), out
);
207 fputs(_(" -m, --logging-format <name> force to 'classic' or 'advanced' format\n"), out
);
208 fputs(USAGE_SEPARATOR
, out
);
210 fputs(_(" -a, --append append to the log file\n"), out
);
211 fputs(_(" -c, --command <command> run <command> rather than an interactive shell\n"
212 " (alternative to '-- <command> [<argument...>]')\n"), out
);
213 fputs(_(" -e, --return return exit code of the child process\n"), out
);
214 fputs(_(" -f, --flush run flush after each write\n"), out
);
215 fputs(_(" --force use output file even when it is a link\n"), out
);
216 fputs(_(" -E, --echo <when> echo input in session (auto, always or never)\n"), out
);
217 fputs(_(" -o, --output-limit <size> terminate if output files exceed size\n"), out
);
218 fputs(_(" -q, --quiet be quiet\n"), out
);
220 fputs(USAGE_SEPARATOR
, out
);
221 fprintf(out
, USAGE_HELP_OPTIONS(31));
223 fputs(USAGE_ARGUMENTS
, out
);
224 fprintf(out
, USAGE_ARG_SIZE(_("<size>")));
226 fprintf(out
, USAGE_MAN_TAIL("script(1)"));
231 static struct script_log
*get_log_by_name(struct script_stream
*stream
,
236 for (i
= 0; i
< stream
->nlogs
; i
++) {
237 struct script_log
*log
= stream
->logs
[i
];
238 if (strcmp(log
->filename
, name
) == 0)
244 static struct script_log
*log_associate(struct script_control
*ctl
,
245 struct script_stream
*stream
,
246 const char *filename
, int format
)
248 struct script_log
*log
;
250 DBG(MISC
, ul_debug("associate %s with stream", filename
));
256 log
= get_log_by_name(stream
, filename
);
258 return log
; /* already defined */
260 log
= get_log_by_name(stream
== &ctl
->out
? &ctl
->in
: &ctl
->out
, filename
);
262 /* create a new log */
263 log
= xcalloc(1, sizeof(*log
));
264 log
->filename
= xstrdup(filename
);
265 log
->format
= format
;
268 /* add log to the stream */
269 stream
->logs
= xreallocarray(stream
->logs
,
270 stream
->nlogs
+ 1, sizeof(log
));
271 stream
->logs
[stream
->nlogs
] = log
;
274 /* remember where to write info about signals */
275 if (format
== SCRIPT_FMT_TIMING_MULTI
) {
285 static int log_close(struct script_control
*ctl
,
286 struct script_log
*log
,
292 if (!log
|| !log
->initialized
)
295 DBG(MISC
, ul_debug("closing %s", log
->filename
));
297 switch (log
->format
) {
300 char buf
[FORMAT_TIMESTAMP_MAX
];
301 time_t tvec
= script_time((time_t *)NULL
);
303 strtime_iso(&tvec
, ISO_TIMESTAMP
, buf
, sizeof(buf
));
305 fprintf(log
->fp
, _("\nScript done on %s [<%s>]\n"), buf
, msg
);
307 fprintf(log
->fp
, _("\nScript done on %s [COMMAND_EXIT_CODE=\"%d\"]\n"), buf
, status
);
310 case SCRIPT_FMT_TIMING_MULTI
:
312 struct timeval now
= { 0 }, delta
= { 0 };
314 gettime_monotonic(&now
);
315 timersub(&now
, &log
->starttime
, &delta
);
317 log_info(ctl
, "DURATION", "%"PRId64
".%06"PRId64
,
318 (int64_t)delta
.tv_sec
,
319 (int64_t)delta
.tv_usec
);
320 log_info(ctl
, "EXIT_CODE", "%d", status
);
323 case SCRIPT_FMT_TIMING_SIMPLE
:
327 if (close_stream(log
->fp
) != 0) {
328 warn(_("write failed: %s"), log
->filename
);
333 memset(log
, 0, sizeof(*log
));
338 static int log_flush(struct script_control
*ctl
__attribute__((__unused__
)), struct script_log
*log
)
341 if (!log
|| !log
->initialized
)
344 DBG(MISC
, ul_debug("flushing %s", log
->filename
));
350 static void log_free(struct script_control
*ctl
, struct script_log
*log
)
357 /* the same log is possible to reference from more places, remove all
358 * (TODO: maybe use include/list.h to make it more elegant)
360 if (ctl
->siglog
== log
)
362 else if (ctl
->infolog
== log
)
365 for (i
= 0; i
< ctl
->out
.nlogs
; i
++) {
366 if (ctl
->out
.logs
[i
] == log
)
367 ctl
->out
.logs
[i
] = NULL
;
369 for (i
= 0; i
< ctl
->in
.nlogs
; i
++) {
370 if (ctl
->in
.logs
[i
] == log
)
371 ctl
->in
.logs
[i
] = NULL
;
376 static int log_start(struct script_control
*ctl
,
377 struct script_log
*log
)
379 if (log
->initialized
)
382 DBG(MISC
, ul_debug("opening %s", log
->filename
));
384 assert(log
->fp
== NULL
);
387 log
->fp
= fopen(log
->filename
,
388 ctl
->append
&& log
->format
== SCRIPT_FMT_RAW
?
392 warn(_("cannot open %s"), log
->filename
);
396 /* write header, etc. */
397 switch (log
->format
) {
401 char buf
[FORMAT_TIMESTAMP_MAX
];
402 time_t tvec
= script_time((time_t *)NULL
);
404 strtime_iso(&tvec
, ISO_TIMESTAMP
, buf
, sizeof(buf
));
405 fprintf(log
->fp
, _("Script started on %s ["), buf
);
408 x
+= fprintf(log
->fp
, "COMMAND=\"%s\"", ctl
->command_norm
);
411 init_terminal_info(ctl
);
414 x
+= fprintf(log
->fp
, "%*sTERM=\"%s\"", !!x
, "", ctl
->ttytype
);
416 x
+= fprintf(log
->fp
, "%*sTTY=\"%s\"", !!x
, "", ctl
->ttyname
);
418 x
+= fprintf(log
->fp
, "%*sCOLUMNS=\"%d\" LINES=\"%d\"", !!x
, "",
419 ctl
->ttycols
, ctl
->ttylines
);
421 fprintf(log
->fp
, _("%*s<not executed on terminal>"), !!x
, "");
423 fputs("]\n", log
->fp
);
426 case SCRIPT_FMT_TIMING_SIMPLE
:
427 case SCRIPT_FMT_TIMING_MULTI
:
428 gettime_monotonic(&log
->oldtime
);
429 gettime_monotonic(&log
->starttime
);
433 log
->initialized
= 1;
437 static int logging_start(struct script_control
*ctl
)
441 /* start all output logs */
442 for (i
= 0; i
< ctl
->out
.nlogs
; i
++) {
443 int rc
= log_start(ctl
, ctl
->out
.logs
[i
]);
448 /* start all input logs */
449 for (i
= 0; i
< ctl
->in
.nlogs
; i
++) {
450 int rc
= log_start(ctl
, ctl
->in
.logs
[i
]);
457 static ssize_t
log_write(struct script_control
*ctl
,
458 struct script_stream
*stream
,
459 struct script_log
*log
,
460 char *obuf
, size_t bytes
)
464 struct timeval now
, delta
;
469 DBG(IO
, ul_debug(" writing [file=%s]", log
->filename
));
471 switch (log
->format
) {
473 DBG(IO
, ul_debug(" log raw data"));
474 rc
= fwrite_all(obuf
, 1, bytes
, log
->fp
);
476 warn(_("cannot write %s"), log
->filename
);
482 case SCRIPT_FMT_TIMING_SIMPLE
:
483 DBG(IO
, ul_debug(" log timing info"));
485 gettime_monotonic(&now
);
486 timersub(&now
, &log
->oldtime
, &delta
);
487 ssz
= fprintf(log
->fp
, "%"PRId64
".%06"PRId64
" %zd\n",
488 (int64_t)delta
.tv_sec
, (int64_t)delta
.tv_usec
, bytes
);
495 case SCRIPT_FMT_TIMING_MULTI
:
496 DBG(IO
, ul_debug(" log multi-stream timing info"));
498 gettime_monotonic(&now
);
499 timersub(&now
, &log
->oldtime
, &delta
);
500 ssz
= fprintf(log
->fp
, "%c %"PRId64
".%06"PRId64
" %zd\n",
502 (int64_t)delta
.tv_sec
, (int64_t)delta
.tv_usec
, bytes
);
517 static ssize_t
log_stream_activity(
518 struct script_control
*ctl
,
519 struct script_stream
*stream
,
520 char *buf
, size_t bytes
)
525 for (i
= 0; i
< stream
->nlogs
; i
++) {
526 ssize_t ssz
= log_write(ctl
, stream
, stream
->logs
[i
], buf
, bytes
);
536 static ssize_t
__attribute__ ((__format__ (__printf__
, 3, 4)))
537 log_signal(struct script_control
*ctl
, int signum
, const char *msgfmt
, ...)
539 struct script_log
*log
;
540 struct timeval now
, delta
;
541 char msg
[BUFSIZ
] = {0};
551 assert(log
->format
== SCRIPT_FMT_TIMING_MULTI
);
552 DBG(IO
, ul_debug(" writing signal to multi-stream timing"));
554 gettime_monotonic(&now
);
555 timersub(&now
, &log
->oldtime
, &delta
);
559 va_start(ap
, msgfmt
);
560 rc
= vsnprintf(msg
, sizeof(msg
), msgfmt
, ap
);
567 sz
= fprintf(log
->fp
, "S %"PRId64
".%06"PRId64
" SIG%s %s\n",
568 (int64_t)delta
.tv_sec
, (int64_t)delta
.tv_usec
,
569 signum_to_signame(signum
), msg
);
571 sz
= fprintf(log
->fp
, "S %"PRId64
".%06"PRId64
" SIG%s\n",
572 (int64_t)delta
.tv_sec
, (int64_t)delta
.tv_usec
,
573 signum_to_signame(signum
));
579 static ssize_t
log_info(struct script_control
*ctl
, const char *name
, const char *msgfmt
, ...)
581 struct script_log
*log
;
582 char msg
[BUFSIZ
] = {0};
592 assert(log
->format
== SCRIPT_FMT_TIMING_MULTI
);
593 DBG(IO
, ul_debug(" writing info to multi-stream log"));
597 va_start(ap
, msgfmt
);
598 rc
= vsnprintf(msg
, sizeof(msg
), msgfmt
, ap
);
605 sz
= fprintf(log
->fp
, "H %f %s %s\n", 0.0, name
, msg
);
607 sz
= fprintf(log
->fp
, "H %f %s\n", 0.0, name
);
613 static void logging_done(struct script_control
*ctl
, const char *msg
)
618 DBG(MISC
, ul_debug("stop logging"));
620 if (WIFSIGNALED(ctl
->childstatus
))
621 status
= WTERMSIG(ctl
->childstatus
) + 0x80;
623 status
= WEXITSTATUS(ctl
->childstatus
);
625 DBG(MISC
, ul_debug(" status=%d", status
));
627 /* close all output logs */
628 for (i
= 0; i
< ctl
->out
.nlogs
; i
++) {
629 struct script_log
*log
= ctl
->out
.logs
[i
];
630 log_close(ctl
, log
, msg
, status
);
634 ctl
->out
.logs
= NULL
;
637 /* close all input logs */
638 for (i
= 0; i
< ctl
->in
.nlogs
; i
++) {
639 struct script_log
*log
= ctl
->in
.logs
[i
];
640 log_close(ctl
, log
, msg
, status
);
648 static void callback_child_die(
650 pid_t child
__attribute__((__unused__
)),
653 struct script_control
*ctl
= (struct script_control
*) data
;
655 ctl
->child
= (pid_t
) -1;
656 ctl
->childstatus
= status
;
659 static void callback_child_sigstop(
660 void *data
__attribute__((__unused__
)),
663 DBG(SIGNAL
, ul_debug(" child stop by SIGSTOP -- stop parent too"));
664 kill(getpid(), SIGSTOP
);
665 DBG(SIGNAL
, ul_debug(" resume"));
666 kill(child
, SIGCONT
);
669 static int callback_log_stream_activity(void *data
, int fd
, char *buf
, size_t bufsz
)
671 struct script_control
*ctl
= (struct script_control
*) data
;
674 DBG(IO
, ul_debug("stream activity callback"));
676 /* from stdin (user) to command */
677 if (fd
== STDIN_FILENO
)
678 ssz
= log_stream_activity(ctl
, &ctl
->in
, buf
, (size_t) bufsz
);
680 /* from command (master) to stdout and log */
681 else if (fd
== ul_pty_get_childfd(ctl
->pty
))
682 ssz
= log_stream_activity(ctl
, &ctl
->out
, buf
, (size_t) bufsz
);
687 DBG(IO
, ul_debug(" append %zd bytes [summary=%" PRIu64
", max=%" PRIu64
"]", ssz
,
688 ctl
->outsz
, ctl
->maxsz
));
692 /* check output limit */
693 if (ctl
->maxsz
!= 0 && ctl
->outsz
>= ctl
->maxsz
) {
695 printf(_("Script terminated, max output files size %"PRIu64
" exceeded.\n"), ctl
->maxsz
);
696 DBG(IO
, ul_debug("output size %"PRIu64
", exceeded limit %"PRIu64
, ctl
->outsz
, ctl
->maxsz
));
697 logging_done(ctl
, _("max output size exceeded"));
703 static int callback_log_signal(void *data
, struct signalfd_siginfo
*info
, void *sigdata
)
705 struct script_control
*ctl
= (struct script_control
*) data
;
708 switch (info
->ssi_signo
) {
711 struct winsize
*win
= (struct winsize
*) sigdata
;
712 ssz
= log_signal(ctl
, info
->ssi_signo
, "ROWS=%d COLS=%d",
713 win
->ws_row
, win
->ws_col
);
721 ssz
= log_signal(ctl
, info
->ssi_signo
, NULL
);
728 return ssz
< 0 ? ssz
: 0;
731 static int callback_flush_logs(void *data
)
733 struct script_control
*ctl
= (struct script_control
*) data
;
736 for (i
= 0; i
< ctl
->out
.nlogs
; i
++) {
737 int rc
= log_flush(ctl
, ctl
->out
.logs
[i
]);
742 for (i
= 0; i
< ctl
->in
.nlogs
; i
++) {
743 int rc
= log_flush(ctl
, ctl
->in
.logs
[i
]);
750 static void die_if_link(struct script_control
*ctl
, const char *filename
)
756 if (lstat(filename
, &s
) == 0 && (S_ISLNK(s
.st_mode
) || s
.st_nlink
> 1))
758 _("output file `%s' is a link\n"
759 "Use --force if you really want to use it.\n"
760 "Program not started."), filename
);
763 int main(int argc
, char **argv
)
765 struct script_control ctl
= {
766 .out
= { .ident
= 'O' },
767 .in
= { .ident
= 'I' },
769 struct ul_pty_callbacks
*cb
;
770 int ch
, format
= 0, caught_signal
= 0, rc
= 0, echo
= 1;
771 const char *outfile
= NULL
, *infile
= NULL
;
772 const char *timingfile
= NULL
, *shell
= NULL
;
774 enum { FORCE_OPTION
= CHAR_MAX
+ 1 };
776 static const struct option longopts
[] = {
777 {"append", no_argument
, NULL
, 'a'},
778 {"command", required_argument
, NULL
, 'c'},
779 {"echo", required_argument
, NULL
, 'E'},
780 {"return", no_argument
, NULL
, 'e'},
781 {"flush", no_argument
, NULL
, 'f'},
782 {"force", no_argument
, NULL
, FORCE_OPTION
,},
783 {"log-in", required_argument
, NULL
, 'I'},
784 {"log-out", required_argument
, NULL
, 'O'},
785 {"log-io", required_argument
, NULL
, 'B'},
786 {"log-timing", required_argument
, NULL
, 'T'},
787 {"logging-format", required_argument
, NULL
, 'm'},
788 {"output-limit", required_argument
, NULL
, 'o'},
789 {"quiet", no_argument
, NULL
, 'q'},
790 {"timing", optional_argument
, NULL
, 't'},
791 {"version", no_argument
, NULL
, 'V'},
792 {"help", no_argument
, NULL
, 'h'},
795 static const ul_excl_t excl
[] = { /* rows and cols in ASCII order */
799 int excl_st
[ARRAY_SIZE(excl
)] = UL_EXCL_STATUS_INIT
;
800 setlocale(LC_ALL
, "");
802 * script -t prints time delays as floating point numbers. The example
803 * program (scriptreplay) that we provide to handle this timing output
804 * is a perl script, and does not handle numbers in locale format (not
805 * even when "use locale;" is added). So, since these numbers are not
806 * for human consumption, it seems easiest to set LC_NUMERIC here.
808 setlocale(LC_NUMERIC
, "C");
809 bindtextdomain(PACKAGE
, LOCALEDIR
);
811 close_stdout_atexit();
814 ON_DBG(PTY
, ul_pty_init_debug(0xFFFF));
816 ctl
.isterm
= isatty(STDIN_FILENO
);
818 while ((ch
= getopt_long(argc
, argv
, "+aB:c:eE:fI:O:o:qm:T:t::Vh", longopts
, NULL
)) != -1) {
820 err_exclusive_options(ch
, longopts
, excl
, excl_st
);
827 ctl
.command
= optarg
;
828 ctl
.command_norm
= xstrdup(ctl
.command
);
829 ul_strrep(ctl
.command_norm
, '\n', ' ');
832 if (strcmp(optarg
, "auto") == 0)
834 else if (strcmp(optarg
, "never") == 0)
836 else if (strcmp(optarg
, "always") == 0)
839 errx(EXIT_FAILURE
, _("unsupported echo mode: '%s'"), optarg
);
851 log_associate(&ctl
, &ctl
.in
, optarg
, SCRIPT_FMT_RAW
);
852 log_associate(&ctl
, &ctl
.out
, optarg
, SCRIPT_FMT_RAW
);
853 infile
= outfile
= optarg
;
856 log_associate(&ctl
, &ctl
.in
, optarg
, SCRIPT_FMT_RAW
);
860 log_associate(&ctl
, &ctl
.out
, optarg
, SCRIPT_FMT_RAW
);
864 ctl
.maxsz
= strtosize_or_err(optarg
, _("failed to parse output limit size"));
870 if (c_strcasecmp(optarg
, "classic") == 0)
871 format
= SCRIPT_FMT_TIMING_SIMPLE
;
872 else if (c_strcasecmp(optarg
, "advanced") == 0)
873 format
= SCRIPT_FMT_TIMING_MULTI
;
875 errx(EXIT_FAILURE
, _("unsupported logging format: '%s'"), optarg
);
878 if (optarg
&& *optarg
== '=')
880 timingfile
= optarg
? optarg
: "/dev/stderr";
886 print_version(EXIT_SUCCESS
);
890 errtryhelp(EXIT_FAILURE
);
898 * Note that "--" separates non-option arguments. The script can be
899 * used with an <outfile> name as well as with a <command>. The
900 * <outfile> is always before "--" and <command> is always after "--".
901 * Possible combinations are:
903 * script [options] <outfile>
904 * script [options] -- <command ...>
905 * script [options] <outfile> -- <command ...>
908 /* default if no --log-* specified */
909 if (!outfile
&& !infile
) {
910 /* if argv[0] is not after --, use argv[0] as outfile */
911 if (argc
> 0 && strcmp(argv
[-1], "--") != 0) {
916 die_if_link(&ctl
, DEFAULT_TYPESCRIPT_FILENAME
);
917 outfile
= DEFAULT_TYPESCRIPT_FILENAME
;
920 /* associate stdout with typescript file */
921 log_associate(&ctl
, &ctl
.out
, outfile
, SCRIPT_FMT_RAW
);
924 /* skip -- to non-option arguments */
925 if (argc
> 0 && strcmp(argv
[0], "--") == 0) {
930 /* concat non-option arguments as command */
931 if (argc
> 0 && strcmp(argv
[-1], "--") == 0) {
932 if (ctl
.command
!= NULL
) {
933 warnx(_("option --command and a command after '--' cannot be combined"));
934 errtryhelp(EXIT_FAILURE
);
937 ctl
.command
= ul_strv_join(argv
, " ");
939 errx(EXIT_FAILURE
, _("failed to concatenate arguments"));
941 ctl
.command_norm
= xstrdup(ctl
.command
);
942 ul_strrep(ctl
.command_norm
, '\n', ' ');
947 warnx(_("unexpected number of arguments"));
948 errtryhelp(EXIT_FAILURE
);
952 /* the old SCRIPT_FMT_TIMING_SIMPLE should be used when
953 * recoding output only (just for backward compatibility),
954 * otherwise switch to new format. */
956 format
= infile
|| (outfile
&& infile
) ?
957 SCRIPT_FMT_TIMING_MULTI
:
958 SCRIPT_FMT_TIMING_SIMPLE
;
960 else if (format
== SCRIPT_FMT_TIMING_SIMPLE
&& outfile
&& infile
)
961 errx(EXIT_FAILURE
, _("log multiple streams is mutually "
962 "exclusive with 'classic' format"));
964 log_associate(&ctl
, &ctl
.out
, timingfile
, format
);
966 log_associate(&ctl
, &ctl
.in
, timingfile
, format
);
969 shell
= getenv("SHELL");
971 shell
= _PATH_BSHELL
;
973 ctl
.pty
= ul_new_pty(ctl
.isterm
);
975 err(EXIT_FAILURE
, "failed to allocate PTY handler");
977 ul_pty_slave_echo(ctl
.pty
, echo
);
979 ul_pty_set_callback_data(ctl
.pty
, (void *) &ctl
);
980 cb
= ul_pty_get_callbacks(ctl
.pty
);
981 cb
->child_die
= callback_child_die
;
982 cb
->child_sigstop
= callback_child_sigstop
;
983 cb
->log_stream_activity
= callback_log_stream_activity
;
984 cb
->log_signal
= callback_log_signal
;
985 cb
->flush_logs
= callback_flush_logs
;
988 printf(_("Script started"));
990 printf(_(", output log file is '%s'"), outfile
);
992 printf(_(", input log file is '%s'"), infile
);
994 printf(_(", timing file is '%s'"), timingfile
);
999 if (ul_pty_setup(ctl
.pty
))
1000 err(EXIT_FAILURE
, _("failed to create pseudo-terminal"));
1002 #ifdef HAVE_LIBUTEMPTER
1003 utempter_add_record(ul_pty_get_childfd(ctl
.pty
), NULL
);
1006 if (ul_pty_signals_setup(ctl
.pty
))
1007 err(EXIT_FAILURE
, _("failed to initialize signals handler"));
1011 * We have terminal, do not use err() from now, use "goto done"
1014 switch ((int) (ctl
.child
= fork())) {
1015 case -1: /* error */
1016 warn(_("cannot create child process"));
1024 ul_pty_init_slave(ctl
.pty
);
1026 signal(SIGTERM
, SIG_DFL
); /* because /etc/csh.login */
1028 shname
= strrchr(shell
, '/');
1029 shname
= shname
? shname
+ 1 : shell
;
1031 if (access(shell
, X_OK
) == 0) {
1033 execl(shell
, shname
, "-c", ctl
.command
, (char *)NULL
);
1035 execl(shell
, shname
, "-i", (char *)NULL
);
1038 execlp(shname
, shname
, "-c", ctl
.command
, (char *)NULL
);
1040 execlp(shname
, shname
, "-i", (char *)NULL
);
1043 err(EXIT_FAILURE
, "failed to execute %s", shell
);
1051 ul_pty_set_child(ctl
.pty
, ctl
.child
);
1053 rc
= logging_start(&ctl
);
1057 /* add extra info to advanced timing file */
1058 if (timingfile
&& format
== SCRIPT_FMT_TIMING_MULTI
) {
1059 char buf
[FORMAT_TIMESTAMP_MAX
];
1060 time_t tvec
= script_time((time_t *)NULL
);
1062 strtime_iso(&tvec
, ISO_TIMESTAMP
, buf
, sizeof(buf
));
1063 log_info(&ctl
, "START_TIME", "%s", buf
);
1066 init_terminal_info(&ctl
);
1067 log_info(&ctl
, "TERM", "%s", ctl
.ttytype
);
1068 log_info(&ctl
, "TTY", "%s", ctl
.ttyname
);
1069 log_info(&ctl
, "COLUMNS", "%d", ctl
.ttycols
);
1070 log_info(&ctl
, "LINES", "%d", ctl
.ttylines
);
1072 log_info(&ctl
, "SHELL", "%s", shell
);
1074 log_info(&ctl
, "COMMAND", "%s", ctl
.command_norm
);
1075 log_info(&ctl
, "TIMING_LOG", "%s", timingfile
);
1077 log_info(&ctl
, "OUTPUT_LOG", "%s", outfile
);
1079 log_info(&ctl
, "INPUT_LOG", "%s", infile
);
1082 /* this is the main loop */
1083 rc
= ul_pty_proxy_master(ctl
.pty
);
1085 /* all done; cleanup and kill */
1086 caught_signal
= ul_pty_get_delivered_signal(ctl
.pty
);
1088 if (!caught_signal
&& ctl
.child
!= (pid_t
)-1)
1089 ul_pty_wait_for_child(ctl
.pty
); /* final wait */
1091 if (caught_signal
&& ctl
.child
!= (pid_t
)-1) {
1092 fprintf(stderr
, "\nSession terminated, killing shell...");
1093 kill(ctl
.child
, SIGTERM
);
1095 kill(ctl
.child
, SIGKILL
);
1096 fprintf(stderr
, " ...killed.\n");
1100 ul_pty_cleanup(ctl
.pty
);
1101 logging_done(&ctl
, NULL
);
1104 printf(_("Script done.\n"));
1106 #ifdef HAVE_LIBUTEMPTER
1107 if (ul_pty_get_childfd(ctl
.pty
) >= 0)
1108 utempter_remove_record(ul_pty_get_childfd(ctl
.pty
));
1110 ul_free_pty(ctl
.pty
);
1112 /* default exit code */
1113 rc
= rc
? EXIT_FAILURE
: EXIT_SUCCESS
;
1115 /* exit code based on child status */
1116 if (ctl
.rc_wanted
&& rc
== EXIT_SUCCESS
) {
1117 if (WIFSIGNALED(ctl
.childstatus
))
1118 rc
= WTERMSIG(ctl
.childstatus
) + 0x80;
1120 rc
= WEXITSTATUS(ctl
.childstatus
);
1123 DBG(MISC
, ul_debug("done [rc=%d]", rc
));