/* * tio - a simple serial terminal I/O tool * * Copyright (c) 2014-2022 Martin Lund * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "configfile.h" #include "tty.h" #include "print.h" #include "options.h" #include "misc.h" #include "log.h" #include "error.h" #include "socket.h" #ifdef HAVE_TERMIOS2 extern int setspeed2(int fd, int baudrate); #endif #ifdef HAVE_IOSSIOSPEED extern int iossiospeed(int fd, int baudrate); #endif #ifdef __APPLE__ #define PATH_SERIAL_DEVICES "/dev/" #else #define PATH_SERIAL_DEVICES "/dev/serial/by-id/" #endif bool interactive_mode = true; static struct termios tio, tio_old, stdout_new, stdout_old, stdin_new, stdin_old; static unsigned long rx_total = 0, tx_total = 0; static bool connected = false; static bool print_mode = NORMAL; static bool standard_baudrate = true; static void (*print)(char c); static int fd; static bool map_i_nl_crnl = false; static bool map_o_cr_nl = false; static bool map_o_nl_crnl = false; static bool map_o_del_bs = false; static char hex_chars[2]; static unsigned char hex_char_index = 0; static char tty_buffer[BUFSIZ*2]; static size_t tty_buffer_count = 0; static char *tty_buffer_write_ptr = tty_buffer; static void optional_local_echo(char c) { if (!option.local_echo) { return; } print(c); if (option.log) { log_putc(c); } } inline static bool is_valid_hex(char c) { return ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); } inline static unsigned char char_to_nibble(char c) { if(c >= '0' && c <= '9') { return c - '0'; } else if (c >= 'a' && c <= 'f') { return c - 'a' + 10; } else if (c >= 'A' && c <= 'F') { return c - 'A' + 10; } else { return 0; } } void tty_flush(int fd) { ssize_t count; do { count = write(fd, tty_buffer, tty_buffer_count); if (count < 0) { // Error debug_printf("Write error while flushing tty buffer (%s)", strerror(errno)); break; } tty_buffer_count -= count; } while (tty_buffer_count > 0); // Reset tty_buffer_write_ptr = tty_buffer; tty_buffer_count = 0; } ssize_t tty_write(int fd, const void *buffer, size_t count) { ssize_t bytes_written = 0; if (option.output_delay || option.eol_delay ) { // Write byte by byte with output delay for (size_t i=0; i BUFSIZ) { tty_flush(fd); } // convert lower case to upper case, in situ if ( option.upcase ) { for ( size_t i = 0; i 0) { if (FD_ISSET(STDIN_FILENO, &rdfs)) { /* Input from stdin ready */ /* Read one character */ status = read(STDIN_FILENO, &input_char, 1); if (status <= 0) { error_printf("Could not read from stdin"); exit(EXIT_FAILURE); } /* Handle commands */ handle_command_sequence(input_char, previous_char, NULL, NULL); previous_char = input_char; } socket_handle_input(&rdfs, NULL); } else if (status == -1) { error_printf("select() failed (%s)", strerror(errno)); exit(EXIT_FAILURE); } /* Test for accessible device file */ status = access(option.tty_device, R_OK); if (status == 0) { last_errno = 0; return; } else if (last_errno != errno) { warning_printf("Could not open tty device (%s)", strerror(errno)); tio_printf("Waiting for tty device.."); last_errno = errno; } } } void tty_disconnect(void) { if (connected) { tio_printf("Disconnected"); flock(fd, LOCK_UN); close(fd); connected = false; } } void tty_restore(void) { tcsetattr(fd, TCSANOW, &tio_old); if (connected) { tty_disconnect(); } } void forward_to_tty(int fd, char output_char) { int status; /* Map output character */ if ((output_char == 127) && (map_o_del_bs)) { output_char = '\b'; } if ((output_char == '\r') && (map_o_cr_nl)) { output_char = '\n'; } /* Map newline character */ if ((output_char == '\n' || output_char == '\r') && (map_o_nl_crnl)) { const char *crlf = "\r\n"; optional_local_echo(crlf[0]); optional_local_echo(crlf[1]); status = tty_write(fd, crlf, 2); if (status < 0) { warning_printf("Could not write to tty device"); } tx_total += 2; } else { if (print_mode == HEX) { output_hex(output_char); } else { /* Send output to tty device */ optional_local_echo(output_char); status = tty_write(fd, &output_char, 1); if (status < 0) { warning_printf("Could not write to tty device"); } /* Update transmit statistics */ tx_total++; } } } int tty_connect(void) { fd_set rdfs; /* Read file descriptor set */ int maxfd; /* Maximum file descriptor used */ char input_char, output_char; char input_buffer[BUFSIZ]; static char previous_char = 0; static bool first = true; int status; bool next_timestamp = false; char* now = NULL; /* Open tty device */ fd = open(option.tty_device, O_RDWR | O_NOCTTY | O_NONBLOCK); if (fd < 0) { error_printf_silent("Could not open tty device (%s)", strerror(errno)); goto error_open; } /* Make sure device is of tty type */ if (!isatty(fd)) { error_printf("Not a tty device"); exit(EXIT_FAILURE);; } /* Lock device file */ status = flock(fd, LOCK_EX | LOCK_NB); if ((status == -1) && (errno == EWOULDBLOCK)) { error_printf("Device file is locked by another process"); exit(EXIT_FAILURE); } /* Flush stale I/O data (if any) */ tcflush(fd, TCIOFLUSH); /* Print connect status */ tio_printf("Connected"); connected = true; print_tainted = false; if (option.timestamp) { next_timestamp = true; } /* Manage print output mode */ if (option.hex_mode) { print = print_hex; print_mode = HEX; } else { print = print_normal; print_mode = NORMAL; } /* Save current port settings */ if (tcgetattr(fd, &tio_old) < 0) { goto error_tcgetattr; } #ifdef HAVE_IOSSIOSPEED if (!standard_baudrate) { /* OS X wants these fields left alone. We'll set baudrate with iossiospeed below. */ tio.c_ispeed = tio_old.c_ispeed; tio.c_ospeed = tio_old.c_ospeed; } #endif /* Make sure we restore tty settings on exit */ if (first) { atexit(&tty_restore); first = false; } /* Activate new port settings */ status = tcsetattr(fd, TCSANOW, &tio); if (status == -1) { error_printf_silent("Could not apply port settings (%s)", strerror(errno)); goto error_tcsetattr; } #ifdef HAVE_TERMIOS2 if (!standard_baudrate) { if (setspeed2(fd, option.baudrate) != 0) { error_printf_silent("Could not set baudrate speed (%s)", strerror(errno)); goto error_setspeed; } } #endif #ifdef HAVE_IOSSIOSPEED if (!standard_baudrate) { if (iossiospeed(fd, option.baudrate) != 0) { error_printf_silent("Could not set baudrate speed (%s)", strerror(errno)); goto error_setspeed; } } #endif /* Input loop */ while (true) { FD_ZERO(&rdfs); FD_SET(fd, &rdfs); FD_SET(STDIN_FILENO, &rdfs); maxfd = MAX(fd, STDIN_FILENO); maxfd = MAX(maxfd, socket_add_fds(&rdfs, true)); /* Block until input becomes available */ status = select(maxfd + 1, &rdfs, NULL, NULL, NULL); if (status > 0) { bool forward = false; if (FD_ISSET(fd, &rdfs)) { /* Input from tty device ready */ ssize_t bytes_read = read(fd, input_buffer, BUFSIZ); if (bytes_read <= 0) { /* Error reading - device is likely unplugged */ error_printf_silent("Could not read from tty device"); goto error_read; } /* Update receive statistics */ rx_total += bytes_read; /* Process input byte by byte */ for (int i=0; id_name, ".")) && (strcmp(dir->d_name, ".."))) { #ifdef __APPLE__ #define TTY_DEVICES_PREFIX "tty." if (!strncmp(dir->d_name, TTY_DEVICES_PREFIX, sizeof(TTY_DEVICES_PREFIX) - 1)) #endif printf("%s%s\n", PATH_SERIAL_DEVICES, dir->d_name); } } closedir(d); } }