2 * tio - a simple TTY terminal I/O tool
4 * Copyright (c) 2014-2022 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>
33 #include <sys/ioctl.h>
49 extern int setspeed2(int fd, int baudrate);
53 #define PATH_SERIAL_DEVICES "/dev/"
55 #define PATH_SERIAL_DEVICES "/dev/serial/by-id/"
58 static struct termios tio, tio_old, stdout_new, stdout_old, stdin_new, stdin_old;
59 static unsigned long rx_total = 0, tx_total = 0;
60 static bool connected = false;
61 static bool print_mode = NORMAL;
62 static bool standard_baudrate = true;
63 static void (*print)(char c);
65 static bool map_i_nl_crnl = false;
66 static bool map_o_cr_nl = false;
67 static bool map_o_nl_crnl = false;
68 static bool map_o_del_bs = false;
71 static void toggle_line(const char *line_name, int mask)
75 if (ioctl(fd, TIOCMGET, &state) < 0)
77 error_printf("Could not get line state: %s", strerror(errno));
84 tio_printf("set %s to LOW", line_name);
89 tio_printf("set %s to HIGH", line_name);
91 if (ioctl(fd, TIOCMSET, &state) < 0)
92 error_printf("Could not set line state: %s", strerror(errno));
96 void handle_command_sequence(char input_char, char previous_char, char *output_char, bool *forward)
102 /* Ignore unused arguments */
103 if (output_char == NULL)
104 output_char = &unused_char;
107 forward = &unused_bool;
109 /* Handle escape key commands */
110 if (previous_char == KEY_CTRL_T)
112 /* Do not forward input char to output by default */
118 tio_printf("Key commands:");
119 tio_printf(" ctrl-t ? List available key commands");
120 tio_printf(" ctrl-t b Send break");
121 tio_printf(" ctrl-t c Show configuration");
122 tio_printf(" ctrl-t d Toggle DTR line");
123 tio_printf(" ctrl-t e Toggle local echo mode");
124 tio_printf(" ctrl-t h Toggle hexadecimal mode");
125 tio_printf(" ctrl-t l Clear screen");
126 tio_printf(" ctrl-t L Show lines state");
127 tio_printf(" ctrl-t q Quit");
128 tio_printf(" ctrl-t r Toggle RTS line");
129 tio_printf(" ctrl-t s Show statistics");
130 tio_printf(" ctrl-t t Send ctrl-t key code");
131 tio_printf(" ctrl-t T Toggle line timestamp mode");
132 tio_printf(" ctrl-t v Show version");
136 if (ioctl(fd, TIOCMGET, &state) < 0)
138 error_printf("Could not get line state: %s", strerror(errno));
141 tio_printf("Lines state:");
142 tio_printf(" DTR: %s", (state & TIOCM_DTR) ? "HIGH" : "LOW");
143 tio_printf(" RTS: %s", (state & TIOCM_RTS) ? "HIGH" : "LOW");
144 tio_printf(" CTS: %s", (state & TIOCM_CTS) ? "HIGH" : "LOW");
145 tio_printf(" DSR: %s", (state & TIOCM_DSR) ? "HIGH" : "LOW");
146 tio_printf(" DCD: %s", (state & TIOCM_CD) ? "HIGH" : "LOW");
147 tio_printf(" RI : %s", (state & TIOCM_RI) ? "HIGH" : "LOW");
150 toggle_line("DTR", TIOCM_DTR);
154 toggle_line("RTS", TIOCM_RTS);
162 tio_printf("Configuration:");
163 tio_printf(" TTY device: %s", option.tty_device);
164 tio_printf(" Baudrate: %u", option.baudrate);
165 tio_printf(" Databits: %d", option.databits);
166 tio_printf(" Flow: %s", option.flow);
167 tio_printf(" Stopbits: %d", option.stopbits);
168 tio_printf(" Parity: %s", option.parity);
169 tio_printf(" Local echo: %s", option.local_echo ? "enabled" : "disabled");
170 tio_printf(" Timestamps: %s", option.timestamp ? "enabled" : "disabled");
171 tio_printf(" Output delay: %d", option.output_delay);
172 tio_printf(" Auto connect: %s", option.no_autoconnect ? "disabled" : "enabled");
173 if (option.map[0] != 0)
174 tio_printf(" Map flags: %s", option.map);
176 tio_printf(" Log file: %s", option.log_filename);
180 option.local_echo = !option.local_echo;
184 /* Toggle hexadecimal printing mode */
185 if (print_mode == NORMAL)
189 tio_printf("Switched to hexadecimal mode");
193 print = print_normal;
195 tio_printf("Switched to normal mode");
200 /* Clear screen using ANSI/VT100 escape code */
206 /* Exit upon ctrl-t q sequence */
210 /* Show tx/rx statistics upon ctrl-t s sequence */
211 tio_printf("Statistics:");
212 tio_printf(" Sent %lu bytes", tx_total);
213 tio_printf(" Received %lu bytes", rx_total);
217 /* Send ctrl-t key code upon ctrl-t t sequence */
218 *output_char = KEY_CTRL_T;
223 option.timestamp = !option.timestamp;
227 tio_printf("tio v%s", VERSION);
231 /* Ignore unknown ctrl-t escaped keys */
237 void stdin_configure(void)
241 /* Save current stdin settings */
242 if (tcgetattr(STDIN_FILENO, &stdin_old) < 0)
244 error_printf("Saving current stdin settings failed");
248 /* Prepare new stdin settings */
249 memcpy(&stdin_new, &stdin_old, sizeof(stdin_old));
251 /* Reconfigure stdin (RAW configuration) */
252 stdin_new.c_iflag &= ~(ICRNL); // Do not translate CR -> NL on input
253 stdin_new.c_oflag &= ~(OPOST);
254 stdin_new.c_lflag &= ~(ECHO|ICANON|ISIG|ECHOE|ECHOK|ECHONL);
256 /* Control characters */
257 stdin_new.c_cc[VTIME] = 0; /* Inter-character timer unused */
258 stdin_new.c_cc[VMIN] = 1; /* Blocking read until 1 character received */
260 /* Activate new stdin settings */
261 status = tcsetattr(STDIN_FILENO, TCSANOW, &stdin_new);
264 error_printf("Could not apply new stdin settings (%s)", strerror(errno));
268 /* Make sure we restore old stdin settings on exit */
269 atexit(&stdin_restore);
272 void stdin_restore(void)
274 tcsetattr(STDIN_FILENO, TCSANOW, &stdin_old);
277 void stdout_configure(void)
281 /* Disable line buffering in stdout. This is necessary if we
282 * want things like local echo to work correctly. */
283 setbuf(stdout, NULL);
285 /* Save current stdout settings */
286 if (tcgetattr(STDOUT_FILENO, &stdout_old) < 0)
288 error_printf("Saving current stdio settings failed");
292 /* Prepare new stdout settings */
293 memcpy(&stdout_new, &stdout_old, sizeof(stdout_old));
295 /* Reconfigure stdout (RAW configuration) */
296 stdout_new.c_oflag &= ~(OPOST);
297 stdout_new.c_lflag &= ~(ECHO|ICANON|ISIG|ECHOE|ECHOK|ECHONL);
299 /* Control characters */
300 stdout_new.c_cc[VTIME] = 0; /* Inter-character timer unused */
301 stdout_new.c_cc[VMIN] = 1; /* Blocking read until 1 character received */
303 /* Activate new stdout settings */
304 status = tcsetattr(STDOUT_FILENO, TCSANOW, &stdout_new);
307 error_printf("Could not apply new stdout settings (%s)", strerror(errno));
311 /* At start use normal print function */
312 print = print_normal;
314 /* Make sure we restore old stdout settings on exit */
315 atexit(&stdout_restore);
318 void stdout_restore(void)
320 tcsetattr(STDOUT_FILENO, TCSANOW, &stdout_old);
323 void tty_configure(void)
325 bool token_found = true;
331 memset(&tio, 0, sizeof(tio));
334 switch (option.baudrate)
336 /* The macro below expands into switch cases autogenerated by the
337 * configure script. Each switch case verifies and configures the baud
338 * rate and is of the form:
340 * case $baudrate: baudrate = B$baudrate; break;
342 * Only switch cases for baud rates detected supported by the host
343 * system are inserted.
345 * To see which baud rates are being probed see meson.build
351 standard_baudrate = false;
354 error_printf("Invalid baud rate");
359 if (standard_baudrate)
362 status = cfsetispeed(&tio, baudrate);
365 error_printf("Could not configure input speed (%s)", strerror(errno));
370 status = cfsetospeed(&tio, baudrate);
373 error_printf("Could not configure output speed (%s)", strerror(errno));
379 tio.c_cflag &= ~CSIZE;
380 switch (option.databits)
395 error_printf("Invalid data bits");
399 /* Set flow control */
400 if (strcmp("hard", option.flow) == 0)
402 tio.c_cflag |= CRTSCTS;
403 tio.c_iflag &= ~(IXON | IXOFF | IXANY);
405 else if (strcmp("soft", option.flow) == 0)
407 tio.c_cflag &= ~CRTSCTS;
408 tio.c_iflag |= IXON | IXOFF;
410 else if (strcmp("none", option.flow) == 0)
412 tio.c_cflag &= ~CRTSCTS;
413 tio.c_iflag &= ~(IXON | IXOFF | IXANY);
417 error_printf("Invalid flow control");
422 switch (option.stopbits)
425 tio.c_cflag &= ~CSTOPB;
428 tio.c_cflag |= CSTOPB;
431 error_printf("Invalid stop bits");
436 if (strcmp("odd", option.parity) == 0)
438 tio.c_cflag |= PARENB;
439 tio.c_cflag |= PARODD;
441 else if (strcmp("even", option.parity) == 0)
443 tio.c_cflag |= PARENB;
444 tio.c_cflag &= ~PARODD;
446 else if (strcmp("none", option.parity) == 0)
447 tio.c_cflag &= ~PARENB;
450 error_printf("Invalid parity");
454 /* Control, input, output, local modes for tty device */
455 tio.c_cflag |= CLOCAL | CREAD;
459 /* Control characters */
460 tio.c_cc[VTIME] = 0; // Inter-character timer unused
461 tio.c_cc[VMIN] = 1; // Blocking read until 1 character received
463 /* Configure any specified input or output mappings */
464 buffer = strdup(option.map);
465 while (token_found == true)
468 token = strtok(buffer,",");
470 token = strtok(NULL, ",");
474 if (strcmp(token,"INLCR") == 0)
475 tio.c_iflag |= INLCR;
476 else if (strcmp(token,"IGNCR") == 0)
477 tio.c_iflag |= IGNCR;
478 else if (strcmp(token,"ICRNL") == 0)
479 tio.c_iflag |= ICRNL;
480 else if (strcmp(token,"OCRNL") == 0)
482 else if (strcmp(token,"ODELBS") == 0)
484 else if (strcmp(token,"INLCRNL") == 0)
485 map_i_nl_crnl = true;
486 else if (strcmp(token, "ONLCRNL") == 0)
487 map_o_nl_crnl = true;
490 printf("Error: Unknown mapping flag %s\n", token);
500 void tty_wait_for_device(void)
505 static char input_char, previous_char = 0;
506 static bool first = true;
507 static int last_errno = 0;
509 /* Loop until device pops up */
514 /* Don't wait first time */
520 /* Wait up to 1 second */
526 FD_SET(STDIN_FILENO, &rdfs);
528 /* Block until input becomes available or timeout */
529 status = select(STDIN_FILENO + 1, &rdfs, NULL, NULL, &tv);
532 /* Input from stdin ready */
534 /* Read one character */
535 status = read(STDIN_FILENO, &input_char, 1);
538 error_printf("Could not read from stdin");
542 /* Handle commands */
543 handle_command_sequence(input_char, previous_char, NULL, NULL);
545 previous_char = input_char;
547 } else if (status == -1)
549 error_printf("select() failed (%s)", strerror(errno));
553 /* Test for accessible device file */
554 status = access(option.tty_device, R_OK);
559 else if (last_errno != errno)
561 warning_printf("Could not open tty device (%s)", strerror(errno));
562 tio_printf("Waiting for tty device..");
568 void tty_disconnect(void)
572 tio_printf("Disconnected");
579 void tty_restore(void)
581 tcsetattr(fd, TCSANOW, &tio_old);
587 static void optional_local_echo(char c)
589 if (!option.local_echo)
597 int tty_connect(void)
599 fd_set rdfs; /* Read file descriptor set */
600 int maxfd; /* Maximum file descriptor used */
601 char input_char, output_char;
602 static char previous_char = 0;
603 static bool first = true;
605 bool next_timestamp = false;
608 /* Open tty device */
610 fd = open(option.tty_device, O_RDWR | O_NOCTTY | O_NONBLOCK );
612 fd = open(option.tty_device, O_RDWR | O_NOCTTY);
616 error_printf_silent("Could not open tty device (%s)", strerror(errno));
620 /* Make sure device is of tty type */
623 error_printf("Not a tty device");
627 /* Lock device file */
628 status = flock(fd, LOCK_EX | LOCK_NB);
629 if ((status == -1) && (errno == EWOULDBLOCK))
631 error_printf("Device file is locked by another process");
635 /* Flush stale I/O data (if any) */
636 tcflush(fd, TCIOFLUSH);
638 /* Print connect status */
639 tio_printf("Connected");
641 print_tainted = false;
643 if (option.timestamp)
644 next_timestamp = true;
646 /* Save current port settings */
647 if (tcgetattr(fd, &tio_old) < 0)
648 goto error_tcgetattr;
650 /* Make sure we restore tty settings on exit */
653 atexit(&tty_restore);
657 /* Activate new port settings */
658 status = tcsetattr(fd, TCSANOW, &tio);
661 error_printf_silent("Could not apply port settings (%s)", strerror(errno));
662 goto error_tcsetattr;
666 if (!standard_baudrate)
668 if (setspeed2(fd, option.baudrate) != 0)
670 error_printf_silent("Could not set baudrate speed (%s)", strerror(errno));
671 goto error_setspeed2;
676 maxfd = MAX(fd, STDIN_FILENO) + 1; /* Maximum bit entry (fd) to test */
683 FD_SET(STDIN_FILENO, &rdfs);
685 /* Block until input becomes available */
686 status = select(maxfd, &rdfs, NULL, NULL, NULL);
689 if (FD_ISSET(fd, &rdfs))
691 /* Input from tty device ready */
692 if (read(fd, &input_char, 1) > 0)
694 /* Update receive statistics */
697 /* Print timestamp on new line, if desired. */
698 if (next_timestamp && input_char != '\n' && input_char != '\r')
700 now = current_time();
703 ansi_printf_raw("[%s] ", now);
715 next_timestamp = false;
719 /* Map input character */
720 if ((input_char == '\n') && (map_i_nl_crnl))
724 if (option.timestamp)
725 next_timestamp = true;
728 /* Print received tty character to stdout */
735 log_write(input_char);
737 print_tainted = true;
739 if (input_char == '\n' && option.timestamp)
740 next_timestamp = true;
743 /* Error reading - device is likely unplugged */
744 error_printf_silent("Could not read from tty device");
748 if (FD_ISSET(STDIN_FILENO, &rdfs))
752 /* Input from stdin ready */
753 status = read(STDIN_FILENO, &input_char, 1);
756 error_printf_silent("Could not read from stdin");
760 /* Forward input to output except ctrl-t key */
761 output_char = input_char;
762 if (input_char == KEY_CTRL_T)
765 /* Handle commands */
766 handle_command_sequence(input_char, previous_char, &output_char, &forward);
770 /* Map output character */
771 if ((output_char == 127) && (map_o_del_bs))
773 if ((output_char == '\r') && (map_o_cr_nl))
776 /* Map newline character */
777 if ((output_char == '\n' || output_char == '\r') && (map_o_nl_crnl)) {
778 const char *crlf = "\r\n";
780 optional_local_echo(crlf[0]);
781 optional_local_echo(crlf[1]);
782 status = write(fd, crlf, 2);
784 warning_printf("Could not write to tty device");
787 delay(option.output_delay);
790 /* Send output to tty device */
791 optional_local_echo(output_char);
792 status = write(fd, &output_char, 1);
794 warning_printf("Could not write to tty device");
797 /* Update transmit statistics */
800 /* Insert output delay */
801 delay(option.output_delay);
805 /* Save previous key */
806 previous_char = input_char;
809 } else if (status == -1)
811 error_printf("Error: select() failed (%s)", strerror(errno));
829 void list_serial_devices(void)
831 DIR *d = opendir(PATH_SERIAL_DEVICES);
835 while ((dir = readdir(d)) != NULL)
837 if ((strcmp(dir->d_name, ".")) && (strcmp(dir->d_name, "..")))
840 #define TTY_DEVICES_PREFIX "tty."
841 if (!strncmp(dir->d_name, TTY_DEVICES_PREFIX, sizeof(TTY_DEVICES_PREFIX) - 1))
843 printf("%s%s\n", PATH_SERIAL_DEVICES, dir->d_name);