]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
scriptreplay: move all utils to script-playutils.{c,h}
authorKarel Zak <kzak@redhat.com>
Mon, 1 Jul 2019 13:00:52 +0000 (15:00 +0200)
committerKarel Zak <kzak@redhat.com>
Tue, 8 Oct 2019 11:11:53 +0000 (13:11 +0200)
Signed-off-by: Karel Zak <kzak@redhat.com>
term-utils/Makemodule.am
term-utils/script-playutils.c [new file with mode: 0644]
term-utils/script-playutils.h [new file with mode: 0644]
term-utils/scriptreplay.c

index d0550f54a7ddeaa923de239c6a278d02724147da..f2ea5bc530de70939d6a0df962e053974c2eb8c6 100644 (file)
@@ -20,7 +20,9 @@ endif # BUILD_SCRIPT
 if BUILD_SCRIPTREPLAY
 usrbin_exec_PROGRAMS += scriptreplay
 dist_man_MANS += term-utils/scriptreplay.1
-scriptreplay_SOURCES = term-utils/scriptreplay.c
+scriptreplay_SOURCES = term-utils/scriptreplay.c \
+                      term-utils/script-playutils.c \
+                      term-utils/script-playutils.h
 scriptreplay_LDADD = $(LDADD) libcommon.la $(MATH_LIBS)
 endif # BUILD_SCRIPTREPLAY
 
