Skip to content

fix(netbsd): add NetBSD support to daemon IPC and runtime - #1342

Merged
DeusData merged 1 commit into
DeusData:mainfrom
cmeerw:fix/netbsd-daemon-support
Aug 28, 2026
Merged

fix(netbsd): add NetBSD support to daemon IPC and runtime#1342
DeusData merged 1 commit into
DeusData:mainfrom
cmeerw:fix/netbsd-daemon-support

Conversation

@cmeerw

@cmeerw cmeerw commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Extend Linux codepaths in runtime.c to also cover NetBSD, and add NetBSD-specific peer PID retrieval via LOCAL_PEEREID in ipc.c.

@cmeerw
cmeerw requested a review from DeusData as a code owner July 29, 2026 21:45
@DeusData

Copy link
Copy Markdown
Owner

Thank you for this — it is a careful, minimal port, and I want to tell you plainly where it stands rather than leaving it silent.

The code review came back clean, and I checked the part that would worry me most. The runtime.c changes are purely || defined(__NetBSD__) added to conditionals that already existed, so they are preprocessor-inert on Linux, macOS and Windows. The new ipc.c branch is guarded on SOL_LOCAL && LOCAL_PEEREID, which are NetBSD-only, and it sits after the Linux SO_PEERCRED and macOS LOCAL_PEERPID branches — so it is unreachable on the three platforms we ship. I verified that specifically, because a widened #ifdef quietly capturing an existing platform is the failure mode that would make a 16-line port dangerous. It does not happen here.

The NetBSD peer-credential logic itself reads correctly — unp_pid with a length check, failing closed to 0 rather than open.

What is holding it is a policy question, not your code. Merging means the project claims NetBSD support, and we have no NetBSD CI leg — so this path would be contributor-verified only, indefinitely. That is a maintainer decision about which platforms we are willing to stand behind without automated coverage, and I have put it in front of him rather than deciding it myself. I would rather give you a real answer than a quiet merge that leaves you as the sole guarantor of a platform forever.

Two things that would help whichever way it goes:

  • A line in the docs stating NetBSD is best-effort and untested by CI would make the commitment honest and explicit.
  • Worth noting in that same place: the shared path relies on /proc/<pid>/exe, which needs procfs mounted and that is not a NetBSD default. It degrades fail-closed — acquire simply returns false — so it is not unsafe, but a NetBSD user hitting it would otherwise have no idea why.

One practical heads-up: #1138 also edits the same #elif defined(__linux__) chains in runtime.c for a FreeBSD port, so whichever of the two moves first will conflict the other. Not something you need to act on now.

Thanks for the patience, and for keeping the change as small as it is — that is a large part of why the review was straightforward.

@DeusData DeusData added enhancement New feature or request priority/backlog Valuable contribution, lower scheduling urgency; review when maintainer capacity opens. labels Aug 3, 2026
@DeusData DeusData added this to the 0.9.2-rc milestone Aug 3, 2026
@DeusData

Copy link
Copy Markdown
Owner

