# BST-TIMEOUT: 900 . "$BST_LIB" # NSPR 4.35 (meta-oe recipes-support/nspr) is the Netscape Portable Runtime -- # the portability layer NSS is written against, and the reason this package is # on the porting list at all. It is not a utility library: it is an operating # system interface. Threads, locks, condition variables and monitors; interval # timers and wall-clock time; file, pipe and socket I/O with per-call timeouts; # arena allocation; atomics; shared memory and named semaphores; a layered-I/O # stack. What ships here is /usr/lib/libnspr4.a, libplc4.a and libplds4.a, the # headers under /usr/include/nspr, nspr.pc, /usr/bin/nspr-config, and 109 of # upstream's own test programs under /usr/lib/nspr/tests -- seven more cannot be # linked on this arm at all, and are named at the end of this file. # # --------------------------------------------------------------------------- # THE ONE FACT THAT SHAPES THIS WHOLE FILE: NSPR BUILDS AND LINKS, AND A SECOND # THREAD CANNOT BE CREATED. # # configure finds a linkable -lpthread (glibc supplies one) and NSPR is built # with its _PTH implementation strategy throughout, so every lock, condition # variable, thread-private-data slot and monitor in the library is real. What # is refused is the clone, by the guest kernel, in its own words: # # wasm32: REFUSING thread-mode clone (CLONE_VM, ...) with EAGAIN - the coro # substrate's runtime protocol state is single-lane per process; a second # Worker lane over the same user_memory is a structural protocol violation, # not a schedulable thread. # # So PR_CreateThread returns NULL with the OS error set. That is deterministic # and catchable, and if it were the whole story all 109 programs could run here # and 63 of them would ship red-but-honest. # # IT IS NOT THE WHOLE STORY, AND THE PART THAT MATTERS IS THE ABSENCE OF # PREEMPTION UNDER IT. A runnable guest process holds the substrate's single # lane, so a program that waits on a thread which will never exist does not # fail -- it starves the lane, and with it the console bridge, so nothing this # suite has already printed escapes either. Concurrency Kit measured that twice # on this platform: a run wedged after nine EAGAINs and lost the 47 serial # results of its own that it had already produced. A timeout(1) wrapper does not help, because # the wrapper needs the same lane the spinning process is holding. # # So the 63 programs that reach a thread-creation entry point are REPORTED # WITHOUT BEING RUN, each naming the issue, and the 46 that do not are run. # # THE SPLIT IS GENERATED, NOT TYPED. nspr-serial.txt is produced by the recipe # from the sources of upstream's own TESTS list, keyed on every spelling of a # thread-creation entry point rather than on `pthread_create' alone -- attach.c # calls _PT_PTHREAD_CREATE and nothing else, and a narrower pattern would have # classified it serial and run a program that then waits on pthread_join. It # cannot be asked of the binaries the way Concurrency Kit's equivalent list is: # static linking against glibc DEFINES pthread_create in every module here # whether it is called or not, so `nm -u' answers nothing. # # BRINTOS-WORKAROUND: linux-in-the-browser#85 remove -- the 63 are MASKED and # will NOT flip green by themselves when threads arrive. Delete the # BST_NSPR_THREADED gate below, and this marker with it, once clone(2) stops # answering EAGAIN. # # --------------------------------------------------------------------------- # WHAT THE PROBE ADDS THAT 109 EXIT STATUSES DO NOT. # # Upstream's programs answer with an exit status and English prose. The probe # (built by the same bbappend, against the archive that ships) reports the four # subsystems this ticket names as VALUES -- PR_Now and the interval timers, # a lock + condition-variable handshake between two threads, an arena # allocation and reset, and a loopback socket pair -- so the suite can assert # what is knowable and REPORT what is platform-dependent. The thread handshake # is the case that cannot be asserted in either direction today, and it is # reported for exactly that reason. # # It also reads the ported machine block back out of the built library: P06..P12 # are the sizes and alignments the wasm32 entry in pr/include/md/_linux.cfg # declares, measured against the compiler before the patch was written. A # toolchain change that moved one of them would be a red here rather than a # silent miscompile in every consumer. R="${BST_NSPR_ROOT:-}" tmp=${TMPDIR:-/tmp} LIBDIR=$R/usr/lib INCDIR=$R/usr/include/nspr PCDIR=$R/usr/lib/pkgconfig BINDIR=$R/usr/bin TESTDIR=${BST_NSPR_TESTDIR:-$R/usr/lib/nspr/tests} PROBEDIR=${BST_NSPR_PROBEDIR:-$R/usr/libexec/nspr-tests} SERIAL=$PROBEDIR/nspr-serial.txt # THE CANARY, which is not an assertion about NSPR at all. # # brintos_local_verify.py loses a suite's FIRST BST result line before the host # parser ever sees it -- BRINTOS-1149, a defect in our own host-side tooling and # not in the runner, the guest or any package. The harness reports the deficit # (`declared N case(s), N-1 reached the console`) but computes its pass count as # total-minus-failures, so THE DROPPED LINE IS COUNTED AS A PASS. # # This file's first case WAS nspr/present, and it is the worst one to lose: it is # the guard that reports `skip` and exits 0 when the package is not installed at # all, so an image that shipped without NSPR would have lost the single line # saying so and read as a smaller clean run. Both local verifies of this port # duly ate it (`declared 246 case(s), 245 reached the console`, the same case # each time). So the first line out of the file now asserts something already # true. It hides nothing -- the harness still reports the deficit and every real # case below is still counted. Same idiom, and the same reason, as # 82-double-conversion and 83-flatbuffers. # # Remove this case when 1149 is fixed. bst_ok nspr/suite-canary if [ ! -f "$LIBDIR/libnspr4.a" ]; then bst_skip nspr/present "nspr not installed on this image" exit 0 fi bst_ok nspr/present # --- the package is whole --------------------------------------------------- check nspr/lib-nspr test -s "$LIBDIR/libnspr4.a" check nspr/lib-plc test -s "$LIBDIR/libplc4.a" check nspr/lib-plds test -s "$LIBDIR/libplds4.a" # Shape, not absence: this arm converts to shared libraries per recipe # (BRINTOS-1740), so a .so here is a conversion, not a policy breach. bst_lib_shape nspr/no-shared-nspr "$LIBDIR/libnspr4.so" bst_lib_shape nspr/no-shared-plc "$LIBDIR/libplc4.so" bst_lib_shape nspr/no-shared-plds "$LIBDIR/libplds4.so" # One header per surface a consumer reaches. nspr.h is the umbrella; the rest # are the ones a partial install would silently drop while libnspr4.a still # linked. plarena.h/plhash.h/plstr.h belong to the plds/plc halves and are the # tell that those two libraries were installed as libraries rather than as # stubs. for h in nspr.h prtypes.h prio.h prthread.h prlock.h prcvar.h prmon.h \ prinrval.h prtime.h prnetdb.h prerror.h prmem.h prlong.h pratom.h \ prshm.h pripcsem.h prproces.h prenv.h prinit.h prlog.h prprf.h \ plarena.h plhash.h plstr.h plerror.h prrwlock.h prtpool.h; do check "nspr/header-$h" test -s "$INCDIR/$h" done check nspr/pkgconfig test -s "$PCDIR/nspr.pc" check_out nspr/pkgconfig-libs "-lnspr4" cat "$PCDIR/nspr.pc" # nspr-config is the pre-pkg-config way consumers find these flags, and it is # perl. It is worth a case because it is the only executable the -dev package # ships and because it is the one file here that needs an interpreter present # on the target. check nspr/config-present test -x "$BINDIR/nspr-config" check_out nspr/config-libs "-lnspr4" "$BINDIR/nspr-config" --libs # --- symbol census over the archive that ships ------------------------------ # # Bounded and named, so a truncation is visible: 28 entry points spanning every # subsystem the library claims. strings(1) over the archive rather than nm, # which this image does not ship. SYMS="PR_Init PR_Cleanup PR_GetVersion PR_VersionCheck PR_GetSystemInfo PR_Now PR_IntervalNow PR_TicksPerSecond PR_Sleep PR_MillisecondsToInterval PR_NewLock PR_Lock PR_Unlock PR_NewCondVar PR_WaitCondVar PR_NotifyCondVar PR_CreateThread PR_JoinThread PR_GetCurrentThread PR_NewTCPSocket PR_Bind PR_Listen PR_Accept PR_Connect PR_Send PR_Recv PR_NewTCPSocketPair PR_Open" nsym=0 strings "$LIBDIR/libnspr4.a" > "$tmp/bst-nspr-syms.$$" 2>/dev/null for sym in $SYMS; do nsym=$((nsym + 1)) if grep -qx "$sym" "$tmp/bst-nspr-syms.$$" 2>/dev/null; then bst_ok "nspr/sym-$sym" else bst_notok "nspr/sym-$sym" "not in libnspr4.a" fi done rm -f "$tmp/bst-nspr-syms.$$" bst_ok "nspr/sym-count-$nsym" # The arena and hash allocators live in libplds4, the string and error helpers # in libplc4. A build that produced the two as empty archives would pass every # case above. for pair in "libplds4.a PL_InitArenaPool" "libplds4.a PL_FinishArenaPool" \ "libplds4.a PL_NewHashTable" "libplc4.a PL_strdup" \ "libplc4.a PL_FPrintError"; do lib=${pair%% *}; sym=${pair#* } if strings "$LIBDIR/$lib" 2>/dev/null | grep -qx "$sym"; then bst_ok "nspr/sym-$lib-$sym" else bst_notok "nspr/sym-$lib-$sym" "not in $lib" fi done # --- the probe --------------------------------------------------------------- work=$tmp/bst-nspr.$$ mkdir -p "$work" 2>/dev/null got=$work/probe.out exp=$work/probe.exp if [ ! -x "$PROBEDIR/nspr-probe" ]; then bst_notok nspr/probe-present "no probe at $PROBEDIR/nspr-probe" else bst_ok nspr/probe-present ( cd "$work" && "$PROBEDIR/nspr-probe" ) > "$got" 2>"$work/probe.err" /dev/null | sed "s|^|# probe-stderr: |" fi if [ ! -s "$got" ]; then bst_notok nspr/probe-output "the probe produced no output (rc=$prc)" else bst_ok nspr/probe-output # tagexpectedid, written as data rather than embedded in the # awk program. Everything here is a property NSPR promises on any platform # OR a constant the ported machine block declares; nothing that depends on # a thread, a clock rate or a socket is scored (those are reported below). cat > "$exp" <<'EOF' P00-init 1 init P01-version-check 1 version-check P03-architecture wasm32 architecture P06-bytes-per-word 4 bytes-per-word P07-bytes-per-long 4 bytes-per-long P08-bytes-per-int64 8 bytes-per-int64 P09-align-of-double 8 align-of-double P10-align-of-pointer 4 align-of-pointer P11-sizeof-prword 4 sizeof-prword P12-sizeof-prtime 8 sizeof-prtime P13-atomic-increment 1 atomic-increment P14-atomic-decrement 1 atomic-decrement P15-atomic-add 1 atomic-add P16-atomic-set 1 atomic-set T02-ms-roundtrip 1 interval-ms-roundtrip T03-us-roundtrip 1 interval-us-roundtrip T04-now-nonzero 1 now-nonzero T05-now-advances 1 now-advances T07-interval-advances 1 interval-advances H01-lock-created 1 lock-created H02-cvar-created 1 cvar-created H03-cvar-timedwait-rv 1 cvar-timedwait A01-alloc 1 arena-alloc A02-alloc-oversized 1 arena-alloc-oversized A03-first-block-intact 1 arena-first-block-intact A04-alignment 1 arena-alignment A05-alloc-after-mark 1 arena-alloc-after-mark A06-release-reuses 1 arena-release-reuses A07-alloc-after-free 1 arena-alloc-after-free A08-finish 1 arena-finish Z01-cleanup 1 cleanup Z02-done 1 probe-reached-end EOF awk ' NR == FNR { want[$1] = $2; id[$1] = $3; next } { got[$1] = $2 } END { for (t in want) { if (!(t in got)) printf "BST not ok nspr/%s : the probe emitted no %s line\n", id[t], t else if (got[t] == want[t]) printf "BST ok nspr/%s\n", id[t] else printf "BST not ok nspr/%s : %s = %s, expected %s\n", id[t], t, got[t], want[t] } } ' "$exp" "$got" _v() { awk -v k="$1" '$1==k{print $2}' "$got"; } # REPORTED, NEVER SCORED, and each for its own reason. # # The three timing figures are measurements of a host under load, not # contracts: a 200 ms sleep on a shared build machine can wake late by any # amount, and asserting a bound would buy a false red on a busy day. What # IS scored above is the direction (the clock advanced, the interval # advanced), which is the part NSPR promises. bst_ok "nspr/ticks-per-second-$(_v T01-ticks-per-second)" bst_ok "nspr/sleep200-wallclock-ms-$(_v T06-now-elapsed-ms)" bst_ok "nspr/sleep200-interval-ms-$(_v T08-interval-elapsed-ms)" bst_ok "nspr/cvar-timedwait120-ms-$(_v H03-cvar-timedwait-ms)" bst_ok "nspr/page-size-$(_v P04-page-size)" bst_ok "nspr/version-$(_v P02-version)" # The thread handshake. 0 is the expected answer on this platform today and # is not a failure of this port: PR_CreateThread returns NULL because # clone(2) answers EAGAIN. It is reported with the OS error beside it so # that the day the substrate grows a second lane, this line changes on its # own and says so. h04=$(_v H04-thread-created) h05=$(_v H05-handshake) bst_ok "nspr/thread-created-$h04" bst_ok "nspr/thread-oserr-$(_v H04-thread-oserr)" bst_ok "nspr/handshake-$h05" if [ "$h04" = "1" ] && [ "$h05" != "1" ]; then bst_notok nspr/handshake-with-threads \ "a thread was created and the lock/cvar handshake still did not complete" else bst_ok nspr/handshake-consistent fi # Sockets. AF_UNIX socketpair and a TCP loopback round trip are both # single-threaded here by construction (the header on the probe explains # why the loopback case cannot park), so unlike the thread case these ARE # scored -- a socket layer that cannot carry a byte to itself would be a # real defect in the port or in the kernel, and either is worth a red. check_eq nspr/socketpair 1 "$(_v S01-socketpair)" check_eq nspr/socketpair-payload 1 "$(_v S03-socketpair-payload)" check_eq nspr/loopback-listener 1 "$(_v S04-listener)" check_eq nspr/loopback-bind 1 "$(_v S05-bind)" check_eq nspr/loopback-listen 1 "$(_v S06-listen)" check_eq nspr/loopback-connect 1 "$(_v S07-connect)" check_eq nspr/loopback-accept 1 "$(_v S08-accept)" check_eq nspr/loopback-payload 1 "$(_v S10-loopback-payload)" fi fi # --- upstream's own programs ------------------------------------------------- # # NO timeout(1), AND NO LIVE fd ACROSS THE FORKS. Both cost Concurrency Kit a # run to learn and the reasoning is identical here: the bound cannot protect # against an unpreemptable spin (it needs the lane that is being held), and a # shell blocked in read(2) on a list file while a child signals can take # ERESTARTNOINTR back through glibc's errno path and end the suite mid-file, # which presents as a small green suite rather than as an error. So the list is # slurped before the loop and every child gets errno 19 # (linux-in-the-browser#72)". # # dlltest -- PR_LoadLibrary("dll/libmy.so"). The library it wants is built by # pr/tests/dll, which a --disable-shared arm does not produce, so this is # the static-only policy rather than a defect. Same class as version. # # libfilename -- the SAME static-only cause as dlltest, and it is worth # spelling out because the obvious reading of this one is wrong. The program # runs two checks. The FIRST, against the implicitly-linked libnspr4, is the # one ac_cv_func_dladdr=no is aimed at, and it PASSES: PR_GetLibraryFilePathname # returns NULL with PR_NOT_IMPLEMENTED_ERROR, and upstream's own RunTest # returns PR_SUCCESS on exactly that error ("we let this test pass if this # function is not implemented"). The stderr line "PR_GetLibraryFilePathname # failed" is printed on that PASSING path, which is what makes it easy to # misread as the cause. What actually ends the program is the SECOND check: # PR_LoadLibrary("dll/libmy.so") -> exit(1), the same object dlltest wants and # that a --disable-shared arm does not build. So the dladdr workaround did its # whole job -- it turned a process-killing trap (rc=132, RuntimeError: # unreachable inside glibc's _dl_addr) into a documented, catchable failure -- # and this red is the static-only policy, not brintos/linux-in-the-browser#140. # It will not flip green when #140 closes; it flips when the distro gains # shared libraries. # # gethost -- PR_GetHostByName. No resolver is reachable from the guest. # # Each stays RED. Attributing a red is not the same as excusing it: the day #85 # or #72 closes, or the distro gains shared libraries, these flip on their own # and the suite says so. _nspr_attr() { case "$1" in parent|pipeping|pipeping2|sockping) printf '%s' "PR_CreateProcess needs NSPR's child-reaper thread -- $B85" ;; anonfm) printf '%s' "writable MAP_SHARED file-backed mmap refused -- brintos/linux-in-the-browser#72" ;; dlltest) printf '%s' "needs pr/tests/dll/libmy.so; a static-only distro builds none" ;; libfilename) printf '%s' "dladdr half PASSES (PR_NOT_IMPLEMENTED_ERROR); dies on PR_LoadLibrary(dll/libmy.so) -- a static-only distro builds none" ;; gethost) printf '%s' "no resolver reachable from the guest" ;; *) printf '' ;; esac } if [ ! -d "$TESTDIR" ]; then bst_notok nspr/tests-present "no $TESTDIR" elif [ ! -s "$SERIAL" ]; then bst_notok nspr/serial-list "no generated serial list at $SERIAL" else bst_ok nspr/tests-present bst_ok nspr/serial-list _serial=$(cat "$SERIAL") _all=$(cd "$TESTDIR" && ls 2>/dev/null | grep -v '^runtests\.') _oifs=$IFS IFS=' ' set -- $_all IFS=$_oifs nser=0; nthr=0 for t in "$@"; do [ -f "$TESTDIR/$t" ] || continue id="nspr/t-$t" if printf '%s\n' "$_serial" | grep -qxF "$t"; then nser=$((nser + 1)) out=$(cd "$work" && "$TESTDIR/$t" &1); rc=$? if [ "$rc" -eq 0 ]; then bst_ok "$id" else _why=$(_nspr_attr "$t") if [ -n "$_why" ]; then bst_notok "$id" "$_why (rc=$rc)" else bst_notok "$id" "rc=$rc $(_bst_1line "$out")" fi fi continue fi nthr=$((nthr + 1)) if [ -z "${BST_NSPR_THREADED:-}" ]; then bst_notok "$id" "$NOTRUN" continue fi out=$(cd "$work" && "$TESTDIR/$t" &1); rc=$? if [ "$rc" -eq 0 ]; then bst_ok "$id" else bst_notok "$id" "$B85 (rc=$rc) $(_bst_1line "$out")" fi done # The counts are an assertion, not a footnote. A -tests package that # installed short, or a generated list that came out empty, would leave this # suite reporting a clean green over a fraction of the programs -- the one # failure mode a per-case report cannot show, because what is missing emits # no line at all. 46 + 63 is upstream 4.35's TESTS list as meta-oe carries it # (114) minus the five the recipe cannot link (below), classified 2026-08-19; # a version bump that adds a program is expected to move these and to be # looked at when it does. check_eq nspr/count-serial 46 "$nser" check_eq nspr/count-threaded 63 "$nthr" fi # --- the programs that could not be LINKED ---------------------------------- # # These seven are named rather than left to vanish from the count, which is the # whole point: the recipe drops them, so the loop above cannot see them, and a # suite that only reports what it found would show a smaller green total and no # trace of why. Each is a program upstream ships and this arm cannot build. # They fall into TWO groups with different causes and different lifetimes. # # BRINTOS-WORKAROUND: hwjs-cc#31 remove -- PR_OpenSemaphore's SysV implementation # passes `union semun' by value through semctl(2)'s variadic tail, which is # POSIX's documented calling convention for that call, and the coroutinize pass # cannot lower a variadic edge whose tail is an aggregate. It refuses the caller, # the caller stays native on a static setjmp/longjmp unwind path, and wasmld # FATALs the whole-program link. libnspr4.a is unaffected and still exports the # function -- what cannot be built is a program that CALLS it, which is also why # nspr/openbsemaphore-exported below is an ok rather than a red. Delete this # block, the recipe's patch, and this marker together when #31 closes. B31="PR_OpenSemaphore link REFUSED: aggregate through semctl(2)'s variadic tail -- brintos/hwjs-cc#31" for t in nameshm1 sema semaerr semaerr1 semaping semapong; do bst_notok "nspr/t-$t" "$B31" done # The seventh has a different cause and no expiry against a brintOS defect: # version.c calls libVersionPoint(), which upstream deliberately keeps OUT of # the static libraries (config/rules.mk filters p*vrsion.o out of # STATICLIB_OBJS -- "these collide for static libraries, and are not useful for # that case anyway"). All three archives would define it, and it only means # anything when each library is a separate DSO you PR_FindSymbol into. This # distro is static-only, so the symbol is absent and the link ends as # `phantom import: env.libVersionPoint'. Nothing is broken in NSPR; this case # goes away when the distro gains shared libraries, not when a defect closes. bst_notok nspr/t-version \ "libVersionPoint is excluded from NSPR's static libs by upstream design -- static-only distro" # The library still exports it, and that is the honest statement of the # exposure: the workaround removes the build failure, not the defect. A # downstream consumer calling PR_OpenSemaphore is unlinkable on this arm until # #31 closes. if strings "$LIBDIR/libnspr4.a" 2>/dev/null | grep -qx PR_OpenSemaphore; then bst_ok nspr/opensemaphore-exported else bst_notok nspr/opensemaphore-exported \ "PR_OpenSemaphore is not in libnspr4.a -- the workaround was widened past the test programs" fi rm -rf "$work" 2>/dev/null