Public bytenuts repository creation

This commit is contained in:
Aaron Cook 2022-02-06 16:50:55 -06:00
commit aaaf6cb805
14 changed files with 2576 additions and 0 deletions

518
src/bytenuts.c Normal file
View file

@ -0,0 +1,518 @@
#include <ncurses.h>
#include <pthread.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <termios.h>
#include <unistd.h>
#include "bytenuts.h"
#include "cheerios.h"
#include "ingest.h"
#define USAGE ( \
"USAGE\n\n" \
"bytenuts [OPTIONS] <serial path>\n" \
"\nConfigs get loaded from ${HOME}/.bytenuts/config (if file exists)\n" \
"\nOPTIONS\n\n" \
"-h: show this help\n" \
"-b <baud>: set a baud rate (default 115200)\n" \
"-l <path>: log all output to the given file\n" \
"-c <path>: load a config from the given path rather than the default\n" \
"--colors=<0|1>: turn 8-bit ANSI colors off/on\n" \
"--echo=<0|1>: turn input echoing off/on\n" \
"--no_crlf=<0|1>: choose to send LF and not CRLF on input\n" \
"--escape=<char>: change the default ctrl+b escape character\n" \
)
static int parse_args(int argc, char **argv);
static int load_configs();
static speed_t string_to_speed(const char *speed);
static const char *speed_to_string(speed_t speed);
static bytenuts_t bytenuts;
int
bytenuts_run(int argc, char **argv)
{
memset(&bytenuts, 0, sizeof(bytenuts_t));
if (parse_args(argc, argv)) {
printf(USAGE);
return -1;
}
if (load_configs()) {
return -1;
}
bytenuts.serial_fd = serial_open(bytenuts.config.serial_path, bytenuts.config.baud);
if (bytenuts.serial_fd < 0) {
return -1;
}
/* use pseudo-terminals for testing purposes */
if (!strcmp(bytenuts.config.serial_path, "/dev/ptmx")) {
grantpt(bytenuts.serial_fd);
unlockpt(bytenuts.serial_fd);
}
// Initialize ncurses
initscr();
raw();
noecho();
#if 0
mousemask(BUTTON4_PRESSED | BUTTON5_PRESSED, NULL);
#endif
// setup status, output, and input windows
bytenuts.status_win = newwin(1, COLS, LINES - 2, 0);
wmove(bytenuts.status_win, 0, 0);
wrefresh(bytenuts.status_win);
bytenuts.out_win = newwin(LINES - 2, COLS, 0, 0);
wmove(bytenuts.out_win, 0, 0);
wrefresh(bytenuts.out_win);
bytenuts.in_win = newwin(1, COLS, LINES - 1, 0);
keypad(bytenuts.in_win, TRUE);
wtimeout(bytenuts.in_win, 1);
wmove(bytenuts.in_win, 0, 0);
wrefresh(bytenuts.in_win);
bytenuts.ingest_status = strdup("");
bytenuts.cheerios_status = strdup("");
bytenuts_set_status(STATUS_BYTENUTS, "%s", bytenuts.config.serial_path);
// Initialize in and out threads
if (pthread_mutex_init(&bytenuts.lock, NULL)) {
return -1;
}
if (pthread_mutex_init(&bytenuts.term_lock, NULL)) {
return -1;
}
if (pthread_cond_init(&bytenuts.stop_cond, NULL)) {
return -1;
}
cheerios_start(&bytenuts);
ingest_start(&bytenuts);
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);
}
pthread_mutex_lock(&bytenuts.lock);
pthread_cond_wait(&bytenuts.stop_cond, &bytenuts.lock);
pthread_mutex_unlock(&bytenuts.lock);
bytenuts_kill();
return 0;
}
int
bytenuts_stop()
{
pthread_mutex_lock(&bytenuts.lock);
pthread_cond_signal(&bytenuts.stop_cond);
pthread_mutex_unlock(&bytenuts.lock);
return 0;
}
void
bytenuts_kill()
{
ingest_stop();
cheerios_stop();
delwin(bytenuts.status_win);
delwin(bytenuts.in_win);
delwin(bytenuts.out_win);
endwin();
close(bytenuts.serial_fd);
}
int
bytenuts_print_stats()
{
char st_line[128];
sprintf(st_line, "colors: %s\r\n", bytenuts.config.colors ? "enabled" : "disabled");
cheerios_insert(st_line, strlen(st_line));
sprintf(st_line, "echo: %s\r\n", bytenuts.config.echo ? "enabled" : "disabled");
cheerios_insert(st_line, strlen(st_line));
sprintf(st_line, "no_crlf: %s\r\n", bytenuts.config.no_crlf ? "enabled" : "disabled");
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));
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));
sprintf(st_line, "log_path: %s\r\n", bytenuts.config.log_path);
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));
return 0;
}
int
bytenuts_set_status(int user, const char *fmt, ...)
{
int cx, cy;
char *new_status;
int len;
va_list ap;
va_start(ap, fmt);
len = vsnprintf(NULL, 0, fmt, ap);
va_end(ap);
new_status = calloc(1, len + 1);
va_start(ap, fmt);
vsnprintf(new_status, len+1, fmt, ap);
va_end(ap);
pthread_mutex_lock(&bytenuts.lock);
switch (user) {
case STATUS_BYTENUTS:
if (bytenuts.bytenuts_status)
free(bytenuts.bytenuts_status);
bytenuts.bytenuts_status = new_status;
break;
case STATUS_INGEST:
if (bytenuts.ingest_status)
free(bytenuts.ingest_status);
bytenuts.ingest_status = new_status;
break;
case STATUS_CHEERIOS:
if (bytenuts.cheerios_status)
free(bytenuts.cheerios_status);
bytenuts.cheerios_status = new_status;
break;
case STATUS_CMDPAGE:
if (bytenuts.cmdpg_status)
free(bytenuts.cmdpg_status);
bytenuts.cmdpg_status = new_status;
break;
default:
free(new_status);
pthread_mutex_unlock(&bytenuts.lock);
return 0;
}
pthread_mutex_unlock(&bytenuts.lock);
pthread_mutex_lock(&bytenuts.term_lock);
getyx(bytenuts.in_win, cy, cx);
curs_set(0);
wmove(bytenuts.status_win, 0, 0);
int width = getmaxx(bytenuts.status_win);
for (int i = 0; i < width - 1; i++) {
waddch(bytenuts.status_win, '-');
}
waddch(bytenuts.status_win, '|');
wmove(bytenuts.status_win, 0, 0);
wprintw(
bytenuts.status_win, "|--%s--|--%s--|--%s--|--%s--|",
bytenuts.bytenuts_status, bytenuts.ingest_status,
bytenuts.cheerios_status, bytenuts.cmdpg_status
);
wmove(bytenuts.in_win, cy, cx);
curs_set(1);
wrefresh(bytenuts.status_win);
refresh();
pthread_mutex_unlock(&bytenuts.term_lock);
return 0;
}
int
bytenuts_update_screen_size()
{
pthread_mutex_lock(&bytenuts.term_lock);
wresize(bytenuts.status_win, 1, COLS);
mvwin(bytenuts.status_win, LINES - 2, 0);
wrefresh(bytenuts.status_win);
wresize(bytenuts.out_win, LINES - 2, COLS);
mvwin(bytenuts.out_win, 0, 0);
wrefresh(bytenuts.out_win);
wresize(bytenuts.in_win, 1, COLS);
mvwin(bytenuts.in_win, LINES - 1, 0);
wrefresh(bytenuts.in_win);
pthread_mutex_unlock(&bytenuts.term_lock);
cheerios_insert("", 0);
return 0;
}
static int
parse_args(int argc, char **argv)
{
if (argc < 2)
return -1;
if (argc == 2 && !strcmp(argv[1], "-h"))
return -1;
/* setup default options first */
bytenuts.config = CONFIG_DEFAULT;
{
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);
}
for (int i = 1; i < argc - 1; i++) {
size_t arg_len = strlen(argv[i]);
if (!strcmp(argv[i], "-h")) {
return -1;
}
else if (!strcmp(argv[i], "-b")) {
i++;
if (i == argc - 1)
return -1;
bytenuts.config.baud = string_to_speed(argv[i]);
}
else if (!strcmp(argv[i], "-l")) {
i++;
if (i == argc - 1)
return -1;
bytenuts.config.log_path = strdup(argv[i]);
}
else if (!strcmp(argv[i], "-c")) {
i++;
if (i == argc - 1)
return -1;
bytenuts.config.config_path = strdup(argv[i]);
}
else if (arg_len == 10 && !memcmp(argv[i], "--colors=", 9)) {
if (argv[i][9] == '1') {
bytenuts.config.colors = 1;
} else if (argv[i][9] == '0') {
bytenuts.config.colors = 0;
}
bytenuts.config_overrides[0] = 1;
}
else if (arg_len == 8 && !memcmp(argv[i], "--echo=", 7)) {
if (argv[i][7] == '1') {
bytenuts.config.echo = 1;
} else if (argv[i][7] == '0') {
bytenuts.config.echo = 0;
}
bytenuts.config_overrides[1] = 1;
}
else if (arg_len == 11 && !memcmp(argv[i], "--no_crlf=", 10)) {
if (argv[i][10] == '1') {
bytenuts.config.no_crlf = 1;
} else if (argv[i][10] == '0') {
bytenuts.config.no_crlf = 0;
}
bytenuts.config_overrides[2] = 1;
}
else if (arg_len == 10 && !memcmp(argv[i], "--escape=", 9)) {
bytenuts.config.escape = argv[i][9];
bytenuts.config_overrides[3] = 1;
}
else {
return -1;
}
}
bytenuts.config.serial_path = strdup(argv[argc - 1]);
return 0;
}
static int
load_configs()
{
FILE *fd = NULL;
char line[128];
fd = fopen(bytenuts.config.config_path, "r");
if (!fd) {
return 0;
}
while (fgets(line, sizeof(line), fd) != NULL) {
if (!bytenuts.config_overrides[0] && !memcmp(line, "colors=", 7)) {
if (line[7] == '0')
bytenuts.config.colors = 0;
else if (line[7] == '1')
bytenuts.config.colors = 1;
}
else if (!bytenuts.config_overrides[1] && !memcmp(line, "echo=", 5)) {
if (line[5] == '0')
bytenuts.config.echo = 0;
else if (line[5] == '1')
bytenuts.config.echo = 1;
}
else if (!bytenuts.config_overrides[2] && !memcmp(line, "no_crlf=", 8)) {
if (line[8] == '0')
bytenuts.config.no_crlf = 0;
else if (line[8] == '1')
bytenuts.config.no_crlf = 1;
}
else if (!bytenuts.config_overrides[3] && !memcmp(line, "escape=", 7)) {
bytenuts.config.escape = line[7];
}
}
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 B1152000;
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 "B1152000";
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 "";
}

