]> git.sur5r.net Git - tio/blob - src/tty.c
Imported Upstream version 1.14
[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 <stdio.h>
23 #include <unistd.h>
24 #include <string.h>
25 #include <stdlib.h>
26 #include <limits.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <sys/param.h>
30 #include <sys/file.h>
31 #include <fcntl.h>
32 #include <termios.h>
33 #include <stdbool.h>
34 #include <errno.h>
35 #include "config.h"
36 #include "tio/tty.h"
37 #include "tio/print.h"
38 #include "tio/options.h"
39 #include "tio/time.h"
40 #include "tio/log.h"
41 #include "tio/error.h"
42
43 static struct termios new_stdout, old_stdout, old_tio;
44 static long rx_total = 0, tx_total = 0;
45 static bool connected = false;
46 static bool tainted = false;
47 static int fd;
48
49 #define tio_printf(format, args...) \
50 { \
51     if (tainted) putchar('\n'); \
52     color_printf("[tio %s] " format, current_time(), ## args); \
53     tainted = false; \
54 }
55
56 void handle_command_sequence(char input_char, char previous_char, char *output_char, bool *forward)
57 {
58     char unused_char;
59     bool unused_bool;
60
61     /* Ignore unused arguments */
62     if (output_char == NULL)
63         output_char = &unused_char;
64
65     if (forward == NULL)
66         forward = &unused_bool;
67
68     /* Handle escape key commands */
69     if (previous_char == KEY_CTRL_T)
70     {
71         switch (input_char)
72         {
73             case KEY_QUESTION:
74                 tio_printf("Key commands:");
75                 tio_printf(" ctrl-t ?   List available key commands");
76                 tio_printf(" ctrl-t i   Show settings information");
77                 tio_printf(" ctrl-t q   Quit");
78                 tio_printf(" ctrl-t s   Show statistics");
79                 tio_printf(" ctrl-t t   Send ctrl-t key code");
80                 *forward = false;
81                 break;
82             case KEY_I:
83                 tio_printf("Settings information:");
84                 tio_printf(" TTY device: %s", option.tty_device);
85                 tio_printf(" Baudrate: %d", option.baudrate);
86                 tio_printf(" Databits: %d", option.databits);
87                 tio_printf(" Flow: %s", option.flow);
88                 tio_printf(" Stopbits: %d", option.stopbits);
89                 tio_printf(" Parity: %s", option.parity);
90                 tio_printf(" Output delay: %d", option.output_delay);
91                 if (option.log)
92                     tio_printf(" Log file: %s", option.log_filename);
93                 *forward = false;
94                 break;
95             case KEY_Q:
96                 /* Exit upon ctrl-t q sequence */
97                 exit(EXIT_SUCCESS);
98             case KEY_T:
99                 /* Send ctrl-t key code upon ctrl-t t sequence */
100                 *output_char = KEY_CTRL_T;
101                 break;
102             case KEY_S:
103                 /* Show tx/rx statistics upon ctrl-t s sequence */
104                 tio_printf("Statistics:");
105                 tio_printf(" Sent %ld bytes, received %ld bytes", tx_total, rx_total);
106                 *forward = false;
107                 break;
108             default:
109                 /* Ignore unknown ctrl-t escaped keys */
110                 *forward = false;
111                 break;
112         }
113     }
114 }
115
116 void wait_for_tty_device(void)
117 {
118     fd_set rdfs;
119     int    status;
120     struct timeval tv;
121     static char input_char, previous_char = 0;
122     static bool first = true;
123
124     /* Loop until device pops up */
125     while (true)
126     {
127         if (first)
128         {
129             /* Don't wait first time */
130             tv.tv_sec = 0;
131             tv.tv_usec = 1;
132             first = false;
133         } else
134         {
135             /* Wait up to 1 second */
136             tv.tv_sec = 1;
137             tv.tv_usec = 0;
138         }
139
140         FD_ZERO(&rdfs);
141         FD_SET(STDIN_FILENO, &rdfs);
142
143         /* Block until input becomes available or timeout */
144         status = select(STDIN_FILENO + 1, &rdfs, NULL, NULL, &tv);
145         if (status > 0)
146         {
147             /* Input from stdin ready */
148
149             /* Read one character */
150             status = read(STDIN_FILENO, &input_char, 1);
151             if (status <= 0)
152             {
153                 error_printf("Could not read from stdin");
154                 exit(EXIT_FAILURE);
155             }
156
157             /* Handle commands */
158             handle_command_sequence(input_char, previous_char, NULL, NULL);
159
160             previous_char = input_char;
161
162         } else if (status == -1)
163         {
164             error_printf("select() failed (%s)", strerror(errno));
165             exit(EXIT_FAILURE);
166         }
167
168         /* Test for accessible device file */
169         if (access(option.tty_device, R_OK) == 0)
170             return;
171     }
172 }
173
174 void configure_stdout(void)
175 {
176     /* Save current stdout settings */
177     if (tcgetattr(STDOUT_FILENO, &old_stdout) < 0)
178     {
179         error_printf("Saving current stdio settings failed");
180         exit(EXIT_FAILURE);
181     }
182
183     /* Prepare new stdout settings */
184     bzero(&new_stdout, sizeof(new_stdout));
185
186     /* Control, input, output, local modes for stdout */
187     new_stdout.c_cflag = 0;
188     new_stdout.c_iflag = 0;
189     new_stdout.c_oflag = 0;
190     new_stdout.c_lflag = 0;
191
192     /* Control characters */
193     new_stdout.c_cc[VTIME] = 0; /* Inter-character timer unused */
194     new_stdout.c_cc[VMIN]  = 1; /* Blocking read until 1 character received */
195
196     /* Activate new stdout settings */
197     tcsetattr(STDOUT_FILENO, TCSANOW, &new_stdout);
198     tcsetattr(STDOUT_FILENO, TCSAFLUSH, &new_stdout);
199
200     /* Print launch hints */
201     tio_printf("tio v%s", VERSION);
202     tio_printf("Press ctrl-t + q to quit");
203
204     /* Make sure we restore old stdout settings on exit */
205     atexit(&restore_stdout);
206 }
207
208 void restore_stdout(void)
209 {
210     tcsetattr(STDOUT_FILENO, TCSANOW, &old_stdout);
211     tcsetattr(STDOUT_FILENO, TCSAFLUSH, &old_stdout);
212 }
213
214 void disconnect_tty(void)
215 {
216     if (connected)
217     {
218         tio_printf("Disconnected");
219         flock(fd, LOCK_UN);
220         close(fd);
221         connected = false;
222     }
223 }
224
225 void restore_tty(void)
226 {
227     tcsetattr(fd, TCSANOW, &old_tio);
228     tcsetattr(fd, TCSAFLUSH, &old_tio);
229
230     if (connected)
231         disconnect_tty();
232 }
233
234 int connect_tty(void)
235 {
236     fd_set rdfs;           /* Read file descriptor set */
237     int    maxfd;          /* Maximum file descriptor used */
238     char   input_char, output_char;
239     static char previous_char = 0;
240     static bool first = true;
241     int    status;
242
243     /* Open tty device */
244     fd = open(option.tty_device, O_RDWR | O_NOCTTY );
245     if (fd < 0)
246     {
247         error_printf_silent("Could not open tty device (%s)", strerror(errno));
248         goto error_open;
249     }
250
251     /* Make sure device is of tty type */
252     if (!isatty(fd))
253     {
254         error_printf_silent("Not a tty device");
255         goto error_isatty;
256     }
257
258     /* Lock device file */
259     status = flock(fd, LOCK_EX | LOCK_NB);
260     if ((status == -1) && (errno == EWOULDBLOCK))
261     {
262         error_printf("Device file is locked by another process");
263         exit(EXIT_FAILURE);
264     }
265
266     /* Flush stale I/O data (if any) */
267     tcflush(fd, TCIOFLUSH);
268
269     /* Print connect status */
270     tio_printf("Connected");
271     connected = true;
272     tainted = false;
273
274     /* Save current port settings */
275     if (tcgetattr(fd, &old_tio) < 0)
276         goto error_tcgetattr;
277
278     /* Make sure we restore tty settings on exit */
279     if (first)
280     {
281         atexit(&restore_tty);
282         first = false;
283     }
284
285     /* Control, input, output, local modes for tty device */
286     option.tio.c_cflag |= CLOCAL | CREAD;
287     option.tio.c_oflag = 0;
288     option.tio.c_lflag = 0;
289
290     /* Control characters */
291     option.tio.c_cc[VTIME] = 0; /* Inter-character timer unused */
292     option.tio.c_cc[VMIN]  = 1; /* Blocking read until 1 character received */
293
294     /* Activate new port settings */
295     tcsetattr(fd, TCSANOW, &option.tio);
296     tcsetattr(fd, TCSAFLUSH, &option.tio);
297
298     maxfd = MAX(fd, STDIN_FILENO) + 1;  /* Maximum bit entry (fd) to test */
299
300     /* Input loop */
301     while (true)
302     {
303         FD_ZERO(&rdfs);
304         FD_SET(fd, &rdfs);
305         FD_SET(STDIN_FILENO, &rdfs);
306
307         /* Block until input becomes available */
308         status = select(maxfd, &rdfs, NULL, NULL, NULL);
309         if (status > 0)
310         {
311             if (FD_ISSET(fd, &rdfs))
312             {
313                 /* Input from tty device ready */
314                 if (read(fd, &input_char, 1) > 0)
315                 {
316                     /* Update receive statistics */
317                     rx_total++;
318
319                     /* Print received tty character to stdout */
320                     putchar(input_char);
321                     fflush(stdout);
322
323                     /* Write to log */
324                     if (option.log)
325                         log_write(input_char);
326
327                     tainted = true;
328
329                 } else
330                 {
331                     /* Error reading - device is likely unplugged */
332                     error_printf_silent("Could not read from tty device");
333                     goto error_read;
334                 }
335             }
336             if (FD_ISSET(STDIN_FILENO, &rdfs))
337             {
338                 bool forward = true;
339
340                 /* Input from stdin ready */
341                 status = read(STDIN_FILENO, &input_char, 1);
342                 if (status <= 0)
343                 {
344                     error_printf_silent("Could not read from stdin");
345                     goto error_read;
346                 }
347
348                 /* Forward input to output except ctrl-t key */
349                 output_char = input_char;
350                 if (input_char == KEY_CTRL_T)
351                     forward = false;
352
353                 /* Handle commands */
354                 handle_command_sequence(input_char, previous_char, &output_char, &forward);
355
356                 if (forward)
357                 {
358                     /* Send output to tty device */
359                     status = write(fd, &output_char, 1);
360                     if (status < 0)
361                         warning_printf("Could not write to tty device");
362
363                     /* Update transmit statistics */
364                     tx_total++;
365
366                     /* Insert output delay */
367                     delay(option.output_delay);
368                 }
369
370                 /* Save previous key */
371                 previous_char = input_char;
372
373             }
374         } else if (status == -1)
375         {
376             error_printf("Error: select() failed (%s)", strerror(errno));
377             exit(EXIT_FAILURE);
378         }
379     }
380
381     return TIO_SUCCESS;
382
383 error_tcgetattr:
384 error_isatty:
385 error_read:
386     disconnect_tty();
387 error_open:
388     return TIO_ERROR;
389 }