Fix native bundles on FreeBSD and Linux

ober

26f85c6a2a826a4fb68ff8956362f2f2cf6b57ce

diff --git a/Makefile b/Makefile
index 62db932..cf530d8 100644
--- a/Makefile
+++ b/Makefile
@@ -6,6 +6,9 @@ export SOURCE_DATE_EPOCH
 HOST_UNAME_S := $(shell uname -s)
 HOST_UNAME_M := $(shell uname -m)
 HOST_UNAME_O := $(shell uname -o 2>/dev/null || true)
+# SQLite is provided by vendored jsqlite. Packet capture remains explicit
+# opt-in because rscap 0.3 does not compile with the supported Rust toolchain.
+JERBOA_NATIVE_FEATURES ?= full
 STATIC ?= 0
 STATIC_ENABLED := $(filter 1 yes true on,$(STATIC))
 CHEZ_BUILD_DIR ?= $(JERBOA_HOME)/build/chez
@@ -318,7 +321,7 @@ jerbuild-smoke: jerbuild
 jerboa: chez build mcp-check lsp-gen jerboa-multicall
 
 native-bundle:
-	cd $(RUST_NATIVE_DIR) && CARGO_TARGET_DIR=target/bundle-native cargo build --locked --release --features full
+	cd $(RUST_NATIVE_DIR) && CARGO_TARGET_DIR=target/bundle-native cargo build --locked --release --features $(JERBOA_NATIVE_FEATURES)
 
 jerboa-multicall: chez native-bundle
 	$(SCHEME) --libdirs $(LIBDIRS) --script support/build-jerboa-multicall.ss
@@ -1664,10 +1667,10 @@ NATIVE_TEST_ENV = JERBOA_HOME=$(JERBOA_HOME) JERBOA_DEV_NATIVE=1 JERBOA_NATIVE_L
 RUST_NATIVE_LIB = $(RUST_NATIVE_DIR)/target/release/libjerboa_native.$(NATIVE_LIB_EXT)
 
 $(RUST_NATIVE_LIB): $(RUST_NATIVE_DIR)/src/*.rs $(RUST_NATIVE_DIR)/Cargo.toml
-	cd $(RUST_NATIVE_DIR) && cargo build --locked --release --features full
+	cd $(RUST_NATIVE_DIR) && cargo build --locked --release --features $(JERBOA_NATIVE_FEATURES)
 
 native:
-	cd $(RUST_NATIVE_DIR) && cargo build --locked --release --features full
+	cd $(RUST_NATIVE_DIR) && cargo build --locked --release --features $(JERBOA_NATIVE_FEATURES)
 	cp $(RUST_NATIVE_LIB) lib/
 ifeq ($(UNAME_S),Darwin)
 	@# Re-sign ad-hoc so dyld accepts the freshly-copied dylib.
diff --git a/data/error-fixes.sexp b/data/error-fixes.sexp
index 33b2a8e..0c25c48 100644
--- a/data/error-fixes.sexp
+++ b/data/error-fixes.sexp
@@ -2725,4 +2725,16 @@
     "Trace the failing syscall before changing directory logic. On FreeBSD use arc4random_buf (or getentropy) for cryptographic random bytes; retain the checked O_NOFOLLOW /dev/urandom path only on platforms where it is not a symlink.")
    ("id" . "freebsd-urandom-nofollow-emlink")
    ("pattern" . "create private extraction directory: Too many links")
-   ("type" . "runtime")))
+   ("type" . "runtime"))
+ (("code_example"
+    .
+    "# Keep unstable optional backends opt-in rather than in Cargo's normal bundle.\n[features]\nfull = [\"tls\", \"crypto\", \"duckdb_feat\", \"postgres_feat\", \"wasm\"]\npcap = [\"rscap\"]")
+   ("explanation"
+    .
+    "rscap 0.3.1 stores a raw pointer inside its Linux packet ring, so its Sniffer is not Send. Placing Sniffer handles behind a process-wide OnceLock<Mutex<...>> therefore fails the static Sync requirement on supported Rust toolchains.")
+   ("fix"
+    .
+    "Do not enable rscap through the normal/full native bundle until its Sniffer has a sound Send implementation or the wrapper is redesigned for thread-local ownership. Keep packet capture behind an explicit Cargo feature and verify the default native library has no packet-capture symbols.")
+   ("id" . "rscap-sniffer-not-send-static-registry")
+   ("pattern" . "cannot be sent between threads safely.*(PacketRxRing|Sniffer)")
+   ("type" . "rust-compile")))
diff --git a/jerboa-native-rs/Cargo.toml b/jerboa-native-rs/Cargo.toml
index a97c269..4ee131a 100644
--- a/jerboa-native-rs/Cargo.toml
+++ b/jerboa-native-rs/Cargo.toml
@@ -61,8 +61,9 @@ wasm = ["wasmi", "getrandom"]
 pcap = ["rscap"]
 spidermonkey = ["mozjs", "mozjs_sys"]
 
-# Full feature set — backward compatible, what jerboa itself uses
-full = ["tls", "crypto", "sqlite", "duckdb_feat", "postgres_feat", "wasm", "pcap"]
+# Jerboa's native bundle. SQLite comes from vendor/jsqlite; legacy rusqlite and
+# packet capture remain available only through explicit `sqlite`/`pcap` features.
+full = ["tls", "crypto", "duckdb_feat", "postgres_feat", "wasm"]
 
 [target.'cfg(target_os = "linux")'.dependencies]
 inotify = { version = "0.11", default-features = false }
diff --git a/support/multicall-main.c b/support/multicall-main.c
index 3355d06..27b47e3 100644
--- a/support/multicall-main.c
+++ b/support/multicall-main.c
@@ -176,6 +176,13 @@ static int open_secure_cache_root(const char *path) {
 }
 
 static int secure_random_bytes(unsigned char *out, size_t n) {
+#if defined(__FreeBSD__)
+    /* /dev/urandom is a symlink to /dev/random on FreeBSD. Opening it with
+     * O_NOFOLLOW therefore fails with EMLINK; arc4random_buf is the native
+     * nonblocking cryptographic entropy API and requires no file descriptor. */
+    arc4random_buf(out, n);
+    return 0;
+#else
     int fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC | O_NOFOLLOW);
     size_t off = 0;
     if (fd < 0) return -1;
@@ -186,6 +193,7 @@ static int secure_random_bytes(unsigned char *out, size_t n) {
         off += (size_t)got;
     }
     return close(fd);
+#endif
 }
 
 static int make_private_temp_dir(int rootfd, char *name, size_t name_size) {