fix: data races, missing persistence, and input safety bugs

- Make client width/height _Atomic to fix data race between window-change
  callback thread and session thread
- Persist join/leave system messages via message_save()
- Fix ftell() error handling: check <= 0 instead of == 0
- Make room_add_message static to enforce lock-must-be-held contract
- Use local copy in execute_command to avoid mutating command_input
- Increase help_copy buffer from 4096 to 8192 for CJK text safety
- Add :q/:quit/:exit command for Vim-style disconnect
- Fix unit test Makefile to link common.c
This commit is contained in:
m1ngsama 2026-04-19 17:14:48 +08:00
parent 0de13a6314
commit 9ca1e9d977
7 changed files with 40 additions and 31 deletions

View file

@ -36,9 +36,6 @@ void room_remove_client(chat_room_t *room, struct client *client);
/* Broadcast message to all clients */ /* Broadcast message to all clients */
void room_broadcast(chat_room_t *room, const message_t *msg); void room_broadcast(chat_room_t *room, const message_t *msg);
/* Add message to room history */
void room_add_message(chat_room_t *room, const message_t *msg);
/* Get message by index (thread-safe value copy) */ /* Get message by index (thread-safe value copy) */
bool room_get_message(chat_room_t *room, int index, message_t *out); bool room_get_message(chat_room_t *room, int index, message_t *out);

View file

