From 589a73f98cf58cfd3b3a5a941a264cee39c815af Mon Sep 17 00:00:00 2001 From: Michel Pollet Date: Sat, 7 Sep 2024 18:24:08 +0100 Subject: [PATCH] libmish: Updated from upstream Thread safety stuff etc Signed-off-by: Michel Pollet --- libmish/src/mish_capture_select.c | 33 ++++++++++++++- libmish/src/mish_client.c | 4 +- libmish/src/mish_client_input.c | 5 ++- libmish/src/mish_cmd.c | 69 ++++++++++++++++++------------- libmish/src/mish_priv.h | 28 +++++++++---- libmish/src/mish_session.c | 21 ++++++---- 6 files changed, 113 insertions(+), 47 deletions(-) diff --git a/libmish/src/mish_capture_select.c b/libmish/src/mish_capture_select.c index 98ebc4b..065a2eb 100644 --- a/libmish/src/mish_capture_select.c +++ b/libmish/src/mish_capture_select.c @@ -9,9 +9,33 @@ #include #include #include +#include +#include #include "mish_priv.h" #include "mish.h" +/* + * We now use a thread to run the commands; this solve the problem of + * command generating a lot of output, deadlocking the select() thread, + * as the command write() would fill up the pipe buffer and block. + */ +void * +_mish_cmd_runner_thread( + void *param) +{ + mish_p m = param; + + printf("%s\n", __func__); + while (!(m->flags & MISH_QUIT)) { + sem_wait(&m->runner_block); + _mish_cmd_flush(0); + }; + printf("Exiting %s\n", __func__); + m->cmd_runner = 0; + sem_destroy(&m->runner_block); + return NULL; +} + /* * This is a select() based capture thread, it's not /ideal/ in terms of * performances, but it's portable and should work on OSX/BSD Linux etc. @@ -68,6 +92,12 @@ _mish_capture_select( _mish_input_read(m, &r, &c->input); if (c->input.fd == -1 || (c->flags & MISH_CLIENT_DELETE)) mish_client_delete(m, c); + if (c->flags & MISH_CLIENT_HAS_CMD) { + // printf("Waking up cmd_runner\n"); + c->flags &= ~MISH_CLIENT_HAS_CMD; + // wake up the command runner + sem_post(&m->runner_block); + } } unsigned int max_lines = m->backlog.max_lines; @@ -112,8 +142,9 @@ _mish_capture_select( mish_client_p c; while ((c = TAILQ_FIRST(&m->clients)) != NULL) mish_client_delete(m, c); - m->flags &= ~MISH_QUIT; +// m->flags &= ~MISH_QUIT; m->capture = 0; // mark the thread done +// printf("Exiting %s\n", __func__); exit(0); // this calls mish_terminate, on main thread // return NULL; } diff --git a/libmish/src/mish_client.c b/libmish/src/mish_client.c index 1cd19cc..d783e95 100644 --- a/libmish/src/mish_client.c +++ b/libmish/src/mish_client.c @@ -354,7 +354,7 @@ _mish_cmd_history( MISH_CMD_NAMES(history, "history"); MISH_CMD_HELP(history, "Display the history of commands."); -MISH_CMD_REGISTER(history, _mish_cmd_history); +MISH_CMD_REGISTER_KIND(history, _mish_cmd_history, 0, MISH_CLIENT_CMD_KIND); static void @@ -380,5 +380,5 @@ _mish_cmd_disconnect( MISH_CMD_NAMES(disconnect, "dis", "disconnect", "logout"); MISH_CMD_HELP(disconnect, "Disconnect this telnet session. If appropriate"); -MISH_CMD_REGISTER(disconnect, _mish_cmd_disconnect); +MISH_CMD_REGISTER_KIND(disconnect, _mish_cmd_disconnect, 0, MISH_CLIENT_CMD_KIND); diff --git a/libmish/src/mish_client_input.c b/libmish/src/mish_client_input.c index b297fda..83a4a84 100644 --- a/libmish/src/mish_client_input.c +++ b/libmish/src/mish_client_input.c @@ -195,7 +195,10 @@ kb_end: fprintf(stdout, "%s", c->cmd->line + c->cmd->done+1); fprintf(stdout, "'\n"); } - mish_cmd_call(c->cmd->line, c); + // if we have a non-safe command, we need to signal the + // cmd execution thread, so mark the client as having a command + if (mish_cmd_call(c->cmd->line, c) == 1) + c->flags |= MISH_CLIENT_HAS_CMD; c->cmd = NULL; // new one { // reuse the last empty one mish_line_p last = TAILQ_LAST(&in->backlog, mish_line_queue_t); diff --git a/libmish/src/mish_cmd.c b/libmish/src/mish_cmd.c index e5d07b6..d033ccc 100644 --- a/libmish/src/mish_cmd.c +++ b/libmish/src/mish_cmd.c @@ -46,7 +46,7 @@ DECLARE_FIFO(mish_cmd_call_t, mish_call_queue, 4); DEFINE_FIFO(mish_cmd_call_t, mish_call_queue); static TAILQ_HEAD(,mish_cmd_t) _cmd_list = TAILQ_HEAD_INITIALIZER(_cmd_list); -static mish_call_queue_t _cmd_fifo = {0}; +static mish_call_queue_t _cmd_fifo[2] = {0}; void __attribute__((weak)) mish_register_cmd_kind( @@ -146,7 +146,7 @@ mish_argv_make( _mish_argv_t * r = calloc(1, sizeof(*r)); r->line = strdup(line); char *dup = r->line; - char quote; + char quote = 0; enum { s_newarg = 0, s_startarg, s_copyquote, s_skip, s_copy }; int state = s_newarg; do { @@ -232,39 +232,52 @@ mish_cmd_call( int ac = 0; char ** av = mish_argv_make(cmd_line, &ac); - if (cmd->flags.safe) { - if (!mish_call_queue_isfull(&_cmd_fifo)) { - mish_cmd_call_t fe = { - .cmd = cmd, - .argv = av, - .argc = ac, - }; - mish_call_queue_write(&_cmd_fifo, fe); - } else { - fprintf(stderr, - "mish: cmd FIFO full, make sure to call mish_cmd_poll()!\n"); - } - } else { - cmd->cmd_cb(cmd->param_cb ? cmd->param_cb : c, ac, (const char**)av); + mish_call_queue_t *fifo = &_cmd_fifo[cmd->flags.safe]; + + // these are special commands, their parameter is the client + if (cmd->kind == MISH_CLIENT_CMD_KIND) { + cmd->cmd_cb(c, ac, (const char**)av); mish_argv_free(av); + return 0; } - return 0; + // all other commands are queued + if (!mish_call_queue_isfull(fifo)) { + mish_cmd_call_t fe = { + .cmd = cmd, + .argv = av, + .argc = ac, + }; + mish_call_queue_write(fifo, fe); + } else { + fprintf(stderr, + "mish: cmd FIFO%d full, make sure to call mish_cmd_poll()!\n", + cmd->flags.safe); + } + return cmd->flags.safe == 0; // we got a command to run? +} + +int +_mish_cmd_flush( + unsigned int queue) +{ + int res = 0; + mish_call_queue_t *fifo = &_cmd_fifo[!!queue]; + while (!mish_call_queue_isempty(fifo)) { + mish_cmd_call_t c = mish_call_queue_read(fifo); + c.cmd->cmd_cb( + c.cmd->param_cb, + c.argc, (const char**)c.argv); + mish_argv_free(c.argv); + res++; + } + return res; + } int mish_cmd_poll() { - int res = 0; - - while (!mish_call_queue_isempty(&_cmd_fifo)) { - mish_cmd_call_t c = mish_call_queue_read(&_cmd_fifo); - - c.cmd->cmd_cb( - c.cmd->param_cb, c.argc, (const char**)c.argv); - mish_argv_free(c.argv); - res++; - } - return res; + return _mish_cmd_flush(1); } static const char *_help[] = { diff --git a/libmish/src/mish_priv.h b/libmish/src/mish_priv.h index c12d236..8ba2b24 100644 --- a/libmish/src/mish_priv.h +++ b/libmish/src/mish_priv.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include "bsd_queue.h" #include "mish_priv_vt.h" @@ -28,6 +29,9 @@ enum { MISH_IN_SPLIT, // store current line, add it to backlog }; +#define MISH_CMD_KIND ('m' << 24 | 'i' << 16 | 's' << 8 | 'h') +#define MISH_CLIENT_CMD_KIND ('c' << 24 | 'l' << 16 | 'i' << 8 | 'e') + /* * This receives stuff from a file descriptor, 'processes' it, and split it * into lines to eventually store new lines into the 'backlog'. @@ -49,13 +53,14 @@ typedef struct mish_input_t { /* various internal states for the client */ enum { - MISH_CLIENT_INIT_SENT = (1 << 0), + MISH_CLIENT_INIT_SENT = (1 << 0), MISH_CLIENT_HAS_WINDOW_SIZE = (1 << 1), - MISH_CLIENT_HAS_CURSOR_POS = (1 << 2), - MISH_CLIENT_UPDATE_PROMPT = (1 << 3), - MISH_CLIENT_UPDATE_WINDOW = (1 << 4), - MISH_CLIENT_SCROLLING = (1 << 5), - MISH_CLIENT_DELETE = (1 << 6), + MISH_CLIENT_HAS_CURSOR_POS = (1 << 2), + MISH_CLIENT_UPDATE_PROMPT = (1 << 3), + MISH_CLIENT_UPDATE_WINDOW = (1 << 4), + MISH_CLIENT_SCROLLING = (1 << 5), + MISH_CLIENT_HAS_CMD = (1 << 6), + MISH_CLIENT_DELETE = (1 << 7), }; typedef struct mish_client_t { @@ -150,6 +155,8 @@ typedef struct mish_t { mish_client_p console; // client that is also the original terminal. pthread_t capture; // libmish main thread + pthread_t cmd_runner; // command runner thread + sem_t runner_block; // semaphore to block the runner thread pthread_t main; // todo: allow pause/stop/resume? struct { @@ -258,13 +265,20 @@ _mish_input_read( mish_p m, fd_set * fds, mish_input_p in); +// flush the command FIFO (queue = 0 for non-safe commands) +int +_mish_cmd_flush( + unsigned int queue); /* - * Capture thread function + * Thread functions */ void * _mish_capture_select( void *param); +void * +_mish_cmd_runner_thread( + void *param); /* * https://en.wikipedia.org/wiki/ANSI_escape_code#Terminal_output_sequences diff --git a/libmish/src/mish_session.c b/libmish/src/mish_session.c index b321990..34f0902 100644 --- a/libmish/src/mish_session.c +++ b/libmish/src/mish_session.c @@ -147,9 +147,12 @@ mish_prepare( goto error; } } + mish_set_command_parameter(MISH_CMD_KIND, m); atexit(_mish_atexit); // m->main = pthread_self(); // TODO: Make an epoll polling thread for linux + sem_init(&m->runner_block, 0, 0); + pthread_create(&m->cmd_runner, NULL, _mish_cmd_runner_thread, m); pthread_create(&m->capture, NULL, _mish_capture_select, m); _mish = m; @@ -196,19 +199,21 @@ mish_terminate( perror("mish_terminate tcsetattr"); #endif close(m->originals[0]); close(m->originals[1]); - if (m->capture) { - m->flags |= MISH_QUIT; + pthread_t t1 = m->cmd_runner, t2 = m->capture; + m->flags |= MISH_QUIT; + if (t1) + sem_post(&m->runner_block); + if (t2) { // this will wake the select() call from sleep if (write(1, "\n", 1)) ; - time_t start = time(NULL); time_t now; while (((now = time(NULL)) - start < 2) && m->capture) usleep(1000); } printf("\033[4l\033[;r\033[999;1H"); fflush(stdout); - printf("%s done\n", __func__); + //printf("%s done\n", __func__); free(m); _mish = NULL; } @@ -223,7 +228,7 @@ _mish_cmd_quit( "mish: Quitting." MISH_COLOR_RESET "\n"); - mish_p m = ((mish_client_p)param)->mish; + mish_p m = param; m->flags |= MISH_QUIT; } @@ -231,7 +236,7 @@ MISH_CMD_NAMES(quit, "q", "quit"); MISH_CMD_HELP(quit, "exit running program", "Close all clients and exit(0)"); -MISH_CMD_REGISTER(quit, _mish_cmd_quit); +MISH_CMD_REGISTER_KIND(quit, _mish_cmd_quit, 0, MISH_CMD_KIND); #define VT_COL(_c) "\033[" #_c "G" @@ -245,7 +250,7 @@ _mish_cmd_mish( "mish: mish command." MISH_COLOR_RESET "\n"); - mish_p m = ((mish_client_p)param)->mish; + mish_p m = param; printf("Backlog: %6d lines (%5dKB)" VT_COL(40) "Telnet Port: %5d\n", m->backlog.size, (int)m->backlog.alloc / 1024, @@ -302,5 +307,5 @@ MISH_CMD_HELP(mish, " also set the maximum lines in the backlog\n" " (0 = unlimited)\n" "Show status and a few bits of internals."); -MISH_CMD_REGISTER(mish, _mish_cmd_mish); +MISH_CMD_REGISTER_KIND(mish, _mish_cmd_mish, 0, MISH_CMD_KIND);