75
src/bytenuts.h Normal file
View file

@ -0,0 +1,75 @@
#ifndef _BYTENUTES_H_
#define _BYTENUTES_H_
#include <ncurses.h>
#include <pthread.h>
#include <stdarg.h>
#include <stdio.h>
#include "serial.h"
/* https://en.wikipedia.org/wiki/Control_character#How_control_characters_map_to_keyboards */
#define CTRL(c) ((c)&31)
typedef struct bytenuts_config_struct {
int colors; /* enable terminal colors, default 1 */
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 */
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 */
} bytenuts_config_t;
#define CONFIG_DEFAULT (bytenuts_config_t){ \
.colors = 1, \
.echo = 0, \
.no_crlf = 0, \
.escape = 'b', \
.baud = B115200, \
.config_path = NULL, \
.log_path = NULL, \
.serial_path = NULL, \
}
typedef struct bytenuts_struct {
int serial_fd;
bytenuts_config_t config;
int config_overrides[4];
WINDOW *status_win;
WINDOW *out_win;
WINDOW *in_win;
pthread_mutex_t lock;
pthread_mutex_t term_lock;
pthread_cond_t stop_cond;
pthread_t in_thr;
pthread_t out_thr;
char *bytenuts_status;
char *ingest_status;
char *cheerios_status;
char *cmdpg_status;
} bytenuts_t;
/* startup the application */
int bytenuts_run(int argc, char **argv);
/* cleanly stop threads */
int bytenuts_stop();
/* perform kill routing for exiting */
void bytenuts_kill();
/* print some bytenuts stats to the output window */
int bytenuts_print_stats();
#define STATUS_BYTENUTS (0)
#define STATUS_INGEST (1)
#define STATUS_CHEERIOS (2)
#define STATUS_CMDPAGE (3)
/* set the status for the given thread */
int bytenuts_set_status(int user, const char *fmt, ...);
int bytenuts_update_screen_size();
#endif /* _BYTENUTES_H_ */

523
src/cheerios.c Normal file
View file

