1 #include <stdio.h>
2 #include <stdarg.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <errno.h>
6 #include <time.h>
7 #include <limits.h>
8 #include <math.h>
9 #include <unistd.h>
10 #include <sys/time.h>
11
12 #include "c.h"
13 #include "xalloc.h"
14 #include "closestream.h"
15 #include "nls.h"
16 #include "strutils.h"
17 #include "script-playutils.h"
18
19 UL_DEBUG_DEFINE_MASK(scriptreplay);
20 UL_DEBUG_DEFINE_MASKNAMES(scriptreplay) = UL_DEBUG_EMPTY_MASKNAMES;
21
22 #define DBG(m, x) __UL_DBG(scriptreplay, SCRIPTREPLAY_DEBUG_, m, x)
23 #define ON_DBG(m, x) __UL_DBG_CALL(scriptreplay, SCRIPTREPLAY_DEBUG_, m, x)
24
25 /*
26 * The script replay is driven by timing file where each entry describes one
27 * step in the replay. The timing step may refer input or output (or
28 * signal, extra information, etc.)
29 *
30 * The step data are stored in log files, the right log file for the step is
31 * selected from replay_setup.
32 */
33 enum {
34 REPLAY_TIMING_SIMPLE, /* timing info in classic "<delta> <offset>" format */
35 REPLAY_TIMING_MULTI /* multiple streams in format "<type> <delta> <offset|etc> */
36 };
37
38 struct replay_log {
39 const char *streams; /* 'I'nput, 'O'utput or both */
40 const char *filename;
41 FILE *fp;
42
43 unsigned int noseek : 1; /* do not seek in this log */
44 };
45
46 struct replay_step {
47 char type; /* 'I'nput, 'O'utput, ... */
48 size_t size;
49
50 char *name; /* signals / headers */
51 char *value;
52
53 struct timeval delay;
54 struct replay_log *data;
55 };
56
57 struct replay_setup {
58 struct replay_log *logs;
59 size_t nlogs;
60
61 struct replay_step step; /* current step */
62
63 FILE *timing_fp;
64 const char *timing_filename;
65 int timing_format;
66 int timing_line;
67
68 struct timeval delay_max;
69 struct timeval delay_min;
70 double delay_div;
71
72 char default_type; /* type for REPLAY_TIMING_SIMPLE */
73 int crmode;
74 };
75
76 void replay_init_debug(void)
77 {
78 __UL_INIT_DEBUG_FROM_ENV(scriptreplay, SCRIPTREPLAY_DEBUG_, 0, SCRIPTREPLAY_DEBUG);
79 }
80
81 static int ignore_line(FILE *f)
82 {
83 int c;
84
85 while((c = fgetc(f)) != EOF && c != '\n');
86 if (ferror(f))
87 return -errno;
88
89 DBG(LOG, ul_debug(" ignore line"));
90 return 0;
91 }
92
93 /* incretemt @a by @b */
94 static inline void timerinc(struct timeval *a, struct timeval *b)
95 {
96 struct timeval res;
97
98 timeradd(a, b, &res);
99 a->tv_sec = res.tv_sec;
100 a->tv_usec = res.tv_usec;
101 }
102
103 struct replay_setup *replay_new_setup(void)
104 {
105 return xcalloc(1, sizeof(struct replay_setup));
106 }
107
108 void replay_free_setup(struct replay_setup *stp)
109 {
110 if (!stp)
111 return;
112
113 free(stp->logs);
114 free(stp->step.name);
115 free(stp->step.value);
116 free(stp);
117 }
118
119 /* if timing file does not contains types of entries (old format) than use this
120 * type as the default */
121 int replay_set_default_type(struct replay_setup *stp, char type)
122 {
123 assert(stp);
124 stp->default_type = type;
125
126 return 0;
127 }
128
129 int replay_set_crmode(struct replay_setup *stp, int mode)
130 {
131 assert(stp);
132 stp->crmode = mode;
133
134 return 0;
135 }
136
137 int replay_set_delay_min(struct replay_setup *stp, const struct timeval *tv)
138 {
139 stp->delay_min.tv_sec = tv->tv_sec;
140 stp->delay_min.tv_usec = tv->tv_usec;
141 return 0;
142 }
143
144 int replay_set_delay_max(struct replay_setup *stp, const struct timeval *tv)
145 {
146 stp->delay_max.tv_sec = tv->tv_sec;
147 stp->delay_max.tv_usec = tv->tv_usec;
148 return 0;
149 }
150
151 int replay_set_delay_div(struct replay_setup *stp, const double divi)
152 {
153 stp->delay_div = divi;
154 return 0;
155 }
156
157 static struct replay_log *replay_new_log(struct replay_setup *stp,
158 const char *streams,
159 const char *filename,
160 FILE *f)
161 {
162 struct replay_log *log;
163
164 assert(stp);
165 assert(streams);
166 assert(filename);
167
168 stp->logs = xrealloc(stp->logs, (stp->nlogs + 1) * sizeof(*log));
169 log = &stp->logs[stp->nlogs];
170 stp->nlogs++;
171
172 memset(log, 0, sizeof(*log));
173 log->filename = filename;
174 log->streams = streams;
175 log->fp = f;
176
177 return log;
178 }
179
180 int replay_set_timing_file(struct replay_setup *stp, const char *filename)
181 {
182 int c, rc = 0;
183
184 assert(stp);
185 assert(filename);
186
187 stp->timing_filename = filename;
188 stp->timing_line = 0;
189
190 stp->timing_fp = fopen(filename, "r");
191 if (!stp->timing_fp)
192 rc = -errno;
193 else {
194 /* detect timing file format */
195 c = fgetc(stp->timing_fp);
196 if (c != EOF) {
197 if (isdigit((unsigned int) c))
198 stp->timing_format = REPLAY_TIMING_SIMPLE;
199 else
200 stp->timing_format = REPLAY_TIMING_MULTI;
201 ungetc(c, stp->timing_fp);
202 } else if (ferror(stp->timing_fp))
203 rc = -errno;
204 }
205
206 if (rc && stp->timing_fp) {
207 fclose(stp->timing_fp);
208 stp->timing_fp = NULL;
209 }
210
211 /* create quasi-log for signals, headers, etc. */
212 if (rc == 0 && stp->timing_format == REPLAY_TIMING_MULTI) {
213 struct replay_log *log = replay_new_log(stp, "SH",
214 filename, stp->timing_fp);
215 if (!log)
216 rc = -ENOMEM;
217 else {
218 log->noseek = 1;
219 DBG(LOG, ul_debug("associate file '%s' for streams 'SH'", filename));
220 }
221 }
222
223 DBG(TIMING, ul_debug("timing file set to '%s' [rc=%d]", filename, rc));
224 return rc;
225 }
226
227 const char *replay_get_timing_file(struct replay_setup *setup)
228 {
229 assert(setup);
230 return setup->timing_filename;
231 }
232
233 int replay_get_timing_line(struct replay_setup *setup)
234 {
235 assert(setup);
236 return setup->timing_line;
237 }
238
239 int replay_associate_log(struct replay_setup *stp,
240 const char *streams, const char *filename)
241 {
242 FILE *f;
243 int rc;
244
245 assert(stp);
246 assert(streams);
247 assert(filename);
248
249 /* open the file and skip the first line */
250 f = fopen(filename, "r");
251 rc = f == NULL ? -errno : ignore_line(f);
252
253 if (rc == 0)
254 replay_new_log(stp, streams, filename, f);
255
256 DBG(LOG, ul_debug("associate log file '%s', streams '%s' [rc=%d]", filename, streams, rc));
257 return rc;
258 }
259
260 static int is_wanted_stream(char type, const char *streams)
261 {
262 if (streams == NULL)
263 return 1;
264 if (strchr(streams, type))
265 return 1;
266 return 0;
267 }
268
269 static void replay_reset_step(struct replay_step *step)
270 {
271 assert(step);
272
273 step->size = 0;
274 step->data = NULL;
275 step->type = 0;
276 timerclear(&step->delay);
277 }
278
279 struct timeval *replay_step_get_delay(struct replay_step *step)
280 {
281 assert(step);
282 return &step->delay;
283 }
284
285 /* current data log file */
286 const char *replay_step_get_filename(struct replay_step *step)
287 {
288 assert(step);
289 return step->data->filename;
290 }
291
292 int replay_step_is_empty(struct replay_step *step)
293 {
294 assert(step);
295 return step->size == 0 && step->type == 0;
296 }
297
298
299 static int read_multistream_step(struct replay_step *step, FILE *f, char type)
300 {
301 int rc = 0;
302 char nl;
303 int64_t sec = 0, usec = 0;
304
305 switch (type) {
306 case 'O': /* output */
307 case 'I': /* input */
308 rc = fscanf(f, "%"SCNd64".%06"SCNd64" %zu%c\n",
309 &sec, &usec, &step->size, &nl);
310 if (rc != 4 || nl != '\n')
311 rc = -EINVAL;
312 else
313 rc = 0;
314
315 step->delay.tv_sec = (time_t) sec;
316 step->delay.tv_usec = (suseconds_t) usec;
317 break;
318
319 case 'S': /* signal */
320 case 'H': /* header */
321 {
322 char buf[BUFSIZ];
323
324 rc = fscanf(f, "%"SCNd64".%06"SCNd64" ",
325 &sec, &usec);
326 if (rc != 2)
327 break;
328
329 step->delay.tv_sec = (time_t) sec;
330 step->delay.tv_usec = (suseconds_t) usec;
331
332 rc = fscanf(f, "%128s", buf); /* name */
333 if (rc != 1)
334 break;
335 step->name = strrealloc(step->name, buf);
336 if (!step->name)
337 err_oom();
338
339 if (!fgets(buf, sizeof(buf), f)) { /* value */
340 rc = -errno;
341 break;
342 }
343 if (*buf) {
344 strrem(buf, '\n');
345 step->value = strrealloc(step->value, buf);
346 if (!step->value)
347 err_oom();
348 }
349 rc = 0;
350 break;
351 }
352 default:
353 break;
354 }
355
356 DBG(TIMING, ul_debug(" read step delay & size [rc=%d]", rc));
357 return rc;
358 }
359
360 static struct replay_log *replay_get_stream_log(struct replay_setup *stp, char stream)
361 {
362 size_t i;
363
364 for (i = 0; i < stp->nlogs; i++) {
365 struct replay_log *log = &stp->logs[i];
366
367 if (is_wanted_stream(stream, log->streams))
368 return log;
369 }
370 return NULL;
371 }
372
373 static int replay_seek_log(struct replay_log *log, size_t move)
374 {
375 if (log->noseek)
376 return 0;
377 DBG(LOG, ul_debug(" %s: seek ++ %zu", log->filename, move));
378 return fseek(log->fp, move, SEEK_CUR) == (off_t) -1 ? -errno : 0;
379 }
380
381 /* returns next step with pointer to the right log file for specified streams (e.g.
382 * "IOS" for in/out/signals) or all streams if stream is NULL.
383 *
384 * returns: 0 = success, <0 = error, 1 = done (EOF)
385 */
386 int replay_get_next_step(struct replay_setup *stp, char *streams, struct replay_step **xstep)
387 {
388 struct replay_step *step;
389 int rc;
390 struct timeval ignored_delay;
391
392 assert(stp);
393 assert(stp->timing_fp);
394 assert(xstep);
395
396 step = &stp->step;
397 *xstep = NULL;
398
399 timerclear(&ignored_delay);
400
401 do {
402 struct replay_log *log = NULL;
403
404 rc = 1; /* done */
405 if (feof(stp->timing_fp))
406 break;
407
408 DBG(TIMING, ul_debug("reading next step"));
409
410 replay_reset_step(step);
411 stp->timing_line++;
412
413 switch (stp->timing_format) {
414 case REPLAY_TIMING_SIMPLE:
415 /* old format is the same as new format, but without <type> prefix */
416 rc = read_multistream_step(step, stp->timing_fp, stp->default_type);
417 if (rc == 0)
418 step->type = stp->default_type;
419 break;
420 case REPLAY_TIMING_MULTI:
421 rc = fscanf(stp->timing_fp, "%c ", &step->type);
422 if (rc != 1)
423 rc = -EINVAL;
424 else
425 rc = read_multistream_step(step,
426 stp->timing_fp,
427 step->type);
428 break;
429 }
430
431 if (rc) {
432 if (rc < 0 && feof(stp->timing_fp))
433 rc = 1;
434 break; /* error or EOF */
435 }
436
437 DBG(TIMING, ul_debug(" step entry is '%c'", step->type));
438
439 log = replay_get_stream_log(stp, step->type);
440 if (log) {
441 if (is_wanted_stream(step->type, streams)) {
442 step->data = log;
443 *xstep = step;
444 DBG(LOG, ul_debug(" use %s as data source", log->filename));
445 goto done;
446 }
447 /* The step entry is unwanted, but we keep the right
448 * position in the log file although the data are ignored.
449 */
450 replay_seek_log(log, step->size);
451 } else
452 DBG(TIMING, ul_debug(" not found log for '%c' stream", step->type));
453
454 DBG(TIMING, ul_debug(" ignore step '%c' [delay=%"PRId64".%06"PRId64"]",
455 step->type,
456 (int64_t) step->delay.tv_sec,
457 (int64_t) step->delay.tv_usec));
458
459 timerinc(&ignored_delay, &step->delay);
460 } while (rc == 0);
461
462 done:
463 if (timerisset(&ignored_delay))
464 timerinc(&step->delay, &ignored_delay);
465
466 DBG(TIMING, ul_debug("reading next step done [rc=%d delay=%"PRId64".%06"PRId64
467 "(ignored=%"PRId64".%06"PRId64") size=%zu]",
468 rc,
469 (int64_t) step->delay.tv_sec, (int64_t) step->delay.tv_usec,
470 (int64_t) ignored_delay.tv_sec, (int64_t) ignored_delay.tv_usec,
471 step->size));
472
473 /* normalize delay */
474 if (stp->delay_div) {
475 DBG(TIMING, ul_debug(" normalize delay: divide"));
476 step->delay.tv_sec /= stp->delay_div;
477 step->delay.tv_usec /= stp->delay_div;
478 }
479 if (timerisset(&stp->delay_max) &&
480 timercmp(&step->delay, &stp->delay_max, >)) {
481 DBG(TIMING, ul_debug(" normalize delay: align to max"));
482 step->delay.tv_sec = stp->delay_max.tv_sec;
483 step->delay.tv_usec = stp->delay_max.tv_usec;
484 }
485 if (timerisset(&stp->delay_min) &&
486 timercmp(&step->delay, &stp->delay_min, <)) {
487 DBG(TIMING, ul_debug(" normalize delay: align to min"));
488 timerclear(&step->delay);
489 }
490
491 return rc;
492 }
493
494 /* return: 0 = success, <0 = error, 1 = done (EOF) */
495 int replay_emit_step_data(struct replay_setup *stp, struct replay_step *step, int fd)
496 {
497 size_t ct;
498 int rc = 0, cr2nl = 0;
499 char buf[BUFSIZ];
500
501 assert(stp);
502 assert(step);
503 switch (step->type) {
504 case 'S':
505 assert(step->name);
506 assert(step->value);
507 dprintf(fd, "%s %s\n", step->name, step->value);
508 DBG(LOG, ul_debug("log signal emitted"));
509 return 0;
510 case 'H':
511 assert(step->name);
512 assert(step->value);
513 dprintf(fd, "%10s: %s\n", step->name, step->value);
514 DBG(LOG, ul_debug("log header emitted"));
515 return 0;
516 default:
517 break; /* continue with real data */
518 }
519
520 assert(step->size);
521 assert(step->data);
522 assert(step->data->fp);
523
524 switch (stp->crmode) {
525 case REPLAY_CRMODE_AUTO:
526 if (step->type == 'I')
527 cr2nl = 1;
528 break;
529 case REPLAY_CRMODE_NEVER:
530 cr2nl = 0;
531 break;
532 case REPLAY_CRMODE_ALWAYS:
533 cr2nl = 1;
534 break;
535 }
536
537 for (ct = step->size; ct > 0; ) {
538 size_t len, cc;
539
540 cc = ct > sizeof(buf) ? sizeof(buf): ct;
541 len = fread(buf, 1, cc, step->data->fp);
542
543 if (!len) {
544 DBG(LOG, ul_debug("log data emit: failed to read log %m"));
545 break;
546 }
547
548 if (cr2nl) {
549 size_t i;
550
551 for (i = 0; i < len; i++) {
552 if (buf[i] == 0x0D)
553 buf[i] = '\n';
554 }
555 }
556
557 ct -= len;
558 cc = write(fd, buf, len);
559 if (cc != len) {
560 rc = -errno;
561 DBG(LOG, ul_debug("log data emit: failed write data %m"));
562 break;
563 }
564 }
565
566 if (ct && ferror(step->data->fp))
567 rc = -errno;
568 if (ct && feof(step->data->fp))
569 rc = 1;
570
571 DBG(LOG, ul_debug("log data emitted [rc=%d size=%zu]", rc, step->size));
572 return rc;
573 }