]> git.ipfire.org Git - thirdparty/util-linux.git/blob - term-utils/script-playutils.c
flock: initialize timevals [-Werror=maybe-uninitialized]
[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 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 = xreallocarray(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 else if (f)
256 fclose(f);
257
258 DBG(LOG, ul_debug("associate log file '%s', streams '%s' [rc=%d]", filename, streams, rc));
259 return rc;
260 }
261
262 static int is_wanted_stream(char type, const char *streams)
263 {
264 if (streams == NULL)
265 return 1;
266 if (strchr(streams, type))
267 return 1;
268 return 0;
269 }
270
271 static void replay_reset_step(struct replay_step *step)
272 {
273 assert(step);
274
275 step->size = 0;
276 step->data = NULL;
277 step->type = 0;
278 timerclear(&step->delay);
279 }
280
281 struct timeval *replay_step_get_delay(struct replay_step *step)
282 {
283 assert(step);
284 return &step->delay;
285 }
286
287 /* current data log file */
288 const char *replay_step_get_filename(struct replay_step *step)
289 {
290 assert(step);
291 return step->data->filename;
292 }
293
294 int replay_step_is_empty(struct replay_step *step)
295 {
296 assert(step);
297 return step->size == 0 && step->type == 0;
298 }
299
300
301 static int read_multistream_step(struct replay_step *step, FILE *f, char type)
302 {
303 int rc = 0;
304 char nl;
305 int64_t sec = 0, usec = 0;
306
307 switch (type) {
308 case 'O': /* output */
309 case 'I': /* input */
310 rc = fscanf(f, "%"SCNd64".%06"SCNd64" %zu%c\n",
311 &sec, &usec, &step->size, &nl);
312 if (rc != 4 || nl != '\n')
313 rc = -EINVAL;
314 else
315 rc = 0;
316
317 step->delay.tv_sec = (time_t) sec;
318 step->delay.tv_usec = (suseconds_t) usec;
319 break;
320
321 case 'S': /* signal */
322 case 'H': /* header */
323 {
324 char buf[BUFSIZ];
325
326 rc = fscanf(f, "%"SCNd64".%06"SCNd64" ",
327 &sec, &usec);
328 if (rc != 2)
329 break;
330
331 step->delay.tv_sec = (time_t) sec;
332 step->delay.tv_usec = (suseconds_t) usec;
333
334 rc = fscanf(f, "%128s", buf); /* name */
335 if (rc != 1)
336 break;
337 step->name = strrealloc(step->name, buf);
338 if (!step->name)
339 err_oom();
340
341 if (!fgets(buf, sizeof(buf), f)) { /* value */
342 rc = -errno;
343 break;
344 }
345 if (*buf) {
346 strrem(buf, '\n');
347 step->value = strrealloc(step->value, buf);
348 if (!step->value)
349 err_oom();
350 }
351 rc = 0;
352 break;
353 }
354 default:
355 break;
356 }
357
358 DBG(TIMING, ul_debug(" read step delay & size [rc=%d]", rc));
359 return rc;
360 }
361
362 static struct replay_log *replay_get_stream_log(struct replay_setup *stp, char stream)
363 {
364 size_t i;
365
366 for (i = 0; i < stp->nlogs; i++) {
367 struct replay_log *log = &stp->logs[i];
368
369 if (is_wanted_stream(stream, log->streams))
370 return log;
371 }
372 return NULL;
373 }
374
375 static int replay_seek_log(struct replay_log *log, size_t move)
376 {
377 if (log->noseek)
378 return 0;
379 DBG(LOG, ul_debug(" %s: seek ++ %zu", log->filename, move));
380 return fseek(log->fp, move, SEEK_CUR) == (off_t) -1 ? -errno : 0;
381 }
382
383 /* returns next step with pointer to the right log file for specified streams (e.g.
384 * "IOS" for in/out/signals) or all streams if stream is NULL.
385 *
386 * returns: 0 = success, <0 = error, 1 = done (EOF)
387 */
388 int replay_get_next_step(struct replay_setup *stp, char *streams, struct replay_step **xstep)
389 {
390 struct replay_step *step;
391 int rc;
392 struct timeval ignored_delay;
393
394 assert(stp);
395 assert(stp->timing_fp);
396 assert(xstep);
397
398 step = &stp->step;
399 *xstep = NULL;
400
401 timerclear(&ignored_delay);
402
403 do {
404 struct replay_log *log = NULL;
405
406 rc = 1; /* done */
407 if (feof(stp->timing_fp))
408 break;
409
410 DBG(TIMING, ul_debug("reading next step"));
411
412 replay_reset_step(step);
413 stp->timing_line++;
414
415 switch (stp->timing_format) {
416 case REPLAY_TIMING_SIMPLE:
417 /* old format is the same as new format, but without <type> prefix */
418 rc = read_multistream_step(step, stp->timing_fp, stp->default_type);
419 if (rc == 0)
420 step->type = stp->default_type;
421 break;
422 case REPLAY_TIMING_MULTI:
423 rc = fscanf(stp->timing_fp, "%c ", &step->type);
424 if (rc != 1)
425 rc = -EINVAL;
426 else
427 rc = read_multistream_step(step,
428 stp->timing_fp,
429 step->type);
430 break;
431 }
432
433 if (rc) {
434 if (rc < 0 && feof(stp->timing_fp))
435 rc = 1;
436 break; /* error or EOF */
437 }
438
439 DBG(TIMING, ul_debug(" step entry is '%c'", step->type));
440
441 log = replay_get_stream_log(stp, step->type);
442 if (log) {
443 if (is_wanted_stream(step->type, streams)) {
444 step->data = log;
445 *xstep = step;
446 DBG(LOG, ul_debug(" use %s as data source", log->filename));
447 goto done;
448 }
449 /* The step entry is unwanted, but we keep the right
450 * position in the log file although the data are ignored.
451 */
452 replay_seek_log(log, step->size);
453 } else
454 DBG(TIMING, ul_debug(" not found log for '%c' stream", step->type));
455
456 DBG(TIMING, ul_debug(" ignore step '%c' [delay=%"PRId64".%06"PRId64"]",
457 step->type,
458 (int64_t) step->delay.tv_sec,
459 (int64_t) step->delay.tv_usec));
460
461 timerinc(&ignored_delay, &step->delay);
462 } while (rc == 0);
463
464 done:
465 if (timerisset(&ignored_delay))
466 timerinc(&step->delay, &ignored_delay);
467
468 DBG(TIMING, ul_debug("reading next step done [rc=%d delay=%"PRId64".%06"PRId64
469 "(ignored=%"PRId64".%06"PRId64") size=%zu]",
470 rc,
471 (int64_t) step->delay.tv_sec, (int64_t) step->delay.tv_usec,
472 (int64_t) ignored_delay.tv_sec, (int64_t) ignored_delay.tv_usec,
473 step->size));
474
475 /* normalize delay */
476 if (stp->delay_div) {
477 DBG(TIMING, ul_debug(" normalize delay: divide"));
478 step->delay.tv_sec /= stp->delay_div;
479 step->delay.tv_usec /= stp->delay_div;
480 }
481 if (timerisset(&stp->delay_max) &&
482 timercmp(&step->delay, &stp->delay_max, >)) {
483 DBG(TIMING, ul_debug(" normalize delay: align to max"));
484 step->delay.tv_sec = stp->delay_max.tv_sec;
485 step->delay.tv_usec = stp->delay_max.tv_usec;
486 }
487 if (timerisset(&stp->delay_min) &&
488 timercmp(&step->delay, &stp->delay_min, <)) {
489 DBG(TIMING, ul_debug(" normalize delay: align to min"));
490 timerclear(&step->delay);
491 }
492
493 return rc;
494 }
495
496 /* return: 0 = success, <0 = error, 1 = done (EOF) */
497 int replay_emit_step_data(struct replay_setup *stp, struct replay_step *step, int fd)
498 {
499 size_t ct;
500 int rc = 0, cr2nl = 0;
501 char buf[BUFSIZ];
502
503 assert(stp);
504 assert(step);
505 switch (step->type) {
506 case 'S':
507 assert(step->name);
508 assert(step->value);
509 dprintf(fd, "%s %s\n", step->name, step->value);
510 DBG(LOG, ul_debug("log signal emitted"));
511 return 0;
512 case 'H':
513 assert(step->name);
514 assert(step->value);
515 dprintf(fd, "%10s: %s\n", step->name, step->value);
516 DBG(LOG, ul_debug("log header emitted"));
517 return 0;
518 default:
519 break; /* continue with real data */
520 }
521
522 assert(step->size);
523 assert(step->data);
524 assert(step->data->fp);
525
526 switch (stp->crmode) {
527 case REPLAY_CRMODE_AUTO:
528 if (step->type == 'I')
529 cr2nl = 1;
530 break;
531 case REPLAY_CRMODE_NEVER:
532 cr2nl = 0;
533 break;
534 case REPLAY_CRMODE_ALWAYS:
535 cr2nl = 1;
536 break;
537 }
538
539 for (ct = step->size; ct > 0; ) {
540 size_t len, cc;
541
542 cc = ct > sizeof(buf) ? sizeof(buf): ct;
543 len = fread(buf, 1, cc, step->data->fp);
544
545 if (!len) {
546 DBG(LOG, ul_debug("log data emit: failed to read log %m"));
547 break;
548 }
549
550 if (cr2nl) {
551 size_t i;
552
553 for (i = 0; i < len; i++) {
554 if (buf[i] == 0x0D)
555 buf[i] = '\n';
556 }
557 }
558
559 ct -= len;
560 cc = write(fd, buf, len);
561 if (cc != len) {
562 rc = -errno;
563 DBG(LOG, ul_debug("log data emit: failed write data %m"));
564 break;
565 }
566 }
567
568 if (ct && ferror(step->data->fp))
569 rc = -errno;
570 if (ct && feof(step->data->fp))
571 rc = 1;
572
573 DBG(LOG, ul_debug("log data emitted [rc=%d size=%zu]", rc, step->size));
574 return rc;
575 }