]> git.ipfire.org Git - thirdparty/util-linux.git/blame - term-utils/scriptreplay.c
scriptreplay: cleanup usage()
[thirdparty/util-linux.git] / term-utils / scriptreplay.c
CommitLineData
18a706bd
KZ
1/*
2 * Copyright (C) 2008, Karel Zak <kzak@redhat.com>
3 * Copyright (C) 2008, James Youngman <jay@gnu.org>
4 *
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.
9 *
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.
14 *
15 *
16 * Based on scriptreplay.pl by Joey Hess <joey@kitenet.net>
17 */
18
19#include <stdio.h>
20#include <stdarg.h>
21#include <stdlib.h>
22#include <string.h>
23#include <errno.h>
24#include <time.h>
25#include <limits.h>
26#include <math.h>
27#include <sys/select.h>
28#include <unistd.h>
84adb5e6 29#include <getopt.h>
18a706bd 30
fec06a06
KZ
31
32#include "c.h"
33#include "debug.h"
34#include "xalloc.h"
cdd2a8c3 35#include "closestream.h"
18a706bd 36#include "nls.h"
f0b3b904 37#include "strutils.h"
fec06a06
KZ
38
39static UL_DEBUG_DEFINE_MASK(scriptreplay);
40UL_DEBUG_DEFINE_MASKNAMES(scriptreplay) = UL_DEBUG_EMPTY_MASKNAMES;
41
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
47
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)
18a706bd
KZ
50
51#define SCRIPT_MIN_DELAY 0.0001 /* from original sripreplay.pl */
52
fec06a06
KZ
53/*
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.)
57 *
58 * The step data are stored in log files, the right log file for the step is
59 * selected from replay_setup.
60 *
61 * TODO: move struct replay_{log,step,setup} to script-playutils.c to make it
62 * usable for scriptlive(1) code.
63 */
64
65enum {
66 REPLAY_TIMING_SIMPLE, /* timing info in classic "<delta> <offset>" format */
67 REPLAY_TIMING_MULTI /* multiple streams in format "<type> <delta> <offset|etc> */
68};
69
70struct replay_log {
71 const char *streams; /* 'I'nput, 'O'utput or both */
72 const char *filename;
73 FILE *fp;
74};
75
76struct replay_step {
77 char type; /* 'I'nput, 'O'utput, ... */
78 double delay;
79 size_t size;
80
81 struct replay_log *data;
82};
83
84struct replay_setup {
85 struct replay_log *logs;
86 size_t nlogs;
87
88 struct replay_step step; /* current step */
89
90 FILE *timing_fp;
91 const char *timing_filename;
92 int timing_format;
93 int timing_line;
94};
95
96static void scriptreplay_init_debug(void)
97{
98 __UL_INIT_DEBUG_FROM_ENV(scriptreplay, SCRIPTREPLAY_DEBUG_, 0, SCRIPTREPLAY_DEBUG);
99}
100
101static int ignore_line(FILE *f)
102{
103 int c;
104
105 while((c = fgetc(f)) != EOF && c != '\n');
106 if (ferror(f))
107 return -errno;
108
109 DBG(LOG, ul_debug(" ignore line"));
110 return 0;
111}
112
113static int replay_set_timing_file(struct replay_setup *stp, const char *filename)
114{
115 int c, rc = 0;
116
117 assert(stp);
118 assert(filename);
119
120 stp->timing_filename = filename;
121 stp->timing_line = 0;
122
123 stp->timing_fp = fopen(filename, "r");
124 if (!stp->timing_fp)
125 rc = -errno;
126 else {
127 /* detect timing file format */
128 c = fgetc(stp->timing_fp);
129 if (c != EOF) {
130 if (isdigit((unsigned int) c))
131 stp->timing_format = REPLAY_TIMING_SIMPLE;
132 else
133 stp->timing_format = REPLAY_TIMING_MULTI;
134 ungetc(c, stp->timing_fp);
135 } else if (ferror(stp->timing_fp))
136 rc = -errno;
137 }
138
139 if (rc) {
140 fclose(stp->timing_fp);
141 stp->timing_fp = NULL;
142 }
143
144 DBG(TIMING, ul_debug("timing file set to %s [rc=%d]", filename, rc));
145 return rc;
146}
147
148static int replay_associate_log(struct replay_setup *stp,
149 const char *streams, const char *filename)
150{
151 struct replay_log *log;
152 int rc;
153
154 assert(stp);
155 assert(streams);
156 assert(filename);
157
158 stp->logs = xrealloc(stp->logs, (stp->nlogs + 1) * sizeof(*log));
159 log = &stp->logs[stp->nlogs];
160 stp->nlogs++;
161
162 log->filename = filename;
163 log->streams = streams;
164
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);
168
169 DBG(LOG, ul_debug("accociate log file %s with '%s' [rc=%d]", filename, streams, rc));
170 return rc;
171}
172
173static int is_wanted_stream(char type, const char *streams)
174{
175 if (streams == NULL)
176 return 1;
177 if (strchr(streams, type))
178 return 1;
179 return 0;
180}
181
182static int read_multistream_step(struct replay_step *step, FILE *f, char type)
183{
184 int rc = 0;
185 char nl;
186
187 switch (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')
192 rc = -EINVAL;
193 else
194 rc = 0;
195 break;
196
197 case 'S': /* signal */
198 rc = ignore_line(f); /* not implemnted yet */
199 break;
200
201 case 'H': /* header */
202 rc = ignore_line(f); /* not implemnted yet */
203 break;
204 default:
205 break;
206 }
207
167a4851 208 DBG(TIMING, ul_debug(" read step delay & size [rc=%d]", rc));
fec06a06
KZ
209 return rc;
210}
211
167a4851
KZ
212static struct replay_log *replay_get_stream_log(struct replay_setup *stp, char stream)
213{
214 size_t i;
215
216 for (i = 0; i < stp->nlogs; i++) {
217 struct replay_log *log = &stp->logs[i];
218
219 if (is_wanted_stream(stream, log->streams))
220 return log;
221 }
222 return NULL;
223}
224
225static int replay_seek_log(struct replay_log *log, size_t move)
226{
227 DBG(LOG, ul_debug(" %s: seek ++ %zu", log->filename, move));
228 return fseek(log->fp, move, SEEK_CUR) == (off_t) -1 ? -errno : 0;
229}
230
fec06a06
KZ
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.
233 *
234 * returns: 0 = success, <0 = error, 1 = done (EOF)
235 */
236static int replay_get_next_step(struct replay_setup *stp, char *streams, struct replay_step **xstep)
237{
238 struct replay_step *step;
239 int rc;
167a4851 240 double ignored_delay = 0;
fec06a06
KZ
241
242 assert(stp);
243 assert(stp->timing_fp);
244 assert(xstep && *xstep);
245
246 /* old format supports only 'O'utput */
247 if (stp->timing_format == REPLAY_TIMING_SIMPLE &&
248 !is_wanted_stream('O', streams))
249 return 1;
250
fec06a06
KZ
251
252 step = &stp->step;
fec06a06
KZ
253 *xstep = NULL;
254
255 do {
167a4851 256 struct replay_log *log = NULL;
fec06a06
KZ
257
258 rc = 1; /* done */
259 if (feof(stp->timing_fp))
260 break;
167a4851
KZ
261
262 DBG(TIMING, ul_debug("reading next step"));
263
264 memset(step, 0, sizeof(*step));
fec06a06
KZ
265 stp->timing_line++;
266
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');
272 if (rc == 0)
273 step->type = 'O'; /* 'O'utput */
274 break;
275 case REPLAY_TIMING_MULTI:
276 rc = fscanf(stp->timing_fp, "%c ", &step->type);
277 if (rc != 1)
278 rc = -EINVAL;
167a4851 279 else
fec06a06
KZ
280 rc = read_multistream_step(step,
281 stp->timing_fp,
282 step->type);
fec06a06
KZ
283 break;
284 }
285
286 if (rc)
287 break;; /* error */
fec06a06 288
167a4851 289 DBG(TIMING, ul_debug(" step entry is '%c'", step->type));
fec06a06 290
167a4851
KZ
291 log = replay_get_stream_log(stp, step->type);
292 if (log) {
293 if (is_wanted_stream(step->type, streams)) {
fec06a06
KZ
294 step->data = log;
295 *xstep = step;
167a4851
KZ
296 DBG(LOG, ul_debug(" use %s as data source", log->filename));
297 goto done;
fec06a06 298 }
167a4851
KZ
299 /* The step entry is unwanted, but we keep the right
300 * position in the log file although the data are ignored.
301 */
302 replay_seek_log(log, step->size);
303 } else
304 DBG(TIMING, ul_debug(" not found log for '%c' stream", step->type));
305
306 DBG(TIMING, ul_debug(" ignore step '%c' [delay=%f]",
307 step->type, step->delay));
308 ignored_delay += step->delay;
309 } while (rc == 0);
310
311done:
312 if (ignored_delay)
313 step->delay += ignored_delay;
314
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));
fec06a06
KZ
317 return rc;
318}
319
320/* return: 0 = success, <0 = error, 1 = done (EOF) */
321static int replay_emit_step_data(struct replay_step *step, int fd)
322{
323 size_t ct;
324 int rc = 0;
325 char buf[BUFSIZ];
326
327 assert(step);
328 assert(step->size);
329 assert(step->data);
330 assert(step->data->fp);
331
332 for (ct = step->size; ct > 0; ) {
333 size_t len, cc;
334
335 cc = ct > sizeof(buf) ? sizeof(buf): ct;
336 len = fread(buf, 1, cc, step->data->fp);
337
167a4851
KZ
338 if (!len) {
339 DBG(LOG, ul_debug("log data emit: failed to read log %m"));
fec06a06 340 break;
167a4851
KZ
341 }
342
fec06a06
KZ
343 ct -= len;
344 cc = write(fd, buf, len);
345 if (cc != len) {
346 rc = -errno;
167a4851 347 DBG(LOG, ul_debug("log data emit: failed write data %m"));
fec06a06
KZ
348 break;
349 }
350 }
351
352 if (ct && ferror(step->data->fp))
353 rc = -errno;
354 if (ct && feof(step->data->fp))
355 rc = 1;
356
167a4851 357 DBG(LOG, ul_debug("log data emited [rc=%d size=%zu]", rc, step->size));
fec06a06
KZ
358 return rc;
359}
360
5769a938 361static void __attribute__((__noreturn__))
86be6a32 362usage(void)
18a706bd 363{
86be6a32 364 FILE *out = stdout;
db433bf7 365 fputs(USAGE_HEADER, out);
14cc9dda
KZ
366 fprintf(out,
367 _(" %s [-t] timingfile [typescript] [divisor]\n"),
368 program_invocation_short_name);
369
451dbcfa
BS
370 fputs(USAGE_SEPARATOR, out);
371 fputs(_("Play back terminal typescripts, using timing information.\n"), out);
372
db433bf7 373 fputs(USAGE_OPTIONS, out);
02a51ce1
KZ
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);
f45f3ec3 378 printf(USAGE_HELP_OPTIONS(25));
84adb5e6 379
f45f3ec3 380 printf(USAGE_MAN_TAIL("scriptreplay(1)"));
86be6a32 381 exit(EXIT_SUCCESS);
18a706bd
KZ
382}
383
384static double
385getnum(const char *s)
386{
f0b3b904 387 const double d = strtod_or_err(s, _("failed to parse number"));
18a706bd 388
f0b3b904 389 if (isnan(d)) {
18a706bd 390 errno = EINVAL;
f0b3b904 391 err(EXIT_FAILURE, "%s: %s", _("failed to parse number"), s);
18a706bd
KZ
392 }
393 return d;
394}
395
396static void
397delay_for(double delay)
398{
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;
403
167a4851
KZ
404 DBG(TIMING, ul_debug("going to sleep for %fs", delay));
405
18a706bd
KZ
406 while (-1 == nanosleep(&ts, &remainder)) {
407 if (EINTR == errno)
408 ts = remainder;
409 else
410 break;
411 }
412#else
413 struct timeval tv;
414 tv.tv_sec = (long) delay;
415 tv.tv_usec = (delay - tv.tv_sec) * 1.0e6;
416 select(0, NULL, NULL, NULL, &tv);
417#endif
418}
419
18a706bd
KZ
420int
421main(int argc, char *argv[])
422{
fec06a06
KZ
423 struct replay_setup setup = { .nlogs = 0 };
424 struct replay_step *step;
425 int rc;
426
0da871b5 427 const char *sname = NULL, *tname = NULL;
7f1d4836 428 double divi = 1, maxdelay = 0;
fec06a06 429 int diviopt = FALSE, maxdelayopt = FALSE, idx;
94757ece 430 int ch;
84adb5e6
SK
431
432 static const struct option longopts[] = {
0da871b5
SK
433 { "timing", required_argument, 0, 't' },
434 { "typescript", required_argument, 0, 's' },
435 { "divisor", required_argument, 0, 'd' },
7f1d4836 436 { "maxdelay", required_argument, 0, 'm' },
84adb5e6
SK
437 { "version", no_argument, 0, 'V' },
438 { "help", no_argument, 0, 'h' },
439 { NULL, 0, 0, 0 }
440 };
18a706bd
KZ
441
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.
445 */
446 setlocale(LC_ALL, "");
447 setlocale(LC_NUMERIC, "C");
448
449 bindtextdomain(PACKAGE, LOCALEDIR);
450 textdomain(PACKAGE);
2c308875 451 close_stdout_atexit();
18a706bd 452
fec06a06
KZ
453 scriptreplay_init_debug();
454
7f1d4836 455 while ((ch = getopt_long(argc, argv, "t:s:d:m:Vh", longopts, NULL)) != -1)
84adb5e6 456 switch(ch) {
0da871b5
SK
457 case 't':
458 tname = optarg;
459 break;
460 case 's':
461 sname = optarg;
462 break;
463 case 'd':
464 diviopt = TRUE;
465 divi = getnum(optarg);
466 break;
7f1d4836
JDN
467 case 'm':
468 maxdelayopt = TRUE;
469 maxdelay = getnum(optarg);
470 break;
2c308875 471
84adb5e6 472 case 'V':
2c308875 473 print_version(EXIT_SUCCESS);
84adb5e6 474 case 'h':
86be6a32 475 usage();
84adb5e6 476 default:
677ec86c 477 errtryhelp(EXIT_FAILURE);
84adb5e6
SK
478 }
479 argc -= optind;
480 argv += optind;
0da871b5 481 idx = 0;
84adb5e6 482
0da871b5 483 if ((argc < 1 && !tname) || argc > 3) {
84adb5e6 484 warnx(_("wrong number of arguments"));
677ec86c 485 errtryhelp(EXIT_FAILURE);
84adb5e6 486 }
0da871b5
SK
487 if (!tname)
488 tname = argv[idx++];
489 if (!sname)
490 sname = idx < argc ? argv[idx++] : "typescript";
491 if (!diviopt)
492 divi = idx < argc ? getnum(argv[idx]) : 1;
7f1d4836
JDN
493 if (maxdelay < 0)
494 maxdelay = 0;
18a706bd 495
fec06a06
KZ
496 if (replay_set_timing_file(&setup, tname) != 0)
497 err(EXIT_FAILURE, _("cannot open %s"), tname);
7f1d4836 498
fec06a06
KZ
499 if (replay_associate_log(&setup, "O", sname) != 0)
500 err(EXIT_FAILURE, _("cannot open %s"), sname);
18a706bd 501
fec06a06
KZ
502 do {
503 rc = replay_get_next_step(&setup, "O", &step);
504 if (rc)
505 break;
18a706bd 506
fec06a06
KZ
507 step->delay /= divi;
508 if (maxdelayopt && step->delay > maxdelay)
509 step->delay = maxdelay;
510 if (step->delay > SCRIPT_MIN_DELAY)
511 delay_for(step->delay);
512
513 rc = replay_emit_step_data(step, STDOUT_FILENO);
514 } while (rc == 0);
515
516 if (step && rc < 0)
517 err(EXIT_FAILURE, _("%s: log file error"), step->data->filename);
518 else if (rc < 0)
519 err(EXIT_FAILURE, _("%s: line %d: timing file error"),
520 setup.timing_filename,
521 setup.timing_line);
f004a02d 522 printf("\n");
18a706bd
KZ
523 exit(EXIT_SUCCESS);
524}