]> git.sur5r.net Git - tio/blob - src/tty.c
Imported Upstream version 1.18
[tio] / src / tty.c
1 /*
2  * tio - a simple TTY terminal I/O application
3  *
4  * Copyright (c) 2014-2016  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 <unistd.h>
25 #include <string.h>
26 #include <stdlib.h>
27 #include <limits.h>
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <sys/param.h>
31 #include <sys/file.h>
32 #include <fcntl.h>
33 #include <termios.h>
34 #include <stdbool.h>
35 #include <errno.h>
36 #include "config.h"
37 #include "tio/tty.h"
38 #include "tio/print.h"
39 #include "tio/options.h"
40 #include "tio/time.h"
41 #include "tio/log.h"
42 #include "tio/error.h"
43
44 static struct termios tio, new_stdout, old_stdout, old_tio;
45 static unsigned long rx_total = 0, tx_total = 0;
46 static bool connected = false;
47 static bool tainted = false;
48 static bool standard_baudrate = true;
49 static int fd;
50
51 #ifndef BOTHER
52 #define BOTHER 0010000
53 #endif
54
55 #define tio_printf(format, args...) \
56 { \
57     if (tainted) putchar('\n'); \
58     color_printf("[tio %s] " format, current_time(), ## args); \
59     tainted = false; \
60 }
61
62 void handle_command_sequence(char input_char, char previous_char, char *output_char, bool *forward)
63 {
64     char unused_char;
65     bool unused_bool;
66
67     /* Ignore unused arguments */
68     if (output_char == NULL)
69         output_char = &unused_char;
70
71     if (forward == NULL)
72         forward = &unused_bool;
73
74     /* Handle escape key commands */
75     if (previous_char == KEY_CTRL_T)
76     {
77         switch (input_char)
78         {
79             case KEY_QUESTION:
80                 tio_printf("Key commands:");
81                 tio_printf(" ctrl-t ?   List available key commands");
82                 tio_printf(" ctrl-t i   Show settings information");
83                 tio_printf(" ctrl-t q   Quit");
84                 tio_printf(" ctrl-t s   Show statistics");
85                 tio_printf(" ctrl-t t   Send ctrl-t key code");
86                 *forward = false;
87                 break;
88             case KEY_I:
89                 tio_printf("Settings information:");
90                 tio_printf(" TTY device: %s", option.tty_device);
91                 tio_printf(" Baudrate: %u", option.baudrate);
92                 tio_printf(" Databits: %d", option.databits);
93                 tio_printf(" Flow: %s", option.flow);
94                 tio_printf(" Stopbits: %d", option.stopbits);
95                 tio_printf(" Parity: %s", option.parity);
96                 tio_printf(" Output delay: %d", option.output_delay);
97                 if (option.log)
98                     tio_printf(" Log file: %s", option.log_filename);
99                 *forward = false;
100                 break;
101             case KEY_Q:
102                 /* Exit upon ctrl-t q sequence */
103                 exit(EXIT_SUCCESS);
104             case KEY_T:
105                 /* Send ctrl-t key code upon ctrl-t t sequence */
106                 *output_char = KEY_CTRL_T;
107                 break;
108             case KEY_S:
109                 /* Show tx/rx statistics upon ctrl-t s sequence */
110                 tio_printf("Statistics:");
111                 tio_printf(" Sent %lu bytes, received %lu bytes", tx_total, rx_total);
112                 *forward = false;
113                 break;
114             default:
115                 /* Ignore unknown ctrl-t escaped keys */
116                 *forward = false;
117                 break;
118         }
119     }
120 }
121
122 void stdout_configure(void)
123 {
124     /* Save current stdout settings */
125     if (tcgetattr(STDOUT_FILENO, &old_stdout) < 0)
126     {
127         error_printf("Saving current stdio settings failed");
128         exit(EXIT_FAILURE);
129     }
130
131     /* Prepare new stdout settings */
132     memset(&new_stdout, 0, sizeof(new_stdout));
133
134     /* Control, input, output, local modes for stdout */
135     new_stdout.c_cflag = 0;
136     new_stdout.c_iflag = 0;
137     new_stdout.c_oflag = 0;
138     new_stdout.c_lflag = 0;
139
140     /* Control characters */
141     new_stdout.c_cc[VTIME] = 0; /* Inter-character timer unused */
142     new_stdout.c_cc[VMIN]  = 1; /* Blocking read until 1 character received */
143
144     /* Activate new stdout settings */
145     tcsetattr(STDOUT_FILENO, TCSANOW, &new_stdout);
146     tcsetattr(STDOUT_FILENO, TCSAFLUSH, &new_stdout);
147
148     /* Print launch hints */
149     tio_printf("tio v%s", VERSION);
150     tio_printf("Press ctrl-t q to quit");
151
152     /* Make sure we restore old stdout settings on exit */
153     atexit(&stdout_restore);
154 }
155
156 void stdout_restore(void)
157 {
158     tcsetattr(STDOUT_FILENO, TCSANOW, &old_stdout);
159     tcsetattr(STDOUT_FILENO, TCSAFLUSH, &old_stdout);
160 }
161
162 void tty_configure(void)
163 {
164     speed_t baudrate = 0;
165
166     memset(&tio, 0, sizeof(tio));
167
168     /* Set speed */
169     switch (option.baudrate)
170     {
171         /* The macro below expands into switch cases autogenerated by the
172          * configure script. Each switch case verifies and configures the baud
173          * rate and is of the form:
174          *
175          * case $baudrate: baudrate = B$baudrate; break;
176          *
177          * Only switch cases for baud rates detected supported by the host
178          * system are inserted.
179          *
180          * To see which baud rates are being probed see configure.ac
181          */
182         AUTOCONF_BAUDRATE_CASES
183
184         default:
185 #if !HAVE_DECL_BOTHER
186             error_printf("Invalid baud rate");
187             exit(EXIT_FAILURE);
188 #else
189             standard_baudrate = false;
190             break;
191 #endif
192     }
193
194     if (standard_baudrate)
195     {
196         cfsetispeed(&tio, baudrate);
197         cfsetospeed(&tio, baudrate);
198     } else
199     {
200         tio.c_ispeed = tio.c_ospeed = baudrate;
201         tio.c_cflag &= ~CBAUD;
202         tio.c_cflag |= BOTHER;
203     }
204
205     /* Set databits */
206     tio.c_cflag &= ~CSIZE;
207     switch (option.databits)
208     {
209         case 5:
210             tio.c_cflag |= CS5;
211             break;
212         case 6:
213             tio.c_cflag |= CS6;
214             break;
215         case 7:
216             tio.c_cflag |= CS7;
217             break;
218         case 8:
219             tio.c_cflag |= CS8;
220             break;
221         default:
222             error_printf("Invalid data bits");
223             exit(EXIT_FAILURE);
224     }
225
226     /* Set flow control */
227     if (strcmp("hard", option.flow) == 0)
228     {
229         tio.c_cflag |= CRTSCTS;
230         tio.c_iflag &= ~(IXON | IXOFF | IXANY);
231     }
232     else if (strcmp("soft", option.flow) == 0)
233     {
234         tio.c_cflag &= ~CRTSCTS;
235         tio.c_iflag |= IXON | IXOFF;
236     }
237     else if (strcmp("none", option.flow) == 0)
238     {
239         tio.c_cflag &= ~CRTSCTS;
240         tio.c_iflag &= ~(IXON | IXOFF | IXANY);
241     }
242     else
243     {
244         error_printf("Invalid flow control");
245         exit(EXIT_FAILURE);
246     }
247
248     /* Set stopbits */
249     switch (option.stopbits)
250     {
251         case 1:
252             tio.c_cflag &= ~CSTOPB;
253             break;
254         case 2:
255             tio.c_cflag |= CSTOPB;
256             break;
257         default:
258             error_printf("Invalid stop bits");
259             exit(EXIT_FAILURE);
260     }
261
262     /* Set parity */
263     if (strcmp("odd", option.parity) == 0)
264     {
265         tio.c_cflag |= PARENB;
266         tio.c_cflag |= PARODD;
267     }
268     else if (strcmp("even", option.parity) == 0)
269     {
270         tio.c_cflag |= PARENB;
271         tio.c_cflag &= ~PARODD;
272     }
273     else if (strcmp("none", option.parity) == 0)
274         tio.c_cflag &= ~PARENB;
275     else
276     {
277         error_printf("Invalid parity");
278         exit(EXIT_FAILURE);
279     }
280 }
281
282 void tty_wait_for_device(void)
283 {
284     fd_set rdfs;
285     int    status;
286     struct timeval tv;
287     static char input_char, previous_char = 0;
288     static bool first = true;
289
290     /* Loop until device pops up */
291     while (true)
292     {
293         if (first)
294         {
295             /* Don't wait first time */
296             tv.tv_sec = 0;
297             tv.tv_usec = 1;
298             first = false;
299         } else
300         {
301             /* Wait up to 1 second */
302             tv.tv_sec = 1;
303             tv.tv_usec = 0;
304         }
305
306         FD_ZERO(&rdfs);
307         FD_SET(STDIN_FILENO, &rdfs);
308
309         /* Block until input becomes available or timeout */
310         status = select(STDIN_FILENO + 1, &rdfs, NULL, NULL, &tv);
311         if (status > 0)
312         {
313             /* Input from stdin ready */
314
315             /* Read one character */
316             status = read(STDIN_FILENO, &input_char, 1);
317             if (status <= 0)
318             {
319                 error_printf("Could not read from stdin");
320                 exit(EXIT_FAILURE);
321             }
322
323             /* Handle commands */
324             handle_command_sequence(input_char, previous_char, NULL, NULL);
325
326             previous_char = input_char;
327
328         } else if (status == -1)
329         {
330             error_printf("select() failed (%s)", strerror(errno));
331             exit(EXIT_FAILURE);
332         }
333
334         /* Test for accessible device file */
335         if (access(option.tty_device, R_OK) == 0)
336             return;
337     }
338 }
339
340 void tty_disconnect(void)
341 {
342     if (connected)
343     {
344         tio_printf("Disconnected");
345         flock(fd, LOCK_UN);
346         close(fd);
347         connected = false;
348     }
349 }
350
351 void tty_restore(void)
352 {
353     tcsetattr(fd, TCSANOW, &old_tio);
354     tcsetattr(fd, TCSAFLUSH, &old_tio);
355
356     if (connected)
357         tty_disconnect();
358 }
359
360 int tty_connect(void)
361 {
362     fd_set rdfs;           /* Read file descriptor set */
363     int    maxfd;          /* Maximum file descriptor used */
364     char   input_char, output_char;
365     static char previous_char = 0;
366     static bool first = true;
367     int    status;
368
369     /* Open tty device */
370     fd = open(option.tty_device, O_RDWR | O_NOCTTY );
371     if (fd < 0)
372     {
373         error_printf_silent("Could not open tty device (%s)", strerror(errno));
374         goto error_open;
375     }
376
377     /* Make sure device is of tty type */
378     if (!isatty(fd))
379     {
380         error_printf("Not a tty device");
381         exit(EXIT_FAILURE);;
382     }
383
384     /* Lock device file */
385     status = flock(fd, LOCK_EX | LOCK_NB);
386     if ((status == -1) && (errno == EWOULDBLOCK))
387     {
388         error_printf("Device file is locked by another process");
389         exit(EXIT_FAILURE);
390     }
391
392     /* Flush stale I/O data (if any) */
393     tcflush(fd, TCIOFLUSH);
394
395     /* Warn if non standard baud rate is used */
396     if (!standard_baudrate)
397         tio_printf("Warning: Using a non standard baud rate");
398
399     /* Print connect status */
400     tio_printf("Connected");
401     connected = true;
402     tainted = false;
403
404     /* Save current port settings */
405     if (tcgetattr(fd, &old_tio) < 0)
406         goto error_tcgetattr;
407
408     /* Make sure we restore tty settings on exit */
409     if (first)
410     {
411         atexit(&tty_restore);
412         first = false;
413     }
414
415     /* Control, input, output, local modes for tty device */
416     tio.c_cflag |= CLOCAL | CREAD;
417     tio.c_oflag = 0;
418     tio.c_lflag = 0;
419
420     /* Control characters */
421     tio.c_cc[VTIME] = 0; /* Inter-character timer unused */
422     tio.c_cc[VMIN]  = 1; /* Blocking read until 1 character received */
423
424     /* Activate new port settings */
425     tcsetattr(fd, TCSANOW, &tio);
426     tcsetattr(fd, TCSAFLUSH, &tio);
427
428     maxfd = MAX(fd, STDIN_FILENO) + 1;  /* Maximum bit entry (fd) to test */
429
430     /* Input loop */
431     while (true)
432     {
433         FD_ZERO(&rdfs);
434         FD_SET(fd, &rdfs);
435         FD_SET(STDIN_FILENO, &rdfs);
436
437         /* Block until input becomes available */
438         status = select(maxfd, &rdfs, NULL, NULL, NULL);
439         if (status > 0)
440         {
441             if (FD_ISSET(fd, &rdfs))
442             {
443                 /* Input from tty device ready */
444                 if (read(fd, &input_char, 1) > 0)
445                 {
446                     /* Update receive statistics */
447                     rx_total++;
448
449                     /* Print received tty character to stdout */
450                     putchar(input_char);
451                     fflush(stdout);
452
453                     /* Write to log */
454                     if (option.log)
455                         log_write(input_char);
456
457                     tainted = true;
458
459                 } else
460                 {
461                     /* Error reading - device is likely unplugged */
462                     error_printf_silent("Could not read from tty device");
463                     goto error_read;
464                 }
465             }
466             if (FD_ISSET(STDIN_FILENO, &rdfs))
467             {
468                 bool forward = true;
469
470                 /* Input from stdin ready */
471                 status = read(STDIN_FILENO, &input_char, 1);
472                 if (status <= 0)
473                 {
474                     error_printf_silent("Could not read from stdin");
475                     goto error_read;
476                 }
477
478                 /* Forward input to output except ctrl-t key */
479                 output_char = input_char;
480                 if (input_char == KEY_CTRL_T)
481                     forward = false;
482
483                 /* Handle commands */
484                 handle_command_sequence(input_char, previous_char, &output_char, &forward);
485
486                 if (forward)
487                 {
488                     /* Send output to tty device */
489                     status = write(fd, &output_char, 1);
490                     if (status < 0)
491                         warning_printf("Could not write to tty device");
492
493                     /* Update transmit statistics */
494                     tx_total++;
495
496                     /* Insert output delay */
497                     delay(option.output_delay);
498                 }
499
500                 /* Save previous key */
501                 previous_char = input_char;
502
503             }
504         } else if (status == -1)
505         {
506             error_printf("Error: select() failed (%s)", strerror(errno));
507             exit(EXIT_FAILURE);
508         }
509     }
510
511     return TIO_SUCCESS;
512
513 error_tcgetattr:
514 error_read:
515     tty_disconnect();
516 error_open:
517     return TIO_ERROR;
518 }