• exec/tests/qwkjob/fixture.qwk hub.jsexec/tests/qwkjob/skipif src/sbbs3

    From Rob Swindell (on Debian Linux)@VERT to Git commit to main/sbbs/master on Mon Oct 26 00:09:03 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/3c3577994c6e06dbfcc34fa3
    Added Files:
    exec/tests/qwkjob/fixture.qwk hub.js
    Modified Files:
    exec/tests/qwkjob/skipif src/sbbs3/js_system.cpp pack_rep.cpp qwkjob.cpp qwkjob.h sbbs.h
    Log Message:
    qwkjob: QWKnet hub REP packing and QWK import for any server or script

    Add the hub direction of the QWK job API, so this BBS acting as a QWKnet
    node can pack a REP for, or import a QWK packet from, a hub outside the Terminal Server's event thread:

    - qwkjob_pack_rep() loads the hub's export pointers
    (data/qnet/<HUBID>.ptr), runs pack_rep() into data/<HUBID>.REP (an
    unsent REP is merged into, as before) and saves the pointers. It does
    not touch ctrl/time.ini or run the call-out command; those stay with
    the event thread's schedule.
    - qwkjob_unpack_qwk() imports a QWK packet from any path; on success the
    file is removed, on failure it is kept as
    data/<HUBID>.qwk.<hex-time>.bad (newest 10 kept), so a sysop can retry
    it by renaming it back for the event thread's sweep. An empty packet is
    discarded without an error.
    - Both are serialized per hub by data/qnet/<HUBID>.lock and return
    "busy" when it is held. qwkjob_lock_hub(), qwkjob_unlock_hub() and
    qwkjob_pack_rep_locked() let the event thread hold that lock across
    the pack and the call-out, so a scripted pack can't merge messages into
    a REP the call-out is sending (and then deleting).
    - An unknown or disabled hub is "bad_hub"; qwkjob_find_hub() matches a
    hub ID case-insensitively.
    - pack_rep() gains an optional out-parameter counting what it packed: it
    returns true both when it packed something and when there was nothing
    new (so the pointers get saved), which callers could not tell apart.
    - system.qwk_pack(hub_id) and system.qwk_unpack(hub_id, path) now select
    the hub direction for a string argument.

    The hub job's sbbs_t has no user (useron.number == 0, online ==
    ON_LOCAL), as in the event thread.

    Audit of the hub paths in a headless job (pack_rep.cpp, un_qwk.cpp, qwktomsg.cpp, netmail.cpp qwktonetmail(), qwk.cpp update_qwkroute()):
    - term->lncntr/newline() in pack_rep(): the job's discarding Terminal.
    - useron: every use in qwk_import_msg() and qwktonetmail() is gated on
    fromhub or reads the zeroed user (not a QWK node, not a sysop), the
    same as the event thread.
    - external() with the hub's pack/unpack commands, when libarchive can't
    do the job: both ran in a job with the zeroed job startup (zip packing
    a REP for an unsupported format, and an unpack command after a
    libarchive extraction failure). As in the event thread, a command
    whose program isn't listed as native in xtrn.ini is treated as a DOS
    program and not run.
    - sbbs_t::terminated (unpack_qwk's per-message check) stays false in a
    job; host->terminated is polled only between job steps.
    - update_qwkroute() rewrites data/qnet/route.dat without a lock, so two
    imports at once (different hubs, or a hub and a QWK node's REP) can
    lose a route entry; already true across hosts today.
    - mqtt, startup->, ::lputs and node_num uses: none beyond those audited
    for the user direction.

    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

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