Merge branch 'master' of https://github.com/tio/tio into eintr

This commit is contained in:
HiFiPhile 2023-09-22 11:21:49 +02:00
commit b1d74c3d27
10 changed files with 334 additions and 8 deletions

View file

@ -45,6 +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.

28
NEWS
View file

@ -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

View file

@ -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
@ -194,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
```

View file

@ -323,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

View file

@ -1,4 +1,4 @@
tio(1) User Commands tio(1)
tio(1) User Commands tio(1)
NAME
tio - a simple serial device I/O tool
@ -241,6 +241,8 @@ KEYS
ctrl-t L Show line states (DTR, RTS, CTS, DSR, DCD, RI)
ctrl-t m Toggle MSB to LSB bit order
ctrl-t p Pulse serial port line
ctrl-t q Quit
@ -253,6 +255,10 @@ KEYS
ctrl-t v Show version
ctrl-t x Send a file using the XMODEM protocol (prompts for file name)
ctrl-t y Send a file using the YMODEM protocol (prompts for file name)
ctrl-t ctrl-t Send ctrl-t character
HEXADECIMAL MODE
@ -279,7 +285,8 @@ CONFIGURATION FILE
The following configuration file options are available:
pattern Pattern matching user input. This pattern can be an extended regular expression with a single group.
pattern
Pattern matching user input. This pattern can be an extended regular expression with a single group.
device TTY device to open. If it contains a "%s" it is substituted with the first group match.
@ -450,4 +457,4 @@ WEBSITE
AUTHOR
Created by Martin Lund <martin.lund@keep-it-simple.com>.
tio 2.6 2022-12-17 tio(1)
tio 2.7 2023-09-19 tio(1)

View 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')

View file

@ -17,7 +17,8 @@ tio_sources = [
'setspeed.c',
'rs485.c',
'timestamp.c',
'alert.c'
'alert.c',
'xymodem.c'
]
tio_dep = [

View 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);

View file

@ -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
@ -105,6 +107,8 @@
#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)
{
@ -321,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)
@ -472,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;
@ -562,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:
@ -721,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;

225
src/xymodem.c Normal file
View file

@ -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;
}