Decision on the BSD pair: FreeBSD (#1467) just merged, and we want NetBSD too — with one fix first. Your NetBSD branch reuses the Linux /proc//exe path, but NetBSD's procfs is an optional mount and 'exe' is its Linux-compat name, so on a default install the identity check would silently fail — the exact class of quiet breakage the daemon code avoids elsewhere. The native idiom would be sysctl KERN_PROC_PATHNAME (see how #1467 did FreeBSD, including the O_NOFOLLOW/fstat double-check). You'll also need a small rebase over #1467's guard renames (runtime_linux_stat_same_image → runtime_posix_stat_same_image). With those two, this merges — with the same honest-limits note FreeBSD carries: no NetBSD CI leg, community-verified path. Thank you for bringing the platform this far.

@cmeerw
cmeerw force-pushed the fix/netbsd-daemon-support branch from f140939 to 24f6819 Compare August 18, 2026 20:42
@DeusData

Copy link
Copy Markdown
Owner

Both code asks are addressed — you replaced /proc/<pid>/exe with the native sysctl KERN_PROC_PATHNAME idiom and rebased over #1467's runtime_posix_stat_same_image rename, and all 34 checks are green. Sorry for the three-day silence after you did that.

One correctness defect before merge, and no CI leg can catch it.

In runtime_process_image_reference_acquire, the NetBSD MIB is built with the PID slot hardcoded:

int mib[4] = {CTL_KERN, KERN_PROC_ARGS, -1, KERN_PROC_PATHNAME};

On NetBSD -1 in that slot means the calling process. But this function's contract is to resolve the image of the peer passed in as process_id — the FreeBSD arm immediately beside it does exactly that (int pid = (int)process_id; then {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, pid}). As written, the NetBSD arm resolves the daemon's own executable, and runtime_process_image_reference_matches_process then compares that image against itself — so the peer-image identity check succeeds unconditionally on NetBSD. process_id also goes unused in that branch, which may trip an unused-parameter warning.

Worth noting the same -1 is correct in cli.c's cbm_detect_self_path, where self genuinely is the target. The pattern is right in one place and wrong in the other, which is exactly how this survives review.

One scope note, not a blocker. The new ipc.c branch is guarded by #elif defined(SOL_LOCAL) && defined(LOCAL_PEEREID) rather than by __NetBSD__. Linux takes the earlier SO_PEERCRED arm, macOS takes LOCAL_PEERPID, and Windows compiles a different function entirely — so none of the shipped platforms are affected. But OpenBSD also defines those macros and struct unpcbid, so it would newly reach this branch and start returning a real peer PID. Probably fine, arguably desirable, but broader than the title implies and worth a word in the commit message.

On the honest-limits documentation note I asked for: I checked, and no such note exists for FreeBSD either — docs/, README.md and CONTRIBUTING.md mention neither BSD. So that was me asking you to establish a convention that does not exist yet. Skip it; if we want a best-effort platforms note it should land once, covering both, and that is our job rather than yours.

Fix the MIB and I will merge.

Extend Linux/FreeBSD codepaths in runtime.c to also cover NetBSD, and add
NetBSD-specific peer PID retrieval via LOCAL_PEEREID in ipc.c.

Signed-off-by: Christof Meerwald <cmeerw@cmeerw.org>
@cmeerw
cmeerw force-pushed the fix/netbsd-daemon-support branch from 24f6819 to 3e1703d Compare August 22, 2026 13:09
@DeusData
DeusData merged commit f899770 into DeusData:main Aug 28, 2026
62 of 65 checks passed
@DeusData

Copy link
Copy Markdown
Owner

Merged in f899770. Thank you, @cmeerw — NetBSD support lands with every review condition met, and met precisely.

The MIB fix was verified hunk-by-hunk before merge: the self-path in cli.c keeps -1, the peer-identity check in runtime.c takes the real peer pid, and the LOCAL_PEEREID branch fails closed on any getsockopt anomaly — exactly the split the 21 August review asked for. Every hunk is preprocessor-gated; the preprocessed source on the shipped platforms is byte-identical to main, which is what let this merge confidently without a NetBSD CI leg.

The two CI reds on the way in were both documented flake classes on our attribution ledger (a wall-clock-windowed lock_registry case and the Windows daemon-stability cluster), unrelated to a provably-inert diff, and both cleared on rerun.

Same honest-limits framing as FreeBSD's runtime support: the NetBSD paths are community-verified — your verification — with no CI leg, by explicit decision. The best-effort-platforms docs note covering both BSDs is on our side and will land once, separately. The OpenBSD reachability note stays waived per the review.

A model platform contribution: native sysctl over procfs when asked, a clean rebase over the identity rename, and fail-closed semantics throughout.

DeusData added a commit that referenced this pull request Sep 4, 2026
…istic

lock_registry_absolute_deadline_survives_repeated_wakes has failed on the
test-unix (macos-15-intel) leg of three unrelated pull requests in one
week -- #1342 (08-28), #1811 (09-02) and #1819 (09-03, run 33799475629,
job 100823626965) -- always with the same signature:

  FAIL tests/test_lock_registry.c:1153: ASSERT(tail_queued)
  7648 passed / 1 failed