diff --git a/term-utils/script-playutils.c b/term-utils/script-playutils.c
new file mode 100644 (file)
index 0000000..9594aef
--- /dev/null
@@ -0,0 +1,484 @@
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <limits.h>
+#include <math.h>
+#include <unistd.h>
+
+#include "c.h"
+#include "xalloc.h"
+#include "closestream.h"
+#include "nls.h"
+#include "strutils.h"
+#include "script-playutils.h"
+
+UL_DEBUG_DEFINE_MASK(scriptreplay);
+UL_DEBUG_DEFINE_MASKNAMES(scriptreplay) = UL_DEBUG_EMPTY_MASKNAMES;
+
+#define DBG(m, x)       __UL_DBG(scriptreplay, SCRIPTREPLAY_DEBUG_, m, x)
+#define ON_DBG(m, x)    __UL_DBG_CALL(scriptreplay, SCRIPTREPLAY_DEBUG_, m, x)
+
+/*
+ * The script replay is driven by timing file where each entry describes one
+ * step in the replay. The timing step may refer input or output (or
+ * signal, extra informations, etc.)
+ *
+ * The step data are stored in log files, the right log file for the step is
+ * selected from replay_setup.
+ */
+enum {
+       REPLAY_TIMING_SIMPLE,           /* timing info in classic "<delta> <offset>" format */
+       REPLAY_TIMING_MULTI             /* multiple streams in format "<type> <delta> <offset|etc> */
+};
+
+struct replay_log {
+       const char      *streams;       /* 'I'nput, 'O'utput or both */
+       const char      *filename;
+       FILE            *fp;
+
+       unsigned int    noseek;         /* do not seek in this log */
+};
+
+struct replay_step {
+       char    type;           /* 'I'nput, 'O'utput, ... */
+       double  delay;
+       size_t  size;
+
+       char    *name;          /* signals / heders */
+       char    *value;
+
+       struct replay_log *data;
+};
+
+struct replay_setup {
+       struct replay_log       *logs;
+       size_t                  nlogs;
+
+       struct replay_step      step;   /* current step */
+
+       FILE                    *timing_fp;
+       const char              *timing_filename;
+       int                     timing_format;
+       int                     timing_line;
+
+       char                    default_type;   /* type for REPLAY_TIMING_SIMPLE */
+       int                     crmode;
+};
+
+void replay_init_debug(void)
+{
+       __UL_INIT_DEBUG_FROM_ENV(scriptreplay, SCRIPTREPLAY_DEBUG_, 0, SCRIPTREPLAY_DEBUG);
+}
+
+static int ignore_line(FILE *f)
+{
+       int c;
+
+       while((c = fgetc(f)) != EOF && c != '\n');
+       if (ferror(f))
+               return -errno;
+
+       DBG(LOG, ul_debug("  ignore line"));
+       return 0;
+}
+
+struct replay_setup *replay_new_setup(void)
+{
+       return xcalloc(1, sizeof(struct replay_setup));
+}
+
+/* if timing file does not contains types of entries (old format) than use this
+ * type as the default */
+int replay_set_default_type(struct replay_setup *stp, char type)
+{
+       assert(stp);
+       stp->default_type = type;
+
+       return 0;
+}
+
+int replay_set_crmode(struct replay_setup *stp, int mode)
+{
+       assert(stp);
+       stp->crmode = mode;
+
+       return 0;
+}
+
+static struct replay_log *replay_new_log(struct replay_setup *stp,
+                                        const char *streams,
+                                        const char *filename,
+                                        FILE *f)
+{
+       struct replay_log *log;
+
+       assert(stp);
+       assert(streams);
+       assert(filename);
+
+       stp->logs = xrealloc(stp->logs, (stp->nlogs + 1) *  sizeof(*log));
+       log = &stp->logs[stp->nlogs];
+       stp->nlogs++;
+
+       log->filename = filename;
+       log->streams = streams;
+       log->fp = f;
+
+       return log;
+}
+
+int replay_set_timing_file(struct replay_setup *stp, const char *filename)
+{
+       int c, rc = 0;
+
+       assert(stp);
+       assert(filename);
+
+       stp->timing_filename = filename;
+       stp->timing_line = 0;
+
+       stp->timing_fp = fopen(filename, "r");
+       if (!stp->timing_fp)
+               rc = -errno;
+       else {
+               /* detect timing file format */
+               c = fgetc(stp->timing_fp);
+               if (c != EOF) {
+                       if (isdigit((unsigned int) c))
+                               stp->timing_format = REPLAY_TIMING_SIMPLE;
+                       else
+                               stp->timing_format = REPLAY_TIMING_MULTI;
+                       ungetc(c, stp->timing_fp);
+               } else if (ferror(stp->timing_fp))
+                       rc = -errno;
+       }
+
+       if (rc && stp->timing_fp) {
+               fclose(stp->timing_fp);
+               stp->timing_fp = NULL;
+       }
+
+       /* create quasi-log for signals, headers, etc. */
+       if (rc == 0 && stp->timing_format == REPLAY_TIMING_MULTI) {
+               struct replay_log *log = replay_new_log(stp, "SH",
+                                               filename, stp->timing_fp);
+               if (!log)
+                       rc = -ENOMEM;
+               else {
+                       log->noseek = 1;
+                       DBG(LOG, ul_debug("accociate log file '%s' with 'SH'", filename));
+               }
+       }
+
+       DBG(TIMING, ul_debug("timing file set to '%s' [rc=%d]", filename, rc));
+       return rc;
+}
+
+const char *replay_get_timing_file(struct replay_setup *setup)
+{
+       assert(setup);
+       return setup->timing_filename;
+}
+
+int replay_get_timing_line(struct replay_setup *setup)
+{
+       assert(setup);
+       return setup->timing_line;
+}
+
+int replay_associate_log(struct replay_setup *stp,
+                       const char *streams, const char *filename)
+{
+       FILE *f;
+       int rc;
+
+       assert(stp);
+       assert(streams);
+       assert(filename);
+
+       /* open the file and skip the first line */
+       f = fopen(filename, "r");
+       rc = f == NULL ? -errno : ignore_line(f);
+
+       if (rc == 0)
+               replay_new_log(stp, streams, filename, f);
+
+       DBG(LOG, ul_debug("accociate log file '%s' with '%s' [rc=%d]", filename, streams, rc));
+       return rc;
+}
+
+static int is_wanted_stream(char type, const char *streams)
+{
+       if (streams == NULL)
+               return 1;
+       if (strchr(streams, type))
+               return 1;
+       return 0;
+}
+
+static void replay_reset_step(struct replay_step *step)
+{
+       assert(step);
+
+       step->size = 0;
+       step->delay = 0;
+       step->data = NULL;
+       step->type = 0;
+}
+
+double replay_step_get_delay(struct replay_step *step)
+{
+       assert(step);
+       return step->delay;
+}
+
+/* current data log file */
+const char *replay_step_get_filename(struct replay_step *step)
+{
+       assert(step);
+       return step->data->filename;
+}
+
+static int read_multistream_step(struct replay_step *step, FILE *f, char type)
+{
+       int rc = 0;
+       char nl;
+
+
+       switch (type) {
+       case 'O': /* output */
+       case 'I': /* input */
+               rc = fscanf(f, "%lf %zu%c\n", &step->delay, &step->size, &nl);
+               if (rc != 3 || nl != '\n')
+                       rc = -EINVAL;
+               else
+                       rc = 0;
+               break;
+
+       case 'S': /* signal */
+       case 'H': /* header */
+       {
+               char buf[BUFSIZ];
+
+               rc = fscanf(f, "%lf ", &step->delay);   /* delay */
+               if (rc != 1)
+                       break;
+
+               rc = fscanf(f, "%s", buf);              /* name */
+               if (rc != 1)
+                       break;
+               step->name = strrealloc(step->name, buf);
+               if (!step->name)
+                       err_oom();
+
+               if (!fgets(buf, sizeof(buf), f)) {      /* value */
+                       rc = -errno;
+                       break;
+               }
+               if (*buf) {
+                       strrem(buf, '\n');
+                       step->value = strrealloc(step->value, buf);
+                       if (!step->value)
+                               err_oom();
+               }
+               rc = 0;
+               break;
+       }
+       default:
+               break;
+       }
+
+       DBG(TIMING, ul_debug(" read step delay & size [rc=%d]", rc));
+       return rc;
+}
+
+static struct replay_log *replay_get_stream_log(struct replay_setup *stp, char stream)
+{
+       size_t i;
+
+       for (i = 0; i < stp->nlogs; i++) {
+               struct replay_log *log = &stp->logs[i];
+
+               if (is_wanted_stream(stream, log->streams))
+                       return log;
+       }
+       return NULL;
+}
+
+static int replay_seek_log(struct replay_log *log, size_t move)
+{
+       if (log->noseek)
+               return 0;
+
+       DBG(LOG, ul_debug(" %s: seek ++ %zu", log->filename, move));
+       return fseek(log->fp, move, SEEK_CUR) == (off_t) -1 ? -errno : 0;
+}
+
+/* returns next step with pointer to the right log file for specified streams (e.g.
+ * "IOS" for in/out/signals) or all streams if stream is NULL.
+ *
+ * returns: 0 = success, <0 = error, 1 = done (EOF)
+ */
+int replay_get_next_step(struct replay_setup *stp, char *streams, struct replay_step **xstep)
+{
+       struct replay_step *step;
+       int rc;
+       double ignored_delay = 0;
+
+       assert(stp);
+       assert(stp->timing_fp);
+       assert(xstep);
+
+       step = &stp->step;
+       *xstep = NULL;
+
+       do {
+               struct replay_log *log = NULL;
+
+               rc = 1; /* done */
+               if (feof(stp->timing_fp))
+                       break;
+
+               DBG(TIMING, ul_debug("reading next step"));
+
+               replay_reset_step(step);
+               stp->timing_line++;
+
+               switch (stp->timing_format) {
+               case REPLAY_TIMING_SIMPLE:
+                       /* old format is the same as new format, but without <type> prefix */
+                       rc = read_multistream_step(step, stp->timing_fp, stp->default_type);
+                       if (rc == 0)
+                               step->type = stp->default_type;
+                       break;
+               case REPLAY_TIMING_MULTI:
+                       rc = fscanf(stp->timing_fp, "%c ", &step->type);
+                       if (rc != 1)
+                               rc = -EINVAL;
+                       else
+                               rc = read_multistream_step(step,
+                                               stp->timing_fp,
+                                               step->type);
+                       break;
+               }
+
+               if (rc) {
+                       if (rc < 0 && feof(stp->timing_fp))
+                               rc = 1;
+                       break;          /* error or EOF */
+               }
+
+               DBG(TIMING, ul_debug(" step entry is '%c'", step->type));
+
+               log = replay_get_stream_log(stp, step->type);
+               if (log) {
+                       if (is_wanted_stream(step->type, streams)) {
+                               step->data = log;
+                               *xstep = step;
+                               DBG(LOG, ul_debug(" use %s as data source", log->filename));
+                               goto done;
+                       }
+                       /* The step entry is unwanted, but we keep the right
+                        * position in the log file although the data are ignored.
+                        */
+                       replay_seek_log(log, step->size);
+               } else
+                       DBG(TIMING, ul_debug(" not found log for '%c' stream", step->type));
+
+               DBG(TIMING, ul_debug(" ignore step '%c' [delay=%f]",
+                                       step->type, step->delay));
+               ignored_delay += step->delay;
+       } while (rc == 0);
+
+done:
+       if (ignored_delay)
+               step->delay += ignored_delay;
+
+       DBG(TIMING, ul_debug("reading next step done [rc=%d delay=%f (ignored=%f) size=%zu]",
+                               rc, step->delay, ignored_delay, step->size));
+       return rc;
+}
+
+/* return: 0 = success, <0 = error, 1 = done (EOF) */
+int replay_emit_step_data(struct replay_setup *stp, struct replay_step *step, int fd)
+{
+       size_t ct;
+       int rc = 0, cr2nl = 0;
+       char buf[BUFSIZ];
+
+       assert(stp);
+       assert(step);
+       switch (step->type) {
+       case 'S':
+               assert(step->name);
+               assert(step->value);
+               dprintf(fd, "%s %s\n", step->name, step->value);
+               DBG(LOG, ul_debug("log signal emited"));
+               return 0;
+       case 'H':
+               assert(step->name);
+               assert(step->value);
+               dprintf(fd, "%10s: %s\n", step->name, step->value);
+               DBG(LOG, ul_debug("log header emited"));
+               return 0;
+       default:
+               break;          /* continue with real data */
+       }
+
+       assert(step->size);
+       assert(step->data);
+       assert(step->data->fp);
+
+       switch (stp->crmode) {
+       case REPLAY_CRMODE_AUTO:
+               if (step->type == 'I')
+                       cr2nl = 1;
+               break;
+       case REPLAY_CRMODE_NEVER:
+               cr2nl = 0;
+               break;
+       case REPLAY_CRMODE_ALWAYS:
+               cr2nl = 1;
+               break;
+       }
+
+       for (ct = step->size; ct > 0; ) {
+               size_t len, cc;
+
+               cc = ct > sizeof(buf) ? sizeof(buf): ct;
+               len = fread(buf, 1, cc, step->data->fp);
+
+               if (!len) {
+                       DBG(LOG, ul_debug("log data emit: failed to read log %m"));
+                       break;
+               }
+
+               if (cr2nl) {
+                       size_t i;
+
+                       for (i = 0; i < len; i++) {
+                               if (buf[i] == 0x0D)
+                                       buf[i] = '\n';
+                       }
+               }
+
+               ct -= len;
+               cc = write(fd, buf, len);
+               if (cc != len) {
+                       rc = -errno;
+                       DBG(LOG, ul_debug("log data emit: failed write data %m"));
+                       break;
+               }
+       }
+
+       if (ct && ferror(step->data->fp))
+               rc = -errno;
+       if (ct && feof(step->data->fp))
+               rc = 1;
+
+       DBG(LOG, ul_debug("log data emited [rc=%d size=%zu]", rc, step->size));
+       return rc;
+}
diff --git a/term-utils/script-playutils.h b/term-utils/script-playutils.h
new file mode 100644 (file)
index 0000000..9e803fa
--- /dev/null
@@ -0,0 +1,45 @@
+#ifndef UTIL_LINUX_SCRIPT_PLAYUTILS_H
+#define UTIL_LINUX_SCRIPT_PLAYUTILS_H
+
+#include "c.h"
+#include "debug.h"
+
+#define SCRIPTREPLAY_DEBUG_INIT        (1 << 1)
+#define SCRIPTREPLAY_DEBUG_TIMING (1 << 2)
+#define SCRIPTREPLAY_DEBUG_LOG (1 << 3)
+#define SCRIPTREPLAY_DEBUG_MISC        (1 << 4)
+#define SCRIPTREPLAY_DEBUG_ALL 0xFFFF
+
+UL_DEBUG_DECLARE_MASK(scriptreplay);
+
+#define DBG(m, x)       __UL_DBG(scriptreplay, SCRIPTREPLAY_DEBUG_, m, x)
+#define ON_DBG(m, x)    __UL_DBG_CALL(scriptreplay, SCRIPTREPLAY_DEBUG_, m, x)
+
+/* CR to '\n' mode */
+enum {
+       REPLAY_CRMODE_AUTO      = 0,
+       REPLAY_CRMODE_NEVER,
+       REPLAY_CRMODE_ALWAYS
+};
+
+struct replay_setup;
+struct replay_step;
+
+void replay_init_debug(void);
+struct replay_setup *replay_new_setup(void);
+
+
+int replay_set_default_type(struct replay_setup *stp, char type);
+int replay_set_crmode(struct replay_setup *stp, int mode);
+int replay_set_timing_file(struct replay_setup *stp, const char *filename);
+const char *replay_get_timing_file(struct replay_setup *setup);
+int replay_get_timing_line(struct replay_setup *setup);
+int replay_associate_log(struct replay_setup *stp, const char *streams, const char *filename);
+
+double replay_step_get_delay(struct replay_step *step);
+const char *replay_step_get_filename(struct replay_step *step);
+int replay_get_next_step(struct replay_setup *stp, char *streams, struct replay_step **xstep);
+
+int replay_emit_step_data(struct replay_setup *stp, struct replay_step *step, int fd);
+
+#endif /* UTIL_LINUX_SCRIPT_PLAYUTILS_H */
index 0693c1719067b929fc3f5009728a87c76ded6940..a6bbeaf814e3bced1581d26639131ae440ca36f3 100644 (file)
 #include <unistd.h>
 #include <getopt.h>
 
