• sexyz: buffered YMODEM receiver drops the batch-terminating ACK

    From Deucе@1:103/705 to GitLab issue in main/sbbs on Sun Oct 25 17:03:29 2026
    open https://gitlab.synchro.net/main/sbbs/-/work_items/1300

    `sexyz` receives every file and recognizes the final empty YMODEM header, but exits without transmitting its ACK. A sender that checks the final handshake reports failure (or waits/retries until its timeout), even though the received files are complete.

    Reproduced with the native FreeBSD `sexyz` binary reporting `v3.6 master/6986ac66b7`. The corresponding source at that revision has the same buffering and exit paths described below.

    ## Reproduction

    1. Connect a YMODEM sender to `sexyz -raw ry` using a bidirectional socket on stdin/stdout. Send a batch (a single small file is sufficient).
    2. Trace both directions through completion. All file data and EOT handshakes succeed.
    3. Repeat with `sexyz -raw -s ry`, or `sexyz -raw -w4096 ry`. Both options disable the producer buffer, and the terminating ACK appears.

    The two runs used the same sender, payload and socket relay. `sexyz` exits with status 0 in both cases. Its default-mode log ends with:

    ```text
    Fetching YMODEM header block
    Block 0: Requesting mode: 16-bit CRC
    Received YMODEM termination block
    Exiting - Error level: 0, flows: 0, select_errors=0
    ```

    Wire trace at the end of the default-mode transfer:

    ```text
    sender -> receiver: EOT (04)
    receiver -> sender: NAK (15)
    sender -> receiver: EOT (04)
    receiver -> sender: ACK (06)
    receiver -> sender: C (43), requesting the next header
    sender -> receiver: SOH 00 FF, 128 zero bytes, CRC16 00 00
    receiver -> sender: EOF; no terminating ACK
    ```

    With `-s` or `-w4096`, the receiver sends `06` after that final empty header and before EOF. These are diagnostic controls here; YMODEM should not depend on ZMODEM window/streaming options.

    ## Cause

    In [src/sbbs3/sexyz.c at 6986ac66b7](https://gitlab.synchro.net/main/sbbs/-/blob/6986ac66b7/src/sbbs3/sexyz.c):

    - `send_buffered` is enabled whenever the ZMODEM window is unlimited and segmented mode is off, including during X/YMODEM.
    - `send_byte()` then accumulates bytes in the producer's `txbuf`, ahead of the `outbuf` ring.
    - The YMODEM receiver calls `send_byte(NULL, ACK, 10)` after accepting header block 0. If its filename is empty, `receive_files()` returns success immediately, without flushing that ACK.
    - Ordinary headers work because the next receive operation calls `tx_flush(10)` before waiting for input. There is no next receive after the empty header.
    - The shutdown path waits only for `outbuf` to empty. It never flushes the remaining producer `txbuf`, so the final ACK never reaches the output thread or socket.

    ## Expected behavior / fix direction

    Flush the final YMODEM header ACK before returning success, check transmission errors, and ensure normal shutdown drains the producer buffer and the output worker's outstanding bytes. Restricting producer buffering to its intended streaming use would also prevent X/YMODEM from depending on ZMODEM settings, but shutdown should still handle buffered output correctly.

    A regression test should verify a real `06` on the wire after the empty batch header under the default options, without requiring another incoming byte to trigger a flush. It should also check that an ACK queued immediately before process exit is not lost.
    --- SBBSecho 3.38-Linux
    * Origin: Vertrauen - [vert/cvs/bbs].synchro.net (1:103/705)