Disabled stdout/stderr buffering
[slsnif.git] / src / slsnif.c
1
2 /*  slsnif.c
3  *  Copyright (C) 2001 Yan "Warrior" Gurtovoy (ymg@azstarnet.com)
4  *
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program; if not, write to the Free Software
17  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 #include "ascii.h"
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include "slsnif.h"
27
28 void copyright() {
29     printf("\n\nSerial Line Sniffer. Version %s\n", VERSION);
30     printf("\tCopyright (C) 2001 Yan \"Warrior\" Gurtovoy (ymg@azstarnet.com)\n\n");
31 }
32
33 void usage() {
34     copyright();
35     printf("Usage: slsnif [options] <port>\n\n");
36     printf("REQUIRED PARAMETERS:\n");
37     printf("  <port>     - serial port to use (i.e /dev/ttyS0, /dev/ttyS1, etc.)\n\n");
38     printf("OPTIONS:\n");
39     printf("  -h (--help)             - displays this help.\n");
40     printf("  -b (--bytes)            - print number of bytes transmitted on every read.\n");
41     printf("  -n (--nolock)           - don't try to lock the port.\n");
42     printf("  -t (--timestamp)        - print timestamp for every transmission.\n");
43     printf("  -l (--log) <logfile>    - file to store output in, defaults to stdout.\n");
44     printf("  -s (--speed) <speed>    - baudrate to use, defaults to 9600 baud.\n");
45     printf("  -p (--port2) <port2>    - serial port to use instead of pty.\n");
46     printf("  --color      <color>    - color to use for normal output.\n");
47     printf("  --timecolor  <color>    - color to use for timestamp.\n");
48     printf("  --bytescolor <color>    - color to use for number of bytes transmitted.\n\n");
49     printf("Following names are valid colors:\n");
50     printf(" \tblack, red, green, yellow, blue, magenta, cyan, white,\n");
51     printf("\tbrightblack,brightred, brightgreen, brightyellow,\n");
52     printf("\tbrightblue, brightmagenta, brightcyan, brightwhite\n\n");
53     printf("Example: slsnif -l log.txt -s 2400 /dev/ttyS1\n\n");
54 }
55
56 void fatalError(char *msg) {
57     perror(msg);
58     _exit(-1);
59 }
60
61 int setRaw(int fd, struct termios *ttystate_orig) {
62 /* set tty into raw mode */
63
64     struct termios    tty_state;
65         
66     if (tcgetattr(fd, &tty_state) < 0) return 0;
67     /* save off original settings */
68     *ttystate_orig = tty_state;
69     /* set raw mode */
70     tty_state.c_lflag &= ~(ICANON | IEXTEN | ISIG | ECHO);
71     tty_state.c_iflag &= ~(ICRNL | INPCK | ISTRIP | IXON | BRKINT);
72     tty_state.c_oflag &= ~OPOST;
73     tty_state.c_cflag |= CS8;   
74     tty_state.c_cc[VMIN]  = 1;
75     tty_state.c_cc[VTIME] = 0;
76     cfsetispeed(&tty_state, tty_data.baudrate);
77     cfsetospeed(&tty_state, tty_data.baudrate); 
78     if (tcsetattr(fd, TCSAFLUSH, &tty_state) < 0) return 0;
79     return 1;
80 }
81
82 void fmtData(unsigned char *in, char *out, int in_size) {
83 /* format data */
84
85     char    charbuf[15];
86     int     i;
87
88     /* flush output buffer */
89     out[0] = 0;
90     for (i = 0; i < in_size; i++) {
91         if (in[i] == 127) {
92             /* it's a DEL character */
93             sprintf(charbuf, "%s (%03i) ", DEL, 127);
94         } else {
95             if (in[i] < 33)
96                 /* it's a control character or space */
97                 sprintf(charbuf, "%s (%03i) ", ascii_chars[(int) in[i]], in[i]);
98             else
99                 /* it's a printable character */
100                 sprintf(charbuf, "%c (%03i) ", in[i], in[i]);
101         }
102         /* put formatted data into output buffer */
103         strcat(out, charbuf);
104     }
105 }
106 void setColor(int out, char *color) {
107 /* changes color on the terminal */
108
109     if (color[0]) {
110         write(out, color, 7);
111     } else {
112         write(out, colors[WHITE].color, 7);
113     }
114 }
115
116 void writeData(int in, int out, int aux, int mode) {
117 /* reads data from `in`, formats it, writes it to `out` and `aux`.
118  * mode 0 - read from pipe
119  * mode 1 - read from port
120  * mode 2 - read from pty
121  */
122
123     unsigned char   buffer[BUFFSIZE];
124     char            outbuf[BUFFSIZE * 16 + 1];
125     int             n;
126 #ifdef HAVE_SYS_TIMEB_H
127     struct timeb    tstamp;
128     char            tbuf[29];
129     char            tmp[25];
130     char            tmp1[4];
131 #else
132 #ifdef HAVE_TIME_H
133     time_t          tstamp;
134 #endif
135 #endif
136     if ((n = read(in, buffer, BUFFSIZE)) < 0) {
137         if (mode)
138             if (errno == EIO)
139                 sleep(1);
140             else
141                 perror(mode == 1 ? RPORTFAIL : RPTYFAIL);
142         else
143             perror(RPIPEFAIL);
144     } else {
145         if (n > 0) {
146             if (mode) {
147                 write(out, buffer, n);
148                 write(aux, buffer, n);                    
149             } else {
150                 /* print timestamp if necessary */
151                 if (tty_data.tstamp) {
152                     if (out == STDOUT_FILENO) setColor(out, tty_data.tclr);
153                     write(out, "\n\n", 2);
154 #ifdef HAVE_SYS_TIMEB_H
155                     ftime(&tstamp);
156                     tmp[0] = tmp1[0] = tbuf[0] = 0;
157                     strncat(tmp, ctime(&(tstamp.time)), 24);
158                     strncat(tbuf, tmp, 19);
159                     sprintf(tmp1, ".%2ui", tstamp.millitm);
160                     strncat(tbuf, tmp1, 3);
161                     strcat(tbuf, tmp + 19);
162                     write(out, tbuf, 28);
163 #else
164 #ifdef HAVE_TIME_H
165                     time(&tstamp);
166                     write(out, ctime(&tstamp), 24);
167 #endif
168 #endif                    
169                 } else {
170                     write(out, "\n", 1);
171                 }
172                 if (out == STDOUT_FILENO) setColor(out, tty_data.clr);
173                 /* print prefix */
174                 write(out, aux ? PORT_IN : PORT_OUT, PRFXSIZE);
175                 /* format data */
176                 fmtData(buffer, outbuf, n);
177                 /* print data */
178                 write(out, outbuf, strlen(outbuf));
179                 /* print total number of bytes if necessary */
180                 if (tty_data.dspbytes) {
181                     buffer[0] = 0;
182                     sprintf(buffer, "\n%s %i", TOTALBYTES, n);
183                     if (out == STDOUT_FILENO) setColor(out, tty_data.bclr);
184                     write (out, buffer, strlen(buffer));
185                 }
186             }
187         }
188     }
189 }
190
191 void pipeReader() {
192 /* get data drom pipes */
193
194     int             maxfd;
195     fd_set          read_set;
196
197     maxfd = max(tty_data.ptypipefd[0], tty_data.portpipefd[0]);
198     while (TRUE) {
199         FD_ZERO(&read_set);
200         FD_SET(tty_data.ptypipefd[0], &read_set);
201         FD_SET(tty_data.portpipefd[0], &read_set);
202         if (select(maxfd + 1, &read_set, NULL, NULL, NULL) < 0) {
203             perror(SELFAIL);
204             return;
205         }
206         if (FD_ISSET(tty_data.ptypipefd[0], &read_set))
207             writeData(tty_data.ptypipefd[0], tty_data.logfd, 0, 0);
208         else
209             if (FD_ISSET(tty_data.portpipefd[0], &read_set))
210                 writeData(tty_data.portpipefd[0], tty_data.logfd, 1, 0);
211     }
212 }
213
214 void closeAll() {
215 /* close all opened file descriptors */
216     /* unlock the port(s) if necessary */
217     if (!tty_data.nolock) {
218         if (tty_data.portName && tty_data.portName[0])
219             dev_unlock(tty_data.portName);
220         /* this pointer should be NULL if pty is used */
221         if (tty_data.ptyName) dev_unlock(tty_data.ptyName);
222     }
223     /* restore color */
224     if (tty_data.logfd == STDOUT_FILENO) 
225            setColor(tty_data.logfd, colors[WHITE].color);
226     /* restore settings on pty */
227     if (tty_data.ptyraw)
228         tcsetattr(tty_data.ptyfd, TCSAFLUSH, &tty_data.ptystate_orig);
229     /* close pty */
230     if (tty_data.ptyfd >= 0) close(tty_data.ptyfd);
231     /* restore settings on port */
232     if (tty_data.portraw)
233         tcsetattr(tty_data.portfd, TCSAFLUSH, &tty_data.portstate_orig);
234     /* close port */
235     if (tty_data.portfd >= 0) close(tty_data.portfd);
236     /* close log file */
237     write(tty_data.logfd, "\n", 1);
238     if (tty_data.logfd != STDOUT_FILENO && tty_data.logfd >= 0)
239         if ((close(tty_data.logfd)) < 0) perror(CLOSEFAIL);
240     /* close write pipes */
241     if (tty_data.ptypipefd[1] >= 0) close(tty_data.ptypipefd[1]);
242     if (tty_data.portpipefd[1] >= 0) close(tty_data.portpipefd[1]);
243     /* free allocated memory for portName */
244     if (tty_data.portName) free(tty_data.portName);
245     if (pid >= 0) kill(pid, SIGINT);
246 }
247
248 RETSIGTYPE sigintP(int sig) {
249 /*parent signal handler for SIGINT */
250     closeAll();
251     _exit(1);
252 }
253
254 RETSIGTYPE sigintC(int sig) {
255 /* child signal handler for SIGINT */
256     /* close read pipes */
257     if (tty_data.ptypipefd[0] >= 0) close(tty_data.ptypipefd[0]);
258     if (tty_data.portpipefd[0] >= 0) close(tty_data.portpipefd[0]);    
259     _exit(1);    
260 }
261
262 RETSIGTYPE sigchldP(int sig) {
263 /* signal handler for SIGCHLD */
264
265     int status;
266     
267     wait(&status);
268 }
269
270 char *getColor(char *name) {
271 /* returns escape sequence that corresponds to a given color name */
272
273     int i = 0;
274
275     while (colors[i].name && strcmp(name, colors[i].name)) i++;
276     if (colors[i].name) return colors[i].color;
277     return NULL;
278 }
279
280 int main(int argc, char *argv[]) {
281     
282     int             i, j, maxfd, optret, needsync = 1;
283     char            *logName = NULL, baudstr[7], *ptr1, *ptr2;
284     struct termios  tty_state;
285     fd_set          rset;
286     
287 #ifdef HAVE_GETOPT_LONG
288     struct option longopts[] = {
289         {"help",       0, NULL, 'h'},
290         {"log",        1, NULL, 'l'},
291         {"nolock",     0, NULL, 'n'},
292         {"port2",      1, NULL, 'p'},
293         {"speed",      1, NULL, 's'},
294         {"bytes",      0, NULL, 'b'},
295         {"timestamp",  0, NULL, 't'},
296         {"color",      1, NULL, 'x'},
297         {"timecolor",  1, NULL, 'y'},
298         {"bytescolor", 1, NULL, 'z'},
299         {NULL,         0, NULL,   0}
300     };
301 #endif
302
303     setvbuf(stdout,NULL,_IONBF,0);
304     setvbuf(stderr,NULL,_IONBF,0);
305
306     /* initialize variables */
307     baudstr[0] = 0;
308     tty_data.portName = tty_data.ptyName = NULL;
309     tty_data.ptyfd = tty_data.portfd = tty_data.logfd = -1;
310     tty_data.ptyraw = tty_data.portraw = tty_data.nolock = FALSE;
311     tty_data.dspbytes = tty_data.tstamp = FALSE;
312     tty_data.ptypipefd[0] = tty_data.ptypipefd[1] = -1;
313     tty_data.portpipefd[0] = tty_data.portpipefd[1] = -1;
314     tty_data.baudrate = DEFBAUDRATE;
315     tty_data.clr[0] = tty_data.bclr[0] = tty_data.tclr[0] = 0;
316     /* parse rc-file */
317     readRC(&tty_data);
318     /* activate signal handlers */
319     signal(SIGINT, sigintP);
320     signal(SIGCHLD, sigchldP);
321     /* register closeAll() function to be called on normal termination */
322     atexit(closeAll);
323     /* process command line arguments */
324 #ifdef HAVE_GETOPT_LONG
325     while ((optret = getopt_long(argc, argv, OPTSTR, longopts, NULL)) != -1) {
326 #else
327     while ((optret = getopt(argc, argv, OPTSTR)) != -1) {
328 #endif
329         switch (optret) {
330             case 'b':
331                 tty_data.dspbytes = TRUE;
332                 break;
333             case 't':
334                 tty_data.tstamp = TRUE;
335                 break;
336             case 'n':
337                 tty_data.nolock = TRUE;
338                 break;
339             case 'l':
340                 logName = (optarg[0] == '=' ? optarg + 1 : optarg);
341                 break;
342             case 's':
343                 i = 0;
344                 while (baudrates[i].spdstr &&
345                         strcmp(optarg, baudrates[i].spdstr)) i++;
346                 if (baudrates[i].spdstr) {
347                     tty_data.baudrate = baudrates[i].speed;
348                     strcat(baudstr, baudrates[i].spdstr);
349                 }
350                 break;
351             case 'p':
352                 tty_data.ptyName = (optarg[0] == '=' ? optarg + 1 : optarg);
353                 break;
354             case 'x':
355                 ptr1 = getColor(optarg);
356                 if (ptr1) {
357                     tty_data.clr[0] = 0;
358                     strcat(tty_data.clr, ptr1);
359                 }
360                 break;
361             case 'y':
362                 ptr1 = getColor(optarg);
363                 if (ptr1) {
364                     tty_data.tclr[0] = 0;
365                     strcat(tty_data.tclr, ptr1);
366                 }
367                 break;
368             case 'z':
369                 ptr1 = getColor(optarg);
370                 if (ptr1) {
371                     tty_data.bclr[0] = 0;
372                     strcat(tty_data.bclr, ptr1);
373                 }
374                 break;
375             case 'h':
376             case '?':
377             default :
378                 usage();
379                 tty_data.ptyName = NULL;
380                 return -1;
381         }
382     }
383     if (optind < argc) {
384            if (!(tty_data.portName = malloc(PORTNAMELEN))) fatalError(MEMFAIL);
385            ptr1 = argv[optind];
386            ptr2 = tty_data.portName;
387            while((*ptr1 == '/' || isalnum(*ptr1))
388                 && ptr2 - tty_data.portName < PORTNAMELEN - 1) *ptr2++ = *ptr1++;
389            *ptr2 = 0;
390     }
391     if (!tty_data.portName || !tty_data.portName[0]) {
392         usage();
393         tty_data.ptyName = NULL;
394         return -1;
395     }
396     copyright();
397     /* open logfile */
398     if (!logName)
399         tty_data.logfd = STDOUT_FILENO;
400     else {
401         if((tty_data.logfd = open(logName,
402                     O_RDWR|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR)) < 0) {
403             perror(LOGFAIL);
404             tty_data.logfd = STDOUT_FILENO;
405         } else {
406             printf("Started logging data into file '%s'.\n", logName);
407         }
408     }
409     /* create pipe */
410     if (pipe(tty_data.ptypipefd) < 0 || pipe(tty_data.portpipefd) < 0) {
411         perror(PIPEFAIL);
412         return -1;
413     }
414     /* fork child process */
415     switch (pid = fork()) {
416     case 0:
417         /* child process */
418         /* close write pipe */
419         close(tty_data.ptypipefd[1]);
420         close(tty_data.portpipefd[1]);
421         signal(SIGINT, sigintC);
422         pipeReader(&tty_data);
423         break;
424     case -1:
425         /* fork() failed */
426         perror(FORKFAIL);
427         return -1;
428     default:
429         /* parent process */
430         /* close read pipe */
431         close(tty_data.ptypipefd[0]);
432         close(tty_data.portpipefd[0]);
433         break;
434     }
435     if (!tty_data.ptyName) {
436         /* open master side of the pty */
437         /* Search for a free pty */
438         if (!(tty_data.ptyName = strdup(DEFPTRNAME))) fatalError(MEMFAIL);
439         for(i = 0; i < 16 && tty_data.ptyfd < 0; i++) {
440             tty_data.ptyName[8] = "pqrstuvwxyzPQRST"[i];
441             for(j = 0; j < 16 && tty_data.ptyfd < 0; j++) {
442                 tty_data.ptyName[9] = "0123456789abcdef"[j];
443                 /* try to open master side */
444                 tty_data.ptyfd = open(tty_data.ptyName, O_RDWR|O_NONBLOCK|O_NOCTTY);
445             } 
446         }
447         if (tty_data.ptyfd < 0) {
448             /* failed to find an available pty */
449             free(tty_data.ptyName);
450             /*set pointer to NULL as it will be checked in closeAll() */
451             tty_data.ptyName = NULL;
452             perror(PTYFAIL);
453             return -1;
454         }
455         /* create the name of the slave pty */
456         tty_data.ptyName[5] = 't';
457         printf("Opened pty: %s\n", tty_data.ptyName);
458         free(tty_data.ptyName);
459         /*set pointer to NULL as it will be checked in closeAll() */
460         tty_data.ptyName = NULL;
461     } else {
462         /* open port2 instead of pty */
463         /* lock port2 */
464         if (!tty_data.nolock && dev_setlock(tty_data.ptyName) == -1) {
465             /* couldn't lock the device */
466             return -1;
467         }
468         /* try to open port2 */
469         if ((tty_data.ptyfd = open(tty_data.ptyName, O_RDWR|O_NONBLOCK)) < 0) {
470             perror(PORTFAIL);
471             return -1;
472         }
473         printf("Opened port: %s\n", tty_data.ptyName);
474     }
475     /* set raw mode on pty */
476     if(!setRaw(tty_data.ptyfd, &tty_data.ptystate_orig)) {
477         perror(RAWFAIL);
478         return -1;
479     }
480     tty_data.ptyraw = TRUE;
481     /* lock port */
482     if (!tty_data.nolock && dev_setlock(tty_data.portName) == -1) {
483         /* couldn't lock the device */
484         return -1;
485     }
486     /* try to open port */
487     if ((tty_data.portfd = open(tty_data.portName, O_RDWR|O_NONBLOCK)) < 0) {
488         perror(PORTFAIL);
489         return -1;
490     }
491     printf("Opened port: %s\n", tty_data.portName);
492     /* set raw mode on port */
493     if (!setRaw(tty_data.portfd, &tty_data.portstate_orig)) {
494         perror(RAWFAIL);
495         return -1;
496     }
497     tty_data.portraw = TRUE;
498     printf("Baudrate is set to %s baud%s.\n",
499                 baudstr[0] ? baudstr : "9600",
500                 baudstr[0] ? "" : " (default)");
501     /* start listening to the slave and port */
502     maxfd = max(tty_data.ptyfd, tty_data.portfd);
503     while (TRUE) {
504         FD_ZERO(&rset);
505         FD_SET(tty_data.ptyfd, &rset);
506         FD_SET(tty_data.portfd, &rset);
507         if (select(maxfd + 1, &rset, NULL, NULL, NULL) < 0) {
508             perror(SELFAIL);
509             return -1;
510         }
511         if (FD_ISSET(tty_data.portfd, &rset)) {
512             /* data coming from device */
513             writeData(tty_data.portfd, tty_data.ptyfd, tty_data.portpipefd[1], 1);
514         } else {
515             if (FD_ISSET(tty_data.ptyfd, &rset)) {
516                 /* data coming from host */
517                 if (needsync) {
518                     printf("Syncronizing ports...");
519                     if (tcgetattr(tty_data.ptyfd, &tty_state) ||
520                             tcsetattr(tty_data.portfd, TCSANOW, &tty_state)) {
521                         perror(SYNCFAIL);
522                         return -1;
523                     }
524                     needsync = 0;
525                     printf("Done!\n");
526                 }
527                 writeData(tty_data.ptyfd, tty_data.portfd, tty_data.ptypipefd[1], 2);
528             }
529         }
530     }
531     return 1;
532 }