-
 #include "c.h"
-#include "debug.h"
 #include "xalloc.h"
 #include "closestream.h"
 #include "nls.h"
 #include "strutils.h"
 #include "optutils.h"
-
-static UL_DEBUG_DEFINE_MASK(scriptreplay);
-UL_DEBUG_DEFINE_MASKNAMES(scriptreplay) = UL_DEBUG_EMPTY_MASKNAMES;
-
-#define SCRIPTREPLAY_DEBUG_INIT        (1 << 1)
-#define SCRIPTREPLAY_DEBUG_TIMING (1 << 2)
-#define SCRIPTREPLAY_DEBUG_LOG (1 << 3)
-#define SCRIPTREPLAY_DEBUG_MISC        (1 << 4)
-#define SCRIPTREPLAY_DEBUG_ALL 0xFFFF
-
-#define DBG(m, x)       __UL_DBG(scriptreplay, SCRIPTREPLAY_DEBUG_, m, x)
-#define ON_DBG(m, x)    __UL_DBG_CALL(scriptreplay, SCRIPTREPLAY_DEBUG_, m, x)
+#include "script-playutils.h"
 
 #define SCRIPT_MIN_DELAY 0.0001                /* from original sripreplay.pl */
 
-/*
- * The script replay is driven by timing file where each entry describes one
- * step in the replay. The timing step may refer input or output (or
- * signal, extra informations, etc.)
- *
- * The step data are stored in log files, the right log file for the step is
- * selected from replay_setup.
- *
- * TODO: move struct replay_{log,step,setup} to script-playutils.c to make it
- * usable for scriptlive(1) code.
- */
-
-enum {
-       REPLAY_TIMING_SIMPLE,           /* timing info in classic "<delta> <offset>" format */
-       REPLAY_TIMING_MULTI             /* multiple streams in format "<type> <delta> <offset|etc> */
-};
-
-/* CR to '\n' mode */
-enum {
-       REPLAY_CRMODE_AUTO      = 0,
-       REPLAY_CRMODE_NEVER,
-       REPLAY_CRMODE_ALWAYS
-};
-
-struct replay_log {
-       const char      *streams;       /* 'I'nput, 'O'utput or both */
-       const char      *filename;
-       FILE            *fp;
-
-       unsigned int    noseek;         /* do not seek in this log */
-};
-
-struct replay_step {
-       char    type;           /* 'I'nput, 'O'utput, ... */
-       double  delay;
-       size_t  size;
-
-       char    *name;          /* signals / heders */
-       char    *value;
-
-       struct replay_log *data;
-};
-
-struct replay_setup {
-       struct replay_log       *logs;
-       size_t                  nlogs;
-
-       struct replay_step      step;   /* current step */
-
-       FILE                    *timing_fp;
-       const char              *timing_filename;
-       int                     timing_format;
-       int                     timing_line;
-
-       char                    default_type;   /* type for REPLAY_TIMING_SIMPLE */
-       int                     crmode;
-};
-
-static void scriptreplay_init_debug(void)
-{
-       __UL_INIT_DEBUG_FROM_ENV(scriptreplay, SCRIPTREPLAY_DEBUG_, 0, SCRIPTREPLAY_DEBUG);
-}
-
-static int ignore_line(FILE *f)
-{
-       int c;
-
-       while((c = fgetc(f)) != EOF && c != '\n');
-       if (ferror(f))
-               return -errno;
-
-       DBG(LOG, ul_debug("  ignore line"));
-       return 0;
-}
-
-/* if timing file does not contains types of entries (old format) than use this
- * type as the default */
-static int replay_set_default_type(struct replay_setup *stp, char type)
-{
-       assert(stp);
-       stp->default_type = type;
-
-       return 0;
-}
-
-static int replay_set_crmode(struct replay_setup *stp, int mode)
-{
-       assert(stp);
-       stp->crmode = mode;
-
-       return 0;
-}
-
-static struct replay_log *replay_new_log(struct replay_setup *stp,
-                                        const char *streams,
-                                        const char *filename,
-                                        FILE *f)
-{
-       struct replay_log *log;
-
-       assert(stp);
-       assert(streams);
-       assert(filename);
-
-       stp->logs = xrealloc(stp->logs, (stp->nlogs + 1) *  sizeof(*log));
-       log = &stp->logs[stp->nlogs];
-       stp->nlogs++;
-
-       log->filename = filename;
-       log->streams = streams;
-       log->fp = f;
-
-       return log;
-}
-
-static int replay_set_timing_file(struct replay_setup *stp, const char *filename)
-{
-       int c, rc = 0;
-
-       assert(stp);
-       assert(filename);
-
-       stp->timing_filename = filename;
-       stp->timing_line = 0;
-
-       stp->timing_fp = fopen(filename, "r");
-       if (!stp->timing_fp)
-               rc = -errno;
-       else {
-               /* detect timing file format */
-               c = fgetc(stp->timing_fp);
-               if (c != EOF) {
-                       if (isdigit((unsigned int) c))
-                               stp->timing_format = REPLAY_TIMING_SIMPLE;
-                       else
-                               stp->timing_format = REPLAY_TIMING_MULTI;
-                       ungetc(c, stp->timing_fp);
-               } else if (ferror(stp->timing_fp))
-                       rc = -errno;
-       }
-
-       if (rc && stp->timing_fp) {
-               fclose(stp->timing_fp);
-               stp->timing_fp = NULL;
-       }
-
-       /* create quasi-log for signals, headers, etc. */
-       if (rc == 0 && stp->timing_format == REPLAY_TIMING_MULTI) {
-               struct replay_log *log = replay_new_log(stp, "SH",
-                                               filename, stp->timing_fp);
-               if (!log)
-                       rc = -ENOMEM;
-               else {
-                       log->noseek = 1;
-                       DBG(LOG, ul_debug("accociate log file '%s' with 'SH'", filename));
-               }
-       }
-
-       DBG(TIMING, ul_debug("timing file set to '%s' [rc=%d]", filename, rc));
-       return rc;
-}
-
-
-static int replay_associate_log(struct replay_setup *stp,
-                               const char *streams, const char *filename)
-{
-       FILE *f;
-       int rc;
-
-       assert(stp);
-       assert(streams);
-       assert(filename);
-
-       /* open the file and skip the first line */
-       f = fopen(filename, "r");
-       rc = f == NULL ? -errno : ignore_line(f);
-
-       if (rc == 0)
-               replay_new_log(stp, streams, filename, f);
-
-       DBG(LOG, ul_debug("accociate log file '%s' with '%s' [rc=%d]", filename, streams, rc));
-       return rc;
-}
-
-static int is_wanted_stream(char type, const char *streams)
-{
-       if (streams == NULL)
-               return 1;
-       if (strchr(streams, type))
-               return 1;
-       return 0;
-}
-
-static void replay_reset_step(struct replay_step *step)
-{
-       assert(step);
-
-       step->size = 0;
-       step->delay = 0;
-       step->data = NULL;
-       step->type = 0;
-}
-
-static int read_multistream_step(struct replay_step *step, FILE *f, char type)
-{
-       int rc = 0;
-       char nl;
-
-
-       switch (type) {
-       case 'O': /* output */
-       case 'I': /* input */
-               rc = fscanf(f, "%lf %zu%c\n", &step->delay, &step->size, &nl);
-               if (rc != 3 || nl != '\n')
-                       rc = -EINVAL;
-               else
-                       rc = 0;
-               break;
-
-       case 'S': /* signal */
-       case 'H': /* header */
-       {
-               char buf[BUFSIZ];
-
-               rc = fscanf(f, "%lf ", &step->delay);   /* delay */
-               if (rc != 1)
-                       break;
-
-               rc = fscanf(f, "%s", buf);              /* name */
-               if (rc != 1)
-                       break;
-               step->name = strrealloc(step->name, buf);
-               if (!step->name)
-                       err_oom();
-
-               if (!fgets(buf, sizeof(buf), f)) {      /* value */
-                       rc = -errno;
-                       break;
-               }
-               if (*buf) {
-                       strrem(buf, '\n');
-                       step->value = strrealloc(step->value, buf);
-                       if (!step->value)
-                               err_oom();
-               }
-               rc = 0;
-               break;
-       }
-       default:
-               break;
-       }
-
-       DBG(TIMING, ul_debug(" read step delay & size [rc=%d]", rc));
-       return rc;
-}
-
-static struct replay_log *replay_get_stream_log(struct replay_setup *stp, char stream)
-{
-       size_t i;
-
-       for (i = 0; i < stp->nlogs; i++) {
-               struct replay_log *log = &stp->logs[i];
-
-               if (is_wanted_stream(stream, log->streams))
-                       return log;
-       }
-       return NULL;
-}
-
-static int replay_seek_log(struct replay_log *log, size_t move)
-{
-       if (log->noseek)
-               return 0;
-
-       DBG(LOG, ul_debug(" %s: seek ++ %zu", log->filename, move));
-       return fseek(log->fp, move, SEEK_CUR) == (off_t) -1 ? -errno : 0;
-}
-
-/* returns next step with pointer to the right log file for specified streams (e.g.
- * "IOS" for in/out/signals) or all streams if stream is NULL.
- *
- * returns: 0 = success, <0 = error, 1 = done (EOF)
- */
-static int replay_get_next_step(struct replay_setup *stp, char *streams, struct replay_step **xstep)
-{
-       struct replay_step *step;
-       int rc;
-       double ignored_delay = 0;
-
-       assert(stp);
-       assert(stp->timing_fp);
-       assert(xstep);
-
-       step = &stp->step;
-       *xstep = NULL;
-
-       do {
-               struct replay_log *log = NULL;
-
-               rc = 1; /* done */
-               if (feof(stp->timing_fp))
-                       break;
-
-               DBG(TIMING, ul_debug("reading next step"));
-
-               replay_reset_step(step);
-               stp->timing_line++;
-
-               switch (stp->timing_format) {
-               case REPLAY_TIMING_SIMPLE:
-                       /* old format is the same as new format, but without <type> prefix */
-                       rc = read_multistream_step(step, stp->timing_fp, stp->default_type);
-                       if (rc == 0)
-                               step->type = stp->default_type;
-                       break;
-               case REPLAY_TIMING_MULTI:
-                       rc = fscanf(stp->timing_fp, "%c ", &step->type);
-                       if (rc != 1)
-                               rc = -EINVAL;
-                       else
-                               rc = read_multistream_step(step,
-                                               stp->timing_fp,
-                                               step->type);
-                       break;
-               }
-
-               if (rc) {
-                       if (rc < 0 && feof(stp->timing_fp))
-                               rc = 1;
-                       break;          /* error or EOF */
-               }
-
-               DBG(TIMING, ul_debug(" step entry is '%c'", step->type));
-
-               log = replay_get_stream_log(stp, step->type);
-               if (log) {
-                       if (is_wanted_stream(step->type, streams)) {
-                               step->data = log;
-                               *xstep = step;
-                               DBG(LOG, ul_debug(" use %s as data source", log->filename));
-                               goto done;
-                       }
-                       /* The step entry is unwanted, but we keep the right
-                        * position in the log file although the data are ignored.
-                        */
-                       replay_seek_log(log, step->size);
-               } else
-                       DBG(TIMING, ul_debug(" not found log for '%c' stream", step->type));
-
-               DBG(TIMING, ul_debug(" ignore step '%c' [delay=%f]",
-                                       step->type, step->delay));
-               ignored_delay += step->delay;
-       } while (rc == 0);
-
-done:
-       if (ignored_delay)
-               step->delay += ignored_delay;
-
-       DBG(TIMING, ul_debug("reading next step done [rc=%d delay=%f (ignored=%f) size=%zu]",
-                               rc, step->delay, ignored_delay, step->size));
-       return rc;
-}
-
-/* return: 0 = success, <0 = error, 1 = done (EOF) */
-static int replay_emit_step_data(struct replay_setup *stp, struct replay_step *step, int fd)
-{
-       size_t ct;
-       int rc = 0, cr2nl = 0;
-       char buf[BUFSIZ];
-
-       assert(stp);
-       assert(step);
-       switch (step->type) {
-       case 'S':
-               assert(step->name);
-               assert(step->value);
-               dprintf(fd, "%s %s\n", step->name, step->value);
-               DBG(LOG, ul_debug("log signal emited"));
-               return 0;
-       case 'H':
-               assert(step->name);
-               assert(step->value);
-               dprintf(fd, "%10s: %s\n", step->name, step->value);
-               DBG(LOG, ul_debug("log header emited"));
-               return 0;
-       default:
-               break;          /* continue with real data */
-       }
-
-       assert(step->size);
-       assert(step->data);
-       assert(step->data->fp);
-
-       switch (stp->crmode) {
-       case REPLAY_CRMODE_AUTO:
-               if (step->type == 'I')
-                       cr2nl = 1;
-               break;
-       case REPLAY_CRMODE_NEVER:
-               cr2nl = 0;
-               break;
-       case REPLAY_CRMODE_ALWAYS:
-               cr2nl = 1;
-               break;
-       }
-
-       for (ct = step->size; ct > 0; ) {
-               size_t len, cc;
-
-               cc = ct > sizeof(buf) ? sizeof(buf): ct;
-               len = fread(buf, 1, cc, step->data->fp);
-
-               if (!len) {
-                       DBG(LOG, ul_debug("log data emit: failed to read log %m"));
-                       break;
-               }
-
-               if (cr2nl) {
-                       size_t i;
-
-                       for (i = 0; i < len; i++) {
-                               if (buf[i] == 0x0D)
-                                       buf[i] = '\n';
-                       }
-               }
-
-               ct -= len;
-               cc = write(fd, buf, len);
-               if (cc != len) {
-                       rc = -errno;
-                       DBG(LOG, ul_debug("log data emit: failed write data %m"));
-                       break;
-               }
-       }
-
-       if (ct && ferror(step->data->fp))
-               rc = -errno;
-       if (ct && feof(step->data->fp))
-               rc = 1;
-
-       DBG(LOG, ul_debug("log data emited [rc=%d size=%zu]", rc, step->size));
-       return rc;
-}
-
 static void __attribute__((__noreturn__))
 usage(void)
 {
@@ -580,7 +123,7 @@ static void appendchr(char *buf, size_t bufsz, int c)
 int
 main(int argc, char *argv[])
 {
-       struct replay_setup setup = { .nlogs = 0 };
+       struct replay_setup *setup = NULL;
        struct replay_step *step = NULL;
        char streams[6] = {0};          /* IOSI - in, out, signal,info */
        const char *log_out = NULL,
@@ -625,7 +168,7 @@ main(int argc, char *argv[])
        textdomain(PACKAGE);
        close_stdout_atexit();
 
-       scriptreplay_init_debug();
+       replay_init_debug();
 
        while ((ch = getopt_long(argc, argv, "B:c:I:O:t:s:d:m:x:Vh", longopts, NULL)) != -1) {
 
@@ -708,16 +251,18 @@ main(int argc, char *argv[])
        if (!(log_out || log_in || log_io))
                errx(EXIT_FAILURE, _("data log file not specified"));
 
-       if (replay_set_timing_file(&setup, log_tm) != 0)
+       setup = replay_new_setup();
+
+       if (replay_set_timing_file(setup, log_tm) != 0)
                err(EXIT_FAILURE, _("cannot open %s"), log_tm);
 
-       if (log_out && replay_associate_log(&setup, "O", log_out) != 0)
+       if (log_out && replay_associate_log(setup, "O", log_out) != 0)
                err(EXIT_FAILURE, _("cannot open %s"), log_out);
 
-       if (log_in && replay_associate_log(&setup, "I", log_in) != 0)
+       if (log_in && replay_associate_log(setup, "I", log_in) != 0)
                err(EXIT_FAILURE, _("cannot open %s"), log_in);
 
-       if (log_io && replay_associate_log(&setup, "IO", log_io) != 0)
+       if (log_io && replay_associate_log(setup, "IO", log_io) != 0)
                err(EXIT_FAILURE, _("cannot open %s"), log_io);
 
        if (!*streams) {
@@ -728,32 +273,34 @@ main(int argc, char *argv[])
                        appendchr(streams, sizeof(streams), 'I');
        }
 
-       replay_set_default_type(&setup,
+       replay_set_default_type(setup,
                        *streams && streams[1] == '\0' ? *streams : 'O');
-       replay_set_crmode(&setup, crmode);
+       replay_set_crmode(setup, crmode);
 
        do {
-               rc = replay_get_next_step(&setup, streams, &step);
+               rc = replay_get_next_step(setup, streams, &step);
                if (rc)
                        break;
 
                if (!summary) {
-                       step->delay /= divi;
-                       if (maxdelayopt && step->delay > maxdelay)
-                               step->delay = maxdelay;
-                       if (step->delay > SCRIPT_MIN_DELAY)
-                               delay_for(step->delay);
+                       double delay = replay_step_get_delay(step);
+
+                       delay /= divi;
+                       if (maxdelayopt && delay > maxdelay)
+                               delay = maxdelay;
+                       if (delay > SCRIPT_MIN_DELAY)
+                               delay_for(delay);
                }
 
-               rc = replay_emit_step_data(&setup, step, STDOUT_FILENO);
+               rc = replay_emit_step_data(setup, step, STDOUT_FILENO);
        } while (rc == 0);
 
        if (step && rc < 0)
-               err(EXIT_FAILURE, _("%s: log file error"), step->data->filename);
+               err(EXIT_FAILURE, _("%s: log file error"), replay_step_get_filename(step));
        else if (rc < 0)
                err(EXIT_FAILURE, _("%s: line %d: timing file error"),
-                               setup.timing_filename,
-                               setup.timing_line);
+                               replay_get_timing_file(setup),
+                               replay_get_timing_line(setup));
        printf("\n");
        exit(EXIT_SUCCESS);
 }