diff --git a/.gitignore b/.gitignore index ecbd5a1..662296d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,4 @@ build/ .vscode/ .vim/ *.log -tags +tags \ No newline at end of file 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/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. diff --git a/Makefile b/Makefile index 2c57a52..02282fd 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,30 @@ -CC=clang -CFLAGS=-Wall -MMD -pthread -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=199309L -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) @@ -16,11 +33,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) @@ -30,16 +43,19 @@ 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 $@) @echo "compile $<" @$(CC) $(CFLAGS) -c $< -o $@ +PDCurses: + cd lib/PDCurses/wincon && $(MAKE) -j CC=x86_64-w64-mingw32-gcc + clean: rm -rf $(DIR_BUILD) diff --git a/README.md b/README.md index a028776..a0c3a82 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: @@ -67,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 @@ -81,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 @@ -89,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 @@ -96,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/Screenshot.png b/Screenshot.png new file mode 100644 index 0000000..1b9c0a2 Binary files /dev/null and b/Screenshot.png differ 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 diff --git a/src/bstr.c b/src/bstr.c new file mode 100644 index 0000000..b508ad6 --- /dev/null +++ b/src/bstr.c @@ -0,0 +1,117 @@ +#include +#include +#include +#include + +#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, ...) +{ + 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; + } +} + +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 new file mode 100644 index 0000000..b87585f --- /dev/null +++ b/src/bstr.h @@ -0,0 +1,40 @@ +#ifndef _BSTR_H_ +#define _BSTR_H_ + +#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_ */ diff --git a/src/bytenuts.c b/src/bytenuts.c index a5c65a1..7d8af20 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" @@ -26,15 +29,14 @@ "--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); 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; @@ -57,15 +59,23 @@ 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")) { grantpt(bytenuts.serial_fd); unlockpt(bytenuts.serial_fd); } +#endif // Initialize ncurses initscr(); @@ -108,11 +118,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(); @@ -154,7 +166,7 @@ bytenuts_kill() delwin(bytenuts.out_win); endwin(); - close(bytenuts.serial_fd); + serial_close(bytenuts.serial_fd); } int @@ -170,7 +182,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)); @@ -180,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; } @@ -299,10 +313,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++) { @@ -316,7 +337,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++; @@ -367,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; } @@ -420,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; @@ -498,137 +539,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..0bb0ce3 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,11 +28,14 @@ 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 */ 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){ \ @@ -36,17 +43,18 @@ 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, \ .inter_cmd_to = 10, \ + .time_fmt = NULL, \ } typedef struct bytenuts_struct { - int serial_fd; + 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 6d27b0e..40f1eff 100644 --- a/src/cheerios.c +++ b/src/cheerios.c @@ -1,5 +1,8 @@ -#include -#include +#ifdef __MINGW32__ +# include +#else +# include +#endif #include #include #include @@ -50,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"); } @@ -157,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; } @@ -216,17 +232,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)); @@ -290,6 +307,7 @@ cheerios_getmaxy() { int y, x; getmaxyx(cheerios.output, y, x); + (void)x; return y; } @@ -298,6 +316,7 @@ cheerios_getmaxx() { int y, x; getmaxyx(cheerios.output, y, x); + (void)y; return x; } @@ -320,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); @@ -329,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); } @@ -659,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; } 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..e417ee2 100644 --- a/src/ingest.c +++ b/src/ingest.c @@ -1,5 +1,9 @@ #include -#include +#ifdef __MINGW32__ +# include +#else +# include +#endif #include #include #include @@ -29,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) { @@ -40,6 +51,13 @@ 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(); + + /* 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) { @@ -55,14 +73,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"); @@ -205,7 +223,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)); @@ -214,8 +231,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); @@ -458,6 +473,8 @@ ingest_thread(void *arg) free(ingest.history_filename); } + bstr_history_destroy(ingest.xmodem_hist); + pthread_exit(NULL); return NULL; } @@ -599,6 +616,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); @@ -617,6 +636,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]; @@ -652,6 +699,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; @@ -765,10 +814,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: diff --git a/src/ingest.h b/src/ingest.h index e8e4474..bb64216 100644 --- a/src/ingest.h +++ b/src/ingest.h @@ -1,11 +1,16 @@ #ifndef _INGEST_H_ #define _INGEST_H_ -#include +#ifdef __MINGW32__ +# include +#else +# include +#endif #include #include #include +#include "bstr.h" #include "bytenuts.h" enum ingest_mode_enum { @@ -42,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 */ 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/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_ */ diff --git a/src/serial.c b/src/serial.c index 364fcbe..1368584 100644 --- a/src/serial.c +++ b/src/serial.c @@ -2,28 +2,158 @@ #include #include #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) { + /* referred to: https://github.com/waynix/SPinGW/tree/master */ + HANDLE serial = CreateFile( + comport, + GENERIC_READ | GENERIC_WRITE, + 0, + 0, + OPEN_EXISTING, + 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; + } + + 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 + +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; fd = open(path, O_RDWR | O_NOCTTY | O_NDELAY); if (fd == -1) { - return -1; + return SERIAL_INVALID; } 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 +187,107 @@ serial_open(const char *path, speed_t bps) return fd; } + +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, to_ms) == 0) { + return 0; + } + + 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_close(serial_t serial) +{ + return close(serial); +} + +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..eff7835 100644 --- a/src/serial.h +++ b/src/serial.h @@ -1,8 +1,31 @@ #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); +#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); + +/* 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 8edc8e9..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,11 +15,11 @@ 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, - 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 +29,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; @@ -105,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; } @@ -120,11 +120,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: @@ -136,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) && @@ -152,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 ecc0092..c9a44ca 100644 --- a/src/xmodem.h +++ b/src/xmodem.h @@ -30,11 +30,11 @@ * length of `block_sz`. */ int xmodem_send( - int dest_fd, + serial_t dest_fd, 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