@ -13,8 +13,8 @@ typedef struct client {
ssh_channel channel; /* SSH channel */ ssh_channel channel; /* SSH channel */
char username[MAX_USERNAME_LEN]; char username[MAX_USERNAME_LEN];
char client_ip[INET6_ADDRSTRLEN]; char client_ip[INET6_ADDRSTRLEN];
int width; _Atomic int width;
int height; _Atomic int height;
client_mode_t mode; client_mode_t mode;
help_lang_t help_lang; help_lang_t help_lang;
int scroll_pos; int scroll_pos;

View file

@ -87,23 +87,9 @@ void room_remove_client(chat_room_t *room, struct client *client) {
pthread_rwlock_unlock(&room->lock); pthread_rwlock_unlock(&room->lock);
} }
/* Broadcast message to all clients */ /* Add message to room history (caller must hold write lock) */
void room_broadcast(chat_room_t *room, const message_t *msg) { static void room_add_message(chat_room_t *room, const message_t *msg) {
pthread_rwlock_wrlock(&room->lock);
/* Add to history */
room_add_message(room, msg);
room->update_seq++;
pthread_rwlock_unlock(&room->lock);
}
/* Add message to room history */
void room_add_message(chat_room_t *room, const message_t *msg) {
/* Caller should hold write lock */
if (room->message_count >= MAX_MESSAGES) { if (room->message_count >= MAX_MESSAGES) {
/* Shift messages to make room */
memmove(&room->messages[0], &room->messages[1], memmove(&room->messages[0], &room->messages[1],
(MAX_MESSAGES - 1) * sizeof(message_t)); (MAX_MESSAGES - 1) * sizeof(message_t));
room->message_count = MAX_MESSAGES - 1; room->message_count = MAX_MESSAGES - 1;
@ -112,6 +98,16 @@ void room_add_message(chat_room_t *room, const message_t *msg) {
room->messages[room->message_count++] = *msg; room->messages[room->message_count++] = *msg;
} }
/* Broadcast message to all clients */
void room_broadcast(chat_room_t *room, const message_t *msg) {
pthread_rwlock_wrlock(&room->lock);
room_add_message(room, msg);
room->update_seq++;
pthread_rwlock_unlock(&room->lock);
}
/* Get message by index (thread-safe value copy) */ /* Get message by index (thread-safe value copy) */
bool room_get_message(chat_room_t *room, int index, message_t *out) { bool room_get_message(chat_room_t *room, int index, message_t *out) {
if (!room || !out) return false; if (!room || !out) return false;

View file

@ -61,7 +61,7 @@ int message_load(message_t **messages, int max_messages) {
} }
long file_size = ftell(fp); long file_size = ftell(fp);
if (file_size == 0) { if (file_size <= 0) {
fclose(fp); fclose(fp);
*messages = msg_array; *messages = msg_array;
return 0; return 0;

View file

@ -1069,7 +1069,10 @@ static int execute_exec_command(client_t *client) {
/* Execute a command */ /* Execute a command */
static void execute_command(client_t *client) { static void execute_command(client_t *client) {
char *cmd = client->command_input; char cmd_buf[256];
strncpy(cmd_buf, client->command_input, sizeof(cmd_buf) - 1);
cmd_buf[sizeof(cmd_buf) - 1] = '\0';
char *cmd = cmd_buf;
char output[2048] = {0}; char output[2048] = {0};
size_t pos = 0; size_t pos = 0;
@ -1118,8 +1121,14 @@ static void execute_command(client_t *client) {
"list, users, who - Show online users\n" "list, users, who - Show online users\n"
"help, commands - Show this help\n" "help, commands - Show this help\n"
"clear, cls - Clear command output\n" "clear, cls - Clear command output\n"
"q, quit, exit - Disconnect from chat\n"
"========================================\n"); "========================================\n");
} else if (strcmp(cmd, "q") == 0 || strcmp(cmd, "quit") == 0 ||
strcmp(cmd, "exit") == 0) {
client->connected = false;
return;
} else if (strcmp(cmd, "clear") == 0 || strcmp(cmd, "cls") == 0) { } else if (strcmp(cmd, "clear") == 0 || strcmp(cmd, "cls") == 0) {
buffer_appendf(output, sizeof(output), &pos, "Command output cleared\n"); buffer_appendf(output, sizeof(output), &pos, "Command output cleared\n");
@ -1367,6 +1376,7 @@ void* client_handle_session(void *arg) {
join_msg.username[MAX_USERNAME_LEN - 1] = '\0'; join_msg.username[MAX_USERNAME_LEN - 1] = '\0';
snprintf(join_msg.content, MAX_MESSAGE_LEN, "%s 加入了聊天室", client->username); snprintf(join_msg.content, MAX_MESSAGE_LEN, "%s 加入了聊天室", client->username);
room_broadcast(g_room, &join_msg); room_broadcast(g_room, &join_msg);
message_save(&join_msg);
/* Render initial screen */ /* Render initial screen */
tui_render_screen(client); tui_render_screen(client);
@ -1496,6 +1506,7 @@ cleanup:
client->connected = false; client->connected = false;
room_remove_client(g_room, client); room_remove_client(g_room, client);
room_broadcast(g_room, &leave_msg); room_broadcast(g_room, &leave_msg);
message_save(&leave_msg);
} }
release_ip_connection(client->client_ip); release_ip_connection(client->client_ip);
@ -1768,9 +1779,11 @@ static int client_channel_window_change(ssh_session session, ssh_channel channel
return SSH_ERROR; return SSH_ERROR;
} }
client->width = width; int w = width;
client->height = height; int h = height;
sanitize_terminal_size(&client->width, &client->height); sanitize_terminal_size(&w, &h);
client->width = w;
client->height = h;
client->redraw_pending = true; client->redraw_pending = true;
return SSH_OK; return SSH_OK;
} }
@ -1942,9 +1955,11 @@ static void *bootstrap_client_session(void *arg) {
client->session = session; client->session = session;
client->channel = channel; client->channel = channel;
client->width = ctx->pty_width; int init_w = ctx->pty_width;
client->height = ctx->pty_height; int init_h = ctx->pty_height;
sanitize_terminal_size(&client->width, &client->height); sanitize_terminal_size(&init_w, &init_h);
client->width = init_w;
client->height = init_h;
client->ref_count = 1; client->ref_count = 1;
pthread_mutex_init(&client->ref_lock, NULL); pthread_mutex_init(&client->ref_lock, NULL);
pthread_mutex_init(&client->io_lock, NULL); pthread_mutex_init(&client->io_lock, NULL);

View file

@ -386,7 +386,7 @@ void tui_render_help(client_t *client) {
/* Help content */ /* Help content */
const char *help_text = tui_get_help_text(client->help_lang); const char *help_text = tui_get_help_text(client->help_lang);
char help_copy[4096]; char help_copy[8192];
strncpy(help_copy, help_text, sizeof(help_copy) - 1); strncpy(help_copy, help_text, sizeof(help_copy) - 1);
help_copy[sizeof(help_copy) - 1] = '\0'; help_copy[sizeof(help_copy) - 1] = '\0';

View file

@ -6,6 +6,7 @@ LDFLAGS = -pthread
# Source files # Source files
UTF8_SRC = ../../src/utf8.c UTF8_SRC = ../../src/utf8.c
MESSAGE_SRC = ../../src/message.c MESSAGE_SRC = ../../src/message.c
COMMON_SRC = ../../src/common.c
TESTS = test_utf8 test_message TESTS = test_utf8 test_message
@ -16,7 +17,7 @@ all: $(TESTS)
test_utf8: test_utf8.c $(UTF8_SRC) test_utf8: test_utf8.c $(UTF8_SRC)
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
test_message: test_message.c $(MESSAGE_SRC) $(UTF8_SRC) test_message: test_message.c $(MESSAGE_SRC) $(UTF8_SRC) $(COMMON_SRC)
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
run: all run: all