2 * tio - a simple TTY terminal I/O application
4 * Copyright (c) 2014-2017 Martin Lund
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.
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.
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
29 #include <sys/types.h>
31 #include <sys/param.h>
39 #include "tio/print.h"
40 #include "tio/options.h"
43 #include "tio/error.h"
46 extern int setspeed2(int fd, int baudrate);
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);
57 static bool map_inlcrnl = false;
58 static bool map_onlcrnl = false;
59 static bool map_odelbs = false;
61 #define tio_printf(format, args...) \
63 if (tainted) putchar('\n'); \
64 color_printf("[tio %s] " format, current_time(), ## args); \
68 static void print_hex(char c)
70 printf("%02x ", (unsigned char) c);
73 static void print_normal(char c)
78 void handle_command_sequence(char input_char, char previous_char, char *output_char, bool *forward)
83 /* Ignore unused arguments */
84 if (output_char == NULL)
85 output_char = &unused_char;
88 forward = &unused_bool;
90 /* Handle escape key commands */
91 if (previous_char == KEY_CTRL_T)
93 /* Do not forward input char to output by default */
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");
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);
126 tio_printf(" Log file: %s", option.log_filename);
130 /* Toggle hexadecimal printing mode */
131 if (print_mode == NORMAL)
135 tio_printf("Switched to hexadecimal mode");
139 print = print_normal;
141 tio_printf("Switched to normal mode");
146 /* Clear screen using ANSI/VT100 escape code */
152 /* Exit upon ctrl-t q sequence */
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);
162 /* Send ctrl-t key code upon ctrl-t t sequence */
163 *output_char = KEY_CTRL_T;
168 /* Ignore unknown ctrl-t escaped keys */
174 void stdin_configure(void)
178 /* Save current stdin settings */
179 if (tcgetattr(STDIN_FILENO, &stdin_old) < 0)
181 error_printf("Saving current stdin settings failed");
185 /* Prepare new stdin settings */
186 memcpy(&stdin_new, &stdin_old, sizeof(stdin_old));
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);
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 */
197 /* Activate new stdin settings */
198 status = tcsetattr(STDIN_FILENO, TCSANOW, &stdin_new);
201 error_printf("Could not apply new stdin settings (%s)", strerror(errno));
205 /* Make sure we restore old stdin settings on exit */
206 atexit(&stdin_restore);
209 void stdin_restore(void)
211 tcsetattr(STDIN_FILENO, TCSANOW, &stdin_old);
214 void stdout_configure(void)
218 /* Save current stdout settings */
219 if (tcgetattr(STDOUT_FILENO, &stdout_old) < 0)
221 error_printf("Saving current stdio settings failed");
225 /* Prepare new stdout settings */
226 memcpy(&stdout_new, &stdout_old, sizeof(stdout_old));
228 /* Reconfigure stdout (RAW configuration) */
229 stdout_new.c_oflag &= ~(OPOST);
230 stdout_new.c_lflag &= ~(ECHO|ICANON|ISIG|ECHOE|ECHOK|ECHONL);
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 */
236 /* Activate new stdout settings */
237 status = tcsetattr(STDOUT_FILENO, TCSANOW, &stdout_new);
240 error_printf("Could not apply new stdout settings (%s)", strerror(errno));
244 /* Print launch hints */
245 tio_printf("tio v%s", VERSION);
246 tio_printf("Press ctrl-t q to quit");
248 /* At start use normal print function */
249 print = print_normal;
251 /* Make sure we restore old stdout settings on exit */
252 atexit(&stdout_restore);
255 void stdout_restore(void)
257 tcsetattr(STDOUT_FILENO, TCSANOW, &stdout_old);
260 void tty_configure(void)
262 bool token_found = true;
268 memset(&tio, 0, sizeof(tio));
271 switch (option.baudrate)
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:
277 * case $baudrate: baudrate = B$baudrate; break;
279 * Only switch cases for baud rates detected supported by the host
280 * system are inserted.
282 * To see which baud rates are being probed see configure.ac
284 AUTOCONF_BAUDRATE_CASES
288 standard_baudrate = false;
291 error_printf("Invalid baud rate");
296 if (standard_baudrate)
299 status = cfsetispeed(&tio, baudrate);
302 error_printf("Could not configure input speed (%s)", strerror(errno));
307 cfsetospeed(&tio, baudrate);
310 error_printf("Could not configure output speed (%s)", strerror(errno));
316 tio.c_cflag &= ~CSIZE;
317 switch (option.databits)
332 error_printf("Invalid data bits");
336 /* Set flow control */
337 if (strcmp("hard", option.flow) == 0)
339 tio.c_cflag |= CRTSCTS;
340 tio.c_iflag &= ~(IXON | IXOFF | IXANY);
342 else if (strcmp("soft", option.flow) == 0)
344 tio.c_cflag &= ~CRTSCTS;
345 tio.c_iflag |= IXON | IXOFF;
347 else if (strcmp("none", option.flow) == 0)
349 tio.c_cflag &= ~CRTSCTS;
350 tio.c_iflag &= ~(IXON | IXOFF | IXANY);
354 error_printf("Invalid flow control");
359 switch (option.stopbits)
362 tio.c_cflag &= ~CSTOPB;
365 tio.c_cflag |= CSTOPB;
368 error_printf("Invalid stop bits");
373 if (strcmp("odd", option.parity) == 0)
375 tio.c_cflag |= PARENB;
376 tio.c_cflag |= PARODD;
378 else if (strcmp("even", option.parity) == 0)
380 tio.c_cflag |= PARENB;
381 tio.c_cflag &= ~PARODD;
383 else if (strcmp("none", option.parity) == 0)
384 tio.c_cflag &= ~PARENB;
387 error_printf("Invalid parity");
391 /* Control, input, output, local modes for tty device */
392 tio.c_cflag |= CLOCAL | CREAD;
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
400 /* Configure any specified input or output mappings */
401 buffer = strdup(option.map);
402 while (token_found == true)
405 token = strtok(buffer,",");
407 token = strtok(NULL, ",");
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)
421 else if (strcmp(token,"INLCRNL") == 0)
423 else if (strcmp(token, "ONLCRNL") == 0)
427 printf("Error: Unknown mapping flag %s\n", token);
437 void tty_wait_for_device(void)
442 static char input_char, previous_char = 0;
443 static bool first = true;
445 /* Loop until device pops up */
450 /* Don't wait first time */
456 /* Wait up to 1 second */
462 FD_SET(STDIN_FILENO, &rdfs);
464 /* Block until input becomes available or timeout */
465 status = select(STDIN_FILENO + 1, &rdfs, NULL, NULL, &tv);
468 /* Input from stdin ready */
470 /* Read one character */
471 status = read(STDIN_FILENO, &input_char, 1);
474 error_printf("Could not read from stdin");
478 /* Handle commands */
479 handle_command_sequence(input_char, previous_char, NULL, NULL);
481 previous_char = input_char;
483 } else if (status == -1)
485 error_printf("select() failed (%s)", strerror(errno));
489 /* Test for accessible device file */
490 if (access(option.tty_device, R_OK) == 0)
495 void tty_disconnect(void)
499 tio_printf("Disconnected");
506 void tty_restore(void)
508 tcsetattr(fd, TCSANOW, &tio_old);
514 int tty_connect(void)
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;
523 /* Open tty device */
524 fd = open(option.tty_device, O_RDWR | O_NOCTTY );
527 error_printf_silent("Could not open tty device (%s)", strerror(errno));
531 /* Make sure device is of tty type */
534 error_printf("Not a tty device");
538 /* Lock device file */
539 status = flock(fd, LOCK_EX | LOCK_NB);
540 if ((status == -1) && (errno == EWOULDBLOCK))
542 error_printf("Device file is locked by another process");
546 /* Flush stale I/O data (if any) */
547 tcflush(fd, TCIOFLUSH);
549 /* Print connect status */
550 tio_printf("Connected");
554 /* Save current port settings */
555 if (tcgetattr(fd, &tio_old) < 0)
556 goto error_tcgetattr;
558 /* Make sure we restore tty settings on exit */
561 atexit(&tty_restore);
565 /* Activate new port settings */
566 status = tcsetattr(fd, TCSANOW, &tio);
569 error_printf_silent("Could not apply port settings (%s)", strerror(errno));
570 goto error_tcsetattr;
574 if (!standard_baudrate)
576 if (setspeed2(fd, option.baudrate) != 0)
578 error_printf_silent("Could not set baudrate speed (%s)", strerror(errno));
579 goto error_setspeed2;
584 maxfd = MAX(fd, STDIN_FILENO) + 1; /* Maximum bit entry (fd) to test */
591 FD_SET(STDIN_FILENO, &rdfs);
593 /* Block until input becomes available */
594 status = select(maxfd, &rdfs, NULL, NULL, NULL);
597 if (FD_ISSET(fd, &rdfs))
599 /* Input from tty device ready */
600 if (read(fd, &input_char, 1) > 0)
602 /* Update receive statistics */
605 /* Map input character */
606 if ((input_char == '\n') && (map_inlcrnl))
612 /* Print received tty character to stdout */
619 log_write(input_char);
625 /* Error reading - device is likely unplugged */
626 error_printf_silent("Could not read from tty device");
630 if (FD_ISSET(STDIN_FILENO, &rdfs))
634 /* Input from stdin ready */
635 status = read(STDIN_FILENO, &input_char, 1);
638 error_printf_silent("Could not read from stdin");
642 /* Forward input to output except ctrl-t key */
643 output_char = input_char;
644 if (input_char == KEY_CTRL_T)
647 /* Handle commands */
648 handle_command_sequence(input_char, previous_char, &output_char, &forward);
652 /* Map output character */
653 if ((output_char == 127) && (map_odelbs))
656 /* Map newline character */
657 if ((output_char == '\n') && (map_onlcrnl)) {
660 status = write(fd, &r, 1);
662 warning_printf("Could not write to tty device");
665 delay(option.output_delay);
668 /* Send output to tty device */
669 status = write(fd, &output_char, 1);
671 warning_printf("Could not write to tty device");
673 /* Update transmit statistics */
676 /* Insert output delay */
677 delay(option.output_delay);
680 /* Save previous key */
681 previous_char = input_char;
684 } else if (status == -1)
686 error_printf("Error: select() failed (%s)", strerror(errno));