• docs/v322_new.md exec/tests/file/popen.js src/sbbs3/js_file.cpp src/xp

    From Rob Swindell (on Windows 11)@VERT to Git commit to main/sbbs/master on Sun Sep 20 23:06:07 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/17981f079f919fc563891aa9
    Modified Files:
    docs/v322_new.md exec/tests/file/popen.js src/sbbs3/js_file.cpp src/xpdev/genwrap.c genwrap.h
    Log Message:
    File.popen() on Windows, and bidirectionally on every platform

    The File object's pipe support has been Unix-only since it was added in 06ee2c71fb: on Windows, C's _popen() requires the calling process to own
    a console, which a service or anything launched by sbbsctrl.exe does not.
    Its documented default mode narrowed that further -- "r+" is a BSD
    extension that glibc rejects with EINVAL, so the default has been failing
    on Linux and working only on FreeBSD and macOS.

    New in xpdev:

    xp_pipe_open() - popen() with the same three modes everywhere. On
    Windows, "r" and "w" get a CreatePipe() and a CREATE_NO_WINDOW child;
    "r+" gets a uniquely-named single-instance duplex named pipe, an
    anonymous one being one-way. Our end becomes a real FILE* through
    _open_osfhandle() and fdopen(), so File.read(), readln() and write()
    work against it unchanged. Elsewhere "r" and "w" still go through
    popen(), and "r+" gets a socketpair()-connected /bin/sh child, which is
    how the BSD popen() implements that mode.

    xp_pipe_close() - closes the stream first, so a command reading it sees
    EOF, then waits for it and returns its exit status.

    js_close() uses xp_pipe_close(), and so does the File finalizer, which
    until now fclose()d a pipe -- leaking the child process on every
    platform. The JSDOC drops "only functional on UNIX systems" and picks up
    the 'b' (binary) suffix and the flush()-between-write-and-read rule that
    any bidirectional stream needs.

    exec/tests/file/popen.js no longer exits early on Windows: it covers "r",
    the default "r+", "w" into a command whose output file must be complete
    once close() returns, and an unusable mode. The POSIX side was checked
    with gcc against a C harness over the same three modes, exit-status
    propagation and child reaping.

    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net