@ -0,0 +1,523 @@
#include <ncurses.h>
#include <poll.h>
#include <pthread.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#include "cheerios.h"
#include "xmodem.h"
static cheerios_t cheerios;
static void *cheerios_thread(void *arg);
static int insert_buf(line_buffer_t *lines, const char *buf, size_t len);
static int write_lines(line_buffer_t *lines);
static int handle_color(line_buffer_t *lines, int line_idx, int *pos);
short curs_color(int fg);
static int newline(line_buffer_t *lines);
int
cheerios_start(bytenuts_t *bytenuts)
{
memset(&cheerios, 0, sizeof(cheerios_t));
cheerios.output = bytenuts->out_win;
cheerios.term_lock = &bytenuts->term_lock;
cheerios.ser_fd = bytenuts->serial_fd;
cheerios.lines.bot = -1;
cheerios.config = &bytenuts->config;
if (cheerios.config->log_path) {
cheerios.log = fopen(cheerios.config->log_path, "w");
if (!cheerios.log) {
return -1;
}
}
pthread_cond_init(&cheerios.cond, NULL);
pthread_mutex_init(&cheerios.lock, NULL);
cheerios.running = 1;
pthread_create(&cheerios.thr, NULL, cheerios_thread, NULL);
if (cheerios.config->colors) {
start_color();
use_default_colors();
}
return 0;
}
int
cheerios_pause()
{
pthread_mutex_lock(&cheerios.lock);
cheerios.mode = CHEERIOS_MODE_PAUSED;
pthread_mutex_unlock(&cheerios.lock);
cheerios_info("Paused");
return 0;
}
int
cheerios_resume()
{
pthread_mutex_lock(&cheerios.lock);
cheerios.mode = CHEERIOS_MODE_NORMAL;
pthread_cond_signal(&cheerios.cond);
pthread_mutex_unlock(&cheerios.lock);
cheerios_info("Resumed");
return 0;
}
int
cheerios_goback(int lines)
{
pthread_mutex_lock(&cheerios.lock);
if (lines < 0) { /* go back as far as we can */
cheerios.lines.bot = getmaxy(cheerios.output) % cheerios.lines.n_lines;
}
else { /* just increment the bot pointer */
if (cheerios.lines.bot < 0)
cheerios.lines.bot = cheerios.lines.n_lines - 1;
cheerios.lines.bot -= lines;
if (cheerios.lines.bot < 0)
cheerios.lines.bot = 0;
}
write_lines(&cheerios.lines);
pthread_mutex_unlock(&cheerios.lock);
return 0;
}
int
cheerios_gofwd(int lines)
{
pthread_mutex_lock(&cheerios.lock);
if (lines < 0) { /* go to front */
cheerios.lines.bot = -1;
}
else { /* just increment the bot pointer */
if (cheerios.lines.bot < 0)
cheerios.lines.bot = cheerios.lines.n_lines - 1;
cheerios.lines.bot += lines;
if (cheerios.lines.bot >= cheerios.lines.n_lines)
cheerios.lines.bot = -1;
}
write_lines(&cheerios.lines);
pthread_mutex_unlock(&cheerios.lock);
return 0;
}
int
cheerios_stop()
{
cheerios.running = 0;
pthread_join(cheerios.thr, NULL);
return 0;
}
int
cheerios_input(const char *line)
{
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);
pthread_mutex_unlock(&cheerios.lock);
return 0;
}
int
cheerios_info(const char *line)
{
size_t line_len = strlen(line);
char *line_parsed = calloc(1, line_len + 12 + 1);
sprintf(line_parsed, "BYTENUTS: %s\r\n", line);
pthread_mutex_lock(&cheerios.lock);
if (cheerios.lines.pos != 0) {
insert_buf(&cheerios.lines, "\r\n", 2);
}
insert_buf(&cheerios.lines, line_parsed, strlen(line_parsed));
pthread_mutex_unlock(&cheerios.lock);
return 0;
}
int
cheerios_insert(const char *buf, size_t len)
{
pthread_mutex_lock(&cheerios.lock);
insert_buf(&cheerios.lines, buf, len);
pthread_mutex_unlock(&cheerios.lock);
return 0;
}
static void
__xmodem_callback(size_t sent, size_t total)
{
char status_str[1024];
snprintf(
status_str, sizeof(status_str),
"\r %.02f/%.02fKB (%.01f%%)",
(sent / 1024.0),
(total / 1024.0),
(sent * 100.0 / total)
);
cheerios_insert(status_str, strlen(status_str));
}
int
cheerios_xmodem(const char *path, int block_sz)
{
struct stat st = { 0 };
FILE *fd;
char info[128];
stat(path, &st);
snprintf(
info, sizeof(info),
"Sending %s (%ld) with %dB payloads",
path,
st.st_size,
block_sz
);
cheerios_info(info);
fd = fopen(path, "r");
if (!fd) {
cheerios_info("Failed to open file!");
return -1;
}
cheerios_pause();
if (xmodem_send(
cheerios.ser_fd,
fileno(fd),
st.st_size,
block_sz,
__xmodem_callback
)) {
fclose(fd);
cheerios_resume();
return -1;
}
fclose(fd);
cheerios_resume();
return 0;
}
int
cheerios_print_stats()
{
char st_line[128];
sprintf(st_line, "output line count: %d\r\n", cheerios.lines.n_lines);
cheerios_insert(st_line, strlen(st_line));
return 0;
}
int
cheerios_getmaxy()
{
int y, x;
getmaxyx(cheerios.output, y, x);
return y;
}
int
cheerios_getmaxx()
{
int y, x;
getmaxyx(cheerios.output, y, x);
return x;
}
int
cheerios_set_window(WINDOW *win)
{
pthread_mutex_lock(&cheerios.lock);
delwin(cheerios.output);
cheerios.output = win;
write_lines(&cheerios.lines);
pthread_mutex_unlock(&cheerios.lock);
return 0;
}
static void *
cheerios_thread(void *arg)
{
char buf[1024];
ssize_t read_ret = 0;
struct pollfd fds;
while (cheerios.running) {
pthread_mutex_lock(&cheerios.lock);
while (cheerios.mode == CHEERIOS_MODE_PAUSED) {
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));
if (read_ret > 0) {
insert_buf(&cheerios.lines, buf, read_ret);
}
pthread_mutex_unlock(&cheerios.lock);
nanosleep(&(struct timespec){ 0, 100000 }, NULL);
}
if (cheerios.log)
fclose(cheerios.log);
pthread_exit(NULL);
return NULL;
}
static int
insert_buf(line_buffer_t *lines, const char *buf, size_t len)
{
if (lines->n_lines == 0)
newline(lines);
for (size_t i = 0; i < len; i++) {
if (cheerios.log && cheerios.mode == CHEERIOS_MODE_NORMAL)
fwrite(&buf[i], 1, 1, cheerios.log);
/* line feed starts a new row */
if (buf[i] == '\n') {
newline(lines);
}
/* carriage return just sets pos to 0 */
else if (buf[i] == '\r') {
lines->pos = 0;
}
else {
if (lines->pos >= lines->line_lens[lines->n_lines - 1]) {
lines->line_lens[lines->n_lines - 1] = lines->pos + 1;
lines->lines[lines->n_lines - 1] = realloc(
lines->lines[lines->n_lines - 1],
sizeof(uint8_t) * lines->line_lens[lines->n_lines - 1]
);
}
lines->lines[lines->n_lines - 1][lines->pos] = buf[i];
lines->pos++;
}
}
write_lines(lines);
return 0;
}
static int
write_lines(line_buffer_t *lines)
{
int row = lines->bot;
int rows_printed = 0;
int window_height = getmaxy(cheerios.output);
if (row < 0)
row = lines->n_lines - 1;
pthread_mutex_lock(cheerios.term_lock);
curs_set(0);
werase(cheerios.output);
while (row >= 0 && rows_printed < window_height) {
wmove(cheerios.output, window_height - rows_printed - 1, 0);
for (int i = 0; i < lines->line_lens[row]; i++) {
handle_color(lines, row, &i);
if (i >= getmaxx(cheerios.output)) {
break;
}
if (i < lines->line_lens[row]) {
waddch(cheerios.output, lines->lines[row][i]);
}
}
rows_printed++;
row--;
}
curs_set(1);
wrefresh(cheerios.output);
pthread_mutex_unlock(cheerios.term_lock);
if (lines->bot < 0) {
bytenuts_set_status(STATUS_CHEERIOS, "scrolling");
} else {
bytenuts_set_status(STATUS_CHEERIOS, "locked");
}
return 0;
}
static int
handle_color(line_buffer_t *lines, int line_idx, int *pos)
{
short fg, bg = -1;
int enable = -1;
int p = *pos;
uint8_t *line = lines->lines[line_idx];
int line_len = lines->line_lens[line_idx];
if (!cheerios.config->colors)
return 0;
while (p < line_len) {
/* fg or bg color */
if (!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;
p += 7;
if (p >= line_len)
break;
for (int i = 0; i < 3; i++) {
if (line[p] == 'm') {
break;
}
else if (line[p] < '0' || line[p] > '9') {
col_str[0] = '\0';
break;
}
col_str[i] = line[p];
p++;
}
if (col_str[0] == '\0')
break;
*col = strtol(col_str, NULL, 10);
p++;
}
/* enable our color */
else if (!memcmp(&line[p], "\e[1m", 4)) {
p += 4;
enable = 1;
break;
}
/* disable our color */
else if (!memcmp(&line[p], "\e[0m", 4)) {
p += 4;
enable = 0;
break;
}
/* this is not a color code */
else {
break;
}
}
if (enable == 0) {
for (int i = 0; i < NCOLOR_PAIRS; i++) {
if (lines->enabled_pairs[i]) {
wattroff(cheerios.output, COLOR_PAIR(i + 1));
lines->enabled_pairs[i] = 0;
}
}
}
else if (enable == 1 && (fg >= 0 || bg >= 0)) {
int pair_pos = -1;
if (fg < 0)
fg = COLOR_WHITE;
if (bg < 0)
bg = COLOR_BLACK;
for (int i = 0; i < NCOLOR_PAIRS; i++) {
if (lines->color_pairs[i].fg == fg && lines->color_pairs[i].bg == bg) {
pair_pos = i;
break;
}
}
if (pair_pos < 0) {
pair_pos = lines->color_pos;
init_pair(pair_pos + 1, fg, bg);
lines->color_pairs[pair_pos].fg = fg;
lines->color_pairs[pair_pos].bg = bg;
lines->color_pos = (lines->color_pos + 1) % NCOLOR_PAIRS;
}
lines->enabled_pairs[pair_pos] = 1;
wattron(cheerios.output, COLOR_PAIR(pair_pos + 1));
}
else {
return 0;
}
*pos = p;
return 0;
}
static int
newline(line_buffer_t *lines)
{
lines->n_lines++;
lines->lines = realloc(lines->lines, sizeof(uint8_t *) * lines->n_lines);
lines->lines[lines->n_lines - 1] = NULL;
lines->line_lens = realloc(lines->line_lens, sizeof(int) * lines->n_lines);
lines->line_lens[lines->n_lines - 1] = 0;
lines->pos = 0;
return 0;
}

