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 */
83 unsigned int noseek
; /* do not seek in this log */
87 char type
; /* 'I'nput, 'O'utput, ... */
91 char *name
; /* signals / heders */
94 struct replay_log
*data
;
98 struct replay_log
*logs
;
101 struct replay_step step
; /* current step */
104 const char *timing_filename
;
108 char default_type
; /* type for REPLAY_TIMING_SIMPLE */
112 static void scriptreplay_init_debug(void)
114 __UL_INIT_DEBUG_FROM_ENV(scriptreplay
, SCRIPTREPLAY_DEBUG_
, 0, SCRIPTREPLAY_DEBUG
);
117 static int ignore_line(FILE *f
)
121 while((c
= fgetc(f
)) != EOF
&& c
!= '\n');
125 DBG(LOG
, ul_debug(" ignore line"));
129 /* if timing file does not contains types of entries (old format) than use this
130 * type as the default */
131 static int replay_set_default_type(struct replay_setup
*stp
, char type
)
134 stp
->default_type
= type
;
139 static int replay_set_crmode(struct replay_setup
*stp
, int mode
)
147 static struct replay_log
*replay_new_log(struct replay_setup
*stp
,
149 const char *filename
,
152 struct replay_log
*log
;
158 stp
->logs
= xrealloc(stp
->logs
, (stp
->nlogs
+ 1) * sizeof(*log
));
159 log
= &stp
->logs
[stp
->nlogs
];
162 log
->filename
= filename
;
163 log
->streams
= streams
;
169 static int replay_set_timing_file(struct replay_setup
*stp
, const char *filename
)
176 stp
->timing_filename
= filename
;
177 stp
->timing_line
= 0;
179 stp
->timing_fp
= fopen(filename
, "r");
183 /* detect timing file format */
184 c
= fgetc(stp
->timing_fp
);
186 if (isdigit((unsigned int) c
))
187 stp
->timing_format
= REPLAY_TIMING_SIMPLE
;
189 stp
->timing_format
= REPLAY_TIMING_MULTI
;
190 ungetc(c
, stp
->timing_fp
);
191 } else if (ferror(stp
->timing_fp
))
195 if (rc
&& stp
->timing_fp
) {
196 fclose(stp
->timing_fp
);
197 stp
->timing_fp
= NULL
;
200 /* create quasi-log for signals, headers, etc. */
201 if (rc
== 0 && stp
->timing_format
== REPLAY_TIMING_MULTI
) {
202 struct replay_log
*log
= replay_new_log(stp
, "SH",
203 filename
, stp
->timing_fp
);
208 DBG(LOG
, ul_debug("accociate log file '%s' with 'SH'", filename
));
212 DBG(TIMING
, ul_debug("timing file set to '%s' [rc=%d]", filename
, rc
));
217 static int replay_associate_log(struct replay_setup
*stp
,
218 const char *streams
, const char *filename
)
227 /* open the file and skip the first line */
228 f
= fopen(filename
, "r");
229 rc
= f
== NULL
? -errno
: ignore_line(f
);
232 replay_new_log(stp
, streams
, filename
, f
);
234 DBG(LOG
, ul_debug("accociate log file '%s' with '%s' [rc=%d]", filename
, streams
, rc
));
238 static int is_wanted_stream(char type
, const char *streams
)
242 if (strchr(streams
, type
))
247 static void replay_reset_step(struct replay_step
*step
)
257 static int read_multistream_step(struct replay_step
*step
, FILE *f
, char type
)
264 case 'O': /* output */
265 case 'I': /* input */
266 rc
= fscanf(f
, "%lf %zu%c\n", &step
->delay
, &step
->size
, &nl
);
267 if (rc
!= 3 || nl
!= '\n')
273 case 'S': /* signal */
274 case 'H': /* header */
278 rc
= fscanf(f
, "%lf ", &step
->delay
); /* delay */
282 rc
= fscanf(f
, "%s", buf
); /* name */
285 step
->name
= strrealloc(step
->name
, buf
);
289 if (!fgets(buf
, sizeof(buf
), f
)) { /* value */
295 step
->value
= strrealloc(step
->value
, buf
);
306 DBG(TIMING
, ul_debug(" read step delay & size [rc=%d]", rc
));
310 static struct replay_log
*replay_get_stream_log(struct replay_setup
*stp
, char stream
)
314 for (i
= 0; i
< stp
->nlogs
; i
++) {
315 struct replay_log
*log
= &stp
->logs
[i
];
317 if (is_wanted_stream(stream
, log
->streams
))
323 static int replay_seek_log(struct replay_log
*log
, size_t move
)
328 DBG(LOG
, ul_debug(" %s: seek ++ %zu", log
->filename
, move
));
329 return fseek(log
->fp
, move
, SEEK_CUR
) == (off_t
) -1 ? -errno
: 0;
332 /* returns next step with pointer to the right log file for specified streams (e.g.
333 * "IOS" for in/out/signals) or all streams if stream is NULL.
335 * returns: 0 = success, <0 = error, 1 = done (EOF)
337 static int replay_get_next_step(struct replay_setup
*stp
, char *streams
, struct replay_step
**xstep
)
339 struct replay_step
*step
;
341 double ignored_delay
= 0;
344 assert(stp
->timing_fp
);
345 assert(xstep
&& *xstep
);
351 struct replay_log
*log
= NULL
;
354 if (feof(stp
->timing_fp
))
357 DBG(TIMING
, ul_debug("reading next step"));
359 replay_reset_step(step
);
362 switch (stp
->timing_format
) {
363 case REPLAY_TIMING_SIMPLE
:
364 /* old format is the same as new format, but without <type> prefix */
365 rc
= read_multistream_step(step
, stp
->timing_fp
, stp
->default_type
);
367 step
->type
= stp
->default_type
;
369 case REPLAY_TIMING_MULTI
:
370 rc
= fscanf(stp
->timing_fp
, "%c ", &step
->type
);
374 rc
= read_multistream_step(step
,
383 DBG(TIMING
, ul_debug(" step entry is '%c'", step
->type
));
385 log
= replay_get_stream_log(stp
, step
->type
);
387 if (is_wanted_stream(step
->type
, streams
)) {
390 DBG(LOG
, ul_debug(" use %s as data source", log
->filename
));
393 /* The step entry is unwanted, but we keep the right
394 * position in the log file although the data are ignored.
396 replay_seek_log(log
, step
->size
);
398 DBG(TIMING
, ul_debug(" not found log for '%c' stream", step
->type
));
400 DBG(TIMING
, ul_debug(" ignore step '%c' [delay=%f]",
401 step
->type
, step
->delay
));
402 ignored_delay
+= step
->delay
;
407 step
->delay
+= ignored_delay
;
409 DBG(TIMING
, ul_debug("reading next step done [rc=%d delay=%f (ignored=%f) size=%zu]",
410 rc
, step
->delay
, ignored_delay
, step
->size
));
414 /* return: 0 = success, <0 = error, 1 = done (EOF) */
415 static int replay_emit_step_data(struct replay_setup
*stp
, struct replay_step
*step
, int fd
)
418 int rc
= 0, cr2nl
= 0;
423 switch (step
->type
) {
427 dprintf(fd
, "%s %s\n", step
->name
, step
->value
);
428 DBG(LOG
, ul_debug("log signal emited"));
433 dprintf(fd
, "%10s: %s\n", step
->name
, step
->value
);
434 DBG(LOG
, ul_debug("log signal emited"));
437 break; /* continue with real data */
442 assert(step
->data
->fp
);
444 switch (stp
->crmode
) {
445 case REPLAY_CRMODE_AUTO
:
446 if (step
->type
== 'I')
449 case REPLAY_CRMODE_NEVER
:
452 case REPLAY_CRMODE_ALWAYS
:
457 for (ct
= step
->size
; ct
> 0; ) {
460 cc
= ct
> sizeof(buf
) ? sizeof(buf
): ct
;
461 len
= fread(buf
, 1, cc
, step
->data
->fp
);
464 DBG(LOG
, ul_debug("log data emit: failed to read log %m"));
471 for (i
= 0; i
< len
; i
++) {
478 cc
= write(fd
, buf
, len
);
481 DBG(LOG
, ul_debug("log data emit: failed write data %m"));
486 if (ct
&& ferror(step
->data
->fp
))
488 if (ct
&& feof(step
->data
->fp
))
491 DBG(LOG
, ul_debug("log data emited [rc=%d size=%zu]", rc
, step
->size
));
495 static void __attribute__((__noreturn__
))
499 fputs(USAGE_HEADER
, out
);
501 _(" %s [options]\n"),
502 program_invocation_short_name
);
504 _(" %s [-t] timingfile [typescript] [divisor]\n"),
505 program_invocation_short_name
);
507 fputs(USAGE_SEPARATOR
, out
);
508 fputs(_("Play back terminal typescripts, using timing information.\n"), out
);
510 fputs(USAGE_OPTIONS
, out
);
511 fputs(_(" -t, --timing <file> script timing log file\n"), out
);
512 fputs(_(" -I, --log-in <file> script stdin log file\n"), out
);
513 fputs(_(" -O, --log-out <file> script stdout log file (default)\n"), out
);
514 fputs(_(" -B, --log-io <file> script stdin and stdout log file\n"), out
);
515 fputs(_(" -s, --typescript <file> deprecated alist to -O\n"), out
);
517 fputs(USAGE_SEPARATOR
, out
);
518 fputs(_(" -d, --divisor <num> speed up or slow down execution with time divisor\n"), out
);
519 fputs(_(" -m, --maxdelay <num> wait at most this many seconds between updates\n"), out
);
520 fputs(_(" -x, --stream <name> stream type (out, in, signal or info)\n"), out
);
521 fputs(_(" -c, --cr-mode <type> CR char mode (auto, never, always)\n"), out
);
522 printf(USAGE_HELP_OPTIONS(25));
524 printf(USAGE_MAN_TAIL("scriptreplay(1)"));
529 getnum(const char *s
)
531 const double d
= strtod_or_err(s
, _("failed to parse number"));
535 err(EXIT_FAILURE
, "%s: %s", _("failed to parse number"), s
);
541 delay_for(double delay
)
543 #ifdef HAVE_NANOSLEEP
544 struct timespec ts
, remainder
;
545 ts
.tv_sec
= (time_t) delay
;
546 ts
.tv_nsec
= (delay
- ts
.tv_sec
) * 1.0e9
;
548 DBG(TIMING
, ul_debug("going to sleep for %fs", delay
));
550 while (-1 == nanosleep(&ts
, &remainder
)) {
558 tv
.tv_sec
= (long) delay
;
559 tv
.tv_usec
= (delay
- tv
.tv_sec
) * 1.0e6
;
560 select(0, NULL
, NULL
, NULL
, &tv
);
564 static void appendchr(char *buf
, size_t bufsz
, int c
)
569 return; /* already in */
577 main(int argc
, char *argv
[])
579 struct replay_setup setup
= { .nlogs
= 0 };
580 struct replay_step
*step
;
581 char streams
[6] = {0}; /* IOSI - in, out, signal,info */
582 const char *log_out
= NULL
,
586 double divi
= 1, maxdelay
= 0;
587 int diviopt
= FALSE
, maxdelayopt
= FALSE
, idx
;
588 int ch
, rc
, crmode
= REPLAY_CRMODE_AUTO
;
590 static const struct option longopts
[] = {
591 { "cr-mode", required_argument
, 0, 'c' },
592 { "timing", required_argument
, 0, 't' },
593 { "log-in", required_argument
, 0, 'I'},
594 { "log-out", required_argument
, 0, 'O'},
595 { "log-io", required_argument
, 0, 'B'},
596 { "typescript", required_argument
, 0, 's' },
597 { "divisor", required_argument
, 0, 'd' },
598 { "maxdelay", required_argument
, 0, 'm' },
599 { "stream", required_argument
, 0, 'x' },
600 { "version", no_argument
, 0, 'V' },
601 { "help", no_argument
, 0, 'h' },
604 static const ul_excl_t excl
[] = { /* rows and cols in ASCII order */
608 int excl_st
[ARRAY_SIZE(excl
)] = UL_EXCL_STATUS_INIT
;
609 /* Because we use space as a separator, we can't afford to use any
610 * locale which tolerates a space in a number. In any case, script.c
611 * sets the LC_NUMERIC locale to C, anyway.
613 setlocale(LC_ALL
, "");
614 setlocale(LC_NUMERIC
, "C");
616 bindtextdomain(PACKAGE
, LOCALEDIR
);
618 close_stdout_atexit();
620 scriptreplay_init_debug();
622 while ((ch
= getopt_long(argc
, argv
, "B:c:I:O:t:s:d:m:x:Vh", longopts
, NULL
)) != -1) {
624 err_exclusive_options(ch
, longopts
, excl
, excl_st
);
628 if (strcmp("auto", optarg
) == 0)
629 crmode
= REPLAY_CRMODE_AUTO
;
630 else if (strcmp("never", optarg
) == 0)
631 crmode
= REPLAY_CRMODE_NEVER
;
632 else if (strcmp("always", optarg
) == 0)
633 crmode
= REPLAY_CRMODE_ALWAYS
;
635 errx(EXIT_FAILURE
, _("unsupported mode name: '%s'"), optarg
);
652 divi
= getnum(optarg
);
656 maxdelay
= getnum(optarg
);
659 if (strcmp("in", optarg
) == 0)
660 appendchr(streams
, sizeof(streams
), 'I');
661 else if (strcmp("out", optarg
) == 0)
662 appendchr(streams
, sizeof(streams
), 'O');
663 else if (strcmp("signal", optarg
) == 0)
664 appendchr(streams
, sizeof(streams
), 'S');
665 else if (strcmp("info", optarg
) == 0)
666 appendchr(streams
, sizeof(streams
), 'H');
668 errx(EXIT_FAILURE
, _("unsupported stream name: '%s'"), optarg
);
671 print_version(EXIT_SUCCESS
);
675 errtryhelp(EXIT_FAILURE
);
682 if (!log_tm
&& idx
< argc
)
683 log_tm
= argv
[idx
++];
684 if (!log_out
&& !log_in
&& !log_io
)
685 log_out
= idx
< argc
? argv
[idx
++] : "typescript";
688 divi
= idx
< argc
? getnum(argv
[idx
]) : 1;
693 errx(EXIT_FAILURE
, _("timing file not specified"));
694 if (!(log_out
|| log_in
|| log_io
))
695 errx(EXIT_FAILURE
, _("data log file not specified"));
697 if (replay_set_timing_file(&setup
, log_tm
) != 0)
698 err(EXIT_FAILURE
, _("cannot open %s"), log_tm
);
700 if (log_out
&& replay_associate_log(&setup
, "O", log_out
) != 0)
701 err(EXIT_FAILURE
, _("cannot open %s"), log_out
);
703 if (log_in
&& replay_associate_log(&setup
, "I", log_in
) != 0)
704 err(EXIT_FAILURE
, _("cannot open %s"), log_in
);
706 if (log_io
&& replay_associate_log(&setup
, "IO", log_io
) != 0)
707 err(EXIT_FAILURE
, _("cannot open %s"), log_io
);
710 /* output is prefered default */
711 if (log_out
|| log_io
)
712 appendchr(streams
, sizeof(streams
), 'O');
714 appendchr(streams
, sizeof(streams
), 'I');
717 replay_set_default_type(&setup
,
718 *streams
&& streams
[1] == '\0' ? *streams
: 'O');
719 replay_set_crmode(&setup
, crmode
);
722 rc
= replay_get_next_step(&setup
, streams
, &step
);
727 if (maxdelayopt
&& step
->delay
> maxdelay
)
728 step
->delay
= maxdelay
;
729 if (step
->delay
> SCRIPT_MIN_DELAY
)
730 delay_for(step
->delay
);
732 rc
= replay_emit_step_data(&setup
, step
, STDOUT_FILENO
);
736 err(EXIT_FAILURE
, _("%s: log file error"), step
->data
->filename
);
738 err(EXIT_FAILURE
, _("%s: line %d: timing file error"),
739 setup
.timing_filename
,