]> git.sur5r.net Git - tio/commitdiff
New upstream version 1.38 upstream/1.38
authorJakob Haufe <sur5r@debian.org>
Mon, 6 Jun 2022 20:58:22 +0000 (20:58 +0000)
committerJakob Haufe <sur5r@debian.org>
Mon, 6 Jun 2022 20:58:22 +0000 (20:58 +0000)
18 files changed:
AUTHORS
ChangeLog
README.md
man/tio.1.in
meson.build
src/bash-completion/tio.in
src/configfile.c
src/configfile.h
src/error.c
src/log.c
src/main.c
src/meson.build
src/options.c
src/options.h
src/print.h
src/socket.c [new file with mode: 0644]
src/socket.h [new file with mode: 0644]
src/tty.c

diff --git a/AUTHORS b/AUTHORS
index b126af630b633e4c9bb52237bb7231100d427e33..65f22e5369e053b768c517e1d569630e4f016003 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -32,5 +32,6 @@ Mariusz Midor <dexlab@o2.pl>
 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.
index 55ebe02b14143b49bdb8a415e7dd6023a9469301..26b1f3e66ceb06f5d31605aaebb0ed58ec01dfda 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,4 +1,88 @@
-=== 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
 
 
 
index 668385ec7645890326cac868abb1e67e7121bbce..fdeacde451d5af26ad0ed71b294e579c8f17318b 100644 (file)
--- a/README.md
+++ b/README.md
@@ -2,6 +2,7 @@
 
 [![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
 
@@ -24,7 +25,7 @@ embedded developers and hackers.
 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)
@@ -40,12 +41,15 @@ The command-line interface is straightforward as reflected in the output from
       -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
index 38c44d8154fc64a1c3248b78f1f138cf45df613b..f129dc6a470824bd873a6a18b04b8b7475f6064e 100644 (file)
@@ -6,13 +6,13 @@ tio \- a simple serial terminal I/O tool
 .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"
 
@@ -114,6 +114,23 @@ Colorize tio text using ANSI color code ranging from 0 to 255.
 
 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
 
@@ -159,14 +176,14 @@ Show 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:
@@ -201,17 +218,19 @@ Set log filename
 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
@@ -220,14 +239,14 @@ 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
@@ -270,4 +289,4 @@ Visit https://tio.github.io
 
 .SH "AUTHOR"
 .PP
-Written by Martin Lund <martin.lund@keep\-it\-simple.com>.
+Created by Martin Lund <martin.lund@keep\-it\-simple.com>.
index 22e9684102286fcd10f3b341054de167c774fb5d..63900cf68271b68ffcf53ac998c2db2a38906bc7 100644 (file)
@@ -1,12 +1,12 @@
 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')
index d64d7e3fe427416c7d4a1e459fa8b741c1af1a1c..58da18da21bb176ba07aa0ccc6df89ab867029d3 100644 (file)
@@ -80,6 +80,10 @@ _tio()
             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
index 9d110239b9de1d21b1f3e384d6ecc32f74ed63f8..ac73c547f3942b19f27a86508f1494b116e0f3ad 100644 (file)
@@ -56,7 +56,7 @@ static int get_match(const char *input, const char *pattern, char **match)
     if (ret)
     {
         regerror(ret, &re, err, sizeof(err));
-        printf("reg error: %s\n", err);
+        fprintf(stderr, "regex error: %s", err);
         return ret;
     }
 
@@ -152,19 +152,24 @@ static int data_handler(void *user, const char *section, const char *name,
         {
             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);
 
@@ -185,6 +190,28 @@ static int section_search_handler(void *user, const char *section, const char
     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"));
@@ -216,7 +243,12 @@ void config_file_parse(const int argc, char *argv[])
     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++)
     {
@@ -232,17 +264,33 @@ void config_file_parse(const int argc, char *argv[])
         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);
     }
 }
index 7d942736451cbde8013094206c769f357464d87a..57ae2a1a7b65ea46fbc1862bd3390dab2049f7b2 100644 (file)
@@ -34,6 +34,7 @@ struct config_t
        char *flow;
        char *parity;
        char *log_filename;
+       char *socket;
        char *map;
 };
 
index 96969f7bf0f0575252c65e7aa750ee8b538936b0..8e25a8cde26d5d32e5d19cdad5a65601dc0d3b5d 100644 (file)
@@ -35,11 +35,11 @@ void error_exit(void)
   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]);
   }
 }
index 5aef6b4e0572f2510795caacc85b327416db5f74..640f8349c778759b63a2ced74c7c2ddd2f4edb8d 100644 (file)
--- a/src/log.c
+++ b/src/log.c
@@ -60,7 +60,7 @@ void log_open(const char *filename)
         option.log_filename = automatic_filename;
     }
 
