• src/sftp/sftp.h sftp_clie

    From Deuc¿@VERT to Git commit to main/sbbs/m on Friday, April 24, 2026 14:19:00
    https://gitlab.synchro.net/main/sbbs/-/commit/cc5aba735c34f4dcbde70869
    Modified Files:
    src/sftp/sftp.h sftp_client.c sftp_pkt.c
    Log Message:
    sftp: fix getstring bounds + distinguish do_open failure modes

    getstring() bounded against pkt->sz - offsetof(data) - pkt->cur -
    sizeof(sz). The extra -sizeof(sz) made the check require 4 bytes of
    trailing slop past the string's actual content, which rejected small
    valid replies in any packet whose allocation was tight (e.g. the
    reply packets extract_packet() hands to the pending waiter). A
    redundant second check after get32() duplicated work get32 had
    already done. Drop the -sizeof(sz) and the redundant second check;
    roll cur back fully on failure so retries see an untouched buffer.

    do_open() previously returned false without setting the per-thread
    err code when anything other than a real SSH_FXP_STATUS reply went
    wrong. Callers saw get_err() == SSH_FX_OK and had no way to tell
    whether the send failed, the reply was NULL, or the reply type was
    unexpected. Now every failure branch sets a specific code: FAILURE
    for local build errors, CONNECTION_LOST for send/delivery failures,
    BAD_MESSAGE for unrecognized or malformed replies.

    Add sftpc_debug_last_reply_type() exposing the type byte of the most
    recent reply for diagnostic messages.

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

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Saturday, April 25, 2026 02:38:00
    https://gitlab.synchro.net/main/sbbs/-/commit/97dd3955d69d482a48a6010f
    Modified Files:
    src/sftp/sftp.h sftp_client.c sftp_server.c src/syncterm/sftp_browser.c Log Message:
    sftp: drop speculative public API surface with no callers

    Eight client ops (sftpc_lstat/fstat/fsetstat/remove/rmdir/mkdir/rename /reclaim) and the sftpc_debug_last_reply_type accessor were declared
    in sftp.h and defined in sftp_client.c with no callers anywhere in
    the tree (verified: src/sftp, src/syncterm, src/sbbs3). They came in
    with f193bd5695 as speculative surface; nothing uses them today, and
    the debug accessor only existed to prepend a reply-type line to a
    single error string in sftp_browser.c.

    sftps_reclaim is internal-only (one self-caller in sftp_server.c);
    make it static rather than exposing it as public API.

    Also drop the dead state->last_reply_type field, its two writes in sftp_client.c, and the do_one_path helper that was only used by the
    removed sftpc_remove/rmdir.

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

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Deuc¿@VERT to Git commit to main/sbbs/m on Tuesday, April 28, 2026 17:47:00
    https://gitlab.synchro.net/main/sbbs/-/commit/50dfd1318ca341eaea03d98b
    Modified Files:
    src/sftp/sftp.h sftp_client.c src/syncterm/Wren.adoc src/syncterm/scripts/syncterm.wren wrentest.wren src/syncterm/term.c wren_bind.c wren_host.c wren_host_internal.h
    Log Message:
    SyncTERM: Wren SFTP API + Input.nextEvent fiber-arg primitive

    Adds a Wren-callable SFTP surface on top of the async sftpc_ library
    and converts Input.nextEvent to the same fiber-arg shape.

    Async ops take the fiber-to-resume as their first argument, fire the
    underlying request, and return immediately:

    SFTP.<op>(fiber, args...) -> null | SFTPError

    null means the request was queued (the caller may yield to receive
    the result via fiber.call); SFTPError means the request couldn't be
    queued (session is gone, OOM at the foreign-method site) Ä no
    callback will fire. All other errors round-trip through the result
    queue so the caller's yield surfaces them as an SFTPError too.

    Common idioms:

    // fire and immediately await
    var r = SFTP.realpath(Fiber.current, ".") || Fiber.yield()

    // multi-fire event loop, demuxed by type
    SFTP.stat(Fiber.current, "/a")
    SFTP.stat(Fiber.current, "/b")
    Input.nextEvent(Fiber.current)
    while (true) {
    var x = Fiber.yield()
    if (x is SFTPStat) ...
    if (x is KeyEvent) { Input.nextEvent(Fiber.current); ... }
    if (x is SFTPError) break
    }

    // hook-friendly: callback fiber, calling fiber doesn't yield
    SFTP.realpath(Fiber.new {|r| ... }, ".")

    Library additions (src/sftp/):

    sftpc_mkdir / sftpc_rmdir / sftpc_remove / sftpc_rename Ä four
    status-only async ops that were stripped in 97dd3955d6. They
    reuse the bare struct sftpc_pending plus parse_status_only and
    share a do_one_path helper for the single-path variants.

    Wren classes (foreign):

    SFTP Ä static-only; available, pubdir, plus 12 async ops
    (realpath, stat, opendir, readdir, close, open,
    read, write, mkdir, rmdir, remove, rename).
    SFTPEntry Ä name, longname, size, mtime, isDir, hash.
    SFTPStat Ä size, mtime, atime, mode, uid, gid.
    SFTPHandle Ä opaque server file/dir handle; finalizer fires
    sftpc_close fire-and-forget when GC'd.
    SFTPError Ä code (sftp_err_code_t), serverStatus (SSH_FX_*),
    message, isTransient.
    FileFlag Ä six SSH_FXF_* bitmask constants for SFTP.open.
    Zero-copy delivery: the recv-thread cb runs under state->mtx and
    just stamps ctx->pending = p + pushes onto the result queue. The
    owner-thread deliver fn reads typed-pending fields directly
    (sftp_str_t bytes, sftpc_attrs getters, sftpc_dir_entry array) and
    frees the pending in the queue's free fn. No memcpy under the
    mutex; the only Wren-heap copy is wrenSetSlotBytes / strdup at
    delivery, which is unavoidable.

    Input.nextEvent converted to the same shape:

    Input.nextEvent(fiber) -> null

    ...replacing the prior Input.park_(fiber) primitive + Input.nextEvent() auto-yielding wrapper. The wrapper existed only to work around now-
    removed quirks (implicit CTerm.suspended setting, the now-dropped Hook.onOutput). Dropping it lets hooks pass Fiber.new {|ev| ...}
    for callback-style delivery without nesting Fiber.new {...}.call().
    Throws if another fiber is already registered (single-subscriber
    is structural).

    Wren.adoc, wrentest.wren, and the relevant internal-comment
    references updated to match the new API.

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