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"
39 static UL_DEBUG_DEFINE_MASK(scriptreplay
);
40 UL_DEBUG_DEFINE_MASKNAMES(scriptreplay
) = UL_DEBUG_EMPTY_MASKNAMES
;
42 #define SCRIPTREPLAY_DEBUG_INIT (1 << 1)
43 #define SCRIPTREPLAY_DEBUG_TIMING (1 << 2)
44 #define SCRIPTREPLAY_DEBUG_LOG (1 << 3)
45 #define SCRIPTREPLAY_DEBUG_MISC (1 << 4)
46 #define SCRIPTREPLAY_DEBUG_ALL 0xFFFF
48 #define DBG(m, x) __UL_DBG(scriptreplay, SCRIPTREPLAY_DEBUG_, m, x)
49 #define ON_DBG(m, x) __UL_DBG_CALL(scriptreplay, SCRIPTREPLAY_DEBUG_, m, x)
51 #define SCRIPT_MIN_DELAY 0.0001 /* from original sripreplay.pl */
54 * The script replay is driven by timing file where each entry describes one
55 * step in the replay. The timing step may refer input or output (or
56 * signal, extra informations, etc.)
58 * The step data are stored in log files, the right log file for the step is
59 * selected from replay_setup.
61 * TODO: move struct replay_{log,step,setup} to script-playutils.c to make it
62 * usable for scriptlive(1) code.
66 REPLAY_TIMING_SIMPLE
, /* timing info in classic "<delta> <offset>" format */
67 REPLAY_TIMING_MULTI
/* multiple streams in format "<type> <delta> <offset|etc> */
71 const char *streams
; /* 'I'nput, 'O'utput or both */
77 char type
; /* 'I'nput, 'O'utput, ... */
81 struct replay_log
*data
;
85 struct replay_log
*logs
;
88 struct replay_step step
; /* current step */
91 const char *timing_filename
;
96 static void scriptreplay_init_debug(void)
98 __UL_INIT_DEBUG_FROM_ENV(scriptreplay
, SCRIPTREPLAY_DEBUG_
, 0, SCRIPTREPLAY_DEBUG
);
101 static int ignore_line(FILE *f
)
105 while((c
= fgetc(f
)) != EOF
&& c
!= '\n');
109 DBG(LOG
, ul_debug(" ignore line"));
113 static int replay_set_timing_file(struct replay_setup
*stp
, const char *filename
)
120 stp
->timing_filename
= filename
;
121 stp
->timing_line
= 0;
123 stp
->timing_fp
= fopen(filename
, "r");
127 /* detect timing file format */
128 c
= fgetc(stp
->timing_fp
);
130 if (isdigit((unsigned int) c
))
131 stp
->timing_format
= REPLAY_TIMING_SIMPLE
;
133 stp
->timing_format
= REPLAY_TIMING_MULTI
;
134 ungetc(c
, stp
->timing_fp
);
135 } else if (ferror(stp
->timing_fp
))
140 fclose(stp
->timing_fp
);
141 stp
->timing_fp
= NULL
;
144 DBG(TIMING
, ul_debug("timing file set to %s [rc=%d]", filename
, rc
));
148 static int replay_associate_log(struct replay_setup
*stp
,
149 const char *streams
, const char *filename
)
151 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
;
165 /* open the file and skip the first line */
166 log
->fp
= fopen(filename
, "r");
167 rc
= log
->fp
== NULL
? -errno
: ignore_line(log
->fp
);
169 DBG(LOG
, ul_debug("accociate log file %s with '%s' [rc=%d]", filename
, streams
, rc
));
173 static int is_wanted_stream(char type
, const char *streams
)
177 if (strchr(streams
, type
))
182 static int read_multistream_step(struct replay_step
*step
, FILE *f
, char type
)
188 case 'O': /* output */
189 case 'I': /* input */
190 rc
= fscanf(f
, "%lf %zu%c\n", &step
->delay
, &step
->size
, &nl
);
191 if (rc
!= 3 || nl
!= '\n')
197 case 'S': /* signal */
198 rc
= ignore_line(f
); /* not implemnted yet */
201 case 'H': /* header */
202 rc
= ignore_line(f
); /* not implemnted yet */
208 DBG(TIMING
, ul_debug(" read step delay & size [rc=%d]", rc
));
212 static struct replay_log
*replay_get_stream_log(struct replay_setup
*stp
, char stream
)
216 for (i
= 0; i
< stp
->nlogs
; i
++) {
217 struct replay_log
*log
= &stp
->logs
[i
];
219 if (is_wanted_stream(stream
, log
->streams
))
225 static int replay_seek_log(struct replay_log
*log
, size_t move
)
227 DBG(LOG
, ul_debug(" %s: seek ++ %zu", log
->filename
, move
));
228 return fseek(log
->fp
, move
, SEEK_CUR
) == (off_t
) -1 ? -errno
: 0;
231 /* returns next step with pointer to the right log file for specified streams (e.g.
232 * "IOS" for in/out/signals) or all streams if stream is NULL.
234 * returns: 0 = success, <0 = error, 1 = done (EOF)
236 static int replay_get_next_step(struct replay_setup
*stp
, char *streams
, struct replay_step
**xstep
)
238 struct replay_step
*step
;
240 double ignored_delay
= 0;
243 assert(stp
->timing_fp
);
244 assert(xstep
&& *xstep
);
246 /* old format supports only 'O'utput */
247 if (stp
->timing_format
== REPLAY_TIMING_SIMPLE
&&
248 !is_wanted_stream('O', streams
))
256 struct replay_log
*log
= NULL
;
259 if (feof(stp
->timing_fp
))
262 DBG(TIMING
, ul_debug("reading next step"));
264 memset(step
, 0, sizeof(*step
));
267 switch (stp
->timing_format
) {
268 case REPLAY_TIMING_SIMPLE
:
269 /* old format supports only output entries and format is the same
270 * as new format, but without <type> prefix */
271 rc
= read_multistream_step(step
, stp
->timing_fp
, 'O');
273 step
->type
= 'O'; /* 'O'utput */
275 case REPLAY_TIMING_MULTI
:
276 rc
= fscanf(stp
->timing_fp
, "%c ", &step
->type
);
280 rc
= read_multistream_step(step
,
289 DBG(TIMING
, ul_debug(" step entry is '%c'", step
->type
));
291 log
= replay_get_stream_log(stp
, step
->type
);
293 if (is_wanted_stream(step
->type
, streams
)) {
296 DBG(LOG
, ul_debug(" use %s as data source", log
->filename
));
299 /* The step entry is unwanted, but we keep the right
300 * position in the log file although the data are ignored.
302 replay_seek_log(log
, step
->size
);
304 DBG(TIMING
, ul_debug(" not found log for '%c' stream", step
->type
));
306 DBG(TIMING
, ul_debug(" ignore step '%c' [delay=%f]",
307 step
->type
, step
->delay
));
308 ignored_delay
+= step
->delay
;
313 step
->delay
+= ignored_delay
;
315 DBG(TIMING
, ul_debug("reading next step done [rc=%d delay=%f (ignored=%f) size=%zu]",
316 rc
, step
->delay
, ignored_delay
, step
->size
));
320 /* return: 0 = success, <0 = error, 1 = done (EOF) */
321 static int replay_emit_step_data(struct replay_step
*step
, int fd
)
330 assert(step
->data
->fp
);
332 for (ct
= step
->size
; ct
> 0; ) {
335 cc
= ct
> sizeof(buf
) ? sizeof(buf
): ct
;
336 len
= fread(buf
, 1, cc
, step
->data
->fp
);
339 DBG(LOG
, ul_debug("log data emit: failed to read log %m"));
344 cc
= write(fd
, buf
, len
);
347 DBG(LOG
, ul_debug("log data emit: failed write data %m"));
352 if (ct
&& ferror(step
->data
->fp
))
354 if (ct
&& feof(step
->data
->fp
))
357 DBG(LOG
, ul_debug("log data emited [rc=%d size=%zu]", rc
, step
->size
));
361 static void __attribute__((__noreturn__
))
365 fputs(USAGE_HEADER
, out
);
367 _(" %s [-t] timingfile [typescript] [divisor]\n"),
368 program_invocation_short_name
);
370 fputs(USAGE_SEPARATOR
, out
);
371 fputs(_("Play back terminal typescripts, using timing information.\n"), out
);
373 fputs(USAGE_OPTIONS
, out
);
374 fputs(_(" -t, --timing <file> script timing log file\n"), out
);
375 fputs(_(" -s, --typescript <file> script data log file\n"), out
);
376 fputs(_(" -d, --divisor <num> speed up or slow down execution with time divisor\n"), out
);
377 fputs(_(" -m, --maxdelay <num> wait at most this many seconds between updates\n"), out
);
378 printf(USAGE_HELP_OPTIONS(25));
380 printf(USAGE_MAN_TAIL("scriptreplay(1)"));
385 getnum(const char *s
)
387 const double d
= strtod_or_err(s
, _("failed to parse number"));
391 err(EXIT_FAILURE
, "%s: %s", _("failed to parse number"), s
);
397 delay_for(double delay
)
399 #ifdef HAVE_NANOSLEEP
400 struct timespec ts
, remainder
;
401 ts
.tv_sec
= (time_t) delay
;
402 ts
.tv_nsec
= (delay
- ts
.tv_sec
) * 1.0e9
;
404 DBG(TIMING
, ul_debug("going to sleep for %fs", delay
));
406 while (-1 == nanosleep(&ts
, &remainder
)) {
414 tv
.tv_sec
= (long) delay
;
415 tv
.tv_usec
= (delay
- tv
.tv_sec
) * 1.0e6
;
416 select(0, NULL
, NULL
, NULL
, &tv
);
421 main(int argc
, char *argv
[])
423 struct replay_setup setup
= { .nlogs
= 0 };
424 struct replay_step
*step
;
427 const char *sname
= NULL
, *tname
= NULL
;
428 double divi
= 1, maxdelay
= 0;
429 int diviopt
= FALSE
, maxdelayopt
= FALSE
, idx
;
432 static const struct option longopts
[] = {
433 { "timing", required_argument
, 0, 't' },
434 { "typescript", required_argument
, 0, 's' },
435 { "divisor", required_argument
, 0, 'd' },
436 { "maxdelay", required_argument
, 0, 'm' },
437 { "version", no_argument
, 0, 'V' },
438 { "help", no_argument
, 0, 'h' },
442 /* Because we use space as a separator, we can't afford to use any
443 * locale which tolerates a space in a number. In any case, script.c
444 * sets the LC_NUMERIC locale to C, anyway.
446 setlocale(LC_ALL
, "");
447 setlocale(LC_NUMERIC
, "C");
449 bindtextdomain(PACKAGE
, LOCALEDIR
);
451 close_stdout_atexit();
453 scriptreplay_init_debug();
455 while ((ch
= getopt_long(argc
, argv
, "t:s:d:m:Vh", longopts
, NULL
)) != -1)
465 divi
= getnum(optarg
);
469 maxdelay
= getnum(optarg
);
473 print_version(EXIT_SUCCESS
);
477 errtryhelp(EXIT_FAILURE
);
483 if ((argc
< 1 && !tname
) || argc
> 3) {
484 warnx(_("wrong number of arguments"));
485 errtryhelp(EXIT_FAILURE
);
490 sname
= idx
< argc
? argv
[idx
++] : "typescript";
492 divi
= idx
< argc
? getnum(argv
[idx
]) : 1;
496 if (replay_set_timing_file(&setup
, tname
) != 0)
497 err(EXIT_FAILURE
, _("cannot open %s"), tname
);
499 if (replay_associate_log(&setup
, "O", sname
) != 0)
500 err(EXIT_FAILURE
, _("cannot open %s"), sname
);
503 rc
= replay_get_next_step(&setup
, "O", &step
);
508 if (maxdelayopt
&& step
->delay
> maxdelay
)
509 step
->delay
= maxdelay
;
510 if (step
->delay
> SCRIPT_MIN_DELAY
)
511 delay_for(step
->delay
);
513 rc
= replay_emit_step_data(step
, STDOUT_FILENO
);
517 err(EXIT_FAILURE
, _("%s: log file error"), step
->data
->filename
);
519 err(EXIT_FAILURE
, _("%s: line %d: timing file error"),
520 setup
.timing_filename
,