87
src/cheerios.h Normal file
View file

@ -0,0 +1,87 @@
#ifndef _CHEERIOS_H_
#define _CHEERIOS_H_
#include <ncurses.h>
#include <pthread.h>
#include <stdio.h>
#include "bytenuts.h"
typedef struct line_buffer_struct {
uint8_t **lines;
int *line_lens;
int n_lines; /* how many lines do we have */
int pos; /* position of the cursor in the current line */
int bot; /* index of the bottom line shown */
#define NCOLOR_PAIRS (8)
/* support 8 color pairs on screen at once.
* COLOR_PAIR(pair_pos + 2) will get you your color. */
struct { uint8_t fg; uint8_t bg; } color_pairs[NCOLOR_PAIRS];
uint8_t enabled_pairs[NCOLOR_PAIRS]; /* which pairs are enabled */
int color_pos;
} line_buffer_t;
enum cheerios_mode_enum {
CHEERIOS_MODE_NORMAL = 0,
CHEERIOS_MODE_PAUSED,
};
typedef struct cheerios_struct {
pthread_mutex_t lock;
volatile int running;
pthread_t thr;
WINDOW *output;
pthread_mutex_t *term_lock;
int ser_fd;
line_buffer_t lines;
FILE *log;
bytenuts_config_t *config;
volatile int mode;
pthread_cond_t cond;
} cheerios_t;
/* startup the output window thread */
int cheerios_start(bytenuts_t *bytenuts);
/* pause reading from the device */
int cheerios_pause();
/* resume reading from the device */
int cheerios_resume();
/* go back in the log history, this also stops the buffer from scrolling down
* if negative, jump to the back of the log */
int cheerios_goback(int lines);
/* go forward in the log history
* if a negative number is provided, go back to the start and resume scrolling */
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);
/* output a line only to the terminal for info/prompt purposes
* do not terminate with a newline! */
int cheerios_info(const char *line);
/* directly insert a buffer to the output window */
int cheerios_insert(const char *buf, size_t len);
/* send the file at path over xmodem */
int cheerios_xmodem(const char *path, int block_sz);
int cheerios_print_stats();
/* getter for window height */
int cheerios_getmaxy();
/* getter for window width */
int cheerios_getmaxx();
/* deletes the old window and sets the output window to the new one */
int cheerios_set_window(WINDOW *win);
#endif /* _CHEERIOS_H_ */

804
src/ingest.c Normal file
View file

