1  /*
       2   * Copyright (C) 2008-2019, Karel Zak <kzak@redhat.com>
       3   * Copyright (C) 2008, James Youngman <jay@gnu.org>
       4   *
       5   * This file is free software; you can redistribute it and/or modify
       6   * it under the terms of the GNU General Public License as published by
       7   * the Free Software Foundation; either version 2 of the License, or
       8   * (at your option) any later version.
       9   *
      10   * This file is distributed in the hope that it will be useful,
      11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
      12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13   * GNU General Public License for more details.
      14   *
      15   *
      16   * Based on scriptreplay.pl by Joey Hess <joey@kitenet.net>
      17   */
      18  
      19  #include <stdio.h>
      20  #include <stdarg.h>
      21  #include <stdlib.h>
      22  #include <string.h>
      23  #include <errno.h>
      24  #include <time.h>
      25  #include <limits.h>
      26  #include <math.h>
      27  #include <sys/select.h>
      28  #include <unistd.h>
      29  #include <getopt.h>
      30  #include <sys/time.h>
      31  #include <termios.h>
      32  
      33  #include "c.h"
      34  #include "xalloc.h"
      35  #include "closestream.h"
      36  #include "nls.h"
      37  #include "strutils.h"
      38  #include "optutils.h"
      39  #include "script-playutils.h"
      40  
      41  static void __attribute__((__noreturn__))
      42  usage(void)
      43  {
      44  	FILE *out = stdout;
      45  	fputs(USAGE_HEADER, out);
      46  	fprintf(out,
      47  	      _(" %s [options]\n"),
      48  	      program_invocation_short_name);
      49  	fprintf(out,
      50  	      _(" %s [-t] timingfile [typescript] [divisor]\n"),
      51  	      program_invocation_short_name);
      52  
      53  	fputs(USAGE_SEPARATOR, out);
      54  	fputs(_("Play back terminal typescripts, using timing information.\n"), out);
      55  
      56  	fputs(USAGE_OPTIONS, out);
      57  	fputs(_(" -t, --timing <file>     script timing log file\n"), out);
      58  	fputs(_(" -T, --log-timing <file> alias to -t\n"), out);
      59  	fputs(_(" -I, --log-in <file>     script stdin log file\n"), out);
      60  	fputs(_(" -O, --log-out <file>    script stdout log file (default)\n"), out);
      61  	fputs(_(" -B, --log-io <file>     script stdin and stdout log file\n"), out);
      62  	fputs(USAGE_SEPARATOR, out);
      63  	fputs(_(" -s, --typescript <file> deprecated alias to -O\n"), out);
      64  
      65  	fputs(USAGE_SEPARATOR, out);
      66  	fputs(_("     --summary           display overview about recorded session and exit\n"), out);
      67  	fputs(_(" -d, --divisor <num>     speed up or slow down execution with time divisor\n"), out);
      68  	fputs(_(" -m, --maxdelay <num>    wait at most this many seconds between updates\n"), out);
      69  	fputs(_(" -x, --stream <name>     stream type (out, in, signal or info)\n"), out);
      70  	fputs(_(" -c, --cr-mode <type>    CR char mode (auto, never, always)\n"), out);
      71  	printf(USAGE_HELP_OPTIONS(25));
      72  
      73  	printf(USAGE_MAN_TAIL("scriptreplay(1)"));
      74  	exit(EXIT_SUCCESS);
      75  }
      76  
      77  static double
      78  getnum(const char *s)
      79  {
      80  	const double d = strtod_or_err(s, _("failed to parse number"));
      81  
      82  	if (isnan(d)) {
      83  		errno = EINVAL;
      84  		err(EXIT_FAILURE, "%s: %s", _("failed to parse number"), s);
      85  	}
      86  	return d;
      87  }
      88  
      89  static void
      90  delay_for(struct timeval *delay)
      91  {
      92  #ifdef HAVE_NANOSLEEP
      93  	struct timespec ts, remainder;
      94  	ts.tv_sec = (time_t) delay->tv_sec;
      95  	ts.tv_nsec = delay->tv_usec * 1000;
      96  
      97  	DBG(TIMING, ul_debug("going to sleep for %"PRId64".%06"PRId64,
      98  			(int64_t) delay->tv_sec, (int64_t) delay->tv_usec));
      99  
     100  	while (-1 == nanosleep(&ts, &remainder)) {
     101  		if (EINTR == errno)
     102  			ts = remainder;
     103  		else
     104  			break;
     105  	}
     106  #else
     107  	select(0, NULL, NULL, NULL, delay);
     108  #endif
     109  }
     110  
     111  static void
     112  appendchr(char *buf, size_t bufsz, int c)
     113  {
     114  	size_t sz;
     115  
     116  	if (strchr(buf, c))
     117  		return;		/* already in */
     118  
     119  	sz = strlen(buf);
     120  	if (sz + 1 < bufsz)
     121  		buf[sz] = c;
     122  }
     123  
     124  static int
     125  setterm(struct termios *backup)
     126  {
     127  	struct termios tattr;
     128  
     129  	if (tcgetattr(STDOUT_FILENO, backup) != 0) {
     130  		if (errno != ENOTTY) /* For debugger. */
     131  			err(EXIT_FAILURE, _("unexpected tcgetattr failure"));
     132  		return 0;
     133  	}
     134  	tattr = *backup;
     135  	cfmakeraw(&tattr);
     136  	tattr.c_lflag |= ISIG;
     137  	tcsetattr(STDOUT_FILENO, TCSANOW, &tattr);
     138  	return 1;
     139  }
     140  
     141  int
     142  main(int argc, char *argv[])
     143  {
     144  	static const struct timeval mindelay = { .tv_sec = 0, .tv_usec = 100 };
     145  	struct timeval maxdelay;
     146  
     147  	int isterm;
     148  	struct termios saved;
     149  
     150  	struct replay_setup *setup = NULL;
     151  	struct replay_step *step = NULL;
     152  	char streams[6] = {0};		/* IOSI - in, out, signal,info */
     153  	const char *log_out = NULL,
     154  	           *log_in = NULL,
     155  		   *log_io = NULL,
     156  		   *log_tm = NULL;
     157  	double divi = 1;
     158  	int diviopt = FALSE, idx;
     159  	int ch, rc, crmode = REPLAY_CRMODE_AUTO, summary = 0;
     160  	enum {
     161  		OPT_SUMMARY = CHAR_MAX + 1
     162  	};
     163  
     164  	static const struct option longopts[] = {
     165  		{ "cr-mode",    required_argument,	0, 'c' },
     166  		{ "timing",	required_argument,	0, 't' },
     167  		{ "log-timing", required_argument,      0, 'T' },
     168  		{ "log-in",     required_argument,      0, 'I' },
     169  		{ "log-out",    required_argument,      0, 'O' },
     170  		{ "log-io",     required_argument,      0, 'B' },
     171  		{ "typescript",	required_argument,	0, 's' },
     172  		{ "divisor",	required_argument,	0, 'd' },
     173  		{ "maxdelay",	required_argument,	0, 'm' },
     174  		{ "stream",     required_argument,	0, 'x' },
     175  		{ "summary",    no_argument,            0, OPT_SUMMARY },
     176  		{ "version",	no_argument,		0, 'V' },
     177  		{ "help",	no_argument,		0, 'h' },
     178  		{ NULL,		0, 0, 0 }
     179  	};
     180  	static const ul_excl_t excl[] = {       /* rows and cols in ASCII order */
     181  		{ 'O', 's' },
     182  		{ 0 }
     183  	};
     184  	int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
     185  	/* Because we use space as a separator, we can't afford to use any
     186  	 * locale which tolerates a space in a number.  In any case, script.c
     187  	 * sets the LC_NUMERIC locale to C, anyway.
     188  	 */
     189  	setlocale(LC_ALL, "");
     190  	setlocale(LC_NUMERIC, "C");
     191  
     192  	bindtextdomain(PACKAGE, LOCALEDIR);
     193  	textdomain(PACKAGE);
     194  	close_stdout_atexit();
     195  
     196  	replay_init_debug();
     197  	timerclear(&maxdelay);
     198  
     199  	while ((ch = getopt_long(argc, argv, "B:c:I:O:T:t:s:d:m:x:Vh", longopts, NULL)) != -1) {
     200  
     201  		err_exclusive_options(ch, longopts, excl, excl_st);
     202  
     203  		switch(ch) {
     204  		case 'c':
     205  			if (strcmp("auto", optarg) == 0)
     206  				crmode = REPLAY_CRMODE_AUTO;
     207  			else if (strcmp("never", optarg) == 0)
     208  				crmode = REPLAY_CRMODE_NEVER;
     209  			else if (strcmp("always", optarg) == 0)
     210  				crmode = REPLAY_CRMODE_ALWAYS;
     211  			else
     212  				errx(EXIT_FAILURE, _("unsupported mode name: '%s'"), optarg);
     213  			break;
     214  		case 't':
     215  		case 'T':
     216  			log_tm = optarg;
     217  			break;
     218  		case 'O':
     219  		case 's':
     220  			log_out = optarg;
     221  			break;
     222  		case 'I':
     223  			log_in = optarg;
     224  			break;
     225  		case 'B':
     226  			log_io = optarg;
     227  			break;
     228  		case 'd':
     229  			diviopt = TRUE;
     230  			divi = getnum(optarg);
     231  			break;
     232  		case 'm':
     233  			strtotimeval_or_err(optarg, &maxdelay, _("failed to parse maximal delay argument"));
     234  			break;
     235  		case 'x':
     236  			if (strcmp("in", optarg) == 0)
     237  				appendchr(streams, sizeof(streams), 'I');
     238  			else if (strcmp("out", optarg) == 0)
     239  				appendchr(streams, sizeof(streams), 'O');
     240  			else if (strcmp("signal", optarg) == 0)
     241  				appendchr(streams, sizeof(streams), 'S');
     242  			else if (strcmp("info", optarg) == 0)
     243  				appendchr(streams, sizeof(streams), 'H');
     244  			else
     245  				errx(EXIT_FAILURE, _("unsupported stream name: '%s'"), optarg);
     246  			break;
     247  		case OPT_SUMMARY:
     248  			summary = 1;
     249  			break;
     250  		case 'V':
     251  			print_version(EXIT_SUCCESS);
     252  		case 'h':
     253  			usage();
     254  		default:
     255  			errtryhelp(EXIT_FAILURE);
     256  		}
     257  	}
     258  	argc -= optind;
     259  	argv += optind;
     260  	idx = 0;
     261  
     262  	if (summary)
     263  		streams[0] = 'H', streams[1] = '\0';
     264  
     265  	if (!log_tm && idx < argc)
     266  		log_tm = argv[idx++];
     267  	if (!log_out && !summary && !log_in && !log_io)
     268  		log_out = idx < argc ? argv[idx++] : "typescript";
     269  
     270  	if (!diviopt)
     271  		divi = idx < argc ? getnum(argv[idx]) : 1;
     272  
     273  	if (!log_tm)
     274  		errx(EXIT_FAILURE, _("timing file not specified"));
     275  	if (!(log_out || log_in || log_io) && !summary)
     276  		errx(EXIT_FAILURE, _("data log file not specified"));
     277  
     278  	setup = replay_new_setup();
     279  
     280  	if (replay_set_timing_file(setup, log_tm) != 0)
     281  		err(EXIT_FAILURE, _("cannot open %s"), log_tm);
     282  
     283  	if (log_out && replay_associate_log(setup, "O", log_out) != 0)
     284  		err(EXIT_FAILURE, _("cannot open %s"), log_out);
     285  
     286  	if (log_in && replay_associate_log(setup, "I", log_in) != 0)
     287  		err(EXIT_FAILURE, _("cannot open %s"), log_in);
     288  
     289  	if (log_io && replay_associate_log(setup, "IO", log_io) != 0)
     290  		err(EXIT_FAILURE, _("cannot open %s"), log_io);
     291  
     292  	if (!*streams) {
     293  		/* output is preferred default */
     294  		if (log_out || log_io)
     295  			appendchr(streams, sizeof(streams), 'O');
     296  		else if (log_in)
     297  			appendchr(streams, sizeof(streams), 'I');
     298  	}
     299  
     300  	replay_set_default_type(setup,
     301  			*streams && streams[1] == '\0' ? *streams : 'O');
     302  	replay_set_crmode(setup, crmode);
     303  
     304  	if (divi != 1)
     305  		replay_set_delay_div(setup, divi);
     306  	if (timerisset(&maxdelay))
     307  		replay_set_delay_max(setup, &maxdelay);
     308  	replay_set_delay_min(setup, &mindelay);
     309  
     310  	isterm = setterm(&saved);
     311  
     312  	do {
     313  		rc = replay_get_next_step(setup, streams, &step);
     314  		if (rc)
     315  			break;
     316  
     317  		if (!summary) {
     318  			struct timeval *delay = replay_step_get_delay(step);
     319  
     320  			if (delay && timerisset(delay))
     321  				delay_for(delay);
     322  		}
     323  		rc = replay_emit_step_data(setup, step, STDOUT_FILENO);
     324  	} while (rc == 0);
     325  
     326  	if (isterm)
     327  		tcsetattr(STDOUT_FILENO, TCSADRAIN, &saved);
     328  
     329  	if (step && rc < 0)
     330  		err(EXIT_FAILURE, _("%s: log file error"), replay_step_get_filename(step));
     331  	else if (rc < 0)
     332  		err(EXIT_FAILURE, _("%s: line %d: timing file error"),
     333  				replay_get_timing_file(setup),
     334  				replay_get_timing_line(setup));
     335  	printf("\n");
     336  	replay_free_setup(setup);
     337  
     338  	exit(EXIT_SUCCESS);
     339  }