2 * Copyright (C) 2008, 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"
40 static UL_DEBUG_DEFINE_MASK(scriptreplay
);
41 UL_DEBUG_DEFINE_MASKNAMES(scriptreplay
) = UL_DEBUG_EMPTY_MASKNAMES
;
43 #define SCRIPTREPLAY_DEBUG_INIT (1 << 1)
44 #define SCRIPTREPLAY_DEBUG_TIMING (1 << 2)
45 #define SCRIPTREPLAY_DEBUG_LOG (1 << 3)
46 #define SCRIPTREPLAY_DEBUG_MISC (1 << 4)
47 #define SCRIPTREPLAY_DEBUG_ALL 0xFFFF
49 #define DBG(m, x) __UL_DBG(scriptreplay, SCRIPTREPLAY_DEBUG_, m, x)
50 #define ON_DBG(m, x) __UL_DBG_CALL(scriptreplay, SCRIPTREPLAY_DEBUG_, m, x)
52 #define SCRIPT_MIN_DELAY 0.0001 /* from original sripreplay.pl */
55 * The script replay is driven by timing file where each entry describes one
56 * step in the replay. The timing step may refer input or output (or
57 * signal, extra informations, etc.)
59 * The step data are stored in log files, the right log file for the step is
60 * selected from replay_setup.
62 * TODO: move struct replay_{log,step,setup} to script-playutils.c to make it
63 * usable for scriptlive(1) code.
67 REPLAY_TIMING_SIMPLE
, /* timing info in classic "<delta> <offset>" format */
68 REPLAY_TIMING_MULTI
/* multiple streams in format "<type> <delta> <offset|etc> */
73 REPLAY_CRMODE_AUTO
= 0,
79 const char *streams
; /* 'I'nput, 'O'utput or both */
85 char type
; /* 'I'nput, 'O'utput, ... */
89 struct replay_log
*data
;
93 struct replay_log
*logs
;
96 struct replay_step step
; /* current step */
99 const char *timing_filename
;
103 char default_type
; /* type for REPLAY_TIMING_SIMPLE */
107 static void scriptreplay_init_debug(void)
109 __UL_INIT_DEBUG_FROM_ENV(scriptreplay
, SCRIPTREPLAY_DEBUG_
, 0, SCRIPTREPLAY_DEBUG
);
112 static int ignore_line(FILE *f
)
116 while((c
= fgetc(f
)) != EOF
&& c
!= '\n');
120 DBG(LOG
, ul_debug(" ignore line"));
124 /* if timing file does not contains types of entries (old format) than use this
125 * type as the default */
126 static int replay_set_default_type(struct replay_setup
*stp
, char type
)
129 stp
->default_type
= type
;
134 static int replay_set_crmode(struct replay_setup
*stp
, int mode
)
142 static int replay_set_timing_file(struct replay_setup
*stp
, const char *filename
)
149 stp
->timing_filename
= filename
;
150 stp
->timing_line
= 0;
152 stp
->timing_fp
= fopen(filename
, "r");
156 /* detect timing file format */
157 c
= fgetc(stp
->timing_fp
);
159 if (isdigit((unsigned int) c
))
160 stp
->timing_format
= REPLAY_TIMING_SIMPLE
;
162 stp
->timing_format
= REPLAY_TIMING_MULTI
;
163 ungetc(c
, stp
->timing_fp
);
164 } else if (ferror(stp
->timing_fp
))
168 if (rc
&& stp
->timing_fp
) {
169 fclose(stp
->timing_fp
);
170 stp
->timing_fp
= NULL
;
173 DBG(TIMING
, ul_debug("timing file set to '%s' [rc=%d]", filename
, rc
));
177 static int replay_associate_log(struct replay_setup
*stp
,
178 const char *streams
, const char *filename
)
180 struct replay_log
*log
;
187 stp
->logs
= xrealloc(stp
->logs
, (stp
->nlogs
+ 1) * sizeof(*log
));
188 log
= &stp
->logs
[stp
->nlogs
];
191 log
->filename
= filename
;
192 log
->streams
= streams
;
194 /* open the file and skip the first line */
195 log
->fp
= fopen(filename
, "r");
196 rc
= log
->fp
== NULL
? -errno
: ignore_line(log
->fp
);
198 DBG(LOG
, ul_debug("accociate log file '%s' with '%s' [rc=%d]", filename
, streams
, rc
));
202 static int is_wanted_stream(char type
, const char *streams
)
206 if (strchr(streams
, type
))
211 static int read_multistream_step(struct replay_step
*step
, FILE *f
, char type
)
217 case 'O': /* output */
218 case 'I': /* input */
219 rc
= fscanf(f
, "%lf %zu%c\n", &step
->delay
, &step
->size
, &nl
);
220 if (rc
!= 3 || nl
!= '\n')
226 case 'S': /* signal */
227 rc
= ignore_line(f
); /* not implemnted yet */
230 case 'H': /* header */
231 rc
= ignore_line(f
); /* not implemnted yet */
237 DBG(TIMING
, ul_debug(" read step delay & size [rc=%d]", rc
));
241 static struct replay_log
*replay_get_stream_log(struct replay_setup
*stp
, char stream
)
245 for (i
= 0; i
< stp
->nlogs
; i
++) {
246 struct replay_log
*log
= &stp
->logs
[i
];
248 if (is_wanted_stream(stream
, log
->streams
))
254 static int replay_seek_log(struct replay_log
*log
, size_t move
)
256 DBG(LOG
, ul_debug(" %s: seek ++ %zu", log
->filename
, move
));
257 return fseek(log
->fp
, move
, SEEK_CUR
) == (off_t
) -1 ? -errno
: 0;
260 /* returns next step with pointer to the right log file for specified streams (e.g.
261 * "IOS" for in/out/signals) or all streams if stream is NULL.
263 * returns: 0 = success, <0 = error, 1 = done (EOF)
265 static int replay_get_next_step(struct replay_setup
*stp
, char *streams
, struct replay_step
**xstep
)
267 struct replay_step
*step
;
269 double ignored_delay
= 0;
272 assert(stp
->timing_fp
);
273 assert(xstep
&& *xstep
);
279 struct replay_log
*log
= NULL
;
282 if (feof(stp
->timing_fp
))
285 DBG(TIMING
, ul_debug("reading next step"));
287 memset(step
, 0, sizeof(*step
));
290 switch (stp
->timing_format
) {
291 case REPLAY_TIMING_SIMPLE
:
292 /* old format is the same as new format, but without <type> prefix */
293 rc
= read_multistream_step(step
, stp
->timing_fp
, stp
->default_type
);
295 step
->type
= stp
->default_type
;
297 case REPLAY_TIMING_MULTI
:
298 rc
= fscanf(stp
->timing_fp
, "%c ", &step
->type
);
302 rc
= read_multistream_step(step
,
311 DBG(TIMING
, ul_debug(" step entry is '%c'", step
->type
));
313 log
= replay_get_stream_log(stp
, step
->type
);
315 if (is_wanted_stream(step
->type
, streams
)) {
318 DBG(LOG
, ul_debug(" use %s as data source", log
->filename
));
321 /* The step entry is unwanted, but we keep the right
322 * position in the log file although the data are ignored.
324 replay_seek_log(log
, step
->size
);
326 DBG(TIMING
, ul_debug(" not found log for '%c' stream", step
->type
));
328 DBG(TIMING
, ul_debug(" ignore step '%c' [delay=%f]",
329 step
->type
, step
->delay
));
330 ignored_delay
+= step
->delay
;
335 step
->delay
+= ignored_delay
;
337 DBG(TIMING
, ul_debug("reading next step done [rc=%d delay=%f (ignored=%f) size=%zu]",
338 rc
, step
->delay
, ignored_delay
, step
->size
));
342 /* return: 0 = success, <0 = error, 1 = done (EOF) */
343 static int replay_emit_step_data(struct replay_setup
*stp
, struct replay_step
*step
, int fd
)
346 int rc
= 0, cr2nl
= 0;
353 assert(step
->data
->fp
);
355 switch (stp
->crmode
) {
356 case REPLAY_CRMODE_AUTO
:
357 if (step
->type
== 'I')
360 case REPLAY_CRMODE_NEVER
:
363 case REPLAY_CRMODE_ALWAYS
:
368 for (ct
= step
->size
; ct
> 0; ) {
371 cc
= ct
> sizeof(buf
) ? sizeof(buf
): ct
;
372 len
= fread(buf
, 1, cc
, step
->data
->fp
);
375 DBG(LOG
, ul_debug("log data emit: failed to read log %m"));
382 for (i
= 0; i
< len
; i
++) {
389 cc
= write(fd
, buf
, len
);
392 DBG(LOG
, ul_debug("log data emit: failed write data %m"));
397 if (ct
&& ferror(step
->data
->fp
))
399 if (ct
&& feof(step
->data
->fp
))
402 DBG(LOG
, ul_debug("log data emited [rc=%d size=%zu]", rc
, step
->size
));
406 static void __attribute__((__noreturn__
))
410 fputs(USAGE_HEADER
, out
);
412 _(" %s [options]\n"),
413 program_invocation_short_name
);
415 _(" %s [-t] timingfile [typescript] [divisor]\n"),
416 program_invocation_short_name
);
418 fputs(USAGE_SEPARATOR
, out
);
419 fputs(_("Play back terminal typescripts, using timing information.\n"), out
);
421 fputs(USAGE_OPTIONS
, out
);
422 fputs(_(" -t, --timing <file> script timing log file\n"), out
);
423 fputs(_(" -I, --log-in <file> script stdin log file\n"), out
);
424 fputs(_(" -O, --log-out <file> script stdout log file (default)\n"), out
);
425 fputs(_(" -B, --log-io <file> script stdin and stdout log file\n"), out
);
426 fputs(_(" -s, --typescript <file> deprecated alist to -O\n"), out
);
428 fputs(USAGE_SEPARATOR
, out
);
429 fputs(_(" -d, --divisor <num> speed up or slow down execution with time divisor\n"), out
);
430 fputs(_(" -m, --maxdelay <num> wait at most this many seconds between updates\n"), out
);
431 fputs(_(" -x, --stream <name> stream type (out, in or signal)\n"), out
);
432 fputs(_(" -c, --cr-mode <type> CR char mode (auto, never, always)\n"), out
);
433 printf(USAGE_HELP_OPTIONS(25));
435 printf(USAGE_MAN_TAIL("scriptreplay(1)"));
440 getnum(const char *s
)
442 const double d
= strtod_or_err(s
, _("failed to parse number"));
446 err(EXIT_FAILURE
, "%s: %s", _("failed to parse number"), s
);
452 delay_for(double delay
)
454 #ifdef HAVE_NANOSLEEP
455 struct timespec ts
, remainder
;
456 ts
.tv_sec
= (time_t) delay
;
457 ts
.tv_nsec
= (delay
- ts
.tv_sec
) * 1.0e9
;
459 DBG(TIMING
, ul_debug("going to sleep for %fs", delay
));
461 while (-1 == nanosleep(&ts
, &remainder
)) {
469 tv
.tv_sec
= (long) delay
;
470 tv
.tv_usec
= (delay
- tv
.tv_sec
) * 1.0e6
;
471 select(0, NULL
, NULL
, NULL
, &tv
);
475 static void appendchr(char *buf
, size_t bufsz
, int c
)
480 return; /* already in */
488 main(int argc
, char *argv
[])
490 struct replay_setup setup
= { .nlogs
= 0 };
491 struct replay_step
*step
;
492 char streams
[6] = {0}; /* IOSI - in, out, signal,info */
493 const char *log_out
= NULL
,
497 double divi
= 1, maxdelay
= 0;
498 int diviopt
= FALSE
, maxdelayopt
= FALSE
, idx
;
499 int ch
, rc
, crmode
= REPLAY_CRMODE_AUTO
;
501 static const struct option longopts
[] = {
502 { "cr-mode", required_argument
, 0, 'c' },
503 { "timing", required_argument
, 0, 't' },
504 { "log-in", required_argument
, 0, 'I'},
505 { "log-out", required_argument
, 0, 'O'},
506 { "log-io", required_argument
, 0, 'B'},
507 { "typescript", required_argument
, 0, 's' },
508 { "divisor", required_argument
, 0, 'd' },
509 { "maxdelay", required_argument
, 0, 'm' },
510 { "stream", required_argument
, 0, 'x' },
511 { "version", no_argument
, 0, 'V' },
512 { "help", no_argument
, 0, 'h' },
515 static const ul_excl_t excl
[] = { /* rows and cols in ASCII order */
519 int excl_st
[ARRAY_SIZE(excl
)] = UL_EXCL_STATUS_INIT
;
520 /* Because we use space as a separator, we can't afford to use any
521 * locale which tolerates a space in a number. In any case, script.c
522 * sets the LC_NUMERIC locale to C, anyway.
524 setlocale(LC_ALL
, "");
525 setlocale(LC_NUMERIC
, "C");
527 bindtextdomain(PACKAGE
, LOCALEDIR
);
529 close_stdout_atexit();
531 scriptreplay_init_debug();
533 while ((ch
= getopt_long(argc
, argv
, "B:c:I:O:t:s:d:m:x:Vh", longopts
, NULL
)) != -1) {
535 err_exclusive_options(ch
, longopts
, excl
, excl_st
);
539 if (strcmp("auto", optarg
) == 0)
540 crmode
= REPLAY_CRMODE_AUTO
;
541 else if (strcmp("never", optarg
) == 0)
542 crmode
= REPLAY_CRMODE_NEVER
;
543 else if (strcmp("always", optarg
) == 0)
544 crmode
= REPLAY_CRMODE_ALWAYS
;
546 errx(EXIT_FAILURE
, _("unsupported mode name: '%s'"), optarg
);
563 divi
= getnum(optarg
);
567 maxdelay
= getnum(optarg
);
570 if (strcmp("in", optarg
) == 0)
571 appendchr(streams
, sizeof(streams
), 'I');
572 else if (strcmp("out", optarg
) == 0)
573 appendchr(streams
, sizeof(streams
), 'O');
574 else if (strcmp("signal", optarg
) == 0)
575 appendchr(streams
, sizeof(streams
), 'S');
577 errx(EXIT_FAILURE
, _("unsupported stream name: '%s'"), optarg
);
580 print_version(EXIT_SUCCESS
);
584 errtryhelp(EXIT_FAILURE
);
591 if ((argc
< 1 && !(log_out
|| log_in
|| log_io
)) || argc
> 3) {
592 warnx(_("wrong number of arguments"));
593 errtryhelp(EXIT_FAILURE
);
596 log_tm
= argv
[idx
++];
597 if (!log_out
&& !log_in
&& !log_io
)
598 log_out
= idx
< argc
? argv
[idx
++] : "typescript";
601 divi
= idx
< argc
? getnum(argv
[idx
]) : 1;
606 errx(EXIT_FAILURE
, _("timing file not specified"));
607 else if (replay_set_timing_file(&setup
, log_tm
) != 0)
608 err(EXIT_FAILURE
, _("cannot open %s"), log_tm
);
610 if (log_out
&& replay_associate_log(&setup
, "O", log_out
) != 0)
611 err(EXIT_FAILURE
, _("cannot open %s"), log_out
);
613 if (log_in
&& replay_associate_log(&setup
, "I", log_in
) != 0)
614 err(EXIT_FAILURE
, _("cannot open %s"), log_in
);
616 if (log_io
&& replay_associate_log(&setup
, "IO", log_io
) != 0)
617 err(EXIT_FAILURE
, _("cannot open %s"), log_io
);
620 /* output is prefered default */
621 if (log_out
|| log_io
)
622 appendchr(streams
, sizeof(streams
), 'O');
624 appendchr(streams
, sizeof(streams
), 'I');
627 replay_set_default_type(&setup
,
628 *streams
&& streams
[1] == '\0' ? *streams
: 'O');
629 replay_set_crmode(&setup
, crmode
);
632 rc
= replay_get_next_step(&setup
, streams
, &step
);
637 if (maxdelayopt
&& step
->delay
> maxdelay
)
638 step
->delay
= maxdelay
;
639 if (step
->delay
> SCRIPT_MIN_DELAY
)
640 delay_for(step
->delay
);
642 rc
= replay_emit_step_data(&setup
, step
, STDOUT_FILENO
);
646 err(EXIT_FAILURE
, _("%s: log file error"), step
->data
->filename
);
648 err(EXIT_FAILURE
, _("%s: line %d: timing file error"),
649 setup
.timing_filename
,