• smbutil pack: not error-safe — silent abort + destructive rename bef

    From Rob Swindell@1:103/705 to GitLab issue in main/sbbs on Tue Jun 23 15:48:30 2026
    open https://gitlab.synchro.net/main/sbbs/-/issues/1171

    ## Summary

    `packmsgs()` in `src/sbbs3/smbutil.c` is not error/crash-safe. It renames the live `.shd/.sdt/.sid` to `*_` **first**, writes the surviving messages to `*$`, then performs the final swap. If reading any single message's data fails mid-copy, it takes a **bare `return` with no error message**, *before* the swap — leaving the base with **no `.shd`** (renamed to `.shd_` and never restored). One unreadable/corrupt message destroys the whole base, silently.

    ## Root-caused incident (2026-06-23)

    `smbutil -a mp mail` was run on a 2 GB `mail` base. Message `#711730` had a corrupt `data_offset` of `0xFFFFFF88` (see the companion "header committed on allocation failure" issue). During pack:

    ```c
    fseek(smb.sdt_fp, msg.hdr.offset, SEEK_SET); // seek to 0xFFFFFF88 (~4.3GB) ...
    for (m = 0; m < n; m++)
    if (fread(buf, 1, SDT_BLOCK_LEN, smb.sdt_fp) < 1) {
    free(datoffset);
    return; // <-- past-EOF read → silent return, before the swap
    }
    ```

    Result: `.shd/.sdt/.sid` left as `*_`/`*$`, no live `.shd`; the lock was released on exit; `mailsrvr` then opened the base, found no header, and **created a fresh empty base**. (Recovered from the `_`/`$` temp files + `fixsmb`; no messages were actually lost.)

    ## Specific problems

    1. **Silent failure** — the data-block read-failure path prints nothing (no `fprintf(errfp, ...)`, unlike the neighbouring length/alloc checks which `continue`).
    2. **No rollback** — on any failure after the rename, the `*_` originals are not restored, so the base is left header-less.
    3. **All-or-nothing** — a single corrupt/unreadable message aborts the entire pack; it should log and `continue` past it (like the existing >16 MB "invalid data length" case) and/or roll back.
    4. **(minor/related)** the `-a` (`NOANALYSIS`) flag also makes `maint()` skip the "Freeing allocated header and data blocks for deleted messages" step, leaving orphaned blocks (later reclaimed by a successful pack).

    ## Suggested fix

    On any error after the rename, restore `*_` → live (rollback) before returning; log the offending message number/offset; consider skipping unreadable messages rather than abandoning the pack.

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