@ -0,0 +1,804 @@
#include <libgen.h>
#include <ncurses.h>
#include <pthread.h>
#include <sched.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#include "bytenuts.h"
#include "cheerios.h"
#include "ingest.h"
static void *ingest_thread(void *arg);
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);
static void update_cmd_pg_status();
int
ingest_start(bytenuts_t *bytenuts)
{
char *HOME;
memset(&ingest, 0, sizeof(ingest));
ingest.input = bytenuts->in_win;
ingest.term_lock = &bytenuts->term_lock;
ingest.config = &bytenuts->config;
ingest.cmd_pg_cur = -1;
HOME = getenv("HOME");
if (HOME) {
char *cwd = getcwd(NULL, 0);
int idx = 0;
chdir(HOME);
while (!read_cmd_page(idx)) {
idx++;
}
chdir(cwd);
free(cwd);
}
if (ingest.cmd_pgs_n > 0) {
ingest.cmd_pg_cur = 0;
}
update_cmd_pg_status();
ingest.running = 1;
if (pthread_create(&ingest.thr, NULL, ingest_thread, NULL)) {
return -1;
}
return 0;
}
int
ingest_stop()
{
ingest.running = 0;
pthread_join(ingest.thr, NULL);
return 0;
}
static void *
ingest_thread(void *arg)
{
int esc_flag;
int ch;
memset(ingest.inbuf, 0, sizeof(ingest.inbuf));
ingest.inpos = 0;
cheerios_insert(
"Welcome to Bytenuts\r\n"
"To quit press ctrl-b q\r\n"
"To see all commands, press ctrl-b h\r\n",
82
);
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);
if (ch == ERR) {
nanosleep(&(struct timespec){ 0, 100000 }, NULL);
continue;
}
if (ch == CTRL(ingest.config->escape)) {
int should_quit = 0;
int should_continue = 0;
bytenuts_set_status(STATUS_INGEST, "control");
while (1) {
pthread_mutex_lock(ingest.term_lock);
ch = wgetch(ingest.input);
pthread_mutex_unlock(ingest.term_lock);
if (ch != ERR)
break;
nanosleep(&(struct timespec){ 0, 100000 }, NULL);
}
switch (ch) {
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '0':
{
bytenuts_set_status(STATUS_INGEST, "normal");
should_continue = 1;
if (ingest.cmd_pgs_n == 0)
break;
int idx;
char *toload;
size_t toload_len;
char **cmds = ingest.cmd_pgs[ingest.cmd_pg_cur].cmds;
int cmds_n = ingest.cmd_pgs[ingest.cmd_pg_cur].cmds_n;
if (ch == '0')
ch += 10;
idx = ch - '1';
if (idx >= cmds_n)
break;
toload = cmds[idx];
toload_len = strlen(toload);
memcpy(ingest.inbuf, toload, toload_len);
ingest.inpos = toload_len;
ingest.inlen = toload_len;
inbuf_load();
break;
}
case 'c':
{
bytenuts_set_status(STATUS_INGEST, "normal");
should_continue = 1;
if (ingest.cmd_pgs_n == 0) {
cheerios_insert("No quick commands\r\n", 19);
break;
}
char **cmds = ingest.cmd_pgs[ingest.cmd_pg_cur].cmds;
int cmds_n = ingest.cmd_pgs[ingest.cmd_pg_cur].cmds_n;
char *cmd_str;
size_t cmd_str_len;
cmd_str_len = snprintf(
NULL, 0,
"Quick Commands (pg.%d of %d):\r\n",
ingest.cmd_pg_cur + 1,
ingest.cmd_pgs_n
);
cmd_str = calloc(1, cmd_str_len + 1);
sprintf(
cmd_str,
"Quick Commands (pg.%d of %d):\r\n",
ingest.cmd_pg_cur + 1,
ingest.cmd_pgs_n
);
cheerios_insert(cmd_str, cmd_str_len);
for (int i = 0; i < cmds_n; i++) {
cmd_str_len = snprintf(
NULL, 0,
"%4d: %s\r\n",
i+1, cmds[i]
);
cmd_str = calloc(1, cmd_str_len + 1);
sprintf(cmd_str, "%4d: %s\r\n", i+1, cmds[i]);
cheerios_insert(cmd_str, cmd_str_len);
free(cmd_str);
}
break;
}
case 'p':
{
bytenuts_set_status(STATUS_CMDPAGE, "selecting...");
while (1) {
pthread_mutex_lock(ingest.term_lock);
ch = wgetch(ingest.input);
pthread_mutex_unlock(ingest.term_lock);
if (ch != ERR)
break;
nanosleep(&(struct timespec){ 0, 100000 }, NULL);
}
should_continue = 1;
bytenuts_set_status(STATUS_INGEST, "normal");
if (ch >= '1' || ch <= '9') {
int pg = ch - '1';
if (pg >= 0 && pg < ingest.cmd_pgs_n) {
ingest.cmd_pg_cur = pg;
}
}
update_cmd_pg_status();
break;
}
case 'i':
print_stats();
bytenuts_set_status(STATUS_INGEST, "normal");
should_continue = 1;
break;
case 'q':
bytenuts_stop();
should_quit = 1;
break;
case 'x':
ingest.mode = INGEST_MODE_XMODEM;
bytenuts_set_status(STATUS_INGEST, "xmodem");
break;
case 'X':
ingest.mode = INGEST_MODE_XMODEM1K;
bytenuts_set_status(STATUS_INGEST, "xmodem1k");
break;
case 'h':
cheerios_insert(
"Commands (lead with ctrl-b):\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"
" 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: view this help\r\n"
" q: quit Bytenuts\r\n",
313
);
bytenuts_set_status(STATUS_INGEST, "normal");
should_continue = 1;
break;
default:
bytenuts_set_status(STATUS_INGEST, "normal");
should_continue = 1;
break;
}
if (should_quit) {
break;
} else if (should_continue) {
continue;
}
}
switch (ingest.mode) {
case INGEST_MODE_NORMAL:
mode_normal(ch);
break;
case INGEST_MODE_XMODEM:
mode_xmodem(128);
break;
case INGEST_MODE_XMODEM1K:
mode_xmodem(1024);
break;
default:
break;
}
sched_yield();
}
pthread_exit(NULL);
return NULL;
}
static int
mode_normal(int ch)
{
if (handle_functions(ch))
return 0;
switch (ch) {
case '\n': /* send inputs */
if (cheerios_input(ingest.inbuf)) {
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++;
}
memset(ingest.inbuf, 0, sizeof(ingest.inbuf));
ingest.inpos = 0;
ingest.inlen = 0;
inbuf_load();
break;
case KEY_UP: /* load in history */
if (ingest.history_pos == 0) { /* top of history */
break;
} else if (ingest.history_pos == ingest.history_len) { /* we have not loaded any history */
strcpy(ingest.tmp_history, ingest.inbuf);
}
ingest.history_pos--;
strcpy(ingest.inbuf, ingest.history[ingest.history_pos]);
ingest.inlen = strlen(ingest.inbuf);
ingest.inpos = ingest.inlen;
inbuf_load();
break;
case KEY_DOWN: /* load in history */
if (ingest.history_pos == ingest.history_len) { /* no history loaded */
break;
} else if (ingest.history_pos == ingest.history_len - 1) { /* load back temp storage */
ingest.history_pos++;
strcpy(ingest.inbuf, ingest.tmp_history);
ingest.inlen = strlen(ingest.inbuf);
ingest.inpos = ingest.inlen;
} else {
ingest.history_pos++;
strcpy(ingest.inbuf, ingest.history[ingest.history_pos]);
ingest.inlen = strlen(ingest.inbuf);
ingest.inpos = ingest.inlen;
}
inbuf_load();
break;
default:
{
char tmp[1024];
int tmp_len;
if (ingest.inpos == sizeof(ingest.inbuf) - 1)
break;
tmp_len = ingest.inlen - ingest.inpos;
memcpy(tmp, &ingest.inbuf[ingest.inpos], tmp_len);
ingest.inbuf[ingest.inpos] = (char)ch;
ingest.inpos++;
ingest.inlen++;
memcpy(&ingest.inbuf[ingest.inpos], tmp, tmp_len);
if (ingest.inlen == sizeof(ingest.inbuf)) {
ingest.inbuf[ingest.inlen - 1] = '\0';
ingest.inlen--;
}
inbuf_load();
break;
}
}
return 0;
}
static int
mode_xmodem(int block_sz)
{
int quit_flag = 0;
strcpy(ingest.tmp_history, ingest.inbuf);
memset(ingest.inbuf, 0, sizeof(ingest.inbuf));
ingest.inlen = 0;
ingest.inpos = 0;
if (ingest.prepend)
free(ingest.prepend);
ingest.prepend = strdup("Give me a path (ctrl-c to stop): ");
inbuf_load();
while (ingest.running) {
int ch;
pthread_mutex_lock(ingest.term_lock);
ch = wgetch(ingest.input);
pthread_mutex_unlock(ingest.term_lock);
if (ch == ERR || handle_functions(ch)) {
nanosleep(&(struct timespec){ 0, 100000 }, NULL);
continue;
}
switch (ch) {
case '\n':
pthread_mutex_lock(ingest.term_lock);
werase(ingest.input);
wmove(ingest.input, 0, 0);
wprintw(ingest.input, "Sending...");
wrefresh(ingest.input);
pthread_mutex_unlock(ingest.term_lock);
cheerios_gofwd(-1);
cheerios_xmodem(ingest.inbuf, block_sz);
quit_flag = 1;
break;
case CTRL('c'):
quit_flag = 1;
break;
case '\t': /* tab complete */
auto_complete();
inbuf_load();
break;
default:
{
char tmp[1024];
int tmp_len;
if (ingest.inpos == sizeof(ingest.inbuf) - 1)
break;
tmp_len = ingest.inlen - ingest.inpos;
memcpy(tmp, &ingest.inbuf[ingest.inpos], tmp_len);
ingest.inbuf[ingest.inpos] = (char)ch;
ingest.inpos++;
ingest.inlen++;
memcpy(&ingest.inbuf[ingest.inpos], tmp, tmp_len);
if (ingest.inlen == sizeof(ingest.inbuf)) {
ingest.inbuf[ingest.inlen - 1] = '\0';
ingest.inlen--;
}
inbuf_load();
break;
}
}
if (quit_flag) {
memset(ingest.inbuf, 0, sizeof(ingest.inbuf));
strcpy(ingest.inbuf, ingest.tmp_history);
ingest.inlen = strlen(ingest.inbuf);
ingest.inpos = ingest.inlen;
ingest.mode = INGEST_MODE_NORMAL;
free(ingest.prepend);
ingest.prepend = NULL;
bytenuts_set_status(STATUS_INGEST, "normal");
inbuf_load();
return 0;
}
}
return 0;
}
static int
handle_functions(int ch)
{
// char st[128];
// sprintf(st, "%d", ch);
// cheerios_info(st);
switch (ch) {
case KEY_PPAGE: /* page up */
cheerios_goback(cheerios_getmaxy() / 2);
return 1;
case KEY_NPAGE: /* page down */
cheerios_gofwd(cheerios_getmaxy() / 2);
return 1;
case KEY_SHOME: /* to back of log */
cheerios_goback(-1);
return 1;
case KEY_SEND: /* go to front of log */
cheerios_gofwd(-1);
return 1;
case 566: /* ctrl + up arrow */
cheerios_goback(1);
return 1;
case 525: /* ctrl + down arrow */
cheerios_gofwd(1);
return 1;
case KEY_RESIZE:
bytenuts_update_screen_size();
return 1;
case KEY_BACKSPACE:
{
if (ingest.inpos == 0) {
return 1;
} else if (ingest.inpos == ingest.inlen) {
ingest.inpos--;
ingest.inlen--;
ingest.inbuf[ingest.inlen] = '\0';
inbuf_load();
return 1;
}
char tmp[1024];
int tmp_len = ingest.inlen - ingest.inpos;
memcpy(tmp, &ingest.inbuf[ingest.inpos], ingest.inlen - ingest.inpos);
ingest.inpos--;
ingest.inlen--;
memcpy(&ingest.inbuf[ingest.inpos], tmp, tmp_len);
ingest.inbuf[ingest.inlen] = '\0';
inbuf_load();
return 1;
}
case KEY_DC: /* delete key */
{
char tmp[1024];
int tmp_len;
if (ingest.inpos == ingest.inlen) {
return 1;
} else if (ingest.inpos == ingest.inlen - 1) {
ingest.inbuf[ingest.inpos] = '\0';
ingest.inlen--;
inbuf_load();
return 1;
}
tmp_len = ingest.inlen - ingest.inpos - 1;
memcpy(tmp, &ingest.inbuf[ingest.inpos + 1], tmp_len);
ingest.inlen--;
memcpy(&ingest.inbuf[ingest.inpos], tmp, tmp_len);
ingest.inbuf[ingest.inlen] = '\0';
inbuf_load();
return 1;
}
case KEY_LEFT:
if (ingest.inpos == 0)
return 1;
ingest.inpos--;
inbuf_load();
return 1;
case KEY_RIGHT:
if (ingest.inpos == ingest.inlen)
return 1;
ingest.inpos++;
inbuf_load();
return 1;
case KEY_END:
ingest.inpos = ingest.inlen;
inbuf_load();
return 1;
case KEY_HOME:
ingest.inpos = 0;
inbuf_load();
return 1;
#if 0
case KEY_MOUSE:
{
MEVENT ev;
if (getmouse(&ev) != OK)
return 1;
if (ev.bstate & BUTTON4_PRESSED) { /* scroll up */
cheerios_goback(1);
} else if (ev.bstate & BUTTON5_PRESSED) { /* scroll down */
cheerios_gofwd(1);
}
return 1;
}
#endif
default:
return 0;
}
}
static int
inbuf_load()
{
int cx, cy = -1;
pthread_mutex_lock(ingest.term_lock);
werase(ingest.input);
wmove(ingest.input, 0, 0);
if (ingest.prepend)
wprintw(ingest.input, "%s", ingest.prepend);
for (int i = 0; i < ingest.inlen; i++) {
if (i == ingest.inpos)
getyx(ingest.input, cy, cx);
waddch(ingest.input, ingest.inbuf[i]);
}
if (cx < 0) {
cx = ingest.inlen;
cy = 0;
}
wmove(ingest.input, cy, cx);
wrefresh(ingest.input);
pthread_mutex_unlock(ingest.term_lock);
return 0;
}
static int
print_stats()
{
char st_line[128];
sprintf(st_line, "\r\nSTATS\r\n");
cheerios_insert(st_line, strlen(st_line));
sprintf(st_line, "input line count: %d\r\n", ingest.history_len);
cheerios_insert(st_line, strlen(st_line));
cheerios_print_stats();
bytenuts_print_stats();
return 0;
}
static int
auto_complete()
{
char cmd[1024];
char line[1024];
char **matches = NULL;
int nmatches = 0;
FILE *sh = NULL;
char *inbuf_cpy = NULL;
char *inbuf_basename = NULL;
size_t inbuf_basename_len;
/* retrieve directory listing with ls */
inbuf_cpy = strdup(ingest.inbuf);
if (ingest.inlen > 0 && ingest.inbuf[ingest.inlen - 1] == '/') {
snprintf(
cmd, sizeof(cmd),
"cd %s 2> /dev/null && ls -1 2> /dev/null",
ingest.inbuf
);
inbuf_basename = "";
} else {
snprintf(
cmd, sizeof(cmd),
"cd $(dirname \"%s\") 2> /dev/null && ls -1 2> /dev/null",
ingest.inbuf
);
inbuf_basename = basename(inbuf_cpy);
}
inbuf_basename_len = strlen(inbuf_basename);
sh = popen(cmd, "r");
while (fgets(line, sizeof(line), sh)) {
size_t line_len = strlen(line);
while (line[line_len - 1] == '\n' || line[line_len - 1] == '\r') {
line[line_len - 1] = '\0';
line_len = strlen(line);
}
if (!memcmp(inbuf_basename, line, inbuf_basename_len)) {
nmatches++;
matches = realloc(matches, sizeof(char *) * nmatches);
matches[nmatches - 1] = strdup(line);
}
}
/* the cd/ls just failed, ignore output */
int ret = pclose(sh);
if (ret)
goto auto_complete_cleanup;
if (nmatches == 0)
goto auto_complete_cleanup;
/* check if we can fill the inbuf */
int offset = inbuf_basename_len;
while (1) {
char add = matches[0][offset];
for (int i = 1; i < nmatches; i++) {
if (add != matches[i][offset]) {
add = '\0';
break;
}
}
if (add == '\0')
break;
offset++;
}
if (offset > inbuf_basename_len) {
size_t add_len = offset - inbuf_basename_len;
if (add_len + ingest.inlen >= sizeof(ingest.inbuf))
add_len = sizeof(ingest.inbuf) - ingest.inlen - 1;
memcpy(&ingest.inbuf[ingest.inlen], &matches[0][inbuf_basename_len], add_len);
ingest.inlen += add_len;
ingest.inpos = ingest.inlen;
}
/* auto complete directory '/' */
if (nmatches == 1) {
struct stat st;
stat(ingest.inbuf, &st);
if (
S_ISDIR(st.st_mode) &&
ingest.inlen < sizeof(ingest.inbuf) - 1 &&
(ingest.inpos == 0 || ingest.inbuf[ingest.inpos - 1] != '/')
) {
ingest.inbuf[ingest.inpos] = '/';
ingest.inpos++;
ingest.inlen++;
}
}
auto_complete_cleanup:
if (matches) {
for (int i = 0; i < nmatches; i++) {
free(matches[i]);
}
free(matches);
}
if (inbuf_cpy)
free(inbuf_cpy);
return 0;
}
static int
read_cmd_page(int idx)
{
char fname[128];
FILE *fd;
char line[1024];
snprintf(fname, sizeof(fname), ".config/bytenuts/commands%d", idx+1);
fd = fopen(fname, "r");
if (!fd)
return -1;
ingest.cmd_pgs_n++;
ingest.cmd_pgs = realloc(
ingest.cmd_pgs,
sizeof(*ingest.cmd_pgs) * ingest.cmd_pgs_n
);
memset(&ingest.cmd_pgs[idx], 0, sizeof(*ingest.cmd_pgs));
while (fgets(line, sizeof(line), fd)) {
size_t line_len = strlen(line);
/* strip CRLF */
while (line_len > 0 && (line[line_len-1] == '\n' || line[line_len-1] == '\r')) {
line_len--;
line[line_len] = '\0';
}
ingest.cmd_pgs[idx].cmds_n++;
ingest.cmd_pgs[idx].cmds[ingest.cmd_pgs[idx].cmds_n-1] = strdup(line);
}
fclose(fd);
return 0;
}
static void
update_cmd_pg_status()
{
if (ingest.cmd_pgs_n > 0) {
bytenuts_set_status(STATUS_CMDPAGE, "pg.%d", ingest.cmd_pg_cur+1);
} else {
bytenuts_set_status(STATUS_CMDPAGE, "n/a");
}
}