The registry is not racy. cbm_lock_registry_acquire enqueues the waiter
synchronously under the registry mutex before any wait, so waiter_count
and the attempting count are exact. The defect is in the fixture: it
raced two wall-clock windows against each other, both anchored to a
timestamp the observer thread took before the tail thread had even been
scheduled.

  deadline_start = cbm_now_ms();
  tail.deadline_ms = deadline_start + 200;  /* the tail's acquire deadline */
  queue_deadline   = deadline_start + 100;  /* the observer's budget */

Because both windows start before the tail runs, a loaded runner breaks
the fixture two different ways:

  * the tail is scheduled inside its deadline but after the observer's
    100 ms budget has expired -- the queued state existed and was simply
    no longer being looked at; or
  * the tail is scheduled more than 200 ms late, in which case its
    deadline has already passed when it finally calls acquire, the
    pre-registration deadline check in lock_registry_acquire_internal
    returns BUSY immediately, and the tail never enqueues at all -- the
    asserted state can then never occur, however long the observer waits.

lock_registry is in the parallel wave of run-tests-parallel.sh, not the
serial tail, so on a small CI runner it competes with a full wave of
sanitized suites -- exactly the scheduling delay both paths need.

Widening the observer's budget would only paper over the first path, and
the state it waits for is transient by construction: it exists only
between the tail's enqueue and the tail's own deadline. So the fixture is
rebuilt to observe states that cannot evaporate.

  * The tail anchors its absolute deadline itself, in its own thread,
    immediately before the acquire it bounds. Scheduling delay can no
    longer consume the deadline before the call starts, so the enqueue is
    unconditional, and elapsed is measured from the tail's own anchor --
    it now times the registry instead of timing the scheduler.

  * Enqueue is exposed as a monotonic counter,
    cbm_lock_registry_waiter_enqueue_count_for_test, next to the existing
    test_condition_wait_calls counter it is modelled on. Because the
    count only ever grows, the observer reads it once after the tail has
    returned rather than trying to catch a live queue depth: the polling
    loop, and with it the window, is gone.

  * The fixture's remaining 500 ms and 600 ms budgets become the file's
    LOCK_REGISTRY_TEST_TIMEOUT_MS backstop, and each loop exits on the
    state it waits for instead of on the clock, so the backstop only
    fires when the product is actually broken.

The contract is unchanged: the tail must still return at its absolute
deadline (150 <= elapsed < 350 ms for a 200 ms deadline) despite ~40
unrelated cancel broadcasts, still with BUSY and no lease, with the head
still holding the attempt. The broadcast loop now starts immediately
after the tail is released, so it overlaps the tail's wait at least as
much as it did before.

Verification, all on macOS arm64 with the sanitized runner:

  * The mechanism was reproduced locally by delaying only the tail thread
    after its start gate, with production untouched. A 120 ms delay (the
    state exists, outside the observer's budget) and a 250 ms delay (the
    tail never enqueues) each produced exactly one failure,
    ASSERT(tail_queued) -- the CI signature.
  * After the rebuild the same injections are green at 120, 250, 400 and
    900 ms: an arbitrary scheduling delay no longer decides the verdict.
  * lock_registry 30/30 green plain and 30/30 green under four CPU hogs;
    16/16 inside the saturated 18-job parallel wave.
  * private_file_lock, lock_registry, daemon, project_lock and the
    daemon_* suites: 222 passed, 1 skipped.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request priority/backlog Valuable contribution, lower scheduling urgency; review when maintainer capacity opens.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants