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