scriptreplay: rewrite in C

The dependence on Perl sucks...

Co-Author: James Youngman <jay@gnu.org>
Signed-off-by: Karel Zak <kzak@redhat.com>
Signed-off-by: James Youngman <jay@gnu.org>
This commit is contained in:
Karel Zak 2008-04-02 11:34:12 +02:00
parent e687632e57
commit 18a706bdfd
5 changed files with 215 additions and 94 deletions

View File

@ -693,7 +693,6 @@ tests/Makefile
tests/helpers/Makefile
tests/commands.sh
misc-utils/chkdupexe:misc-utils/chkdupexe.pl
misc-utils/scriptreplay:misc-utils/scriptreplay.pl
])
AC_OUTPUT

View File

@ -5,14 +5,14 @@ EXTRA_DIST = README.flushb
bin_PROGRAMS =
usrbinexec_PROGRAMS = cal ddate logger look mcookie \
namei script whereis
namei script whereis scriptreplay
EXTRA_DIST += README.cal README.ddate README.namei README.namei2
mcookie_SOURCES = mcookie.c ../lib/md5.c
usrbinexec_SCRIPTS = chkdupexe scriptreplay
usrbinexec_SCRIPTS = chkdupexe
CLEANFILES = chkdupexe scriptreplay
CLEANFILES = chkdupexe
dist_man_MANS = cal.1 chkdupexe.1 ddate.1 logger.1 look.1 mcookie.1 \
namei.1 script.1 whereis.1 scriptreplay.1

View File