-    fp = fopen(filename, "w+");
+    fp = fopen(filename, "a+");
 
     if (fp == NULL)
     {
index 44cedf28889eb68a50a2bd969eb4047c2cc6b425..49837a98a093fac463f07f39c19ca2892d88468f 100644 (file)
@@ -30,6 +30,7 @@
 #include "error.h"
 #include "print.h"
 #include "signals.h"
+#include "socket.h"
 
 int main(int argc, char *argv[])
 {
@@ -78,6 +79,9 @@ 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();
index 6f1e73788d09d9953be078e997ef2908332c72b7..38d300ad43d87f597aafa631bc0763ad91e1398b 100644 (file)
@@ -12,7 +12,8 @@ tio_sources = [
   'tty.c',
   'print.c',
   'configfile.c',
-  'signals.c'
+  'signals.c',
+  'socket.c'
 ]
 
 tio_dep = dependency('inih', required: true,
index fc24974b9110ffefe859292636ac7a7d7a2a1039..e02ee52583c3cf3a795097295b3298fbce6fd829 100644 (file)
@@ -50,14 +50,15 @@ struct option_t option =
     .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");
@@ -73,13 +74,17 @@ void print_help(char *argv[])
     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)
@@ -147,6 +152,8 @@ void options_print()
         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[])
@@ -174,6 +181,7 @@ 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'},
@@ -185,7 +193,7 @@ void options_parse(int argc, char *argv[])
         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)
@@ -248,6 +256,10 @@ void options_parse(int argc, char *argv[])
                 option.log_filename = optarg;
                 break;
 
+            case 'S':
+                option.socket = optarg;
+                break;
+
             case 'm':
                 option.map = optarg;
                 break;
index 80106021922ea8e80f56691d41ac96293a36c47a..98bec82744a512bae530fb92810daef20eb10c5b 100644 (file)
@@ -53,6 +53,7 @@ struct option_t
     bool list_devices;
     const char *log_filename;
     const char *map;
+    const char *socket;
     int color;
 };
 
index 3f8e16e915b596d73d0ade0d027ce8843d187e16..1c6b1e58b0be20e68a5694e7e0b91ca79cb14943 100644 (file)
@@ -36,6 +36,12 @@ extern char ansi_format[];
   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); \
@@ -56,6 +62,14 @@ extern char ansi_format[];
   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);
 
diff --git a/src/socket.c b/src/socket.c
new file mode 100644 (file)
index 0000000..d2b125e
--- /dev/null
@@ -0,0 +1,197 @@
+/*
+ * 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;
+}
diff --git a/src/socket.h b/src/socket.h
new file mode 100644 (file)
index 0000000..2caffaf
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * 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);
index 9667ac951dc12292945ec8b5096664cbf1d734a8..0105ea86b0897af8f9e1f341df34e5f8084f0b86 100644 (file)
--- a/src/tty.c
+++ b/src/tty.c
@@ -31,6 +31,8 @@
 #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>
@@ -45,6 +47,7 @@
 #include "misc.h"
 #include "log.h"
 #include "error.h"
+#include "socket.h"
 
 #ifdef HAVE_TERMIOS2
 extern int setspeed2(int fd, int baudrate);
@@ -164,7 +167,7 @@ void handle_command_sequence(char input_char, char previous_char, char *output_c
                 break;
 
             case KEY_C:
-                tio_printf("Configuraiton:");
+                tio_printf("Configuration:");
                 config_file_print();
                 options_print();
                 break;
@@ -494,6 +497,7 @@ void tty_wait_for_device(void)
 {
     fd_set rdfs;
     int    status;
+    int    maxfd;
     struct timeval tv;
     static char input_char, previous_char = 0;
     static bool first = true;
@@ -517,26 +521,30 @@ void tty_wait_for_device(void)
 
         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));
@@ -686,19 +694,20 @@ int tty_connect(void)
     }
 #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 */
@@ -747,6 +756,8 @@ int tty_connect(void)
                     if (option.log)
                         log_write(input_char);
 
+                    socket_write(input_char);
+
                     print_tainted = true;
 
                     if (input_char == '\n' && option.timestamp)
@@ -760,7 +771,7 @@ int tty_connect(void)
             }
             if (FD_ISSET(STDIN_FILENO, &rdfs))
             {
-                bool forward = true;
+                forward = true;
 
                 /* Input from stdin ready */
                 status = read(STDIN_FILENO, &input_char, 1);
@@ -778,47 +789,51 @@ int tty_connect(void)
                 /* 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));