attila-v <attila_v@index.hu>
Yin Fengwei <fengwei.yin@intel.com>
Liam Beguin <liambeguin@gmail.com>
+Peter Collingbourne <pcc@google.com>
Thanks to everyone who has contributed to this project.
-=== tio v1.37 ===
+=== tio v1.38 ===
+
+
+
+Changes since tio v1.37:
+
+ * Redirect error messages to stderr
+
+ * Improve help and man page
+
+ * Mention config file in --help
+
+ * Fix running without config file
+
+ * Fix config file error messages
+
+ * Redirect error messages to stderr
+
+ * Add repology packaging status
+
+ * Fix parsing of default settings
+
+ Default configuration file settings were not parsed in case a section
+ was matched. Now we make sure that the default (unnamed) settings are
+ always parsed.
+
+ * Append to existing log file (no truncation)
+
+ * Add socket info to show configuration
+
+ * Print socket info at startup
+
+ * Fix socket option parsing
+
+Peter Collingbourne:
+
+ * Match user input against config section names if pattern matching was unsuccessful.
+
+ This allows for better config file ergonomics if the user has a diverse
+ set of serial devices as the name does not need to be specified in
+ the config file twice.
+
+ * Add support for external control via a Unix domain socket.
+
+ This feature allows an external program to inject output into and
+ listen to input from a serial port via a Unix domain socket (path
+ specified via the -S/--socket command line flag, or the socket
+ config file option) while tio is running. This is useful for ad-hoc
+ scripting of serial port interactions while still permitting manual
+ control. Since many serial devices (at least on Linux) get confused
+ when opened by multiple processes, and most commands do not know
+ how to correctly open a serial device, this allows a more convenient
+ usage model than directly writing to the device node from an external
+ program.
+
+ Any input from clients connected to the socket is sent on the serial
+ port as if entered at the terminal where tio is running (except that
+ ctrl-t sequences are not recognized), and any input from the serial
+ port is multiplexed to the terminal and all connected clients.
+
+ Sockets remain open while the serial port is disconnected, and writes
+ will block.
+
+ Example usage 1 (issue a command):
+
+ echo command | nc -UN /path/to/socket > /dev/null
+
+ Example usage 2 (use the expect command to script an interaction):
+
+ #!/usr/bin/expect -f
+
+ set timeout -1
+ log_user 0
+
+ spawn nc -UN /path/to/socket
+ set uart $spawn_id
+
+ send -i $uart "command1\n"
+ expect -i $uart "prompt> "
+ send -i $uart "command2\n"
+ expect -i $uart "prompt> "
+
+lexaone:
+
+ * fix for using option 'log' without 'log-filename' in config file
[![CircleCI](https://circleci.com/gh/tio/tio/tree/master.svg?style=shield)](https://circleci.com/gh/tio/tio/tree/master)
[![tio](https://snapcraft.io/tio/badge.svg)](https://snapcraft.io/tio)
+[![Packaging status](https://repology.org/badge/tiny-repos/tio.svg)](https://repology.org/project/tio/versions)
## 1. Introduction
The command-line interface is straightforward as reflected in the output from
'tio --help':
```
- Usage: tio [<options>] <tty-device>
+ Usage: tio [<options>] <tty-device|config>
Options:
-b, --baudrate <bps> Baud rate (default: 115200)
-l, --log <filename> Log to file
-m, --map <flags> Map special characters
-c, --color <0..255> Colorize tio text
+ -S, --socket <socket> Listen on socket
-v, --version Display version
-h, --help Display help
- See the man page for more details.
+ Options may be set via configuration file.
In session, press ctrl-t q to quit.
+
+ See the man page for more details.
```
The only option which requires a bit of elaboration is perhaps the
.SH "SYNOPSIS"
.PP
.B tio
-.RI "[" <options> "] " "<tty-device>"
+.RI "[" <options> "] " "<tty-device|config>"
.SH "DESCRIPTION"
.PP
.B tio
is a simple serial terminal tool which features a straightforward command-line
-interface to easily connect to serial/TTY devices for basic I/O operations.
+interface to easily connect to serial TTY devices for basic I/O operations.
.SH "OPTIONS"
If color code is negative a list of available ANSI colors will be printed.
+.TP
+.BR \-S ", " "\-\-socket \fI<socket>\fR\fB
+
+Listen on socket. Any input from clients connected to the socket is sent on the serial port as if entered at the terminal where tio is running (except that
+.B ctrl-t
+sequences are not recognized), and any input from the serial port is multiplexed to the terminal and all connected clients.
+
+Sockets remain open while the serial port is disconnected, and writes will block.
+
+Two socket types are supported using different prefixes in the socket field:
+
+unix:<filename> - Unix Domain Socket (file)
+
+inet:<IP>:<port> - Internet Socket (network) (NOT YET SUPPORTED)
+
+At present there is a hardcoded limit of 16 clients connected at one time.
+
.TP
.BR \-v ", " \-\-version
Options can be set via a configuration file using the INI format. tio uses the configuration file first found in the following locations in the order listed: $XDG_CONFIG_HOME/tio/tiorc, $HOME/.config/tio/tiorc, $HOME/.tiorc
.TP
-Sections can be used to group settings and their names are only used for readability.
+Config sections can be used to group settings.
.TP
.TP
-tio will try to match the user input to a section pattern to get the tty and other options.
+tio will try to match the user input to a config section by name or by pattern to get the tty and other options.
.TP
-Options without any section name sets the default options.
+Options without any config section name sets the default options.
.TP
The following configuration file options are available:
Map special characters on input or output
.IP "\fBcolor"
Colorize tio text using ANSI color code ranging from 0 to 255.
+.IP "\fBsocket"
+Set socket path (must include
+.BR unix: ).
.SH "CONFIGURATION EXAMPLES"
.TP
-A Typical section used as a short-hand would be defined as such:
+A typical config section used as a short-hand would be defined as such:
.RS
.nf
.eo
-[ftdi device]
-pattern=ftdi
+[ftdi]
baudrate=115200
tty=/dev/serial/by-id/usb-FTDI_TTL232R-3V3_FTGQVXBL-if00-port0
color=11
.RE
.TP
-With this section defined in the configuration file the following commands would be equivalent:
+With this config section defined in the configuration file the following commands would be equivalent:
tio ftdi
tio -b 115200 /dev/serial/by-id/usb-FTDI_TTL232R-3V3_FTGQVXBL-if00-port0
.TP
-A pattern can also be a regular expression:
+A config section can also be matched by its pattern which supports regular expressions:
.RS
.nf
.SH "AUTHOR"
.PP
-Written by Martin Lund <martin.lund@keep\-it\-simple.com>.
+Created by Martin Lund <martin.lund@keep\-it\-simple.com>.
project('tio', 'c',
- version : '1.37',
+ version : '1.38',
license : [ 'GPL-2'],
meson_version : '>= 0.53.2',
default_options : [ 'warning_level=2', 'buildtype=release', 'c_std=gnu99' ]
)
# The tag date of the project_version(), update when the version bumps.
-version_date = '2022-04-13'
+version_date = '2022-06-02'
# Test for dynamic baudrate configuration interface
compiler = meson.get_compiler('c')
COMPREPLY=( $(compgen -W "$(seq 0 255)" -- ${cur}) )
return 0
;;
+ -S | --socket)
+ COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+ return 0
+ ;;
-v | --version)
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
if (ret)
{
regerror(ret, &re, err, sizeof(err));
- printf("reg error: %s\n", err);
+ fprintf(stderr, "regex error: %s", err);
return ret;
}
{
option.color = atoi(value);
}
+ else if (!strcmp(name, "socket"))
+ {
+ asprintf(&c->socket, "%s", value);
+ option.socket = c->socket;
+ }
}
return 0;
}
/**
- * section_search_handler() - walk config file to find section matching user input
+ * section_pattern_search_handler() - walk config file to find section matching user input
*
* INIH handler used to resolve the section matching the user's input.
* This will look for the pattern element of each section and try to match it
* with the user input.
*/
-static int section_search_handler(void *user, const char *section, const char
- *varname, const char *varval)
+static int section_pattern_search_handler(void *user, const char *section, const char *varname,
+ const char *varval)
{
UNUSED(user);
return 0;
}
+/**
+ * section_pattern_search_handler() - walk config file to find section matching user input
+ *
+ * INIH handler used to resolve the section matching the user's input.
+ * This will try to match the user input against a section with the name of the user input.
+ */
+static int section_name_search_handler(void *user, const char *section, const char *varname,
+ const char *varval)
+{
+ UNUSED(user);
+ UNUSED(varname);
+ UNUSED(varval);
+
+ if (!strcmp(section, c->user))
+ {
+ /* section name matches as plain text */
+ asprintf(&c->section_name, "%s", section);
+ }
+
+ return 0;
+}
+
static int resolve_config_file(void)
{
asprintf(&c->path, "%s/tio/tiorc", getenv("XDG_CONFIG_HOME"));
c = malloc(sizeof(struct config_t));
memset(c, 0, sizeof(struct config_t));
- resolve_config_file();
+ // Find config file
+ if (resolve_config_file() != 0)
+ {
+ // None found - stop parsing
+ return;
+ }
for (i = 1; i < argc; i++)
{
return;
}
- ret = ini_parse(c->path, section_search_handler, NULL);
+ // Parse default (unnamed) settings
+ asprintf(&c->section_name, "%s", "");
+ ret = ini_parse(c->path, data_handler, NULL);
+ if (ret < 0)
+ {
+ fprintf(stderr, "Error: Unable to parse configuration file (%d)", ret);
+ exit(EXIT_FAILURE);
+ }
+ c->section_name = NULL;
+
+ // Find matching section
+ ret = ini_parse(c->path, section_pattern_search_handler, NULL);
if (!c->section_name)
{
- debug_printf("unable to match user input to configuration section (%d)\n", ret);
- return;
+ ret = ini_parse(c->path, section_name_search_handler, NULL);
+ if (!c->section_name)
+ {
+ debug_printf("Unable to match user input to configuration section (%d)", ret);
+ return;
+ }
}
+ // Parse settings of found section
ret = ini_parse(c->path, data_handler, NULL);
if (ret < 0)
{
- fprintf(stderr, "Error: unable to parse configuration file (%d)\n", ret);
+ fprintf(stderr, "Error: Unable to parse configuration file (%d)", ret);
exit(EXIT_FAILURE);
}
}
char *flow;
char *parity;
char *log_filename;
+ char *socket;
char *map;
};
if (error[0][0] != 0)
{
/* Print error */
- tio_printf("Error: %s", error[0]);
+ tio_error_printf("Error: %s", error[0]);
}
else if ((error[1][0] != 0) && (option.no_autoconnect))
{
/* Print silent error */
- tio_printf("Error: %s", error[1]);
+ tio_error_printf("Error: %s", error[1]);
}
}
option.log_filename = automatic_filename;
}
- fp = fopen(filename, "w+");
+ fp = fopen(filename, "a+");
if (fp == NULL)
{
#include "error.h"
#include "print.h"
#include "signals.h"
+#include "socket.h"
int main(int argc, char *argv[])
{
tio_printf("tio v%s", VERSION);
tio_printf("Press ctrl-t q to quit");
+ /* Open socket */
+ socket_configure();
+
/* Connect to tty device */
if (option.no_autoconnect)
status = tty_connect();
'tty.c',
'print.c',
'configfile.c',
- 'signals.c'
+ 'signals.c',
+ 'socket.c'
]
tio_dep = dependency('inih', required: true,
.local_echo = false,
.timestamp = TIMESTAMP_NONE,
.list_devices = false,
- .log_filename = "",
+ .log_filename = NULL,
+ .socket = NULL,
.map = "",
.color = -1,
};
void print_help(char *argv[])
{
- printf("Usage: %s [<options>] <tty-device>\n", argv[0]);
+ printf("Usage: %s [<options>] <tty-device|config>\n", argv[0]);
printf("\n");
printf("Options:\n");
printf(" -b, --baudrate <bps> Baud rate (default: 115200)\n");
printf(" -l, --log[=<filename>] Log to file\n");
printf(" -m, --map <flags> Map special characters\n");
printf(" -c, --color <code> Colorize tio text\n");
+ printf(" -S, --socket <socket> Listen on socket\n");
printf(" -v, --version Display version\n");
printf(" -h, --help Display help\n");
printf("\n");
- printf("See the man page for more details.\n");
+ printf("Options may be set via configuration file.\n");
printf("\n");
printf("In session, press ctrl-t q to quit.\n");
printf("\n");
+ printf("See the man page for more details.\n");
+ printf("\n");
+
}
const char* timestamp_token(enum timestamp_t timestamp)
tio_printf(" Map flags: %s", option.map);
if (option.log)
tio_printf(" Log file: %s", option.log_filename);
+ if (option.socket)
+ tio_printf(" Socket: %s", option.socket);
}
void options_parse(int argc, char *argv[])
{"timestamp", optional_argument, 0, 't'},
{"list-devices", no_argument, 0, 'L'},
{"log", optional_argument, 0, 'l'},
+ {"socket", required_argument, 0, 'S'},
{"map", required_argument, 0, 'm'},
{"color", required_argument, 0, 'c'},
{"version", no_argument, 0, 'v'},
int option_index = 0;
/* Parse argument using getopt_long */
- c = getopt_long(argc, argv, "b:d:f:s:p:o:net::Ll::m:c:vh", long_options, &option_index);
+ c = getopt_long(argc, argv, "b:d:f:s:p:o:net::Ll::S:m:c:vh", long_options, &option_index);
/* Detect the end of the options */
if (c == -1)
option.log_filename = optarg;
break;
+ case 'S':
+ option.socket = optarg;
+ break;
+
case 'm':
option.map = optarg;
break;
bool list_devices;
const char *log_filename;
const char *map;
+ const char *socket;
int color;
};
fflush(stdout); \
}
+#define ansi_error_printf(format, args...) \
+{ \
+ fprintf (stderr, "\r%s" format ANSI_RESET "\r\n", ansi_format, ## args); \
+ fflush(stderr); \
+}
+
#define ansi_printf_raw(format, args...) \
{ \
fprintf (stdout, "%s" format ANSI_RESET, ansi_format, ## args); \
print_tainted = false; \
}
+#define tio_error_printf(format, args...) \
+{ \
+ if (print_tainted) \
+ putchar('\n'); \
+ ansi_error_printf("[%s] " format, current_time(), ## args); \
+ print_tainted = false; \
+}
+
#define error_printf(format, args...) \
snprintf(error[0], 1000, format, ## args);
--- /dev/null
+/*
+ * tio - a simple serial terminal I/O tool
+ *
+ * Copyright (c) 2014-2022 Martin Lund
+ * Copyright (c) 2022 Google LLC
+ *
+ * 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 "socket.h"
+#include "options.h"
+#include "print.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#define MAX_SOCKET_CLIENTS 16
+
+static int sockfd;
+static int clientfds[MAX_SOCKET_CLIENTS];
+
+static const char *socket_filename(void)
+{
+ /* skip 'unix:' */
+ return option.socket + 5;
+}
+
+static void socket_exit(void)
+{
+ unlink(socket_filename());
+}
+
+void socket_configure(void)
+{
+ if (!option.socket)
+ {
+ return;
+ }
+
+ if (strncmp(option.socket, "unix:", 5) != 0)
+ {
+ error_printf("%s: Invalid socket scheme, must be 'unix:'", option.socket);
+ exit(EXIT_FAILURE);
+ }
+
+ struct sockaddr_un sockaddr = {};
+ if (strlen(socket_filename()) > sizeof(sockaddr.sun_path) - 1)
+ {
+ error_printf("Socket file path %s too long", option.socket);
+ exit(EXIT_FAILURE);
+ }
+
+ sockaddr.sun_family = AF_UNIX;
+ strncpy(sockaddr.sun_path, socket_filename(), sizeof(sockaddr.sun_path) - 1);
+
+ sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (sockfd < 0)
+ {
+ error_printf("Failed to create socket: %s", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ unlink(socket_filename());
+ if (bind(sockfd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) < 0)
+ {
+ error_printf("Failed to bind to socket %s: %s", socket_filename(), strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ if (listen(sockfd, MAX_SOCKET_CLIENTS) < 0)
+ {
+ error_printf("Failed to listen on socket %s: %s", socket_filename(), strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ memset(clientfds, -1, sizeof(clientfds));
+ atexit(socket_exit);
+
+ tio_printf("Listening on socket %s", socket_filename());
+}
+
+void socket_write(char input_char)
+{
+ if (!option.socket)
+ {
+ return;
+ }
+
+ for (int i = 0; i != MAX_SOCKET_CLIENTS; ++i)
+ {
+ if (clientfds[i] != -1)
+ {
+ if (write(clientfds[i], &input_char, 1) <= 0)
+ {
+ error_printf_silent("Failed to write to socket: %s", strerror(errno));
+ close(clientfds[i]);
+ clientfds[i] = -1;
+ }
+ }
+ }
+}
+
+int socket_add_fds(fd_set *rdfs, bool connected)
+{
+ if (!option.socket)
+ {
+ return 0;
+ }
+
+ int numclients = 0, maxfd = 0;
+ for (int i = 0; i != MAX_SOCKET_CLIENTS; ++i)
+ {
+ if (clientfds[i] != -1)
+ {
+ /* let clients block if they try to send while we're disconnected */
+ if (connected)
+ {
+ FD_SET(clientfds[i], rdfs);
+ maxfd = MAX(maxfd, clientfds[i]);
+ }
+ numclients++;
+ }
+ }
+ /* don't bother to accept clients if we're already full */
+ if (numclients != MAX_SOCKET_CLIENTS)
+ {
+ FD_SET(sockfd, rdfs);
+ maxfd = MAX(maxfd, sockfd);
+ }
+ return maxfd;
+}
+
+bool socket_handle_input(fd_set *rdfs, char *output_char)
+{
+ if (!option.socket)
+ {
+ return false;
+ }
+
+ if (FD_ISSET(sockfd, rdfs))
+ {
+ int clientfd = accept(sockfd, NULL, NULL);
+ /* this loop should always succeed because we don't select on sockfd when full */
+ for (int i = 0; i != MAX_SOCKET_CLIENTS; ++i)
+ {
+ if (clientfds[i] == -1)
+ {
+ clientfds[i] = clientfd;
+ break;
+ }
+ }
+ }
+ for (int i = 0; i != MAX_SOCKET_CLIENTS; ++i)
+ {
+ if (clientfds[i] != -1 && FD_ISSET(clientfds[i], rdfs))
+ {
+ int status = read(clientfds[i], output_char, 1);
+ if (status == 0)
+ {
+ close(clientfds[i]);
+ clientfds[i] = -1;
+ continue;
+ }
+ if (status < 0)
+ {
+ error_printf_silent("Failed to read from socket: %s", strerror(errno));
+ close(clientfds[i]);
+ clientfds[i] = -1;
+ continue;
+ }
+ /* match the behavior of a terminal in raw mode */
+ if (*output_char == '\n')
+ {
+ *output_char = '\r';
+ }
+ return true;
+ }
+ }
+ return false;
+}
--- /dev/null
+/*
+ * tio - a simple serial terminal I/O tool
+ *
+ * Copyright (c) 2014-2022 Martin Lund
+ * Copyright (c) 2022 Google LLC
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include <stdbool.h>
+#include <sys/select.h>
+
+void socket_configure(void);
+void socket_write(char input_char);
+int socket_add_fds(fd_set *fds, bool connected);
+bool socket_handle_input(fd_set *fds, char *output_char);
#include <sys/param.h>
#include <sys/file.h>
#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/un.h>
#include <fcntl.h>
#include <termios.h>
#include <stdbool.h>
#include "misc.h"
#include "log.h"
#include "error.h"
+#include "socket.h"
#ifdef HAVE_TERMIOS2
extern int setspeed2(int fd, int baudrate);
break;
case KEY_C:
- tio_printf("Configuraiton:");
+ tio_printf("Configuration:");
config_file_print();
options_print();
break;
{
fd_set rdfs;
int status;
+ int maxfd;
struct timeval tv;
static char input_char, previous_char = 0;
static bool first = true;
FD_ZERO(&rdfs);
FD_SET(STDIN_FILENO, &rdfs);
+ maxfd = MAX(STDIN_FILENO, socket_add_fds(&rdfs, false));
/* Block until input becomes available or timeout */
- status = select(STDIN_FILENO + 1, &rdfs, NULL, NULL, &tv);
+ status = select(maxfd + 1, &rdfs, NULL, NULL, &tv);
if (status > 0)
{
- /* Input from stdin ready */
-
- /* Read one character */
- status = read(STDIN_FILENO, &input_char, 1);
- if (status <= 0)
+ if (FD_ISSET(STDIN_FILENO, &rdfs))
{
- error_printf("Could not read from stdin");
- exit(EXIT_FAILURE);
- }
+ /* Input from stdin ready */
- /* Handle commands */
- handle_command_sequence(input_char, previous_char, NULL, NULL);
+ /* Read one character */
+ status = read(STDIN_FILENO, &input_char, 1);
+ if (status <= 0)
+ {
+ error_printf("Could not read from stdin");
+ exit(EXIT_FAILURE);
+ }
- previous_char = input_char;
+ /* 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));
}
#endif
- maxfd = MAX(fd, STDIN_FILENO) + 1; /* Maximum bit entry (fd) to test */
-
/* 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, &rdfs, NULL, NULL, NULL);
+ status = select(maxfd + 1, &rdfs, NULL, NULL, NULL);
if (status > 0)
{
+ bool forward = false;
if (FD_ISSET(fd, &rdfs))
{
/* Input from tty device ready */
if (option.log)
log_write(input_char);
+ socket_write(input_char);
+
print_tainted = true;
if (input_char == '\n' && option.timestamp)
}
if (FD_ISSET(STDIN_FILENO, &rdfs))
{
- bool forward = true;
+ forward = true;
/* Input from stdin ready */
status = read(STDIN_FILENO, &input_char, 1);
/* Handle commands */
handle_command_sequence(input_char, previous_char, &output_char, &forward);
- if (forward)
- {
- /* 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 = write(fd, crlf, 2);
- if (status < 0)
- warning_printf("Could not write to tty device");
-
- tx_total += 2;
- delay(option.output_delay);
- } else
- {
- /* Send output to tty device */
- optional_local_echo(output_char);
- status = write(fd, &output_char, 1);
- if (status < 0)
- warning_printf("Could not write to tty device");
- fsync(fd);
-
- /* Update transmit statistics */
- tx_total++;
-
- /* Insert output delay */
- delay(option.output_delay);
- }
- }
-
/* Save previous key */
previous_char = input_char;
}
+ else
+ {
+ forward = socket_handle_input(&rdfs, &output_char);
+ }
+
+ if (forward)
+ {
+ /* 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 = write(fd, crlf, 2);
+ if (status < 0)
+ warning_printf("Could not write to tty device");
+
+ tx_total += 2;
+ delay(option.output_delay);
+ } else
+ {
+ /* Send output to tty device */
+ optional_local_echo(output_char);
+ status = write(fd, &output_char, 1);
+ if (status < 0)
+ warning_printf("Could not write to tty device");
+ fsync(fd);
+
+ /* Update transmit statistics */
+ tx_total++;
+
+ /* Insert output delay */
+ delay(option.output_delay);
+ }
+ }
} else if (status == -1)
{
error_printf("Error: select() failed (%s)", strerror(errno));