]>
git.ipfire.org Git - thirdparty/util-linux.git/blob - term-utils/script-playutils.c
14 #include "closestream.h"
17 #include "script-playutils.h"
19 UL_DEBUG_DEFINE_MASK(scriptreplay
);
20 UL_DEBUG_DEFINE_MASKNAMES(scriptreplay
) = UL_DEBUG_EMPTY_MASKNAMES
;
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)
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.)
30 * The step data are stored in log files, the right log file for the step is
31 * selected from replay_setup.
34 REPLAY_TIMING_SIMPLE
, /* timing info in classic "<delta> <offset>" format */
35 REPLAY_TIMING_MULTI
/* multiple streams in format "<type> <delta> <offset|etc> */
39 const char *streams
; /* 'I'nput, 'O'utput or both */
43 unsigned int noseek
: 1; /* do not seek in this log */
47 char type
; /* 'I'nput, 'O'utput, ... */
50 char *name
; /* signals / headers */
54 struct replay_log
*data
;
58 struct replay_log
*logs
;
61 struct replay_step step
; /* current step */
64 const char *timing_filename
;
68 struct timeval delay_max
;
69 struct timeval delay_min
;
72 char default_type
; /* type for REPLAY_TIMING_SIMPLE */
76 void replay_init_debug(void)
78 __UL_INIT_DEBUG_FROM_ENV(scriptreplay
, SCRIPTREPLAY_DEBUG_
, 0, SCRIPTREPLAY_DEBUG
);
81 static int ignore_line(FILE *f
)
85 while((c
= fgetc(f
)) != EOF
&& c
!= '\n');
89 DBG(LOG
, ul_debug(" ignore line"));
93 /* incretemt @a by @b */
94 static inline void timerinc(struct timeval
*a
, struct timeval
*b
)
99 a
->tv_sec
= res
.tv_sec
;
100 a
->tv_usec
= res
.tv_usec
;
103 struct replay_setup
*replay_new_setup(void)
105 return xcalloc(1, sizeof(struct replay_setup
));
108 void replay_free_setup(struct replay_setup
*stp
)
114 free(stp
->step
.name
);
115 free(stp
->step
.value
);
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
)
124 stp
->default_type
= type
;
129 int replay_set_crmode(struct replay_setup
*stp
, int mode
)
137 int replay_set_delay_min(struct replay_setup
*stp
, const struct timeval
*tv
)
139 stp
->delay_min
.tv_sec
= tv
->tv_sec
;
140 stp
->delay_min
.tv_usec
= tv
->tv_usec
;
144 int replay_set_delay_max(struct replay_setup
*stp
, const struct timeval
*tv
)
146 stp
->delay_max
.tv_sec
= tv
->tv_sec
;
147 stp
->delay_max
.tv_usec
= tv
->tv_usec
;
151 int replay_set_delay_div(struct replay_setup
*stp
, const double divi
)
153 stp
->delay_div
= divi
;
157 static struct replay_log
*replay_new_log(struct replay_setup
*stp
,
159 const char *filename
,
162 struct replay_log
*log
;
168 stp
->logs
= xreallocarray(stp
->logs
, stp
->nlogs
+ 1, sizeof(*log
));
169 log
= &stp
->logs
[stp
->nlogs
];
172 memset(log
, 0, sizeof(*log
));
173 log
->filename
= filename
;
174 log
->streams
= streams
;
180 int replay_set_timing_file(struct replay_setup
*stp
, const char *filename
)
187 stp
->timing_filename
= filename
;
188 stp
->timing_line
= 0;
190 stp
->timing_fp
= fopen(filename
, "r");
194 /* detect timing file format */
195 c
= fgetc(stp
->timing_fp
);
197 if (isdigit((unsigned int) c
))
198 stp
->timing_format
= REPLAY_TIMING_SIMPLE
;
200 stp
->timing_format
= REPLAY_TIMING_MULTI
;
201 ungetc(c
, stp
->timing_fp
);
202 } else if (ferror(stp
->timing_fp
))
206 if (rc
&& stp
->timing_fp
) {
207 fclose(stp
->timing_fp
);
208 stp
->timing_fp
= NULL
;
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
);
219 DBG(LOG
, ul_debug("associate file '%s' for streams 'SH'", filename
));
223 DBG(TIMING
, ul_debug("timing file set to '%s' [rc=%d]", filename
, rc
));
227 const char *replay_get_timing_file(struct replay_setup
*setup
)
230 return setup
->timing_filename
;
233 int replay_get_timing_line(struct replay_setup
*setup
)
236 return setup
->timing_line
;
239 int replay_associate_log(struct replay_setup
*stp
,
240 const char *streams
, const char *filename
)
249 /* open the file and skip the first line */
250 f
= fopen(filename
, "r");
251 rc
= f
== NULL
? -errno
: ignore_line(f
);
254 replay_new_log(stp
, streams
, filename
, f
);
258 DBG(LOG
, ul_debug("associate log file '%s', streams '%s' [rc=%d]", filename
, streams
, rc
));
262 static int is_wanted_stream(char type
, const char *streams
)
266 if (strchr(streams
, type
))
271 static void replay_reset_step(struct replay_step
*step
)
278 timerclear(&step
->delay
);
281 struct timeval
*replay_step_get_delay(struct replay_step
*step
)
287 /* current data log file */
288 const char *replay_step_get_filename(struct replay_step
*step
)
291 return step
->data
->filename
;
294 int replay_step_is_empty(struct replay_step
*step
)
297 return step
->size
== 0 && step
->type
== 0;
301 static int read_multistream_step(struct replay_step
*step
, FILE *f
, char type
)
305 int64_t sec
= 0, usec
= 0;
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')
317 step
->delay
.tv_sec
= (time_t) sec
;
318 step
->delay
.tv_usec
= (suseconds_t
) usec
;
321 case 'S': /* signal */
322 case 'H': /* header */
326 rc
= fscanf(f
, "%"SCNd64
".%06"SCNd64
" ",
331 step
->delay
.tv_sec
= (time_t) sec
;
332 step
->delay
.tv_usec
= (suseconds_t
) usec
;
334 rc
= fscanf(f
, "%128s", buf
); /* name */
337 step
->name
= strrealloc(step
->name
, buf
);
341 if (!fgets(buf
, sizeof(buf
), f
)) { /* value */
347 step
->value
= strrealloc(step
->value
, buf
);
358 DBG(TIMING
, ul_debug(" read step delay & size [rc=%d]", rc
));
362 static struct replay_log
*replay_get_stream_log(struct replay_setup
*stp
, char stream
)
366 for (i
= 0; i
< stp
->nlogs
; i
++) {
367 struct replay_log
*log
= &stp
->logs
[i
];
369 if (is_wanted_stream(stream
, log
->streams
))
375 static int replay_seek_log(struct replay_log
*log
, size_t move
)
379 DBG(LOG
, ul_debug(" %s: seek ++ %zu", log
->filename
, move
));
380 return fseek(log
->fp
, move
, SEEK_CUR
) == (off_t
) -1 ? -errno
: 0;
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.
386 * returns: 0 = success, <0 = error, 1 = done (EOF)
388 int replay_get_next_step(struct replay_setup
*stp
, char *streams
, struct replay_step
**xstep
)
390 struct replay_step
*step
;
392 struct timeval ignored_delay
;
395 assert(stp
->timing_fp
);
401 timerclear(&ignored_delay
);
404 struct replay_log
*log
= NULL
;
407 if (feof(stp
->timing_fp
))
410 DBG(TIMING
, ul_debug("reading next step"));
412 replay_reset_step(step
);
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
);
420 step
->type
= stp
->default_type
;
422 case REPLAY_TIMING_MULTI
:
423 rc
= fscanf(stp
->timing_fp
, "%c ", &step
->type
);
427 rc
= read_multistream_step(step
,
434 if (rc
< 0 && feof(stp
->timing_fp
))
436 break; /* error or EOF */
439 DBG(TIMING
, ul_debug(" step entry is '%c'", step
->type
));
441 log
= replay_get_stream_log(stp
, step
->type
);
443 if (is_wanted_stream(step
->type
, streams
)) {
446 DBG(LOG
, ul_debug(" use %s as data source", log
->filename
));
449 /* The step entry is unwanted, but we keep the right
450 * position in the log file although the data are ignored.
452 replay_seek_log(log
, step
->size
);
454 DBG(TIMING
, ul_debug(" not found log for '%c' stream", step
->type
));
456 DBG(TIMING
, ul_debug(" ignore step '%c' [delay=%"PRId64
".%06"PRId64
"]",
458 (int64_t) step
->delay
.tv_sec
,
459 (int64_t) step
->delay
.tv_usec
));
461 timerinc(&ignored_delay
, &step
->delay
);
465 if (timerisset(&ignored_delay
))
466 timerinc(&step
->delay
, &ignored_delay
);
468 DBG(TIMING
, ul_debug("reading next step done [rc=%d delay=%"PRId64
".%06"PRId64
469 "(ignored=%"PRId64
".%06"PRId64
") size=%zu]",
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
,
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
;
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
;
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
);
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
)
500 int rc
= 0, cr2nl
= 0;
505 switch (step
->type
) {
509 dprintf(fd
, "%s %s\n", step
->name
, step
->value
);
510 DBG(LOG
, ul_debug("log signal emitted"));
515 dprintf(fd
, "%10s: %s\n", step
->name
, step
->value
);
516 DBG(LOG
, ul_debug("log header emitted"));
519 break; /* continue with real data */
524 assert(step
->data
->fp
);
526 switch (stp
->crmode
) {
527 case REPLAY_CRMODE_AUTO
:
528 if (step
->type
== 'I')
531 case REPLAY_CRMODE_NEVER
:
534 case REPLAY_CRMODE_ALWAYS
:
539 for (ct
= step
->size
; ct
> 0; ) {
542 cc
= ct
> sizeof(buf
) ? sizeof(buf
): ct
;
543 len
= fread(buf
, 1, cc
, step
->data
->fp
);
546 DBG(LOG
, ul_debug("log data emit: failed to read log %m"));
553 for (i
= 0; i
< len
; i
++) {
560 cc
= write(fd
, buf
, len
);
563 DBG(LOG
, ul_debug("log data emit: failed write data %m"));
568 if (ct
&& ferror(step
->data
->fp
))
570 if (ct
&& feof(step
->data
->fp
))
573 DBG(LOG
, ul_debug("log data emitted [rc=%d size=%zu]", rc
, step
->size
));