/* * Serial data replay program. * * This is designed to take captured serial-line data, logged by (or as * if by) serialconsole's -vlog option, with lines that look like * * 1234567890.123456 <<< data\r\nhere\r\n * * where the number is a seconds-and-microseconds timestamp such as is * returned by gettimeofday(), the <<< is fixed syntax, and the data * is a blob of data, with backslash escapes: \a \b \t \n \f \r \e \\ * and up-to-3-digit octal. This replays it in real time, with all * timing offset to preserve the relative timing of the data. (Of * course, the absolute timing cannot be preserved.) * * The logged data comes in on stdin; the replayed data appears on * stdout. These are presumed to be redirected appropriately. */ #include #include #include #include #include #include extern const char *__progname; #define MAXLEN 256 static unsigned char rawtext[MAXLEN]; static int rawlen; static unsigned char proctext[MAXLEN]; static int proclen; static unsigned long long int tbase_in; static unsigned long long int tbase_clk; static void getline(void) { int c; rawlen = 0; while (1) { c = getchar(); if (c == EOF) exit(0); if (rawlen >= MAXLEN-1) { fprintf(stderr,"%s: input line overflow\n",__progname); exit(1); } if (c == '\n') { if ((rawlen > 0) && (rawtext[rawlen-1] == '\r')) rawlen --; rawtext[rawlen] = '\0'; return; } rawtext[rawlen++] = c; } } static int getdata(const char *dp) { int bq; int bql; unsigned char c; proclen = 0; bq = 0; while (*dp) { if (proclen >= MAXLEN-1) { fprintf(stderr,"%s: input data overflow\n",__progname); exit(1); } if (bq) { switch (*dp++) { case 'a': c = 7; bq = 0; break; case 'b': c = 8; bq = 0; break; case 't': c = 9; bq = 0; break; case 'n': c = 10; bq = 0; break; case 'f': c = 12; bq = 0; break; case 'r': c = 13; bq = 0; break; case 'e': c = 27; bq = 0; break; case '\\': c = '\\'; bq = 0; break; case '0': c = (c << 3) | 0; break; case '1': c = (c << 3) | 1; break; case '2': c = (c << 3) | 2; break; case '3': c = (c << 3) | 3; break; case '4': c = (c << 3) | 4; break; case '5': c = (c << 3) | 5; break; case '6': c = (c << 3) | 6; break; case '7': c = (c << 3) | 7; break; default: if (bql > 0) { bq = 0; dp --; proctext[proclen++] = c; continue; } fprintf(stderr,"%s: bad input line (bad \\ escape): %.*s\n",__progname,rawlen,&rawtext[0]); return(1); break; } if (! bq) { proctext[proclen++] = c; continue; } bql ++; if (bql >= 3) { bq = 0; proctext[proclen++] = c; } } else { if (*dp == '\\') { bq = 1; c = 0; bql = 0; dp ++; } else { proctext[proclen++] = *dp++; } } } if (bq) { if (bql) { proctext[proclen++] = c; } else { fprintf(stderr,"%s: bad input line (ends with \\): %.*s\n",__progname,rawlen,&rawtext[0]); return(1); } } return(0); } int main(void); int main(void) { unsigned long int sec; unsigned long int usec; char *lp; char *ep; unsigned long long int t; struct timeval nowtv; unsigned long long int tc; tbase_clk = 0; while (1) { getline(); lp = (void *)&rawtext[0]; sec = strtoul(lp,&ep,10); if (lp == ep) { fprintf(stderr,"%s: bad input line (no tv_sec): %.*s\n",__progname,rawlen,&rawtext[0]); continue; } if (*ep != '.') { fprintf(stderr,"%s: bad input line (dot isn't): %.*s\n",__progname,rawlen,&rawtext[0]); continue; } lp = ep + 1; usec = strtoul(lp,&ep,10); if (lp == ep) { fprintf(stderr,"%s: bad input line (no tv_usec): %.*s\n",__progname,rawlen,&rawtext[0]); continue; } if (usec >= 1000000UL) { fprintf(stderr,"%s: bad input line (tv_usec out of range): %.*s\n",__progname,rawlen,&rawtext[0]); continue; } t = (sec * 1000000ULL) + usec; if (strncmp(ep," <<< ",5)) { fprintf(stderr,"%s: bad input line (<<< isn't): %.*s\n",__progname,rawlen,&rawtext[0]); continue; } if (getdata(ep+5)) continue; gettimeofday(&nowtv,0); tc = (nowtv.tv_sec * 1000000ULL) + nowtv.tv_usec; if (tbase_clk == 0) { tbase_in = t; tbase_clk = tc; } if (tc < (tbase_clk + t - tbase_in)) { struct timespec ts; unsigned long long int d; d = tbase_clk + t - tbase_in - tc; ts.tv_sec = d / 1000000ULL; ts.tv_nsec = (d % 1000000ULL) * 1000ULL; nanosleep(&ts,0); } write(1,&proctext[0],proclen); } }