From nobody@FreeBSD.org  Mon Jul  9 23:47:37 2007
Return-Path: <nobody@FreeBSD.org>
Received: from mx1.freebsd.org (mx1.freebsd.org [69.147.83.52])
	by hub.freebsd.org (Postfix) with ESMTP id 098A416A400
	for <freebsd-gnats-submit@FreeBSD.org>; Mon,  9 Jul 2007 23:47:37 +0000 (UTC)
	(envelope-from nobody@FreeBSD.org)
Received: from www.freebsd.org (www.freebsd.org [69.147.83.33])
	by mx1.freebsd.org (Postfix) with ESMTP id EE1B413C483
	for <freebsd-gnats-submit@FreeBSD.org>; Mon,  9 Jul 2007 23:47:36 +0000 (UTC)
	(envelope-from nobody@FreeBSD.org)
Received: from www.freebsd.org (localhost [127.0.0.1])
	by www.freebsd.org (8.13.1/8.13.1) with ESMTP id l69NlaqX035060
	for <freebsd-gnats-submit@FreeBSD.org>; Mon, 9 Jul 2007 23:47:36 GMT
	(envelope-from nobody@www.freebsd.org)
Received: (from nobody@localhost)
	by www.freebsd.org (8.13.1/8.13.1/Submit) id l69Nla0L035059;
	Mon, 9 Jul 2007 23:47:36 GMT
	(envelope-from nobody)
Message-Id: <200707092347.l69Nla0L035059@www.freebsd.org>
Date: Mon, 9 Jul 2007 23:47:36 GMT
From: Ighighi <ighighi@gmail.com>
To: freebsd-gnats-submit@FreeBSD.org
Subject: [patch]: script(1): add really cool -d, -p & -r options from NetBSD
X-Send-Pr-Version: www-3.0

>Number:         114465
>Category:       bin
>Synopsis:       [patch] [request] script(1): add really cool -d, -p & -r options from NetBSD
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    brian
>State:          patched
>Quarter:        
>Keywords:       
>Date-Required:  
>Class:          change-request
>Submitter-Id:   current-users
>Arrival-Date:   Mon Jul 09 23:50:03 GMT 2007
>Closed-Date:    
>Last-Modified:  Mon Jul 30 07:20:12 UTC 2012
>Originator:     Ighighi
>Release:        6.2-STABLE
>Organization:
>Environment:
FreeBSD orion 6.2-STABLE FreeBSD 6.2-STABLE #0: Fri Jul  6 04:35:46 VET 2007     root@orion:/usr/obj/usr/src/sys/CUSTOM  i386

>Description:
Inspired by this BSDNews article:
http://www.feyrer.de/NetBSD/bx/blosxom.cgi/nb_20070707_1417.html

Using latest 1.12 version of src/usr.bin/script/script.c available at
http://cvsweb.netbsd.org/bsdweb.cgi/~checkout~/src/usr.bin/script/script.c?rev=1.12


>How-To-Repeat:

>Fix:
Download this patch and apply with
patch -d /usr < /path/to/patch
cd /usr/src/usr.bin/script
make clean obj depend && make && make install


Patch attached with submission follows:

#
# (!c) 2007 by Ighighi
#
# This patch to script(1) adds support for the -d, -p & -r options from NetBSD
# http://cvsweb.netbsd.org/bsdweb.cgi/~checkout~/src/usr.bin/script/script.c?rev=1.12
#

--- src/usr.bin/script/script.c.orig	Sun Feb 15 13:30:13 2004
+++ src/usr.bin/script/script.c	Mon Jul  9 19:22:38 2007
@@ -50,6 +50,9 @@
 #include <sys/stat.h>
 #include <sys/ioctl.h>
 #include <sys/time.h>
+#include <sys/uio.h>
+#include <sys/endian.h>
+#include <sys/param.h>
 
 #include <err.h>
 #include <errno.h>
@@ -63,19 +66,30 @@
 #include <termios.h>
 #include <unistd.h>
 
+#define DEF_BUF 65536
+
+struct stamp {
+	uint64_t scr_len;	/* amount of data */
+	uint64_t scr_sec;	/* time it arrived in seconds... */
+	uint32_t scr_usec;	/* ...and microseconds */
+	uint32_t scr_direction;	/* 'i', 'o', etc (also indicates endianness) */
+};
+
 FILE	*fscript;
 int	master, slave;
 int	child;
 const char *fname;
 int	qflg, ttyflg;
+int	usesleep, rawout;
 
 struct	termios tt;
 
 void	done(int) __dead2;
-void	dooutput(void);
 void	doshell(char **);
 void	fail(void);
 void	finish(void);
+void    record(FILE *, char *, size_t, int);
+void    playback(FILE *);
 static void usage(void);
 
 int
@@ -84,7 +98,7 @@
 	int cc;
 	struct termios rtt, stt;
 	struct winsize win;
-	int aflg, kflg, ch, n;
+	int aflg, kflg, pflg, ch, n;
 	struct timeval tv, *tvp;
 	time_t tvec, start;
 	char obuf[BUFSIZ];
@@ -92,18 +106,30 @@
 	fd_set rfd;
 	int flushtime = 30;
 
-	aflg = kflg = 0;
-	while ((ch = getopt(argc, argv, "aqkt:")) != -1)
+	aflg = kflg = pflg = 0;
+	usesleep = 1;
+	rawout = 0;
+ 
+	while ((ch = getopt(argc, argv, "adkpqrt:")) != -1)
 		switch(ch) {
 		case 'a':
 			aflg = 1;
 			break;
-		case 'q':
-			qflg = 1;
+		case 'd':
+			usesleep = 0;
 			break;
 		case 'k':
 			kflg = 1;
 			break;
+		case 'p':
+			pflg = 1;
+			break;
+		case 'q':
+			qflg = 1;
+			break;
+		case 'r':
+			rawout = 1;
+			break;
 		case 't':
 			flushtime = atoi(optarg);
 			if (flushtime < 0)
@@ -123,9 +149,12 @@
 	} else
 		fname = "typescript";
 
-	if ((fscript = fopen(fname, aflg ? "a" : "w")) == NULL)
+	if ((fscript = fopen(fname, pflg ? "r" : aflg ? "a" : "w")) == NULL)
 		err(1, "%s", fname);
 
+	if (pflg)
+		playback(fscript);
+
 	if (ttyflg = isatty(STDIN_FILENO)) {
 		if (tcgetattr(STDIN_FILENO, &tt) == -1)
 			err(1, "tcgetattr");
@@ -138,10 +167,14 @@
 			err(1, "openpty");
 	}
 
