From b1bcd6f75d62fb35750d3bdebd871c240d03570a Mon Sep 17 00:00:00 2001 From: Aaron Cook Date: Sat, 11 Jun 2022 12:56:08 -0500 Subject: [PATCH 01/33] Cleanup readme --- README.md | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/README.md b/README.md index cebd00d..7e74cab 100644 --- a/README.md +++ b/README.md @@ -24,22 +24,6 @@ Bytenuts has been tested and built on x86-64 and 32-bit ARM Ubuntu 18. - Quick commands - Pages of quick commands are loaded from `~/.config/bytenuts/commands[1-10]` - Session resumption - Bytenuts can load the previous instance's commands and serial output -## Planned Features - -Bytenuts is still very early in development. Here are some features that may get implemented. - -- Other transfer protocols - Maybe Kermit and ZModem - -## Un-Planned Features - -Bytenuts is not supposed to be the be-all end-all application, nor is it supposed to be able to do everything SecureCRT can. Here are some things it probably won't have. - -- Non-serial communications - No SSH, telnet, etc. -- Elaborate menuing - Bytenuts functions will be shortcut driven to focus on what's important (the serial communication) -- Script loading - Think like how you can load scripts in ZOC on SecureCRT. Write normal Python scripts if you want to do that kind of stuff. -- Multi connection support - Open another instance of Bytenuts if you need to -- Windows support - No - ## Launch Options Launching Bytenuts like `bytenuts -h` provides this help print: From bcec4fed688b4804d566f5a3340c9ee0d20c5dae Mon Sep 17 00:00:00 2001 From: Aaron Cook Date: Sat, 2 Jul 2022 18:04:22 -0500 Subject: [PATCH 02/33] Improve window refresh It's not perfect, sometimes the input buffer isn't printed properly --- src/bytenuts.c | 5 ++ src/ingest.c | 183 ++++++++++++++++++++++++------------------------- src/ingest.h | 3 + 3 files changed, 99 insertions(+), 92 deletions(-) diff --git a/src/bytenuts.c b/src/bytenuts.c index 54090df..01b7a0e 100644 --- a/src/bytenuts.c +++ b/src/bytenuts.c @@ -258,6 +258,8 @@ bytenuts_set_status(int user, const char *fmt, ...) int bytenuts_update_screen_size() { + char *old_status = strdup(bytenuts.bytenuts_status); + pthread_mutex_lock(&bytenuts.term_lock); wresize(bytenuts.status_win, 1, COLS); @@ -274,7 +276,10 @@ bytenuts_update_screen_size() pthread_mutex_unlock(&bytenuts.term_lock); + ingest_refresh(); + bytenuts_set_status(STATUS_BYTENUTS, old_status); cheerios_insert("", 0); + free(old_status); return 0; } diff --git a/src/ingest.c b/src/ingest.c index 3b48079..94bb0ea 100644 --- a/src/ingest.c +++ b/src/ingest.c @@ -20,7 +20,6 @@ static ingest_t ingest; static int mode_normal(int ch); static int mode_xmodem(int block_sz); static int handle_functions(int ch); -static int inbuf_load(); static int print_stats(); static int auto_complete(); static int read_cmd_page(int idx); @@ -80,6 +79,80 @@ ingest_stop() return 0; } +int +ingest_refresh() +{ + static int start = 0; + int startx = 0, starty = 0; + int cx = -1, cy = -1; + int win_len = getmaxx(ingest.input) + 1; + + pthread_mutex_lock(ingest.term_lock); + + werase(ingest.input); + wmove(ingest.input, 0, 0); + if (ingest.prepend) { + wprintw(ingest.input, "%s", ingest.prepend); + getyx(ingest.input, starty, startx); + } + + if (start > ingest.inpos) { + start = ingest.inpos; + } + /* this is not perfect as some characters are wider than one space, but it + * makes the algorithm for printing less redundant */ + else if (ingest.inpos > (start + win_len)) { + start = ingest.inpos - win_len; + } + + int i = 0; + int hit_cursor = 0; + while (1) { + int p = i + start; + int iy, ix; + i++; + + getyx(ingest.input, iy, ix); + + /* print the cursor at this position */ + if (p == ingest.inpos) { + cy = iy; + cx = ix; + hit_cursor = 1; + } + + /* nothing left to write... */ + if (p >= ingest.inlen) + break; + + /* we want to leave a blank character for printing the cursor at the end of the line */ + if (ix >= (win_len-2)) { + /* we printed the cursor, so we can break */ + if (hit_cursor) + break; + /* otherwise, we didn't print the cursor, so start over */ + i = 0; + start++; + wmove(ingest.input, starty, startx); + continue; + } + + waddch(ingest.input, ingest.inbuf[p]); + } + + if (cx < 0) { + cx = ingest.inlen < win_len ? ingest.inlen : win_len - 1; + cy = 0; + } + + wmove(ingest.input, cy, cx); + wrefresh(ingest.input); + + pthread_mutex_unlock(ingest.term_lock); + + return 0; +} + int ingest_set_history(char **history, int history_len) { @@ -191,7 +264,7 @@ ingest_thread(void *arg) memcpy(ingest.inbuf, toload, toload_len); ingest.inpos = toload_len; ingest.inlen = toload_len; - inbuf_load(); + ingest_refresh(); break; } case 'c': @@ -363,7 +436,7 @@ mode_normal(int ch) memset(ingest.inbuf, 0, sizeof(ingest.inbuf)); ingest.inpos = 0; ingest.inlen = 0; - inbuf_load(); + ingest_refresh(); break; case KEY_UP: /* load in history */ @@ -378,7 +451,7 @@ mode_normal(int ch) strcpy(ingest.inbuf, ingest.history[ingest.history_pos]); ingest.inlen = strlen(ingest.inbuf); ingest.inpos = ingest.inlen; - inbuf_load(); + ingest_refresh(); break; case KEY_DOWN: /* load in history */ @@ -398,7 +471,7 @@ mode_normal(int ch) ingest.inpos = ingest.inlen; } - inbuf_load(); + ingest_refresh(); break; default: @@ -421,7 +494,7 @@ mode_normal(int ch) ingest.inlen--; } - inbuf_load(); + ingest_refresh(); break; } @@ -442,7 +515,7 @@ mode_xmodem(int block_sz) if (ingest.prepend) free(ingest.prepend); ingest.prepend = strdup("Give me a path (ctrl-c to stop): "); - inbuf_load(); + ingest_refresh(); while (ingest.running) { int ch; @@ -474,7 +547,7 @@ mode_xmodem(int block_sz) break; case '\t': /* tab complete */ auto_complete(); - inbuf_load(); + ingest_refresh(); break; default: { @@ -496,7 +569,7 @@ mode_xmodem(int block_sz) ingest.inlen--; } - inbuf_load(); + ingest_refresh(); break; } @@ -512,7 +585,7 @@ mode_xmodem(int block_sz) ingest.prepend = NULL; bytenuts_set_status(STATUS_INGEST, "normal"); - inbuf_load(); + ingest_refresh(); return 0; } } @@ -557,7 +630,7 @@ handle_functions(int ch) ingest.inpos--; ingest.inlen--; ingest.inbuf[ingest.inlen] = '\0'; - inbuf_load(); + ingest_refresh(); return 1; } @@ -569,7 +642,7 @@ handle_functions(int ch) ingest.inlen--; memcpy(&ingest.inbuf[ingest.inpos], tmp, tmp_len); ingest.inbuf[ingest.inlen] = '\0'; - inbuf_load(); + ingest_refresh(); return 1; } @@ -583,7 +656,7 @@ handle_functions(int ch) } else if (ingest.inpos == ingest.inlen - 1) { ingest.inbuf[ingest.inpos] = '\0'; ingest.inlen--; - inbuf_load(); + ingest_refresh(); return 1; } @@ -592,7 +665,7 @@ handle_functions(int ch) ingest.inlen--; memcpy(&ingest.inbuf[ingest.inpos], tmp, tmp_len); ingest.inbuf[ingest.inlen] = '\0'; - inbuf_load(); + ingest_refresh(); return 1; } @@ -600,21 +673,21 @@ handle_functions(int ch) if (ingest.inpos == 0) return 1; ingest.inpos--; - inbuf_load(); + ingest_refresh(); return 1; case KEY_RIGHT: if (ingest.inpos == ingest.inlen) return 1; ingest.inpos++; - inbuf_load(); + ingest_refresh(); return 1; case KEY_END: ingest.inpos = ingest.inlen; - inbuf_load(); + ingest_refresh(); return 1; case KEY_HOME: ingest.inpos = 0; - inbuf_load(); + ingest_refresh(); return 1; #if 0 case KEY_MOUSE: @@ -637,80 +710,6 @@ handle_functions(int ch) } } -static int -inbuf_load() -{ - static int start = 0; - int startx = 0, starty = 0; - int cx = -1, cy = -1; - int win_len = getmaxx(ingest.input) + 1; - - pthread_mutex_lock(ingest.term_lock); - - werase(ingest.input); - wmove(ingest.input, 0, 0); - if (ingest.prepend) { - wprintw(ingest.input, "%s", ingest.prepend); - getyx(ingest.input, starty, startx); - } - - if (start > ingest.inpos) { - start = ingest.inpos; - } - /* this is not perfect as some characters are wider than one space, but it - * makes the algorithm for printing less redundant */ - else if (ingest.inpos > (start + win_len)) { - start = ingest.inpos - win_len; - } - - int i = 0; - int hit_cursor = 0; - while (1) { - int p = i + start; - int iy, ix; - i++; - - getyx(ingest.input, iy, ix); - - /* print the cursor at this position */ - if (p == ingest.inpos) { - cy = iy; - cx = ix; - hit_cursor = 1; - } - - /* nothing left to write... */ - if (p >= ingest.inlen) - break; - - /* we want to leave a blank character for printing the cursor at the end of the line */ - if (ix >= (win_len-2)) { - /* we printed the cursor, so we can break */ - if (hit_cursor) - break; - /* otherwise, we didn't print the cursor, so start over */ - i = 0; - start++; - wmove(ingest.input, starty, startx); - continue; - } - - waddch(ingest.input, ingest.inbuf[p]); - } - - if (cx < 0) { - cx = ingest.inlen < win_len ? ingest.inlen : win_len - 1; - cy = 0; - } - - wmove(ingest.input, cy, cx); - wrefresh(ingest.input); - - pthread_mutex_unlock(ingest.term_lock); - - return 0; -} - static int print_stats() { diff --git a/src/ingest.h b/src/ingest.h index d069827..91b94cc 100644 --- a/src/ingest.h +++ b/src/ingest.h @@ -46,6 +46,9 @@ int ingest_start(bytenuts_t *bytenuts); /* stop the input window thread */ int ingest_stop(); +/* refresh the inbuf window */ +int ingest_refresh(); + /* Set the the command history directly. Values are copied */ int ingest_set_history(char **history, int history_len); From 38cde1611fdb28831e48d3b77d802435e390742a Mon Sep 17 00:00:00 2001 From: Aaron Cook Date: Sun, 3 Jul 2022 16:26:21 -0500 Subject: [PATCH 03/33] For some reason changing the refresh order fixes resize issues --- src/bytenuts.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bytenuts.c b/src/bytenuts.c index 01b7a0e..57f9be4 100644 --- a/src/bytenuts.c +++ b/src/bytenuts.c @@ -276,9 +276,9 @@ bytenuts_update_screen_size() pthread_mutex_unlock(&bytenuts.term_lock); - ingest_refresh(); bytenuts_set_status(STATUS_BYTENUTS, old_status); cheerios_insert("", 0); + ingest_refresh(); free(old_status); return 0; From 28d342aa42df5adc8ef83b685eba93c5a96de342 Mon Sep 17 00:00:00 2001 From: Aaron Cook Date: Sun, 3 Jul 2022 16:49:32 -0500 Subject: [PATCH 04/33] Update help prints to print the configured escape character --- src/bytenuts.c | 8 ++++---- src/cheerios.c | 23 +++++++++++++++++++++++ src/cheerios.h | 5 +++++ src/ingest.c | 6 +++--- 4 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/bytenuts.c b/src/bytenuts.c index 57f9be4..2eaea83 100644 --- a/src/bytenuts.c +++ b/src/bytenuts.c @@ -116,11 +116,11 @@ bytenuts_run(int argc, char **argv) if (bytenuts.resume) { load_state(); } else { - cheerios_insert( + cheerios_print( "Welcome to Bytenuts\r\n" - "To quit press ctrl-b q\r\n" - "To see all commands, press ctrl-b h\r\n", - 82 + "To quit press ctrl-%c q\r\n" + "To see all commands, press ctrl-%c h\r\n", + bytenuts.config.escape, bytenuts.config.escape ); } diff --git a/src/cheerios.c b/src/cheerios.c index 1527a6d..a38f3d5 100644 --- a/src/cheerios.c +++ b/src/cheerios.c @@ -184,6 +184,29 @@ cheerios_info(const char *line) return 0; } +int +cheerios_print(const char *fmt, ...) +{ + va_list ap; + char *buf; + size_t len; + + va_start(ap, fmt); + len = vsnprintf(NULL, 0, fmt, ap); + va_end(ap); + + buf = calloc(1, len+1); + + va_start(ap, fmt); + vsprintf(buf, fmt, ap); + va_end(ap); + + cheerios_insert(buf, len); + free(buf); + + return 0; +} + int cheerios_insert(const char *buf, size_t len) { diff --git a/src/cheerios.h b/src/cheerios.h index a07a3a0..08b3a5b 100644 --- a/src/cheerios.h +++ b/src/cheerios.h @@ -3,6 +3,7 @@ #include #include +#include #include #include "bytenuts.h" @@ -69,6 +70,10 @@ int cheerios_input(const char *line); * do not terminate with a newline! */ int cheerios_info(const char *line); +/* Like cheerios_insert except using printf formatting */ +__attribute__((format(printf, 1, 2))) +int cheerios_print(const char *fmt, ...); + /* directly insert a buffer to the output window */ int cheerios_insert(const char *buf, size_t len); diff --git a/src/ingest.c b/src/ingest.c index 94bb0ea..c20a7c2 100644 --- a/src/ingest.c +++ b/src/ingest.c @@ -359,8 +359,8 @@ ingest_thread(void *arg) bytenuts_set_status(STATUS_INGEST, "xmodem1k"); break; case 'h': - cheerios_insert( - "Commands (lead with ctrl-b):\r\n" + cheerios_print( + "Commands (lead with ctrl-%c):\r\n" " c: print available quick commands\r\n" " 0-9: load the given quick command (0 is 10)\r\n" " p: select a different quick commands page\r\n" @@ -369,7 +369,7 @@ ingest_thread(void *arg) " X: start XModem upload with 1024B payloads\r\n" " h: view this help\r\n" " q: quit Bytenuts\r\n", - 313 + ingest.config->escape ); bytenuts_set_status(STATUS_INGEST, "normal"); should_continue = 1; From c4a334bdee5aa80326449c84862b674c450eb506 Mon Sep 17 00:00:00 2001 From: Aaron Cook Date: Fri, 23 Sep 2022 18:48:47 -0500 Subject: [PATCH 05/33] Add the Hex input mode --- src/ingest.c | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/ingest.h | 1 + 2 files changed, 69 insertions(+) diff --git a/src/ingest.c b/src/ingest.c index c20a7c2..1c80214 100644 --- a/src/ingest.c +++ b/src/ingest.c @@ -19,6 +19,7 @@ static ingest_t ingest; static int mode_normal(int ch); static int mode_xmodem(int block_sz); +static int mode_hex(int ch); static int handle_functions(int ch); static int print_stats(); static int auto_complete(); @@ -358,6 +359,16 @@ ingest_thread(void *arg) ingest.mode = INGEST_MODE_XMODEM1K; bytenuts_set_status(STATUS_INGEST, "xmodem1k"); break; + case 'H': + if (ingest.mode == INGEST_MODE_NORMAL) { + ingest.mode = INGEST_MODE_HEX; + bytenuts_set_status(STATUS_INGEST, "hex"); + } else { + ingest.mode = INGEST_MODE_NORMAL; + bytenuts_set_status(STATUS_INGEST, "normal"); + } + should_continue = 1; + break; case 'h': cheerios_print( "Commands (lead with ctrl-%c):\r\n" @@ -397,6 +408,9 @@ ingest_thread(void *arg) case INGEST_MODE_XMODEM1K: mode_xmodem(1024); break; + case INGEST_MODE_HEX: + mode_hex(ch); + break; default: break; } @@ -593,6 +607,60 @@ mode_xmodem(int block_sz) return 0; } +static int +mode_hex(int ch) +{ + if (handle_functions(ch)) + return 0; + + switch (ch) { + case '\n': { /* send inputs */ + char hexbuf[sizeof(ingest.inbuf)/2]; + size_t inbuflen = strlen(ingest.inbuf); + + for (int i = 0; i < inbuflen; i += 2) { + char tmp = ingest.inbuf[i+2]; + ingest.inbuf[i+2] = '\0'; + hexbuf[i/2] = strtol(&ingest.inbuf[i], NULL, 16); + // cheerios_print("HEX 0x%02X (%c)\r\n", hexbuf[i/2], hexbuf[i/2]); + ingest.inbuf[i+2] = tmp; + } + + if (cheerios_insert(hexbuf, inbuflen/2)) { + break; + } + + /* store in history */ + ingest.history_pos = ingest.history_len; + if (ingest.inlen > 0) { + ingest.history_len++; + ingest.history = realloc(ingest.history, ingest.history_len * sizeof(char *)); + ingest.history[ingest.history_pos] = strdup(ingest.inbuf); + ingest.history_pos++; + if (ingest.history_fd) { + fwrite(ingest.inbuf, 1, strlen(ingest.inbuf), ingest.history_fd); + fwrite("\n", 1, 1, ingest.history_fd); + } + } + + memset(ingest.inbuf, 0, sizeof(ingest.inbuf)); + ingest.inpos = 0; + ingest.inlen = 0; + ingest_refresh(); + + break; + } + + case KEY_UP: /* load in history */ + case KEY_DOWN: /* load in history */ + default: + mode_normal(ch); + break; + } + + return 0; +} + static int handle_functions(int ch) { diff --git a/src/ingest.h b/src/ingest.h index 91b94cc..06d1fec 100644 --- a/src/ingest.h +++ b/src/ingest.h @@ -11,6 +11,7 @@ enum ingest_mode_enum { INGEST_MODE_NORMAL = 0, INGEST_MODE_XMODEM, INGEST_MODE_XMODEM1K, + INGEST_MODE_HEX, }; typedef struct ingest_struct { From 796dc1449cb5bdfce4532304dbf3ac1b7598eaf7 Mon Sep 17 00:00:00 2001 From: Kellen Overvig Date: Tue, 27 Sep 2022 16:33:46 -0500 Subject: [PATCH 06/33] Fix default baud incompatibilty --- src/bytenuts.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bytenuts.c b/src/bytenuts.c index 2eaea83..f8978f3 100644 --- a/src/bytenuts.c +++ b/src/bytenuts.c @@ -515,7 +515,7 @@ string_to_speed(const char *speed) else if (!strcmp("57600", speed)) return B57600; else if (!strcmp("115200", speed)) - return B1152000; + return B115200; else if (!strcmp("230400", speed)) return B230400; else if (!strcmp("460800", speed)) @@ -582,7 +582,7 @@ speed_to_string(speed_t speed) else if (speed == B57600) return "B57600"; else if (speed == B115200) - return "B1152000"; + return "B115200"; else if (speed == B230400) return "B230400"; else if (speed == B460800) From d7b0c80f56a5a8a25ac1644423cc65f9eeb85be1 Mon Sep 17 00:00:00 2001 From: Aaron Cook Date: Sun, 16 Oct 2022 14:22:24 -0500 Subject: [PATCH 07/33] Fix hex buffer mode and add it to the readme --- README.md | 12 ++++++++++- src/cheerios.c | 19 ++--------------- src/cheerios.h | 5 ++--- src/ingest.c | 57 +++++++++++++++++++++++++++++++++++++++++++------- 4 files changed, 65 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 7e74cab..da56837 100644 --- a/README.md +++ b/README.md @@ -110,14 +110,24 @@ The help prompt lists the available commands. Commands (lead with ctrl-b): c: print available quick commands 0-9: load the given quick command (0 is 10) - p, 0-9: select a different quick commands page (0 is 10) + p: select a different quick commands page i: view info/stats x: start XModem upload with 128B payloads X: start XModem upload with 1024B payloads + H: enter/exit hex buffer mode h: view this help q: quit Bytenuts ``` +### Hex Buffer Mode +When the `ctrl+b H` command has been issued for the first time, you will enter hex buffer mode. In this mode, the input buffer is interpreted as a hex string and will be converted to its byte equivalent before it gets sent to the target. Example inputs: + +- `746573740d0a` gets sent as `test\r\n` +- `d0a` gets sent as `\r\n` as it assumed the trailing `0` has been omitted +- `74657374xd0a` gets sent as `test` since the `xd` byte contains an invalid hex character + +Hex buffer mode can be turned off with a second `ctrl+b H`. + ## Quick Commands You can provide multiple pages of quick commands you can easily load in with `ctrl+b [0-9]` in the files `~/.config/bytenuts/commands`. Bytenuts will load pages starting from `commands1` until a `commands` no longer exists. The commands should be newline separated. When a command is loaded, the entire contents of the line is loaded into the input buffer. diff --git a/src/cheerios.c b/src/cheerios.c index a38f3d5..05212e7 100644 --- a/src/cheerios.c +++ b/src/cheerios.c @@ -143,25 +143,10 @@ cheerios_stop() } int -cheerios_input(const char *line) +cheerios_input(const char *buf, size_t len) { - size_t line_len = strlen(line); - pthread_mutex_lock(&cheerios.lock); - - if (cheerios.config->echo) { - char *line_parsed = calloc(1, line_len + 5 + 1); - sprintf(line_parsed, ">> %s\r\n", line); - insert_buf(&cheerios.lines, line_parsed, strlen(line_parsed)); - free(line_parsed); - } - - write(cheerios.ser_fd, line, strlen(line)); - if (cheerios.config->no_crlf) - write(cheerios.ser_fd, "\n", 1); - else - write(cheerios.ser_fd, "\r\n", 2); - + write(cheerios.ser_fd, buf, len); pthread_mutex_unlock(&cheerios.lock); return 0; } diff --git a/src/cheerios.h b/src/cheerios.h index 08b3a5b..e83dee2 100644 --- a/src/cheerios.h +++ b/src/cheerios.h @@ -62,9 +62,8 @@ int cheerios_gofwd(int lines); /* stop the thread and release memory */ int cheerios_stop(); -/* send over a line of user input to the output - * do not terminate with a newline! */ -int cheerios_input(const char *line); +/* send over a line of user input to the output */ +int cheerios_input(const char *buf, size_t len); /* output a line only to the terminal for info/prompt purposes * do not terminate with a newline! */ diff --git a/src/ingest.c b/src/ingest.c index 1c80214..1c491f5 100644 --- a/src/ingest.c +++ b/src/ingest.c @@ -378,6 +378,7 @@ ingest_thread(void *arg) " i: view info/stats\r\n" " x: start XModem upload with 128B payloads\r\n" " X: start XModem upload with 1024B payloads\r\n" + " H: enter/exit hex buffer mode\r\n" " h: view this help\r\n" " q: quit Bytenuts\r\n", ingest.config->escape @@ -429,9 +430,21 @@ mode_normal(int ch) return 0; switch (ch) { - case '\n': /* send inputs */ - if (cheerios_input(ingest.inbuf)) { - break; + case '\n': { /* send inputs */ + size_t inlen = strlen(ingest.inbuf); + const char *ending; + + if (ingest.config->no_crlf) + ending = "\n"; + else + ending = "\r\n"; + + cheerios_input(ingest.inbuf, inlen); + cheerios_input(ending, strlen(ending)); + if (ingest.config->echo) { + cheerios_insert(">> ", 3); + cheerios_insert(ingest.inbuf, inlen); + cheerios_insert("\r\n", 2); } /* store in history */ @@ -453,6 +466,7 @@ mode_normal(int ch) ingest_refresh(); break; + } case KEY_UP: /* load in history */ if (ingest.history_pos == 0) { /* top of history */ break; @@ -617,17 +631,46 @@ mode_hex(int ch) case '\n': { /* send inputs */ char hexbuf[sizeof(ingest.inbuf)/2]; size_t inbuflen = strlen(ingest.inbuf); + size_t hexlen = 0; + /* assume leading 0 on odd length buffers */ + if (inbuflen % 2 != 0) { + memmove(&ingest.inbuf[1], ingest.inbuf, inbuflen); + ingest.inbuf[0] = '0'; + inbuflen++; + } for (int i = 0; i < inbuflen; i += 2) { char tmp = ingest.inbuf[i+2]; + char *inval; + long ch; + ingest.inbuf[i+2] = '\0'; - hexbuf[i/2] = strtol(&ingest.inbuf[i], NULL, 16); - // cheerios_print("HEX 0x%02X (%c)\r\n", hexbuf[i/2], hexbuf[i/2]); + ch = strtol(&ingest.inbuf[i], &inval, 16); ingest.inbuf[i+2] = tmp; + + /* strtol could not parse a character */ + if (inval != &ingest.inbuf[i+2]) + break; + + hexbuf[i/2] = ch; + hexlen++; } - if (cheerios_insert(hexbuf, inbuflen/2)) { - break; + cheerios_input(hexbuf, hexlen); + if (ingest.config->echo) { + cheerios_insert(">> (hex)\r\n", 10); + for (int i = 0; i < hexlen; i++) { + if ( + i > 0 && + i != hexlen-1 && + i % 16 == 0 + ) { + cheerios_insert("\r\n", 2); + } + cheerios_insert(&ingest.inbuf[i*2], 2); + cheerios_insert(" ", 1); + } + cheerios_insert("\r\n", 2); } /* store in history */ From 3311b39cadd8c53b32c295e6fa8d5ec98b8a5155 Mon Sep 17 00:00:00 2001 From: Aaron Cook Date: Sun, 16 Oct 2022 14:24:58 -0500 Subject: [PATCH 08/33] Add dependency install hint for building guide --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index da56837..df08617 100644 --- a/README.md +++ b/README.md @@ -146,4 +146,4 @@ Check out known bugs in the [issues tab](https://github.com/cookthebook/bytenuts ## Building -Building Bytenuts is very simple. All you need is clang and libncurses. Run `make` in the Bytenuts root directory to build. You can also install the build (creating a link in `/usr/local/bin` to the `build` directory) by running `sudo make install`. +Building Bytenuts is very simple. All you need is clang and libncurses (`sudo apt install clang libncurses5-dev`). Run `make` in the Bytenuts root directory to build. You can also install the build (creating a link in `/usr/local/bin` to the `build` directory) by running `sudo make install`. From a2b2c5d1848e93c902ddb59475986e2b26a9ddf5 Mon Sep 17 00:00:00 2001 From: Aaron Cook Date: Sun, 16 Oct 2022 14:26:07 -0500 Subject: [PATCH 09/33] Readme typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index df08617..eb728ab 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ Commands (lead with ctrl-b): When the `ctrl+b H` command has been issued for the first time, you will enter hex buffer mode. In this mode, the input buffer is interpreted as a hex string and will be converted to its byte equivalent before it gets sent to the target. Example inputs: - `746573740d0a` gets sent as `test\r\n` -- `d0a` gets sent as `\r\n` as it assumed the trailing `0` has been omitted +- `d0a` gets sent as `\r\n` as it assumed the leading `0` has been omitted - `74657374xd0a` gets sent as `test` since the `xd` byte contains an invalid hex character Hex buffer mode can be turned off with a second `ctrl+b H`. From 8fcefce793d93707e2cce931953019fa12d9e5ca Mon Sep 17 00:00:00 2001 From: Aaron Cook Date: Sun, 18 Dec 2022 03:06:15 -0600 Subject: [PATCH 10/33] Add support for client cancel XModem --- src/xmodem.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/xmodem.c b/src/xmodem.c index e004422..8edc8e9 100644 --- a/src/xmodem.c +++ b/src/xmodem.c @@ -115,6 +115,8 @@ xmodem_send( if (recv_char == XMODEM_ACK) { break; + } else if (recv_char == XMODEM_CAN) { + return XMODEM_ERR_CANCEL; } retries++; From 40adb336fbeb43c2ae34051174b3a1c13d9e0ff9 Mon Sep 17 00:00:00 2001 From: Aaron Cook Date: Mon, 8 May 2023 09:55:57 -0500 Subject: [PATCH 11/33] Fix invalid read errors found with valgrind --- src/cheerios.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/cheerios.c b/src/cheerios.c index 05212e7..1cff23b 100644 --- a/src/cheerios.c +++ b/src/cheerios.c @@ -514,8 +514,17 @@ handle_color(line_buffer_t *lines, int line_idx, int *pos, int apply) return 0; while (p < line_len) { + int fit_col = (p + 7) <= line_len; + int fit_en = (p + 4) <= line_len; + /* fg or bg color */ - if (!memcmp(&line[p], "\e[38;5;", 7) || !memcmp(&line[p], "\e[48;5;", 7)) { + if ( + fit_col && + ( + !memcmp(&line[p], "\e[38;5;", 7) || + !memcmp(&line[p], "\e[48;5;", 7) + ) + ) { char col_str[4] = { 0 }; short *col = line[p + 2] == '3' ? &fg : &bg; @@ -543,13 +552,13 @@ handle_color(line_buffer_t *lines, int line_idx, int *pos, int apply) p++; } /* enable our color */ - else if (!memcmp(&line[p], "\e[1m", 4)) { + else if (fit_en && !memcmp(&line[p], "\e[1m", 4)) { p += 4; enable = 1; break; } /* disable our color */ - else if (!memcmp(&line[p], "\e[0m", 4)) { + else if (fit_en && !memcmp(&line[p], "\e[0m", 4)) { p += 4; enable = 0; break; From 1989c3688631c8f82455fb51b6f185063e17b659 Mon Sep 17 00:00:00 2001 From: Aaron Cook Date: Mon, 8 May 2023 10:00:37 -0500 Subject: [PATCH 12/33] #10 Process specific backup log files A process will open outbuf/inbuf.pid.log and move it to the outbuf/inbuf.log upon close, allowing for multiple processes to be open at once --- src/bytenuts.c | 6 +++++- src/cheerios.c | 53 ++++++++++++++++++++++++++++++++++++++++++-------- src/cheerios.h | 5 +++-- src/ingest.c | 52 +++++++++++++++++++++++++++++++++++++++---------- src/ingest.h | 1 + 5 files changed, 96 insertions(+), 21 deletions(-) diff --git a/src/bytenuts.c b/src/bytenuts.c index f8978f3..b4b158d 100644 --- a/src/bytenuts.c +++ b/src/bytenuts.c @@ -414,10 +414,14 @@ read_state() { struct stat st; char *buf; - char *cwd = getcwd(NULL, 0); + char *cwd; char *home = getenv("HOME"); FILE *fd; + if (!home) + return 0; + + cwd = getcwd(NULL, 0); chdir(home); fd = fopen(".config/bytenuts/inbuf.log", "r"); diff --git a/src/cheerios.c b/src/cheerios.c index 1cff23b..6d27b0e 100644 --- a/src/cheerios.c +++ b/src/cheerios.c @@ -25,7 +25,6 @@ int cheerios_start(bytenuts_t *bytenuts) { char *home = getenv("HOME"); - char *cwd; memset(&cheerios, 0, sizeof(cheerios_t)); @@ -43,11 +42,25 @@ cheerios_start(bytenuts_t *bytenuts) } } - cwd = getcwd(NULL, 0); - chdir(home); - cheerios.backup = fopen(".config/bytenuts/outbuf.log", "w"); - chdir(cwd); - free(cwd); + if (home) { + pid_t pid; + int name_len; + + /* ensure that a process has a unique log */ + pid = getpid(); + name_len = snprintf( + NULL, 0, + "%s/.config/bytenuts/outbuf.%d.log", + home, pid + ); + cheerios.backup_filename = calloc(1, name_len + 1); + snprintf( + cheerios.backup_filename, name_len + 1, + "%s/.config/bytenuts/outbuf.%d.log", + home, pid + ); + cheerios.backup = fopen(cheerios.backup_filename, "w"); + } pthread_cond_init(&cheerios.cond, NULL); pthread_mutex_init(&cheerios.lock, NULL); @@ -138,7 +151,6 @@ cheerios_stop() { cheerios.running = 0; pthread_join(cheerios.thr, NULL); - return 0; } @@ -338,8 +350,33 @@ cheerios_thread(void *arg) if (cheerios.log) fclose(cheerios.log); - if (cheerios.backup) + + if (cheerios.backup) { + char *out_filename; + int out_filename_len; + char *home = getenv("HOME"); + + /* do not chdir as other threads can still be running */ + out_filename_len = snprintf( + NULL, 0, + "%s/.config/bytenuts/outbuf.log", + home + ); + out_filename = calloc(1, out_filename_len + 1); + snprintf( + out_filename, out_filename_len + 1, + "%s/.config/bytenuts/outbuf.log", + home + ); + + /* move this processes log to the path that can be loaded on resumption */ fclose(cheerios.backup); + rename(cheerios.backup_filename, out_filename); + + free(out_filename); + free(cheerios.backup_filename); + } + pthread_exit(NULL); return NULL; } diff --git a/src/cheerios.h b/src/cheerios.h index e83dee2..97e2a53 100644 --- a/src/cheerios.h +++ b/src/cheerios.h @@ -35,8 +35,9 @@ typedef struct cheerios_struct { pthread_mutex_t *term_lock; int ser_fd; line_buffer_t lines; - FILE *log; - FILE *backup; + FILE *log; /* log file which was opened with -l */ + char *backup_filename; /* realpath to the backup outbuf.pid.log */ + FILE *backup; /* backup log file object */ bytenuts_config_t *config; volatile int mode; pthread_cond_t cond; diff --git a/src/ingest.c b/src/ingest.c index 1c491f5..1d043ac 100644 --- a/src/ingest.c +++ b/src/ingest.c @@ -40,19 +40,29 @@ ingest_start(bytenuts_t *bytenuts) HOME = getenv("HOME"); if (HOME) { - char *cwd = getcwd(NULL, 0); int idx = 0; - - chdir(HOME); + int name_len; + pid_t pid; while (!read_cmd_page(idx)) { idx++; } - ingest.history_fd = fopen(".config/bytenuts/inbuf.log", "w"); + /* ensure that a process has a unique log */ + pid = getpid(); + name_len = snprintf( + NULL, 0, + "%s/.config/bytenuts/inbuf.%d.log", + HOME, pid + ); + ingest.history_filename = calloc(1, name_len + 1); + snprintf( + ingest.history_filename, name_len + 1, + "%s/.config/bytenuts/inbuf.%d.log", + HOME, pid + ); - chdir(cwd); - free(cwd); + ingest.history_fd = fopen(ingest.history_filename, "w"); } if (ingest.cmd_pgs_n > 0) { @@ -73,10 +83,6 @@ ingest_stop() { ingest.running = 0; pthread_join(ingest.thr, NULL); - - if (ingest.history_fd) - fclose(ingest.history_fd); - return 0; } @@ -419,6 +425,32 @@ ingest_thread(void *arg) sched_yield(); } + if (ingest.history_fd) { + char *in_filename; + int in_filename_len; + char *home = getenv("HOME"); + + /* do not chdir as other threads can still be running */ + in_filename_len = snprintf( + NULL, 0, + "%s/.config/bytenuts/inbuf.log", + home + ); + in_filename = calloc(1, in_filename_len + 1); + snprintf( + in_filename, in_filename_len + 1, + "%s/.config/bytenuts/inbuf.log", + home + ); + + /* move this processes history to the path that can be loaded on resumption */ + fclose(ingest.history_fd); + rename(ingest.history_filename, in_filename); + + free(in_filename); + free(ingest.history_filename); + } + pthread_exit(NULL); return NULL; } diff --git a/src/ingest.h b/src/ingest.h index 06d1fec..5f39b06 100644 --- a/src/ingest.h +++ b/src/ingest.h @@ -24,6 +24,7 @@ typedef struct ingest_struct { int inpos; /* cursor position within the inbuf itself */ int inlen; /* length of the input buffer */ char **history; /* array of strings representing the command history */ + char *history_filename; /* realpath of inbuf.pid.log */ FILE *history_fd; char tmp_history[4096]; /* copy of inbuf before we load in history */ int history_len; From a128f57e88e52c64d758030cff8d42fff7e673c3 Mon Sep 17 00:00:00 2001 From: Aaron Cook Date: Mon, 8 May 2023 10:00:58 -0500 Subject: [PATCH 13/33] Optimize build by default with debug option --- Makefile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 3a7376d..7302347 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ CC=clang -CFLAGS=-g -Wall -MMD -pthread -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE +CFLAGS=-Wall -MMD -pthread -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE LDFLAGS=-lpthread -lncurses DIR_SRC=src @@ -11,7 +11,9 @@ TARGET=$(DIR_BIN)/bytenuts DEBUG ?= 0 ifeq ($(DEBUG), 1) - CFLAGS += -DDEBUG + CFLAGS += -g +else + CFLAGS += -O2 endif SRCS=$(wildcard $(DIR_SRC)/*.c) From eb1bab1638d0ebcf3b7cc9170a5e8b246472ecb9 Mon Sep 17 00:00:00 2001 From: Aaron Cook Date: Mon, 8 May 2023 10:07:17 -0500 Subject: [PATCH 14/33] #10 Update readme with info on cache log paths --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index eb728ab..936260c 100644 --- a/README.md +++ b/README.md @@ -140,6 +140,8 @@ On startup, command page 1 is selected. You can select which command page is cur Bytenuts caches the current instance's command list and output buffer in `~/.config/bytenuts/inbuf.log` and `~/.config/bytenuts/outbuf.log` respectively. If launched with the `-r` flag, Bytenuts will load in the input and output history from those files. The `outbuf.log` can also serve as a backup log if one forgot to launch with the `-l` flag. +While Bytenuts is running, the process will be writing its backup output and input to `~/.config/bytenuts/outbuf..log` and `~/.config/bytenuts/inbuf..log` and rename those files to the paths without the PID upon exit. This allows for multiple processes to be open without writing to the same log files. Thus, the `-r` flag will load the most recently exited Bytenuts instance. + ## Bugs Check out known bugs in the [issues tab](https://github.com/cookthebook/bytenuts/issues?q=is%3Aissue+is%3Aopen+label%3Abug). From f553dbf7db9f78667eeb6e84fe17e0977e501e92 Mon Sep 17 00:00:00 2001 From: Aaron Cook Date: Sat, 13 May 2023 14:09:16 -0500 Subject: [PATCH 15/33] #11 Add helpers for doing math with timespec structs --- src/timer_math.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++++ src/timer_math.h | 27 ++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 src/timer_math.c create mode 100644 src/timer_math.h diff --git a/src/timer_math.c b/src/timer_math.c new file mode 100644 index 0000000..bce2efd --- /dev/null +++ b/src/timer_math.c @@ -0,0 +1,71 @@ +#include "timer_math.h" + +int +timer_cmp(const struct timespec *a, const struct timespec *b) +{ + if (a->tv_sec < b->tv_sec) { + return -1; + } + else if (a->tv_sec > b->tv_sec) { + return 1; + } + else if (a->tv_nsec < b->tv_nsec) { + return -1; + } + else if (a->tv_nsec > b->tv_nsec) { + return 1; + } + else { + return 0; + } +} + +void +timer_add(struct timespec *a, const struct timespec *b) +{ + a->tv_sec += b->tv_sec; + a->tv_nsec += b->tv_nsec; + + if (a->tv_nsec > 1000000000) { + a->tv_sec += 1; + a->tv_nsec -= 1000000000; + } +} + +void +timer_add_ms(struct timespec *ts, uint32_t ms) +{ + struct timespec tmp = { + .tv_sec = ms / 1000, + .tv_nsec = (ms % 1000) * 1000000 + }; + timer_add(ts, &tmp); +} + +void +timer_sub(struct timespec *a, const struct timespec *b) +{ + if (timer_cmp(a, b) <= 0) { + a->tv_sec = 0; + a->tv_nsec = 0; + return; + } + + a->tv_sec -= b->tv_sec; + a->tv_nsec -= b->tv_nsec; + + if (a->tv_nsec < 0) { + a->tv_nsec += 1000000000; + a->tv_sec -= 1; + } +} + +void +timer_sub_ms(struct timespec *ts, uint32_t ms) +{ + struct timespec tmp = { + .tv_sec = ms / 1000, + .tv_nsec = (ms % 1000) * 1000000 + }; + timer_sub(ts, &tmp); +} diff --git a/src/timer_math.h b/src/timer_math.h new file mode 100644 index 0000000..99cdcd9 --- /dev/null +++ b/src/timer_math.h @@ -0,0 +1,27 @@ +#ifndef _TIMER_MATH_H_ +#define _TIMER_MATH_H_ + +#include +#include + +/* NOTE + * All math here is assuming that the second and nanosecond values of the + * timespecs are all non-negative, and nanoseconds is in the range [0, 999999999] */ + +/* Compare the two timespecs + * Return -1 if a < b, 0 if a = b, and 1 if a > b */ +int timer_cmp(const struct timespec *a, const struct timespec *b); + +/* Add the timespec value of b to a */ +void timer_add(struct timespec *a, const struct timespec *b); + +/* Add ms milliseconds to the timespec*/ +void timer_add_ms(struct timespec *ts, uint32_t ms); + +/* Subtract time b from a. If b > a, then a is set to 0 */ +void timer_sub(struct timespec *a, const struct timespec *b); + +/* Subtract ms milliseconds from ts. If ts would become negative, it is set to 0 */ +void timer_sub_ms(struct timespec *ts, uint32_t ms); + +#endif /* _TIMER_MATH_H_ */ From ef764dabc6bf5cef74ae9228f81e661dbd701fe0 Mon Sep 17 00:00:00 2001 From: Aaron Cook Date: Sat, 13 May 2023 14:11:22 -0500 Subject: [PATCH 16/33] #11 Implement inter command timeout --- Makefile | 2 +- src/bytenuts.c | 15 +++++++++++++++ src/bytenuts.h | 5 ++++- src/ingest.c | 31 ++++++++++++++++++++++++++++--- src/ingest.h | 2 ++ 5 files changed, 50 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 7302347..2c57a52 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ CC=clang -CFLAGS=-Wall -MMD -pthread -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE +CFLAGS=-Wall -MMD -pthread -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=199309L LDFLAGS=-lpthread -lncurses DIR_SRC=src diff --git a/src/bytenuts.c b/src/bytenuts.c index b4b158d..02db1ea 100644 --- a/src/bytenuts.c +++ b/src/bytenuts.c @@ -177,6 +177,8 @@ bytenuts_print_stats() cheerios_insert(st_line, strlen(st_line)); sprintf(st_line, "serial_path: %s\r\n", bytenuts.config.serial_path); cheerios_insert(st_line, strlen(st_line)); + sprintf(st_line, "inter_cmd_to: %d\r\n", bytenuts.config.inter_cmd_to); + cheerios_insert(st_line, strlen(st_line)); return 0; } @@ -357,6 +359,13 @@ parse_args(int argc, char **argv) bytenuts.config.escape = argv[i][9]; bytenuts.config_overrides[3] = 1; } + else if (arg_len > 15 && !memcmp(argv[i], "--inter_cmd_to=", 15)) { + long cmd_to = strtol(&argv[i][15], NULL, 10); + if (cmd_to >= 0) { + bytenuts.config.inter_cmd_to = cmd_to; + bytenuts.config_overrides[4] = 1; + } + } else if (!strcmp(argv[i], "--resume") || !strcmp(argv[i], "-r")) { bytenuts.resume = 1; } @@ -404,6 +413,12 @@ load_configs() else if (!bytenuts.config_overrides[3] && !memcmp(line, "escape=", 7)) { bytenuts.config.escape = line[7]; } + else if (!bytenuts.config_overrides[4] && !memcmp(line, "inter_cmd_to=", 13)) { + long cmd_to = strtol(&line[13], NULL, 10); + if (cmd_to >= 0) { + bytenuts.config.inter_cmd_to = cmd_to; + } + } } return 0; diff --git a/src/bytenuts.h b/src/bytenuts.h index a8f470a..389047e 100644 --- a/src/bytenuts.h +++ b/src/bytenuts.h @@ -5,6 +5,7 @@ #include #include #include +#include #include "serial.h" @@ -27,6 +28,7 @@ typedef struct bytenuts_config_struct { char *config_path; /* config file path */ char *log_path; /* path to the log file (if it exists) */ char *serial_path; /* path to the target serial device */ + uint32_t inter_cmd_to; /* inter command timeout in ms */ } bytenuts_config_t; #define CONFIG_DEFAULT (bytenuts_config_t){ \ @@ -38,12 +40,13 @@ typedef struct bytenuts_config_struct { .config_path = NULL, \ .log_path = NULL, \ .serial_path = NULL, \ + .inter_cmd_to = 10, \ } typedef struct bytenuts_struct { int serial_fd; bytenuts_config_t config; - int config_overrides[4]; + int config_overrides[5]; int resume; bytenuts_state_t state; WINDOW *status_win; diff --git a/src/ingest.c b/src/ingest.c index 1d043ac..4189730 100644 --- a/src/ingest.c +++ b/src/ingest.c @@ -8,10 +8,12 @@ #include #include #include +#include #include "bytenuts.h" #include "cheerios.h" #include "ingest.h" +#include "timer_math.h" static void *ingest_thread(void *arg); @@ -21,10 +23,11 @@ static int mode_normal(int ch); static int mode_xmodem(int block_sz); static int mode_hex(int ch); static int handle_functions(int ch); -static int print_stats(); -static int auto_complete(); +static int print_stats(void); +static int auto_complete(void); static int read_cmd_page(int idx); -static void update_cmd_pg_status(); +static void update_cmd_pg_status(void); +static void inter_command_wait(void); int ingest_start(bytenuts_t *bytenuts) @@ -70,6 +73,10 @@ ingest_start(bytenuts_t *bytenuts) } update_cmd_pg_status(); + clock_gettime(CLOCK_REALTIME, &ingest.cmd_ts); + /* ensure first command is not delayed */ + timer_sub_ms(&ingest.cmd_ts, ingest.config->inter_cmd_to); + ingest.running = 1; if (pthread_create(&ingest.thr, NULL, ingest_thread, NULL)) { return -1; @@ -471,6 +478,7 @@ mode_normal(int ch) else ending = "\r\n"; + inter_command_wait(); cheerios_input(ingest.inbuf, inlen); cheerios_input(ending, strlen(ending)); if (ingest.config->echo) { @@ -688,6 +696,7 @@ mode_hex(int ch) hexlen++; } + inter_command_wait(); cheerios_input(hexbuf, hexlen); if (ingest.config->echo) { cheerios_insert(">> (hex)\r\n", 10); @@ -1029,3 +1038,19 @@ update_cmd_pg_status() bytenuts_set_status(STATUS_CMDPAGE, "n/a"); } } + +static void +inter_command_wait() +{ + struct timespec target; + struct timespec now; + + clock_gettime(CLOCK_REALTIME, &now); + memcpy(&target, &ingest.cmd_ts, sizeof(struct timespec)); + + timer_add_ms(&target, ingest.config->inter_cmd_to); + timer_sub(&target, &now); + + nanosleep(&target, NULL); + clock_gettime(CLOCK_REALTIME, &ingest.cmd_ts); +} diff --git a/src/ingest.h b/src/ingest.h index 5f39b06..e8e4474 100644 --- a/src/ingest.h +++ b/src/ingest.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "bytenuts.h" @@ -40,6 +41,7 @@ typedef struct ingest_struct { int cmd_pgs_n; /* what command page is currently selected */ int cmd_pg_cur; + struct timespec cmd_ts; /* last command sent timestamp */ } ingest_t; /* startup the input window thread */ From 6c5e576729f2498a9a0703d588e830f4ea259d3f Mon Sep 17 00:00:00 2001 From: Aaron Cook Date: Sat, 13 May 2023 14:16:57 -0500 Subject: [PATCH 17/33] #11 Update help/readme with inter command timeout --- README.md | 5 +++++ src/bytenuts.c | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 936260c..a028776 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,9 @@ Configs get loaded from ${HOME}/.bytenuts/config (if file exists) --escape= Change the default ctrl+b escape character. + +--inter_cmd_to= + Set the intercommand timeout in milliseconds (default is 10ms). ``` ## Navigation @@ -85,12 +88,14 @@ colors=1 echo=0 no_crlf=0 escape=a +inter_cmd_to=100 ``` - `colors` - enable parsing of 8-bit ANSI color codes - `echo` - echo input to the terminal in app - `no_crlf` - just send a line feed (`\n`) for user input rather than carriage return + line feed (`\r\n`) - `escape` - change what character is used as an escape sequence for commands (e.g. if set to `escape=a`, Bytenuts can be exited with `ctrl+a, q`) +- `inter_cmd_to` - Set a timeout in milliseconds that must be met. Useful for pasting in multiple lines and ensuring a short delay in between the commands. Bytenuts looks for the configs at `~/.config/bytenuts/config`. diff --git a/src/bytenuts.c b/src/bytenuts.c index 02db1ea..a5c65a1 100644 --- a/src/bytenuts.c +++ b/src/bytenuts.c @@ -25,7 +25,8 @@ "--colors=<0|1>\n Turn 8-bit ANSI colors off/on.\n\n" \ "--echo=<0|1>\n Turn input echoing off/on.\n\n" \ "--no_crlf=<0|1>\n Choose to send LF and not CRLF on input.\n\n" \ -"--escape=\n Change the default ctrl+b escape character.\n" \ +"--escape=\n Change the default ctrl+b escape character.\n\n" \ +"--inter_cmd_to=\n Set the intercommand timeout in milliseconds (default is 10ms).\n" \ ) static int parse_args(int argc, char **argv); From cb64ea4198327d1954ab99eb81bb542e4681737f Mon Sep 17 00:00:00 2001 From: Aaron Cook Date: Sun, 28 May 2023 01:22:26 -0500 Subject: [PATCH 18/33] #13 Fix command page loading due to CWD changes --- src/ingest.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/ingest.c b/src/ingest.c index 4189730..e458421 100644 --- a/src/ingest.c +++ b/src/ingest.c @@ -25,7 +25,7 @@ static int mode_hex(int ch); static int handle_functions(int ch); static int print_stats(void); static int auto_complete(void); -static int read_cmd_page(int idx); +static int read_cmd_page(const char *home_dir, int idx); static void update_cmd_pg_status(void); static void inter_command_wait(void); @@ -47,7 +47,7 @@ ingest_start(bytenuts_t *bytenuts) int name_len; pid_t pid; - while (!read_cmd_page(idx)) { + while (!read_cmd_page(HOME, idx)) { idx++; } @@ -993,13 +993,17 @@ auto_complete_cleanup: } static int -read_cmd_page(int idx) +read_cmd_page(const char *home_dir, int idx) { char fname[128]; FILE *fd; char line[1024]; - snprintf(fname, sizeof(fname), ".config/bytenuts/commands%d", idx+1); + snprintf( + fname, sizeof(fname), + "%s/.config/bytenuts/commands%d", + home_dir, idx+1 + ); fd = fopen(fname, "r"); if (!fd) From 8182b384ba74cd0ababb6787c5445f2541778c60 Mon Sep 17 00:00:00 2001 From: Aaron Cook Date: Mon, 17 Jul 2023 16:08:40 -0500 Subject: [PATCH 19/33] Add ack failure count to XModem status --- src/cheerios.c | 7 ++++--- src/xmodem.c | 6 ++++-- src/xmodem.h | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/cheerios.c b/src/cheerios.c index 6d27b0e..5ab880f 100644 --- a/src/cheerios.c +++ b/src/cheerios.c @@ -216,17 +216,18 @@ cheerios_insert(const char *buf, size_t len) static void -__xmodem_callback(size_t sent, size_t total) +__xmodem_callback(size_t sent, size_t total, int ack_fails) { char status_str[1024]; snprintf( status_str, sizeof(status_str), - "\r %.02f/%.02fKB (%.01f%%)", + "\r %.02f/%.02fKB (%.01f%%, %d ACK fails)", (sent / 1024.0), (total / 1024.0), - (sent * 100.0 / total) + (sent * 100.0 / total), + ack_fails ); cheerios_insert(status_str, strlen(status_str)); diff --git a/src/xmodem.c b/src/xmodem.c index 8edc8e9..9bf850d 100644 --- a/src/xmodem.c +++ b/src/xmodem.c @@ -20,7 +20,7 @@ xmodem_send( int in_fd, size_t sz, int block_sz, - void (*status_cb)(size_t sent, size_t total) + void (*status_cb)(size_t sent, size_t total, int ack_fails) ) { uint8_t start_byte; @@ -30,6 +30,7 @@ xmodem_send( uint8_t packet_num; int retries; int ret = 0; + int ack_fails = 0; if (block_sz != 128 && block_sz != 1024) { return -1; @@ -120,11 +121,12 @@ xmodem_send( } retries++; + ack_fails++; } sent += read_len; packet_num++; /* expect overflow */ - status_cb(sent, sz); + status_cb(sent, sz, ack_fails); } end_of_transmission: diff --git a/src/xmodem.h b/src/xmodem.h index ecc0092..ac65b02 100644 --- a/src/xmodem.h +++ b/src/xmodem.h @@ -34,7 +34,7 @@ int xmodem_send( int in_fd, size_t sz, int block_sz, - void (*status_cb)(size_t sent, size_t total) + void (*status_cb)(size_t sent, size_t total, int ack_fails) ); #if 0 From 15879787a5b80b98dd500c39fbe3e3c61d41d9bf Mon Sep 17 00:00:00 2001 From: Aaron Cook Date: Wed, 23 Aug 2023 22:04:48 -0500 Subject: [PATCH 20/33] Add PDCurses and make Windows option --- .gitmodules | 3 +++ Makefile | 25 +++++++++++++++++++------ 2 files changed, 22 insertions(+), 6 deletions(-) create mode 100644 .gitmodules diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..8634b5a --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/PDCurses"] + path = lib/PDCurses + url = git@github.com:wmcbrine/PDCurses.git diff --git a/Makefile b/Makefile index 2c57a52..e09ca14 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,19 @@ -CC=clang +TARGET_OS=LINUX + CFLAGS=-Wall -MMD -pthread -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=199309L + +SRCS=$(wildcard $(DIR_SRC)/*.c) +OBJS=$(addprefix $(DIR_OBJ)/,$(addsuffix .o,$(basename $(SRCS)))) +DEPS=$(addprefix $(DIR_OBJ)/,$(addsuffix .d,$(basename $(SRCS)))) + +ifeq ($(TARGET_OS), LINUX) + CC=clang +else + CC=x86_64-w64-mingw32-gcc + CFLAGS+=-Llib/PDCurses/wincon -Llib/PDCurses + DEPS+=PDCurses +endif + LDFLAGS=-lpthread -lncurses DIR_SRC=src @@ -16,11 +30,7 @@ else CFLAGS += -O2 endif -SRCS=$(wildcard $(DIR_SRC)/*.c) -OBJS=$(addprefix $(DIR_OBJ)/,$(addsuffix .o,$(basename $(SRCS)))) -DEPS=$(addprefix $(DIR_OBJ)/,$(addsuffix .d,$(basename $(SRCS)))) - -.PHONY: all install uninstall clean +.PHONY: all install uninstall clean PDCurses all: $(TARGET) @@ -40,6 +50,9 @@ $(DIR_OBJ)/$(DIR_SRC)/%.o: $(DIR_SRC)/%.c @echo "compile $<" @$(CC) $(CFLAGS) -c $< -o $@ +PDCurses: + cd lib/PDCurses/wincon && $(MAKE) -j CC=x86_64-w64-mingw32-gcc + clean: rm -rf $(DIR_BUILD) From be0e8a997cd3a20f2df0b7ed16f8187addb80ecf Mon Sep 17 00:00:00 2001 From: Aaron Cook Date: Wed, 23 Aug 2023 22:07:44 -0500 Subject: [PATCH 21/33] Unfinished serial support in Windows --- src/bytenuts.c | 149 +++---------------------------------------------- src/bytenuts.h | 12 ++-- src/cheerios.c | 6 +- src/cheerios.h | 8 ++- src/ingest.c | 6 +- src/ingest.h | 6 +- src/serial.c | 132 +++++++++++++++++++++++++++++++++++++++++-- src/serial.h | 11 +++- 8 files changed, 174 insertions(+), 156 deletions(-) diff --git a/src/bytenuts.c b/src/bytenuts.c index a5c65a1..908ac61 100644 --- a/src/bytenuts.c +++ b/src/bytenuts.c @@ -1,11 +1,14 @@ -#include +#ifdef __MINGW32__ +# include +#else +# include +#endif #include #include #include #include #include #include -#include #include #include "bytenuts.h" @@ -33,8 +36,6 @@ static int parse_args(int argc, char **argv); static int load_configs(); static int read_state(); static int load_state(); -static speed_t string_to_speed(const char *speed); -static const char *speed_to_string(speed_t speed); static bytenuts_t bytenuts; @@ -61,11 +62,13 @@ bytenuts_run(int argc, char **argv) return -1; } +#ifndef __MINGW32__ /* use pseudo-terminals for testing purposes */ if (!strcmp(bytenuts.config.serial_path, "/dev/ptmx")) { grantpt(bytenuts.serial_fd); unlockpt(bytenuts.serial_fd); } +#endif // Initialize ncurses initscr(); @@ -170,7 +173,7 @@ bytenuts_print_stats() cheerios_insert(st_line, strlen(st_line)); sprintf(st_line, "escape: %c\r\n", bytenuts.config.escape); cheerios_insert(st_line, strlen(st_line)); - sprintf(st_line, "baud: %s\r\n", speed_to_string(bytenuts.config.baud)); + sprintf(st_line, "baud: %ld\r\n", bytenuts.config.baud); cheerios_insert(st_line, strlen(st_line)); sprintf(st_line, "config_path: %s\r\n", bytenuts.config.config_path); cheerios_insert(st_line, strlen(st_line)); @@ -316,7 +319,7 @@ parse_args(int argc, char **argv) if (i == argc - 1) return -1; - bytenuts.config.baud = string_to_speed(argv[i]); + bytenuts.config.baud = strtol(argv[i], NULL, 10); } else if (!strcmp(argv[i], "-l")) { i++; @@ -498,137 +501,3 @@ load_state() return 0; } - -static speed_t -string_to_speed(const char *speed) -{ - if (!strcmp("50", speed)) - return B50; - else if (!strcmp("75", speed)) - return B75; - else if (!strcmp("110", speed)) - return B110; - else if (!strcmp("134", speed)) - return B134; - else if (!strcmp("150", speed)) - return B150; - else if (!strcmp("200", speed)) - return B200; - else if (!strcmp("300", speed)) - return B300; - else if (!strcmp("600", speed)) - return B600; - else if (!strcmp("1200", speed)) - return B1200; - else if (!strcmp("1800", speed)) - return B1800; - else if (!strcmp("2400", speed)) - return B2400; - else if (!strcmp("4800", speed)) - return B4800; - else if (!strcmp("9600", speed)) - return B9600; - else if (!strcmp("19200", speed)) - return B19200; - else if (!strcmp("38400", speed)) - return B38400; - else if (!strcmp("57600", speed)) - return B57600; - else if (!strcmp("115200", speed)) - return B115200; - else if (!strcmp("230400", speed)) - return B230400; - else if (!strcmp("460800", speed)) - return B460800; - else if (!strcmp("500000", speed)) - return B500000; - else if (!strcmp("576000", speed)) - return B576000; - else if (!strcmp("921600", speed)) - return B921600; - else if (!strcmp("1000000", speed)) - return B1000000; - else if (!strcmp("1152000", speed)) - return B1152000; - else if (!strcmp("1500000", speed)) - return B1500000; - else if (!strcmp("2000000", speed)) - return B2000000; - else if (!strcmp("2500000", speed)) - return B2500000; - else if (!strcmp("3000000", speed)) - return B3000000; - else if (!strcmp("3500000", speed)) - return B3500000; - else if (!strcmp("4000000", speed)) - return B4000000; - else - return B0; -} - -static const char * -speed_to_string(speed_t speed) -{ - if (speed == B50) - return "B50"; - else if (speed == B75) - return "B75"; - else if (speed == B110) - return "B110"; - else if (speed == B134) - return "B134"; - else if (speed == B150) - return "B150"; - else if (speed == B200) - return "B200"; - else if (speed == B300) - return "B300"; - else if (speed == B600) - return "B600"; - else if (speed == B1200) - return "B1200"; - else if (speed == B1800) - return "B1800"; - else if (speed == B2400) - return "B2400"; - else if (speed == B4800) - return "B4800"; - else if (speed == B9600) - return "B9600"; - else if (speed == B19200) - return "B19200"; - else if (speed == B38400) - return "B38400"; - else if (speed == B57600) - return "B57600"; - else if (speed == B115200) - return "B115200"; - else if (speed == B230400) - return "B230400"; - else if (speed == B460800) - return "B460800"; - else if (speed == B500000) - return "B500000"; - else if (speed == B576000) - return "B576000"; - else if (speed == B921600) - return "B921600"; - else if (speed == B1000000) - return "B1000000"; - else if (speed == B1152000) - return "B1152000"; - else if (speed == B1500000) - return "B1500000"; - else if (speed == B2000000) - return "B2000000"; - else if (speed == B2500000) - return "B2500000"; - else if (speed == B3000000) - return "B3000000"; - else if (speed == B3500000) - return "B3500000"; - else if (speed == B4000000) - return "B4000000"; - else - return ""; -} diff --git a/src/bytenuts.h b/src/bytenuts.h index 389047e..6198db5 100644 --- a/src/bytenuts.h +++ b/src/bytenuts.h @@ -1,7 +1,11 @@ #ifndef _BYTENUTES_H_ #define _BYTENUTES_H_ -#include +#ifdef __MINGW32__ +# include +#else +# include +#endif #include #include #include @@ -24,7 +28,7 @@ typedef struct bytenuts_config_struct { int echo; /* echo user input to the output window, default 0 */ int no_crlf; /* only send LF line endings, not CRLF, default 0 */ char escape; /* which character is used for escape sequence */ - speed_t baud; /* baud rate */ + long baud; /* baud rate */ char *config_path; /* config file path */ char *log_path; /* path to the log file (if it exists) */ char *serial_path; /* path to the target serial device */ @@ -36,7 +40,7 @@ typedef struct bytenuts_config_struct { .echo = 0, \ .no_crlf = 0, \ .escape = 'b', \ - .baud = B115200, \ + .baud = 115200, \ .config_path = NULL, \ .log_path = NULL, \ .serial_path = NULL, \ @@ -44,7 +48,7 @@ typedef struct bytenuts_config_struct { } typedef struct bytenuts_struct { - int serial_fd; + serial_t serial_fd; bytenuts_config_t config; int config_overrides[5]; int resume; diff --git a/src/cheerios.c b/src/cheerios.c index 5ab880f..f0f6b99 100644 --- a/src/cheerios.c +++ b/src/cheerios.c @@ -1,4 +1,8 @@ -#include +#ifdef __MINGW32__ +# include +#else +# include +#endif #include #include #include diff --git a/src/cheerios.h b/src/cheerios.h index 97e2a53..7e48360 100644 --- a/src/cheerios.h +++ b/src/cheerios.h @@ -1,7 +1,11 @@ #ifndef _CHEERIOS_H_ #define _CHEERIOS_H_ -#include +#ifdef __MINGW32__ +# include +#else +# include +#endif #include #include #include @@ -33,7 +37,7 @@ typedef struct cheerios_struct { pthread_t thr; WINDOW *output; pthread_mutex_t *term_lock; - int ser_fd; + serial_t ser_fd; line_buffer_t lines; FILE *log; /* log file which was opened with -l */ char *backup_filename; /* realpath to the backup outbuf.pid.log */ diff --git a/src/ingest.c b/src/ingest.c index e458421..d374626 100644 --- a/src/ingest.c +++ b/src/ingest.c @@ -1,5 +1,9 @@ #include -#include +#ifdef __MINGW32__ +# include +#else +# include +#endif #include #include #include diff --git a/src/ingest.h b/src/ingest.h index e8e4474..95a2f8c 100644 --- a/src/ingest.h +++ b/src/ingest.h @@ -1,7 +1,11 @@ #ifndef _INGEST_H_ #define _INGEST_H_ -#include +#ifdef __MINGW32__ +# include +#else +# include +#endif #include #include #include diff --git a/src/serial.c b/src/serial.c index 364fcbe..16d0016 100644 --- a/src/serial.c +++ b/src/serial.c @@ -2,14 +2,44 @@ #include #include #include -#include #include #include "serial.h" -int -serial_open(const char *path, speed_t bps) +#ifdef __MINGW32__ +# include +serial_t +serial_open(const char *comport, long bps) { + DCB dcb = { 0 }; + HANDLE serial = CreateFile( + comport, + GENERIC_READ | GENERIC_WRITE, + 0, + 0, + OPEN_EXISTING, + 0, + 0 + ); + + if (serial == INVALID_HANDLE_VALUE) { + return -1; + } + + /* TODO + * reference: https://github.com/waynix/SPinGW/tree/master */ + return serial; +} +#else +# include +# include + +static speed_t long_to_speed(long bps); + +serial_t +serial_open(const char *path, long bps) +{ + speed_t speed = long_to_speed(bps); int fd; struct termios otio; struct termios ntio; @@ -22,8 +52,8 @@ serial_open(const char *path, speed_t bps) tcgetattr(fd, &otio); tcgetattr(fd, &ntio); - cfsetispeed(&ntio, bps); - cfsetospeed(&ntio, bps); + cfsetispeed(&ntio, speed); + cfsetospeed(&ntio, speed); /* https://www.cmrr.umn.edu/~strupp/serial.html */ /* 8N1 */ @@ -57,3 +87,95 @@ serial_open(const char *path, speed_t bps) return fd; } + +int +serial_read(serial_t serial, void *buf, size_t len) +{ + struct pollfd fds; + fds.fd = serial; + fds.events = POLLIN; + fds.revents = 0; + + if (poll(&fds, 1, 1) == 0) { + return 0; + } + + return (int)read(serial, buf, len); +} + +int +serial_write(serial_t serial, void *buf, size_t len) +{ + return (int)write(serial, buf, len); +} + +static speed_t +long_to_speed(long bps) +{ + switch (bps) { + case 50: + return B50; + case 75: + return B75; + case 110: + return B110; + case 134: + return B134; + case 150: + return B150; + case 200: + return B200; + case 300: + return B300; + case 600: + return B600; + case 1200: + return B1200; + case 1800: + return B1800; + case 2400: + return B2400; + case 4800: + return B4800; + case 9600: + return B9600; + case 19200: + return B19200; + case 38400: + return B38400; + case 57600: + return B57600; + case 115200: + return B115200; + case 230400: + return B230400; + case 460800: + return B460800; + case 500000: + return B500000; + case 576000: + return B576000; + case 921600: + return B921600; + case 1000000: + return B1000000; + case 1152000: + return B1152000; + case 1500000: + return B1500000; + case 2000000: + return B2000000; + case 2500000: + return B2500000; + case 3000000: + return B3000000; + case 3500000: + return B3500000; + case 4000000: + return B4000000; + default: + return B0; + } +} + +#endif /* __MINGW32__ */ diff --git a/src/serial.h b/src/serial.h index 9478298..bcae708 100644 --- a/src/serial.h +++ b/src/serial.h @@ -1,8 +1,15 @@ #ifndef _SERIAL_H_ #define _SERIAL_H_ -#include +#ifdef __MINGW32__ +# include +typedef HANDLE serial_t; +#else +typedef int serial_t; +#endif -int serial_open(const char *path, speed_t bps); +serial_t serial_open(const char *path, long bps); +int serial_read(serial_t serial, void *buf, size_t len); +int serial_write(serial_t serial, void *buf, size_t len); #endif /* _SERIAL_H_ */ From 4eaac6d822ee06c4bc9db1e41b73e5d8d7cd6fd2 Mon Sep 17 00:00:00 2001 From: Aaron Cook Date: Fri, 1 Sep 2023 00:08:44 -0500 Subject: [PATCH 22/33] Untested serial implementation for windows --- Makefile | 43 ++++++++-------- src/bytenuts.c | 19 ++++++- src/cheerios.c | 39 ++++++++------- src/ingest.c | 11 ++--- src/main.c | 6 +++ src/serial.c | 132 +++++++++++++++++++++++++++++++++++++++++++++---- src/serial.h | 20 +++++++- src/xmodem.c | 21 +++----- src/xmodem.h | 2 +- 9 files changed, 218 insertions(+), 75 deletions(-) diff --git a/Makefile b/Makefile index e09ca14..02282fd 100644 --- a/Makefile +++ b/Makefile @@ -1,27 +1,30 @@ -TARGET_OS=LINUX - -CFLAGS=-Wall -MMD -pthread -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=199309L - -SRCS=$(wildcard $(DIR_SRC)/*.c) -OBJS=$(addprefix $(DIR_OBJ)/,$(addsuffix .o,$(basename $(SRCS)))) -DEPS=$(addprefix $(DIR_OBJ)/,$(addsuffix .d,$(basename $(SRCS)))) - -ifeq ($(TARGET_OS), LINUX) - CC=clang -else - CC=x86_64-w64-mingw32-gcc - CFLAGS+=-Llib/PDCurses/wincon -Llib/PDCurses - DEPS+=PDCurses -endif - -LDFLAGS=-lpthread -lncurses +OS=LINUX DIR_SRC=src DIR_BUILD=build DIR_OBJ=$(DIR_BUILD)/obj DIR_BIN=$(DIR_BUILD)/bin -TARGET=$(DIR_BIN)/bytenuts +CFLAGS=-Wall -MMD -pthread -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=199309L +LIB_DEPS= +LIB_OBJS= + +SRCS=$(wildcard $(DIR_SRC)/*.c) +OBJS=$(addprefix $(DIR_OBJ)/,$(addsuffix .o,$(basename $(SRCS)))) +DEPS=$(addprefix $(DIR_OBJ)/,$(addsuffix .d,$(basename $(SRCS)))) + +ifeq ($(OS), LINUX) + CC=clang + LDFLAGS=-lncurses -lpthread + TARGET=$(DIR_BIN)/bytenuts +else + CC=x86_64-w64-mingw32-gcc + CFLAGS+=-Ilib/PDCurses/wincon/ -Ilib/PDCurses/ + LDFLAGS=-lpthread -static + LIB_DEPS+=PDCurses + LIB_OBJS+=lib/PDCurses/wincon/*.o + TARGET=$(DIR_BIN)/bytenuts.exe +endif DEBUG ?= 0 ifeq ($(DEBUG), 1) @@ -40,10 +43,10 @@ install: uninstall: rm -f /usr/local/bin/bytenuts -$(TARGET): $(OBJS) +$(TARGET): $(OBJS) $(LIB_DEPS) @mkdir -p $(dir $@) @echo "link and compile for $@" - @$(CC) $(CFLAGS) $(LDFLAGS) -o $(TARGET) $(OBJS) + @$(CC) $(CFLAGS) $(LDFLAGS) -o $(TARGET) $(OBJS) $(LIB_OBJS) $(DIR_OBJ)/$(DIR_SRC)/%.o: $(DIR_SRC)/%.c @mkdir -p $(dir $@) diff --git a/src/bytenuts.c b/src/bytenuts.c index 908ac61..ff6a585 100644 --- a/src/bytenuts.c +++ b/src/bytenuts.c @@ -58,10 +58,16 @@ bytenuts_run(int argc, char **argv) } bytenuts.serial_fd = serial_open(bytenuts.config.serial_path, bytenuts.config.baud); - if (bytenuts.serial_fd < 0) { + if (bytenuts.serial_fd == SERIAL_INVALID) { + printf( + "Failed to open serial port \"%s\"\r\n", + bytenuts.config.serial_path + ); return -1; } + printf("Opened \"%s\"\r\n", bytenuts.config.serial_path); + #ifndef __MINGW32__ /* use pseudo-terminals for testing purposes */ if (!strcmp(bytenuts.config.serial_path, "/dev/ptmx")) { @@ -111,11 +117,13 @@ bytenuts_run(int argc, char **argv) cheerios_start(&bytenuts); ingest_start(&bytenuts); +#ifndef __MINGW32__ if (!strcmp(bytenuts.config.serial_path, "/dev/ptmx")) { char info[128]; snprintf(info, sizeof(info), "Opened PTY port %s", ptsname(bytenuts.serial_fd)); cheerios_info(info); } +#endif if (bytenuts.resume) { load_state(); @@ -157,7 +165,7 @@ bytenuts_kill() delwin(bytenuts.out_win); endwin(); - close(bytenuts.serial_fd); + serial_close(bytenuts.serial_fd); } int @@ -302,10 +310,17 @@ parse_args(int argc, char **argv) /* setup default options first */ bytenuts.config = CONFIG_DEFAULT; { +#ifdef __MINGW32__ + char *home = getenv("HOMEPATH"); + char path[] = "\\.config\\bytenuts\\config"; + bytenuts.config.config_path = malloc(strlen(home) + strlen(path) + 4); + sprintf(bytenuts.config.config_path, "C:\\%s%s", home, path); +#else char *home = getenv("HOME"); char path[] = "/.config/bytenuts/config"; bytenuts.config.config_path = malloc(strlen(home) + strlen(path) + 1); sprintf(bytenuts.config.config_path, "%s%s", home, path); +#endif } for (int i = 1; i < argc - 1; i++) { diff --git a/src/cheerios.c b/src/cheerios.c index f0f6b99..9c25d7c 100644 --- a/src/cheerios.c +++ b/src/cheerios.c @@ -3,7 +3,6 @@ #else # include #endif -#include #include #include #include @@ -54,14 +53,14 @@ cheerios_start(bytenuts_t *bytenuts) pid = getpid(); name_len = snprintf( NULL, 0, - "%s/.config/bytenuts/outbuf.%d.log", - home, pid + "%s/.config/bytenuts/outbuf.%lld.log", + home, (long long)pid ); cheerios.backup_filename = calloc(1, name_len + 1); snprintf( cheerios.backup_filename, name_len + 1, - "%s/.config/bytenuts/outbuf.%d.log", - home, pid + "%s/.config/bytenuts/outbuf.%lld.log", + home, (long long)pid ); cheerios.backup = fopen(cheerios.backup_filename, "w"); } @@ -161,9 +160,22 @@ cheerios_stop() int cheerios_input(const char *buf, size_t len) { + size_t len_left = len; + size_t p = 0; + pthread_mutex_lock(&cheerios.lock); - write(cheerios.ser_fd, buf, len); + while (len_left > 0) { + ssize_t tmp = serial_write(cheerios.ser_fd, &buf[p], len_left); + + if (tmp < 0) { + pthread_mutex_unlock(&cheerios.lock); + return -1; + } + + len_left -= tmp; + } pthread_mutex_unlock(&cheerios.lock); + return 0; } @@ -295,6 +307,7 @@ cheerios_getmaxy() { int y, x; getmaxyx(cheerios.output, y, x); + (void)x; return y; } @@ -303,6 +316,7 @@ cheerios_getmaxx() { int y, x; getmaxyx(cheerios.output, y, x); + (void)y; return x; } @@ -325,7 +339,6 @@ cheerios_thread(void *arg) { char buf[1024]; ssize_t read_ret = 0; - struct pollfd fds; while (cheerios.running) { pthread_mutex_lock(&cheerios.lock); @@ -334,17 +347,7 @@ cheerios_thread(void *arg) pthread_cond_wait(&cheerios.cond, &cheerios.lock); } - fds.fd = cheerios.ser_fd; - fds.events = POLLIN; - fds.revents = 0; - - if (poll(&fds, 1, 1) == 0) { - pthread_mutex_unlock(&cheerios.lock); - nanosleep(&(struct timespec){ 0, 100000 }, NULL); - continue; - } - - read_ret = read(cheerios.ser_fd, buf, sizeof(buf)); + read_ret = serial_read(cheerios.ser_fd, buf, sizeof(buf)); if (read_ret > 0) { insert_buf(&cheerios.lines, buf, read_ret); } diff --git a/src/ingest.c b/src/ingest.c index d374626..6203077 100644 --- a/src/ingest.c +++ b/src/ingest.c @@ -59,14 +59,14 @@ ingest_start(bytenuts_t *bytenuts) pid = getpid(); name_len = snprintf( NULL, 0, - "%s/.config/bytenuts/inbuf.%d.log", - HOME, pid + "%s/.config/bytenuts/inbuf.%lld.log", + HOME, (long long)pid ); ingest.history_filename = calloc(1, name_len + 1); snprintf( ingest.history_filename, name_len + 1, - "%s/.config/bytenuts/inbuf.%d.log", - HOME, pid + "%s/.config/bytenuts/inbuf.%lld.log", + HOME, (long long)pid ); ingest.history_fd = fopen(ingest.history_filename, "w"); @@ -209,7 +209,6 @@ ingest_set_history(char **history, int history_len) static void * ingest_thread(void *arg) { - int esc_flag; int ch; memset(ingest.inbuf, 0, sizeof(ingest.inbuf)); @@ -218,8 +217,6 @@ ingest_thread(void *arg) bytenuts_set_status(STATUS_INGEST, "normal"); while (ingest.running) { - esc_flag = 0; - pthread_mutex_lock(ingest.term_lock); ch = wgetch(ingest.input); pthread_mutex_unlock(ingest.term_lock); diff --git a/src/main.c b/src/main.c index b75957c..6e1e64b 100644 --- a/src/main.c +++ b/src/main.c @@ -4,16 +4,20 @@ #include "bytenuts.h" +#ifndef __MINGW32__ static void sigint_handler(int s); +#endif int main(int argc, char **argv) { +#ifndef __MINGW32__ struct sigaction act; act.sa_handler = sigint_handler; sigemptyset(&act.sa_mask); act.sa_flags = 0; sigaction(SIGINT, &act, NULL); +#endif if (bytenuts_run(argc, argv)) { bytenuts_stop(); @@ -23,9 +27,11 @@ main(int argc, char **argv) exit(0); } +#ifndef __MINGW32__ static void sigint_handler(int s) { bytenuts_kill(); exit(1); } +#endif diff --git a/src/serial.c b/src/serial.c index 16d0016..1368584 100644 --- a/src/serial.c +++ b/src/serial.c @@ -3,15 +3,18 @@ #include #include #include +#include +#include #include "serial.h" #ifdef __MINGW32__ # include + serial_t serial_open(const char *comport, long bps) { - DCB dcb = { 0 }; + /* referred to: https://github.com/waynix/SPinGW/tree/master */ HANDLE serial = CreateFile( comport, GENERIC_READ | GENERIC_WRITE, @@ -21,15 +24,112 @@ serial_open(const char *comport, long bps) 0, 0 ); + DCB serial_params = { 0 }; + COMMTIMEOUTS timeouts = { 0 }; if (serial == INVALID_HANDLE_VALUE) { + return SERIAL_INVALID; + } + + serial_params.DCBlength = sizeof(serial_params); + if (!GetCommState(serial, &serial_params)) { + return SERIAL_INVALID; + } + + /* 8n1 */ + serial_params.BaudRate = bps; + serial_params.ByteSize = 8; + serial_params.StopBits = 1; + serial_params.Parity = NOPARITY; + + if (!SetCommState(serial, &serial_params)) { + return SERIAL_INVALID; + } + + timeouts.ReadIntervalTimeout = 0; + timeouts.ReadTotalTimeoutConstant = 0; + timeouts.ReadTotalTimeoutMultiplier = 0; + timeouts.WriteTotalTimeoutConstant = 0; + timeouts.WriteTotalTimeoutMultiplier = 0; + + if (!SetCommTimeouts(serial, &timeouts)) { + return SERIAL_INVALID; + } + + return serial; +} + +ssize_t +serial_read(serial_t serial, void *buf, size_t len) +{ + DWORD ret = 0; + + if (!ReadFile(serial, buf, len, &ret, NULL)) { return -1; } - /* TODO - * reference: https://github.com/waynix/SPinGW/tree/master */ - return serial; + return ret; } + +ssize_t +serial_read_to(serial_t serial, void *buf, size_t len, unsigned int to_ms) +{ + DWORD ret = -1; + size_t p = 0; + size_t len_left = len; + + while (to_ms > 0) { + DWORD tmp = 0; + + if (!ReadFile(serial, &((uint8_t *)buf)[p], len_left, &tmp, NULL)) { + return ret; + } + + if (tmp > 0) { + if (ret < 0) { + ret = tmp; + } else { + ret += tmp; + } + + len_left -= tmp; + p += tmp; + } + + if (len_left == 0) { + return ret; + } + + /* sleep 1ms */ + nanosleep(&(struct timespec){ 0, 1000000 }, NULL); + to_ms--; + } + + if (ret < 0) { + ret = 0; + } + + return ret; +} + +ssize_t +serial_write(serial_t serial, const void *buf, size_t len) +{ + DWORD ret = 0; + + if (!WriteFile(serial, buf, len, &ret, NULL)) { + return -1; + } + + return ret; +} + +int +serial_close(serial_t serial) +{ + return (CloseHandle(serial) != 0); +} + #else # include # include @@ -46,7 +146,7 @@ serial_open(const char *path, long bps) fd = open(path, O_RDWR | O_NOCTTY | O_NDELAY); if (fd == -1) { - return -1; + return SERIAL_INVALID; } tcgetattr(fd, &otio); @@ -88,25 +188,37 @@ serial_open(const char *path, long bps) return fd; } -int +ssize_t serial_read(serial_t serial, void *buf, size_t len) +{ + return serial_read_to(serial, buf, len, 0); +} + +ssize_t +serial_read_to(serial_t serial, void *buf, size_t len, unsigned int to_ms) { struct pollfd fds; fds.fd = serial; fds.events = POLLIN; fds.revents = 0; - if (poll(&fds, 1, 1) == 0) { + if (poll(&fds, 1, to_ms) == 0) { return 0; } - return (int)read(serial, buf, len); + return read(serial, buf, len); +} + +ssize_t +serial_write(serial_t serial, const void *buf, size_t len) +{ + return write(serial, buf, len); } int -serial_write(serial_t serial, void *buf, size_t len) +serial_close(serial_t serial) { - return (int)write(serial, buf, len); + return close(serial); } static speed_t diff --git a/src/serial.h b/src/serial.h index bcae708..eff7835 100644 --- a/src/serial.h +++ b/src/serial.h @@ -8,8 +8,24 @@ typedef HANDLE serial_t; typedef int serial_t; #endif +#define SERIAL_INVALID (serial_t)(-1) + +/* Open a serial port with the given speed and return a handle that can be used + * in the other serial APIs*/ serial_t serial_open(const char *path, long bps); -int serial_read(serial_t serial, void *buf, size_t len); -int serial_write(serial_t serial, void *buf, size_t len); + +/* Read up to len bytes (non-blocking), length read is returned */ +ssize_t serial_read(serial_t serial, void *buf, size_t len); + +/* Read up to len bytes, or until to_ms milliseconds have elapsed, length read + * is returned */ +ssize_t serial_read_to(serial_t serial, void *buf, size_t len, unsigned int to_ms); + +/* Write len bytes onto the serial port, actual number of bytes written is + * returned */ +ssize_t serial_write(serial_t serial, const void *buf, size_t len); + +/* Close the serial file */ +int serial_close(serial_t serial); #endif /* _SERIAL_H_ */ diff --git a/src/xmodem.c b/src/xmodem.c index 9bf850d..3752184 100644 --- a/src/xmodem.c +++ b/src/xmodem.c @@ -1,4 +1,3 @@ -#include #include #include #include @@ -8,7 +7,7 @@ #include "xmodem.h" -int _xmodem_wait(int fd, uint8_t *ret, int timeout_ms); +int _xmodem_wait(serial_t fd, uint8_t *ret, int timeout_ms); uint16_t _xmodem_crc(const uint8_t *buf, size_t len); uint8_t _xmodem_csum(const uint8_t *buf, size_t len); @@ -16,7 +15,7 @@ void _xmodem_status_print(size_t sent, size_t total); int xmodem_send( - int dest_fd, + serial_t dest_fd, int in_fd, size_t sz, int block_sz, @@ -106,7 +105,7 @@ xmodem_send( goto end_of_transmission; } - if (write(dest_fd, buf, buf_sz) != buf_sz){ + if (serial_write(dest_fd, buf, buf_sz) != buf_sz){ return XMODEM_ERR_WRITE; } @@ -138,7 +137,7 @@ end_of_transmission: return XMODEM_ERR_TIMEOUT; } - write(dest_fd, &(uint8_t){XMODEM_EOT}, 1); + serial_write(dest_fd, &(uint8_t){XMODEM_EOT}, 1); if ( !_xmodem_wait(dest_fd, &ack_char, XMODEM_TIMEOUT) && @@ -154,20 +153,12 @@ end_of_transmission: } int -_xmodem_wait(int fd, uint8_t *ret, int timeout_ms) +_xmodem_wait(serial_t fd, uint8_t *ret, int timeout_ms) { - struct pollfd pfd; - - pfd.fd = fd; - pfd.events = POLLIN; - pfd.revents = 0; - - if (poll(&pfd, 1, timeout_ms) <= 0) { + if (serial_read_to(fd, ret, 1, timeout_ms) != 1) { return -1; } - read(fd, ret, 1); - return 0; } diff --git a/src/xmodem.h b/src/xmodem.h index ac65b02..c9a44ca 100644 --- a/src/xmodem.h +++ b/src/xmodem.h @@ -30,7 +30,7 @@ * length of `block_sz`. */ int xmodem_send( - int dest_fd, + serial_t dest_fd, int in_fd, size_t sz, int block_sz, From 122862b3c10c2db2ddc3d5120dccd341dc931451 Mon Sep 17 00:00:00 2001 From: Aaron Cook Date: Thu, 7 Sep 2023 16:15:07 -0500 Subject: [PATCH 23/33] Update readme with application screenshot --- README.md | 4 ++++ Screenshot.png | Bin 0 -> 9284 bytes 2 files changed, 4 insertions(+) create mode 100644 Screenshot.png diff --git a/README.md b/README.md index a028776..d516ae4 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,10 @@ Bytenuts has been tested and built on x86-64 and 32-bit ARM Ubuntu 18. - Quick commands - Pages of quick commands are loaded from `~/.config/bytenuts/commands[1-10]` - Session resumption - Bytenuts can load the previous instance's commands and serial output +Sample screenshot running in Windows Terminal and WSL: + +![Bytenuts Screenshot](Screenshot.png) + ## Launch Options Launching Bytenuts like `bytenuts -h` provides this help print: diff --git a/Screenshot.png b/Screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..1b9c0a26a570ccb86b0f7a5fb6fe85e3757c1149 GIT binary patch literal 9284 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 Mr>mdKI;Vst0Q27ln*aa+ literal 0 HcmV?d00001 From 34b1dc2c867c89e7294215584a94006d2f7cdf56 Mon Sep 17 00:00:00 2001 From: Aaron Cook Date: Thu, 26 Oct 2023 01:55:20 -0500 Subject: [PATCH 24/33] Implement untested string and path helpers for easy cross compatability --- src/bstr.c | 37 ++++++++++++++++++++ src/bstr.h | 12 +++++++ src/paths.c | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/paths.h | 20 +++++++++++ 4 files changed, 168 insertions(+) create mode 100644 src/bstr.c create mode 100644 src/bstr.h create mode 100644 src/paths.c create mode 100644 src/paths.h diff --git a/src/bstr.c b/src/bstr.c new file mode 100644 index 0000000..30a70eb --- /dev/null +++ b/src/bstr.c @@ -0,0 +1,37 @@ +#include +#include +#include +#include + +#include "bstr.h" + +char * +bstr_print(char *base, const char *fmt, ...) +{ + char *new_buf; + size_t new_len; + va_list ap; + + va_start(ap, fmt); + new_len = vsnprintf(NULL, 0, fmt, ap); + va_end(ap); + + new_buf = calloc(1, new_len + 1); + + va_start(ap, fmt); + vsprintf(new_buf, fmt, ap); + va_end(ap); + + if (!base) { + return new_buf; + } else { + size_t baselen = strlen(base); + + base = realloc(base, baselen + new_len + 1); + memcpy(&base[baselen], new_buf, new_len); + base[baselen + new_len] = '\0'; + + free(new_buf); + return base; + } +} diff --git a/src/bstr.h b/src/bstr.h new file mode 100644 index 0000000..c442da3 --- /dev/null +++ b/src/bstr.h @@ -0,0 +1,12 @@ +#ifndef _BSTR_H_ +#define _BSTR_H_ + +#include +#include + +/* printf onto the end of base, reallocating and returning that buffer + * can provide NULL for no base */ +__attribute__((format(printf, 2, 3))) +char *bstr_print(char *base, const char *fmt, ...); + +#endif /* _BSTR_H_ */ diff --git a/src/paths.c b/src/paths.c new file mode 100644 index 0000000..7c4de57 --- /dev/null +++ b/src/paths.c @@ -0,0 +1,99 @@ +#include +#include +#include +#include + +#include "bstr.h" +#include "paths.h" + +#if __MINGW32__ +# define BSTR_LLD "%I64d" +#else +# define BSTR_LLD "%lld" +#endif + +char * +paths_append(char *base, const char *suffix) +{ + size_t baselen; + const char *sep = ""; + + if (!base) + return strdup(suffix); + + baselen = strlen(base); + + if ( + (baselen == 0) || + ((base[baselen-1] != '/') && (base[baselen-1] == '\\')) + ) { + /* no separator at end of path */ +#if __MINGW32__ + sep = "\\"; +#else + sep = "/"; +#endif + } + + return bstr_print(base, "%s%s", sep, suffix); +} + +char * +paths_bnconf_dir() +{ +#ifdef __MINGW32__ + char *home = getenv("HOMEPATH"); +#else + char *home = getenv("HOME"); +#endif + char *ret = NULL; + + if (!home) + return NULL; + +#ifdef __MINGW32__ + ret = strdup("C:\\"); +#endif + ret = paths_append(ret, home); + ret = paths_append(ret, ".config/bytenuts"); + + /* TODO free environment variable on Windows? */ + return ret; +} + +char * +paths_bnconf_default() +{ + char *ret = paths_bnconf_dir(); + + if (!ret) + return NULL; + + return paths_append(ret, "config"); +} + +char * +paths_command_file(int idx) +{ + char *ret = paths_bnconf_dir(); + + if (!ret) + return NULL; + + return bstr_print(ret, "/commands%d", idx); +} + +char * +paths_logfile(const char *prefix, long long pid) +{ + char *ret = paths_bnconf_dir(); + + if (!ret) + return NULL; + + if (pid > 0) { + return bstr_print(ret, "/%s." BSTR_LLD ".log", prefix, pid); + } else { + return bstr_print(ret, "/%s.log", prefix); + } +} diff --git a/src/paths.h b/src/paths.h new file mode 100644 index 0000000..179fafe --- /dev/null +++ b/src/paths.h @@ -0,0 +1,20 @@ +#ifndef _PATHS_H_ +#define _PATHS_H_ + +/* Realloc the base path and add the suffix to it, "/"s handled nicely */ +char *paths_append(char *base, const char *suffix); + +/* Get the ~/.config/bytenuts directory */ +char *paths_bnconf_dir(); + +/* Get the default bytenuts config filepath */ +char *paths_bnconf_default(); + +/* Generate the path to the command file */ +char *paths_command_file(int idx); + +/* Generate a logfile path of the form ~/.config/bytenuts/[.].log + * PID only inserted if >0 */ +char *paths_logfile(const char *prefix, long long pid); + +#endif /* _PATHS_H_ */ From 67c6ee8f2dde58445f968c05fa3a6eced7376a39 Mon Sep 17 00:00:00 2001 From: Aaron Cook Date: Tue, 7 Nov 2023 19:47:24 -0600 Subject: [PATCH 25/33] Remove .gitignore definition to fix later --- .gitignore | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .gitignore diff --git a/.gitignore b/.gitignore deleted file mode 100644 index ecbd5a1..0000000 --- a/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -build/ -.vscode/ -.vim/ -*.log -tags From a3626d7a6fc89d37ab3a55b3f87b0fb82665e3fc Mon Sep 17 00:00:00 2001 From: Aaron Cook Date: Tue, 7 Nov 2023 19:51:08 -0600 Subject: [PATCH 26/33] Re-add PDCurses with submodule path set --- lib/PDCurses | 1 + 1 file changed, 1 insertion(+) create mode 160000 lib/PDCurses diff --git a/lib/PDCurses b/lib/PDCurses new file mode 160000 index 0000000..5c62af0 --- /dev/null +++ b/lib/PDCurses @@ -0,0 +1 @@ +Subproject commit 5c62af03e9a05e3a3ae8c8354c1234b772dcf4b0 From 6de7b319958298ce94e3518cc5f126bb70dac10f Mon Sep 17 00:00:00 2001 From: Aaron Cook Date: Tue, 7 Nov 2023 19:53:35 -0600 Subject: [PATCH 27/33] Accidentally remove gitignore not gitmodules --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..662296d --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +build/ +.vscode/ +.vim/ +*.log +tags \ No newline at end of file From c274a55c0eb63bd01ccc9bcbe9fb1bbf4632076c Mon Sep 17 00:00:00 2001 From: Aaron Cook Date: Tue, 30 Apr 2024 16:20:38 -0500 Subject: [PATCH 28/33] #17 Add feature to prepend timestamp to output logfile lines --- README.md | 7 ++++++- src/bytenuts.c | 25 ++++++++++++++++++++++++- src/bytenuts.h | 6 +++++- src/cheerios.c | 17 +++++++++++++++++ 4 files changed, 52 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d516ae4..a0c3a82 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,9 @@ Configs get loaded from ${HOME}/.bytenuts/config (if file exists) --inter_cmd_to= Set the intercommand timeout in milliseconds (default is 10ms). + +--time_fmt= + Time format as used by strftime to prepend to every log line. ``` ## Navigation @@ -85,7 +88,7 @@ If the output window reaches the current line of output, then the output will co ## Configuration File -There are only three supported configs currently. Each config is defined like `=`. Here is a sample config: +Many of the launch options have a corresponding configuration token that can be saved to a config file. Each config is defined like `=`. Here is a sample config: ``` colors=1 @@ -93,6 +96,7 @@ echo=0 no_crlf=0 escape=a inter_cmd_to=100 +time_fmt=%X %m/%d %Z|> ``` - `colors` - enable parsing of 8-bit ANSI color codes @@ -100,6 +104,7 @@ inter_cmd_to=100 - `no_crlf` - just send a line feed (`\n`) for user input rather than carriage return + line feed (`\r\n`) - `escape` - change what character is used as an escape sequence for commands (e.g. if set to `escape=a`, Bytenuts can be exited with `ctrl+a, q`) - `inter_cmd_to` - Set a timeout in milliseconds that must be met. Useful for pasting in multiple lines and ensuring a short delay in between the commands. +- `time_fmt` - The time format string (see `man 3 strftime`) to be prepended to every line in the log file (will not get printed in the console view) Bytenuts looks for the configs at `~/.config/bytenuts/config`. diff --git a/src/bytenuts.c b/src/bytenuts.c index ff6a585..7d8af20 100644 --- a/src/bytenuts.c +++ b/src/bytenuts.c @@ -29,7 +29,8 @@ "--echo=<0|1>\n Turn input echoing off/on.\n\n" \ "--no_crlf=<0|1>\n Choose to send LF and not CRLF on input.\n\n" \ "--escape=\n Change the default ctrl+b escape character.\n\n" \ -"--inter_cmd_to=\n Set the intercommand timeout in milliseconds (default is 10ms).\n" \ +"--inter_cmd_to=\n Set the intercommand timeout in milliseconds (default is 10ms).\n\n" \ +"--time_fmt=\n Time format as used by strftime to prepend to every log line.\n" \ ) static int parse_args(int argc, char **argv); @@ -191,6 +192,8 @@ bytenuts_print_stats() cheerios_insert(st_line, strlen(st_line)); sprintf(st_line, "inter_cmd_to: %d\r\n", bytenuts.config.inter_cmd_to); cheerios_insert(st_line, strlen(st_line)); + sprintf(st_line, "time_fmt: %s\r\n", bytenuts.config.time_fmt); + cheerios_insert(st_line, strlen(st_line)); return 0; } @@ -385,6 +388,10 @@ parse_args(int argc, char **argv) bytenuts.config_overrides[4] = 1; } } + else if (arg_len > 11 && !memcmp(argv[i], "--time_fmt=", 11)) { + bytenuts.config.time_fmt = strdup(&argv[i][11]); + bytenuts.config_overrides[5] = 1; + } else if (!strcmp(argv[i], "--resume") || !strcmp(argv[i], "-r")) { bytenuts.resume = 1; } @@ -438,6 +445,22 @@ load_configs() bytenuts.config.inter_cmd_to = cmd_to; } } + else if (!bytenuts.config_overrides[5] && !memcmp(line, "time_fmt=", 9)) { + size_t time_fmt_len; + bytenuts.config.time_fmt = strdup(&line[9]); + + time_fmt_len = strlen(bytenuts.config.time_fmt); + while ( + time_fmt_len > 0 && + ( + bytenuts.config.time_fmt[time_fmt_len-1] == '\r' || + bytenuts.config.time_fmt[time_fmt_len-1] == '\n' + ) + ) { + bytenuts.config.time_fmt[time_fmt_len-1] = 0; + time_fmt_len--; + } + } } return 0; diff --git a/src/bytenuts.h b/src/bytenuts.h index 6198db5..0bb0ce3 100644 --- a/src/bytenuts.h +++ b/src/bytenuts.h @@ -33,6 +33,9 @@ typedef struct bytenuts_config_struct { char *log_path; /* path to the log file (if it exists) */ char *serial_path; /* path to the target serial device */ uint32_t inter_cmd_to; /* inter command timeout in ms */ + /* time format to be prepended to all log lines in the output file only, + * NULL for no time prepended */ + char *time_fmt; } bytenuts_config_t; #define CONFIG_DEFAULT (bytenuts_config_t){ \ @@ -45,12 +48,13 @@ typedef struct bytenuts_config_struct { .log_path = NULL, \ .serial_path = NULL, \ .inter_cmd_to = 10, \ + .time_fmt = NULL, \ } typedef struct bytenuts_struct { serial_t serial_fd; bytenuts_config_t config; - int config_overrides[5]; + int config_overrides[6]; int resume; bytenuts_state_t state; WINDOW *status_win; diff --git a/src/cheerios.c b/src/cheerios.c index 9c25d7c..40f1eff 100644 --- a/src/cheerios.c +++ b/src/cheerios.c @@ -667,5 +667,22 @@ newline(line_buffer_t *lines) lines->line_lens = realloc(lines->line_lens, sizeof(int) * lines->n_lines); lines->line_lens[lines->n_lines - 1] = 0; lines->pos = 0; + + if (cheerios.config->time_fmt && (cheerios.log || cheerios.backup)) { + char tstr[128]; + time_t now; + struct tm *tinfo; + size_t tstr_len; + + time(&now); + tinfo = localtime(&now); + tstr_len = strftime(tstr, 128, cheerios.config->time_fmt, tinfo); + + if (cheerios.log) + fwrite(tstr, 1, tstr_len, cheerios.log); + if (cheerios.backup) + fwrite(tstr, 1, tstr_len, cheerios.backup); + } + return 0; } From 0ecb1bab0cb4f2a7a5be5e306ff4f2f820ad3326 Mon Sep 17 00:00:00 2001 From: Aaron Coook Date: Mon, 13 May 2024 00:10:01 -0500 Subject: [PATCH 29/33] Add the bstr history APIs Trying out an opaque handle style --- src/bstr.c | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/bstr.h | 28 +++++++++++++++++++ 2 files changed, 108 insertions(+) diff --git a/src/bstr.c b/src/bstr.c index 30a70eb..b508ad6 100644 --- a/src/bstr.c +++ b/src/bstr.c @@ -5,6 +5,12 @@ #include "bstr.h" +typedef struct bstr_history_struct { + char **strs; /* strings in our history, in reverse chronological order */ + int len; /* number of strings in the history */ + int pos; /* current position index in the history */ +} bstr_history_t; + char * bstr_print(char *base, const char *fmt, ...) { @@ -35,3 +41,77 @@ bstr_print(char *base, const char *fmt, ...) return base; } } + +bstr_history_handle +bstr_history_create(void) +{ + bstr_history_t *ret = calloc(1, sizeof(bstr_history_t)); + ret->pos = -1; + return ret; +} + +const char * +bstr_history_atpos(bstr_history_handle hist) +{ + if ((hist->len == 0) || (hist->pos < 0)) { + return NULL; + } else { + return hist->strs[hist->pos]; + } +} + +void +bstr_history_new_entry(bstr_history_handle hist, const char *str) +{ + /* stored in reverse chronological order */ + hist->len++; + hist->strs = realloc(hist->strs, sizeof(char *) * hist->len); + hist->strs[hist->len-1] = strdup(str); +} + +void +bstr_history_older(bstr_history_handle hist) +{ + if (hist->len == 0) + return; + + if (hist->pos == -1) { + hist->pos = hist->len - 1; + } else { + hist->pos--; + } + + if (hist->pos < 0) + hist->pos = 0; +} + +void +bstr_history_newer(bstr_history_handle hist) +{ + if ((hist->len == 0) || (hist->pos == -1)) + return; + + hist->pos++; + if (hist->pos >= hist->len) + hist->pos = -1; +} + +void +bstr_history_unset_pos(bstr_history_handle hist) +{ + hist->pos = -1; +} + +void +bstr_history_destroy(bstr_history_handle hist) +{ + if (!hist) + return; + + if (hist->len > 0) { + for (int i = 0; i < hist->len; i++) { + free(hist->strs[i]); + } + free(hist->strs); + } +} diff --git a/src/bstr.h b/src/bstr.h index c442da3..b87585f 100644 --- a/src/bstr.h +++ b/src/bstr.h @@ -4,9 +4,37 @@ #include #include +typedef struct bstr_history_struct * bstr_history_handle; + + /* printf onto the end of base, reallocating and returning that buffer * can provide NULL for no base */ __attribute__((format(printf, 2, 3))) char *bstr_print(char *base, const char *fmt, ...); +/* Create a new bstr_history struct. The position of the history will start as + * unset. */ +bstr_history_handle bstr_history_create(void); + +/* return the string at the current history position (NULL if empty or position + * is not set) */ +const char *bstr_history_atpos(bstr_history_handle hist); + +/* Put a new entry into the history */ +void bstr_history_new_entry(bstr_history_handle hist, const char *str); + +/* Move the postition index one entry older. If the history position is unset, + * it is set to the newest entry. */ +void bstr_history_older(bstr_history_handle hist); + +/* Move the position index one entry newer. If the position is at the newest + * entry, it becomes unset. If the position is unset, it stays unset. */ +void bstr_history_newer(bstr_history_handle hist); + +/* Unset the history position index */ +void bstr_history_unset_pos(bstr_history_handle hist); + +/* destroy/free a bstr_history */ +void bstr_history_destroy(bstr_history_handle hist); + #endif /* _BSTR_H_ */ From 64f8a0c54f6ed5fc1f6178202a6e6a95c60819e2 Mon Sep 17 00:00:00 2001 From: Aaron Coook Date: Mon, 13 May 2024 00:10:19 -0500 Subject: [PATCH 30/33] Implement xmodem file history --- src/ingest.c | 35 +++++++++++++++++++++++++++++++++++ src/ingest.h | 2 ++ 2 files changed, 37 insertions(+) diff --git a/src/ingest.c b/src/ingest.c index 6203077..8ec9deb 100644 --- a/src/ingest.c +++ b/src/ingest.c @@ -44,6 +44,7 @@ ingest_start(bytenuts_t *bytenuts) ingest.term_lock = &bytenuts->term_lock; ingest.config = &bytenuts->config; ingest.cmd_pg_cur = -1; + ingest.xmodem_hist = bstr_history_create(); HOME = getenv("HOME"); if (HOME) { @@ -459,6 +460,8 @@ ingest_thread(void *arg) free(ingest.history_filename); } + bstr_history_destroy(ingest.xmodem_hist); + pthread_exit(NULL); return NULL; } @@ -600,6 +603,8 @@ mode_xmodem(int block_sz) switch (ch) { case '\n': + bstr_history_new_entry(ingest.xmodem_hist, ingest.inbuf); + pthread_mutex_lock(ingest.term_lock); werase(ingest.input); wmove(ingest.input, 0, 0); @@ -618,6 +623,34 @@ mode_xmodem(int block_sz) auto_complete(); ingest_refresh(); break; + case KEY_BACKSPACE: + case KEY_UP: + /* fall-through */ + case KEY_DOWN: + { + const char *filename; + + if (ch == KEY_UP) { + bstr_history_older(ingest.xmodem_hist); + } else { + bstr_history_newer(ingest.xmodem_hist); + } + + filename = bstr_history_atpos(ingest.xmodem_hist); + if (!filename) { + memset(ingest.inbuf, 0, sizeof(ingest.inbuf)); + ingest.inpos = 0; + ingest.inlen = 0; + ingest_refresh(); + break; + } + + strncpy(ingest.inbuf, filename, sizeof(ingest.inbuf)); + ingest.inpos = strlen(ingest.inbuf); + ingest.inlen = ingest.inpos; + ingest_refresh(); + break; + } default: { char tmp[1024]; @@ -653,6 +686,8 @@ mode_xmodem(int block_sz) free(ingest.prepend); ingest.prepend = NULL; + bstr_history_unset_pos(ingest.xmodem_hist); + bytenuts_set_status(STATUS_INGEST, "normal"); ingest_refresh(); return 0; diff --git a/src/ingest.h b/src/ingest.h index 95a2f8c..bb64216 100644 --- a/src/ingest.h +++ b/src/ingest.h @@ -10,6 +10,7 @@ #include #include +#include "bstr.h" #include "bytenuts.h" enum ingest_mode_enum { @@ -46,6 +47,7 @@ typedef struct ingest_struct { /* what command page is currently selected */ int cmd_pg_cur; struct timespec cmd_ts; /* last command sent timestamp */ + bstr_history_handle xmodem_hist; /* xmodem filename transfer history */ } ingest_t; /* startup the input window thread */ From a50fa90d02119084db451f4df3957ddbf70b62b6 Mon Sep 17 00:00:00 2001 From: Aaron Cook Date: Fri, 3 Oct 2025 21:31:22 -0500 Subject: [PATCH 31/33] Add License --- LICENSE | 201 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..abf5af7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2025 Aaron Cook + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. From 731b014397003ddbe07d531e5cef3e52d6e7f38a Mon Sep 17 00:00:00 2001 From: Aaron Cook Date: Thu, 15 Oct 2026 16:19:25 -0500 Subject: [PATCH 32/33] ingest: Add macros for ctrl + up/down I don't think ncurses has macros for these, but the magic numbers I have in there seem to work. So add macros for those that conform to the rest of the ncurses KEY_* naming convention. --- src/ingest.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/ingest.c b/src/ingest.c index 8ec9deb..3679b2e 100644 --- a/src/ingest.c +++ b/src/ingest.c @@ -33,6 +33,13 @@ static int read_cmd_page(const char *home_dir, int idx); static void update_cmd_pg_status(void); static void inter_command_wait(void); +#ifndef KEY_CUP +# define KEY_CUP (566) +#endif +#ifndef KEY_CDOWN +# define KEY_CDOWN (525) +#endif + int ingest_start(bytenuts_t *bytenuts) { @@ -801,10 +808,10 @@ handle_functions(int ch) case KEY_SEND: /* go to front of log */ cheerios_gofwd(-1); return 1; - case 566: /* ctrl + up arrow */ + case KEY_CUP: /* ctrl + up arrow */ cheerios_goback(1); return 1; - case 525: /* ctrl + down arrow */ + case KEY_CDOWN: /* ctrl + down arrow */ cheerios_gofwd(1); return 1; case KEY_RESIZE: From 0bf4610c02ead6abbcf8e5d27954868581d07562 Mon Sep 17 00:00:00 2001 From: Aaron Cook Date: Thu, 15 Oct 2026 16:22:29 -0500 Subject: [PATCH 33/33] ingest: Define ncurses key maps for ctrl/shift shortcuts This ensure that sequences like ctrl+up or shift+end work regardless of the terminal settings. Tested with TMUX and foot. --- src/ingest.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ingest.c b/src/ingest.c index 3679b2e..e417ee2 100644 --- a/src/ingest.c +++ b/src/ingest.c @@ -53,6 +53,12 @@ ingest_start(bytenuts_t *bytenuts) ingest.cmd_pg_cur = -1; ingest.xmodem_hist = bstr_history_create(); + /* ensure character sequences work in all terminals */ + define_key("\033[1;5A", KEY_CUP); + define_key("\033[1;5B", KEY_CDOWN); + define_key("\033[1;2H", KEY_SHOME); + define_key("\033[1;2F", KEY_SEND); + HOME = getenv("HOME"); if (HOME) { int idx = 0;