2 * Copyright (C) 2019, Karel Zak <kzak@redhat.com>
4 * This file is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This file is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
23 #include <sys/select.h>
27 #include <sys/types.h>
35 #include "closestream.h"
39 #include "script-playutils.h"
43 #define SCRIPT_MIN_DELAY 0.0001 /* from original sripreplay.pl */
45 static void __attribute__((__noreturn__
))
49 fputs(USAGE_HEADER
, out
);
52 program_invocation_short_name
);
54 _(" %s [-t] timingfile [-I|-B] typescript\n"),
55 program_invocation_short_name
);
57 fputs(USAGE_SEPARATOR
, out
);
58 fputs(_("Execute terminal typescript.\n"), out
);
60 fputs(USAGE_OPTIONS
, out
);
61 fputs(_(" -t, --timing <file> script timing log file\n"), out
);
62 fputs(_(" -I, --log-in <file> script stdin log file\n"), out
);
63 fputs(_(" -B, --log-io <file> script stdin and stdout log file\n"), out
);
65 fputs(USAGE_SEPARATOR
, out
);
66 fputs(_(" -d, --divisor <num> speed up or slow down execution with time divisor\n"), out
);
67 fputs(_(" -m, --maxdelay <num> wait at most this many seconds between updates\n"), out
);
68 printf(USAGE_HELP_OPTIONS(25));
70 printf(USAGE_MAN_TAIL("scriptlive(1)"));
77 const double d
= strtod_or_err(s
, _("failed to parse number"));
81 err(EXIT_FAILURE
, "%s: %s", _("failed to parse number"), s
);
87 delay_for(double delay
)
90 struct timespec ts
, remainder
;
91 ts
.tv_sec
= (time_t) delay
;
92 ts
.tv_nsec
= (delay
- ts
.tv_sec
) * 1.0e9
;
94 DBG(TIMING
, ul_debug("going to sleep for %fs", delay
));
96 while (-1 == nanosleep(&ts
, &remainder
)) {
104 tv
.tv_sec
= (long) delay
;
105 tv
.tv_usec
= (delay
- tv
.tv_sec
) * 1.0e6
;
106 select(0, NULL
, NULL
, NULL
, &tv
);
110 static int start_shell(const char *shell
, pid_t
*shell_pid
, int *shell_fd
)
119 err(EXIT_FAILURE
, _("pipe failed"));
123 if (*shell_pid
== -1)
124 err(EXIT_FAILURE
, _("fork failed"));
125 if (*shell_pid
!= 0) {
133 shname
= strrchr(shell
, '/');
139 dup2(fds
[0], STDIN_FILENO
);
143 execl(shell
, shname
, "-i", NULL
);
148 main(int argc
, char *argv
[])
150 struct replay_setup
*setup
= NULL
;
151 struct replay_step
*step
= NULL
;
152 const char *log_in
= NULL
,
156 double divi
= 1, maxdelay
= 0;
157 int diviopt
= FALSE
, maxdelayopt
= FALSE
, idx
;
161 struct termios attrs
;
163 static const struct option longopts
[] = {
164 { "timing", required_argument
, 0, 't' },
165 { "log-in", required_argument
, 0, 'I'},
166 { "log-io", required_argument
, 0, 'B'},
167 { "divisor", required_argument
, 0, 'd' },
168 { "maxdelay", required_argument
, 0, 'm' },
169 { "version", no_argument
, 0, 'V' },
170 { "help", no_argument
, 0, 'h' },
173 static const ul_excl_t excl
[] = { /* rows and cols in ASCII order */
177 int excl_st
[ARRAY_SIZE(excl
)] = UL_EXCL_STATUS_INIT
;
178 /* Because we use space as a separator, we can't afford to use any
179 * locale which tolerates a space in a number. In any case, script.c
180 * sets the LC_NUMERIC locale to C, anyway.
182 setlocale(LC_ALL
, "");
183 setlocale(LC_NUMERIC
, "C");
185 bindtextdomain(PACKAGE
, LOCALEDIR
);
187 close_stdout_atexit();
191 while ((ch
= getopt_long(argc
, argv
, "B:I:t:d:m:Vh", longopts
, NULL
)) != -1) {
193 err_exclusive_options(ch
, longopts
, excl
, excl_st
);
207 divi
= getnum(optarg
);
211 maxdelay
= getnum(optarg
);
214 print_version(EXIT_SUCCESS
);
218 errtryhelp(EXIT_FAILURE
);
225 if (!isatty(STDIN_FILENO
))
226 errx(EXIT_FAILURE
, _("stdin is not terminal"));
228 if (!log_tm
&& idx
< argc
)
229 log_tm
= argv
[idx
++];
230 if (!log_in
&& !log_io
&& idx
< argc
)
231 log_in
= argv
[idx
++];
234 divi
= idx
< argc
? getnum(argv
[idx
]) : 1;
239 errx(EXIT_FAILURE
, _("timing file not specified"));
240 if (!(log_in
|| log_io
))
241 errx(EXIT_FAILURE
, _("stdin typescript file not specified"));
243 setup
= replay_new_setup();
245 if (replay_set_timing_file(setup
, log_tm
) != 0)
246 err(EXIT_FAILURE
, _("cannot open %s"), log_tm
);
248 if (log_in
&& replay_associate_log(setup
, "I", log_in
) != 0)
249 err(EXIT_FAILURE
, _("cannot open %s"), log_in
);
251 if (log_io
&& replay_associate_log(setup
, "IO", log_io
) != 0)
252 err(EXIT_FAILURE
, _("cannot open %s"), log_io
);
254 replay_set_default_type(setup
, 'I');
255 replay_set_crmode(setup
, REPLAY_CRMODE_AUTO
);
257 shell
= getenv("SHELL");
259 shell
= _PATH_BSHELL
;
261 fprintf(stdout
, _(">>> scriptlive: Starting your typescript execution by %s. <<<\n"), shell
);
263 tcgetattr(STDIN_FILENO
, &attrs
);
264 start_shell(shell
, &shell_pid
, &shell_fd
);
269 rc
= replay_get_next_step(setup
, "I", &step
);
273 delay
= replay_step_get_delay(step
);
276 if (maxdelayopt
&& delay
> maxdelay
)
278 if (delay
> SCRIPT_MIN_DELAY
)
281 rc
= replay_emit_step_data(setup
, step
, shell_fd
);
284 kill(shell_pid
, SIGTERM
);
285 waitpid(shell_pid
, 0, 0);
286 tcsetattr(STDIN_FILENO
, TCSADRAIN
, &attrs
);
289 err(EXIT_FAILURE
, _("%s: log file error"), replay_step_get_filename(step
));
291 err(EXIT_FAILURE
, _("%s: line %d: timing file error"),
292 replay_get_timing_file(setup
),
293 replay_get_timing_line(setup
));
296 fprintf(stdout
, _(">>> scriptlive: Done. <<<\n"));