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)