• input_thread: client-socket errors misreported as "local spy socket" e

    From Rob Swindell@1:103/705 to GitLab issue in main/sbbs on Sun Jul 5 23:18:20 2026
    open https://gitlab.synchro.net/main/sbbs/-/issues/1184

    Seen in `data/error.log` (four occurrences: 2025-01-03, 2025-05-11, 2025-08-30, 2026-07-05, all on the Linux host):

    ```
    Sun Jul 5 14:33:15 2026 master/90b6a1343 cvs.synchro.net
    term Node 11 !ERROR 9 (Bad file descriptor) on local spy socket 179 receive
    ```

    No spy (`localspy*.sock`) connection was involved. Journal correlation for the 2026-07-05 occurrence: fd 179 was the node's **passthru connect socket** — which for an SSH session *is* the node's `client_socket` — and the error fired one second after normal session teardown ("disconnecting client") closed it:

    ```
    13:30:51 term Node 11 passthru connect socket 179 opened
    14:33:13 term Node 11 <...> Executing post external program execution module: postxtrn ...
    14:33:14 term Node 11 <...> disconnecting client
    14:33:14 term Node 11 passthru connection reset by peer on receive
    14:33:15 term Node 11 !ERROR 9 (Bad file descriptor) on local spy socket 179 receive
    ```

    ## Root cause

    In `input_thread()` (`src/sbbs3/main.cpp`), the socket that was polled/`recv()`ed is identified **after the fact** by re-comparing against the mutable atomic:

    ```c
    if (rd == SOCKET_ERROR)
    {
    #ifdef __unix__
    if (sock == sbbs->client_socket) { // <-- re-reads the atomic, post-recv
    ...normal client-disconnect handling...
    } else { // <-- assumes "not client => spy socket"
    errprintf(LOG_ERR, WHERE, "Node %d !ERROR %d (%s) on local spy socket %d receive", ...);
    close_socket(uspy_socket[node-1]);
    ...
    ```

    Normal *nix hangup wakes `input_thread` by closing the client socket from another thread and storing `client_socket = INVALID_SOCKET`. When the wake-up `recv()` fails with `EBADF`, the identity re-check compares the old fd against the now-`INVALID_SOCKET` atomic, fails, and the error is misrouted into the "must be the local spy socket" branch: a routine disconnect gets logged as a LOG_ERR spy-socket failure (with the confusing fd number of the just-closed client/passthru socket) and lands in `error.log`. The spy-branch cleanup then does `close_socket(INVALID_SOCKET)`, which is harmless, so the defect is cosmetic/log-noise — but it reliably sends sysops hunting for spy-socket or passthru problems that don't exist.

    The same stale re-comparison pattern exists at three more sites in `input_thread()`:

    - the `ssh_mode && sock == sbbs->client_socket` receive-path selection,
    - the `rd == 0 && sock == sbbs->client_socket` EOF/"disconnected" check,
    - the `sock != sbbs->client_socket` skip-telnet-interpretation check.

    Each can misclassify the socket when `client_socket` is cleared between poll-time selection and use.

    ## Suggested fix

    Record which socket was selected at poll time (e.g. a `bool spy_sock` set alongside `sock = ...`) and branch on that instead of re-comparing `sock` against `sbbs->client_socket` at the four sites above. A genuinely-errored spy socket keeps the existing log+close+clear handling; a closed client socket routes to the existing client-disconnect handling (which already breaks quietly when `!sbbs->online`).

    — *Authored by Claude (Claude Code), on behalf of @rswindell*
    --- SBBSecho 3.37-Linux
    * Origin: Vertrauen - [vert/cvs/bbs].synchro.net (1:103/705)
  • From Rob Swindell@1:103/705 to GitLab issue in main/sbbs on Mon Jul 6 00:00:18 2026
    close https://gitlab.synchro.net/main/sbbs/-/issues/1184
    --- SBBSecho 3.37-Linux
    * Origin: Vertrauen - [vert/cvs/bbs].synchro.net (1:103/705)
  • From Rob Swindell@1:103/705 to GitLab note in main/sbbs on Mon Jul 6 00:00:31 2026
    https://gitlab.synchro.net/main/sbbs/-/issues/1184#note_9531

    Fixed in 39112546f2 (tables-20-ideas): input_thread() now records which socket was selected at poll time (spy vs. client) and branches on that, instead of re-comparing against the mutable atomic client_socket at the four affected sites. A post-hangup wake-up now routes to the client branch and exits quietly via the existing !online check.

    Validated against a live terminal server: client disconnect, spy connect/mirror/disconnect (clean 'Closing local spy socket' notice), and server-terminated-mid-session teardown — no misattributed spy-socket errors logged.

    Related follow-up in 8101584ded (symbol-19-seek): the server-termination path now uses shutdown() instead of close_socket() on node client sockets, eliminating the double-close (EBADF) noise and fd-reuse hazard from the same teardown sequence.

    — *Authored by Claude (Claude Code), on behalf of @rswindell*
    --- SBBSecho 3.37-Linux
    * Origin: Vertrauen - [vert/cvs/bbs].synchro.net (1:103/705)