• src/xpdev/filewrap.c

    From Deuc¿@VERT to Git commit to main/sbbs/master on Sunday, March 15, 2026 16:01:53
    https://gitlab.synchro.net/main/sbbs/-/commit/e4122aa602189ce9e5154c56
    Modified Files:
    src/xpdev/filewrap.c
    Log Message:
    Remove vestigial (int) casts truncating off_t lock length

    Both lock() and unlock() cast the off_t len parameter to int before
    assigning to alock.l_len (which is off_t). The cast silently
    truncates lock lengths on files > 2GB. Both sides are already off_t,
    so the cast is unnecessary.

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

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell (on Windows 11)@VERT to Git commit to main/sbbs/master on Wednesday, June 03, 2026 09:44:35
    https://gitlab.synchro.net/main/sbbs/-/commit/e3fd4e7cf195c14e9e12c577
    Modified Files:
    src/xpdev/filewrap.c
    Log Message:
    xpdev/filewrap: flock path of xp_lockfile() honors fd open mode too (#1153)

    The non-Linux POSIX (flock) path of xp_lockfile() always took LOCK_EX,
    even for a read-only descriptor, unlike the Linux/fcntl path which
    derives the lock type from the fd's open mode. So on FreeBSD/macOS/etc. (USE_FCNTL_LOCKS is Linux-only) a read-only lock() serialized concurrent readers - the same exclusive-lock-on-reads issue fixed for Windows in the previous commit. flock() (unlike fcntl()) isn't forced to a lock type by
    the access mode, so the LOCK_EX was a choice; mirror the fcntl path and
    take LOCK_SH for an O_RDONLY fd.

    The user.tab read path is already covered on these platforms (rdlock()'s
    flock branch uses LOCK_SH); this brings the lock() primitive itself to
    parity, so any read-only-fd caller benefits. flock remains whole-file
    (pos/len ignored) - a pre-existing coarseness, unchanged here.

    Not compiled in this environment (the flock branch builds only on
    non-Linux POSIX); mirrors the existing fcntl-branch idiom.

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

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell (on Debian Linux)@VERT to Git commit to main/sbbs/master on Saturday, August 08, 2026 18:05:04
    https://gitlab.synchro.net/main/sbbs/-/commit/7384ba6569de8ffbf0720f2d
    Modified Files:
    src/xpdev/filewrap.c
    Log Message:
    Close descriptors with closefrom() rather than close_range()

    closefrom() exists on the BSDs, Solaris and glibc 2.34 and later, where close_range() is a Linux syscall this code was reaching for directly. On
    glibc, closefrom() is implemented in terms of close_range() anyway, so
    calling it gets the better mechanism where there is one and a working
    fallback where there isn't, without this file having to know which.

    Keeping one descriptor no longer needs a range API at all: close everything above it in a single call, and walk the few below it. That loop is bounded by the kept descriptor rather than by the process descriptor limit, which can be very large. Platforms with neither call (macOS) still consult sysconf(_SC_OPEN_MAX).

    Also spell the first descriptor to close as STDERR_FILENO + 1 rather than 3.

    Suggested by Deuce.

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

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell (on Debian Linux)@VERT to Git commit to main/sbbs/master on Wednesday, November 11, 2026 21:04:16
    https://gitlab.synchro.net/main/sbbs/-/commit/36867a9bbfd65e97159fe358
    Modified Files:
    src/xpdev/filewrap.c
    Log Message:
    xpdev: release a CIFS server lock left behind by a failed OFD lock

    On Linux, lock() and rdlock() use OFD fcntl() locks. On a CIFS mount
    (e.g. a Synchronet tree shared over SMB, or a loopback mount of the local
    Samba share), the kernel client acquires the byte-range lock on the
    server first and only then records it in the local VFS lock table. When
    that local step fails with EAGAIN - a conflict with another open file description's lock that is still being released locally - the request
    fails but the server lock is not rolled back.

    The client also shares one SMB handle among concurrent opens of a file
    with identical flags, so the orphaned lock blocks every lock request on
    that range (EACCES) until the process closes its last descriptor for the
    file. Since every thread waiting for the lock holds the file open while it retries, the lock persists until all waiters time out. For user.tab that
    is the full lockuserdat()/rdlockuserdat() retry budget, about 106
    seconds, ending in USER_LOCK_ERROR (-104): e.g. "!ERROR -104 in
    logoutuserdat" when several SMTP sessions authenticate as the same user
    at once.

    When a non-blocking lock fails with EAGAIN on a CIFS/SMB2 filesystem,
    unlock the same range on the same descriptor, releasing the server lock.
    EACCES (a conflict reported by the server) grants nothing and is left
    alone, as are local filesystems, where unlocking after a failed request
    could trim an overlapping lock the descriptor already holds.

    Reproduced with two threads repeatedly taking a shared lock, then an
    exclusive lock, on the same 1000-byte record of a file on a CIFS mount
    (Linux 6.1, Samba 4.22): the first EAGAIN stalled every run, within a few thousand iterations. With this change, 18,200 iterations including 7
    EAGAIN failures completed without a stall. Process-associated F_SETLK
    locks never stalled, as they cannot conflict within one process locally.

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

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