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> */
72 const char *streams
; /* 'I'nput, 'O'utput or both */
78 char type
; /* 'I'nput, 'O'utput, ... */
82 struct replay_log
*data
;
86 struct replay_log
*logs
;
89 struct replay_step step
; /* current step */
92 const char *timing_filename
;
96 char default_type
; /* type for REPLAY_TIMING_SIMPLE */
99 static void scriptreplay_init_debug(void)
101 __UL_INIT_DEBUG_FROM_ENV(scriptreplay
, SCRIPTREPLAY_DEBUG_
, 0, SCRIPTREPLAY_DEBUG
);
104 static int ignore_line(FILE *f
)
108 while((c
= fgetc(f
)) != EOF
&& c
!= '\n');
112 DBG(LOG
, ul_debug(" ignore line"));
116 /* if timing file does not contains types of entries (old format) than use this
117 * type as the default */
118 static int replay_set_default_type(struct replay_setup
*stp
, char type
)
121 stp
->default_type
= type
;
126 static int replay_set_timing_file(struct replay_setup
*stp
, const char *filename
)
133 stp
->timing_filename
= filename
;
134 stp
->timing_line
= 0;
136 stp
->timing_fp
= fopen(filename
, "r");
140 /* detect timing file format */
141 c
= fgetc(stp
->timing_fp
);
143 if (isdigit((unsigned int) c
))
144 stp
->timing_format
= REPLAY_TIMING_SIMPLE
;
146 stp
->timing_format
= REPLAY_TIMING_MULTI
;
147 ungetc(c
, stp
->timing_fp
);
148 } else if (ferror(stp
->timing_fp
))
152 if (rc
&& stp
->timing_fp
) {
153 fclose(stp
->timing_fp
);
154 stp
->timing_fp
= NULL
;
157 DBG(TIMING
, ul_debug("timing file set to '%s' [rc=%d]", filename
, rc
));
161 static int replay_associate_log(struct replay_setup
*stp
,
162 const char *streams
, const char *filename
)
164 struct replay_log
*log
;
171 stp
->logs
= xrealloc(stp
->logs
, (stp
->nlogs
+ 1) * sizeof(*log
));
172 log
= &stp
->logs
[stp
->nlogs
];
175 log
->filename
= filename
;
176 log
->streams
= streams
;
178 /* open the file and skip the first line */
179 log
->fp
= fopen(filename
, "r");
180 rc
= log
->fp
== NULL
? -errno
: ignore_line(log
->fp
);
182 DBG(LOG
, ul_debug("accociate log file '%s' with '%s' [rc=%d]", filename
, streams
, rc
));
186 static int is_wanted_stream(char type
, const char *streams
)
190 if (strchr(streams
, type
))
195 static int read_multistream_step(struct replay_step
*step
, FILE *f
, char type
)
201 case 'O': /* output */
202 case 'I': /* input */
203 rc
= fscanf(f
, "%lf %zu%c\n", &step
->delay
, &step
->size
, &nl
);
204 if (rc
!= 3 || nl
!= '\n')
210 case 'S': /* signal */
211 rc
= ignore_line(f
); /* not implemnted yet */
214 case 'H': /* header */
215 rc
= ignore_line(f
); /* not implemnted yet */
221 DBG(TIMING
, ul_debug(" read step delay & size [rc=%d]", rc
));
225 static struct replay_log
*replay_get_stream_log(struct replay_setup
*stp
, char stream
)
229 for (i
= 0; i
< stp
->nlogs
; i
++) {
230 struct replay_log
*log
= &stp
->logs
[i
];
232 if (is_wanted_stream(stream
, log
->streams
))
238 static int replay_seek_log(struct replay_log
*log
, size_t move
)
240 DBG(LOG
, ul_debug(" %s: seek ++ %zu", log
->filename
, move
));
241 return fseek(log
->fp
, move
, SEEK_CUR
) == (off_t
) -1 ? -errno
: 0;
244 /* returns next step with pointer to the right log file for specified streams (e.g.
245 * "IOS" for in/out/signals) or all streams if stream is NULL.
247 * returns: 0 = success, <0 = error, 1 = done (EOF)
249 static int replay_get_next_step(struct replay_setup
*stp
, char *streams
, struct replay_step
**xstep
)
251 struct replay_step
*step
;
253 double ignored_delay
= 0;
256 assert(stp
->timing_fp
);
257 assert(xstep
&& *xstep
);
263 struct replay_log
*log
= NULL
;
266 if (feof(stp
->timing_fp
))
269 DBG(TIMING
, ul_debug("reading next step"));
271 memset(step
, 0, sizeof(*step
));
274 switch (stp
->timing_format
) {
275 case REPLAY_TIMING_SIMPLE
:
276 /* old format is the same as new format, but without <type> prefix */
277 rc
= read_multistream_step(step
, stp
->timing_fp
, stp
->default_type
);
279 step
->type
= stp
->default_type
;
281 case REPLAY_TIMING_MULTI
:
282 rc
= fscanf(stp
->timing_fp
, "%c ", &step
->type
);
286 rc
= read_multistream_step(step
,
295 DBG(TIMING
, ul_debug(" step entry is '%c'", step
->type
));
297 log
= replay_get_stream_log(stp
, step
->type
);
299 if (is_wanted_stream(step
->type
, streams
)) {
302 DBG(LOG
, ul_debug(" use %s as data source", log
->filename
));
305 /* The step entry is unwanted, but we keep the right
306 * position in the log file although the data are ignored.
308 replay_seek_log(log
, step
->size
);
310 DBG(TIMING
, ul_debug(" not found log for '%c' stream", step
->type
));
312 DBG(TIMING
, ul_debug(" ignore step '%c' [delay=%f]",
313 step
->type
, step
->delay
));
314 ignored_delay
+= step
->delay
;
319 step
->delay
+= ignored_delay
;
321 DBG(TIMING
, ul_debug("reading next step done [rc=%d delay=%f (ignored=%f) size=%zu]",
322 rc
, step
->delay
, ignored_delay
, step
->size
));
326 /* return: 0 = success, <0 = error, 1 = done (EOF) */
327 static int replay_emit_step_data(struct replay_step
*step
, int fd
)
336 assert(step
->data
->fp
);
338 for (ct
= step
->size
; ct
> 0; ) {
341 cc
= ct
> sizeof(buf
) ? sizeof(buf
): ct
;
342 len
= fread(buf
, 1, cc
, step
->data
->fp
);
345 DBG(LOG
, ul_debug("log data emit: failed to read log %m"));
350 cc
= write(fd
, buf
, len
);
353 DBG(LOG
, ul_debug("log data emit: failed write data %m"));
358 if (ct
&& ferror(step
->data
->fp
))
360 if (ct
&& feof(step
->data
->fp
))
363 DBG(LOG
, ul_debug("log data emited [rc=%d size=%zu]", rc
, step
->size
));
367 static void __attribute__((__noreturn__
))
371 fputs(USAGE_HEADER
, out
);
373 _(" %s [options]\n"),
374 program_invocation_short_name
);
376 _(" %s [-t] timingfile [typescript] [divisor]\n"),
377 program_invocation_short_name
);
379 fputs(USAGE_SEPARATOR
, out
);
380 fputs(_("Play back terminal typescripts, using timing information.\n"), out
);
382 fputs(USAGE_OPTIONS
, out
);
383 fputs(_(" -t, --timing <file> script timing log file\n"), out
);
384 fputs(_(" -I, --log-in <file> script stdin log file\n"), out
);
385 fputs(_(" -O, --log-out <file> script stdout log file (default)\n"), out
);
386 fputs(_(" -B, --log-io <file> script stdin and stdout log file\n"), out
);
387 fputs(_(" -s, --typescript <file> deprecated alist to -O\n"), out
);
389 fputs(USAGE_SEPARATOR
, out
);
390 fputs(_(" -d, --divisor <num> speed up or slow down execution with time divisor\n"), out
);
391 fputs(_(" -m, --maxdelay <num> wait at most this many seconds between updates\n"), out
);
392 fputs(_(" -x, --stream <name> stream type (out, in or signal)\n"), out
);
393 printf(USAGE_HELP_OPTIONS(25));
395 printf(USAGE_MAN_TAIL("scriptreplay(1)"));
400 getnum(const char *s
)
402 const double d
= strtod_or_err(s
, _("failed to parse number"));
406 err(EXIT_FAILURE
, "%s: %s", _("failed to parse number"), s
);
412 delay_for(double delay
)
414 #ifdef HAVE_NANOSLEEP
415 struct timespec ts
, remainder
;
416 ts
.tv_sec
= (time_t) delay
;
417 ts
.tv_nsec
= (delay
- ts
.tv_sec
) * 1.0e9
;
419 DBG(TIMING
, ul_debug("going to sleep for %fs", delay
));
421 while (-1 == nanosleep(&ts
, &remainder
)) {
429 tv
.tv_sec
= (long) delay
;
430 tv
.tv_usec
= (delay
- tv
.tv_sec
) * 1.0e6
;
431 select(0, NULL
, NULL
, NULL
, &tv
);
435 static void appendchr(char *buf
, size_t bufsz
, int c
)
440 return; /* already in */
448 main(int argc
, char *argv
[])
450 struct replay_setup setup
= { .nlogs
= 0 };
451 struct replay_step
*step
;
452 char streams
[6] = {0}; /* IOSI - in, out, signal,info */
453 const char *log_out
= NULL
,
457 double divi
= 1, maxdelay
= 0;
458 int diviopt
= FALSE
, maxdelayopt
= FALSE
, idx
;
461 static const struct option longopts
[] = {
462 { "timing", required_argument
, 0, 't' },
463 { "log-in", required_argument
, 0, 'I'},
464 { "log-out", required_argument
, 0, 'O'},
465 { "log-io", required_argument
, 0, 'B'},
466 { "typescript", required_argument
, 0, 's' },
467 { "divisor", required_argument
, 0, 'd' },
468 { "maxdelay", required_argument
, 0, 'm' },
469 { "stream", required_argument
, 0, 'x' },
470 { "version", no_argument
, 0, 'V' },
471 { "help", no_argument
, 0, 'h' },
474 static const ul_excl_t excl
[] = { /* rows and cols in ASCII order */
478 int excl_st
[ARRAY_SIZE(excl
)] = UL_EXCL_STATUS_INIT
;
479 /* Because we use space as a separator, we can't afford to use any
480 * locale which tolerates a space in a number. In any case, script.c
481 * sets the LC_NUMERIC locale to C, anyway.
483 setlocale(LC_ALL
, "");
484 setlocale(LC_NUMERIC
, "C");
486 bindtextdomain(PACKAGE
, LOCALEDIR
);
488 close_stdout_atexit();
490 scriptreplay_init_debug();
492 while ((ch
= getopt_long(argc
, argv
, "B:I:O:t:s:d:m:x:Vh", longopts
, NULL
)) != -1) {
494 err_exclusive_options(ch
, longopts
, excl
, excl_st
);
512 divi
= getnum(optarg
);
516 maxdelay
= getnum(optarg
);
519 if (strcmp("in", optarg
) == 0)
520 appendchr(streams
, sizeof(streams
), 'I');
521 else if (strcmp("out", optarg
) == 0)
522 appendchr(streams
, sizeof(streams
), 'O');
523 else if (strcmp("signal", optarg
) == 0)
524 appendchr(streams
, sizeof(streams
), 'S');
526 errx(EXIT_FAILURE
, _("unsupported stream name: '%s'"), optarg
);
529 print_version(EXIT_SUCCESS
);
533 errtryhelp(EXIT_FAILURE
);
540 if ((argc
< 1 && !(log_out
|| log_in
|| log_io
)) || argc
> 3) {
541 warnx(_("wrong number of arguments"));
542 errtryhelp(EXIT_FAILURE
);
545 log_tm
= argv
[idx
++];
546 if (!log_out
&& !log_in
&& !log_io
)
547 log_out
= idx
< argc
? argv
[idx
++] : "typescript";
550 divi
= idx
< argc
? getnum(argv
[idx
]) : 1;
554 if (replay_set_timing_file(&setup
, log_tm
) != 0)
555 err(EXIT_FAILURE
, _("cannot open %s"), log_tm
);
557 if (log_out
&& replay_associate_log(&setup
, "O", log_out
) != 0)
558 err(EXIT_FAILURE
, _("cannot open %s"), log_out
);
560 if (log_in
&& replay_associate_log(&setup
, "I", log_in
) != 0)
561 err(EXIT_FAILURE
, _("cannot open %s"), log_in
);
563 if (log_io
&& replay_associate_log(&setup
, "IO", log_io
) != 0)
564 err(EXIT_FAILURE
, _("cannot open %s"), log_io
);
567 /* output is prefered default */
568 if (log_out
|| log_io
)
569 appendchr(streams
, sizeof(streams
), 'O');
571 appendchr(streams
, sizeof(streams
), 'I');
574 replay_set_default_type(&setup
,
575 *streams
&& streams
[1] == '\0' ? *streams
: 'O');
578 rc
= replay_get_next_step(&setup
, streams
, &step
);
583 if (maxdelayopt
&& step
->delay
> maxdelay
)
584 step
->delay
= maxdelay
;
585 if (step
->delay
> SCRIPT_MIN_DELAY
)
586 delay_for(step
->delay
);
588 rc
= replay_emit_step_data(step
, STDOUT_FILENO
);
592 err(EXIT_FAILURE
, _("%s: log file error"), step
->data
->filename
);
594 err(EXIT_FAILURE
, _("%s: line %d: timing file error"),
595 setup
.timing_filename
,