47
src/ingest.h Normal file
View file

@ -0,0 +1,47 @@
#ifndef _INGEST_H_
#define _INGEST_H_
#include <ncurses.h>
#include <pthread.h>
#include "bytenuts.h"
enum ingest_mode_enum {
INGEST_MODE_NORMAL = 0,
INGEST_MODE_XMODEM,
INGEST_MODE_XMODEM1K,
};
typedef struct ingest_struct {
WINDOW *input;
pthread_mutex_t *term_lock;
pthread_t thr;
volatile int running;
char *prepend; /* string to be prepended in the prompt */
char inbuf[1024];
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 tmp_history[1024]; /* copy of inbuf before we load in history */
int history_len;
int history_pos; /* where we currently reside in the history */
int mode;
bytenuts_config_t *config;
#define INGEST_COMMAND_PGSZ (10)
/* command pages loaded on startup */
struct {
char *cmds[INGEST_COMMAND_PGSZ];
int cmds_n;
} *cmd_pgs;
int cmd_pgs_n;
/* what command page is currently selected */
int cmd_pg_cur;
} ingest_t;
/* startup the input window thread */
int ingest_start(bytenuts_t *bytenuts);
/* stop the input window thread */
int ingest_stop();
#endif /* _INGEST_H_ */

31
src/main.c Normal file
View file

