commands: reject :nick collisions with active clients (UX-3)

:nick used to swap client->username unconditionally, so two clients
could both end up named "alice" — and the subsequent :msg / :w
disambiguation would just hit whichever came first in g_room->clients.

Now the rename happens under wrlock with an explicit scan: if any
*other* client already owns that username, the change is refused with

    Nickname 'alice' is already taken

If the requested name equals the current one, return a short
"Nickname unchanged" instead of broadcasting a no-op system message.

The room-lock-held scan is O(n) over current clients (n ≤ 64 by
default) and folds naturally into the existing wrlock, so there's no
new lock acquisition.
This commit is contained in:
m1ngsama 2026-05-17 13:44:25 +08:00
parent f3217de36b
commit b1353d904b

View file

@ -164,21 +164,47 @@ void commands_dispatch(client_t *client) {
utf8_truncate(validated_name, 20);
}
/* Reject collisions with active room members. Held under
* wrlock so the username swap below races neither read nor
* concurrent :nick from another client. */
char old_name[MAX_USERNAME_LEN];
bool taken = false;
pthread_rwlock_wrlock(&g_room->lock);
snprintf(old_name, sizeof(old_name), "%s", client->username);
snprintf(client->username, MAX_USERNAME_LEN, "%s", validated_name);
if (strcmp(validated_name, old_name) != 0) {
for (int i = 0; i < g_room->client_count; i++) {
if (g_room->clients[i] == client) continue;
if (strcmp(g_room->clients[i]->username,
validated_name) == 0) {
taken = true;
break;
}
}
}
if (!taken) {
snprintf(client->username, MAX_USERNAME_LEN, "%s", validated_name);
}
pthread_rwlock_unlock(&g_room->lock);
message_t nick_msg = { .timestamp = time(NULL) };
snprintf(nick_msg.username, MAX_USERNAME_LEN, "系统");
snprintf(nick_msg.content, MAX_MESSAGE_LEN,
"%s 更名为 %s", old_name, client->username);
room_broadcast(g_room, &nick_msg);
message_save(&nick_msg);
if (taken) {
buffer_appendf(output, sizeof(output), &pos,
"Nickname '%s' is already taken\n",
validated_name);
} else if (strcmp(validated_name, old_name) == 0) {
buffer_appendf(output, sizeof(output), &pos,
"Nickname unchanged\n");
} else {
message_t nick_msg = { .timestamp = time(NULL) };
snprintf(nick_msg.username, MAX_USERNAME_LEN, "系统");
snprintf(nick_msg.content, MAX_MESSAGE_LEN,
"%s 更名为 %s", old_name, client->username);
room_broadcast(g_room, &nick_msg);
message_save(&nick_msg);
buffer_appendf(output, sizeof(output), &pos,
"Nickname changed: %s -> %s\n", old_name, client->username);
buffer_appendf(output, sizeof(output), &pos,
"Nickname changed: %s -> %s\n",
old_name, client->username);
}
}
} else if (strncmp(cmd, "last", 4) == 0 && (cmd[4] == ' ' || cmd[4] == '\0')) {