@ -149,8 +149,18 @@ scriptreplay timingfile [typescript [divisor]]
.IX Header "DESCRIPTION"
This program replays a typescript, using timing information to ensure that
output happens at the same speed as it originally appeared when the script
was recorded. It is only guaranteed to work properly if run on the same
terminal the script was recorded on.
was recorded.
.PP
The replay simply displays the information again; the programs
that were run when the typescript was being recorded are not run again.
Since the same information is simply being displayed,
.B scriptreplay
is only guaranteed to work properly if run on the same type of
terminal the typescript was recorded on. Otherwise, any escape characters
in the typescript may be interpreted differently by the terminal to
which
.B scriptreplay
is sending its output.
.PP
The timings information is what script outputs to standard error if it is
run with the \-t parameter.
@ -158,8 +168,11 @@ run with the \-t parameter.
By default, the typescript to display is assumed to be named \*(L"typescript\*(R",
but other filenames may be specified, as the second parameter.
.PP
If the third parameter exits, it is used as a time divisor. For example,
specifying a divisor of 2 makes the script be replayed twice as fast.
If the third parameter is specified, it is used as a speed-up multiplier. For
example, a speed-up of 2 makes
.B scriptreplay
go twice as fast and a speed-up of 0.1 makes it go ten times slower
than the original session.
.SH "EXAMPLE"
.IX Header "EXAMPLE"
.Vb 7
@ -176,10 +189,23 @@ specifying a divisor of 2 makes the script be replayed twice as fast.
.BR script (1)
.SH "COPYRIGHT"
.IX Header "COPYRIGHT"
This program is in the public domain.
Copyright \(co 2008 James Youngman
.PP
Copyright \(co 2008 Karel Zak
.PP
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE.
.PP
Released under the GNU General Public License version 2 or later.
.SH "AUTHOR"
.IX Header "AUTHOR"
Joey Hess <joey@kitenet.net>
The original
.B scriptreplay
program was written by Joey Hess <joey@kitenet.net>.
The program was re-written in C by James Youngman <jay@gnu.org> and Karel Zak <kzak@redhat.com>.
.SH AVAILABILITY
The scriptreplay command is part of the util-linux-ng package and is available from
The
.B scriptreplay
command is part of the util-linux-ng package and is available from
ftp://ftp.kernel.org/pub/linux/utils/util-linux-ng/.

179
misc-utils/scriptreplay.c Normal file
View File

@ -0,0 +1,179 @@
/*
* Copyright (C) 2008, Karel Zak <kzak@redhat.com>
* Copyright (C) 2008, James Youngman <jay@gnu.org>
*
* 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.
*
*
* Based on scriptreplay.pl by Joey Hess <joey@kitenet.net>
*/
#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 <err.h>
#include "nls.h"
#define SCRIPT_MIN_DELAY 0.0001 /* from original sripreplay.pl */
void __attribute__((__noreturn__))
usage(int rc)
{
printf("%s <timingfile> [<typescript> [<divisor>]]\n",
program_invocation_short_name);
exit(rc);
}
static double
getnum(const char *s)
{
double d;
char *end;
errno = 0;
d = strtod(s, &end);
if (end && *end != '\0')
errx(EXIT_FAILURE, _("expected a number, but got '%s'"), s);
if ((d == HUGE_VAL || d == -HUGE_VAL) && ERANGE == errno)
err(EXIT_FAILURE, _("divisor '%s'"), s);
if (!(d==d)) { /* did they specify "nan"? */
errno = EINVAL;
err(EXIT_FAILURE, _("divisor '%s'"), 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;
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 void
emit(FILE *fd, const char *filename, size_t ct)
{
char buf[BUFSIZ];
while(ct) {
size_t len, cc;
cc = ct > sizeof(buf) ? sizeof(buf) : ct;
len = fread(buf, 1, cc, fd);
if (!len)
break;
ct -= len;
cc = write(STDOUT_FILENO, buf, len);
if (cc != len)
err(EXIT_FAILURE, "write to stdout failed");
}
if (!ct)
return;
if (feof(fd))
errx(EXIT_FAILURE, _("unexpected end of file on %s"), filename);
err(EXIT_FAILURE, _("failed to read typescript file %s"), filename);
}
int
main(int argc, char *argv[])
{
FILE *tfile, *sfile;
const char *sname, *tname;
double divi;
int c;
unsigned long line;
/* 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);
if (argc < 2 && argc > 4)
usage(EXIT_FAILURE);
tname = argv[1];
sname = argc > 2 ? argv[2] : "typescript";
divi = argc == 4 ? getnum(argv[3]) : 1;
tfile = fopen(tname, "r");
if (!tfile)
err(EXIT_FAILURE, _("cannot open timing file %s"), tname);
sfile = fopen(sname, "r");
if (!sfile)
err(EXIT_FAILURE, _("cannot open typescript file %s"), sname);
/* ignore the first typescript line */
while((c = fgetc(sfile)) != EOF && c != '\n');
for(line = 0; ; line++) {
double delay;
size_t blk;
char nl;
if ((fscanf(tfile, "%lf %zd%[\n]\n", &delay, &blk, &nl) != 3) ||
(nl != '\n')) {
if (feof(tfile))
break;
if (ferror(tfile))
err(EXIT_FAILURE,
"failed to read timing file %s", tname);
errx(EXIT_FAILURE,
_("timings file %s: %lu: expected format"),
tname, line);
}
delay /= divi;
if (delay > SCRIPT_MIN_DELAY)
delay_for(delay);
emit(sfile, sname, blk);
}
fclose(sfile);
fclose(tfile);
exit(EXIT_SUCCESS);
}

View File

@ -1,83 +0,0 @@
#!@PERL@ -w
# "script -t" will output a typescript with timings
# this script "scriptreplay" replays it
# run pod2man on it to get a man page
=head1 NAME
scriptreplay - play back typescripts, using timing information
=head1 SYNOPSIS
scriptreplay timingfile [typescript [divisor]]
=head1 DESCRIPTION
This program replays a typescript, using timing information to ensure that
output happens at the same speed as it originally appeared when the script
was recorded. It is only guaranteed to work properly if run on the same
terminal the script was recorded on.
The timings information is what script outputs to standard error if it is
run with the -t parameter.
By default, the typescript to display is assumed to be named "typescript",
but other filenames may be specified, as the second parameter.
If the third parameter exits, it is used as a time divisor. For example,
specifying a divisor of 2 makes the script be replayed twice as fast.
=head1 EXAMPLE
% script -t 2> timingfile
Script started, file is typescript
% ls
<etc, etc>
% exit
Script done, file is typescript
% scriptreplay timingfile
=cut
use strict;
$|=1;
open (TIMING, shift)
or die "cannot read timing info: $!";
open (TYPESCRIPT, shift || 'typescript')
or die "cannot read typescript: $!";
my $divisor=shift || 1;
# Read starting timestamp line and ignore.
<TYPESCRIPT>;
my $block;
my $oldblock='';
while (<TIMING>) {
my ($delay, $blocksize)=split ' ', $_, 2;
# Sleep, unless the delay is really tiny. Really tiny delays cannot
# be accurately done, because the system calls in this loop will
# have more overhead. The 0.0001 is arbitrary, but works fairly well.
if ($delay / $divisor > 0.0001) {
select(undef, undef, undef, $delay / $divisor - 0.0001);
}
read(TYPESCRIPT, $block, $blocksize)
or die "read failure on typescript: $!";
print $oldblock;
$oldblock=$block;
}
print $oldblock;
=head1 SEE ALSO
script(1)
=head1 COPYRIGHT
This program is in the public domain.
=head1 AUTHOR
Joey Hess <joey@kitenet.net>