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