]> git.sur5r.net Git - tio/blob - src/tty.c
New upstream version 1.30
[tio] / src / tty.c
1 /*
2  * tio - a simple TTY terminal I/O application
3  *
4  * Copyright (c) 2014-2017  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 <fcntl.h>
34 #include <termios.h>
35 #include <stdbool.h>
36 #include <errno.h>
37 #include "config.h"
38 #include "tio/tty.h"
39 #include "tio/print.h"
40 #include "tio/options.h"
41 #include "tio/time.h"
42 #include "tio/log.h"
43 #include "tio/error.h"
44
45 #ifdef HAVE_TERMIOS2
46 extern int setspeed2(int fd, int baudrate);
47 #endif
48
49 static struct termios tio, tio_old, stdout_new, stdout_old, stdin_new, stdin_old;
50 static unsigned long rx_total = 0, tx_total = 0;
51 static bool connected = false;
52 static bool tainted = false;
53 static bool print_mode = NORMAL;
54 static bool standard_baudrate = true;
55 static void (*print)(char c);
56 static int fd;
57 static bool map_inlcrnl = false;
58 static bool map_onlcrnl = false;
59 static bool map_odelbs = false;
60
61 #define tio_printf(format, args...) \
62 { \
63     if (tainted) putchar('\n'); \
64     color_printf("[tio %s] " format, current_time(), ## args); \
65     tainted = false; \
66 }
67
68 static void print_hex(char c)
69 {
70     printf("%02x ", (unsigned char) c);
71 }
72
73 static void print_normal(char c)
74 {
75     putchar(c);
76 }
77
78 void handle_command_sequence(char input_char, char previous_char, char *output_char, bool *forward)
79 {
80     char unused_char;
81     bool unused_bool;
82
83     /* Ignore unused arguments */
84     if (output_char == NULL)
85         output_char = &unused_char;
86
87     if (forward == NULL)
88         forward = &unused_bool;
89
90     /* Handle escape key commands */
91     if (previous_char == KEY_CTRL_T)
92     {
93         /* Do not forward input char to output by default */
94         *forward = false;
95
96         switch (input_char)
97         {
98             case KEY_QUESTION:
99                 tio_printf("Key commands:");
100                 tio_printf(" ctrl-t ?   List available key commands");
101                 tio_printf(" ctrl-t b   Send break");
102                 tio_printf(" ctrl-t c   Show configuration");
103                 tio_printf(" ctrl-t h   Toggle hexadecimal mode");
104                 tio_printf(" ctrl-t l   Clear screen");
105                 tio_printf(" ctrl-t q   Quit");
106                 tio_printf(" ctrl-t s   Show statistics");
107                 tio_printf(" ctrl-t t   Send ctrl-t key code");
108                 break;
109
110             case KEY_B:
111                 tcsendbreak(fd, 0);
112                 break;
113
114             case KEY_C:
115                 tio_printf("Configuration:");
116                 tio_printf(" TTY device: %s", option.tty_device);
117                 tio_printf(" Baudrate: %u", option.baudrate);
118                 tio_printf(" Databits: %d", option.databits);
119                 tio_printf(" Flow: %s", option.flow);
120                 tio_printf(" Stopbits: %d", option.stopbits);
121                 tio_printf(" Parity: %s", option.parity);
122                 tio_printf(" Output delay: %d", option.output_delay);
123                 if (option.map[0] != 0)
124                     tio_printf(" Map flags: %s", option.map);
125                 if (option.log)
126                     tio_printf(" Log file: %s", option.log_filename);
127                 break;
128
129             case KEY_H:
130                 /* Toggle hexadecimal printing mode */
131                 if (print_mode == NORMAL)
132                 {
133                     print = print_hex;
134                     print_mode = HEX;
135                     tio_printf("Switched to hexadecimal mode");
136                 }
137                 else
138                 {
139                     print = print_normal;
140                     print_mode = NORMAL;
141                     tio_printf("Switched to normal mode");
142                 }
143                 break;
144
145             case KEY_L:
146                 /* Clear screen using ANSI/VT100 escape code */
147                 printf("\033c");
148                 fflush(stdout);
149                 break;
150
151             case KEY_Q:
152                 /* Exit upon ctrl-t q sequence */
153                 exit(EXIT_SUCCESS);
154
155             case KEY_S:
156                 /* Show tx/rx statistics upon ctrl-t s sequence */
157                 tio_printf("Statistics:");
158                 tio_printf(" Sent %lu bytes, received %lu bytes", tx_total, rx_total);
159                 break;
160
161             case KEY_T:
162                 /* Send ctrl-t key code upon ctrl-t t sequence */
163                 *output_char = KEY_CTRL_T;
164                 *forward = true;
165                 break;
166
167             default:
168                 /* Ignore unknown ctrl-t escaped keys */
169                 break;
170         }
171     }
172 }
173
174 void stdin_configure(void)
175 {
176     int status;
177
178     /* Save current stdin settings */
179     if (tcgetattr(STDIN_FILENO, &stdin_old) < 0)
180     {
181         error_printf("Saving current stdin settings failed");
182         exit(EXIT_FAILURE);
183     }
184
185     /* Prepare new stdin settings */
186     memcpy(&stdin_new, &stdin_old, sizeof(stdin_old));
187
188     /* Reconfigure stdin (RAW configuration) */
189     stdin_new.c_iflag &= ~(ICRNL); // Do not translate CR -> NL on input
190     stdin_new.c_oflag &= ~(OPOST);
191     stdin_new.c_lflag &= ~(ECHO|ICANON|ISIG|ECHOE|ECHOK|ECHONL);
192
193     /* Control characters */
194     stdin_new.c_cc[VTIME] = 0; /* Inter-character timer unused */
195     stdin_new.c_cc[VMIN]  = 1; /* Blocking read until 1 character received */
196
197     /* Activate new stdin settings */
198     status = tcsetattr(STDIN_FILENO, TCSANOW, &stdin_new);
199     if (status == -1)
200     {
201         error_printf("Could not apply new stdin settings (%s)", strerror(errno));
202         exit(EXIT_FAILURE);
203     }
204
205     /* Make sure we restore old stdin settings on exit */
206     atexit(&stdin_restore);
207 }
208
209 void stdin_restore(void)
210 {
211     tcsetattr(STDIN_FILENO, TCSANOW, &stdin_old);
212 }
213
214 void stdout_configure(void)
215 {
216     int status;
217
218     /* Save current stdout settings */
219     if (tcgetattr(STDOUT_FILENO, &stdout_old) < 0)
220     {
221         error_printf("Saving current stdio settings failed");
222         exit(EXIT_FAILURE);
223     }
224
225     /* Prepare new stdout settings */
226     memcpy(&stdout_new, &stdout_old, sizeof(stdout_old));
227
228     /* Reconfigure stdout (RAW configuration) */
229     stdout_new.c_oflag &= ~(OPOST);
230     stdout_new.c_lflag &= ~(ECHO|ICANON|ISIG|ECHOE|ECHOK|ECHONL);
231
232     /* Control characters */
233     stdout_new.c_cc[VTIME] = 0; /* Inter-character timer unused */
234     stdout_new.c_cc[VMIN]  = 1; /* Blocking read until 1 character received */
235
236     /* Activate new stdout settings */
237     status = tcsetattr(STDOUT_FILENO, TCSANOW, &stdout_new);
238     if (status == -1)
239     {
240         error_printf("Could not apply new stdout settings (%s)", strerror(errno));
241         exit(EXIT_FAILURE);
242     }
243
244     /* Print launch hints */
245     tio_printf("tio v%s", VERSION);
246     tio_printf("Press ctrl-t q to quit");
247
248     /* At start use normal print function */
249     print = print_normal;
250
251     /* Make sure we restore old stdout settings on exit */
252     atexit(&stdout_restore);
253 }
254
255 void stdout_restore(void)
256 {
257     tcsetattr(STDOUT_FILENO, TCSANOW, &stdout_old);
258 }
259
260 void tty_configure(void)
261 {
262     bool token_found = true;
263     char *token = NULL;
264     char *buffer;
265     int status;
266     speed_t baudrate;
267
268     memset(&tio, 0, sizeof(tio));
269
270     /* Set speed */
271     switch (option.baudrate)
272     {
273         /* The macro below expands into switch cases autogenerated by the
274          * configure script. Each switch case verifies and configures the baud
275          * rate and is of the form:
276          *
277          * case $baudrate: baudrate = B$baudrate; break;
278          *
279          * Only switch cases for baud rates detected supported by the host
280          * system are inserted.
281          *
282          * To see which baud rates are being probed see configure.ac
283          */
284         AUTOCONF_BAUDRATE_CASES
285
286         default:
287 #ifdef HAVE_TERMIOS2
288             standard_baudrate = false;
289             break;
290 #else
291             error_printf("Invalid baud rate");
292             exit(EXIT_FAILURE);
293 #endif
294     }
295
296     if (standard_baudrate)
297     {
298         // Set input speed
299         status = cfsetispeed(&tio, baudrate);
300         if (status == -1)
301         {
302             error_printf("Could not configure input speed (%s)", strerror(errno));
303             exit(EXIT_FAILURE);
304         }
305
306         // Set output speed
307         cfsetospeed(&tio, baudrate);
308         if (status == -1)
309         {
310             error_printf("Could not configure output speed (%s)", strerror(errno));
311             exit(EXIT_FAILURE);
312         }
313     }
314
315     /* Set databits */
316     tio.c_cflag &= ~CSIZE;
317     switch (option.databits)
318     {
319         case 5:
320             tio.c_cflag |= CS5;
321             break;
322         case 6:
323             tio.c_cflag |= CS6;
324             break;
325         case 7:
326             tio.c_cflag |= CS7;
327             break;
328         case 8:
329             tio.c_cflag |= CS8;
330             break;
331         default:
332             error_printf("Invalid data bits");
333             exit(EXIT_FAILURE);
334     }
335
336     /* Set flow control */
337     if (strcmp("hard", option.flow) == 0)
338     {
339         tio.c_cflag |= CRTSCTS;
340         tio.c_iflag &= ~(IXON | IXOFF | IXANY);
341     }
342     else if (strcmp("soft", option.flow) == 0)
343     {
344         tio.c_cflag &= ~CRTSCTS;
345         tio.c_iflag |= IXON | IXOFF;
346     }
347     else if (strcmp("none", option.flow) == 0)
348     {
349         tio.c_cflag &= ~CRTSCTS;
350         tio.c_iflag &= ~(IXON | IXOFF | IXANY);
351     }
352     else
353     {
354         error_printf("Invalid flow control");
355         exit(EXIT_FAILURE);
356     }
357
358     /* Set stopbits */
359     switch (option.stopbits)
360     {
361         case 1:
362             tio.c_cflag &= ~CSTOPB;
363             break;
364         case 2:
365             tio.c_cflag |= CSTOPB;
366             break;
367         default:
368             error_printf("Invalid stop bits");
369             exit(EXIT_FAILURE);
370     }
371
372     /* Set parity */
373     if (strcmp("odd", option.parity) == 0)
374     {
375         tio.c_cflag |= PARENB;
376         tio.c_cflag |= PARODD;
377     }
378     else if (strcmp("even", option.parity) == 0)
379     {
380         tio.c_cflag |= PARENB;
381         tio.c_cflag &= ~PARODD;
382     }
383     else if (strcmp("none", option.parity) == 0)
384         tio.c_cflag &= ~PARENB;
385     else
386     {
387         error_printf("Invalid parity");
388         exit(EXIT_FAILURE);
389     }
390
391     /* Control, input, output, local modes for tty device */
392     tio.c_cflag |= CLOCAL | CREAD;
393     tio.c_oflag = 0;
394     tio.c_lflag = 0;
395
396     /* Control characters */
397     tio.c_cc[VTIME] = 0; // Inter-character timer unused
398     tio.c_cc[VMIN]  = 1; // Blocking read until 1 character received
399
400     /* Configure any specified input or output mappings */
401     buffer = strdup(option.map);
402     while (token_found == true)
403     {
404         if (token == NULL)
405             token = strtok(buffer,",");
406         else
407             token = strtok(NULL, ",");
408
409         if (token != NULL)
410         {
411             if (strcmp(token,"INLCR") == 0)
412                 tio.c_iflag |= INLCR;
413             else if (strcmp(token,"IGNCR") == 0)
414                 tio.c_iflag |= IGNCR;
415             else if (strcmp(token,"ICRNL") == 0)
416                 tio.c_iflag |= ICRNL;
417             else if (strcmp(token,"OCRNL") == 0)
418                 tio.c_oflag |= OCRNL;
419             else if (strcmp(token,"ODELBS") == 0)
420                 map_odelbs = true;
421             else if (strcmp(token,"INLCRNL") == 0)
422                 map_inlcrnl = true;
423             else if (strcmp(token, "ONLCRNL") == 0)
424                 map_onlcrnl = true;
425             else
426             {
427                 printf("Error: Unknown mapping flag %s\n", token);
428                 exit(EXIT_FAILURE);
429             }
430         }
431         else
432             token_found = false;
433     }
434     free(buffer);
435 }
436
437 void tty_wait_for_device(void)
438 {
439     fd_set rdfs;
440     int    status;
441     struct timeval tv;
442     static char input_char, previous_char = 0;
443     static bool first = true;
444
445     /* Loop until device pops up */
446     while (true)
447     {
448         if (first)
449         {
450             /* Don't wait first time */
451             tv.tv_sec = 0;
452             tv.tv_usec = 1;
453             first = false;
454         } else
455         {
456             /* Wait up to 1 second */
457             tv.tv_sec = 1;
458             tv.tv_usec = 0;
459         }
460
461         FD_ZERO(&rdfs);
462         FD_SET(STDIN_FILENO, &rdfs);
463
464         /* Block until input becomes available or timeout */
465         status = select(STDIN_FILENO + 1, &rdfs, NULL, NULL, &tv);
466         if (status > 0)
467         {
468             /* Input from stdin ready */
469
470             /* Read one character */
471             status = read(STDIN_FILENO, &input_char, 1);
472             if (status <= 0)
473             {
474                 error_printf("Could not read from stdin");
475                 exit(EXIT_FAILURE);
476             }
477
478             /* Handle commands */
479             handle_command_sequence(input_char, previous_char, NULL, NULL);
480
481             previous_char = input_char;
482
483         } else if (status == -1)
484         {
485             error_printf("select() failed (%s)", strerror(errno));
486             exit(EXIT_FAILURE);
487         }
488
489         /* Test for accessible device file */
490         if (access(option.tty_device, R_OK) == 0)
491             return;
492     }
493 }
494
495 void tty_disconnect(void)
496 {
497     if (connected)
498     {
499         tio_printf("Disconnected");
500         flock(fd, LOCK_UN);
501         close(fd);
502         connected = false;
503     }
504 }
505
506 void tty_restore(void)
507 {
508     tcsetattr(fd, TCSANOW, &tio_old);
509
510     if (connected)
511         tty_disconnect();
512 }
513
514 int tty_connect(void)
515 {
516     fd_set rdfs;           /* Read file descriptor set */
517     int    maxfd;          /* Maximum file descriptor used */
518     char   input_char, output_char;
519     static char previous_char = 0;
520     static bool first = true;
521     int    status;
522
523     /* Open tty device */
524     fd = open(option.tty_device, O_RDWR | O_NOCTTY );
525     if (fd < 0)
526     {
527         error_printf_silent("Could not open tty device (%s)", strerror(errno));
528         goto error_open;
529     }
530
531     /* Make sure device is of tty type */
532     if (!isatty(fd))
533     {
534         error_printf("Not a tty device");
535         exit(EXIT_FAILURE);;
536     }
537
538     /* Lock device file */
539     status = flock(fd, LOCK_EX | LOCK_NB);
540     if ((status == -1) && (errno == EWOULDBLOCK))
541     {
542         error_printf("Device file is locked by another process");
543         exit(EXIT_FAILURE);
544     }
545
546     /* Flush stale I/O data (if any) */
547     tcflush(fd, TCIOFLUSH);
548
549     /* Print connect status */
550     tio_printf("Connected");
551     connected = true;
552     tainted = false;
553
554     /* Save current port settings */
555     if (tcgetattr(fd, &tio_old) < 0)
556         goto error_tcgetattr;
557
558     /* Make sure we restore tty settings on exit */
559     if (first)
560     {
561         atexit(&tty_restore);
562         first = false;
563     }
564
565     /* Activate new port settings */
566     status = tcsetattr(fd, TCSANOW, &tio);
567     if (status == -1)
568     {
569         error_printf_silent("Could not apply port settings (%s)", strerror(errno));
570         goto error_tcsetattr;
571     }
572
573 #ifdef HAVE_TERMIOS2
574     if (!standard_baudrate)
575     {
576         if (setspeed2(fd, option.baudrate) != 0)
577         {
578             error_printf_silent("Could not set baudrate speed (%s)", strerror(errno));
579             goto error_setspeed2;
580         }
581     }
582 #endif
583
584     maxfd = MAX(fd, STDIN_FILENO) + 1;  /* Maximum bit entry (fd) to test */
585
586     /* Input loop */
587     while (true)
588     {
589         FD_ZERO(&rdfs);
590         FD_SET(fd, &rdfs);
591         FD_SET(STDIN_FILENO, &rdfs);
592
593         /* Block until input becomes available */
594         status = select(maxfd, &rdfs, NULL, NULL, NULL);
595         if (status > 0)
596         {
597             if (FD_ISSET(fd, &rdfs))
598             {
599                 /* Input from tty device ready */
600                 if (read(fd, &input_char, 1) > 0)
601                 {
602                     /* Update receive statistics */
603                     rx_total++;
604
605                     /* Map input character */
606                     if ((input_char == '\n') && (map_inlcrnl))
607                     {
608                         print('\r');
609                         print('\n');
610                     } else
611                     {
612                         /* Print received tty character to stdout */
613                         print(input_char);
614                     }
615                     fflush(stdout);
616
617                     /* Write to log */
618                     if (option.log)
619                         log_write(input_char);
620
621                     tainted = true;
622
623                 } else
624                 {
625                     /* Error reading - device is likely unplugged */
626                     error_printf_silent("Could not read from tty device");
627                     goto error_read;
628                 }
629             }
630             if (FD_ISSET(STDIN_FILENO, &rdfs))
631             {
632                 bool forward = true;
633
634                 /* Input from stdin ready */
635                 status = read(STDIN_FILENO, &input_char, 1);
636                 if (status <= 0)
637                 {
638                     error_printf_silent("Could not read from stdin");
639                     goto error_read;
640                 }
641
642                 /* Forward input to output except ctrl-t key */
643                 output_char = input_char;
644                 if (input_char == KEY_CTRL_T)
645                     forward = false;
646
647                 /* Handle commands */
648                 handle_command_sequence(input_char, previous_char, &output_char, &forward);
649
650                 if (forward)
651                 {
652                     /* Map output character */
653                     if ((output_char == 127) && (map_odelbs))
654                         output_char = '\b';
655
656                     /* Map newline character */
657                     if ((output_char == '\n') && (map_onlcrnl)) {
658                         char r = '\r';
659
660                         status = write(fd, &r, 1);
661                         if (status < 0)
662                             warning_printf("Could not write to tty device");
663
664                         tx_total++;
665                         delay(option.output_delay);
666                     }
667
668                     /* Send output to tty device */
669                     status = write(fd, &output_char, 1);
670                     if (status < 0)
671                         warning_printf("Could not write to tty device");
672
673                     /* Update transmit statistics */
674                     tx_total++;
675
676                     /* Insert output delay */
677                     delay(option.output_delay);
678                 }
679
680                 /* Save previous key */
681                 previous_char = input_char;
682
683             }
684         } else if (status == -1)
685         {
686             error_printf("Error: select() failed (%s)", strerror(errno));
687             exit(EXIT_FAILURE);
688         }
689     }
690
691     return TIO_SUCCESS;
692
693 #ifdef HAVE_TERMIOS2
694 error_setspeed2:
695 #endif
696 error_tcsetattr:
697 error_tcgetattr:
698 error_read:
699     tty_disconnect();
700 error_open:
701     return TIO_ERROR;
702 }