00001 /* 00002 Record and Playback Example 00003 AUP2, Sec. 4.10.2 00004 00005 Copyright 2003 by Marc J. Rochkind. All rights reserved. 00006 May be copied only for purposes and under conditions described 00007 on the Web page www.basepath.com/aup/copyright.htm. 00008 00009 The Example Files are provided "as is," without any warranty; 00010 without even the implied warranty of merchantability or fitness 00011 for a particular purpose. The author and his publisher are not 00012 responsible for any damages, direct or incidental, resulting 00013 from the use or non-use of these Example Files. 00014 00015 The Example Files may contain defects, and some contain deliberate 00016 coding mistakes that were included for educational reasons. 00017 You are responsible for determining if and how the Example Files 00018 are to be used. 00019 00020 */ 00021 #include "defs.h" 00022 #include "ptutil.h" 00023 #include "tc_setraw.h" 00024 #include <sys/time.h> 00025 #include <termios.h> 00026 00027 /* 00028 Only works if x >= y. 00029 */ 00030 /*[timeval_subtract]*/ 00031 static void timeval_subtract(const struct timeval *x, 00032 const struct timeval *y, struct timeval *diff) 00033 { 00034 if (x->tv_sec == y->tv_sec || x->tv_usec >= y->tv_usec) { 00035 diff->tv_sec = x->tv_sec - y->tv_sec; 00036 diff->tv_usec = x->tv_usec - y->tv_usec; 00037 } 00038 else { 00039 diff->tv_sec = x->tv_sec - 1 - y->tv_sec; 00040 diff->tv_usec = 1000000 + x->tv_usec - y->tv_usec; 00041 } 00042 } 00043 /*[get_rel_time]*/ 00044 static void get_rel_time(struct timeval *tv_rel) 00045 { 00046 static bool first = true; 00047 static struct timeval starttime; 00048 struct timeval tv; 00049 00050 if (first) { 00051 first = false; 00052 (void)gettimeofday(&starttime, NULL); 00053 } 00054 (void)gettimeofday(&tv, NULL); 00055 timeval_subtract(&tv, &starttime, tv_rel); 00056 } 00057 /*[event-struct]*/ 00058 #define EVFILE "recording.tmp" 00059 #define EVBUFSIZE 512 00060 00061 struct event { 00062 struct timeval e_time; 00063 unsigned e_datalen; 00064 }; 00065 static int fd_ev = -1; 00066 /*[ev_open]*/ 00067 static bool ev_open(void) 00068 { 00069 ec_neg1( fd_ev = open(EVFILE, O_RDONLY) ) 00070 return true; 00071 00072 EC_CLEANUP_BGN 00073 return false; 00074 EC_CLEANUP_END 00075 } 00076 /*[ev_creat]*/ 00077 static bool ev_creat(void) 00078 { 00079 ec_neg1( fd_ev = open(EVFILE, O_WRONLY | O_CREAT | O_TRUNC, 00080 PERM_FILE) ) 00081 return true; 00082 00083 EC_CLEANUP_BGN 00084 return false; 00085 EC_CLEANUP_END 00086 } 00087 /*[ev_write]*/ 00088 static bool ev_write(char *data, unsigned datalen) 00089 { 00090 struct event ev = { { 0 } }; 00091 00092 get_rel_time(&ev.e_time); 00093 ev.e_datalen = datalen; 00094 ec_neg1( writeall(fd_ev, &ev, sizeof(ev)) ) 00095 ec_neg1( writeall(fd_ev, data, datalen) ) 00096 return true; 00097 00098 EC_CLEANUP_BGN 00099 return false; 00100 EC_CLEANUP_END 00101 } 00102 /*[ev_read]*/ 00103 static bool ev_read(struct event *ev, char *data, unsigned datalen) 00104 { 00105 ssize_t nread; 00106 00107 ec_neg1( nread = read(fd_ev, ev, sizeof(*ev)) ) 00108 if (nread != sizeof(*ev)) { 00109 errno = EIO; 00110 EC_FAIL 00111 } 00112 ec_neg1( nread = read(fd_ev, data, ev->e_datalen) ) 00113 if (nread != ev->e_datalen) { 00114 errno = EIO; 00115 EC_FAIL 00116 } 00117 return true; 00118 00119 EC_CLEANUP_BGN 00120 return false; 00121 EC_CLEANUP_END 00122 } 00123 /*[ev_close]*/ 00124 static void ev_close(void) 00125 { 00126 (void)close(fd_ev); 00127 fd_ev = -1; 00128 } 00129 /*[ev_sleep]*/ 00130 static bool ev_sleep(struct timeval *tv) 00131 { 00132 struct timeval tv_rel, tv_diff; 00133 00134 get_rel_time(&tv_rel); 00135 if (tv->tv_sec > tv_rel.tv_sec || (tv->tv_sec == tv_rel.tv_sec && tv->tv_usec >= tv_rel.tv_usec)) { 00136 timeval_subtract(tv, &tv_rel, &tv_diff); 00137 (void)sleep(tv_diff.tv_sec); 00138 ec_neg1( usleep(tv_diff.tv_usec) ) 00139 } 00140 /* else we are already running late */ 00141 return true; 00142 00143 EC_CLEANUP_BGN 00144 return false; 00145 EC_CLEANUP_END 00146 } 00147 /*[playback]*/ 00148 static bool playback(void) 00149 { 00150 bool ok = false; 00151 struct event ev; 00152 char buf[EVBUFSIZE]; 00153 struct termios tbuf, tbufsave; 00154 00155 ec_neg1( tcgetattr(STDIN_FILENO, &tbuf) ) 00156 tbufsave = tbuf; 00157 tbuf.c_lflag &= ~ECHO; 00158 ec_neg1( tcsetattr(STDIN_FILENO, TCSAFLUSH, &tbuf) ) 00159 ec_false( ev_open() ) 00160 while (true) { 00161 ec_false( ev_read(&ev, buf, sizeof(buf)) ) 00162 if (ev.e_datalen == 0) 00163 break; 00164 ev_sleep(&ev.e_time); 00165 ec_neg1( writeall(STDOUT_FILENO, buf, ev.e_datalen) ) 00166 } 00167 ec_neg1( write(STDOUT_FILENO, "\n", 1) ) 00168 ok = true; 00169 EC_CLEANUP 00170 00171 EC_CLEANUP_BGN 00172 (void)tcdrain(STDOUT_FILENO); 00173 (void)sleep(1); /* Give the terminal a chance to respond. */ 00174 (void)tcsetattr(STDIN_FILENO, TCSAFLUSH, &tbufsave); 00175 ev_close(); 00176 return ok; 00177 EC_CLEANUP_END 00178 } 00179 /*[]*/ 00180 /* 00181 Following function won't be explained until Chap. 6. 00182 */ 00183 /*[exec_redirected]*/ 00184 bool exec_redirected(const char *file, char *const argv[], int fd_stdin, 00185 int fd_stdout, int fd_stderr) 00186 { 00187 if (fd_stdin != STDIN_FILENO) 00188 ec_neg1( dup2(fd_stdin, STDIN_FILENO) ) 00189 if (fd_stdout != STDOUT_FILENO) 00190 ec_neg1( dup2(fd_stdout, STDOUT_FILENO) ) 00191 if (fd_stderr != STDERR_FILENO) 00192 ec_neg1( dup2(fd_stderr, STDERR_FILENO) ) 00193 if (fd_stdin != STDIN_FILENO) 00194 (void)close(fd_stdin); 00195 if (fd_stdout != STDOUT_FILENO) 00196 (void)close(fd_stdout); 00197 if (fd_stderr != STDERR_FILENO) 00198 (void)close(fd_stderr); 00199 ec_neg1( execvp(file, argv) ) 00200 00201 EC_CLEANUP_BGN 00202 return false; 00203 EC_CLEANUP_END 00204 } 00205 /*[main-1]*/ 00206 int main(int argc, char *argv[]) 00207 { 00208 bool ok = false; 00209 PTINFO *p = NULL; 00210 00211 if (argc < 2) { 00212 fprintf(stderr, "Usage: record cmd ...\n record -p\n"); 00213 exit(EXIT_FAILURE); 00214 } 00215 if (strcmp(argv[1], "-p") == 0) { 00216 playback(); 00217 ok = true; 00218 EC_CLEANUP 00219 } 00220 /*[main-2]*/ 00221 ec_null( p = pt_open_master() ) 00222 switch (fork()) { 00223 case 0: 00224 ec_false( pt_open_slave(p) ) 00225 ec_false( exec_redirected(argv[1], &argv[1], PT_GET_SLAVE_FD(p), 00226 PT_GET_SLAVE_FD(p), PT_GET_SLAVE_FD(p)) ) 00227 break; 00228 case -1: 00229 EC_FAIL 00230 } 00231 ec_false( ev_creat() ) 00232 ec_false( pt_wait_master(p) ) 00233 /*[main-3]*/ 00234 tc_setraw(); 00235 while (true) { 00236 fd_set fd_set_read; 00237 char buf[EVBUFSIZE]; 00238 ssize_t nread; 00239 00240 FD_ZERO(&fd_set_read); 00241 FD_SET(STDIN_FILENO, &fd_set_read); 00242 FD_SET(PT_GET_MASTER_FD(p), &fd_set_read); 00243 ec_neg1( select(FD_SETSIZE, &fd_set_read, NULL, NULL, NULL) ) 00244 if (FD_ISSET(STDIN_FILENO, &fd_set_read)) { 00245 ec_neg1( nread = read(STDIN_FILENO, &buf, sizeof(buf)) ) 00246 ec_neg1( writeall(PT_GET_MASTER_FD(p), buf, nread) ) 00247 } 00248 if (FD_ISSET(PT_GET_MASTER_FD(p), &fd_set_read)) { 00249 if ((nread = read(PT_GET_MASTER_FD(p), &buf, 00250 sizeof(buf))) > 0) { 00251 ec_false( ev_write(buf, nread) ) 00252 ec_neg1( writeall(STDOUT_FILENO, buf, nread) ) 00253 } 00254 else if (nread == 0 || (nread == -1 && errno == EIO)) 00255 break; 00256 else 00257 EC_FAIL 00258 } 00259 } 00260 ec_neg1( ev_write(NULL, 0) ) 00261 fprintf(stderr, 00262 "EOF or error reading stdin or master pseudo-terminal; exiting\n"); 00263 ok = true; 00264 EC_CLEANUP 00265 /*[main-4]*/ 00266 EC_CLEANUP_BGN 00267 if (p != NULL) 00268 (void)pt_close_master(p); 00269 tc_restore(); 00270 ev_close(); 00271 printf("\n"); 00272 exit(ok ? EXIT_SUCCESS : EXIT_FAILURE); 00273 EC_CLEANUP_END 00274 } 00275 /*[]*/