2 * Copyright (C) 2008-2019, Karel Zak <kzak@redhat.com>
3 * Copyright (C) 2008, James Youngman <jay@gnu.org>
5 * This file is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This file is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
16 * Based on scriptreplay.pl by Joey Hess <joey@kitenet.net>
27 #include <sys/select.h>
35 #include "closestream.h"
39 #include "script-playutils.h"
41 static void __attribute__((__noreturn__
))
45 fputs(USAGE_HEADER
, out
);
48 program_invocation_short_name
);
50 _(" %s [-t] 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
);
71 printf(USAGE_HELP_OPTIONS(25));
73 printf(USAGE_MAN_TAIL("scriptreplay(1)"));
80 const double d
= strtod_or_err(s
, _("failed to parse number"));
84 err(EXIT_FAILURE
, "%s: %s", _("failed to parse number"), s
);
90 delay_for(struct timeval
*delay
)
93 struct timespec ts
, remainder
;
94 ts
.tv_sec
= (time_t) delay
->tv_sec
;
95 ts
.tv_nsec
= delay
->tv_usec
* 1000;
97 DBG(TIMING
, ul_debug("going to sleep for %"PRId64
".%06"PRId64
,
98 (int64_t) delay
->tv_sec
, (int64_t) delay
->tv_usec
));
100 while (-1 == nanosleep(&ts
, &remainder
)) {
107 select(0, NULL
, NULL
, NULL
, delay
);
112 appendchr(char *buf
, size_t bufsz
, int c
)
117 return; /* already in */
125 setterm(struct termios
*backup
)
127 struct termios tattr
;
129 if (tcgetattr(STDOUT_FILENO
, backup
) != 0) {
130 if (errno
!= ENOTTY
) /* For debugger. */
131 err(EXIT_FAILURE
, _("unexpected tcgetattr failure"));
136 tattr
.c_lflag
|= ISIG
;
137 tattr
.c_iflag
|= IXON
;
138 tcsetattr(STDOUT_FILENO
, TCSANOW
, &tattr
);
143 main(int argc
, char *argv
[])
145 static const struct timeval mindelay
= { .tv_sec
= 0, .tv_usec
= 100 };
146 struct timeval maxdelay
;
149 struct termios saved
;
151 struct replay_setup
*setup
= NULL
;
152 struct replay_step
*step
= NULL
;
153 char streams
[6] = {0}; /* IOSI - in, out, signal,info */
154 const char *log_out
= NULL
,
159 int diviopt
= FALSE
, idx
;
160 int ch
, rc
, crmode
= REPLAY_CRMODE_AUTO
, summary
= 0;
162 OPT_SUMMARY
= CHAR_MAX
+ 1
165 static const struct option longopts
[] = {
166 { "cr-mode", required_argument
, 0, 'c' },
167 { "timing", required_argument
, 0, 't' },
168 { "log-timing", required_argument
, 0, 'T' },
169 { "log-in", required_argument
, 0, 'I' },
170 { "log-out", required_argument
, 0, 'O' },
171 { "log-io", required_argument
, 0, 'B' },
172 { "typescript", required_argument
, 0, 's' },
173 { "divisor", required_argument
, 0, 'd' },
174 { "maxdelay", required_argument
, 0, 'm' },
175 { "stream", required_argument
, 0, 'x' },
176 { "summary", no_argument
, 0, OPT_SUMMARY
},
177 { "version", no_argument
, 0, 'V' },
178 { "help", no_argument
, 0, 'h' },
181 static const ul_excl_t excl
[] = { /* rows and cols in ASCII order */
185 int excl_st
[ARRAY_SIZE(excl
)] = UL_EXCL_STATUS_INIT
;
186 /* Because we use space as a separator, we can't afford to use any
187 * locale which tolerates a space in a number. In any case, script.c
188 * sets the LC_NUMERIC locale to C, anyway.
190 setlocale(LC_ALL
, "");
191 setlocale(LC_NUMERIC
, "C");
193 bindtextdomain(PACKAGE
, LOCALEDIR
);
195 close_stdout_atexit();
198 timerclear(&maxdelay
);
200 while ((ch
= getopt_long(argc
, argv
, "B:c:I:O:T:t:s:d:m:x:Vh", longopts
, NULL
)) != -1) {
202 err_exclusive_options(ch
, longopts
, excl
, excl_st
);
206 if (strcmp("auto", optarg
) == 0)
207 crmode
= REPLAY_CRMODE_AUTO
;
208 else if (strcmp("never", optarg
) == 0)
209 crmode
= REPLAY_CRMODE_NEVER
;
210 else if (strcmp("always", optarg
) == 0)
211 crmode
= REPLAY_CRMODE_ALWAYS
;
213 errx(EXIT_FAILURE
, _("unsupported mode name: '%s'"), optarg
);
231 divi
= getnum(optarg
);
234 strtotimeval_or_err(optarg
, &maxdelay
, _("failed to parse maximal delay argument"));
237 if (strcmp("in", optarg
) == 0)
238 appendchr(streams
, sizeof(streams
), 'I');
239 else if (strcmp("out", optarg
) == 0)
240 appendchr(streams
, sizeof(streams
), 'O');
241 else if (strcmp("signal", optarg
) == 0)
242 appendchr(streams
, sizeof(streams
), 'S');
243 else if (strcmp("info", optarg
) == 0)
244 appendchr(streams
, sizeof(streams
), 'H');
246 errx(EXIT_FAILURE
, _("unsupported stream name: '%s'"), optarg
);
252 print_version(EXIT_SUCCESS
);
256 errtryhelp(EXIT_FAILURE
);
264 streams
[0] = 'H', streams
[1] = '\0';
266 if (!log_tm
&& idx
< argc
)
267 log_tm
= argv
[idx
++];
268 if (!log_out
&& !summary
&& !log_in
&& !log_io
)
269 log_out
= idx
< argc
? argv
[idx
++] : "typescript";
272 divi
= idx
< argc
? getnum(argv
[idx
]) : 1;
275 errx(EXIT_FAILURE
, _("timing file not specified"));
276 if (!(log_out
|| log_in
|| log_io
) && !summary
)
277 errx(EXIT_FAILURE
, _("data log file not specified"));
279 setup
= replay_new_setup();
281 if (replay_set_timing_file(setup
, log_tm
) != 0)
282 err(EXIT_FAILURE
, _("cannot open %s"), log_tm
);
284 if (log_out
&& replay_associate_log(setup
, "O", log_out
) != 0)
285 err(EXIT_FAILURE
, _("cannot open %s"), log_out
);
287 if (log_in
&& replay_associate_log(setup
, "I", log_in
) != 0)
288 err(EXIT_FAILURE
, _("cannot open %s"), log_in
);
290 if (log_io
&& replay_associate_log(setup
, "IO", log_io
) != 0)
291 err(EXIT_FAILURE
, _("cannot open %s"), log_io
);
294 /* output is preferred default */
295 if (log_out
|| log_io
)
296 appendchr(streams
, sizeof(streams
), 'O');
298 appendchr(streams
, sizeof(streams
), 'I');
301 replay_set_default_type(setup
,
302 *streams
&& streams
[1] == '\0' ? *streams
: 'O');
303 replay_set_crmode(setup
, crmode
);
306 replay_set_delay_div(setup
, divi
);
307 if (timerisset(&maxdelay
))
308 replay_set_delay_max(setup
, &maxdelay
);
309 replay_set_delay_min(setup
, &mindelay
);
311 isterm
= setterm(&saved
);
314 rc
= replay_get_next_step(setup
, streams
, &step
);
319 struct timeval
*delay
= replay_step_get_delay(step
);
321 if (delay
&& timerisset(delay
))
324 rc
= replay_emit_step_data(setup
, step
, STDOUT_FILENO
);
328 tcsetattr(STDOUT_FILENO
, TCSADRAIN
, &saved
);
331 err(EXIT_FAILURE
, _("%s: log file error"), replay_step_get_filename(step
));
333 err(EXIT_FAILURE
, _("%s: line %d: timing file error"),
334 replay_get_timing_file(setup
),
335 replay_get_timing_line(setup
));
337 replay_free_setup(setup
);