+	if (rawout)
+		record(fscript, NULL, 0, 's');
+
 	if (!qflg) {
 		tvec = time(NULL);
 		(void)printf("Script started, output file is %s\n", fname);
-		(void)fprintf(fscript, "Script started on %s", ctime(&tvec));
+		if (!rawout)
+			(void)fprintf(fscript, "Script started on %s", ctime(&tvec));
 		fflush(fscript);
 	}
 	if (ttyflg) {
@@ -183,6 +216,8 @@
 			if (cc == 0)
 				(void)write(master, ibuf, 0);
 			if (cc > 0) {
+				if (rawout)
+					record(fscript, ibuf, cc, 'i');
 				(void)write(master, ibuf, cc);
 				if (kflg && tcgetattr(master, &stt) >= 0 &&
 				    ((stt.c_lflag & ECHO) == 0)) {
@@ -195,7 +230,10 @@
 			if (cc <= 0)
 				break;
 			(void)write(STDOUT_FILENO, obuf, cc);
-			(void)fwrite(obuf, 1, cc, fscript);
+			if (rawout)
+				record(fscript, obuf, cc, 'o');
+			else
+				(void)fwrite(obuf, 1, cc, fscript);
 		}
 		tvec = time(0);
 		if (tvec - start >= flushtime) {
@@ -274,11 +312,114 @@
 	if (ttyflg)
 		(void)tcsetattr(STDIN_FILENO, TCSAFLUSH, &tt);
 	tvec = time(NULL);
+	if (rawout)
+		record(fscript, NULL, 0, 'e');
 	if (!qflg) {
-		(void)fprintf(fscript,"\nScript done on %s", ctime(&tvec));
+		if (!rawout)
+			(void)fprintf(fscript,"\nScript done on %s", ctime(&tvec));
 		(void)printf("\nScript done, output file is %s\n", fname);
 	}
 	(void)fclose(fscript);
 	(void)close(master);
 	exit(eno);
+}
+
+
+void
+record(FILE *fscript, char *buf, size_t cc, int direction)
+{
+	struct iovec iov[2];
+	struct stamp stamp;
+	struct timeval tv;
+
+	(void)gettimeofday(&tv, NULL);
+	stamp.scr_len = cc;
+	stamp.scr_sec = tv.tv_sec;
+	stamp.scr_usec = tv.tv_usec;
+	stamp.scr_direction = direction;
+	iov[0].iov_len = sizeof(stamp);
+	iov[0].iov_base = &stamp;
+	iov[1].iov_len = cc;
+	iov[1].iov_base = buf;
+	if (writev(fileno(fscript), &iov[0], 2) == -1)
+		err(1, "writev");
+}
+
+#define swapstamp(stamp) do { \
+	if (stamp.scr_direction > 0xff) { \
+		stamp.scr_len = bswap64(stamp.scr_len); \
+		stamp.scr_sec = bswap64(stamp.scr_sec); \
+		stamp.scr_usec = bswap32(stamp.scr_usec); \
+		stamp.scr_direction = bswap32(stamp.scr_direction); \
+	} \
+} while (0/*CONSTCOND*/)
+
+void
+playback(FILE *fscript)
+{
+	struct timespec tsi, tso;
+	struct stamp stamp;
+	struct stat playback_stat;
+	char buf[DEF_BUF];
+	off_t nread, save_len;
+	size_t l;
+	time_t clock;
+
+	if (fstat(fileno(fscript), &playback_stat) == -1)
+		err(1, "fstat failed");	
+
+	for (nread = 0; nread < playback_stat.st_size; nread += save_len) {
+		if (fread(&stamp, sizeof(stamp), 1, fscript) != 1)
+			err(1, "reading playback header");
+		swapstamp(stamp);
+		save_len = sizeof(stamp);
+
+		if (stamp.scr_len >
+		    (uint64_t)(playback_stat.st_size - save_len) - nread)
+			err(1, "invalid stamp");
+
+		save_len += stamp.scr_len;
+		clock = stamp.scr_sec;
+		tso.tv_sec = stamp.scr_sec;
+		tso.tv_nsec = stamp.scr_usec * 1000;
+
+		switch (stamp.scr_direction) {
+		case 's':
+			(void)printf("Script started on %s", ctime(&clock));
+			tsi = tso;
+			fseek(fscript, stamp.scr_len, SEEK_CUR);
+			break;
+		case 'e':
+			(void)printf("\nScript done on %s", ctime(&clock));
+			fseek(fscript, stamp.scr_len, SEEK_CUR);
+			break;
+		case 'i':
+			/* throw input away */
+			fseek(fscript, stamp.scr_len, SEEK_CUR);
+			break;
+		case 'o':
+			tsi.tv_sec = tso.tv_sec - tsi.tv_sec;
+			tsi.tv_nsec = tso.tv_nsec - tsi.tv_nsec;
+			if (tsi.tv_nsec < 0) {
+				tsi.tv_sec -= 1;
+				tsi.tv_nsec += 1000000000;
+			}
+			if (usesleep)
+				(void)nanosleep(&tsi, NULL);
+			tsi = tso;
+			while (stamp.scr_len > 0) {
+				l = MIN(DEF_BUF, stamp.scr_len);
+				if (fread(buf, sizeof(char), l, fscript) != l)
+					err(1, "cannot read buffer");
+
+				(void)write(STDOUT_FILENO, buf, l);
+				stamp.scr_len -= l;
+			}
+			break;
+		default:
+			err(1, "invalid direction");
+		}
+	}
+	(void)fclose(fscript);
+	exit(0);
 }
--- src/usr.bin/script/script.1.orig	Fri Jul  2 20:24:43 2004
+++ src/usr.bin/script/script.1	Mon Jul  9 19:25:22 2007
@@ -40,7 +40,7 @@
 .Nd make typescript of terminal session
 .Sh SYNOPSIS
 .Nm
-.Op Fl akq
+.Op Fl adkpqr
 .Op Fl t Ar time
 .Op Ar file Op Ar command ...
 .Sh DESCRIPTION
@@ -76,10 +76,16 @@
 or
 .Pa typescript ,
 retaining the prior contents.
+.It Fl d
+Don't sleep between records when playing back a timestamped session.
 .It Fl k
 Log keys sent to program as well as output.
+.It Fl p
+Play back a recorded session in real time.
 .It Fl q
 Run in quiet mode, omit the start and stop status messages.
+.It Fl r
+Record a session with input, output, and timestamping.
 .It Fl t Ar time
 Specify time interval between flushing script output file.
 A value of 0
@@ -139,6 +145,13 @@
 .Nm
 command appeared in
 .Bx 3.0 .
+.Pp
+The
+.Fl d ,
+.Fl p
+and
+.Fl r
+options were imported from NetBSD.
 .Sh BUGS
 The
 .Nm


>Release-Note:
>Audit-Trail:

From: Ighighi <ighighi@gmail.com>
To: bug-followup@freebsd.org
Cc:  
Subject: Re: bin/114465: [patch]: script(1): add really cool -d, -p & -r options
 from NetBSD
Date: Sat, 18 Aug 2007 18:45:58 -0400

 This is a multi-part message in MIME format.
 --------------040209040109040009020001
 Content-Type: text/plain; charset=ISO-8859-1; format=flowed
 Content-Transfer-Encoding: 7bit
 
 This new patch uses fseeko() instead of fseek().
 I must notify the original NetBSD developers as fseek() uses long, not 
 off_t.
 
 --------------040209040109040009020001
 Content-Type: text/x-patch;
  name="script.patch"
 Content-Transfer-Encoding: 7bit
 Content-Disposition: inline;
  filename="script.patch"
 
 #
 # (!c) 2007 by Ighighi
 #
 # This patch to script(1) adds support for the -d, -p & -r options from NetBSD
 # http://cvsweb.netbsd.org/bsdweb.cgi/~checkout~/src/usr.bin/script/script.c?rev=1.12
 #
 # See http://www.freebsd.org/cgi/query-pr.cgi?pr=bin/114465
 #
 
 --- src/usr.bin/script/script.c.orig	Sun Feb 15 13:30:13 2004
 +++ src/usr.bin/script/script.c	Mon Jul  9 19:22:38 2007
 @@ -50,6 +50,9 @@
  #include <sys/stat.h>
  #include <sys/ioctl.h>
  #include <sys/time.h>
 +#include <sys/uio.h>
 +#include <sys/endian.h>
 +#include <sys/param.h>
  
  #include <err.h>
  #include <errno.h>
 @@ -63,19 +66,30 @@
  #include <termios.h>
  #include <unistd.h>
  
 +#define DEF_BUF 65536
 +
 +struct stamp {
 +	uint64_t scr_len;	/* amount of data */
 +	uint64_t scr_sec;	/* time it arrived in seconds... */
 +	uint32_t scr_usec;	/* ...and microseconds */
 +	uint32_t scr_direction;	/* 'i', 'o', etc (also indicates endianness) */
 +};
 +
  FILE	*fscript;
  int	master, slave;
  int	child;
  const char *fname;
  int	qflg, ttyflg;
 +int	usesleep, rawout;
  
  struct	termios tt;
  
  void	done(int) __dead2;
 -void	dooutput(void);
  void	doshell(char **);
  void	fail(void);
  void	finish(void);
 +void    record(FILE *, char *, size_t, int);
 +void    playback(FILE *);
  static void usage(void);
  
  int
 @@ -84,7 +98,7 @@
  	int cc;
  	struct termios rtt, stt;
  	struct winsize win;
 -	int aflg, kflg, ch, n;
 +	int aflg, kflg, pflg, ch, n;
  	struct timeval tv, *tvp;
  	time_t tvec, start;
  	char obuf[BUFSIZ];
 @@ -92,18 +106,30 @@
  	fd_set rfd;
  	int flushtime = 30;
  
 -	aflg = kflg = 0;
 -	while ((ch = getopt(argc, argv, "aqkt:")) != -1)
 +	aflg = kflg = pflg = 0;
 +	usesleep = 1;
 +	rawout = 0;
 + 
 +	while ((ch = getopt(argc, argv, "adkpqrt:")) != -1)
  		switch(ch) {
  		case 'a':
  			aflg = 1;
  			break;
 -		case 'q':
 -			qflg = 1;
 +		case 'd':
 +			usesleep = 0;
  			break;
  		case 'k':
  			kflg = 1;
  			break;
 +		case 'p':
 +			pflg = 1;
 +			break;
 +		case 'q':
 +			qflg = 1;
 +			break;
 +		case 'r':
 +			rawout = 1;
 +			break;
  		case 't':
  			flushtime = atoi(optarg);
  			if (flushtime < 0)
 @@ -123,9 +149,12 @@
  	} else
  		fname = "typescript";
  
 -	if ((fscript = fopen(fname, aflg ? "a" : "w")) == NULL)
 +	if ((fscript = fopen(fname, pflg ? "r" : aflg ? "a" : "w")) == NULL)
  		err(1, "%s", fname);
  
 +	if (pflg)
 +		playback(fscript);
 +
  	if (ttyflg = isatty(STDIN_FILENO)) {
  		if (tcgetattr(STDIN_FILENO, &tt) == -1)
  			err(1, "tcgetattr");
 @@ -138,10 +167,14 @@
  			err(1, "openpty");
  	}
  
 +	if (rawout)
 +		record(fscript, NULL, 0, 's');
 +
  	if (!qflg) {
  		tvec = time(NULL);
  		(void)printf("Script started, output file is %s\n", fname);
 -		(void)fprintf(fscript, "Script started on %s", ctime(&tvec));
 +		if (!rawout)
 +			(void)fprintf(fscript, "Script started on %s", ctime(&tvec));
  		fflush(fscript);
  	}
  	if (ttyflg) {
 @@ -183,6 +216,8 @@
  			if (cc == 0)
  				(void)write(master, ibuf, 0);
  			if (cc > 0) {
 +				if (rawout)
 +					record(fscript, ibuf, cc, 'i');
  				(void)write(master, ibuf, cc);
  				if (kflg && tcgetattr(master, &stt) >= 0 &&
  				    ((stt.c_lflag & ECHO) == 0)) {
 @@ -195,7 +230,10 @@
  			if (cc <= 0)
  				break;
  			(void)write(STDOUT_FILENO, obuf, cc);
 -			(void)fwrite(obuf, 1, cc, fscript);
 +			if (rawout)
 +				record(fscript, obuf, cc, 'o');
 +			else
 +				(void)fwrite(obuf, 1, cc, fscript);
  		}
  		tvec = time(0);
  		if (tvec - start >= flushtime) {
 @@ -274,11 +312,114 @@
  	if (ttyflg)
  		(void)tcsetattr(STDIN_FILENO, TCSAFLUSH, &tt);
  	tvec = time(NULL);
 +	if (rawout)
 +		record(fscript, NULL, 0, 'e');
  	if (!qflg) {
 -		(void)fprintf(fscript,"\nScript done on %s", ctime(&tvec));
 +		if (!rawout)
 +			(void)fprintf(fscript,"\nScript done on %s", ctime(&tvec));
  		(void)printf("\nScript done, output file is %s\n", fname);
  	}
  	(void)fclose(fscript);
  	(void)close(master);
  	exit(eno);
 +}
 +
 +
 +void
 +record(FILE *fscript, char *buf, size_t cc, int direction)
 +{
 +	struct iovec iov[2];
 +	struct stamp stamp;
 +	struct timeval tv;
 +
 +	(void)gettimeofday(&tv, NULL);
 +	stamp.scr_len = cc;
 +	stamp.scr_sec = tv.tv_sec;
 +	stamp.scr_usec = tv.tv_usec;
 +	stamp.scr_direction = direction;
 +	iov[0].iov_len = sizeof(stamp);
 +	iov[0].iov_base = &stamp;
 +	iov[1].iov_len = cc;
 +	iov[1].iov_base = buf;
 +	if (writev(fileno(fscript), &iov[0], 2) == -1)
 +		err(1, "writev");
 +}
 +
 +#define swapstamp(stamp) do { \
 +	if (stamp.scr_direction > 0xff) { \
 +		stamp.scr_len = bswap64(stamp.scr_len); \
 +		stamp.scr_sec = bswap64(stamp.scr_sec); \
 +		stamp.scr_usec = bswap32(stamp.scr_usec); \
 +		stamp.scr_direction = bswap32(stamp.scr_direction); \
 +	} \
 +} while (0/*CONSTCOND*/)
 +
 +void
 +playback(FILE *fscript)
 +{
 +	struct timespec tsi, tso;
 +	struct stamp stamp;
 +	struct stat playback_stat;
 +	char buf[DEF_BUF];
 +	off_t nread, save_len;
 +	size_t l;
 +	time_t clock;
 +
 +	if (fstat(fileno(fscript), &playback_stat) == -1)
 +		err(1, "fstat failed");	
 +
 +	for (nread = 0; nread < playback_stat.st_size; nread += save_len) {
 +		if (fread(&stamp, sizeof(stamp), 1, fscript) != 1)
 +			err(1, "reading playback header");
 +		swapstamp(stamp);
 +		save_len = sizeof(stamp);
 +
 +		if (stamp.scr_len >
 +		    (uint64_t)(playback_stat.st_size - save_len) - nread)
 +			err(1, "invalid stamp");
 +
 +		save_len += stamp.scr_len;
 +		clock = stamp.scr_sec;
 +		tso.tv_sec = stamp.scr_sec;
 +		tso.tv_nsec = stamp.scr_usec * 1000;
 +
 +		switch (stamp.scr_direction) {
 +		case 's':
 +			(void)printf("Script started on %s", ctime(&clock));
 +			tsi = tso;
 +			fseeko(fscript, stamp.scr_len, SEEK_CUR);
 +			break;
 +		case 'e':
 +			(void)printf("\nScript done on %s", ctime(&clock));
 +			fseeko(fscript, stamp.scr_len, SEEK_CUR);
 +			break;
 +		case 'i':
 +			/* throw input away */
 +			fseeko(fscript, stamp.scr_len, SEEK_CUR);
 +			break;
 +		case 'o':
 +			tsi.tv_sec = tso.tv_sec - tsi.tv_sec;
 +			tsi.tv_nsec = tso.tv_nsec - tsi.tv_nsec;
 +			if (tsi.tv_nsec < 0) {
 +				tsi.tv_sec -= 1;
 +				tsi.tv_nsec += 1000000000;
 +			}
 +			if (usesleep)
 +				(void)nanosleep(&tsi, NULL);
 +			tsi = tso;
 +			while (stamp.scr_len > 0) {
 +				l = MIN(DEF_BUF, stamp.scr_len);
 +				if (fread(buf, sizeof(char), l, fscript) != l)
 +					err(1, "cannot read buffer");
 +
 +				(void)write(STDOUT_FILENO, buf, l);
 +				stamp.scr_len -= l;
 +			}
 +			break;
 +		default:
 +			err(1, "invalid direction");
 +		}
 +	}
 +	(void)fclose(fscript);
 +	exit(0);
  }
 --- src/usr.bin/script/script.1.orig	Fri Jul  2 20:24:43 2004
 +++ src/usr.bin/script/script.1	Mon Jul  9 19:25:22 2007
 @@ -40,7 +40,7 @@
  .Nd make typescript of terminal session
  .Sh SYNOPSIS
  .Nm
 -.Op Fl akq
 +.Op Fl adkpqr
  .Op Fl t Ar time
  .Op Ar file Op Ar command ...
  .Sh DESCRIPTION
 @@ -76,10 +76,16 @@
  or
  .Pa typescript ,
  retaining the prior contents.
 +.It Fl d
 +Don't sleep between records when playing back a timestamped session.
  .It Fl k
  Log keys sent to program as well as output.
 +.It Fl p
 +Play back a recorded session in real time.
  .It Fl q
  Run in quiet mode, omit the start and stop status messages.
 +.It Fl r
 +Record a session with input, output, and timestamping.
  .It Fl t Ar time
  Specify time interval between flushing script output file.
  A value of 0
 @@ -139,6 +145,13 @@
  .Nm
  command appeared in
  .Bx 3.0 .
 +.Pp
 +The
 +.Fl d ,
 +.Fl p
 +and
 +.Fl r
 +options were imported from NetBSD.
  .Sh BUGS
  The
  .Nm
 
 --------------040209040109040009020001--

From: Anonymous <swell.k@gmail.com>
To: Ighighi <ighighi@gmail.com>
Cc: bug-followup@FreeBSD.org
Subject: Re: bin/114465: [patch] [request] script(1): add really cool -d, -p & -r options from NetBSD
Date: Sat, 18 Dec 2010 10:01:49 +0300

 The patch no longer applies cleanly against /head. Manual apply
 results in error when trying to play its recorded session
 
   $ script -p my.session
   Script started on Sat Dec 18 09:59:24 2010
   script: invalid stamp: No such file or directory
   Exit 1
 
 However, sessions recorded by script(1) from NetBSD play fine, e.g.
 
   http://www.feyrer.de/NetBSD/script-session.blogpost
 
 Note, -Wshadow warnings during compilation were fixed in r1.17.

From: Anonymous <swell.k@gmail.com>
To: Ighighi <ighighi@gmail.com>
Cc: bug-followup@FreeBSD.org
Subject: Re: bin/114465: [patch] [request] script(1): add really cool -d, -p & -r options from NetBSD
Date: Mon, 20 Dec 2010 10:43:25 +0300

 Anonymous <swell.k@gmail.com> writes:
 
 > The patch no longer applies cleanly against /head. Manual apply
 > results in error when trying to play its recorded session
 >
 >   $ script -p my.session
 >   Script started on Sat Dec 18 09:59:24 2010
 >   script: invalid stamp: No such file or directory
 >   Exit 1
 
 It's caused by r212770 which spams `file' with `command' and its args in
 
   script [file [command ...]]
 
 Here's a regen'd diff against script.c@r1.17 with the *argv[] spamming
 change commented out atm.
 
 --- script_replay.diff begins here ---
 Index: usr.bin/script/script.1
 ===================================================================
 --- usr.bin/script/script.1	(revision 216581)
 +++ usr.bin/script/script.1	(working copy)
 @@ -36,7 +36,7 @@
  .Nd make typescript of terminal session
  .Sh SYNOPSIS
  .Nm
 -.Op Fl akq
 +.Op Fl adkpqr
  .Op Fl t Ar time
  .Op Ar file Op Ar command ...
  .Sh DESCRIPTION
 @@ -72,10 +72,16 @@
  or
  .Pa typescript ,
  retaining the prior contents.
 +.It Fl d
 +Don't sleep between records when playing back a timestamped session.
  .It Fl k
  Log keys sent to program as well as output.
 +.It Fl p
 +Play back a recorded session in real time.
  .It Fl q
  Run in quiet mode, omit the start and stop status messages.
 +.It Fl r
 +Record a session with input, output, and timestamping.
  .It Fl t Ar time
  Specify time interval between flushing script output file.
  A value of 0
 @@ -148,6 +154,13 @@
  .Nm
  command appeared in
  .Bx 3.0 .
 +.Pp
 +The
 +.Fl d ,
 +.Fl p
 +and
 +.Fl r
 +options were imported from NetBSD.
  .Sh BUGS
  The
  .Nm
 Index: usr.bin/script/script.c
 ===================================================================
 --- usr.bin/script/script.c	(revision 216581)
 +++ usr.bin/script/script.c	(working copy)
 @@ -46,6 +46,9 @@
  #include <sys/stat.h>
  #include <sys/ioctl.h>
  #include <sys/time.h>
 +#include <sys/uio.h>
 +#include <sys/endian.h>
 +#include <sys/param.h>
  
  #include <err.h>
  #include <errno.h>
 @@ -59,11 +62,21 @@
  #include <termios.h>
  #include <unistd.h>
  
 +#define DEF_BUF 65536
 +
 +static struct stamp {
 +	uint64_t scr_len;	/* amount of data */
 +	uint64_t scr_sec;	/* time it arrived in seconds... */
 +	uint32_t scr_usec;	/* ...and microseconds */
 +	uint32_t scr_direction;	/* 'i', 'o', etc (also indicates endianness) */
 +};
 +
  static FILE *fscript;
  static int master, slave;
  static int child;
  static const char *fname;
  static int qflg, ttyflg;
 +static int usesleep, rawout;
  
  static struct termios tt;
  
 @@ -71,6 +84,9 @@
  static void doshell(char **);
  static void fail(void);
  static void finish(void);
 +static void record(FILE *, char *, size_t, int);
 +static void consume(FILE *, off_t, char *, int);
 +static void playback(FILE *);
  static void usage(void);
  
  int
 @@ -79,7 +95,7 @@
  	int cc;
  	struct termios rtt, stt;
  	struct winsize win;
 -	int aflg, kflg, ch, n;
 +	int aflg, kflg, pflg, ch, n;
  	struct timeval tv, *tvp;
  	time_t tvec, start;
  	char obuf[BUFSIZ];
 @@ -87,18 +103,30 @@
  	fd_set rfd;
  	int flushtime = 30;
  
 -	aflg = kflg = 0;
 -	while ((ch = getopt(argc, argv, "aqkt:")) != -1)
 +	aflg = kflg = pflg = 0;
 +	usesleep = 1;
 +	rawout = 0;
 + 
 +	while ((ch = getopt(argc, argv, "adkpqrt:")) != -1)
  		switch(ch) {
  		case 'a':
  			aflg = 1;
  			break;
 -		case 'q':
 -			qflg = 1;
 +		case 'd':
 +			usesleep = 0;
  			break;
  		case 'k':
  			kflg = 1;
  			break;
 +		case 'p':
 +			pflg = 1;
 +			break;
 +		case 'q':
 +			qflg = 1;
 +			break;
 +		case 'r':
 +			rawout = 1;
 +			break;
  		case 't':
  			flushtime = atoi(optarg);
  			if (flushtime < 0)
 @@ -118,9 +146,12 @@
  	} else
  		fname = "typescript";
  
 -	if ((fscript = fopen(fname, aflg ? "a" : "w")) == NULL)
 +	if ((fscript = fopen(fname, pflg ? "r" : aflg ? "a" : "w")) == NULL)
  		err(1, "%s", fname);
  
 +	if (pflg)
 +		playback(fscript);
 +
  	if ((ttyflg = isatty(STDIN_FILENO)) != 0) {
  		if (tcgetattr(STDIN_FILENO, &tt) == -1)
  			err(1, "tcgetattr");
 @@ -133,10 +164,14 @@
  			err(1, "openpty");
  	}
  
 +	if (rawout)
 +		record(fscript, NULL, 0, 's');
 +
  	if (!qflg) {
  		tvec = time(NULL);
  		(void)printf("Script started, output file is %s\n", fname);
 -		(void)fprintf(fscript, "Script started on %s", ctime(&tvec));
 +		if (!rawout)
 +			(void)fprintf(fscript, "Script started on %s", ctime(&tvec));
  		fflush(fscript);
  	}
  	if (ttyflg) {
 @@ -179,6 +214,8 @@
  			if (cc == 0)
  				(void)write(master, ibuf, 0);
  			if (cc > 0) {
 +				if (rawout)
 +					record(fscript, ibuf, cc, 'i');
  				(void)write(master, ibuf, cc);
  				if (kflg && tcgetattr(master, &stt) >= 0 &&
  				    ((stt.c_lflag & ECHO) == 0)) {
 @@ -191,7 +228,10 @@
  			if (cc <= 0)
  				break;
  			(void)write(STDOUT_FILENO, obuf, cc);
 -			(void)fwrite(obuf, 1, cc, fscript);
 +			if (rawout)
 +				record(fscript, obuf, cc, 'o');
 +			else
 +				(void)fwrite(obuf, 1, cc, fscript);
  		}
  		tvec = time(0);
  		if (tvec - start >= flushtime) {
 @@ -237,10 +277,12 @@
  	if (shell == NULL)
  		shell = _PATH_BSHELL;
  
 +#if 0
  	if (av[0])
  		for (k = 0 ; av[k] ; ++k)
  			fprintf(fscript, "%s%s", k ? " " : "", av[k]);
  		fprintf(fscript, "\r\n");
 +#endif
  
  	(void)close(master);
  	(void)fclose(fscript);
 @@ -271,11 +313,140 @@
  	if (ttyflg)
  		(void)tcsetattr(STDIN_FILENO, TCSAFLUSH, &tt);
  	tvec = time(NULL);
 +	if (rawout)
 +		record(fscript, NULL, 0, 'e');
  	if (!qflg) {
 -		(void)fprintf(fscript,"\nScript done on %s", ctime(&tvec));
 +		if (!rawout)
 +			(void)fprintf(fscript,"\nScript done on %s", ctime(&tvec));
  		(void)printf("\nScript done, output file is %s\n", fname);
  	}
  	(void)fclose(fscript);
  	(void)close(master);
  	exit(eno);
  }
 +
 +
 +static void
 +record(FILE *fp, char *buf, size_t cc, int direction)
 +{
 +	struct iovec iov[2];
 +	struct stamp stamp;
 +	struct timeval tv;
 +
 +	(void)gettimeofday(&tv, NULL);
 +	stamp.scr_len = cc;
 +	stamp.scr_sec = tv.tv_sec;
 +	stamp.scr_usec = tv.tv_usec;
 +	stamp.scr_direction = direction;
 +	iov[0].iov_len = sizeof(stamp);
 +	iov[0].iov_base = &stamp;
 +	iov[1].iov_len = cc;
 +	iov[1].iov_base = buf;
 +	if (writev(fileno(fp), &iov[0], 2) == -1)
 +		err(1, "writev");
 +}
 +
 +void
 +consume(FILE *fp, off_t len, char *buf, int reg)
 +{
 +	size_t l;
 +
 +	if (reg) {
 +		if (fseeko(fp, len, SEEK_CUR) == -1)
 +			err(1, NULL);
 +	}
 +	else {
 +		while (len > 0) {
 +			l = MIN(DEF_BUF, len);
 +			if (fread(buf, sizeof(char), l, fp) != l)
 +				err(1, "cannot read buffer");
 +			len -= l;
 +		}
 +	}
 +}
 +
 +#define swapstamp(stamp) do { \
 +	if (stamp.scr_direction > 0xff) { \
 +		stamp.scr_len = bswap64(stamp.scr_len); \
 +		stamp.scr_sec = bswap64(stamp.scr_sec); \
 +		stamp.scr_usec = bswap32(stamp.scr_usec); \
 +		stamp.scr_direction = bswap32(stamp.scr_direction); \
 +	} \
 +} while (0/*CONSTCOND*/)
 +
 +static void
 +playback(FILE *fp)
 +{
 +	struct timespec tsi, tso;
 +	struct stamp stamp;
 +	struct stat pst;
 +	char buf[DEF_BUF];
 +	off_t nread, save_len;
 +	size_t l;
 +	time_t tclock;
 +	int reg;
 +
 +	if (fstat(fileno(fp), &pst) == -1)
 +		err(1, "fstat failed");	
 +
 +	reg = S_ISREG(pst.st_mode);
 +
 +	for (nread = 0; !reg || nread < pst.st_size; nread += save_len) {
 +		if (fread(&stamp, sizeof(stamp), 1, fp) != 1) {
 +			if (reg)
 +				err(1, "reading playback header");
 +			else
 +				break;
 +		}
 +		swapstamp(stamp);
 +		save_len = sizeof(stamp);
 +
 +		if (reg && stamp.scr_len >
 +		    (uint64_t)(pst.st_size - save_len) - nread)
 +			err(1, "invalid stamp");
 +
 +		save_len += stamp.scr_len;
 +		tclock = stamp.scr_sec;
 +		tso.tv_sec = stamp.scr_sec;
 +		tso.tv_nsec = stamp.scr_usec * 1000;
 +
 +		switch (stamp.scr_direction) {
 +		case 's':
 +			(void)printf("Script started on %s", ctime(&tclock));
 +			tsi = tso;
 +			(void)consume(fp, stamp.scr_len, buf, reg);
 +			break;
 +		case 'e':
 +			(void)printf("\nScript done on %s", ctime(&tclock));
 +			(void)consume(fp, stamp.scr_len, buf, reg);
 +			break;
 +		case 'i':
 +			/* throw input away */
 +			(void)consume(fp, stamp.scr_len, buf, reg);
 +			break;
 +		case 'o':
 +			tsi.tv_sec = tso.tv_sec - tsi.tv_sec;
 +			tsi.tv_nsec = tso.tv_nsec - tsi.tv_nsec;
 +			if (tsi.tv_nsec < 0) {
 +				tsi.tv_sec -= 1;
 +				tsi.tv_nsec += 1000000000;
 +			}
 +			if (usesleep)
 +				(void)nanosleep(&tsi, NULL);
 +			tsi = tso;
 +			while (stamp.scr_len > 0) {
 +				l = MIN(DEF_BUF, stamp.scr_len);
 +				if (fread(buf, sizeof(char), l, fp) != l)
 +					err(1, "cannot read buffer");
 +
 +				(void)write(STDOUT_FILENO, buf, l);
 +				stamp.scr_len -= l;
 +			}
 +			break;
 +		default:
 +			err(1, "invalid direction");
 +		}
 +	}
 +	(void)fclose(fp);
 +	exit(0);
 +}
 --- script_replay.diff ends here ---

From: Anonymous <swell.k@gmail.com>
To: Ighighi <ighighi@gmail.com>
Cc: bug-followup@FreeBSD.org
Subject: Re: bin/114465: [patch] [request] script(1): add really cool -d, -p & -r options from NetBSD
Date: Mon, 20 Dec 2010 18:11:37 +0300

 Anonymous <swell.k@gmail.com> writes:
 
 > Here's a regen'd diff against script.c@r1.17 with the *argv[] spamming
 > change commented out atm.
 
 And here's a fix that works with r212770.
 
 --- script_replay.1.diff begins here ---
 --- usr.bin/script/script.1~
 +++ usr.bin/script/script.1
 @@ -79,7 +79,7 @@ Log keys sent to program as well as outp
  .It Fl p
  Play back a recorded session in real time.
  .It Fl q
 -Run in quiet mode, omit the start and stop status messages.
 +Run in quiet mode, omit the start, stop and command status messages.
  .It Fl r
  Record a session with input, output, and timestamping.
  .It Fl t Ar time
 --- usr.bin/script/script.c~
 +++ usr.bin/script/script.c
 @@ -64,7 +64,7 @@ static const char sccsid[] = "@(#)script
  
  #define DEF_BUF 65536
  
 -static struct stamp {
 +struct stamp {
  	uint64_t scr_len;	/* amount of data */
  	uint64_t scr_sec;	/* time it arrived in seconds... */
  	uint32_t scr_usec;	/* ...and microseconds */
 @@ -102,6 +102,7 @@ main(int argc, char *argv[])
  	char ibuf[BUFSIZ];
  	fd_set rfd;
  	int flushtime = 30;
 +	int k;
  
  	aflg = kflg = pflg = 0;
  	usesleep = 1;
 @@ -170,8 +171,14 @@ main(int argc, char *argv[])
  	if (!qflg) {
  		tvec = time(NULL);
  		(void)printf("Script started, output file is %s\n", fname);
 -		if (!rawout)
 +		if (!rawout) {
  			(void)fprintf(fscript, "Script started on %s", ctime(&tvec));
 +			if (argv[0])
 +				fprintf(fscript, "command: ");
 +			for (k = 0 ; argv[k] ; ++k)
 +				fprintf(fscript, "%s%s", k ? " " : "", argv[k]);
 +			fprintf(fscript, "\n");
 +		}
  		fflush(fscript);
  	}
  	if (ttyflg) {
 @@ -271,19 +278,11 @@ static void
  doshell(char **av)
  {
  	const char *shell;
 -	int k;
  
  	shell = getenv("SHELL");
  	if (shell == NULL)
  		shell = _PATH_BSHELL;
  
 -#if 0
 -	if (av[0])
 -		for (k = 0 ; av[k] ; ++k)
 -			fprintf(fscript, "%s%s", k ? " " : "", av[k]);
 -		fprintf(fscript, "\r\n");
 -#endif
 -
  	(void)close(master);
  	(void)fclose(fscript);
  	login_tty(slave);
 @@ -346,7 +345,7 @@ record(FILE *fp, char *buf, size_t cc, i
  		err(1, "writev");
  }
  
 -void
 +static void
  consume(FILE *fp, off_t len, char *buf, int reg)
  {
  	size_t l;
 @@ -412,11 +411,13 @@ playback(FILE *fp)
  
  		switch (stamp.scr_direction) {
  		case 's':
 +			if (!qflg)
  			(void)printf("Script started on %s", ctime(&tclock));
  			tsi = tso;
  			(void)consume(fp, stamp.scr_len, buf, reg);
  			break;
  		case 'e':
 +			if (!qflg)
  			(void)printf("\nScript done on %s", ctime(&tclock));
  			(void)consume(fp, stamp.scr_len, buf, reg);
  			break;
 --- script_replay.1.diff ends here ---

From: Anonymous <swell.k@gmail.com>
To: bug-followup@FreeBSD.org
Cc:  
Subject: Re: bin/114465: [patch] [request] script(1): add really cool -d, -p & -r options from NetBSD
Date: Mon, 20 Dec 2010 19:28:15 +0300

 Anonymous <swell.k@gmail.com> writes:
 
 > +			if (argv[0])
 > +				fprintf(fscript, "command: ");
 > +			for (k = 0 ; argv[k] ; ++k)
 > +				fprintf(fscript, "%s%s", k ? " " : "", argv[k]);
 > +			fprintf(fscript, "\n");
 
 I've sent wrong patch, the one without braces.
 
 --- script_replay.diff begins here ---
 Index: usr.bin/script/script.1
 ===================================================================
 --- usr.bin/script/script.1	(revision 216591)
 +++ usr.bin/script/script.1	(working copy)
 @@ -36,7 +36,7 @@
  .Nd make typescript of terminal session
  .Sh SYNOPSIS
  .Nm
 -.Op Fl akq
 +.Op Fl adkpqr
  .Op Fl t Ar time
  .Op Ar file Op Ar command ...
  .Sh DESCRIPTION
 @@ -72,10 +72,16 @@
  or
  .Pa typescript ,
  retaining the prior contents.
 +.It Fl d
 +Don't sleep between records when playing back a timestamped session.
  .It Fl k
  Log keys sent to program as well as output.
 +.It Fl p
 +Play back a recorded session in real time.
  .It Fl q
 -Run in quiet mode, omit the start and stop status messages.
 +Run in quiet mode, omit the start, stop and command status messages.
 +.It Fl r
 +Record a session with input, output, and timestamping.
  .It Fl t Ar time
  Specify time interval between flushing script output file.
  A value of 0
 @@ -148,6 +154,13 @@
  .Nm
  command appeared in
  .Bx 3.0 .
 +.Pp
 +The
 +.Fl d ,
 +.Fl p
 +and
 +.Fl r
 +options were imported from NetBSD.
  .Sh BUGS
  The
  .Nm
 Index: usr.bin/script/script.c
 ===================================================================
 --- usr.bin/script/script.c	(revision 216591)
 +++ usr.bin/script/script.c	(working copy)
 @@ -46,6 +46,9 @@
  #include <sys/stat.h>
  #include <sys/ioctl.h>
  #include <sys/time.h>
 +#include <sys/uio.h>
 +#include <sys/endian.h>
 +#include <sys/param.h>
  
  #include <err.h>
  #include <errno.h>
 @@ -59,11 +62,21 @@
  #include <termios.h>
  #include <unistd.h>
  
 +#define DEF_BUF 65536
 +
 +struct stamp {
 +	uint64_t scr_len;	/* amount of data */
 +	uint64_t scr_sec;	/* time it arrived in seconds... */
 +	uint32_t scr_usec;	/* ...and microseconds */
 +	uint32_t scr_direction;	/* 'i', 'o', etc (also indicates endianness) */
 +};
 +
  static FILE *fscript;
  static int master, slave;
  static int child;
  static const char *fname;
  static int qflg, ttyflg;
 +static int usesleep, rawout;
  
  static struct termios tt;
  
 @@ -71,6 +84,9 @@
  static void doshell(char **);
  static void fail(void);
  static void finish(void);
 +static void record(FILE *, char *, size_t, int);
 +static void consume(FILE *, off_t, char *, int);
 +static void playback(FILE *);
  static void usage(void);
  
  int
 @@ -79,26 +95,39 @@
  	int cc;
  	struct termios rtt, stt;
  	struct winsize win;
 -	int aflg, kflg, ch, n;
 +	int aflg, kflg, pflg, ch, n;
  	struct timeval tv, *tvp;
  	time_t tvec, start;
  	char obuf[BUFSIZ];
  	char ibuf[BUFSIZ];
  	fd_set rfd;
  	int flushtime = 30;
 +	int k;
  
 -	aflg = kflg = 0;
 -	while ((ch = getopt(argc, argv, "aqkt:")) != -1)
 +	aflg = kflg = pflg = 0;
 +	usesleep = 1;
 +	rawout = 0;
 + 
 +	while ((ch = getopt(argc, argv, "adkpqrt:")) != -1)
  		switch(ch) {
  		case 'a':
  			aflg = 1;
  			break;
 -		case 'q':
 -			qflg = 1;
 +		case 'd':
 +			usesleep = 0;
  			break;
  		case 'k':
  			kflg = 1;
  			break;
 +		case 'p':
 +			pflg = 1;
 +			break;
 +		case 'q':
 +			qflg = 1;
 +			break;
 +		case 'r':
 +			rawout = 1;
 +			break;
  		case 't':
  			flushtime = atoi(optarg);
  			if (flushtime < 0)
 @@ -118,9 +147,12 @@
  	} else
  		fname = "typescript";
  
 -	if ((fscript = fopen(fname, aflg ? "a" : "w")) == NULL)
 +	if ((fscript = fopen(fname, pflg ? "r" : aflg ? "a" : "w")) == NULL)
  		err(1, "%s", fname);
  
 +	if (pflg)
 +		playback(fscript);
 +
  	if ((ttyflg = isatty(STDIN_FILENO)) != 0) {
  		if (tcgetattr(STDIN_FILENO, &tt) == -1)
  			err(1, "tcgetattr");
 @@ -133,10 +165,21 @@
  			err(1, "openpty");
  	}
  
 +	if (rawout)
 +		record(fscript, NULL, 0, 's');
 +
  	if (!qflg) {
  		tvec = time(NULL);
  		(void)printf("Script started, output file is %s\n", fname);
 -		(void)fprintf(fscript, "Script started on %s", ctime(&tvec));
 +		if (!rawout) {
 +			(void)fprintf(fscript, "Script started on %s", ctime(&tvec));
 +			if (argv[0]) {
 +				fprintf(fscript, "command: ");
 +				for (k = 0 ; argv[k] ; ++k)
 +					fprintf(fscript, "%s%s", k ? " " : "", argv[k]);
 +				fprintf(fscript, "\n");
 +			}
 +		}
  		fflush(fscript);
  	}
  	if (ttyflg) {
 @@ -179,6 +222,8 @@
  			if (cc == 0)
  				(void)write(master, ibuf, 0);
  			if (cc > 0) {
 +				if (rawout)
 +					record(fscript, ibuf, cc, 'i');
  				(void)write(master, ibuf, cc);
  				if (kflg && tcgetattr(master, &stt) >= 0 &&
  				    ((stt.c_lflag & ECHO) == 0)) {
 @@ -191,7 +236,10 @@
  			if (cc <= 0)
  				break;
  			(void)write(STDOUT_FILENO, obuf, cc);
 -			(void)fwrite(obuf, 1, cc, fscript);
 +			if (rawout)
 +				record(fscript, obuf, cc, 'o');
 +			else
 +				(void)fwrite(obuf, 1, cc, fscript);
  		}
  		tvec = time(0);
  		if (tvec - start >= flushtime) {
 @@ -231,17 +279,11 @@
  doshell(char **av)
  {
  	const char *shell;
 -	int k;
  
  	shell = getenv("SHELL");
  	if (shell == NULL)
  		shell = _PATH_BSHELL;
  
 -	if (av[0])
 -		for (k = 0 ; av[k] ; ++k)
 -			fprintf(fscript, "%s%s", k ? " " : "", av[k]);
 -		fprintf(fscript, "\r\n");
 -
  	(void)close(master);
  	(void)fclose(fscript);
  	login_tty(slave);
 @@ -271,11 +313,142 @@
  	if (ttyflg)
  		(void)tcsetattr(STDIN_FILENO, TCSAFLUSH, &tt);
  	tvec = time(NULL);
 +	if (rawout)
 +		record(fscript, NULL, 0, 'e');
  	if (!qflg) {
 -		(void)fprintf(fscript,"\nScript done on %s", ctime(&tvec));
 +		if (!rawout)
 +			(void)fprintf(fscript,"\nScript done on %s", ctime(&tvec));
  		(void)printf("\nScript done, output file is %s\n", fname);
  	}
  	(void)fclose(fscript);
  	(void)close(master);
  	exit(eno);
  }
 +
 +
 +static void
 +record(FILE *fp, char *buf, size_t cc, int direction)
 +{
 +	struct iovec iov[2];
 +	struct stamp stamp;
 +	struct timeval tv;
 +
 +	(void)gettimeofday(&tv, NULL);
 +	stamp.scr_len = cc;
 +	stamp.scr_sec = tv.tv_sec;
 +	stamp.scr_usec = tv.tv_usec;
 +	stamp.scr_direction = direction;
 +	iov[0].iov_len = sizeof(stamp);
 +	iov[0].iov_base = &stamp;
 +	iov[1].iov_len = cc;
 +	iov[1].iov_base = buf;
 +	if (writev(fileno(fp), &iov[0], 2) == -1)
 +		err(1, "writev");
 +}
 +
 +static void
 +consume(FILE *fp, off_t len, char *buf, int reg)
 +{
 +	size_t l;
 +
 +	if (reg) {
 +		if (fseeko(fp, len, SEEK_CUR) == -1)
 +			err(1, NULL);
 +	}
 +	else {
 +		while (len > 0) {
 +			l = MIN(DEF_BUF, len);
 +			if (fread(buf, sizeof(char), l, fp) != l)
 +				err(1, "cannot read buffer");
 +			len -= l;
 +		}
 +	}
 +}
 +
 +#define swapstamp(stamp) do { \
 +	if (stamp.scr_direction > 0xff) { \
 +		stamp.scr_len = bswap64(stamp.scr_len); \
 +		stamp.scr_sec = bswap64(stamp.scr_sec); \
 +		stamp.scr_usec = bswap32(stamp.scr_usec); \
 +		stamp.scr_direction = bswap32(stamp.scr_direction); \
 +	} \
 +} while (0/*CONSTCOND*/)
 +
 +static void
 +playback(FILE *fp)
 +{
 +	struct timespec tsi, tso;
 +	struct stamp stamp;
 +	struct stat pst;
 +	char buf[DEF_BUF];
 +	off_t nread, save_len;
 +	size_t l;
 +	time_t tclock;
 +	int reg;
 +
 +	if (fstat(fileno(fp), &pst) == -1)
 +		err(1, "fstat failed");	
 +
 +	reg = S_ISREG(pst.st_mode);
 +
 +	for (nread = 0; !reg || nread < pst.st_size; nread += save_len) {
 +		if (fread(&stamp, sizeof(stamp), 1, fp) != 1) {
 +			if (reg)
 +				err(1, "reading playback header");
 +			else
 +				break;
 +		}
 +		swapstamp(stamp);
 +		save_len = sizeof(stamp);
 +
 +		if (reg && stamp.scr_len >
 +		    (uint64_t)(pst.st_size - save_len) - nread)
 +			err(1, "invalid stamp");
 +
 +		save_len += stamp.scr_len;
 +		tclock = stamp.scr_sec;
 +		tso.tv_sec = stamp.scr_sec;
 +		tso.tv_nsec = stamp.scr_usec * 1000;
 +
 +		switch (stamp.scr_direction) {
 +		case 's':
 +			if (!qflg)
 +				(void)printf("Script started on %s", ctime(&tclock));
 +			tsi = tso;
 +			(void)consume(fp, stamp.scr_len, buf, reg);
 +			break;
 +		case 'e':
 +			if (!qflg)
 +				(void)printf("\nScript done on %s", ctime(&tclock));
 +			(void)consume(fp, stamp.scr_len, buf, reg);
 +			break;
 +		case 'i':
 +			/* throw input away */
 +			(void)consume(fp, stamp.scr_len, buf, reg);
 +			break;
 +		case 'o':
 +			tsi.tv_sec = tso.tv_sec - tsi.tv_sec;
 +			tsi.tv_nsec = tso.tv_nsec - tsi.tv_nsec;
 +			if (tsi.tv_nsec < 0) {
 +				tsi.tv_sec -= 1;
 +				tsi.tv_nsec += 1000000000;
 +			}
 +			if (usesleep)
 +				(void)nanosleep(&tsi, NULL);
 +			tsi = tso;
 +			while (stamp.scr_len > 0) {
 +				l = MIN(DEF_BUF, stamp.scr_len);
 +				if (fread(buf, sizeof(char), l, fp) != l)
 +					err(1, "cannot read buffer");
 +
 +				(void)write(STDOUT_FILENO, buf, l);
 +				stamp.scr_len -= l;
 +			}
 +			break;
 +		default:
 +			err(1, "invalid direction");
 +		}
 +	}
 +	(void)fclose(fp);
 +	exit(0);
 +}
 --- script_replay.diff ends here ---

From: h h <aakuusta@gmail.com>
To: bug-followup@FreeBSD.org
Cc:  
Subject: Re: bin/114465: [patch] [request] script(1): add really cool -d, -p & -r options from NetBSD
Date: Wed, 28 Sep 2011 01:45:41 +0000

 misc/ttyrec seems to provide more control over playback.
 And it has a youtube-like service - playterm.org, unlike
 youterm.com which is no longer available. Anyway, ...
 
 - resolve conflict with r225809
 - apply script.c@r1.19 and script.1@r1.12 from NetBSD
 - mention in the manpage that the flags first appeared in NetBSD 2.0
 
 --- script_replay.diff begins here ---
 Index: usr.bin/script/script.1
 ===================================================================
 --- usr.bin/script/script.1	(revision 225817)
 +++ usr.bin/script/script.1	(working copy)
 @@ -36,7 +36,7 @@
  .Nd make typescript of terminal session
  .Sh SYNOPSIS
  .Nm
 -.Op Fl akq
 +.Op Fl adkpqr
  .Op Fl t Ar time
  .Op Ar file Op Ar command ...
  .Sh DESCRIPTION
 @@ -72,10 +72,20 @@ Append the output to
  or
  .Pa typescript ,
  retaining the prior contents.
 +.It Fl d
 +When playing back a session with the
 +.Fl p
 +flag, don't sleep between records when playing back a timestamped session.
  .It Fl k
  Log keys sent to program as well as output.
 +.It Fl p
 +Play back a session recorded with the
 +.Fl r
 +flag in real time.
  .It Fl q
 -Run in quiet mode, omit the start and stop status messages.
 +Run in quiet mode, omit the start, stop and command status messages.
 +.It Fl r
 +Record a session with input, output, and timestamping.
  .It Fl t Ar time
  Specify time interval between flushing script output file.
  A value of 0
 @@ -148,6 +158,14 @@ The
  .Nm
  command appeared in
  .Bx 3.0 .
 +.Pp
 +The
 +.Fl d ,
 +.Fl p
 +and
 +.Fl r
 +first appeared in
 +.Nx 2.0 .
  .Sh BUGS
  The
  .Nm
 Index: usr.bin/script/script.c
 ===================================================================
 --- usr.bin/script/script.c	(revision 225817)
 +++ usr.bin/script/script.c	(working copy)
 @@ -46,6 +46,9 @@ static const char sccsid[] = "@(#)script.c	8.1 (Be
  #include <sys/stat.h>
  #include <sys/ioctl.h>
  #include <sys/time.h>
 +#include <sys/uio.h>
 +#include <sys/endian.h>
 +#include <sys/param.h>
  
  #include <err.h>
  #include <errno.h>
 @@ -59,11 +62,21 @@ static const char sccsid[] = "@(#)script.c	8.1 (Be
  #include <termios.h>
  #include <unistd.h>
  
 +#define DEF_BUF 65536
 +
 +struct stamp {
 +	uint64_t scr_len;	/* amount of data */
 +	uint64_t scr_sec;	/* time it arrived in seconds... */
 +	uint32_t scr_usec;	/* ...and microseconds */
 +	uint32_t scr_direction;	/* 'i', 'o', etc (also indicates endianness) */
 +};
 +
  static FILE *fscript;
  static int master, slave;
  static int child;
  static const char *fname;
  static int qflg, ttyflg;
 +static int usesleep, rawout;
  
  static struct termios tt;
  
 @@ -71,6 +84,9 @@ static void done(int) __dead2;
  static void doshell(char **);
  static void fail(void);
  static void finish(void);
 +static void record(FILE *, char *, size_t, int);
 +static void consume(FILE *, off_t, char *, int);
 +static void playback(FILE *);
  static void usage(void);
  
  int
 @@ -79,7 +95,7 @@ main(int argc, char *argv[])
  	int cc;
  	struct termios rtt, stt;
  	struct winsize win;
 -	int aflg, kflg, ch, n;
 +	int aflg, kflg, pflg, ch, n;
  	struct timeval tv, *tvp;
  	time_t tvec, start;
  	char obuf[BUFSIZ];
 @@ -87,19 +103,32 @@ main(int argc, char *argv[])
  	fd_set rfd;
  	int flushtime = 30;
  	int readstdin;
 +	int k;
  
 -	aflg = kflg = 0;
 -	while ((ch = getopt(argc, argv, "aqkt:")) != -1)
 +	aflg = kflg = pflg = 0;
 +	usesleep = 1;
 +	rawout = 0;
 + 
 +	while ((ch = getopt(argc, argv, "adkpqrt:")) != -1)
  		switch(ch) {
  		case 'a':
  			aflg = 1;
  			break;
 -		case 'q':
 -			qflg = 1;
 +		case 'd':
 +			usesleep = 0;
  			break;
  		case 'k':
  			kflg = 1;
  			break;
 +		case 'p':
 +			pflg = 1;
 +			break;
 +		case 'q':
 +			qflg = 1;
 +			break;
 +		case 'r':
 +			rawout = 1;
 +			break;
  		case 't':
  			flushtime = atoi(optarg);
  			if (flushtime < 0)
 @@ -119,9 +148,12 @@ main(int argc, char *argv[])
  	} else
  		fname = "typescript";
  
 -	if ((fscript = fopen(fname, aflg ? "a" : "w")) == NULL)
 +	if ((fscript = fopen(fname, pflg ? "r" : aflg ? "a" : "w")) == NULL)
  		err(1, "%s", fname);
  
 +	if (pflg)
 +		playback(fscript);
 +
  	if ((ttyflg = isatty(STDIN_FILENO)) != 0) {
  		if (tcgetattr(STDIN_FILENO, &tt) == -1)
  			err(1, "tcgetattr");
 @@ -134,10 +166,21 @@ main(int argc, char *argv[])
  			err(1, "openpty");
  	}
  
 +	if (rawout)
 +		record(fscript, NULL, 0, 's');
 +
  	if (!qflg) {
  		tvec = time(NULL);
  		(void)printf("Script started, output file is %s\n", fname);
 -		(void)fprintf(fscript, "Script started on %s", ctime(&tvec));
 +		if (!rawout) {
 +			(void)fprintf(fscript, "Script started on %s", ctime(&tvec));
 +			if (argv[0]) {
 +				fprintf(fscript, "command: ");
 +				for (k = 0 ; argv[k] ; ++k)
 +					fprintf(fscript, "%s%s", k ? " " : "", argv[k]);
 +				fprintf(fscript, "\n");
 +			}
 +		}
  		fflush(fscript);
  	}
  	if (ttyflg) {
 @@ -187,6 +230,8 @@ main(int argc, char *argv[])
  				readstdin = 0;
  			}
  			if (cc > 0) {
 +				if (rawout)
 +					record(fscript, ibuf, cc, 'i');
  				(void)write(master, ibuf, cc);
  				if (kflg && tcgetattr(master, &stt) >= 0 &&
  				    ((stt.c_lflag & ECHO) == 0)) {
 @@ -199,7 +244,10 @@ main(int argc, char *argv[])
  			if (cc <= 0)
  				break;
  			(void)write(STDOUT_FILENO, obuf, cc);
 -			(void)fwrite(obuf, 1, cc, fscript);
 +			if (rawout)
 +				record(fscript, obuf, cc, 'o');
 +			else
 +				(void)fwrite(obuf, 1, cc, fscript);
  		}
  		tvec = time(0);
  		if (tvec - start >= flushtime) {
 @@ -239,17 +287,11 @@ static void
  doshell(char **av)
  {
  	const char *shell;
 -	int k;
  
  	shell = getenv("SHELL");
  	if (shell == NULL)
  		shell = _PATH_BSHELL;
  
 -	if (av[0])
 -		for (k = 0 ; av[k] ; ++k)
 -			fprintf(fscript, "%s%s", k ? " " : "", av[k]);
 -		fprintf(fscript, "\r\n");
 -
  	(void)close(master);
  	(void)fclose(fscript);
  	login_tty(slave);
 @@ -279,11 +321,142 @@ done(int eno)
  	if (ttyflg)
  		(void)tcsetattr(STDIN_FILENO, TCSAFLUSH, &tt);
  	tvec = time(NULL);
 +	if (rawout)
 +		record(fscript, NULL, 0, 'e');
  	if (!qflg) {
 -		(void)fprintf(fscript,"\nScript done on %s", ctime(&tvec));
 +		if (!rawout)
 +			(void)fprintf(fscript,"\nScript done on %s", ctime(&tvec));
  		(void)printf("\nScript done, output file is %s\n", fname);
  	}
  	(void)fclose(fscript);
  	(void)close(master);
  	exit(eno);
  }
 +
 +
 +static void
 +record(FILE *fp, char *buf, size_t cc, int direction)
 +{
 +	struct iovec iov[2];
 +	struct stamp stamp;
 +	struct timeval tv;
 +
 +	(void)gettimeofday(&tv, NULL);
 +	stamp.scr_len = cc;
 +	stamp.scr_sec = tv.tv_sec;
 +	stamp.scr_usec = tv.tv_usec;
 +	stamp.scr_direction = direction;
 +	iov[0].iov_len = sizeof(stamp);
 +	iov[0].iov_base = &stamp;
 +	iov[1].iov_len = cc;
 +	iov[1].iov_base = buf;
 +	if (writev(fileno(fp), &iov[0], 2) == -1)
 +		err(1, "writev");
 +}
 +
 +static void
 +consume(FILE *fp, off_t len, char *buf, int reg)
 +{
 +	size_t l;
 +
 +	if (reg) {
 +		if (fseeko(fp, len, SEEK_CUR) == -1)
 +			err(1, NULL);
 +	}
 +	else {
 +		while (len > 0) {
 +			l = MIN(DEF_BUF, len);
 +			if (fread(buf, sizeof(char), l, fp) != l)
 +				err(1, "cannot read buffer");
 +			len -= l;
 +		}
 +	}
 +}
 +
 +#define swapstamp(stamp) do { \
 +	if (stamp.scr_direction > 0xff) { \
 +		stamp.scr_len = bswap64(stamp.scr_len); \
 +		stamp.scr_sec = bswap64(stamp.scr_sec); \
 +		stamp.scr_usec = bswap32(stamp.scr_usec); \
 +		stamp.scr_direction = bswap32(stamp.scr_direction); \
 +	} \
 +} while (0/*CONSTCOND*/)
 +
 +static void
 +playback(FILE *fp)
 +{
 +	struct timespec tsi, tso;
 +	struct stamp stamp;
 +	struct stat pst;
 +	char buf[DEF_BUF];
 +	off_t nread, save_len;
 +	size_t l;
 +	time_t tclock;
 +	int reg;
 +
 +	if (fstat(fileno(fp), &pst) == -1)
 +		err(1, "fstat failed");	
 +
 +	reg = S_ISREG(pst.st_mode);
 +
 +	for (nread = 0; !reg || nread < pst.st_size; nread += save_len) {
 +		if (fread(&stamp, sizeof(stamp), 1, fp) != 1) {
 +			if (reg)
 +				err(1, "reading playback header");
 +			else
 +				break;
 +		}
 +		swapstamp(stamp);
 +		save_len = sizeof(stamp);
 +
 +		if (reg && stamp.scr_len >
 +		    (uint64_t)(pst.st_size - save_len) - nread)
 +			errx(1, "invalid stamp");
 +
 +		save_len += stamp.scr_len;
 +		tclock = stamp.scr_sec;
 +		tso.tv_sec = stamp.scr_sec;
 +		tso.tv_nsec = stamp.scr_usec * 1000;
 +
 +		switch (stamp.scr_direction) {
 +		case 's':
 +			if (!qflg)
 +				(void)printf("Script started on %s", ctime(&tclock));
 +			tsi = tso;
 +			(void)consume(fp, stamp.scr_len, buf, reg);
 +			break;
 +		case 'e':
 +			if (!qflg)
 +				(void)printf("\nScript done on %s", ctime(&tclock));
 +			(void)consume(fp, stamp.scr_len, buf, reg);
 +			break;
 +		case 'i':
 +			/* throw input away */
 +			(void)consume(fp, stamp.scr_len, buf, reg);
 +			break;
 +		case 'o':
 +			tsi.tv_sec = tso.tv_sec - tsi.tv_sec;
 +			tsi.tv_nsec = tso.tv_nsec - tsi.tv_nsec;
 +			if (tsi.tv_nsec < 0) {
 +				tsi.tv_sec -= 1;
 +				tsi.tv_nsec += 1000000000;
 +			}
 +			if (usesleep)
 +				(void)nanosleep(&tsi, NULL);
 +			tsi = tso;
 +			while (stamp.scr_len > 0) {
 +				l = MIN(DEF_BUF, stamp.scr_len);
 +				if (fread(buf, sizeof(char), l, fp) != l)
 +					err(1, "cannot read buffer");
 +
 +				(void)write(STDOUT_FILENO, buf, l);
 +				stamp.scr_len -= l;
 +			}
 +			break;
 +		default:
 +			errx(1, "invalid direction");
 +		}
 +	}
 +	(void)fclose(fp);
 +	exit(0);
 +}
 --- script_replay.diff ends here ---

From: h h <aakuusta@gmail.com>
To: bug-followup@FreeBSD.org
Cc:  
Subject: Re: bin/114465: [patch] [request] script(1): add really cool -d, -p & -r options from NetBSD
Date: Wed, 28 Sep 2011 09:26:41 +0000

 a few cosmetic changes
 - wrap long lines, also reduces diff with script.c from NetBSD
 - mark playback() as __dead2, it uses exit(3) and err(3)
 - add forgotten word after `the' in the manpage
 
 Also, the previous followup got revisions wrong
 
  - script.c@1.19 -> script.c@1.20
  - script.1@1.12 -> script.1@1.13
 
 --- a.diff begins here ---
 --- usr.bin/script/script.1~
 +++ usr.bin/script/script.1
 @@ -164,7 +164,7 @@ The
  .Fl p
  and
  .Fl r
 -first appeared in
 +options first appeared in
  .Nx 2.0 .
  .Sh BUGS
  The
 --- usr.bin/script/script.c~
 +++ usr.bin/script/script.c
 @@ -86,7 +86,7 @@ static void fail(void);
  static void finish(void);
  static void record(FILE *, char *, size_t, int);
  static void consume(FILE *, off_t, char *, int);
 -static void playback(FILE *);
 +static void playback(FILE *) __dead2;
  static void usage(void);
  
  int
 @@ -108,7 +108,7 @@ main(int argc, char *argv[])
  	aflg = kflg = pflg = 0;
  	usesleep = 1;
  	rawout = 0;
 - 
 +
  	while ((ch = getopt(argc, argv, "adkpqrt:")) != -1)
  		switch(ch) {
  		case 'a':
 @@ -173,11 +173,13 @@ main(int argc, char *argv[])
  		tvec = time(NULL);
  		(void)printf("Script started, output file is %s\n", fname);
  		if (!rawout) {
 -			(void)fprintf(fscript, "Script started on %s", ctime(&tvec));
 +			(void)fprintf(fscript, "Script started on %s",
 +				      ctime(&tvec));
  			if (argv[0]) {
  				fprintf(fscript, "command: ");
  				for (k = 0 ; argv[k] ; ++k)
 -					fprintf(fscript, "%s%s", k ? " " : "", argv[k]);
 +					fprintf(fscript, "%s%s", k ? " " : "",
 +						argv[k]);
  				fprintf(fscript, "\n");
  			}
  		}
 @@ -325,7 +327,8 @@ done(int eno)
  		record(fscript, NULL, 0, 'e');
  	if (!qflg) {
  		if (!rawout)
 -			(void)fprintf(fscript,"\nScript done on %s", ctime(&tvec));
 +			(void)fprintf(fscript,"\nScript done on %s",
 +				      ctime(&tvec));
  		(void)printf("\nScript done, output file is %s\n", fname);
  	}
  	(void)fclose(fscript);
 @@ -333,7 +336,6 @@ done(int eno)
  	exit(eno);
  }
  
 -
  static void
  record(FILE *fp, char *buf, size_t cc, int direction)
  {
 @@ -421,13 +423,15 @@ playback(FILE *fp)
  		switch (stamp.scr_direction) {
  		case 's':
  			if (!qflg)
 -				(void)printf("Script started on %s", ctime(&tclock));
 +			    (void)printf("Script started on %s",
 +				ctime(&tclock));
  			tsi = tso;
  			(void)consume(fp, stamp.scr_len, buf, reg);
  			break;
  		case 'e':
  			if (!qflg)
 -				(void)printf("\nScript done on %s", ctime(&tclock));
 +				(void)printf("\nScript done on %s",
 +				    ctime(&tclock));
  			(void)consume(fp, stamp.scr_len, buf, reg);
  			break;
  		case 'i':
 --- a.diff ends here ---
Responsible-Changed-From-To: freebsd-bugs->brian 
Responsible-Changed-By: brian 
Responsible-Changed-When: Mon Jul 30 07:07:10 UTC 2012 
Responsible-Changed-Why:  
Take 

http://www.freebsd.org/cgi/query-pr.cgi?pr=114465 
State-Changed-From-To: open->patched 
State-Changed-By: brian 
State-Changed-When: Mon Jul 30 07:11:45 UTC 2012 
State-Changed-Why:  
Committed to -current.  I'll MFC in 3 weeks if there are no complaints. 

http://www.freebsd.org/cgi/query-pr.cgi?pr=114465 

From: dfilter@FreeBSD.ORG (dfilter service)
To: bug-followup@FreeBSD.org
Cc:  
Subject: Re: bin/114465: commit references a PR
Date: Mon, 30 Jul 2012 07:13:22 +0000 (UTC)

 Author: brian
 Date: Mon Jul 30 07:13:06 2012
 New Revision: 238896
 URL: http://svn.freebsd.org/changeset/base/238896
 
 Log:
   Add d, p and r switches for recording script sessions with timing data
   and playing sessions back with or without time delays.
   
   PR:		114465
   Submitted by:	ighighi at gmail dot com
   MFC after:	3 weeks
 
 Modified:
   head/usr.bin/script/script.1
   head/usr.bin/script/script.c
 
 Modified: head/usr.bin/script/script.1
 ==============================================================================
 --- head/usr.bin/script/script.1	Mon Jul 30 06:00:31 2012	(r238895)
 +++ head/usr.bin/script/script.1	Mon Jul 30 07:13:06 2012	(r238896)
 @@ -36,7 +36,7 @@
  .Nd make typescript of terminal session
  .Sh SYNOPSIS
  .Nm
 -.Op Fl akq
 +.Op Fl adkpqr
  .Op Fl t Ar time
  .Op Ar file Op Ar command ...
  .Sh DESCRIPTION
 @@ -72,10 +72,20 @@ Append the output to
  or
  .Pa typescript ,
  retaining the prior contents.
 +.It Fl d
 +When playing back a session with the
 +.Fl p
 +flag, don't sleep between records when playing back a timestamped session.
  .It Fl k
  Log keys sent to the program as well as output.
 +.It Fl p
 +Play back a session recorded with the
 +.Fl r
 +flag in real time.
  .It Fl q
 -Run in quiet mode, omit the start and stop status messages.
 +Run in quiet mode, omit the start, stop and command status messages.
 +.It Fl r
 +Record a session with input, output, and timestamping.
  .It Fl t Ar time
  Specify the interval at which the script output file will be flushed
  to disk, in seconds.
 @@ -151,6 +161,14 @@ The
  .Nm
  command appeared in
  .Bx 3.0 .
 +.Pp
 +The
 +.Fl d ,
 +.Fl p
 +and
 +.Fl r
 +options first appeared in
 +.Nx 2.0 .
  .Sh BUGS
  The
  .Nm
 
 Modified: head/usr.bin/script/script.c
 ==============================================================================
 --- head/usr.bin/script/script.c	Mon Jul 30 06:00:31 2012	(r238895)
 +++ head/usr.bin/script/script.c	Mon Jul 30 07:13:06 2012	(r238896)
 @@ -46,6 +46,9 @@ static const char sccsid[] = "@(#)script
  #include <sys/stat.h>
  #include <sys/ioctl.h>
  #include <sys/time.h>
 +#include <sys/uio.h>
 +#include <sys/endian.h>
 +#include <sys/param.h>
  
  #include <err.h>
  #include <errno.h>
 @@ -59,11 +62,21 @@ static const char sccsid[] = "@(#)script
  #include <termios.h>
  #include <unistd.h>
  
 +#define DEF_BUF 65536
 +
 +struct stamp {
 +	uint64_t scr_len;	/* amount of data */
 +	uint64_t scr_sec;	/* time it arrived in seconds... */
 +	uint32_t scr_usec;	/* ...and microseconds */
 +	uint32_t scr_direction; /* 'i', 'o', etc (also indicates endianness) */
 +};
 +
  static FILE *fscript;
  static int master, slave;
  static int child;
  static const char *fname;
  static int qflg, ttyflg;
 +static int usesleep, rawout;
  
  static struct termios tt;
  
 @@ -71,6 +84,9 @@ static void done(int) __dead2;
  static void doshell(char **);
  static void fail(void);
  static void finish(void);
 +static void record(FILE *, char *, size_t, int);
 +static void consume(FILE *, off_t, char *, int);
 +static void playback(FILE *) __dead2;
  static void usage(void);
  
  int
 @@ -79,7 +95,7 @@ main(int argc, char *argv[])
  	int cc;
  	struct termios rtt, stt;
  	struct winsize win;
 -	int aflg, kflg, ch, n;
 +	int aflg, kflg, pflg, ch, n;
  	struct timeval tv, *tvp;
  	time_t tvec, start;
  	char obuf[BUFSIZ];
 @@ -87,19 +103,32 @@ main(int argc, char *argv[])
  	fd_set rfd;
  	int flushtime = 30;
  	int readstdin;
 +	int k;
 +
 +	aflg = kflg = pflg = 0;
 +	usesleep = 1;
 +	rawout = 0;
  
 -	aflg = kflg = 0;
 -	while ((ch = getopt(argc, argv, "aqkt:")) != -1)
 +	while ((ch = getopt(argc, argv, "adkpqrt:")) != -1)
  		switch(ch) {
  		case 'a':
  			aflg = 1;
  			break;
 -		case 'q':
 -			qflg = 1;
 +		case 'd':
 +			usesleep = 0;
  			break;
  		case 'k':
  			kflg = 1;
  			break;
 +		case 'p':
 +			pflg = 1;
 +			break;
 +		case 'q':
 +			qflg = 1;
 +			break;
 +		case 'r':
 +			rawout = 1;
 +			break;
  		case 't':
  			flushtime = atoi(optarg);
  			if (flushtime < 0)
 @@ -119,9 +148,12 @@ main(int argc, char *argv[])
  	} else
  		fname = "typescript";
  
 -	if ((fscript = fopen(fname, aflg ? "a" : "w")) == NULL)
 +	if ((fscript = fopen(fname, pflg ? "r" : aflg ? "a" : "w")) == NULL)
  		err(1, "%s", fname);
  
 +	if (pflg)
 +		playback(fscript);
 +
  	if ((ttyflg = isatty(STDIN_FILENO)) != 0) {
  		if (tcgetattr(STDIN_FILENO, &tt) == -1)
  			err(1, "tcgetattr");
 @@ -134,10 +166,23 @@ main(int argc, char *argv[])
  			err(1, "openpty");
  	}
  
 +	if (rawout)
 +		record(fscript, NULL, 0, 's');
 +
  	if (!qflg) {
  		tvec = time(NULL);
  		(void)printf("Script started, output file is %s\n", fname);
 -		(void)fprintf(fscript, "Script started on %s", ctime(&tvec));
 +		if (!rawout) {
 +			(void)fprintf(fscript, "Script started on %s",
 +				      ctime(&tvec));
 +			if (argv[0]) {
 +				fprintf(fscript, "command: ");
 +				for (k = 0 ; argv[k] ; ++k)
 +					fprintf(fscript, "%s%s", k ? " " : "",
 +						argv[k]);
 +				fprintf(fscript, "\n");
 +			}
 +		}
  		fflush(fscript);
  	}
  	if (ttyflg) {
 @@ -163,15 +208,12 @@ main(int argc, char *argv[])
  		FD_SET(master, &rfd);
  		if (readstdin)
  			FD_SET(STDIN_FILENO, &rfd);
 -		if (!readstdin && ttyflg) {
 -			tv.tv_sec = 1;
 +		if ((!readstdin && ttyflg) || flushtime > 0) {
 +			tv.tv_sec = !readstdin && ttyflg ? 1 :
 +			    flushtime - (tvec - start);
  			tv.tv_usec = 0;
  			tvp = &tv;
  			readstdin = 1;
 -		} else if (flushtime > 0) {
 -			tv.tv_sec = flushtime - (tvec - start);
 -			tv.tv_usec = 0;
 -			tvp = &tv;
  		} else {
  			tvp = NULL;
  		}
 @@ -190,6 +232,8 @@ main(int argc, char *argv[])
  				readstdin = 0;
  			}
  			if (cc > 0) {
 +				if (rawout)
 +					record(fscript, ibuf, cc, 'i');
  				(void)write(master, ibuf, cc);
  				if (kflg && tcgetattr(master, &stt) >= 0 &&
  				    ((stt.c_lflag & ECHO) == 0)) {
 @@ -202,7 +246,10 @@ main(int argc, char *argv[])
  			if (cc <= 0)
  				break;
  			(void)write(STDOUT_FILENO, obuf, cc);
 -			(void)fwrite(obuf, 1, cc, fscript);
 +			if (rawout)
 +				record(fscript, obuf, cc, 'o');
 +			else
 +				(void)fwrite(obuf, 1, cc, fscript);
  		}
  		tvec = time(0);
  		if (tvec - start >= flushtime) {
 @@ -218,7 +265,7 @@ static void
  usage(void)
  {
  	(void)fprintf(stderr,
 -	    "usage: script [-akq] [-t time] [file [command ...]]\n");
 +	    "usage: script [-adkpqr] [-t time] [file [command ...]]\n");
  	exit(1);
  }
  
 @@ -242,17 +289,11 @@ static void
  doshell(char **av)
  {
  	const char *shell;
 -	int k;
  
  	shell = getenv("SHELL");
  	if (shell == NULL)
  		shell = _PATH_BSHELL;
  
 -	if (av[0])
 -		for (k = 0 ; av[k] ; ++k)
 -			fprintf(fscript, "%s%s", k ? " " : "", av[k]);
 -		fprintf(fscript, "\r\n");
 -
  	(void)close(master);
  	(void)fclose(fscript);
  	login_tty(slave);
 @@ -282,11 +323,144 @@ done(int eno)
  	if (ttyflg)
  		(void)tcsetattr(STDIN_FILENO, TCSAFLUSH, &tt);
  	tvec = time(NULL);
 +	if (rawout)
 +		record(fscript, NULL, 0, 'e');
  	if (!qflg) {
 -		(void)fprintf(fscript,"\nScript done on %s", ctime(&tvec));
 +		if (!rawout)
 +			(void)fprintf(fscript,"\nScript done on %s",
 +				      ctime(&tvec));
  		(void)printf("\nScript done, output file is %s\n", fname);
  	}
  	(void)fclose(fscript);
  	(void)close(master);
  	exit(eno);
  }
 +
 +static void
 +record(FILE *fp, char *buf, size_t cc, int direction)
 +{
 +	struct iovec iov[2];
 +	struct stamp stamp;
 +	struct timeval tv;
 +
 +	(void)gettimeofday(&tv, NULL);
 +	stamp.scr_len = cc;
 +	stamp.scr_sec = tv.tv_sec;
 +	stamp.scr_usec = tv.tv_usec;
 +	stamp.scr_direction = direction;
 +	iov[0].iov_len = sizeof(stamp);
 +	iov[0].iov_base = &stamp;
 +	iov[1].iov_len = cc;
 +	iov[1].iov_base = buf;
 +	if (writev(fileno(fp), &iov[0], 2) == -1)
 +		err(1, "writev");
 +}
 +
 +static void
 +consume(FILE *fp, off_t len, char *buf, int reg)
 +{
 +	size_t l;
 +
 +	if (reg) {
 +		if (fseeko(fp, len, SEEK_CUR) == -1)
 +			err(1, NULL);
 +	}
 +	else {
 +		while (len > 0) {
 +			l = MIN(DEF_BUF, len);
 +			if (fread(buf, sizeof(char), l, fp) != l)
 +				err(1, "cannot read buffer");
 +			len -= l;
 +		}
 +	}
 +}
 +
 +#define swapstamp(stamp) do { \
 +	if (stamp.scr_direction > 0xff) { \
 +		stamp.scr_len = bswap64(stamp.scr_len); \
 +		stamp.scr_sec = bswap64(stamp.scr_sec); \
 +		stamp.scr_usec = bswap32(stamp.scr_usec); \
 +		stamp.scr_direction = bswap32(stamp.scr_direction); \
 +	} \
 +} while (0/*CONSTCOND*/)
 +
 +static void
 +playback(FILE *fp)
 +{
 +	struct timespec tsi, tso;
 +	struct stamp stamp;
 +	struct stat pst;
 +	char buf[DEF_BUF];
 +	off_t nread, save_len;
 +	size_t l;
 +	time_t tclock;
 +	int reg;
 +
 +	if (fstat(fileno(fp), &pst) == -1)
 +		err(1, "fstat failed");
 +
 +	reg = S_ISREG(pst.st_mode);
 +
 +	for (nread = 0; !reg || nread < pst.st_size; nread += save_len) {
 +		if (fread(&stamp, sizeof(stamp), 1, fp) != 1) {
 +			if (reg)
 +				err(1, "reading playback header");
 +			else
 +				break;
 +		}
 +		swapstamp(stamp);
 +		save_len = sizeof(stamp);
 +
 +		if (reg && stamp.scr_len >
 +		    (uint64_t)(pst.st_size - save_len) - nread)
 +			errx(1, "invalid stamp");
 +
 +		save_len += stamp.scr_len;
 +		tclock = stamp.scr_sec;
 +		tso.tv_sec = stamp.scr_sec;
 +		tso.tv_nsec = stamp.scr_usec * 1000;
 +
 +		switch (stamp.scr_direction) {
 +		case 's':
 +			if (!qflg)
 +			    (void)printf("Script started on %s",
 +				ctime(&tclock));
 +			tsi = tso;
 +			(void)consume(fp, stamp.scr_len, buf, reg);
 +			break;
 +		case 'e':
 +			if (!qflg)
 +				(void)printf("\nScript done on %s",
 +				    ctime(&tclock));
 +			(void)consume(fp, stamp.scr_len, buf, reg);
 +			break;
 +		case 'i':
 +			/* throw input away */
 +			(void)consume(fp, stamp.scr_len, buf, reg);
 +			break;
 +		case 'o':
 +			tsi.tv_sec = tso.tv_sec - tsi.tv_sec;
 +			tsi.tv_nsec = tso.tv_nsec - tsi.tv_nsec;
 +			if (tsi.tv_nsec < 0) {
 +				tsi.tv_sec -= 1;
 +				tsi.tv_nsec += 1000000000;
 +			}
 +			if (usesleep)
 +				(void)nanosleep(&tsi, NULL);
 +			tsi = tso;
 +			while (stamp.scr_len > 0) {
 +				l = MIN(DEF_BUF, stamp.scr_len);
 +				if (fread(buf, sizeof(char), l, fp) != l)
 +					err(1, "cannot read buffer");
 +
 +				(void)write(STDOUT_FILENO, buf, l);
 +				stamp.scr_len -= l;
 +			}
 +			break;
 +		default:
 +			errx(1, "invalid direction");
 +		}
 +	}
 +	(void)fclose(fp);
 +	exit(0);
 +}
 _______________________________________________
 svn-src-all@freebsd.org mailing list
 http://lists.freebsd.org/mailman/listinfo/svn-src-all
 To unsubscribe, send any mail to "svn-src-all-unsubscribe@freebsd.org"
 
>Unformatted:
