]> git.sur5r.net Git - tio/commitdiff
New upstream version 2.7 upstream
authorJakob Haufe <sur5r@debian.org>
Fri, 13 Oct 2023 13:16:56 +0000 (15:16 +0200)
committerJakob Haufe <sur5r@debian.org>
Fri, 13 Oct 2023 13:16:56 +0000 (15:16 +0200)
13 files changed:
AUTHORS
NEWS
README.md
TODO
man/tio.1.in
man/tio.1.txt
meson.build
src/bash-completion/tio.in
src/meson.build
src/misc.h
src/options.c
src/tty.c
src/xymodem.c [new file with mode: 0644]

diff --git a/AUTHORS b/AUTHORS
index 481dbe89ae2bdba62c5a330929085252a33d6875..c9684ea6ef1ff1a025eed2e15a6827cc0f328a07 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -45,5 +45,8 @@ Vyacheslav Patkov <slava@patkov.ru>
 Bill Hass <billhass@umich.edu>
 Peter van Dijk <peter@7bits.nl>
 Braden Young <braden@somewearlabs.com>
+Wes Koerber <wkoerber@acsd4u.com>
+HiFiPhile <admin@hifiphile.com>
+Paul Ruizendaal <pnr@planet.nl>
 
 Thanks to everyone who has contributed to this project.
diff --git a/NEWS b/NEWS
index 7d67de2829b4e9164e05db7be795467ed74db692..28a3d3127aa00fc5a185c915a05813e2bde805d1 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,31 @@
 
-=== tio v2.6 ===
+=== tio v2.7 ===
+
+
+
+Changes since tio v2.6:
+
+Paul Ruizendaal:
+
+ * Add xmodem and ymodem file send support
+
+HiFiPhile:
+
+ * tty_stdin_input_thread(): write to pipe only if byte_count > 0.
+
+ * Ignore EINTR error.
+
+ * CYGWIN: Add support for "COM*" naming.
+
+Wes Koerber:
+
+ * chore: reorder log-strip and log-append
+
+   reorder to maintain consistency with documentation
+
+ * chore: update readme, bash completion, man page
+
+ * fix: support --log-append in cli options
 
 
 
index c935cf9b9db7966de36ac1f34dcfb254a5563fc7..b0758f77af4bc6e4bfb0bf1482fe70dc4764e8fb 100644 (file)
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
 [![](https://img.shields.io/circleci/build/github/tio/tio)](https://circleci.com/github/tio/tio/tree/master)
 [![](https://img.shields.io/github/v/release/tio/tio?sort=semver)](https://github.com/tio/tio/releases)
 [![](https://img.shields.io/repology/repositories/tio)](https://repology.org/project/tio/versions)
-[![](https://img.shields.io/tokei/lines/github/tio/tio)](https://github.com/tio/tio)
+<!-- [![](https://img.shields.io/tokei/lines/github/tio/tio)](https://github.com/tio/tio) -->
 
 ## 1. Introduction
 
@@ -35,6 +35,7 @@ when used in combination with [tmux](https://tmux.github.io).
  * Sensible defaults (115200 8n1)
  * Support for non-standard baud rates
  * Support for RS-485 mode
+ * X-modem (1K) and Y-modem file upload
  * Support for mark and space parity
  * List available serial devices by ID
  * Show RX/TX statistics
@@ -91,6 +92,7 @@ The command-line interface is straightforward as reflected in the output from
    -L, --list-devices                     List available serial devices
    -l, --log                              Enable log to file
        --log-file <filename>              Set log filename
+       --log-append                       Append to log file
        --log-strip                        Strip control characters and escape sequences
    -m, --map <flags>                      Map characters
    -c, --color 0..255|bold|none|list      Colorize tio text (default: bold)
@@ -193,6 +195,8 @@ ctrl-t ? to list the available key commands.
 [20:19:12.041]  ctrl-t t       Toggle line timestamp mode
 [20:19:12.041]  ctrl-t U       Toggle conversion to uppercase
 [20:19:12.041]  ctrl-t v       Show version
+[20:19:12.041]  ctrl-t x       Send file using the XMODEM protocol
+[20:19:12.041]  ctrl-t y       Send file using the YMODEM protocol
 [20:19:12.041]  ctrl-t ctrl-t  Send ctrl-t character
 ```
 
diff --git a/TODO b/TODO
index 5a0bbf12167ff996ad120773e589e6ba5f803ae0..8bd7f3f9cbd0daf3691c46701157f5e79cbbc35a 100644 (file)
--- a/TODO
+++ b/TODO
@@ -1,4 +1,9 @@
- * Support for interaction using simple autoresponse strings
+* Line mode feature
+
+  Only send line when pressing enter. Maybe even add readline support so one
+  can use all the readline editing feature before sending the line.
+
+* Support for interaction using simple autoresponse strings
 
    Add support for simple autoresponse strings in the configuration file. For
    example:
index ba2a9ad87cbb7b0591335c8649d8d7f0da4ee325..b9cdf1a207f17a7c6e7d20f0c98410e06e484c88 100644 (file)
@@ -309,6 +309,8 @@ Toggle hexadecimal mode
 Clear screen
 .IP "\fBctrl-t L"
 Show line states (DTR, RTS, CTS, DSR, DCD, RI)
+.IP "\fBctrl-t m"
+Toggle MSB to LSB bit order
 .IP "\fBctrl-t p"
 Pulse serial port line
 .IP "\fBctrl-t q"
@@ -321,6 +323,10 @@ Toggle line timestamp mode
 Toggle conversion to uppercase on output
 .IP "\fBctrl-t v"
 Show version
+.IP "\fBctrl-t x"
+Send a file using the XMODEM protocol (prompts for file name)
+.IP "\fBctrl-t y"
+Send a file using the YMODEM protocol (prompts for file name)
 .IP "\fBctrl-t ctrl-t"
 Send ctrl-t character
 
@@ -390,6 +396,8 @@ Disable automatic connect
 Enable log to file
 .IP "\fBlog-file"
 Set log filename
+.IP "\fBlog-append"
+Append to log file
 .IP "\fBlog-strip"
 Enable strip of control and escape sequences from log
 .IP "\fBlocal-echo"
index 252ca60a9ab12226700999d870e71962bd6f3a57..47318ac35024ca0b5c921c56352dc2aa832b2f8f 100644 (file)
@@ -305,6 +305,8 @@ CONFIGURATION FILE
 
        log-file                 Set log filename
 
+       log-append               Append to log file
+
        log-strip                Enable strip of control and escape sequences from log
 
        local-echo               Enable local echo
index 1ed012e7507413edf5d51ae5f8e68c4255e8b0b4..5295d0db57553b50a4b16b4c0aadc47ed3536dff 100644 (file)
@@ -1,12 +1,12 @@
 project('tio', 'c',
-    version : '2.6',
+    version : '2.7',
     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-12-17'
+version_date = '2023-09-19'
 
 # Test for dynamic baudrate configuration interface
 compiler = meson.get_compiler('c')
index 9602c4fb93a54a599cc286c4beca8aba6128c950..abad32480c5b2a44068d700d7fb567838bfc44fa 100644 (file)
@@ -22,6 +22,7 @@ _tio()
           -e --local-echo \
           -l --log \
              --log-file \
+             --log-append \
              --log-strip \
           -m --map \
           -t --timestamp \
@@ -90,6 +91,10 @@ _tio()
             COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
             return 0
             ;;
+        --log-append)
+            COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+            return 0
+            ;;
         --log-strip)
             COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
             return 0
index cec9429218e67b8958dc903822e8847e303307df..2b4a0fee2bff1fc1ff75280666d69c5a8ac81b25 100644 (file)
@@ -17,7 +17,8 @@ tio_sources = [
   'setspeed.c',
   'rs485.c',
   'timestamp.c',
-  'alert.c'
+  'alert.c',
+  'xymodem.c'
 ]
 
 tio_dep = [
index 6fe5368c2606e9e093290a34312e3e7844d15394..d9a8e2b3a081d1e2b33cd7567830222eb62a8739 100644 (file)
@@ -29,3 +29,6 @@ long string_to_long(char *string);
 int ctrl_key_code(unsigned char key);
 void alert_connect(void);
 void alert_disconnect(void);
+
+extern char key_hit;
+int xymodem_send(int sio, const char *filename, char mode);
index 6217cc48ef932c631b63651d9cfe34cb9aabeaab..4f08e6dcc971203d8cceb4287f02ea6ee1c0f812 100644 (file)
@@ -46,6 +46,7 @@ enum opt_t
     OPT_TIMESTAMP_FORMAT,
     OPT_LOG_FILE,
     OPT_LOG_STRIP,
+    OPT_LOG_APPEND,
     OPT_LINE_PULSE_DURATION,
     OPT_RESPONSE_TIMEOUT,
     OPT_RS485,
@@ -253,6 +254,7 @@ void options_parse(int argc, char *argv[])
             {"list-devices",         no_argument,       0, 'L'                     },
             {"log",                  no_argument,       0, 'l'                     },
             {"log-file",             required_argument, 0, OPT_LOG_FILE            },
+            {"log-append",           no_argument,       0, OPT_LOG_APPEND          },
             {"log-strip",            no_argument,       0, OPT_LOG_STRIP           },
             {"socket",               required_argument, 0, 'S'                     },
             {"map",                  required_argument, 0, 'm'                     },
@@ -357,6 +359,10 @@ void options_parse(int argc, char *argv[])
                 option.log_strip = true;
                 break;
 
+            case OPT_LOG_APPEND:
+                option.log_append = true;
+                break;
+
             case 'S':
                 option.socket = optarg;
                 break;
@@ -486,6 +492,19 @@ void options_parse_final(int argc, char *argv[])
     optind = 1; // Reset option index to restart scanning of argv
     options_parse(argc, argv);
 
+#ifdef __CYGWIN__
+    unsigned char portnum;
+    char *tty_win;
+    if ( ((strncmp("COM", tty_device, 3) == 0)
+        || (strncmp("com", tty_device, 3) == 0) )
+        && (sscanf(tty_device + 3, "%hhu", &portnum) == 1)
+        && (portnum > 0) ) 
+    {
+        asprintf(&tty_win, "/dev/ttyS%hhu", portnum - 1);
+        tty_device = tty_win;
+    }
+#endif
+
     /* Restore tty device */
     option.tty_device = tty_device;
 }
index 186fa77c9745100a6f0442bd9de5f4b612abcc23..027e7953cbc5a681cc8da7e80383ef307116cc58 100644 (file)
--- a/src/tty.c
+++ b/src/tty.c
@@ -82,6 +82,8 @@
 #define CMSPAR   010000000000
 #endif
 
+#define LINE_SIZE_MAX 1000
+
 #define KEY_0 0x30
 #define KEY_1 0x31
 #define KEY_2 0x32
 #define KEY_T 0x74
 #define KEY_U 0x55
 #define KEY_V 0x76
+#define KEY_X 0x78
+#define KEY_Y 0x79
 #define KEY_Z 0x7a
 
 enum line_mode_t
@@ -133,6 +137,8 @@ bool map_i_nl_cr = false;
 bool map_i_cr_nl = false;
 bool map_ign_cr = false;
 
+char key_hit = 0xff;
+
 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;
@@ -153,6 +159,7 @@ static char *tty_buffer_write_ptr = tty_buffer;
 static pthread_t thread;
 static int pipefd[2];
 static pthread_mutex_t mutex_input_ready = PTHREAD_MUTEX_INITIALIZER;
+static char line[LINE_SIZE_MAX];
 
 static void optional_local_echo(char c)
 {
@@ -299,6 +306,11 @@ void *tty_stdin_input_thread(void *arg)
         byte_count = read(STDIN_FILENO, input_buffer, BUFSIZ);
         if (byte_count < 0)
         {
+            /* No error actually occurred */
+            if (errno == EINTR)
+            {
+                continue;
+            }
             tio_warning_printf("Could not read from stdin (%s)", strerror(errno));
         }
         else if (byte_count == 0)
@@ -316,6 +328,14 @@ void *tty_stdin_input_thread(void *arg)
             // Process quit and flush key command
             for (int i = 0; i<byte_count; i++)
             {
+                // first do key hit check for xmodem abort
+                if (!key_hit) {
+                    key_hit = input_buffer[i];
+                    byte_count--;
+                    memcpy(input_buffer+i, input_buffer+i+1, byte_count-i);
+                    continue;
+                }
+
                 input_char = input_buffer[i];
 
                 if (previous_char == option.prefix_code)
@@ -344,7 +364,7 @@ void *tty_stdin_input_thread(void *arg)
         }
 
         // Write all bytes read to pipe
-        while (byte_count)
+        while (byte_count > 0)
         {
             bytes_written = write(pipefd[1], input_buffer, byte_count);
             if (bytes_written < 0)
@@ -467,6 +487,33 @@ static void toggle_line(const char *line_name, int mask, enum line_mode_t line_m
     }
 }
 
+static int tio_readln(void)
+{
+    char *p = line;
+
+    /* Read line, accept BS and DEL as rubout characters */
+    for (p = line ; p < &line[LINE_SIZE_MAX-1]; )
+    {
+        if (read(pipefd[0], p, 1) > 0)
+        {
+            if (*p == 0x08 || *p == 0x7f)
+            {
+                if (p > line )
+                {
+                    write(STDOUT_FILENO, "\b \b", 3);
+                    p--;
+                }
+                continue;
+            }
+            write(STDOUT_FILENO, p, 1);
+            if (*p == '\r') break;
+            p++;
+        }
+    }
+    *p = 0;
+    return (p - line);
+}
+
 void handle_command_sequence(char input_char, char *output_char, bool *forward)
 {
     char unused_char;
@@ -557,7 +604,9 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward)
                 tio_printf(" ctrl-%c t       Toggle line timestamp mode", option.prefix_key);
                 tio_printf(" ctrl-%c U       Toggle conversion to uppercase on output", option.prefix_key);
                 tio_printf(" ctrl-%c v       Show version", option.prefix_key);
-                tio_printf(" ctrl-%c ctrl-%c  Send ctrl-%c character", option.prefix_key, option.prefix_key, option.prefix_key);
+                tio_printf(" ctrl-%c x       Send file via Xmodem-1K", option.prefix_key);
+                tio_printf(" ctrl-%c y       Send file via Ymodem", option.prefix_key);
+                tio_printf(" ctrl-%c ctrl-%c Send ctrl-%c character", option.prefix_key, option.prefix_key, option.prefix_key);
                 break;
 
             case KEY_SHIFT_L:
@@ -716,6 +765,17 @@ void handle_command_sequence(char input_char, char *output_char, bool *forward)
                 tio_printf("tio v%s", VERSION);
                 break;
 
+            case KEY_X:
+            case KEY_Y:
+                tio_printf("Send file with %cMODEM", toupper(input_char));
+                tio_printf_raw("Enter file name: ");
+                if (tio_readln()) {
+                    tio_printf("Sending file '%s'  ", line);
+                    tio_printf("Press any key to abort transfer");
+                    tio_printf("%s", xymodem_send(fd, line, input_char) < 0 ? "Aborted" : "Done");
+                }
+                break;
+
             case KEY_Z:
                 tio_printf_array(random_array);
                 break;
diff --git a/src/xymodem.c b/src/xymodem.c
new file mode 100644 (file)
index 0000000..53ec37d
--- /dev/null
@@ -0,0 +1,225 @@
+/*
+ * Minimalistic implementation of the xmodem-1k and ymodem sender protocol.
+ * https://en.wikipedia.org/wiki/XMODEM
+ * https://en.wikipedia.org/wiki/YMODEM
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later OR MIT-0
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <termios.h>
+#include "misc.h"
+
+#define STX 0x02
+#define ACK 0x06
+#define NAK 0x15
+#define CAN 0x18
+#define EOT "\004"
+
+#define OK  0
+#define ERR (-1)
+
+#define min(a, b)       ((a) < (b) ? (a) : (b))
+
+struct xpacket {
+    uint8_t  type;
+    uint8_t  seq;
+    uint8_t  nseq;
+    uint8_t  data[1024];
+    uint8_t  crc_hi;
+    uint8_t  crc_lo;
+} __attribute__((packed));
+
+/* See https://en.wikipedia.org/wiki/Computation_of_cyclic_redundancy_checks */
+static uint16_t crc16(const uint8_t *data, uint16_t size)
+{
+    uint16_t crc, s;
+
+    for (crc = 0; size > 0; size--) {
+        s = *data++ ^ (crc >> 8);
+        s ^= (s >> 4);
+        crc = (crc << 8) ^ s ^ (s << 5) ^ (s << 12);
+    }
+    return crc;
+}
+
+static int xmodem(int sio, const void *data, size_t len, int seq)
+{
+    struct xpacket  packet;
+    const uint8_t  *buf = data;
+    char            resp = 0;
+    int             rc, crc;
+
+    /* Drain pending characters from serial line. Insist on the
+     * last drained character being 'C'.
+     */
+    while(1) {
+        if (key_hit)
+            return -1;
+        if (read(sio, &resp, 1) < 0) {
+            if (errno == EWOULDBLOCK) {
+                if (resp == 'C') break;
+                if (resp == CAN) return ERR;
+                usleep(50000);
+                continue;
+            }
+            perror("Read sync from serial failed");
+            return ERR;
+        }
+    }
+
+    /* Always work with 1K packets */
+    packet.seq  = seq;
+    packet.type = STX; 
+
+    while (len) {
+        size_t  sz, z = 0;
+        char   *from, status;
+
+        /* Build next packet, pad with 0 to full seq */
+        z = min(len, sizeof(packet.data));
+        memcpy(packet.data, buf, z);
+        memset(packet.data + z, 0, sizeof(packet.data) - z);
+        crc = crc16(packet.data, sizeof(packet.data));
+        packet.crc_hi = crc >> 8;
+        packet.crc_lo = crc;
+        packet.nseq = 0xff - packet.seq;
+
+        /* Send packet */
+        from = (char *) &packet;
+        sz =  sizeof(packet);
+        while (sz) {
+            if (key_hit)
+                return ERR;
+            if ((rc = write(sio, from, sz)) < 0 ) {
+                if (errno ==  EWOULDBLOCK) {
+                    usleep(1000);
+                    continue;
+                }
+                perror("Write packet to serial failed");
+                return ERR;
+            }
+            from += rc;
+            sz   -= rc;
+        }
+
+        /* 'lrzsz' does not ACK ymodem's fin packet */
+        if (seq == 0 && packet.data[0] == 0) resp = ACK;
+
+        /* Read receiver response, timeout 1 s */
+        for(int n=0; n < 20; n++) {
+            if (key_hit)
+                return ERR;
+            if (read(sio, &resp, 1) < 0) {
+                if (errno ==  EWOULDBLOCK) {
+                    usleep(50000);
+                    continue;
+                }
+                perror("Read ack/nak from serial failed");
+                return ERR;
+            }
+            break;
+        }
+
+        /* Update "progress bar" */
+        switch (resp) {
+        case NAK: status = 'N'; break;
+        case ACK: status = '.'; break;
+        case 'C': status = 'C'; break;
+        case CAN: status = '!'; return ERR;
+        default:  status = '?';
+        }
+        write(STDOUT_FILENO, &status, 1);
+
+        /* Move to next block after ACK */
+        if (resp == ACK) {
+            packet.seq++;
+            len -= z;
+            buf += z;
+        }
+    }
+
+    /* Send EOT at 1 Hz until ACK or CAN received */
+    while (seq) {
+        if (key_hit)
+            return ERR;
+        if (write(sio, EOT, 1) < 0) {
+            perror("Write EOT to serial failed");
+            return ERR;
+        }
+        write(STDOUT_FILENO, "|", 1);
+        usleep(1000000); /* 1 s timeout*/
+        if (read(sio, &resp, 1) < 0) {
+            if (errno == EWOULDBLOCK) continue;
+            perror("Read from serial failed");
+            return ERR;
+        }
+        if (resp == ACK || resp == CAN) {
+            write(STDOUT_FILENO, "\r\n", 2);
+            return (resp == ACK) ? OK : ERR;
+        }
+    }
+    return 0; /* not reached */
+}
+
+int xymodem_send(int sio, const char *filename, char mode)
+{
+    size_t         len;
+    int            rc, fd;
+    struct stat    stat;
+    const uint8_t *buf;
+
+    /* Open file, map into memory */
+    fd = open(filename, O_RDONLY);
+    if (fd < 0) {
+        perror("Could not open file");
+        return ERR;
+    }
+    fstat(fd, &stat);
+    len = stat.st_size;
+    buf = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);
+    if (!buf) {
+        close(fd);
+        perror("Could not mmap file");
+        return ERR;
+    }
+
+    /* Do transfer */
+    key_hit = 0;
+    if (mode == 'x') {
+        rc = xmodem(sio, buf, len, 1);
+    }
+    else {
+        /* Ymodem: hdr + file + fin */
+        while(1) {
+            char hdr[1024], *p;
+
+            rc = -1;
+            if (strlen(filename) > 977) break; /* hdr block overrun */
+            p  = stpcpy(hdr, filename) + 1;
+            p += sprintf(p, "%ld %lo %o", len, stat.st_mtime, stat.st_mode);
+
+            if (xmodem(sio, hdr, p - hdr, 0) < 0) break; /* hdr with metadata */
+            if (xmodem(sio, buf, len,     1) < 0) break; /* xmodem file */
+            if (xmodem(sio, "",  1,       0) < 0) break; /* empty hdr = fin */
+            rc = 0;                               break;
+        }
+    }
+    key_hit = 0xff;
+
+    /* Flush serial and release resources */
+    tcflush(sio, TCIOFLUSH);
+    munmap((void *)buf, len);
+    close(fd);
+    return rc;
+}