]>
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 / heders */
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
= xrealloc(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("accociate 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
);
256 DBG(LOG
, ul_debug("accociate log file '%s', streams '%s' [rc=%d]", filename
, streams
, rc
));
260 static int is_wanted_stream(char type
, const char *streams
)
264 if (strchr(streams
, type
))
269 static void replay_reset_step(struct replay_step
*step
)
276 timerclear(&step
->delay
);
279 struct timeval
*replay_step_get_delay(struct replay_step
*step
)
285 /* current data log file */
286 const char *replay_step_get_filename(struct replay_step
*step
)
289 return step
->data
->filename
;
292 int replay_step_is_empty(struct replay_step
*step
)
295 return step
->size
== 0 && step
->type
== 0;
299 static int read_multistream_step(struct replay_step
*step
, FILE *f
, char type
)
306 case 'O': /* output */
307 case 'I': /* input */
308 rc
= fscanf(f
, "%ld.%06ld %zu%c\n",
310 &step
->delay
.tv_usec
,
312 if (rc
!= 4 || nl
!= '\n')
318 case 'S': /* signal */
319 case 'H': /* header */
323 rc
= fscanf(f
, "%ld.%06ld ",
325 &step
->delay
.tv_usec
);
330 rc
= fscanf(f
, "%128s", buf
); /* name */
333 step
->name
= strrealloc(step
->name
, buf
);
337 if (!fgets(buf
, sizeof(buf
), f
)) { /* value */
343 step
->value
= strrealloc(step
->value
, buf
);
354 DBG(TIMING
, ul_debug(" read step delay & size [rc=%d]", rc
));
358 static struct replay_log
*replay_get_stream_log(struct replay_setup
*stp
, char stream
)
362 for (i
= 0; i
< stp
->nlogs
; i
++) {
363 struct replay_log
*log
= &stp
->logs
[i
];
365 if (is_wanted_stream(stream
, log
->streams
))
371 static int replay_seek_log(struct replay_log
*log
, size_t move
)
375 DBG(LOG
, ul_debug(" %s: seek ++ %zu", log
->filename
, move
));
376 return fseek(log
->fp
, move
, SEEK_CUR
) == (off_t
) -1 ? -errno
: 0;
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.
382 * returns: 0 = success, <0 = error, 1 = done (EOF)
384 int replay_get_next_step(struct replay_setup
*stp
, char *streams
, struct replay_step
**xstep
)
386 struct replay_step
*step
;
388 struct timeval ignored_delay
;
391 assert(stp
->timing_fp
);
397 timerclear(&ignored_delay
);
400 struct replay_log
*log
= NULL
;
403 if (feof(stp
->timing_fp
))
406 DBG(TIMING
, ul_debug("reading next step"));
408 replay_reset_step(step
);
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
);
416 step
->type
= stp
->default_type
;
418 case REPLAY_TIMING_MULTI
:
419 rc
= fscanf(stp
->timing_fp
, "%c ", &step
->type
);
423 rc
= read_multistream_step(step
,
430 if (rc
< 0 && feof(stp
->timing_fp
))
432 break; /* error or EOF */
435 DBG(TIMING
, ul_debug(" step entry is '%c'", step
->type
));
437 log
= replay_get_stream_log(stp
, step
->type
);
439 if (is_wanted_stream(step
->type
, streams
)) {
442 DBG(LOG
, ul_debug(" use %s as data source", log
->filename
));
445 /* The step entry is unwanted, but we keep the right
446 * position in the log file although the data are ignored.
448 replay_seek_log(log
, step
->size
);
450 DBG(TIMING
, ul_debug(" not found log for '%c' stream", step
->type
));
452 DBG(TIMING
, ul_debug(" ignore step '%c' [delay=%ld.%06ld]",
455 step
->delay
.tv_usec
));
457 timerinc(&ignored_delay
, &step
->delay
);
461 if (timerisset(&ignored_delay
))
462 timerinc(&step
->delay
, &ignored_delay
);
464 DBG(TIMING
, ul_debug("reading next step done [rc=%d delay=%ld.%06ld (ignored=%ld.%06ld) size=%zu]",
466 step
->delay
.tv_sec
, step
->delay
.tv_usec
,
467 ignored_delay
.tv_sec
, ignored_delay
.tv_usec
,
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
;
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
;
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
);
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
)
495 int rc
= 0, cr2nl
= 0;
500 switch (step
->type
) {
504 dprintf(fd
, "%s %s\n", step
->name
, step
->value
);
505 DBG(LOG
, ul_debug("log signal emitted"));
510 dprintf(fd
, "%10s: %s\n", step
->name
, step
->value
);
511 DBG(LOG
, ul_debug("log header emitted"));
514 break; /* continue with real data */
519 assert(step
->data
->fp
);
521 switch (stp
->crmode
) {
522 case REPLAY_CRMODE_AUTO
:
523 if (step
->type
== 'I')
526 case REPLAY_CRMODE_NEVER
:
529 case REPLAY_CRMODE_ALWAYS
:
534 for (ct
= step
->size
; ct
> 0; ) {
537 cc
= ct
> sizeof(buf
) ? sizeof(buf
): ct
;
538 len
= fread(buf
, 1, cc
, step
->data
->fp
);
541 DBG(LOG
, ul_debug("log data emit: failed to read log %m"));
548 for (i
= 0; i
< len
; i
++) {
555 cc
= write(fd
, buf
, len
);
558 DBG(LOG
, ul_debug("log data emit: failed write data %m"));
563 if (ct
&& ferror(step
->data
->fp
))
565 if (ct
&& feof(step
->data
->fp
))
568 DBG(LOG
, ul_debug("log data emitted [rc=%d size=%zu]", rc
, step
->size
));