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