From 7897d8820e539fb51e3951d0d057771244d23cd1 Mon Sep 17 00:00:00 2001 From: m1ngsama Date: Sun, 17 May 2026 10:16:27 +0800 Subject: [PATCH] refactor: extract client module (PR2-M6) Move client_t I/O and lifecycle out of ssh_server.c into a dedicated module. ssh_server.c is now down to the listening socket, host key setup, ssh_server_init / ssh_server_start, and the accept loop. Migrated to client.{c,h}: - client_send, client_printf - client_addref, client_release (and the ssh_session / ssh_channel / channel_cb teardown that fires on the final release) - client_install_channel_callbacks - client_channel_window_change / _eof / _close (the post-bootstrap callbacks that target the client_t) The client_t struct definition stays in ssh_server.h so we don't have to revisit every existing #include chain. bootstrap, commands, exec, input, and tui pick up the new client.h alongside their existing ssh_server.h include. ssh_server.c shrinks from 402 to 249 lines (-153). Final per-module size after PR2: ssh_server.c 249 accept loop + ssh_server_init/start + host key bootstrap.c 493 per-connection SSH handshake / auth / channel client.c 162 client_t I/O + lifecycle + channel callbacks input.c 637 username read + handle_key + main loop + notify_mentions + idle timeout commands.c 269 vim ':' command dispatcher exec.c 453 SSH exec subcommand dispatcher ratelimit.c 197 IP rate limit + connection counters tui.c 614 screen rendering chat_room.c 151 room + client list message.c 350 message log load/save/search utf8.c 250 UTF-8 width / validation common.c 133 buffer_*, env_int, is_valid_username, sanitize_terminal_size, tnt_state_* main.c 109 process entry Total: ~4 000 lines of C across 13 files, no file over 650 lines, every file is single-purpose. Behaviour is preserved: the migrated code is byte-for-byte identical. --- include/client.h | 34 +++++++++ include/ssh_server.h | 18 ----- src/bootstrap.c | 1 + src/client.c | 162 +++++++++++++++++++++++++++++++++++++++++++ src/commands.c | 1 + src/exec.c | 1 + src/input.c | 1 + src/ssh_server.c | 153 ---------------------------------------- src/tui.c | 1 + 9 files changed, 201 insertions(+), 171 deletions(-) create mode 100644 include/client.h create mode 100644 src/client.c diff --git a/include/client.h b/include/client.h new file mode 100644 index 0000000..968f770 --- /dev/null +++ b/include/client.h @@ -0,0 +1,34 @@ +#ifndef CLIENT_H +#define CLIENT_H + +#include "ssh_server.h" /* for client_t */ + +/* Send `len` bytes to the client over its SSH channel. Serialised on + * client->io_lock so concurrent senders don't interleave. Returns 0 on + * success, -1 if the channel is gone or a partial write fails. */ +int client_send(client_t *client, const char *data, size_t len); + +/* printf-style wrapper around client_send(). The formatted string must + * fit in 2048 bytes; truncation or encoding errors return -1. */ +int client_printf(client_t *client, const char *fmt, ...); + +/* Reference counting for safe cross-thread cleanup. + * + * Lifecycle: bootstrap_run() creates the client_t with ref_count = 1 + * (the "main" ref), then adds a second ref before installing the channel + * callbacks (the "callback" ref) so the client outlives any in-flight + * eof / close / window-change callback invocation. The interactive + * session releases both refs in its cleanup path; the final release + * frees the SSH session, channel, callback struct, and the client_t. */ +void client_addref(client_t *client); +void client_release(client_t *client); + +/* Install the post-bootstrap channel callbacks (window-change, eof, close) + * that target this client_t. Caller MUST have already added one + * client_addref() to keep the client alive across in-flight callback + * invocations; the matching client_release() happens during cleanup in + * input_run_session(). Returns 0 on success, -1 on failure (in which + * case the caller still owns both refs and must release them). */ +int client_install_channel_callbacks(client_t *client); + +#endif /* CLIENT_H */ diff --git a/include/ssh_server.h b/include/ssh_server.h index 07b97b1..a9cb80c 100644 --- a/include/ssh_server.h +++ b/include/ssh_server.h @@ -45,24 +45,6 @@ int ssh_server_init(int port); /* Start SSH server (blocking) */ int ssh_server_start(int listen_fd); -/* Send data to client */ -int client_send(client_t *client, const char *data, size_t len); - -/* Send formatted string to client */ -int client_printf(client_t *client, const char *fmt, ...); - -/* Reference counting helpers */ -void client_addref(client_t *client); -void client_release(client_t *client); - -/* Install the post-bootstrap channel callbacks (window-change, eof, close) - * that target this client_t. Caller MUST have already added one - * client_addref() to keep the client alive across in-flight callback - * invocations; the matching client_release() happens during cleanup in - * input_run_session(). Returns 0 on success, -1 on failure (in which - * case the caller still owns both refs and must release them). */ -int client_install_channel_callbacks(client_t *client); - /* Read-only accessor for the server start time (used by exec stats). */ time_t ssh_server_start_time(void); diff --git a/src/bootstrap.c b/src/bootstrap.c index be1eb2c..2d77284 100644 --- a/src/bootstrap.c +++ b/src/bootstrap.c @@ -1,4 +1,5 @@ #include "bootstrap.h" +#include "client.h" #include "common.h" #include "input.h" #include "ratelimit.h" diff --git a/src/client.c b/src/client.c new file mode 100644 index 0000000..def3c9d --- /dev/null +++ b/src/client.c @@ -0,0 +1,162 @@ +#include "client.h" +#include "common.h" +#include +#include +#include +#include +#include +#include +#include +#include + +/* Send data to client via SSH channel */ +int client_send(client_t *client, const char *data, size_t len) { + size_t total = 0; + + if (!client || !data) return -1; + + pthread_mutex_lock(&client->io_lock); + + if (!client->connected || !client->channel) { + pthread_mutex_unlock(&client->io_lock); + return -1; + } + + while (total < len) { + size_t remaining = len - total; + uint32_t chunk = (remaining > 32768) ? 32768 : (uint32_t)remaining; + int sent = ssh_channel_write(client->channel, data + total, chunk); + if (sent <= 0) { + pthread_mutex_unlock(&client->io_lock); + return -1; + } + total += (size_t)sent; + } + + pthread_mutex_unlock(&client->io_lock); + return 0; +} + +void client_addref(client_t *client) { + if (!client) return; + pthread_mutex_lock(&client->ref_lock); + client->ref_count++; + pthread_mutex_unlock(&client->ref_lock); +} + +void client_release(client_t *client) { + if (!client) return; + + pthread_mutex_lock(&client->ref_lock); + client->ref_count--; + int count = client->ref_count; + pthread_mutex_unlock(&client->ref_lock); + + if (count == 0) { + /* Safe to free now */ + if (client->channel && client->channel_cb) { + ssh_remove_channel_callbacks(client->channel, client->channel_cb); + } + if (client->channel) { + ssh_channel_close(client->channel); + ssh_channel_free(client->channel); + } + if (client->session) { + ssh_disconnect(client->session); + ssh_free(client->session); + } + if (client->channel_cb) { + free(client->channel_cb); + } + pthread_mutex_destroy(&client->io_lock); + pthread_mutex_destroy(&client->ref_lock); + free(client); + } +} + +/* Send formatted string to client */ +int client_printf(client_t *client, const char *fmt, ...) { + char buffer[2048]; + va_list args; + va_start(args, fmt); + int len = vsnprintf(buffer, sizeof(buffer), fmt, args); + va_end(args); + + /* Check for buffer overflow or encoding error */ + if (len < 0 || len >= (int)sizeof(buffer)) { + return -1; + } + + return client_send(client, buffer, len); +} + +static int client_channel_window_change(ssh_session session, ssh_channel channel, + int width, int height, + int pxwidth, int pxheight, + void *userdata) { + (void)session; + (void)channel; + (void)pxwidth; + (void)pxheight; + + client_t *client = (client_t *)userdata; + if (!client) { + return SSH_ERROR; + } + + int w = width; + int h = height; + sanitize_terminal_size(&w, &h); + client->width = w; + client->height = h; + client->redraw_pending = true; + return SSH_OK; +} + +static void client_channel_eof(ssh_session session, ssh_channel channel, + void *userdata) { + (void)session; + (void)channel; + + client_t *client = (client_t *)userdata; + if (client) { + client->connected = false; + } +} + +static void client_channel_close(ssh_session session, ssh_channel channel, + void *userdata) { + (void)session; + (void)channel; + + client_t *client = (client_t *)userdata; + if (client) { + client->connected = false; + } +} + +int client_install_channel_callbacks(client_t *client) { + if (!client || !client->channel) { + return -1; + } + + client->channel_cb = calloc(1, sizeof(struct ssh_channel_callbacks_struct)); + if (!client->channel_cb) { + return -1; + } + + ssh_callbacks_init(client->channel_cb); + client->channel_cb->userdata = client; + client->channel_cb->channel_eof_function = client_channel_eof; + client->channel_cb->channel_close_function = client_channel_close; + client->channel_cb->channel_pty_window_change_function = + client_channel_window_change; + + if (ssh_set_channel_callbacks(client->channel, client->channel_cb) != SSH_OK) { + free(client->channel_cb); + client->channel_cb = NULL; + return -1; + } + + return 0; +} diff --git a/src/commands.c b/src/commands.c index 1bd08d0..86d32e0 100644 --- a/src/commands.c +++ b/src/commands.c @@ -1,5 +1,6 @@ #include "commands.h" #include "chat_room.h" +#include "client.h" #include "common.h" #include "message.h" #include "tui.h" diff --git a/src/exec.c b/src/exec.c index c2a0074..fae6ba6 100644 --- a/src/exec.c +++ b/src/exec.c @@ -1,5 +1,6 @@ #include "exec.h" #include "chat_room.h" +#include "client.h" #include "common.h" #include "input.h" #include "message.h" diff --git a/src/input.c b/src/input.c index a185b07..c9742f0 100644 --- a/src/input.c +++ b/src/input.c @@ -1,5 +1,6 @@ #include "input.h" #include "chat_room.h" +#include "client.h" #include "commands.h" #include "common.h" #include "exec.h" diff --git a/src/ssh_server.c b/src/ssh_server.c index 6f4617d..1559988 100644 --- a/src/ssh_server.c +++ b/src/ssh_server.c @@ -122,159 +122,6 @@ static int setup_host_key(ssh_bind sshbind) { return 0; } -/* Send data to client via SSH channel */ -int client_send(client_t *client, const char *data, size_t len) { - size_t total = 0; - - if (!client || !data) return -1; - - pthread_mutex_lock(&client->io_lock); - - if (!client->connected || !client->channel) { - pthread_mutex_unlock(&client->io_lock); - return -1; - } - - while (total < len) { - size_t remaining = len - total; - uint32_t chunk = (remaining > 32768) ? 32768 : (uint32_t)remaining; - int sent = ssh_channel_write(client->channel, data + total, chunk); - if (sent <= 0) { - pthread_mutex_unlock(&client->io_lock); - return -1; - } - total += (size_t)sent; - } - - pthread_mutex_unlock(&client->io_lock); - return 0; -} - -void client_addref(client_t *client) { - if (!client) return; - pthread_mutex_lock(&client->ref_lock); - client->ref_count++; - pthread_mutex_unlock(&client->ref_lock); -} - -void client_release(client_t *client) { - if (!client) return; - - pthread_mutex_lock(&client->ref_lock); - client->ref_count--; - int count = client->ref_count; - pthread_mutex_unlock(&client->ref_lock); - - if (count == 0) { - /* Safe to free now */ - if (client->channel && client->channel_cb) { - ssh_remove_channel_callbacks(client->channel, client->channel_cb); - } - if (client->channel) { - ssh_channel_close(client->channel); - ssh_channel_free(client->channel); - } - if (client->session) { - ssh_disconnect(client->session); - ssh_free(client->session); - } - if (client->channel_cb) { - free(client->channel_cb); - } - pthread_mutex_destroy(&client->io_lock); - pthread_mutex_destroy(&client->ref_lock); - free(client); - } -} - -/* Send formatted string to client */ -int client_printf(client_t *client, const char *fmt, ...) { - char buffer[2048]; - va_list args; - va_start(args, fmt); - int len = vsnprintf(buffer, sizeof(buffer), fmt, args); - va_end(args); - - /* Check for buffer overflow or encoding error */ - if (len < 0 || len >= (int)sizeof(buffer)) { - return -1; - } - - return client_send(client, buffer, len); -} - -static int client_channel_window_change(ssh_session session, ssh_channel channel, - int width, int height, - int pxwidth, int pxheight, - void *userdata) { - (void)session; - (void)channel; - (void)pxwidth; - (void)pxheight; - - client_t *client = (client_t *)userdata; - if (!client) { - return SSH_ERROR; - } - - int w = width; - int h = height; - sanitize_terminal_size(&w, &h); - client->width = w; - client->height = h; - client->redraw_pending = true; - return SSH_OK; -} - -static void client_channel_eof(ssh_session session, ssh_channel channel, - void *userdata) { - (void)session; - (void)channel; - - client_t *client = (client_t *)userdata; - if (client) { - client->connected = false; - } -} - -static void client_channel_close(ssh_session session, ssh_channel channel, - void *userdata) { - (void)session; - (void)channel; - - client_t *client = (client_t *)userdata; - if (client) { - client->connected = false; - } -} - -int client_install_channel_callbacks(client_t *client) { - if (!client || !client->channel) { - return -1; - } - - client->channel_cb = calloc(1, sizeof(struct ssh_channel_callbacks_struct)); - if (!client->channel_cb) { - return -1; - } - - ssh_callbacks_init(client->channel_cb); - client->channel_cb->userdata = client; - client->channel_cb->channel_eof_function = client_channel_eof; - client->channel_cb->channel_close_function = client_channel_close; - client->channel_cb->channel_pty_window_change_function = - client_channel_window_change; - - if (ssh_set_channel_callbacks(client->channel, client->channel_cb) != SSH_OK) { - free(client->channel_cb); - client->channel_cb = NULL; - return -1; - } - - return 0; -} - - /* Initialize SSH server */ int ssh_server_init(int port) { /* Initialize rate-limit / connection-count subsystem */ diff --git a/src/tui.c b/src/tui.c index c26e3fc..cb3670e 100644 --- a/src/tui.c +++ b/src/tui.c @@ -1,4 +1,5 @@ #include "tui.h" +#include "client.h" #include "ssh_server.h" #include "chat_room.h" #include "utf8.h"