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