scriptlive: add new command to re-execute script(1) typescript
The old good scriptreplay(1) just display your recorded session, the scriptlive(1) uses stdin typescript (from new script(1)) to execute your commands again. Signed-off-by: Karel Zak <kzak@redhat.com>
This commit is contained in:
parent
12352c967c
commit
33869e5ac0
|
@ -149,6 +149,7 @@ ylwrap
|
|||
/sample-*
|
||||
/script
|
||||
/scriptreplay
|
||||
/scriptlive
|
||||
/setarch
|
||||
/setpriv
|
||||
/setsid
|
||||
|
|
|
@ -1820,6 +1820,9 @@ AM_CONDITIONAL([BUILD_SCRIPT], [test "x$build_script" = xyes])
|
|||
UL_BUILD_INIT([scriptreplay], [yes])
|
||||
AM_CONDITIONAL([BUILD_SCRIPTREPLAY], [test "x$build_scriptreplay" = xyes])
|
||||
|
||||
UL_BUILD_INIT([scriptlive], [yes])
|
||||
AM_CONDITIONAL([BUILD_SCRIPTLIVE], [test "x$build_scriptlive" = xyes])
|
||||
|
||||
UL_BUILD_INIT([col], [yes])
|
||||
AM_CONDITIONAL([BUILD_COL], [test "x$build_col" = xyes])
|
||||
|
||||
|
|
|
@ -26,6 +26,15 @@ scriptreplay_SOURCES = term-utils/scriptreplay.c \
|
|||
scriptreplay_LDADD = $(LDADD) libcommon.la $(MATH_LIBS)
|
||||
endif # BUILD_SCRIPTREPLAY
|
||||
|
||||
if BUILD_SCRIPTLIVE
|
||||
usrbin_exec_PROGRAMS += scriptlive
|
||||
dist_man_MANS += term-utils/scriptlive.1
|
||||
scriptlive_SOURCES = term-utils/scriptlive.c \
|
||||
term-utils/script-playutils.c \
|
||||
term-utils/script-playutils.h
|
||||
scriptlive_LDADD = $(LDADD) libcommon.la $(MATH_LIBS)
|
||||
endif # BUILD_SCRIPTLIVE
|
||||
|
||||
|
||||
if BUILD_AGETTY
|
||||
sbin_PROGRAMS += agetty
|
||||
|
|
|
@ -632,7 +632,6 @@ static void __attribute__((__noreturn__)) done_log(struct script_control *ctl, c
|
|||
utempter_remove_record(ctl->master);
|
||||
#endif
|
||||
kill(ctl->child, SIGTERM); /* make sure we don't create orphans */
|
||||
exit(ctl->rc_wanted ? status : EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
static void __attribute__((__noreturn__)) done(struct script_control *ctl)
|
||||
|
|
|
@ -0,0 +1,299 @@
|
|||
/*
|
||||
* Copyright (C) 2019, Karel Zak <kzak@redhat.com>
|
||||
*
|
||||
* This file is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This file is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*/
|
||||
|
||||
#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 <sys/select.h>
|
||||
#include <unistd.h>
|
||||
#include <getopt.h>
|
||||
#include <signal.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
#include <paths.h>
|
||||
|
||||
#include "c.h"
|
||||
#include "xalloc.h"
|
||||
#include "closestream.h"
|
||||
#include "nls.h"
|
||||
#include "strutils.h"
|
||||
#include "optutils.h"
|
||||
#include "script-playutils.h"
|
||||
#include "rpmatch.h"
|
||||
|
||||
|
||||
#define SCRIPT_MIN_DELAY 0.0001 /* from original sripreplay.pl */
|
||||
|
||||
static void __attribute__((__noreturn__))
|
||||
usage(void)
|
||||
{
|
||||
FILE *out = stdout;
|
||||
fputs(USAGE_HEADER, out);
|
||||
fprintf(out,
|
||||
_(" %s [options]\n"),
|
||||
program_invocation_short_name);
|
||||
fprintf(out,
|
||||
_(" %s [-t] timingfile [-I|-B] typescript\n"),
|
||||
program_invocation_short_name);
|
||||
|
||||
fputs(USAGE_SEPARATOR, out);
|
||||
fputs(_("Execute terminal typescript.\n"), out);
|
||||
|
||||
fputs(USAGE_OPTIONS, out);
|
||||
fputs(_(" -t, --timing <file> script timing log file\n"), out);
|
||||
fputs(_(" -I, --log-in <file> script stdin log file\n"), out);
|
||||
fputs(_(" -B, --log-io <file> script stdin and stdout log file\n"), out);
|
||||
|
||||
fputs(USAGE_SEPARATOR, out);
|
||||
fputs(_(" -d, --divisor <num> speed up or slow down execution with time divisor\n"), out);
|
||||
fputs(_(" -m, --maxdelay <num> wait at most this many seconds between updates\n"), out);
|
||||
printf(USAGE_HELP_OPTIONS(25));
|
||||
|
||||
printf(USAGE_MAN_TAIL("scriptlive(1)"));
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
static double
|
||||
getnum(const char *s)
|
||||
{
|
||||
const double d = strtod_or_err(s, _("failed to parse number"));
|
||||
|
||||
if (isnan(d)) {
|
||||
errno = EINVAL;
|
||||
err(EXIT_FAILURE, "%s: %s", _("failed to parse number"), s);
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
static void
|
||||
delay_for(double delay)
|
||||
{
|
||||
#ifdef HAVE_NANOSLEEP
|
||||
struct timespec ts, remainder;
|
||||
ts.tv_sec = (time_t) delay;
|
||||
ts.tv_nsec = (delay - ts.tv_sec) * 1.0e9;
|
||||
|
||||
DBG(TIMING, ul_debug("going to sleep for %fs", delay));
|
||||
|
||||
while (-1 == nanosleep(&ts, &remainder)) {
|
||||
if (EINTR == errno)
|
||||
ts = remainder;
|
||||
else
|
||||
break;
|
||||
}
|
||||
#else
|
||||
struct timeval tv;
|
||||
tv.tv_sec = (long) delay;
|
||||
tv.tv_usec = (delay - tv.tv_sec) * 1.0e6;
|
||||
select(0, NULL, NULL, NULL, &tv);
|
||||
#endif
|
||||
}
|
||||
|
||||
static int start_shell(const char *shell, pid_t *shell_pid, int *shell_fd)
|
||||
{
|
||||
const char *shname;
|
||||
int fds[2];
|
||||
|
||||
assert(shell_pid);
|
||||
assert(shell_fd);
|
||||
|
||||
if (pipe(fds) < 0)
|
||||
err(EXIT_FAILURE, _("pipe failed"));
|
||||
|
||||
*shell_pid = fork();
|
||||
|
||||
if (*shell_pid == -1)
|
||||
err(EXIT_FAILURE, _("fork failed"));
|
||||
if (*shell_pid != 0) {
|
||||
/* parent */
|
||||
*shell_fd = fds[1];
|
||||
close(fds[0]);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
/* child */
|
||||
shname = strrchr(shell, '/');
|
||||
if (shname)
|
||||
shname++;
|
||||
else
|
||||
shname = shell;
|
||||
|
||||
dup2(fds[0], STDIN_FILENO);
|
||||
close(fds[0]);
|
||||
close(fds[1]);
|
||||
|
||||
execl(shell, shname, "-i", NULL);
|
||||
errexec(shell);
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
struct replay_setup *setup = NULL;
|
||||
struct replay_step *step = NULL;
|
||||
const char *log_in = NULL,
|
||||
*log_io = NULL,
|
||||
*log_tm = NULL,
|
||||
*shell;
|
||||
double divi = 1, maxdelay = 0;
|
||||
int diviopt = FALSE, maxdelayopt = FALSE, idx;
|
||||
int ch, rc;
|
||||
int shell_fd;
|
||||
pid_t shell_pid;
|
||||
struct termios attrs;
|
||||
|
||||
static const struct option longopts[] = {
|
||||
{ "timing", required_argument, 0, 't' },
|
||||
{ "log-in", required_argument, 0, 'I'},
|
||||
{ "log-io", required_argument, 0, 'B'},
|
||||
{ "divisor", required_argument, 0, 'd' },
|
||||
{ "maxdelay", required_argument, 0, 'm' },
|
||||
{ "version", no_argument, 0, 'V' },
|
||||
{ "help", no_argument, 0, 'h' },
|
||||
{ NULL, 0, 0, 0 }
|
||||
};
|
||||
static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
|
||||
{ 'B', 'I' },
|
||||
{ 0 }
|
||||
};
|
||||
int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
|
||||
/* Because we use space as a separator, we can't afford to use any
|
||||
* locale which tolerates a space in a number. In any case, script.c
|
||||
* sets the LC_NUMERIC locale to C, anyway.
|
||||
*/
|
||||
setlocale(LC_ALL, "");
|
||||
setlocale(LC_NUMERIC, "C");
|
||||
|
||||
bindtextdomain(PACKAGE, LOCALEDIR);
|
||||
textdomain(PACKAGE);
|
||||
close_stdout_atexit();
|
||||
|
||||
replay_init_debug();
|
||||
|
||||
while ((ch = getopt_long(argc, argv, "B:I:t:d:m:Vh", longopts, NULL)) != -1) {
|
||||
|
||||
err_exclusive_options(ch, longopts, excl, excl_st);
|
||||
|
||||
switch(ch) {
|
||||
case 't':
|
||||
log_tm = optarg;
|
||||
break;
|
||||
case 'I':
|
||||
log_in = optarg;
|
||||
break;
|
||||
case 'B':
|
||||
log_io = optarg;
|
||||
break;
|
||||
case 'd':
|
||||
diviopt = TRUE;
|
||||
divi = getnum(optarg);
|
||||
break;
|
||||
case 'm':
|
||||
maxdelayopt = TRUE;
|
||||
maxdelay = getnum(optarg);
|
||||
break;
|
||||
case 'V':
|
||||
print_version(EXIT_SUCCESS);
|
||||
case 'h':
|
||||
usage();
|
||||
default:
|
||||
errtryhelp(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
idx = 0;
|
||||
|
||||
if (!isatty(STDIN_FILENO))
|
||||
errx(EXIT_FAILURE, _("stdin is not terminal"));
|
||||
|
||||
if (!log_tm && idx < argc)
|
||||
log_tm = argv[idx++];
|
||||
if (!log_in && !log_io && idx < argc)
|
||||
log_in = argv[idx++];
|
||||
|
||||
if (!diviopt)
|
||||
divi = idx < argc ? getnum(argv[idx]) : 1;
|
||||
if (maxdelay < 0)
|
||||
maxdelay = 0;
|
||||
|
||||
if (!log_tm)
|
||||
errx(EXIT_FAILURE, _("timing file not specified"));
|
||||
if (!(log_in || log_io))
|
||||
errx(EXIT_FAILURE, _("stdin typescript file not specified"));
|
||||
|
||||
setup = replay_new_setup();
|
||||
|
||||
if (replay_set_timing_file(setup, log_tm) != 0)
|
||||
err(EXIT_FAILURE, _("cannot open %s"), log_tm);
|
||||
|
||||
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)
|
||||
err(EXIT_FAILURE, _("cannot open %s"), log_io);
|
||||
|
||||
replay_set_default_type(setup, 'I');
|
||||
replay_set_crmode(setup, REPLAY_CRMODE_AUTO);
|
||||
|
||||
shell = getenv("SHELL");
|
||||
if (shell == NULL)
|
||||
shell = _PATH_BSHELL;
|
||||
|
||||
fprintf(stdout, _(">>> scriptlive: Starting your typescript execution by %s. <<<\n"), shell);
|
||||
|
||||
tcgetattr(STDIN_FILENO, &attrs);
|
||||
start_shell(shell, &shell_pid, &shell_fd);
|
||||
|
||||
do {
|
||||
double delay;
|
||||
|
||||
rc = replay_get_next_step(setup, "I", &step);
|
||||
if (rc)
|
||||
break;
|
||||
|
||||
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, shell_fd);
|
||||
} while (rc == 0);
|
||||
|
||||
kill(shell_pid, SIGTERM);
|
||||
waitpid(shell_pid, 0, 0);
|
||||
tcsetattr(STDIN_FILENO, TCSADRAIN, &attrs);
|
||||
|
||||
if (step && rc < 0)
|
||||
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"),
|
||||
replay_get_timing_file(setup),
|
||||
replay_get_timing_line(setup));
|
||||
|
||||
|
||||
fprintf(stdout, _(">>> scriptlive: Done. <<<\n"));
|
||||
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
Loading…
Reference in New Issue