1 /*
2  * Copyright (c) 2000 Satoru Takabayashi <satoru@namazu.org>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *	This product includes software developed by the University of
16  *	California, Berkeley and its contributors.
17  * 4. Neither the name of the University nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  */
33 
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <assert.h>
37 #include <unistd.h>
38 #include <termios.h>
39 #include <sys/time.h>
40 #include <string.h>
41 
42 #include "ttyrec.h"
43 #include "io.h"
44 
45 typedef double	(*WaitFunc)	(struct timeval prev,
46 				 struct timeval cur,
47 				 double speed);
48 typedef int	(*ReadFunc)	(FILE *fp, Header *h, char **buf);
49 typedef void	(*WriteFunc)	(char *buf, int len);
50 typedef void	(*ProcessFunc)	(FILE *fp, double speed,
51 				 ReadFunc read_func, WaitFunc wait_func);
52 
53 struct timeval
timeval_diff(struct timeval tv1,struct timeval tv2)54 timeval_diff (struct timeval tv1, struct timeval tv2)
55 {
56     struct timeval diff;
57 
58     diff.tv_sec = tv2.tv_sec - tv1.tv_sec;
59     diff.tv_usec = tv2.tv_usec - tv1.tv_usec;
60     if (diff.tv_usec < 0) {
61 	diff.tv_sec--;
62 	diff.tv_usec += 1000000;
63     }
64 
65     return diff;
66 }
67 
68 struct timeval
timeval_div(struct timeval tv1,double n)69 timeval_div (struct timeval tv1, double n)
70 {
71     double x = ((double)tv1.tv_sec  + (double)tv1.tv_usec / 1000000.0) / n;
72     struct timeval div;
73 
74     div.tv_sec  = (int)x;
75     div.tv_usec = (x - (int)x) * 1000000;
76 
77     return div;
78 }
79 
80 double
ttywait(struct timeval prev,struct timeval cur,double speed)81 ttywait (struct timeval prev, struct timeval cur, double speed)
82 {
83     static struct timeval drift = {0, 0};
84     struct timeval start;
85     struct timeval diff = timeval_diff(prev, cur);
86     fd_set readfs;
87 
88     gettimeofday(&start, NULL);
89 
90     assert(speed != 0);
91     diff = timeval_diff(drift, timeval_div(diff, speed));
92     if (diff.tv_sec < 0) {
93 	diff.tv_sec = diff.tv_usec = 0;
94     }
95 
96     FD_SET(STDIN_FILENO, &readfs);
97     /*
98      * We use select() for sleeping with subsecond precision.
99      * select() is also used to wait user's input from a keyboard.
100      *
101      * Save "diff" since select(2) may overwrite it to {0, 0}.
102      */
103     struct timeval orig_diff = diff;
104     select(1, &readfs, NULL, NULL, &diff);
105     diff = orig_diff;  /* Restore the original diff value. */
106     if (FD_ISSET(0, &readfs)) { /* a user hits a character? */
107         char c;
108         read(STDIN_FILENO, &c, 1); /* drain the character */
109         switch (c) {
110         case '+':
111         case 'f':
112             speed *= 2;
113             break;
114         case '-':
115         case 's':
116             speed /= 2;
117             break;
118         case '1':
119             speed = 1.0;
120             break;
121         }
122 	drift.tv_sec = drift.tv_usec = 0;
123     } else {
124 	struct timeval stop;
125 	gettimeofday(&stop, NULL);
126 	/* Hack to accumulate the drift */
127 	if (diff.tv_sec == 0 && diff.tv_usec == 0) {
128             diff = timeval_diff(drift, diff);  // diff = 0 - drift.
129         }
130 	drift = timeval_diff(diff, timeval_diff(start, stop));
131     }
132     return speed;
133 }
134 
135 double
ttynowait(struct timeval prev,struct timeval cur,double speed)136 ttynowait (struct timeval prev, struct timeval cur, double speed)
137 {
138     /* do nothing */
139     return 0; /* Speed isn't important. */
140 }
141 
142 int
ttyread(FILE * fp,Header * h,char ** buf)143 ttyread (FILE *fp, Header *h, char **buf)
144 {
145     if (read_header(fp, h) == 0) {
146 	return 0;
147     }
148 
149     *buf = malloc(h->len);
150     if (*buf == NULL) {
151 	perror("malloc");
152     }
153 
154     if (fread(*buf, 1, h->len, fp) == 0) {
155 	perror("fread");
156     }
157     return 1;
158 }
159 
160 int
ttypread(FILE * fp,Header * h,char ** buf)161 ttypread (FILE *fp, Header *h, char **buf)
162 {
163     /*
164      * Read persistently just like tail -f.
165      */
166     while (ttyread(fp, h, buf) == 0) {
167 	struct timeval w = {0, 250000};
168 	select(0, NULL, NULL, NULL, &w);
169 	clearerr(fp);
170     }
171     return 1;
172 }
173 
174 void
ttywrite(char * buf,int len)175 ttywrite (char *buf, int len)
176 {
177     fwrite(buf, 1, len, stdout);
178 }
179 
180 void
ttynowrite(char * buf,int len)181 ttynowrite (char *buf, int len)
182 {
183     /* do nothing */
184 }
185 
186 void
ttyplay(FILE * fp,double speed,ReadFunc read_func,WriteFunc write_func,WaitFunc wait_func)187 ttyplay (FILE *fp, double speed, ReadFunc read_func,
188 	 WriteFunc write_func, WaitFunc wait_func)
189 {
190     int first_time = 1;
191     struct timeval prev;
192 
193     setbuf(stdout, NULL);
194     setbuf(fp, NULL);
195 
196     while (1) {
197 	char *buf;
198 	Header h;
199 
200 	if (read_func(fp, &h, &buf) == 0) {
201 	    break;
202 	}
203 
204 	if (!first_time) {
205 	    speed = wait_func(prev, h.tv, speed);
206 	}
207 	first_time = 0;
208 
209 	write_func(buf, h.len);
210 	prev = h.tv;
211 	free(buf);
212     }
213 }
214 
215 void
ttyskipall(FILE * fp)216 ttyskipall (FILE *fp)
217 {
218     /*
219      * Skip all records.
220      */
221     ttyplay(fp, 0, ttyread, ttynowrite, ttynowait);
222 }
223 
ttyplayback(FILE * fp,double speed,ReadFunc read_func,WaitFunc wait_func)224 void ttyplayback (FILE *fp, double speed,
225 		  ReadFunc read_func, WaitFunc wait_func)
226 {
227     ttyplay(fp, speed, ttyread, ttywrite, wait_func);
228 }
229 
ttypeek(FILE * fp,double speed,ReadFunc read_func,WaitFunc wait_func)230 void ttypeek (FILE *fp, double speed,
231 	      ReadFunc read_func, WaitFunc wait_func)
232 {
233     ttyskipall(fp);
234     ttyplay(fp, speed, ttypread, ttywrite, ttynowait);
235 }
236 
237 
238 void
usage(void)239 usage (void)
240 {
241     printf("Usage: ttyplay [OPTION] [FILE]\n");
242     printf("  -s SPEED Set speed to SPEED [1.0]\n");
243     printf("  -n       No wait mode\n");
244     printf("  -p       Peek another person's ttyrecord\n");
245     exit(EXIT_FAILURE);
246 }
247 
248 /*
249  * We do some tricks so that select(2) properly works on
250  * STDIN_FILENO in ttywait().
251  */
252 FILE *
input_from_stdin(void)253 input_from_stdin (void)
254 {
255     FILE *fp;
256     int fd = edup(STDIN_FILENO);
257     edup2(STDOUT_FILENO, STDIN_FILENO);
258     return efdopen(fd, "r");
259 }
260 
261 int
main(int argc,char ** argv)262 main (int argc, char **argv)
263 {
264     double speed = 1.0;
265     ReadFunc read_func  = ttyread;
266     WaitFunc wait_func  = ttywait;
267     ProcessFunc process = ttyplayback;
268     FILE *input = NULL;
269     struct termios old, new;
270 
271     set_progname(argv[0]);
272     while (1) {
273         int ch = getopt(argc, argv, "s:np");
274         if (ch == EOF) {
275             break;
276 	}
277 	switch (ch) {
278 	case 's':
279 	    if (optarg == NULL) {
280 		perror("-s option requires an argument");
281 		exit(EXIT_FAILURE);
282 	    }
283 	    sscanf(optarg, "%lf", &speed);
284 	    break;
285 	case 'n':
286 	    wait_func = ttynowait;
287 	    break;
288 	case 'p':
289 	    process = ttypeek;
290 	    break;
291 	default:
292 	    usage();
293 	}
294     }
295 
296     if (optind < argc) {
297 	input = efopen(argv[optind], "r");
298     } else {
299         input = input_from_stdin();
300     }
301     assert(input != NULL);
302 
303     tcgetattr(0, &old); /* Get current terminal state */
304     new = old;          /* Make a copy */
305     new.c_lflag &= ~(ICANON | ECHO | ECHONL); /* unbuffered, no echo */
306     tcsetattr(0, TCSANOW, &new); /* Make it current */
307 
308     process(input, speed, read_func, wait_func);
309     tcsetattr(0, TCSANOW, &old);  /* Return terminal state */
310 
311     return 0;
312 }
313