2 * Copyright (C) 2024, Jonathan Ketchker <jonathan@ketchker.com>
3 * Copyright (C) 2008-2019, Karel Zak <kzak@redhat.com>
4 * Copyright (C) 2008, James Youngman <jay@gnu.org>
6 * This file is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This file is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
17 * Based on scriptreplay.pl by Joey Hess <joey@kitenet.net>
28 #include <sys/select.h>
38 #include "closestream.h"
42 #include "script-playutils.h"
44 static void __attribute__((__noreturn__
))
48 fputs(USAGE_HEADER
, out
);
50 _(" %s [options] <timingfile> [<typescript> [<divisor>]]\n"),
51 program_invocation_short_name
);
53 fputs(USAGE_SEPARATOR
, out
);
54 fputs(_("Play back terminal typescripts, using timing information.\n"), out
);
56 fputs(USAGE_OPTIONS
, out
);
57 fputs(_(" -t, --timing <file> script timing log file\n"), out
);
58 fputs(_(" -T, --log-timing <file> alias to -t\n"), out
);
59 fputs(_(" -I, --log-in <file> script stdin log file\n"), out
);
60 fputs(_(" -O, --log-out <file> script stdout log file (default)\n"), out
);
61 fputs(_(" -B, --log-io <file> script stdin and stdout log file\n"), out
);
62 fputs(USAGE_SEPARATOR
, out
);
63 fputs(_(" -s, --typescript <file> deprecated alias to -O\n"), out
);
65 fputs(USAGE_SEPARATOR
, out
);
66 fputs(_(" --summary display overview about recorded session and exit\n"), out
);
67 fputs(_(" -d, --divisor <num> speed up or slow down execution with time divisor\n"), out
);
68 fputs(_(" -m, --maxdelay <num> wait at most this many seconds between updates\n"), out
);
69 fputs(_(" -x, --stream <name> stream type (out, in, signal or info)\n"), out
);
70 fputs(_(" -c, --cr-mode <type> CR char mode (auto, never, always)\n"), out
);
72 fputs(USAGE_SEPARATOR
, out
);
73 fprintf(out
, USAGE_HELP_OPTIONS(25));
75 fputs(USAGE_SEPARATOR
, out
);
76 fputs(_("Key bindings:\n"), out
);
77 fputs(_(" space toggles between pause and play\n"), out
);
78 fputs(_(" up-arrow increases playback speed with ten percent\n"), out
);
79 fputs(_(" down-arrow decreases playback speed with ten percent\n"), out
);
81 fprintf(out
, USAGE_MAN_TAIL("scriptreplay(1)"));
88 const double d
= strtod_or_err(s
, _("failed to parse number"));
92 err(EXIT_FAILURE
, "%s: %s", _("failed to parse number"), s
);
98 delay_for(const struct timeval
*delay
)
100 #ifdef HAVE_NANOSLEEP
101 struct timespec ts
, remainder
;
102 ts
.tv_sec
= (time_t) delay
->tv_sec
;
103 ts
.tv_nsec
= delay
->tv_usec
* 1000;
105 DBG(TIMING
, ul_debug("going to sleep for %"PRId64
".%06"PRId64
,
106 (int64_t) delay
->tv_sec
, (int64_t) delay
->tv_usec
));
108 while (-1 == nanosleep(&ts
, &remainder
)) {
116 struct timeval timeout
;
118 /* On Linux, select() modifies timeout */
119 memcpy(&timeout
, delay
, sizeof(struct timeval
));
120 select(0, NULL
, NULL
, NULL
, &timeout
);
126 appendchr(char *buf
, size_t bufsz
, int c
)
131 return; /* already in */
139 setterm(struct termios
*backup
, int *saved_flag
)
141 struct termios tattr
;
143 *saved_flag
= fcntl(STDIN_FILENO
, F_GETFL
);
144 if (*saved_flag
== -1)
145 err(EXIT_FAILURE
, _("unexpected fcntl failure"));
146 fcntl(STDIN_FILENO
, F_SETFL
, *saved_flag
| O_NONBLOCK
);
148 if (tcgetattr(STDOUT_FILENO
, backup
) != 0) {
149 if (errno
!= ENOTTY
) /* For debugger. */
150 err(EXIT_FAILURE
, _("unexpected tcgetattr failure"));
155 tattr
.c_lflag
|= ISIG
;
156 tattr
.c_iflag
|= IXON
;
157 tcsetattr(STDOUT_FILENO
, TCSANOW
, &tattr
);
162 main(int argc
, char *argv
[])
164 static const struct timeval mindelay
= { .tv_sec
= 0, .tv_usec
= 100 };
165 static const struct timeval input_delay
= { .tv_sec
= 0, .tv_usec
= 100000 };
166 struct timeval step_delay
= { 0, 0 };
167 struct timeval maxdelay
;
171 struct termios saved
;
173 struct replay_setup
*setup
= NULL
;
174 struct replay_step
*step
= NULL
;
175 char streams
[6] = {0}; /* IOSI - in, out, signal,info */
176 const char *log_out
= NULL
,
181 int diviopt
= FALSE
, idx
;
182 int ch
, rc
= 0, crmode
= REPLAY_CRMODE_AUTO
, summary
= 0;
184 OPT_SUMMARY
= CHAR_MAX
+ 1
187 static const struct option longopts
[] = {
188 { "cr-mode", required_argument
, 0, 'c' },
189 { "timing", required_argument
, 0, 't' },
190 { "log-timing", required_argument
, 0, 'T' },
191 { "log-in", required_argument
, 0, 'I' },
192 { "log-out", required_argument
, 0, 'O' },
193 { "log-io", required_argument
, 0, 'B' },
194 { "typescript", required_argument
, 0, 's' },
195 { "divisor", required_argument
, 0, 'd' },
196 { "maxdelay", required_argument
, 0, 'm' },
197 { "stream", required_argument
, 0, 'x' },
198 { "summary", no_argument
, 0, OPT_SUMMARY
},
199 { "version", no_argument
, 0, 'V' },
200 { "help", no_argument
, 0, 'h' },
203 static const ul_excl_t excl
[] = { /* rows and cols in ASCII order */
207 int excl_st
[ARRAY_SIZE(excl
)] = UL_EXCL_STATUS_INIT
;
208 /* Because we use space as a separator, we can't afford to use any
209 * locale which tolerates a space in a number. In any case, script.c
210 * sets the LC_NUMERIC locale to C, anyway.
212 setlocale(LC_ALL
, "");
213 setlocale(LC_NUMERIC
, "C");
215 bindtextdomain(PACKAGE
, LOCALEDIR
);
217 close_stdout_atexit();
220 timerclear(&maxdelay
);
222 while ((ch
= getopt_long(argc
, argv
, "B:c:I:O:T:t:s:d:m:x:Vh", longopts
, NULL
)) != -1) {
224 err_exclusive_options(ch
, longopts
, excl
, excl_st
);
228 if (strcmp("auto", optarg
) == 0)
229 crmode
= REPLAY_CRMODE_AUTO
;
230 else if (strcmp("never", optarg
) == 0)
231 crmode
= REPLAY_CRMODE_NEVER
;
232 else if (strcmp("always", optarg
) == 0)
233 crmode
= REPLAY_CRMODE_ALWAYS
;
235 errx(EXIT_FAILURE
, _("unsupported mode name: '%s'"), optarg
);
253 divi
= getnum(optarg
);
256 strtotimeval_or_err(optarg
, &maxdelay
, _("failed to parse maximal delay argument"));
259 if (strcmp("in", optarg
) == 0)
260 appendchr(streams
, sizeof(streams
), 'I');
261 else if (strcmp("out", optarg
) == 0)
262 appendchr(streams
, sizeof(streams
), 'O');
263 else if (strcmp("signal", optarg
) == 0)
264 appendchr(streams
, sizeof(streams
), 'S');
265 else if (strcmp("info", optarg
) == 0)
266 appendchr(streams
, sizeof(streams
), 'H');
268 errx(EXIT_FAILURE
, _("unsupported stream name: '%s'"), optarg
);
274 print_version(EXIT_SUCCESS
);
278 errtryhelp(EXIT_FAILURE
);
286 streams
[0] = 'H', streams
[1] = '\0';
288 if (!log_tm
&& idx
< argc
)
289 log_tm
= argv
[idx
++];
290 if (!log_out
&& !summary
&& !log_in
&& !log_io
)
291 log_out
= idx
< argc
? argv
[idx
++] : "typescript";
294 divi
= idx
< argc
? getnum(argv
[idx
]) : 1;
297 errx(EXIT_FAILURE
, _("timing file not specified"));
298 if (!(log_out
|| log_in
|| log_io
) && !summary
)
299 errx(EXIT_FAILURE
, _("data log file not specified"));
301 setup
= replay_new_setup();
303 if (replay_set_timing_file(setup
, log_tm
) != 0)
304 err(EXIT_FAILURE
, _("cannot open %s"), log_tm
);
306 if (log_out
&& replay_associate_log(setup
, "O", log_out
) != 0)
307 err(EXIT_FAILURE
, _("cannot open %s"), log_out
);
309 if (log_in
&& replay_associate_log(setup
, "I", log_in
) != 0)
310 err(EXIT_FAILURE
, _("cannot open %s"), log_in
);
312 if (log_io
&& replay_associate_log(setup
, "IO", log_io
) != 0)
313 err(EXIT_FAILURE
, _("cannot open %s"), log_io
);
316 /* output is preferred default */
317 if (log_out
|| log_io
)
318 appendchr(streams
, sizeof(streams
), 'O');
320 appendchr(streams
, sizeof(streams
), 'I');
323 replay_set_default_type(setup
,
324 *streams
&& streams
[1] == '\0' ? *streams
: 'O');
325 replay_set_crmode(setup
, crmode
);
328 replay_set_delay_div(setup
, divi
);
329 if (timerisset(&maxdelay
))
330 replay_set_delay_max(setup
, &maxdelay
);
331 replay_set_delay_min(setup
, &mindelay
);
333 isterm
= setterm(&saved
, &saved_flag
);
336 switch (fgetc(stdin
)) {
338 replay_toggle_pause(setup
);
344 if (ch
== 'A') { /* Up arrow */
346 replay_set_delay_div(setup
, divi
);
347 } else if (ch
== 'B') { /* Down arrow */
351 replay_set_delay_div(setup
, divi
);
352 } else if (ch
== 'C') { /* Right arrow */
353 rc
= replay_emit_step_data(setup
, step
, STDOUT_FILENO
);
355 rc
= replay_get_next_step(setup
, streams
, &step
);
357 struct timeval
*delay
= replay_step_get_delay(step
);
358 if (delay
&& timerisset(delay
))
368 if (replay_get_is_paused(setup
)) {
369 delay_for(&input_delay
);
373 if (timerisset(&step_delay
)) {
374 const struct timeval
*timeout
= (timercmp(&step_delay
, &input_delay
, <) ? (&step_delay
) : (&input_delay
));
376 timersub(&step_delay
, timeout
, &step_delay
);
377 if (step_delay
.tv_sec
< 0 || step_delay
.tv_usec
< 0)
378 timerclear(&step_delay
);
382 if (!timerisset(&step_delay
) && step
)
383 rc
= replay_emit_step_data(setup
, step
, STDOUT_FILENO
);
387 rc
= replay_get_next_step(setup
, streams
, &step
);
392 struct timeval
*delay
= replay_step_get_delay(step
);
394 if (delay
&& timerisset(delay
))
400 fcntl(STDIN_FILENO
, F_SETFL
, &saved_flag
);
401 tcsetattr(STDOUT_FILENO
, TCSADRAIN
, &saved
);
405 err(EXIT_FAILURE
, _("%s: log file error"), replay_step_get_filename(step
));
407 err(EXIT_FAILURE
, _("%s: line %d: timing file error"),
408 replay_get_timing_file(setup
),
409 replay_get_timing_line(setup
));
411 replay_free_setup(setup
);