]> git.ipfire.org Git - thirdparty/util-linux.git/blob - term-utils/script-playutils.c
scriptreplay: (utils) detect empty steps
[thirdparty/util-linux.git] / term-utils / script-playutils.c
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 informations, 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; /* 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 / heders */
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 /* if timing file does not contains types of entries (old format) than use this
109 * type as the default */
110 int replay_set_default_type(struct replay_setup *stp, char type)
111 {
112 assert(stp);
113 stp->default_type = type;
114
115 return 0;
116 }
117
118 int replay_set_crmode(struct replay_setup *stp, int mode)
119 {
120 assert(stp);
121 stp->crmode = mode;
122
123 return 0;
124 }
125
126 int replay_set_delay_min(struct replay_setup *stp, const struct timeval *tv)
127 {
128 stp->delay_min.tv_sec = tv->tv_sec;
129 stp->delay_min.tv_usec = tv->tv_usec;
130 return 0;
131 }
132
133 int replay_set_delay_max(struct replay_setup *stp, const struct timeval *tv)
134 {
135 stp->delay_max.tv_sec = tv->tv_sec;
136 stp->delay_max.tv_usec = tv->tv_usec;
137 return 0;
138 }
139
140 int replay_set_delay_div(struct replay_setup *stp, const double divi)
141 {
142 stp->delay_div = divi;
143 return 0;
144 }
145
146 static struct replay_log *replay_new_log(struct replay_setup *stp,
147 const char *streams,
148 const char *filename,
149 FILE *f)
150 {
151 struct replay_log *log;
152
153 assert(stp);
154 assert(streams);
155 assert(filename);
156
157 stp->logs = xrealloc(stp->logs, (stp->nlogs + 1) * sizeof(*log));
158 log = &stp->logs[stp->nlogs];
159 stp->nlogs++;
160
161 log->filename = filename;
162 log->streams = streams;
163 log->fp = f;
164
165 return log;
166 }
167
168 int replay_set_timing_file(struct replay_setup *stp, const char *filename)
169 {
170 int c, rc = 0;
171
172 assert(stp);
173 assert(filename);
174
175 stp->timing_filename = filename;
176 stp->timing_line = 0;
177
178 stp->timing_fp = fopen(filename, "r");
179 if (!stp->timing_fp)
180 rc = -errno;
181 else {
182 /* detect timing file format */
183 c = fgetc(stp->timing_fp);
184 if (c != EOF) {
185 if (isdigit((unsigned int) c))
186 stp->timing_format = REPLAY_TIMING_SIMPLE;
187 else
188 stp->timing_format = REPLAY_TIMING_MULTI;
189 ungetc(c, stp->timing_fp);
190 } else if (ferror(stp->timing_fp))
191 rc = -errno;
192 }
193
194 if (rc && stp->timing_fp) {
195 fclose(stp->timing_fp);
196 stp->timing_fp = NULL;
197 }
198
199 /* create quasi-log for signals, headers, etc. */
200 if (rc == 0 && stp->timing_format == REPLAY_TIMING_MULTI) {
201 struct replay_log *log = replay_new_log(stp, "SH",
202 filename, stp->timing_fp);
203 if (!log)
204 rc = -ENOMEM;
205 else {
206 log->noseek = 1;
207 DBG(LOG, ul_debug("accociate file '%s' for streams 'SH'", filename));
208 }
209 }
210
211 DBG(TIMING, ul_debug("timing file set to '%s' [rc=%d]", filename, rc));
212 return rc;
213 }
214
215 const char *replay_get_timing_file(struct replay_setup *setup)
216 {
217 assert(setup);
218 return setup->timing_filename;
219 }
220
221 int replay_get_timing_line(struct replay_setup *setup)
222 {
223 assert(setup);
224 return setup->timing_line;
225 }
226
227 int replay_associate_log(struct replay_setup *stp,
228 const char *streams, const char *filename)
229 {
230 FILE *f;
231 int rc;
232
233 assert(stp);
234 assert(streams);
235 assert(filename);
236
237 /* open the file and skip the first line */
238 f = fopen(filename, "r");
239 rc = f == NULL ? -errno : ignore_line(f);
240
241 if (rc == 0)
242 replay_new_log(stp, streams, filename, f);
243
244 DBG(LOG, ul_debug("accociate log file '%s', streams '%s' [rc=%d]", filename, streams, rc));
245 return rc;
246 }
247
248 static int is_wanted_stream(char type, const char *streams)
249 {
250 if (streams == NULL)
251 return 1;
252 if (strchr(streams, type))
253 return 1;
254 return 0;
255 }
256
257 static void replay_reset_step(struct replay_step *step)
258 {
259 assert(step);
260
261 step->size = 0;
262 step->data = NULL;
263 step->type = 0;
264 timerclear(&step->delay);
265 }
266
267 struct timeval *replay_step_get_delay(struct replay_step *step)
268 {
269 assert(step);
270 return &step->delay;
271 }
272
273 /* current data log file */
274 const char *replay_step_get_filename(struct replay_step *step)
275 {
276 assert(step);
277 return step->data->filename;
278 }
279
280 int replay_step_is_empty(struct replay_step *step)
281 {
282 assert(step);
283 return step->size == 0 && step->type == 0;
284 }
285
286
287 static int read_multistream_step(struct replay_step *step, FILE *f, char type)
288 {
289 int rc = 0;
290 char nl;
291
292
293 switch (type) {
294 case 'O': /* output */
295 case 'I': /* input */
296 rc = fscanf(f, "%ld.%06ld %zu%c\n",
297 &step->delay.tv_sec,
298 &step->delay.tv_usec,
299 &step->size, &nl);
300 if (rc != 4 || nl != '\n')
301 rc = -EINVAL;
302 else
303 rc = 0;
304 break;
305
306 case 'S': /* signal */
307 case 'H': /* header */
308 {
309 char buf[BUFSIZ];
310
311 rc = fscanf(f, "%ld.%06ld ",
312 &step->delay.tv_sec,
313 &step->delay.tv_usec);
314
315 if (rc != 2)
316 break;
317
318 rc = fscanf(f, "%s", buf); /* name */
319 if (rc != 1)
320 break;
321 step->name = strrealloc(step->name, buf);
322 if (!step->name)
323 err_oom();
324
325 if (!fgets(buf, sizeof(buf), f)) { /* value */
326 rc = -errno;
327 break;
328 }
329 if (*buf) {
330 strrem(buf, '\n');
331 step->value = strrealloc(step->value, buf);
332 if (!step->value)
333 err_oom();
334 }
335 rc = 0;
336 break;
337 }
338 default:
339 break;
340 }
341
342 DBG(TIMING, ul_debug(" read step delay & size [rc=%d]", rc));
343 return rc;
344 }
345
346 static struct replay_log *replay_get_stream_log(struct replay_setup *stp, char stream)
347 {
348 size_t i;
349
350 for (i = 0; i < stp->nlogs; i++) {
351 struct replay_log *log = &stp->logs[i];
352
353 if (is_wanted_stream(stream, log->streams))
354 return log;
355 }
356 return NULL;
357 }
358
359 static int replay_seek_log(struct replay_log *log, size_t move)
360 {
361 if (log->noseek)
362 return 0;
363
364 DBG(LOG, ul_debug(" %s: seek ++ %zu", log->filename, move));
365 return fseek(log->fp, move, SEEK_CUR) == (off_t) -1 ? -errno : 0;
366 }
367
368 /* returns next step with pointer to the right log file for specified streams (e.g.
369 * "IOS" for in/out/signals) or all streams if stream is NULL.
370 *
371 * returns: 0 = success, <0 = error, 1 = done (EOF)
372 */
373 int replay_get_next_step(struct replay_setup *stp, char *streams, struct replay_step **xstep)
374 {
375 struct replay_step *step;
376 int rc;
377 struct timeval ignored_delay;
378
379 assert(stp);
380 assert(stp->timing_fp);
381 assert(xstep);
382
383 step = &stp->step;
384 *xstep = NULL;
385
386 timerclear(&ignored_delay);
387
388 do {
389 struct replay_log *log = NULL;
390
391 rc = 1; /* done */
392 if (feof(stp->timing_fp))
393 break;
394
395 DBG(TIMING, ul_debug("reading next step"));
396
397 replay_reset_step(step);
398 stp->timing_line++;
399
400 switch (stp->timing_format) {
401 case REPLAY_TIMING_SIMPLE:
402 /* old format is the same as new format, but without <type> prefix */
403 rc = read_multistream_step(step, stp->timing_fp, stp->default_type);
404 if (rc == 0)
405 step->type = stp->default_type;
406 break;
407 case REPLAY_TIMING_MULTI:
408 rc = fscanf(stp->timing_fp, "%c ", &step->type);
409 if (rc != 1)
410 rc = -EINVAL;
411 else
412 rc = read_multistream_step(step,
413 stp->timing_fp,
414 step->type);
415 break;
416 }
417
418 if (rc) {
419 if (rc < 0 && feof(stp->timing_fp))
420 rc = 1;
421 break; /* error or EOF */
422 }
423
424 DBG(TIMING, ul_debug(" step entry is '%c'", step->type));
425
426 log = replay_get_stream_log(stp, step->type);
427 if (log) {
428 if (is_wanted_stream(step->type, streams)) {
429 step->data = log;
430 *xstep = step;
431 DBG(LOG, ul_debug(" use %s as data source", log->filename));
432 goto done;
433 }
434 /* The step entry is unwanted, but we keep the right
435 * position in the log file although the data are ignored.
436 */
437 replay_seek_log(log, step->size);
438 } else
439 DBG(TIMING, ul_debug(" not found log for '%c' stream", step->type));
440
441 DBG(TIMING, ul_debug(" ignore step '%c' [delay=%ld.%06ld]",
442 step->type,
443 step->delay.tv_sec,
444 step->delay.tv_usec));
445
446 timerinc(&ignored_delay, &step->delay);
447 } while (rc == 0);
448
449 done:
450 if (timerisset(&ignored_delay))
451 timerinc(&step->delay, &ignored_delay);
452
453 DBG(TIMING, ul_debug("reading next step done [rc=%d delay=%ld.%06ld (ignored=%ld.%06ld) size=%zu]",
454 rc,
455 step->delay.tv_sec, step->delay.tv_usec,
456 ignored_delay.tv_sec, ignored_delay.tv_usec,
457 step->size));
458
459 /* normalize delay */
460 if (stp->delay_div) {
461 DBG(TIMING, ul_debug(" normalize delay: divide"));
462 step->delay.tv_sec /= stp->delay_div;
463 step->delay.tv_usec /= stp->delay_div;
464 }
465 if (timerisset(&stp->delay_max) &&
466 timercmp(&step->delay, &stp->delay_max, >)) {
467 DBG(TIMING, ul_debug(" normalize delay: align to max"));
468 step->delay.tv_sec = stp->delay_max.tv_sec;
469 step->delay.tv_usec = stp->delay_max.tv_usec;
470 }
471 if (timerisset(&stp->delay_min) &&
472 timercmp(&step->delay, &stp->delay_min, <)) {
473 DBG(TIMING, ul_debug(" normalize delay: align to min"));
474 timerclear(&step->delay);
475 }
476
477 return rc;
478 }
479
480 /* return: 0 = success, <0 = error, 1 = done (EOF) */
481 int replay_emit_step_data(struct replay_setup *stp, struct replay_step *step, int fd)
482 {
483 size_t ct;
484 int rc = 0, cr2nl = 0;
485 char buf[BUFSIZ];
486
487 assert(stp);
488 assert(step);
489 switch (step->type) {
490 case 'S':
491 assert(step->name);
492 assert(step->value);
493 dprintf(fd, "%s %s\n", step->name, step->value);
494 DBG(LOG, ul_debug("log signal emited"));
495 return 0;
496 case 'H':
497 assert(step->name);
498 assert(step->value);
499 dprintf(fd, "%10s: %s\n", step->name, step->value);
500 DBG(LOG, ul_debug("log header emited"));
501 return 0;
502 default:
503 break; /* continue with real data */
504 }
505
506 assert(step->size);
507 assert(step->data);
508 assert(step->data->fp);
509
510 switch (stp->crmode) {
511 case REPLAY_CRMODE_AUTO:
512 if (step->type == 'I')
513 cr2nl = 1;
514 break;
515 case REPLAY_CRMODE_NEVER:
516 cr2nl = 0;
517 break;
518 case REPLAY_CRMODE_ALWAYS:
519 cr2nl = 1;
520 break;
521 }
522
523 for (ct = step->size; ct > 0; ) {
524 size_t len, cc;
525
526 cc = ct > sizeof(buf) ? sizeof(buf): ct;
527 len = fread(buf, 1, cc, step->data->fp);
528
529 if (!len) {
530 DBG(LOG, ul_debug("log data emit: failed to read log %m"));
531 break;
532 }
533
534 if (cr2nl) {
535 size_t i;
536
537 for (i = 0; i < len; i++) {
538 if (buf[i] == 0x0D)
539 buf[i] = '\n';
540 }
541 }
542
543 ct -= len;
544 cc = write(fd, buf, len);
545 if (cc != len) {
546 rc = -errno;
547 DBG(LOG, ul_debug("log data emit: failed write data %m"));
548 break;
549 }
550 }
551
552 if (ct && ferror(step->data->fp))
553 rc = -errno;
554 if (ct && feof(step->data->fp))
555 rc = 1;
556
557 DBG(LOG, ul_debug("log data emited [rc=%d size=%zu]", rc, step->size));
558 return rc;
559 }