]> git.sur5r.net Git - tio/blob - src/tty.c
New upstream version 1.34
[tio] / src / tty.c
1 /*
2  * tio - a simple TTY terminal I/O tool
3  *
4  * Copyright (c) 2014-2022  Martin Lund
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19  * 02110-1301, USA.
20  */
21
22 #include "config.h"
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <unistd.h>
26 #include <string.h>
27 #include <stdlib.h>
28 #include <limits.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <sys/param.h>
32 #include <sys/file.h>
33 #include <sys/ioctl.h>
34 #include <fcntl.h>
35 #include <termios.h>
36 #include <stdbool.h>
37 #include <errno.h>
38 #include <time.h>
39 #include <dirent.h>
40 #include "config.h"
41 #include "tty.h"
42 #include "print.h"
43 #include "options.h"
44 #include "misc.h"
45 #include "log.h"
46 #include "error.h"
47
48 #ifdef HAVE_TERMIOS2
49 extern int setspeed2(int fd, int baudrate);
50 #endif
51
52 #ifdef __APPLE__
53 #define PATH_SERIAL_DEVICES "/dev/"
54 #else
55 #define PATH_SERIAL_DEVICES "/dev/serial/by-id/"
56 #endif
57
58 static struct termios tio, tio_old, stdout_new, stdout_old, stdin_new, stdin_old;
59 static unsigned long rx_total = 0, tx_total = 0;
60 static bool connected = false;
61 static bool print_mode = NORMAL;
62 static bool standard_baudrate = true;
63 static void (*print)(char c);
64 static int fd;
65 static bool map_i_nl_crnl = false;
66 static bool map_o_cr_nl = false;
67 static bool map_o_nl_crnl = false;
68 static bool map_o_del_bs = false;
69
70
71 static void toggle_line(const char *line_name, int mask)
72 {
73     int state;
74
75     if (ioctl(fd, TIOCMGET, &state) < 0)
76     {
77         error_printf("Could not get line state: %s", strerror(errno));
78     }
79     else
80     {
81         if (state & mask)
82         {
83             state &= ~mask;
84             tio_printf("set %s to LOW", line_name);
85         }
86         else
87         {
88             state |= mask;
89             tio_printf("set %s to HIGH", line_name);
90         }
91         if (ioctl(fd, TIOCMSET, &state) < 0)
92             error_printf("Could not set line state: %s", strerror(errno));
93     }
94 }
95
96 void handle_command_sequence(char input_char, char previous_char, char *output_char, bool *forward)
97 {
98     char unused_char;
99     bool unused_bool;
100     int state;
101
102     /* Ignore unused arguments */
103     if (output_char == NULL)
104         output_char = &unused_char;
105
106     if (forward == NULL)
107         forward = &unused_bool;
108
109     /* Handle escape key commands */
110     if (previous_char == KEY_CTRL_T)
111     {
112         /* Do not forward input char to output by default */
113         *forward = false;
114
115         switch (input_char)
116         {
117             case KEY_QUESTION:
118                 tio_printf("Key commands:");
119                 tio_printf(" ctrl-t ?   List available key commands");
120                 tio_printf(" ctrl-t b   Send break");
121                 tio_printf(" ctrl-t c   Show configuration");
122                 tio_printf(" ctrl-t d   Toggle DTR line");
123                 tio_printf(" ctrl-t e   Toggle local echo mode");
124                 tio_printf(" ctrl-t h   Toggle hexadecimal mode");
125                 tio_printf(" ctrl-t l   Clear screen");
126                 tio_printf(" ctrl-t L   Show lines state");
127                 tio_printf(" ctrl-t q   Quit");
128                 tio_printf(" ctrl-t r   Toggle RTS line");
129                 tio_printf(" ctrl-t s   Show statistics");
130                 tio_printf(" ctrl-t t   Send ctrl-t key code");
131                 tio_printf(" ctrl-t T   Toggle line timestamp mode");
132                 tio_printf(" ctrl-t v   Show version");
133                 break;
134
135             case KEY_SHIFT_L:
136                 if (ioctl(fd, TIOCMGET, &state) < 0)
137                 {
138                     error_printf("Could not get line state: %s", strerror(errno));
139                     break;
140                 }
141                 tio_printf("Lines state:");
142                 tio_printf(" DTR: %s", (state & TIOCM_DTR) ? "HIGH" : "LOW");
143                 tio_printf(" RTS: %s", (state & TIOCM_RTS) ? "HIGH" : "LOW");
144                 tio_printf(" CTS: %s", (state & TIOCM_CTS) ? "HIGH" : "LOW");
145                 tio_printf(" DSR: %s", (state & TIOCM_DSR) ? "HIGH" : "LOW");
146                 tio_printf(" DCD: %s", (state & TIOCM_CD) ? "HIGH" : "LOW");
147                 tio_printf(" RI : %s", (state & TIOCM_RI) ? "HIGH" : "LOW");
148                 break;
149             case KEY_D:
150                 toggle_line("DTR", TIOCM_DTR);
151                 break;
152
153             case KEY_R:
154                 toggle_line("RTS", TIOCM_RTS);
155                 break;
156
157             case KEY_B:
158                 tcsendbreak(fd, 0);
159                 break;
160
161             case KEY_C:
162                 tio_printf("Configuration:");
163                 tio_printf(" TTY device: %s", option.tty_device);
164                 tio_printf(" Baudrate: %u", option.baudrate);
165                 tio_printf(" Databits: %d", option.databits);
166                 tio_printf(" Flow: %s", option.flow);
167                 tio_printf(" Stopbits: %d", option.stopbits);
168                 tio_printf(" Parity: %s", option.parity);
169                 tio_printf(" Local echo: %s", option.local_echo ? "enabled" : "disabled");
170                 tio_printf(" Timestamps: %s", option.timestamp ? "enabled" : "disabled");
171                 tio_printf(" Output delay: %d", option.output_delay);
172                 tio_printf(" Auto connect: %s", option.no_autoconnect ? "disabled" : "enabled");
173                 if (option.map[0] != 0)
174                     tio_printf(" Map flags: %s", option.map);
175                 if (option.log)
176                     tio_printf(" Log file: %s", option.log_filename);
177                 break;
178
179             case KEY_E:
180                 option.local_echo = !option.local_echo;
181                 break;
182
183             case KEY_H:
184                 /* Toggle hexadecimal printing mode */
185                 if (print_mode == NORMAL)
186                 {
187                     print = print_hex;
188                     print_mode = HEX;
189                     tio_printf("Switched to hexadecimal mode");
190                 }
191                 else
192                 {
193                     print = print_normal;
194                     print_mode = NORMAL;
195                     tio_printf("Switched to normal mode");
196                 }
197                 break;
198
199             case KEY_L:
200                 /* Clear screen using ANSI/VT100 escape code */
201                 printf("\033c");
202                 fflush(stdout);
203                 break;
204
205             case KEY_Q:
206                 /* Exit upon ctrl-t q sequence */
207                 exit(EXIT_SUCCESS);
208
209             case KEY_S:
210                 /* Show tx/rx statistics upon ctrl-t s sequence */
211                 tio_printf("Statistics:");
212                 tio_printf(" Sent %lu bytes", tx_total);
213                 tio_printf(" Received %lu bytes", rx_total);
214                 break;
215
216             case KEY_T:
217                 /* Send ctrl-t key code upon ctrl-t t sequence */
218                 *output_char = KEY_CTRL_T;
219                 *forward = true;
220                 break;
221
222             case KEY_SHIFT_T:
223                 option.timestamp = !option.timestamp;
224                 break;
225
226             case KEY_V:
227                 tio_printf("tio v%s", VERSION);
228                 break;
229
230             default:
231                 /* Ignore unknown ctrl-t escaped keys */
232                 break;
233         }
234     }
235 }
236
237 void stdin_configure(void)
238 {
239     int status;
240
241     /* Save current stdin settings */
242     if (tcgetattr(STDIN_FILENO, &stdin_old) < 0)
243     {
244         error_printf("Saving current stdin settings failed");
245         exit(EXIT_FAILURE);
246     }
247
248     /* Prepare new stdin settings */
249     memcpy(&stdin_new, &stdin_old, sizeof(stdin_old));
250
251     /* Reconfigure stdin (RAW configuration) */
252     stdin_new.c_iflag &= ~(ICRNL); // Do not translate CR -> NL on input
253     stdin_new.c_oflag &= ~(OPOST);
254     stdin_new.c_lflag &= ~(ECHO|ICANON|ISIG|ECHOE|ECHOK|ECHONL);
255
256     /* Control characters */
257     stdin_new.c_cc[VTIME] = 0; /* Inter-character timer unused */
258     stdin_new.c_cc[VMIN]  = 1; /* Blocking read until 1 character received */
259
260     /* Activate new stdin settings */
261     status = tcsetattr(STDIN_FILENO, TCSANOW, &stdin_new);
262     if (status == -1)
263     {
264         error_printf("Could not apply new stdin settings (%s)", strerror(errno));
265         exit(EXIT_FAILURE);
266     }
267
268     /* Make sure we restore old stdin settings on exit */
269     atexit(&stdin_restore);
270 }
271
272 void stdin_restore(void)
273 {
274     tcsetattr(STDIN_FILENO, TCSANOW, &stdin_old);
275 }
276
277 void stdout_configure(void)
278 {
279     int status;
280
281     /* Disable line buffering in stdout. This is necessary if we
282      * want things like local echo to work correctly. */
283     setbuf(stdout, NULL);
284
285     /* Save current stdout settings */
286     if (tcgetattr(STDOUT_FILENO, &stdout_old) < 0)
287     {
288         error_printf("Saving current stdio settings failed");
289         exit(EXIT_FAILURE);
290     }
291
292     /* Prepare new stdout settings */
293     memcpy(&stdout_new, &stdout_old, sizeof(stdout_old));
294
295     /* Reconfigure stdout (RAW configuration) */
296     stdout_new.c_oflag &= ~(OPOST);
297     stdout_new.c_lflag &= ~(ECHO|ICANON|ISIG|ECHOE|ECHOK|ECHONL);
298
299     /* Control characters */
300     stdout_new.c_cc[VTIME] = 0; /* Inter-character timer unused */
301     stdout_new.c_cc[VMIN]  = 1; /* Blocking read until 1 character received */
302
303     /* Activate new stdout settings */
304     status = tcsetattr(STDOUT_FILENO, TCSANOW, &stdout_new);
305     if (status == -1)
306     {
307         error_printf("Could not apply new stdout settings (%s)", strerror(errno));
308         exit(EXIT_FAILURE);
309     }
310
311     /* At start use normal print function */
312     print = print_normal;
313
314     /* Make sure we restore old stdout settings on exit */
315     atexit(&stdout_restore);
316 }
317
318 void stdout_restore(void)
319 {
320     tcsetattr(STDOUT_FILENO, TCSANOW, &stdout_old);
321 }
322
323 void tty_configure(void)
324 {
325     bool token_found = true;
326     char *token = NULL;
327     char *buffer;
328     int status;
329     speed_t baudrate;
330
331     memset(&tio, 0, sizeof(tio));
332
333     /* Set speed */
334     switch (option.baudrate)
335     {
336         /* The macro below expands into switch cases autogenerated by the
337          * configure script. Each switch case verifies and configures the baud
338          * rate and is of the form:
339          *
340          * case $baudrate: baudrate = B$baudrate; break;
341          *
342          * Only switch cases for baud rates detected supported by the host
343          * system are inserted.
344          *
345          * To see which baud rates are being probed see meson.build
346          */
347         BAUDRATE_CASES
348
349         default:
350 #ifdef HAVE_TERMIOS2
351             standard_baudrate = false;
352             break;
353 #else
354             error_printf("Invalid baud rate");
355             exit(EXIT_FAILURE);
356 #endif
357     }
358
359     if (standard_baudrate)
360     {
361         // Set input speed
362         status = cfsetispeed(&tio, baudrate);
363         if (status == -1)
364         {
365             error_printf("Could not configure input speed (%s)", strerror(errno));
366             exit(EXIT_FAILURE);
367         }
368
369         // Set output speed
370         status = cfsetospeed(&tio, baudrate);
371         if (status == -1)
372         {
373             error_printf("Could not configure output speed (%s)", strerror(errno));
374             exit(EXIT_FAILURE);
375         }
376     }
377
378     /* Set databits */
379     tio.c_cflag &= ~CSIZE;
380     switch (option.databits)
381     {
382         case 5:
383             tio.c_cflag |= CS5;
384             break;
385         case 6:
386             tio.c_cflag |= CS6;
387             break;
388         case 7:
389             tio.c_cflag |= CS7;
390             break;
391         case 8:
392             tio.c_cflag |= CS8;
393             break;
394         default:
395             error_printf("Invalid data bits");
396             exit(EXIT_FAILURE);
397     }
398
399     /* Set flow control */
400     if (strcmp("hard", option.flow) == 0)
401     {
402         tio.c_cflag |= CRTSCTS;
403         tio.c_iflag &= ~(IXON | IXOFF | IXANY);
404     }
405     else if (strcmp("soft", option.flow) == 0)
406     {
407         tio.c_cflag &= ~CRTSCTS;
408         tio.c_iflag |= IXON | IXOFF;
409     }
410     else if (strcmp("none", option.flow) == 0)
411     {
412         tio.c_cflag &= ~CRTSCTS;
413         tio.c_iflag &= ~(IXON | IXOFF | IXANY);
414     }
415     else
416     {
417         error_printf("Invalid flow control");
418         exit(EXIT_FAILURE);
419     }
420
421     /* Set stopbits */
422     switch (option.stopbits)
423     {
424         case 1:
425             tio.c_cflag &= ~CSTOPB;
426             break;
427         case 2:
428             tio.c_cflag |= CSTOPB;
429             break;
430         default:
431             error_printf("Invalid stop bits");
432             exit(EXIT_FAILURE);
433     }
434
435     /* Set parity */
436     if (strcmp("odd", option.parity) == 0)
437     {
438         tio.c_cflag |= PARENB;
439         tio.c_cflag |= PARODD;
440     }
441     else if (strcmp("even", option.parity) == 0)
442     {
443         tio.c_cflag |= PARENB;
444         tio.c_cflag &= ~PARODD;
445     }
446     else if (strcmp("none", option.parity) == 0)
447         tio.c_cflag &= ~PARENB;
448     else
449     {
450         error_printf("Invalid parity");
451         exit(EXIT_FAILURE);
452     }
453
454     /* Control, input, output, local modes for tty device */
455     tio.c_cflag |= CLOCAL | CREAD;
456     tio.c_oflag = 0;
457     tio.c_lflag = 0;
458
459     /* Control characters */
460     tio.c_cc[VTIME] = 0; // Inter-character timer unused
461     tio.c_cc[VMIN]  = 1; // Blocking read until 1 character received
462
463     /* Configure any specified input or output mappings */
464     buffer = strdup(option.map);
465     while (token_found == true)
466     {
467         if (token == NULL)
468             token = strtok(buffer,",");
469         else
470             token = strtok(NULL, ",");
471
472         if (token != NULL)
473         {
474             if (strcmp(token,"INLCR") == 0)
475                 tio.c_iflag |= INLCR;
476             else if (strcmp(token,"IGNCR") == 0)
477                 tio.c_iflag |= IGNCR;
478             else if (strcmp(token,"ICRNL") == 0)
479                 tio.c_iflag |= ICRNL;
480             else if (strcmp(token,"OCRNL") == 0)
481                 map_o_cr_nl = true;
482             else if (strcmp(token,"ODELBS") == 0)
483                 map_o_del_bs = true;
484             else if (strcmp(token,"INLCRNL") == 0)
485                 map_i_nl_crnl = true;
486             else if (strcmp(token, "ONLCRNL") == 0)
487                 map_o_nl_crnl = true;
488             else
489             {
490                 printf("Error: Unknown mapping flag %s\n", token);
491                 exit(EXIT_FAILURE);
492             }
493         }
494         else
495             token_found = false;
496     }
497     free(buffer);
498 }
499
500 void tty_wait_for_device(void)
501 {
502     fd_set rdfs;
503     int    status;
504     struct timeval tv;
505     static char input_char, previous_char = 0;
506     static bool first = true;
507     static int last_errno = 0;
508
509     /* Loop until device pops up */
510     while (true)
511     {
512         if (first)
513         {
514             /* Don't wait first time */
515             tv.tv_sec = 0;
516             tv.tv_usec = 1;
517             first = false;
518         } else
519         {
520             /* Wait up to 1 second */
521             tv.tv_sec = 1;
522             tv.tv_usec = 0;
523         }
524
525         FD_ZERO(&rdfs);
526         FD_SET(STDIN_FILENO, &rdfs);
527
528         /* Block until input becomes available or timeout */
529         status = select(STDIN_FILENO + 1, &rdfs, NULL, NULL, &tv);
530         if (status > 0)
531         {
532             /* Input from stdin ready */
533
534             /* Read one character */
535             status = read(STDIN_FILENO, &input_char, 1);
536             if (status <= 0)
537             {
538                 error_printf("Could not read from stdin");
539                 exit(EXIT_FAILURE);
540             }
541
542             /* Handle commands */
543             handle_command_sequence(input_char, previous_char, NULL, NULL);
544
545             previous_char = input_char;
546
547         } else if (status == -1)
548         {
549             error_printf("select() failed (%s)", strerror(errno));
550             exit(EXIT_FAILURE);
551         }
552
553         /* Test for accessible device file */
554         status = access(option.tty_device, R_OK);
555         if (status == 0) {
556             last_errno = 0;
557             return;
558         }
559         else if (last_errno != errno)
560         {
561             warning_printf("Could not open tty device (%s)", strerror(errno));
562             tio_printf("Waiting for tty device..");
563             last_errno = errno;
564         }
565     }
566 }
567
568 void tty_disconnect(void)
569 {
570     if (connected)
571     {
572         tio_printf("Disconnected");
573         flock(fd, LOCK_UN);
574         close(fd);
575         connected = false;
576     }
577 }
578
579 void tty_restore(void)
580 {
581     tcsetattr(fd, TCSANOW, &tio_old);
582
583     if (connected)
584         tty_disconnect();
585 }
586
587 static void optional_local_echo(char c)
588 {
589     if (!option.local_echo)
590         return;
591     print(c);
592     fflush(stdout);
593     if (option.log)
594         log_write(c);
595 }
596
597 int tty_connect(void)
598 {
599     fd_set rdfs;           /* Read file descriptor set */
600     int    maxfd;          /* Maximum file descriptor used */
601     char   input_char, output_char;
602     static char previous_char = 0;
603     static bool first = true;
604     int    status;
605     bool next_timestamp = false;
606     char*  now = NULL;
607
608     /* Open tty device */
609 #ifdef __APPLE__
610     fd = open(option.tty_device, O_RDWR | O_NOCTTY | O_NONBLOCK );
611 #else
612     fd = open(option.tty_device, O_RDWR | O_NOCTTY);
613 #endif
614     if (fd < 0)
615     {
616         error_printf_silent("Could not open tty device (%s)", strerror(errno));
617         goto error_open;
618     }
619
620     /* Make sure device is of tty type */
621     if (!isatty(fd))
622     {
623         error_printf("Not a tty device");
624         exit(EXIT_FAILURE);;
625     }
626
627     /* Lock device file */
628     status = flock(fd, LOCK_EX | LOCK_NB);
629     if ((status == -1) && (errno == EWOULDBLOCK))
630     {
631         error_printf("Device file is locked by another process");
632         exit(EXIT_FAILURE);
633     }
634
635     /* Flush stale I/O data (if any) */
636     tcflush(fd, TCIOFLUSH);
637
638     /* Print connect status */
639     tio_printf("Connected");
640     connected = true;
641     print_tainted = false;
642
643     if (option.timestamp)
644         next_timestamp = true;
645
646     /* Save current port settings */
647     if (tcgetattr(fd, &tio_old) < 0)
648         goto error_tcgetattr;
649
650     /* Make sure we restore tty settings on exit */
651     if (first)
652     {
653         atexit(&tty_restore);
654         first = false;
655     }
656
657     /* Activate new port settings */
658     status = tcsetattr(fd, TCSANOW, &tio);
659     if (status == -1)
660     {
661         error_printf_silent("Could not apply port settings (%s)", strerror(errno));
662         goto error_tcsetattr;
663     }
664
665 #ifdef HAVE_TERMIOS2
666     if (!standard_baudrate)
667     {
668         if (setspeed2(fd, option.baudrate) != 0)
669         {
670             error_printf_silent("Could not set baudrate speed (%s)", strerror(errno));
671             goto error_setspeed2;
672         }
673     }
674 #endif
675
676     maxfd = MAX(fd, STDIN_FILENO) + 1;  /* Maximum bit entry (fd) to test */
677
678     /* Input loop */
679     while (true)
680     {
681         FD_ZERO(&rdfs);
682         FD_SET(fd, &rdfs);
683         FD_SET(STDIN_FILENO, &rdfs);
684
685         /* Block until input becomes available */
686         status = select(maxfd, &rdfs, NULL, NULL, NULL);
687         if (status > 0)
688         {
689             if (FD_ISSET(fd, &rdfs))
690             {
691                 /* Input from tty device ready */
692                 if (read(fd, &input_char, 1) > 0)
693                 {
694                     /* Update receive statistics */
695                     rx_total++;
696
697                     /* Print timestamp on new line, if desired. */
698                     if (next_timestamp && input_char != '\n' && input_char != '\r')
699                     {
700                         now = current_time();
701                         if (now)
702                         {
703                             ansi_printf_raw("[%s] ", now);
704                             if (option.log)
705                             {
706                                 log_write('[');
707                                 while (*now != '\0')
708                                 {
709                                     log_write(*now);
710                                     ++now;
711                                 }
712                                 log_write(']');
713                                 log_write(' ');
714                             }
715                             next_timestamp = false;
716                         }
717                     }
718
719                     /* Map input character */
720                     if ((input_char == '\n') && (map_i_nl_crnl))
721                     {
722                         print('\r');
723                         print('\n');
724                         if (option.timestamp)
725                             next_timestamp = true;
726                     } else
727                     {
728                         /* Print received tty character to stdout */
729                         print(input_char);
730                     }
731                     fflush(stdout);
732
733                     /* Write to log */
734                     if (option.log)
735                         log_write(input_char);
736
737                     print_tainted = true;
738
739                     if (input_char == '\n' && option.timestamp)
740                         next_timestamp = true;
741                 } else
742                 {
743                     /* Error reading - device is likely unplugged */
744                     error_printf_silent("Could not read from tty device");
745                     goto error_read;
746                 }
747             }
748             if (FD_ISSET(STDIN_FILENO, &rdfs))
749             {
750                 bool forward = true;
751
752                 /* Input from stdin ready */
753                 status = read(STDIN_FILENO, &input_char, 1);
754                 if (status <= 0)
755                 {
756                     error_printf_silent("Could not read from stdin");
757                     goto error_read;
758                 }
759
760                 /* Forward input to output except ctrl-t key */
761                 output_char = input_char;
762                 if (input_char == KEY_CTRL_T)
763                     forward = false;
764
765                 /* Handle commands */
766                 handle_command_sequence(input_char, previous_char, &output_char, &forward);
767
768                 if (forward)
769                 {
770                     /* Map output character */
771                     if ((output_char == 127) && (map_o_del_bs))
772                         output_char = '\b';
773                     if ((output_char == '\r') && (map_o_cr_nl))
774                         output_char = '\n';
775
776                     /* Map newline character */
777                     if ((output_char == '\n' || output_char == '\r') && (map_o_nl_crnl)) {
778                         const char *crlf = "\r\n";
779
780                         optional_local_echo(crlf[0]);
781                         optional_local_echo(crlf[1]);
782                         status = write(fd, crlf, 2);
783                         if (status < 0)
784                             warning_printf("Could not write to tty device");
785
786                         tx_total += 2;
787                         delay(option.output_delay);
788                     } else
789                     {
790                         /* Send output to tty device */
791                         optional_local_echo(output_char);
792                         status = write(fd, &output_char, 1);
793                         if (status < 0)
794                             warning_printf("Could not write to tty device");
795                         fsync(fd);
796
797                         /* Update transmit statistics */
798                         tx_total++;
799
800                         /* Insert output delay */
801                         delay(option.output_delay);
802                     }
803                 }
804
805                 /* Save previous key */
806                 previous_char = input_char;
807
808             }
809         } else if (status == -1)
810         {
811             error_printf("Error: select() failed (%s)", strerror(errno));
812             exit(EXIT_FAILURE);
813         }
814     }
815
816     return TIO_SUCCESS;
817
818 #ifdef HAVE_TERMIOS2
819 error_setspeed2:
820 #endif
821 error_tcsetattr:
822 error_tcgetattr:
823 error_read:
824     tty_disconnect();
825 error_open:
826     return TIO_ERROR;
827 }
828
829 void list_serial_devices(void)
830 {
831     DIR *d = opendir(PATH_SERIAL_DEVICES);
832     if (d)
833     {
834         struct dirent *dir;
835         while ((dir = readdir(d)) != NULL)
836         {
837             if ((strcmp(dir->d_name, ".")) && (strcmp(dir->d_name, "..")))
838             {
839 #ifdef __APPLE__
840 #define TTY_DEVICES_PREFIX "tty."
841                 if (!strncmp(dir->d_name, TTY_DEVICES_PREFIX, sizeof(TTY_DEVICES_PREFIX) - 1))
842 #endif
843                 printf("%s%s\n", PATH_SERIAL_DEVICES, dir->d_name);
844             }
845         }
846         closedir(d);
847     }
848 }