Subj : Re-derive terminal flags after a mid-session reload of the user record To : GitLab issue in main/sbbs From : Rob Swindell Date : Fri Sep 04 2026 18:33:19 open https://gitlab.synchro.net/main/sbbs/-/issues/1233 ### Background A terminal session's effective terminal type is reconciled exactly once, in `logon()` (`src/sbbs3/logon.cpp:171-177`): ```c const int manual_term = ANSI | RIP | PETSCII | UTF8; // Note: don't turn off NO_EXASCII flag (issue #923) if ((useron.misc & AUTOTERM) || ((useron.misc & manual_term) && (useron.misc & manual_term) != (autoterm & manual_term)) || ((autoterm & UTF8) && !(useron.misc & UTF8))) { useron.misc &= ~manual_term; useron.misc |= (AUTOTERM | (autoterm & ~NO_EXASCII)); } ``` That write is to memory only. The user record's terminal bits are therefore a *per-account seed* recording the terminal type last detected for that account by any connection — which is deliberate: with `AUTOTERM` off, the manual prompts in `exec/user_terminal.js:113-118` (and the `useredit`/`uedit` checkboxes) default from those stored bits, so a user who turns auto-detection off isn't asked "ANSI terminal? [no]" on an obviously-ANSI terminal. ### Problem Nothing re-applies that reconciliation when the record is reloaded over `sbbs->useron` mid-session, and there are two paths that do exactly that: 1. **`js_user.cpp`** — the end of `js_user_set()` invalidates the cache (`if (!user_is_guest(p->user)) p->cached = false;`), and `js_user_get()` unconditionally calls `p->getuserdat()`, which `fgetuserdat()`s the record into `p->user`. For the global session user object, `p->user` **is** `&sbbs->useron` (`js_CreateUserObject(..., global_user=true)`, `main.cpp:1540`). So any `user.*` property write followed by any `user.*` property read replaces the live user struct with the on-disk record — including whatever terminal type another node last seeded there. 2. **`change_user()`** (`src/sbbs3/str.cpp:1413`) — `;chuser` loads the target user's record into `useron` on the sysop's existing terminal, so the session inherits that account's saved terminal bits. Observed symptom (reported by a sysop, VERT/CVS): log on with a RIP terminal while a second, non-RIP session is online; the non-RIP session begins auto-displaying the `msgscan` menu at the message-reading prompt — a menu `readmsgs.cpp` shows for RIP terminals only. That particular symptom is fixed in 86f6584082, which moved the C++ terminal-capability tests off the record bits and onto `Terminal::supports()` / `Terminal::charset()` (which re-derive from `sbbs->autoterm` in `Terminal::get_flags()`, `terminal.h:117-133`). What remains are the consumers that read the record's copy directly: - `user.compare_ars("RIP")` (also `ANSI`/`PETSCII`/`CP437`/`UTF8`) from JS — `js_chk_ar()` calls `p->getuserdat()` and then the library `chk_ar()` (`userdat.c:2332`), which tests `user->misc & RIP`. (`js_user.cpp:1577` already documents preferring `bbs.compare_ars()` in the terminal server, which resolves to the terminal-aware `sbbs_t::chk_ar()`.) - Stock JS reading `user.settings & USER_RIP` directly, e.g. `exec/load/user_info_prompts.js:228`. - `;chuser`, as above. There is a second-order effect worth noting: after a reload, the session's `useron.misc` holds another connection's terminal bits, and the next whole-word `putusermisc()` (e.g. `user.settings ^= USER_EXPERT` from any of the JS command shells) writes them straight back to the record. A session that never detected RIP can thus re-persist RIP as the account's seed. ### Proposal Extract the `logon()` block into a member function and call it wherever the record is reloaded into `useron`: ```c void sbbs_t::fixup_term_flags(); // declared in sbbs.h ``` Call sites: - `logon()`, where the block is today. - `change_user()` after the successful `getuserdat()`. - `user_private_t::getuserdat()` in `js_user.cpp`, but **only** when the object owns the live session user (`p->user != &p->storage`) and only in the terminal server. Plumbing note: `user_private_t::getuserdat()` (`js_user.cpp:39-49`) has no `JSContext*` and no `sbbs_t*`. Either pass the context in (`p->getuserdat(cx)`) or apply the fixup in `js_user_get()` right after the call. Either way it needs the same "am I in the terminal server?" test the `USER_PROP_MISC` setter already open-codes at `js_user.cpp:656-670` (global has a `bbs` property that is a `js_bbs_class` instance ⇒ `JS_GetContextPrivate()` is the `sbbs_t*`); that is worth factoring into a small helper used by both. ### Explicitly not part of this - **Do not call `update_terminal()` from the reload path.** It deletes and replaces `sbbs->term` (`terminal.cpp:434`); `Terminal::flags()` warns "We have potentially destructed ourselves now… use the new object", and `inkey.cpp:79` carries a `// update_terminal(this); causes later crash` comment. Fix up `useron.misc` only and let the existing lazy self-heal in `Terminal::flags()` (`terminal.cpp:334-340`) swap the object at a safe point. - **Do not stop persisting the `manual_term` bits.** They are the seed described above; not writing them would regress `user_terminal.js`'s manual prompts and the user-editor checkboxes. - **The whole-word `misc` write is a separate issue.** `putusermisc()` (`js_user.cpp:654`, `execmisc.cpp:1549`, `listfile.cpp:533`) writes all 32 bits from a node's private copy, so concurrent sessions clobber each other's unrelated flags — e.g. `EXPERT` flipping between two logged-on terminals. That wants a field-level read-modify-write and is not addressed here. ### Known limitation after the fix With one record and multiple connections the seed remains last-connection-wins; the stored terminal type will track whichever node most recently wrote `misc`. That only matters at the moment someone turns `AUTOTERM` off. ### Test notes A repro that needs no concurrency: `;chuser` into an account whose record has the `RIP` bit set, from a non-RIP terminal, and check `user.compare_ars("RIP")`. The original report needs a RIP terminal and a non-RIP terminal logged on to the same account simultaneously, a `user.*` property write on the non-RIP node (any settings toggle), and then a message scan. — *Authored by Claude (Claude Code), on behalf of @rswindell* --- SBBSecho 3.37-Linux * Origin: Vertrauen - [vert/cvs/bbs].synchro.net (1:103/705) .