From a6978dd84aa05ec0aa662c41bf3da51d8132c207 Mon Sep 17 00:00:00 2001 From: Edward Emelianov Date: Tue, 19 May 2026 17:37:45 +0300 Subject: [PATCH] fixed some bugs, switching to actual usefull_macros library --- CMakeLists.txt | 8 +- Changelog | 6 + Readme.md | 239 +++++++++++++++++++++++++-- cmdlnopts.c | 10 +- cmdlnopts.h | 13 +- dbg.h | 7 +- main.c | 116 +++++++------ ncurses_and_readline.c | 106 ++++++------ ncurses_and_readline.h | 4 +- string_functions.c | 3 +- ttysocket.c | 357 ++++++++++------------------------------- ttysocket.h | 14 +- 12 files changed, 465 insertions(+), 418 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6a8f54f..0409d32 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 4.0) set(PROJ tty_term) set(MINOR_VERSION "1") -set(MID_VERSION "2") +set(MID_VERSION "3") set(MAJOR_VERSION "0") set(VERSION "${MAJOR_VERSION}.${MID_VERSION}.${MINOR_VERSION}") @@ -15,7 +15,7 @@ option(DEBUG "Compile in debug mode" OFF) # default flags set(CMAKE_C_FLAGS_RELEASE "") set(CMAKE_C_FLAGS_DEBUG "") -set(CMAKE_C_FLAGS "-O3 -std=gnu99 -D_XOPEN_SOURCE=12345 -D_DEFAULT_SOURCE") +set(CMAKE_C_FLAGS "-O3 -std=c23 -D_XOPEN_SOURCE=12345 -D_DEFAULT_SOURCE") set(CMAKE_COLOR_MAKEFILE ON) @@ -36,7 +36,7 @@ endif() ###### pkgconfig ###### # pkg-config modules (for pkg-check-modules) -set(MODULES ncurses readline usefull_macros>=0.3.2) +set(MODULES ncurses readline usefull_macros>=0.3.5) # find packages: find_package(PkgConfig REQUIRED) diff --git a/Changelog b/Changelog index 603aaf7..7524242 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,9 @@ +Tue 19 May 10:56:54 MSK 2026 +ver. 0.3.1. + +Changed to libusefull_macros v.0.3.5. All sockets and device operations now are through its API. + + Mon Apr 28 12:33:57 MSK 2025 Changed to libusefull_macros v.0.3.2. diff --git a/Readme.md b/Readme.md index 5e6d633..74d0b0b 100644 --- a/Readme.md +++ b/Readme.md @@ -1,16 +1,231 @@ -Very simple terminal and socket text client -=========================== +# tty_term – Terminal Serial/Socket Communication Tool +`tty_term` is a terminal-based application for communicating with serial ports, TCP sockets, and +UNIX domain sockets. It provides a split-screen interface with an ncurses display area for received +data and a readline-powered command line for sending data. -Usage: tty_term [args] +## Features - Where args are: +- Connect to: + - Serial TTY devices (with configurable baud rate, parity, data bits, stop bits) + - Network sockets (TCP client) + - UNIX domain sockets +- Multiple display modes: + - **Text** – printable characters shown as-is, non‑printable as `\xXX` + - **Raw** – each byte as a hexadecimal value (`XX`) + - **Hexdump** – classic hexdump format (address + hex bytes + ASCII) +- Multiple input modes: + - **Text** – free text, supports C‑style escape sequences + - **Raw** – numbers in decimal, hex (`0x…`), binary (`0b…`), octal (`0…`) + - **Hex** – hexadecimal bytes (with or without spaces) + - **Modbus RTU (raw/hex)** – automatically appends CRC‑16 (Modbus) +- Scrollable output buffer, mouse wheel support +- Configurable end‑of‑line sequence (LF, CR, CR+LF, LF+CR) +- Dump all traffic to a file +- Exclusive mode for serial devices -- `-S, --socket` open socket -- `-d, --dumpfile=arg` dump data to this file -- `-e, --eol=arg` end of line: n (default), r, nr or rn -- `-h, --help` show this help -- `-n, --name=arg` serial device path or server name/IP -- `-p, --port=arg` socket port (none for UNIX) -- `-s, --speed=arg` baudrate (default: 9600) -- `-t, --timeout=arg` timeout for select() in ms (default: 100) +## Requirements + +- Linux (or POSIX system with termios) +- C23 compiler (GCC or Clang) +- Libraries: + - `ncurses` + - `readline` + - `usefull_macros` (≥0.3.5) – [snippets_library](https://github.com/eddyem/snippets_library) + +## Installation + +```bash +git clone --depth=1 https://github.com/eddyem/tty_term.git +cd tty_term +mkdir build && cd build +cmake .. +make +sudo make install +``` + +By default the program is installed into `/usr/local/bin`. +Use `-DDEBUG=ON` with CMake to build a debug version with extra logging. + +## Usage + +``` +ttyterm [options] +``` + +You **must** specify the device/socket path with `-n` (or `--name`). +All other options are optional. + +### Command‑line options + +| Option | Argument | Default | Description | +| ----------------- | ---------------------- | -------------- | ----------------------------------------------------------------------- | +| `-h`, `--help` | – | – | Show help | +| `-s`, `--speed` | integer | 9600 | Baud rate (serial only) | +| `-n`, `--name` | string | – | Serial device (e.g. `/dev/ttyUSB0`), server `host:port`, or socket path | +| `-e`, `--eol` | `n`, `r`, `rn`, `nr` | `n` | End‑of‑line sequence when sending in text mode | +| `-t`, `--timeout` | milliseconds (float) | 100 | Timeout for `select()` on read | +| `-S`, `--socket` | – | – | Treat `-n` as a network socket (TCP client) | +| `-d`, `--dumpfile`| filename | – | Append all received and sent data to this file | +| `-f`, `--format` | string (e.g. `8N1`) | `8N1` | Serial line format (data bits, parity, stop bits) | +| `-U`, `--unix` | – | – | Treat `-n` as a UNIX domain socket path | +| `-x`, `--exclusive`| – | – | Open serial device in exclusive mode | + +**Examples:** + +```bash +# Serial port at 115200, 8N1, CR+LF as EOL +ttyterm -n /dev/ttyUSB0 -s 115200 -e rn + +# TCP connection to a Modbus server +ttyterm -n 192.168.1.100:502 -S + +# UNIX socket +ttyterm -n /tmp/mysocket -U + +# Log all communication to a file +ttyterm -n /dev/ttyACM0 -d session.log +``` + +## User Interface + +The screen is divided into three areas: + +1. **Message window** (top, scrolling) – displays received data in the selected display mode. +2. **Status bar** (middle) – shows connection details, current display mode, and whether you are in +**Insert** (editing) or **Scroll** mode. +3. **Command line** (bottom) – where you type data to send. + +### Keyboard Controls + +#### Global (always active) + +| Key | Action | +| -------------- | -------------------------------------------- | +| `F1` | Show poput help window | +| `F2` | Switch input/output mode to **Text** | +| `F3` | Switch input/output mode to **Raw** | +| `F4` | Switch input/output mode to **Hexdump** | +| `F5` | Switch **input** mode to Modbus RTU (raw) | +| `F6` | Switch **input** mode to Modbus RTU (hex) | +| `Tab` | Toggle between **Insert** and **Scroll** mode| +| `Ctrl`+`C` / `Q` / `Ctrl`+`D` | Quit program | +| Mouse wheel | Scroll output up/down | + +The action of `F2`-`F6` keys depends on current mode: **Insert** — change input mode, +**Scroll** — change output mode. + +#### Insert mode (for typing commands) + +- Regular typing works as in `readline` (supports line editing, history, arrow keys). +- `Enter` sends the current line. +- `Ctrl`+`D` on an empty line quits. +- Escape sequences are supported when input mode is **Text** (see below). + +#### Scroll mode (navigation) + +| Key | Action | +| ------------------ | -------------------------------- | +| `↑` / `Ctrl`+`P` | Scroll up one line | +| `↓` / `Ctrl`+`N` | Scroll down one line | +| `PageUp` / `Ctrl`+`B` | Scroll up one page | +| `PageDown` / `Ctrl`+`F` | Scroll down one page | +| `Home` | Jump to the very top of the buffer | +| `End` | Jump to the very bottom (follow new data) | +| `←` / `Ctrl`+`L` | Scroll left (if lines exceed screen width) | +| `→` / `Ctrl`+`R` | Scroll right | +| `Q` | Quit (same as `Ctrl`+`C`) | + +### Data Display Modes + +- **Text** (`F2`): + Printable ASCII characters (32–126) are shown as‑is. + Line feed (`\n`) ends the current line. + Non‑printable bytes and control characters are displayed as `\xHH`. + +- **Raw** (`F3`): + Every byte is shown as two hexadecimal digits, separated by a space. + Example: `41 42 0D 0A` + +- **Hexdump** (`F4`): + Classic hexdump format: + `address hex bytes (8‑byte groups) |ASCII representation|` + Lines are automatically adjusted to the terminal width. + +### Input Modes and Data Formatting + +The **input mode** determines how the text you type is converted into raw bytes before +transmission. + +#### Text mode (default after `F2`) + +- Most characters are sent as their ASCII/UTF‑8 bytes. +- **Escape sequences** (C‑style) are recognised after a backslash `\`: + +| Sequence | Meaning | Byte value | +| -------- | ----------------- | ---------- | +| `\a` | Bell (alert) | 0x07 | +| `\b` | Backspace | 0x08 | +| `\e` | Escape | 0x1B | +| `\f` | Form feed | 0x0C | +| `\n` | Line feed (LF) | 0x0A | +| `\r` | Carriage return | 0x0D | +| `\t` | Horizontal tab | 0x09 | +| `\v` | Vertical tab | 0x0B | +| `\xHH` | Hexadecimal byte | 0xHH | +| `\0`–`\377` | Octal byte | up to 0xFF | + +- After processing escapes, the configured EOL sequence (e.g. `\r\n`) is **automatically appended** +to every line you send. + +#### Raw mode (`F3` for display, but also used as input) + +Input is a sequence of numbers, each representing one byte. +Numbers can be: +- Decimal: `65` (space‑separated) +- Hexadecimal: `0x41` or `0X41` +- Binary: `0b01000001` +- Octal: `0101` (leading zero) + +Spaces are ignored, text and other characters treating "as is". +Example: `hel 0x6c 0x6f 0x0A` + +The EOL is **not** added automatically in raw mode. + +#### Hex mode (`F4` for display, also used as input) + +Input must consist of hexadecimal digits (pairs are merged into bytes). +Spaces are ignored. +Example: `41420D0A` → sends four bytes: `A`, `B`, CR, LF. + +#### Modbus RTU modes (`F5` – raw, `F6` – hex) + +- Same input syntax as **Raw** or **Hex**, respectively. +- After converting the user‑typed numbers/hex, the program **calculates a 16‑bit Modbus CRC‑16** +(polynomial 0xA001) and appends it (low byte first). + +Example (raw): `1 3 0 0 0 10` +→ sends: `01 03 00 00 00 0A C5 CD` + +## Logging + +If `-d ` is given, all data is appended to that file. +Received lines are prefixed with `< `, sent lines with `> `. + +## Notes + +- The program uses the library `usefull_macros` for low‑level terminal/socket operations and +logging. +- Debug builds (`cmake -DDEBUG=ON`) produce a `debug.log` file with verbose diagnostic output. +- The terminal must support at least 80 columns for comfortable hexdump viewing. +- When resizing the terminal window, the output is automatically reformatted. + +## License + +GNU General Public License version 3 (or later). +See the `LICENSE` file or [https://www.gnu.org/licenses/gpl-3.0.html](https://www.gnu.org/licenses/gpl-3.0.html). + +## Author + +Edward V. Emelianov +Project page: [https://github.com/eddyem/tty_term](https://github.com/eddyem/tty_term) diff --git a/cmdlnopts.c b/cmdlnopts.c index 1468e2b..04200de 100644 --- a/cmdlnopts.c +++ b/cmdlnopts.c @@ -28,11 +28,10 @@ * here are global parameters initialisation */ static int help; -static glob_pars G; // DEFAULTS // default global parameters -glob_pars const Gdefault = { +static glob_pars G = { .speed = 9600, .eol = "n", .tmoutms = 100, @@ -47,13 +46,13 @@ static sl_option_t cmdlnopts[] = { // set 1 to param despite of its repeating number: {"help", NO_ARGS, NULL, 'h', arg_int, APTR(&help), _("show this help")}, {"speed", NEED_ARG, NULL, 's', arg_int, APTR(&G.speed), _("baudrate (default: 9600)")}, - {"name", NEED_ARG, NULL, 'n', arg_string, APTR(&G.ttyname), _("serial device path or server name/IP or socket path")}, + {"name", NEED_ARG, NULL, 'n', arg_string, APTR(&G.node), _("path to serial device, server name:IP or socket path")}, {"eol", NEED_ARG, NULL, 'e', arg_string, APTR(&G.eol), _("end of line: n (default), r, nr or rn")}, {"timeout", NEED_ARG, NULL, 't', arg_int, APTR(&G.tmoutms), _("timeout for select() in ms (default: 100)")}, - {"port", NEED_ARG, NULL, 'p', arg_string, APTR(&G.port), _("socket port (none for UNIX)")}, - {"socket", NO_ARGS, NULL, 'S', arg_int, APTR(&G.socket), _("open socket")}, + {"socket", NO_ARGS, NULL, 'S', arg_int, APTR(&G.socket), _("open INET socket")}, {"dumpfile",NEED_ARG, NULL, 'd', arg_string, APTR(&G.dumpfile), _("dump data to this file")}, {"format", NEED_ARG, NULL, 'f', arg_string, APTR(&G.serformat), _("tty format (default: 8N1)")}, + {"unix", NO_ARGS, NULL, 'U', arg_int, APTR(&G.unixsock), _("open UNIX-socket")}, {"exclusive",NO_ARGS, NULL, 'x', arg_int, APTR(&G.exclusive), _("open serial in exclusive mode")}, end_option }; @@ -66,7 +65,6 @@ static sl_option_t cmdlnopts[] = { * @return allocated structure with global parameters */ glob_pars *parse_args(int argc, char **argv){ - void *ptr = memcpy(&G, &Gdefault, sizeof(G)); assert(ptr); // format of help: "Usage: progname [args]\n" sl_helpstring(_(PROJECT " version " PACKAGE_VERSION "\nUsage: %s [args]\n\n\tWhere args are:\n")); // parse arguments diff --git a/cmdlnopts.h b/cmdlnopts.h index e01b252..cd1adbf 100644 --- a/cmdlnopts.h +++ b/cmdlnopts.h @@ -17,27 +17,22 @@ */ #pragma once -#ifndef CMDLNOPTS_H__ -#define CMDLNOPTS_H__ /* * here are some typedef's for global data */ typedef struct{ int speed; // baudrate - int tmoutms; // timeout for select() in ms int socket; // open socket + int unixsock; // UNIX-socket instead of INET int exclusive; // open serial in exclusive mode + double tmoutms; // timeout for select() in ms char *dumpfile; // file to save dump - char *ttyname; // device name + char *node; // node name / device path char *eol; // end of line: \r (CR), \rn (CR+LF) or \n (LF): "r", "rn", "n" - char *port; // socket port char *serformat; // format of serial line } glob_pars; -// default & global parameters -extern glob_pars const Gdefault; - glob_pars *parse_args(int argc, char **argv); -#endif // CMDLNOPTS_H__ + diff --git a/dbg.h b/dbg.h index a9af66c..3238d69 100644 --- a/dbg.h +++ b/dbg.h @@ -17,8 +17,6 @@ */ #pragma once -#ifndef DBG_H__ -#define DBG_H__ // dirty trick #define termios xxtermios @@ -35,10 +33,9 @@ #define FNAME() do{LOGDBG("%s (%s, line %d)", __func__, __FILE__, __LINE__);}while(0) #define DBG(...) do{LOGDBG("%s (%s, line %d):", __func__, __FILE__, __LINE__); \ LOGDBGADD(__VA_ARGS__);} while(0) -#define ERR(...) do{red(__VA_ARGS__); printf("\n"); LOGERR(__VA_ARGS__); signals(9);}while(0) -#define ERRX(...) do{red(__VA_ARGS__); printf("\n"); LOGERR(__VA_ARGS__); signals(9);}while(0) +#define ERR(...) do{red(__VA_ARGS__); printf("\n"); LOGERR(__VA_ARGS__); signals(-1);}while(0) +#define ERRX(...) do{red(__VA_ARGS__); printf("\n"); LOGERR(__VA_ARGS__); signals(-1);}while(0) //#define WARN(...) do{LOGWARN(__VA_ARGS__);}while(0) #define WARNX(...) do{red(__VA_ARGS__); printf("\n"); LOGWARN(__VA_ARGS__);}while(0) #endif -#endif // DBG_H__ diff --git a/main.c b/main.c index 9a9bedc..2beec62 100644 --- a/main.c +++ b/main.c @@ -19,62 +19,85 @@ #include #include #include // strcmp +#include +#include + #include "cmdlnopts.h" #include "ncurses_and_readline.h" #include "ttysocket.h" #include "dbg.h" -static chardevice conndev = {.dev = NULL, .mutex = PTHREAD_MUTEX_INITIALIZER, .name = NULL, .type = DEV_TTY}; +static int should_exit = 0; void signals(int signo){ - signal(signo, SIG_IGN); - closedev(); - deinit_ncurses(); - deinit_readline(); - DBG("Exit by signal %d", signo); - exit(signo); + DBG("signo = %d", signo); + should_exit = 1; + if(signo > 0){ + DBG("Exit by signal %d", signo); + }else if(signo < 0){ + should_exit = -1; + DBG("exit by disconnection"); + } } int main(int argc, char **argv){ + chardevice *conndev = MALLOC(chardevice, 1); // should be allocated to FREE later + pthread_mutex_init(&conndev->mutex, NULL); + conndev->type = DEV_TTY; glob_pars *G = NULL; // default parameters see in cmdlnopts.c sl_init(); #ifdef EBUG OPENLOG("debug.log", LOGLEVEL_ANY, 1); #endif + DBG("Parsing"); G = parse_args(argc, argv); - if(G->tmoutms < 0) ERRX("Timeout should be >= 0"); + if(G->tmoutms < 0.){ + ERRX("Timeout should be >= 0"); + return 1; + } + if(sl_tty_tmout(G->tmoutms) < 0){ + ERRX("Can't set timeout to %g ms", G->tmoutms); + return 1; + } + if(!G->node){ + ERRX("You should point node name"); + return 1; + } const char *EOL = "\n", *seol = "\\n"; if(strcasecmp(G->eol, "n")){ if(strcasecmp(G->eol, "r") == 0){ EOL = "\r"; seol = "\\r"; } else if(strcasecmp(G->eol, "rn") == 0){ EOL = "\r\n"; seol = "\\r\\n"; } else if(strcasecmp(G->eol, "nr") == 0){ EOL = "\n\r"; seol = "\\n\\r"; } - else ERRX("End of line should be \"r\", \"n\" or \"rn\" or \"nr\""); - } - strcpy(conndev.eol, EOL); - strcpy(conndev.seol, seol); - DBG("eol: %s, seol: %s", conndev.eol, conndev.seol); - if(!G->ttyname){ - WARNX("You should point name"); - signals(0); - } - conndev.name = strdup(G->ttyname); - DBG("device name: %s", conndev.name); - if(G->socket){ - if(!G->port) conndev.type = DEV_UNIXSOCKET; else{ - conndev.port = strdup(G->port); - conndev.type = DEV_NETSOCKET; + ERRX("End of line should be \"r\", \"n\", \"rn\" or \"nr\""); + return 1; } - DBG("socket port=%s, type=%d", conndev.port, conndev.type); - }else{ - conndev.speed = G->speed; - conndev.port = strdup(G->serformat); // `port` of tty is serial format - DBG("speed=%d, format=%s", conndev.speed, conndev.port); - conndev.exclusive = G->exclusive; } - if(!opendev(&conndev, G->dumpfile)){ - signals(0); + strcpy(conndev->eol, EOL); + strcpy(conndev->seol, seol); + DBG("eol: %s, seol: %s", conndev->eol, conndev->seol); + conndev->node = strdup(G->node); + if(!conndev->node){ + ERR("strdup()"); + return 1; + } + DBG("node: %s", conndev->node); + if(G->socket || G->unixsock){ // INET/UNIX socket + if(G->unixsock) conndev->type = DEV_UNIXSOCKET; + else conndev->type = DEV_NETSOCKET; + }else{ // serial device + conndev->speed = G->speed; + conndev->serformat = strdup(G->serformat); // `port` of tty is serial format + if(!conndev->serformat){ + ERR("strdup()"); + return 1; + } + DBG("speed=%d, format=%s", conndev->speed, conndev->serformat); + conndev->exclusive = G->exclusive; + } + if(!opendev(conndev, G->dumpfile)){ + exit(1); } init_ncurses(); init_readline(); @@ -84,23 +107,26 @@ int main(int argc, char **argv){ signal(SIGQUIT, signals); // ctrl+\ - quit signal(SIGTSTP, SIG_IGN); // ignore ctrl+Z pthread_t writer; - if(pthread_create(&writer, NULL, cmdline, (void*)&conndev)) ERR("pthread_create()"); - settimeout(G->tmoutms); - while(1){ - if(0 == pthread_mutex_lock(&conndev.mutex)){ - int l; - uint8_t *buf = ReadData(&l); - if(buf && l > 0){ - AddData(buf, l); - }else if(l < 0){ - pthread_mutex_unlock(&conndev.mutex); - ERRX("Device disconnected"); - } - pthread_mutex_unlock(&conndev.mutex); + if(pthread_create(&writer, NULL, cmdline, (void*)conndev)) ERR("pthread_create()"); + uint8_t buf[BUFSIZ]; + while(conndev && conndev->fd > -1 && !should_exit){ + ssize_t got = ReadData(buf, BUFSIZ); + if(got > 0){ + AddData(buf, got); + }else if(got < 0){ + WARNX("Device disconnected -> exit"); + signals(-1); } usleep(1000); } - // never reached + DBG("Exit writer"); + exit_writer(); + usleep(1000); + deinit_ncurses(); + deinit_readline(); + closedev(); + if(should_exit < 0) red("\nExit with error (disconnected?)\n\n"); + else DBG("main(): Normal exit"); return 0; } diff --git a/ncurses_and_readline.c b/ncurses_and_readline.c index 022655d..6606e36 100644 --- a/ncurses_and_readline.c +++ b/ncurses_and_readline.c @@ -25,11 +25,10 @@ #include #include #include -#include +#include #include #include #include - //#include #include "dbg.h" @@ -47,18 +46,19 @@ enum { // using colors }; #define COLOR(x) COLOR_PAIR(x ## _NO) - // Keeps track of the terminal mode so we can reset the terminal if needed on errors -static bool visual_mode = false; +static atomic_bool visual_mode = false; // insert commands when true; roll upper screen when false -static bool insert_mode = true; -static bool should_exit = false; +static atomic_bool insert_mode = true; +static atomic_bool should_exit = false; static disptype disp_type = DISP_TEXT; // type of displaying data static disptype input_type = DISP_TEXT; // parsing type of input data const char *dispnames[DISP_SIZE] = {"TEXT", "RAW", "HEX", "RTU (RAW)", "RTU (HEX)", "Error"}; static chardevice *dtty = NULL; +// ring buffer for new data +static sl_ringbuffer_t *incoming_data = NULL; static void fail_exit(const char *msg){ // Make sure endwin() is only called in visual mode. As a note, calling it @@ -104,6 +104,10 @@ static unsigned char input; // Input character for readline // Used to signal "no more input" after feeding a character to readline static bool input_avail = false; +void exit_writer(){ + should_exit = true; +} + // Not bothering with 'input_avail' and just returning 0 here seems to do the // right thing too, but this might be safer across readline versions static int readline_input_avail(){ @@ -134,7 +138,7 @@ static void show_err(const char *text){ } /** - * @brief ptrtobuf - get n'th string of `formatted_buffer` + * @brief ptrtobuf - get n'th string of `formatted_buffer`; called when mutex locked * @param lineno - line number * @return pointer to n'th string in formatted buffer */ @@ -149,35 +153,10 @@ static char *ptrtobuf(size_t lineno){ return (linebuffer->formatted_buffer + idx); } -#if 0 -// functions to modify output data -static char *text_putchar(char *next){ - char c = *next++; - DBG("put 0x%02X (%c)", c, c); - if(c < 31 || c > 126){ - wattron(msg_win, COLOR(MARKED)); - //waddch(msg_win, c); - wprintw(msg_win, "%02X", (uint8_t)c); - wattroff(msg_win, COLOR(MARKED)); - }else{ - //wprintw(msg_win, "%c" COLOR_GREEN "green" COLOR_RED "red" COLOR_OLD, c); - waddch(msg_win, c); - } - return next; -} -static char *raw_putchar(char *next){ - waddch(msg_win, *next); - return next+1; -} -static char *hex_putchar(char *next){ - waddch(msg_win, *next); - return next+1; -} -#endif - /** * @brief msg_win_redisplay - redisplay message window * @param group_refresh - true for grouping refresh (don't call doupdate()) + * // called with mutex locked */ static void msg_win_redisplay(bool group_refresh){ if(!linebuffer) return; @@ -237,20 +216,20 @@ static void show_mode(bool group_refresh){ if(dtty){ switch(dtty->type){ case DEV_NETSOCKET: - snprintf(buf, 127, "%s HOST: %s, ENDLINE: %s, PORT: %s", - insmodetext, dtty->name, dtty->seol, dtty->port); + snprintf(buf, 127, "%s NODE: %s, ENDLINE: %s", + insmodetext, dtty->node, dtty->seol); break; case DEV_UNIXSOCKET: - if(*dtty->name) + if(*dtty->node) snprintf(buf, 127, "%s PATH: %s, ENDLINE: %s", - insmodetext, dtty->name, dtty->seol); + insmodetext, dtty->node, dtty->seol); else // name starting from \0 - snprintf(buf, 127, "%s PATH: \\0%s, ENDLINE: %s", - insmodetext, dtty->name+1, dtty->seol); + snprintf(buf, 127, "%s PATH: @%s, ENDLINE: %s", + insmodetext, dtty->node+1, dtty->seol); break; case DEV_TTY: snprintf(buf, 127, "%s DEV: %s, ENDLINE: %s, SPEED: %d, FORMAT: %s", - insmodetext, dtty->name, dtty->seol, dtty->speed, dtty->port); + insmodetext, dtty->node, dtty->seol, dtty->speed, dtty->serformat ? dtty->serformat : "8N1"); break; default: break; @@ -258,7 +237,7 @@ static void show_mode(bool group_refresh){ snprintf(buf, 127, "INSERT (TAB to switch, ctrl+D to quit) NOT INITIALIZED"); } }else{ - snprintf(buf, 127, "SCROLL (F1 - help) ENDLINE: %s", dtty?dtty->seol:"n"); + snprintf(buf, 127, "SCROLL (F1 - help) ENDLINE: %s", dtty ? dtty->seol : "n"); } wattron(sep_win, COLOR(BKGMARKED)); wprintw(sep_win, "%s ", dispnames[disp_type]); @@ -272,6 +251,7 @@ static void show_mode(bool group_refresh){ /** * @brief redisplay_addline - redisplay after line adding * @param group_refresh - true for grouping refresh (don't call doupdate()) + * called with mutex locked */ static void redisplay_addline(){ // redisplay only if previous line was on screen @@ -299,6 +279,7 @@ static void linebuf_free(){ /** * @brief chksizes - check sizes of buffers and enlarge them if need + * called with locked mutex */ static void chksizes(){ size_t addportion = MAXCOLS * LINEARRSZ; @@ -357,6 +338,7 @@ static void linebuf_new(){ /** * @brief finalize_line - finalize last line in linebuffer & increase buffer sizes if nesessary + * // called when mutex locked */ static void finalize_line(){ chksizes(); @@ -476,18 +458,15 @@ void FormatData(const uint8_t *data, int len){ } /** - * @brief AddData - add new data buffer to global buffer and last displayed string + * @brief AddData - add new data to temporary ring buffer * @param data - data * @param len - length of `data` */ void AddData(const uint8_t *data, int len){ - // now print all symbols into buff - chksizes(); - memcpy(raw_buffer + rawbufcur, data, len); - DBG("Got %d bytes, now buffer have %d", len, rawbufcur+len); - FormatData(raw_buffer + rawbufcur, len); - rawbufcur += len; - redisplay_addline(); // display last symbols if can + while(len > 0){ + size_t written = sl_RB_write(incoming_data, data, len); + len -= written; + } } static void resize(){ @@ -499,20 +478,18 @@ static void resize(){ mvwin(sep_win, LINES - 2, 0); mvwin(cmd_win, LINES - 1, 0); } - pthread_mutex_lock(&dtty->mutex); linebuf_new(); // free old and alloc new FormatData(raw_buffer, rawbufcur); // reformat all data - pthread_mutex_unlock(&dtty->mutex); msg_win_redisplay(true); show_mode(true); doupdate(); } /* void swinch(_U_ int sig){ - //signal(SIGWINCH, swinch); + signal(SIGWINCH, swinch); DBG("got resize"); -}*/ - +} +*/ void init_ncurses(){ if (!initscr()) fail_exit("Failed to initialize ncurses"); @@ -556,16 +533,20 @@ void init_ncurses(){ rawbufsz = RBUFSIZ; raw_buffer = MALLOC(uint8_t, rawbufsz); linebuf_new(); + if(!incoming_data) incoming_data = sl_RB_new(BUFSIZ); + if(!incoming_data) ERRX("Can't create input ring buffer"); //signal(SIGWINCH, swinch); } void deinit_ncurses(){ + FNAME(); visual_mode = false; - linebuf_free(); delwin(msg_win); delwin(sep_win); delwin(cmd_win); endwin(); + linebuf_free(); + sl_RB_delete(&incoming_data); } static char *previous_line = NULL; // previous line in readline input @@ -613,6 +594,7 @@ static void change_disp(disptype in, disptype out){ } void deinit_readline(){ + FNAME(); rl_callback_handler_remove(); } @@ -682,9 +664,20 @@ static const char *help[] = { */ void *cmdline(void* arg){ MEVENT event; + uint8_t locbuff[MAXCOLS]; dtty = (chardevice*)arg; show_mode(false); do{ + // check for incoming data + size_t got = sl_RB_read(incoming_data, locbuff, MAXCOLS); + if(got){ + chksizes(); + memcpy(raw_buffer + rawbufcur, locbuff, got); + DBG("Got %zd bytes, now buffer have %d", got, rawbufcur+got); + FormatData(raw_buffer + rawbufcur, got); + rawbufcur += got; + redisplay_addline(); // display last symbols if can + } int c = wgetch(cmd_win); if(c < 0) continue; bool processed = true; @@ -755,7 +748,7 @@ void *cmdline(void* arg){ break; case KEY_BACKSPACE: forward_to_readline(127); // ^? - break; + break; case KEY_IC: // ^[[2~ DBG("key insert"); ptr = "2~"; @@ -808,6 +801,7 @@ void *cmdline(void* arg){ } } }while(!should_exit); + DBG("should_exit == 0"); signals(0); return NULL; } diff --git a/ncurses_and_readline.h b/ncurses_and_readline.h index 54f4706..271635f 100644 --- a/ncurses_and_readline.h +++ b/ncurses_and_readline.h @@ -16,8 +16,6 @@ * along with this program. If not, see . */ #pragma once -#ifndef NCURSES_AND_READLINE_H__ -#define NCURSES_AND_READLINE_H__ #include "dbg.h" #include "ttysocket.h" @@ -34,9 +32,9 @@ typedef enum{ // display/input data as void init_readline(); void deinit_readline(); +void exit_writer(); void init_ncurses(); void deinit_ncurses(); void *cmdline(void* arg); void AddData(const uint8_t *data, int len); -#endif // NCURSES_AND_READLINE_H__ diff --git a/string_functions.c b/string_functions.c index 62a9f20..4394e72 100644 --- a/string_functions.c +++ b/string_functions.c @@ -140,7 +140,7 @@ static inline const char *gethex(const char *line, int *ch){ static inline const char *getspec(const char *line, int *ch){ if(!*line){ *ch = -1; return line; } int got = -1, s = *line++; // next symbol after '\' - if(s >= '0' && s <= '7'){ // octal symbol + if(s >= '0' && s <= '7'){ // octal symbol like \127 line = getoct(line-1, &got); }else switch(s){ case 'a': got = '\a'; break; @@ -186,6 +186,7 @@ int convert_and_send(disptype input_type, const char *line){ if(curpos + sz >= bufsiz){ // out ouptut buffer can't be larger than input bufsiz += BUFSIZ; buf = realloc(buf, bufsiz); + if(!buf) ERR("realloc()"); } } while(*line){ diff --git a/ttysocket.c b/ttysocket.c index 710a79c..4429a6f 100644 --- a/ttysocket.c +++ b/ttysocket.c @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +#if 0 #include #include #include @@ -27,297 +28,128 @@ #include #include #include // unix socket +#endif + +#include +#include #include "dbg.h" #include "string_functions.h" #include "ttysocket.h" -static int sec = 0, usec = 100; // timeout static FILE *dupfile = NULL; // file for output static chardevice *device = NULL; // current opened device -// TODO: if unix socket name starts with \0 translate it as \\0 to d->name! - -// set Read_tty timeout in milliseconds -void settimeout(int tmout){ - sec = 0; - if(tmout > 999){ - sec = tmout / 1000; - tmout -= sec * 1000; - } - usec = tmout * 1000L; -} - -/** - * wait for answer from socket - * @param sock - socket fd - * @return 0 in case of timeout, 1 in case of socket ready, -1 if error - */ -static int waittoread(int fd){ - fd_set fds; - struct timeval timeout; - timeout.tv_sec = sec; - timeout.tv_usec = usec; - FD_ZERO(&fds); - FD_SET(fd, &fds); - do{ - int rc = select(fd+1, &fds, NULL, NULL, &timeout); - if(rc < 0){ - if(errno != EINTR){ - WARN("select()"); - return -1; - } - continue; - } - break; - }while(1); - if(FD_ISSET(fd, &fds)){ - //DBG("FD_ISSET"); - return 1; - } - return 0; -} - -// get data drom TTY -static uint8_t *getttydata(int *len){ - if(!device || !device->dev) return NULL; - sl_tty_t *D = device->dev; - if(D->comfd < 0) return NULL; - int L = 0; - int length = D->bufsz - 1; // -1 for terminating zero - uint8_t *ptr = (uint8_t*)D->buf; - int s = 0; - do{ - if(!(s = waittoread(D->comfd))) break; - if(s < 0){ - if(len) *len = 0; - return NULL; - } - int l = read(D->comfd, ptr, length); - if(l < 1){ // disconnected - if(len) *len = -1; - return NULL; - } - ptr += l; L += l; - length -= l; - }while(length); - D->buflen = L; - D->buf[L] = 0; // for text buffers - if(len) *len = L; - if(!L) return NULL; - DBG("buffer len: %zd, content: =%s=", D->buflen, D->buf); - return (uint8_t*)D->buf; -} - -static uint8_t *getsockdata(int *len){ - if(!device || !device->dev) return NULL; - sl_tty_t *D = device->dev; - if(D->comfd < 0) return NULL; - uint8_t *ptr = NULL; - int n = waittoread(D->comfd); - if(n == 1){ - n = read(D->comfd, D->buf, D->bufsz-1); - if(n > 0){ - ptr = (uint8_t*)D->buf; - ptr[n] = 0; - D->buflen = n; - DBG("got %d: ..%s..", n, ptr); - }else{ - DBG("Got nothing"); - n = -1; - } - } - if(len) *len = n; - return ptr; -} - /** * @brief ReadData - get data from serial device or socket - * @param d - device - * @param len (o) - length of data read (-1 if device disconnected) - * @return NULL or string + * @param buf - buffer + * @param len - buffer length + * @return amount of read bytes or -1 in case of error / disconnection */ -uint8_t *ReadData(int *len){ - if(!device || !device->dev) return NULL; - if(len) *len = -1; - uint8_t *r = NULL; - switch(device->type){ - case DEV_TTY: - r = getttydata(len); - break; - case DEV_NETSOCKET: - case DEV_UNIXSOCKET: - r = getsockdata(len); - break; - default: - break; +ssize_t ReadData(uint8_t *buf, size_t len){ + if(!device || device->fd < 0){ + WARNX("ReadData(): No connection"); + return -1; } - if(r && dupfile){ - fwrite("< ", 1, 2, dupfile); - fwrite(r, 1, *len, dupfile); + if(!buf || len == 0){ + WARNX("ReadData(): Bad receive buffer"); + return 0; } - return r; + ssize_t l = -1; + static int errctr = 0; + if(0 == pthread_mutex_lock(&device->mutex)){ + int s = sl_canread(device->fd); + if(s < 1){ + pthread_mutex_unlock(&device->mutex); + return (ssize_t)s; + } + l = read(device->fd, buf, len); + if(l > 0){ + DBG("receive: %zd", l); + if(dupfile){ + fwrite("< ", 1, 2, dupfile); + fwrite(buf, 1, l, dupfile); + } + errctr = 0; + }else{ + DBG("Disconnected? Got %zd bytes", l); + if(++errctr > 3){ + l = -1; + errctr = 0; + } + } + pthread_mutex_unlock(&device->mutex); + }else WARN("ReadData(): pthread_mutex_lock()"); + return l; } /** * @brief SendData - send data to tty or socket * @param d - device * @param data - buffer with data - * @return 0 if error or empty string, -1 if disconnected + * @return 0 if error or empty string, -1 if disconnected or error */ -int SendData(const uint8_t *data, size_t len){ - if(!device) return -1; - if(!data || len == 0) return 0; - int ret = 0; +ssize_t SendData(const uint8_t *data, size_t len){ + if(!device || device->fd < 0){ + WARNX("SendData(): No connection"); + return -1; + } + if(!data || len == 0){ + WARNX("SendData(): Bad data to send"); + return 0; + } + ssize_t ret = -1; DBG("Send %d bytes", len); + DBG("lock"); if(0 == pthread_mutex_lock(&device->mutex)){ - switch(device->type){ - case DEV_TTY: - if(sl_tty_write(device->dev->comfd, (const char*)data, len)) ret = 0; - else ret = len; - break; - case DEV_NETSOCKET: - case DEV_UNIXSOCKET: - if(len != (size_t)send(device->dev->comfd, data, len, MSG_NOSIGNAL)) ret = 0; - else ret = len; - break; - default: - data = NULL; - break; - } + DBG("write"); + ret = write(device->fd, data, len); if(data && dupfile){ fwrite("> ", 1, 2, dupfile); fwrite(data, 1, len, dupfile); } + DBG("unlock"); pthread_mutex_unlock(&device->mutex); - }else ret = -1; - DBG("ret=%d", ret); + }else WARN("SendData(): pthread_mutex_lock()"); + DBG("send: %zd", ret); return ret; } -static const int socktypes[] = {SOCK_STREAM, SOCK_RAW, SOCK_RDM, SOCK_SEQPACKET, SOCK_DCCP, SOCK_PACKET, SOCK_DGRAM, 0}; - -static sl_tty_t* opensocket(){ - if(!device) return FALSE; - sl_tty_t *descr = MALLOC(sl_tty_t, 1); // only for `buf` and bufsz/buflen - descr->buf = MALLOC(char, BUFSIZ); - descr->bufsz = BUFSIZ; - // now try to open a socket - descr->comfd = -1; - struct hostent *host; - struct sockaddr_in addr = {0}; - struct sockaddr_un saddr = {0}; - struct sockaddr *sa = NULL; - socklen_t addrlen = 0; - int domain = -1; - if(device->type == DEV_NETSOCKET){ - DBG("NETSOCK to %s", device->name); - sa = (struct sockaddr*) &addr; - addrlen = sizeof(addr); - if((host = gethostbyname(device->name)) == NULL ){ - WARN("gethostbyname()"); - FREE(descr->buf); - FREE(descr); - return NULL; - } - struct in_addr *ia = (struct in_addr*)host->h_addr_list[0]; - DBG("addr: %s", inet_ntoa(*ia)); - addr.sin_family = AF_INET; - int p = atoi(device->port); DBG("PORT: %s - %d", device->port, p); - addr.sin_port = htons(p); - //addr.sin_addr.s_addr = *(long*)(host->h_addr); - addr.sin_addr.s_addr = ia->s_addr; - domain = AF_INET; - }else{ - DBG("UNSOCK"); - sa = (struct sockaddr*) &saddr; - addrlen = sizeof(saddr); - saddr.sun_family = AF_UNIX; - if(*(device->name) == 0){ // if sun_path[0] == 0 then don't create a file - DBG("convert name"); - saddr.sun_path[0] = 0; - strncpy(saddr.sun_path+1, device->name+1, 105); - } - else if(strncmp("\\0", device->name, 2) == 0){ - DBG("convert name"); - saddr.sun_path[0] = 0; - strncpy(saddr.sun_path+1, device->name+2, 105); - }else strncpy(saddr.sun_path, device->name, 106); - domain = AF_UNIX; - } - const int *type = socktypes; - while(*type){ - DBG("type = %d", *type); - if((descr->comfd = socket(domain, *type, 0)) > -1){ - if(connect(descr->comfd, sa, addrlen) < 0){ - DBG("CANT connect"); - close(descr->comfd); - }else break; - } - ++type; - } - if(descr->comfd < 0){ - DBG("NO types"); - WARNX("No types can be choosen"); - FREE(descr->buf); - FREE(descr); - return NULL; - } - return descr; -} - -static sl_tty_t* opentty(){ - if(!device->name){ - /// - WARNX(_("Port name is missing")); - return NULL; - } - sl_tty_t *descr = sl_tty_new(device->name, device->speed, 4096); - if(!descr){ - WARN("Can't init %s", device->name); - return NULL; - } - descr->format = device->port; - descr = sl_tty_open(descr, device->exclusive); - return descr; -} - /** * @brief opendev - open TTY or socket output device * @param d - device type + * @param path - path to dump file or NULL * @return FALSE if failed */ int opendev(chardevice *d, char *path){ if(!d) return FALSE; DBG("Try to open device"); - device = MALLOC(chardevice, 1); - memcpy(device, d, sizeof(chardevice)); - device->name = strdup(d->name); - if(d->port) device->port = strdup(d->port); + device = d;//MALLOC(chardevice, 1); + //memcpy(device, d, sizeof(chardevice)); + //device->node = strdup(d->node); + //if(d->serformat) device->serformat = strdup(d->serformat); DBG("devtype=%d", device->type); switch(device->type){ case DEV_TTY: DBG("Serial"); - device->dev = opentty(); - if(!device->dev){ - WARN("Can't open device %s", device->name); - DBG("CANT OPEN"); - return FALSE; - } + device->fd = sl_tty_fdescr(device->node, device->serformat, device->speed, device->exclusive); break; case DEV_NETSOCKET: + DBG("INET socket"); + device->fd = sl_sock_open(SOCKT_NET, device->node, 0, 0); + break; case DEV_UNIXSOCKET: - DBG("Socket"); - device->dev = opensocket(); - if(!device->dev){ - WARNX("Can't open socket"); - DBG("CANT OPEN"); - return FALSE; - } + DBG("UNIX-socket"); + device->fd = sl_sock_open(SOCKT_UNIX, device->node, 0, 0); break; default: return FALSE; } + if(device->fd < 0){ + WARN("Can't open %s", device->node); + DBG("CANT OPEN"); + return FALSE; + } if(path){ // open logging file dupfile = fopen(path, "a"); if(!dupfile){ @@ -327,42 +159,31 @@ int opendev(chardevice *d, char *path){ } } changeeol(device->eol); // allow string functions to know EOL - memcpy(d, device, sizeof(chardevice)); + //d->fd = device->fd; return TRUE; } void closedev(){ + FNAME(); if(!device) return; - pthread_mutex_unlock(&device->mutex); + // avoid dead-locks pthread_mutex_trylock(&device->mutex); + int fd = device->fd; + device->fd = -1; + usleep(1000); // wait a little for threads + close(fd); + DBG("Device closed"); + pthread_mutex_unlock(&device->mutex); if(dupfile){ + DBG("Dupfile closed"); fclose(dupfile); dupfile = NULL; } - switch(device->type){ - case DEV_TTY: - if(device->dev){ - sl_tty_t *t = device->dev; - ioctl(t->comfd, TCSETS2, &t->oldtty); // return TTY to previous state - close(t->comfd); - } - break; - case DEV_NETSOCKET: - if(device->dev){ - close(device->dev->comfd); - FREE(device->dev); - } - break; - default: - return; - } - if(device->dev){ - FREE(device->dev->format); - FREE(device->dev->portname); - FREE(device->dev->buf); - FREE(device->dev); - } - FREE(device->name); + DBG("free node"); + FREE(device->node); + DBG("free serformat"); + FREE(device->serformat); + DBG("free device"); FREE(device); DBG("Device closed"); } diff --git a/ttysocket.h b/ttysocket.h index 3e07bc2..bf08942 100644 --- a/ttysocket.h +++ b/ttysocket.h @@ -17,8 +17,6 @@ */ #pragma once -#ifndef TTY_H__ -#define TTY_H__ #include #include @@ -34,9 +32,9 @@ typedef enum{ // device: tty terminal, network socket or UNIX socket typedef struct{ devtype type; // type - char *name; // filename (dev or UNIX socket) or server name/IP - sl_tty_t *dev; // tty serial device - char *port; // port to connect + char *node; // filename (dev or UNIX socket) or server name:IP + char *serformat; // serial format like "8N1" + int fd; // file descriptor int speed; // tty speed pthread_mutex_t mutex; // reading/writing mutex char eol[3]; // end of line @@ -44,10 +42,8 @@ typedef struct{ int exclusive; } chardevice; -uint8_t *ReadData(int *l); -int SendData(const uint8_t *data, size_t len); -void settimeout(int tms); +ssize_t ReadData(uint8_t *buf, size_t len); +ssize_t SendData(const uint8_t *data, size_t len); int opendev(chardevice *d, char *path); void closedev(); -#endif // TTY_H__