@ -0,0 +1,31 @@
#include <sched.h>
#include <signal.h>
#include <stdlib.h>
#include "bytenuts.h"
static void sigint_handler(int s);
int
main(int argc, char **argv)
{
struct sigaction act;
act.sa_handler = sigint_handler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGINT, &act, NULL);
if (bytenuts_run(argc, argv)) {
bytenuts_stop();
exit(1);
}
exit(0);
}
static void
sigint_handler(int s)
{
bytenuts_kill();
exit(1);
}

59
src/serial.c Normal file
View file

@ -0,0 +1,59 @@
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <termios.h>
#include <unistd.h>
#include "serial.h"
int
serial_open(const char *path, speed_t bps)
{
int fd;
struct termios otio;
struct termios ntio;
fd = open(path, O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1) {
return -1;
}
tcgetattr(fd, &otio);
tcgetattr(fd, &ntio);
cfsetispeed(&ntio, bps);
cfsetospeed(&ntio, bps);
/* https://www.cmrr.umn.edu/~strupp/serial.html */
/* 8N1 */
ntio.c_cflag |= (CLOCAL | CREAD);
ntio.c_cflag &= ~(CSIZE|PARENB|CSTOPB);
ntio.c_cflag |= (CS8); /* Set size after setting CSIZE mask */
ntio.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON|IXOFF|IXANY);
ntio.c_oflag &= ~(OPOST);
ntio.c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
ntio.c_cc[VINTR] = 0; /* Ctrl-c */
ntio.c_cc[VQUIT] = 0; /* Ctrl-\ */
ntio.c_cc[VERASE] = 0; /* del */
ntio.c_cc[VKILL] = 0; /* @ */
ntio.c_cc[VEOF] = 4; /* Ctrl-d */
ntio.c_cc[VTIME] = 0; /* inter-character timer unused */
ntio.c_cc[VMIN] = 0; /* non-blocking */
ntio.c_cc[VSWTC] = 0; /* '\0' */
ntio.c_cc[VSTART] = 0; /* Ctrl-q */
ntio.c_cc[VSTOP] = 0; /* Ctrl-s */
ntio.c_cc[VSUSP] = 0; /* Ctrl-z */
ntio.c_cc[VEOL] = 0; /* '\0' */
ntio.c_cc[VREPRINT] = 0; /* Ctrl-r */
ntio.c_cc[VDISCARD] = 0; /* Ctrl-u */
ntio.c_cc[VWERASE] = 0; /* Ctrl-w */
ntio.c_cc[VLNEXT] = 0; /* Ctrl-v */
ntio.c_cc[VEOL2] = 0; /* '\0' */
tcflush(fd, TCIFLUSH);
tcsetattr(fd, TCSANOW, &ntio);
return fd;
}

8
src/serial.h Normal file
View file

@ -0,0 +1,8 @@
#ifndef _SERIAL_H_
#define _SERIAL_H_
#include <termios.h>
int serial_open(const char *path, speed_t bps);
#endif /* _SERIAL_H_ */

203
src/xmodem.c Normal file
View file

@ -0,0 +1,203 @@
#include <poll.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/time.h>
#include <unistd.h>
#include "xmodem.h"
int _xmodem_wait(int fd, uint8_t *ret, int timeout_ms);
uint16_t _xmodem_crc(void *buf, size_t len);
uint8_t _xmodem_csum(void *buf, size_t len);
void _xmodem_status_print(size_t sent, size_t total);
int
xmodem_send(
int dest_fd,
int in_fd,
size_t sz,
int block_sz,
void (*status_cb)(size_t sent, size_t total)
)
{
uint8_t start_byte;
size_t sent;
uint8_t *buf;
size_t buf_sz;
uint8_t packet_num;
int retries;
int ret = 0;
if (block_sz != 128 && block_sz != 1024) {
return -1;
}
/* wait for start byte, 'C' for CRC, NAK for CSUM */
for (int i = 0; i < 11; i++) {
if (i == 10) {
return XMODEM_ERR_TIMEOUT;
}
if (
!_xmodem_wait(dest_fd, &start_byte, XMODEM_TIMEOUT) &&
(start_byte == XMODEM_CRC || start_byte == XMODEM_NAK)
) {
break;
}
}
/* start sendin' bytes */
buf_sz = 3 + block_sz; /* SOH/STX | idx | ~idx | payload */
if (start_byte == XMODEM_NAK) {
buf_sz += 1;
} else {
buf_sz += 2;
}
buf = malloc(buf_sz);
sent = 0;
packet_num = 1; /* yes, this starts at 1 */
while (sent < sz) {
size_t read_len;
if (block_sz == 128) {
buf[0] = XMODEM_SOH;
} else {
buf[0] = XMODEM_STX;
}
buf[1] = packet_num;
buf[2] = ~packet_num;
if ((sz - sent) >= block_sz) {
read_len = block_sz;
} else {
/* back fill payload with pad */
read_len = sz - sent;
for (int i = read_len; i < block_sz; i++) {
buf[i] = XMODEM_PAD;
}
}
if (read(in_fd, &buf[3], read_len) != read_len) {
return XMODEM_ERR_FILEIO;
}
if (start_byte == XMODEM_CRC) {
/* write CRC big endian */
uint16_t crc = _xmodem_crc(&buf[3], block_sz);
buf[buf_sz-2] = (uint8_t)((0xFF00 & crc) >> 8);
buf[buf_sz-1] = (uint8_t)(0x00FF & crc);
} else {
buf[buf_sz-1] = _xmodem_csum(&buf[3], block_sz);
}
/* try to send this packet until we get an ACK */
retries = 0;
while (1) {
uint8_t recv_char;
if (retries == 10) {
ret = XMODEM_ERR_TIMEOUT;
goto end_of_transmission;
}
if (write(dest_fd, buf, buf_sz) != buf_sz){
return XMODEM_ERR_WRITE;
}
if (_xmodem_wait(dest_fd, &recv_char, XMODEM_TIMEOUT)) {
return XMODEM_ERR_READ;
}
if (recv_char == XMODEM_ACK) {
break;
}
retries++;
}
sent += read_len;
packet_num++; /* expect overflow */
status_cb(sent, sz);
}
end_of_transmission:
retries = 0;
while (1) {
uint8_t ack_char;
if (retries == 10) {
return XMODEM_ERR_TIMEOUT;
}
write(dest_fd, &(uint8_t){XMODEM_EOT}, 1);
if (
!_xmodem_wait(dest_fd, &ack_char, XMODEM_TIMEOUT) &&
ack_char == XMODEM_ACK
) {
break;
}
retries++;
}
return ret;
}
int
_xmodem_wait(int 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) {
return -1;
}
read(fd, ret, 1);
return 0;
}
uint16_t
_xmodem_crc(void *buf, size_t len)
{
uint16_t crc;
char i;
crc = 0;
while (--len >= 0)
{
crc = crc ^ (uint16_t) (*(uint8_t *)buf++) << 8;
i = 8;
do
{
if (crc & 0x8000)
crc = crc << 1 ^ 0x1021;
else
crc = crc << 1;
} while(--i);
}
return (crc);
}
uint8_t
_xmodem_csum(void *buf, size_t len)
{
uint8_t ret = 0;
for (size_t i = 0; i < len; i++) {
ret += ((uint8_t *)buf)[i];
}
return ret;
}

48
src/xmodem.h Normal file
View file

@ -0,0 +1,48 @@
#ifndef _XMODEM_H_
#define _XMODEM_H_
#include <stdio.h>
#include "serial.h"
#define XMODEM_SOH (0x01)
#define XMODEM_STX (0x02)
#define XMODEM_EOT (0x04)
#define XMODEM_ACK (0x06)
#define XMODEM_NAK (0x15)
#define XMODEM_ETB (0x17)
#define XMODEM_CAN (0x18)
#define XMODEM_CRC (0x43) /* 'C' */
#define XMODEM_PAD (0x1A)
#define XMODEM_TIMEOUT (10000) /* in ms */
#define XMODEM_ERR_TIMEOUT (1) /* ACK timeout */
#define XMODEM_ERR_WRITE (2) /* Failed to write to file descriptor */
#define XMODEM_ERR_FILEIO (3) /* Error on reading from file descriptor */
#define XMODEM_ERR_READ (4) /* Failed to read from the serial connection */
#define XMODEM_ERR_CANCEL (5) /* Sender cancelled the transmission */
#define XMODEM_ERR_DONE (6) /* Sender finished the transmission */
/*
* Following this setup: https://pythonhosted.org/xmodem/xmodem.html
* Choose to send `sz` bytes from `in_fd` to `dest_fd` via XModem with a payload
* length of `block_sz`.
*/
int xmodem_send(
int dest_fd,
int in_fd,
size_t sz,
int block_sz,
void (*status_cb)(size_t sent, size_t total)
);
#if 0
/* Receive an xmodem message
*
* If sz is non-negative, then only that many bytes will actually be written
* to fd. But the entire xmodem transfer will be completed */
int xmodem_recv(int in_fd, int dest_fd, ssize_t sz);
#endif
#endif /* _XMODEM_H_ */