# BST-TIMEOUT: 180 # MEASURED: see the Self-Test Record on BRINTOS-971. Most of # # the cost is eight probe runs, each a fresh wasm module # # instantiation; the assertions themselves are a single awk # # pass. The DEFAULT bound is 60 and the `net' section is the # # one that could in principle block (it is the only path # # here that reaches a socket), so a bound that left no # # headroom would report a platform change as a suite-timeout # # with nothing to point at. . "$BST_LIB" # libnsl 2.0.1 -- the NIS/YP client library split out of glibc (BRINTOS-971), # poky recipes-extended/libnsl. What ships is /usr/lib/libnsl.a, seven headers # under /usr/include/rpcsvc, /usr/lib/pkgconfig/libnsl.pc, and # /usr/libexec/libnsl2-tests/libnsl2-probe. # # --------------------------------------------------------------------------- # THIS IS A NETWORK CLIENT WITH NO SERVER, AND THE SUITE IS BUILT AROUND THAT # RATHER THAN IN SPITE OF IT. # # Every interesting path in yp_match/yp_first/yp_next/yp_all/yp_order/yp_master # ends at a ypbind on some peer machine. There is no peer here -- no NIS server, # no network -- and there will not be one: this image has no transport for it. # Binding to a real server is explicitly out of this ticket's scope. # # What a serverless image CAN establish is more than it sounds, and it is four # things: # # 1. THE ERROR VOCABULARY IS COMPLETE AND CORRECT. yperr_string() and # ypbinderr_string() are the library's entire means of telling a caller what # went wrong, and on this image EVERY call fails -- so these tables are not # a corner of the API, they are the part of it a consumer here actually # reads. Both are switches with a default, so the out-of-range answers are # as much a part of the contract as the named codes. # # 2. THE WIRE-CODE MAPPING IS CORRECT AT EVERY POINT AND AT BOTH EDGES. # ypprot_err() converts the ypstat a server sends into the YPERR_* a caller # sees. It is an int8_t lookup table indexed by `code - YP_VERS' behind a # range check, so it has exactly two failure modes: a wrong entry, and a # guard that lets an index out of bounds. Every in-range code is asserted # individually and BOTH ends are walked one past -- an off-by-one there # reads adjacent memory and would otherwise surface only as an occasional # inexplicable error code. # # 3. THE ARGUMENT VALIDATION HOLDS. Each entry point open-codes its own check # before it touches the wire, and the checks are NOT identical -- yp_match # and yp_next also reject a non-positive key length, yp_first and yp_order # take none. On this image that validation is the whole reachable API, so # 32 cases go through it. # # 4. THE ADDRESS HELPERS CONVERT CORRECTLY. taddr2ipstr/taddr2port are pure # functions over a libtirpc netbuf the probe builds itself, so they are # driven directly on fixtures and resolve nothing. # # THE ORACLE IS A NATIVE x86_64 RUN OF THE IDENTICAL PROBE SOURCE against the # identical libnsl source at the identical SRCREV -- data/libnsl2-native.txt, # regenerated by src/libnsl2_mknative.sh, which links against bitbake's OWN # libnsl2-native from the same build. Sections err, binderr, prot, args and # taddr are pure functions of that source and there is NOTHING in them that is # allowed to differ between the two arms, so they are diffed wholesale. # # THREE SECTIONS ARE DELIBERATELY NOT DIFFED, and each is scored here instead: # # abi sizeof(long) is 4 here and 8 on the control. REQUIRED to differ. # domain yp_get_default_domain() reports a property of THIS HOST. Scored as # the CONTRACT between it and getdomainname(), which holds on a build # host with a real NIS domain and on a target that can never have one. # net yp_bind() against a domain nothing serves. A build host may run a # real ypbind; what is asserted is that the call RETURNS, returns a # code from the documented set, and leaves the library usable. # # ONE ENTRY POINT IS UNREACHABLE FROM THIS IMAGE ENTIRELY, AND IT IS THE PORT'S # SHARPEST FINDING. yp_all() is the only caller of __xdr_ypresp_all(), which # builds TWO VLAs sized from the wire (do_ypcall.c:514-515) and hands their # addresses to the caller's foreach callback. The HWJS-3 coroutinize pass refuses # that shape -- `REFUSE fn=__xdr_ypresp_all # reason=vla/dynamic-alloca-address-escape-opaque-sink' -- so a program that so # much as REFERENCES yp_all does not link on this arm. The probe therefore never # names it: doing so would not redden a case, it would fail the recipe's # do_compile and the port would ship no probe at all. # # MEASURED one entry point at a time against this build's own libnsl.a: # yperr_string, all three tables, yp_get_default_domain, yp_match and yp_bind # each LINK and produce an artifact; only yp_all is refused. The refusal is # reachability-based, so the rest of the library is unaffected -- which is why # this port ships rather than stopping. Reported on hwjs-cc#15, the canonical # thread for this refusal class. libnsl2/sym-yp_all below stays GREEN and # is not a contradiction: the symbol IS in the archive, and the archive is not # where the refusal happens. # # NO CASE HERE IS EXPECTED TO BE RED. The platform limits -- no NIS server, no # network, no yp_all -- are asserted as the behaviour they produce (a documented # failure code from yp_bind, YPERR_BADARGS from every entry point given bad # arguments, and yp_all's absence from the probe) rather than left as sanctioned # failures. If a red appears it is a regression, not the known gap. # # THE PROBE IS RUN ONCE PER SECTION, which is why there are eight invocations # below rather than one. The `net' section is the only path in this library that # reaches clnt_create(), so it is the only one that could block or trap on a # runtime that grows a socket layer; giving it its own process means that if it # ever does, it costs its own nine cases and not the other hundred. This is the # libdbi lesson applied before it costs anything. # A root prefix, empty on the target and set only when this suite is validated # natively before it ships. The runner never sets it, so on the image these are # exactly the absolute paths. R="${BST_LIBNSL2_ROOT:-}" ARCHIVE=$R/usr/lib/libnsl.a INCDIR=$R/usr/include/rpcsvc PCFILE=$R/usr/lib/pkgconfig/libnsl.pc PROBE=${BST_LIBNSL2_PROBE:-$R/usr/libexec/libnsl2-tests/libnsl2-probe} NATIVE=$BST_DATA/libnsl2-native.txt if [ ! -f "$ARCHIVE" ]; then bst_skip libnsl2/present "libnsl2 not installed on this image" exit 0 fi bst_ok libnsl2/present # --- the package is whole ---------------------------------------------------- check libnsl2/staticlib test -s "$ARCHIVE" check libnsl2/pkgconfig test -s "$PCFILE" for h in ypclnt.h yp_prot.h yp.h yp.x ypupd.h yppasswd.h yppasswd.x; do check "libnsl2/hdr-$(echo "$h" | tr '.' '-')" test -s "$INCDIR/$h" done # A real ar archive. One that is present but truncated would pass the size check # above and fail every consumer. check_eq libnsl2/staticlib-ar '!' "$(head -c 7 "$ARCHIVE" 2>/dev/null)" # 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 libnsl2/no-shared "$R/usr/lib/libnsl.so" "$R/usr/lib/libnsl.so.3" # libnsl.pc must name libtirpc: a consumer that picks up the .pc and drops the # Requires: line gets an archive full of unresolved XDR symbols at link time. check_out libnsl2/pc-requires-tirpc libtirpc cat "$PCFILE" # The archive carries the API. grep rather than nm: the image ships no binutils # nm, and the names are present as plain bytes, so this works with no toolchain. # # -a WHERE THE grep SUPPORTS IT. On this arm the archive members are LLVM IR # BITCODE, not wasm objects -- the toolchain builds ThinLTO -- so a grep that # suppresses matches in a file containing NULs reports NOTHING. GNU grep needs # -a here, busybox grep as shipped does not. Probing for it rather than assuming # either keeps the block measuring libnsl instead of measuring the image's grep. if echo x | grep -aq x 2>/dev/null; then _grepq="grep -aq"; else _grepq="grep -q"; fi # yp_all IS in this list on purpose. It is present in the archive and a consumer # still cannot link it (see the header note); the archive's contents and the # link-time refusal are different facts and the suite asserts the one it can see. for sym in yp_get_default_domain yp_bind yp_unbind yp_match yp_first yp_next \ yp_all yp_order yp_master yp_maplist yperr_string ypbinderr_string \ ypprot_err taddr2host taddr2ipstr taddr2port \ xdr_ypreq_key xdr_ypresp_val xdr_ypstat xdr_domainname; do if $_grepq "$sym" "$ARCHIVE" 2>/dev/null; then bst_ok "libnsl2/sym-$sym" else bst_notok "libnsl2/sym-$sym" "$sym absent from $ARCHIVE" fi done # TWO SYMBOLS THAT MUST *NOT* BE THERE, and they are the port's sharpest finding. # rpcsvc/ypclnt.h -- the installed public header -- declares yp_update() and # __yp_check(), and libnsl defines NEITHER and exports neither. A consumer that # calls either compiles clean against the shipped header and fails at link with # an undefined symbol. That is upstream's defect on every platform, not this # arm's; asserting it here pins it as KNOWN rather than leaving the next port to # rediscover it, and turns green into red the day upstream implements them. for sym in yp_update __yp_check; do if $_grepq "$sym" "$ARCHIVE" 2>/dev/null; then bst_notok "libnsl2/absent-$sym" "$sym is now defined -- ypclnt.h's declaration is no longer a dead one; re-check the header/ABI notes" else bst_ok "libnsl2/absent-$sym" fi done # --- the probe --------------------------------------------------------------- for f in "$PROBE" "$NATIVE"; do if [ ! -f "$f" ]; then bst_skip libnsl2/probe-runs "$(basename "$f") absent from the image" exit 0 fi done check libnsl2/probe-exec test -x "$PROBE" _tmp=${TMPDIR:-/tmp} _pout=$_tmp/bst-libnsl2-out.$$ _sout=$_tmp/bst-libnsl2-sec.$$ _cmp=$_tmp/bst-libnsl2-cmp.$$ : > "$_pout" # run_section : append the section's output to $_pout (minus the # terminator) and report whether it ran to completion. A section that dies # partway leaves its earlier cases in $_pout, so the join below still scores # them and localises the death to the first missing id rather than smearing it # across every remaining case. run_section() { "$PROBE" "$1" > "$_sout" 2>&1 _rc=$? if [ "$_rc" -ne 0 ]; then if bst_execfail "$(head -c 200 "$_sout" 2>/dev/null)"; then bst_unrun "libnsl2/section-$1" "$(head -c 200 "$_sout" 2>/dev/null)" else bst_notok "libnsl2/section-$1" "probe exited $_rc" fi elif grep -q '^PROBE-END$' "$_sout" 2>/dev/null; then bst_ok "libnsl2/section-$1" else bst_notok "libnsl2/section-$1" \ "no PROBE-END terminator (died at $(awk 'END{print $1}' "$_sout" 2>/dev/null))" fi grep -v '^PROBE-END$' "$_sout" >> "$_pout" 2>/dev/null } for s in abi err binderr prot domain args taddr net; do run_section "$s" done # val : the probe's value for one case id, empty if the probe never got # there. Reads the accumulated output, so it costs no extra process per lookup # beyond the awk itself. val() { awk -v k="$1" '$1==k { $1=""; sub(/^ /,""); print; exit }' "$_pout"; } # --- the ILP32 cases, scored HERE rather than against the control ------------ # # wasm32 is ILP32 and the control host is LP64, so these are REQUIRED to # disagree and are dropped from the diff below. They are not trivia: yp_order's # out-parameter is `unsigned int *' and yp_match's length is a plain `int', so a # consumer of this library on this image is writing against these widths. The # pointer size is the one that bites -- a caller that stores a `char *' returned # by yp_match in a long is fine here only because both are 4. check_eq libnsl2/sizeof-long 4 "$(val A01)" check_eq libnsl2/sizeof-long-long 8 "$(val A02)" check_eq libnsl2/sizeof-pointer 4 "$(val A03)" check_eq libnsl2/sizeof-int 4 "$(val A04)" check_eq libnsl2/sizeof-uint 4 "$(val A05)" # time_t is 64-bit even on this 32-bit arm -- the distro is built with a 64-bit # time_t, and a suite that let this read 4 would be recording a Y2038 image. check_eq libnsl2/sizeof-time-t 8 "$(val A06)" # --- yp_get_default_domain: the CONTRACT, not a value ------------------------ # # The function reports what getdomainname() says, and getdomainname() reports a # property of the host. Asserting "rc is 12" would be asserting that nobody ever # sets an NIS domain on the build host -- true today, and not something this # suite has any business depending on. So the probe prints the raw name beside # the answer and this scores the documented relationship between them: # # raw is "" or "(none)" -> rc == YPERR_NODOM (12) and *outdomain == NULL # otherwise -> rc == 0 (YPERR_SUCCESS) and *outdomain == raw # # On this image the raw name is expected to be "(none)" -- the kernel's default, # with nothing on the target ever calling setdomainname() -- so the NODOM branch # is the one that runs, and that is exactly the ticket's requirement: the # documented error when no domain is bound. _raw=$(val D01) _rc=$(val D02) _dom=$(val D03) case "$_raw" in "(none)"|""|"") check_eq libnsl2/domain-unbound-rc 12 "$_rc" check_eq libnsl2/domain-unbound-out '' "$_dom" bst_ok libnsl2/domain-branch ;; "") bst_notok libnsl2/domain-branch "getdomainname() failed outright; libnsl cannot be scored" ;; *) check_eq libnsl2/domain-bound-rc 0 "$_rc" check_eq libnsl2/domain-bound-out "$_raw" "$_dom" bst_ok libnsl2/domain-branch ;; esac # The second call takes the cached branch through the file-static buffer, and a # caller is entitled to the same answer from it. check_eq libnsl2/domain-idempotent-rc "$_rc" "$(val D04)" check_eq libnsl2/domain-idempotent-out "$_dom" "$(val D05)" check_eq libnsl2/domain-same same "$(val D06)" case "$(val D07)" in both-null|both-equal) bst_ok libnsl2/domain-agree ;; *) bst_notok libnsl2/domain-agree "two calls disagreed: $(val D07)" ;; esac # --- yp_bind with nothing to bind to ----------------------------------------- # # Not scored against the control: a build host may run a real ypbind. What is # asserted is that the call comes back at all, comes back with a code from the # documented range, and leaves the library working -- which is the whole of what # a consumer on this image can rely on. _nrc=$(val N01) if [ -z "$_nrc" ]; then bst_notok libnsl2/bind-returns "yp_bind() produced no result -- it did not return" else bst_ok libnsl2/bind-returns fi check_eq libnsl2/bind-code-in-range in-range "$(val N02)" # The code must map to a sentence, not to the default. On this image the answer # is expected to be YPERR_YPBIND (10) -- there is no ypbind to talk to -- but a # runtime that refused the socket differently could legitimately give YPERR_RPC # or YPERR_DOMAIN, so the assertion is that the message is a NAMED one. case "$(val N03)" in "Unknown NIS error code"|"") bst_notok libnsl2/bind-code-named "yp_bind returned $_nrc, which yperr_string does not name" ;; *) bst_ok libnsl2/bind-code-named ;; esac # Unbinding what never bound, twice, and unbinding a domain never seen: each # walks a list that is empty. A crash here would be a NULL deref in the cleanup # path, which is the one thing a caller does after every failure. check_eq libnsl2/unbind-unbound unbind-returned "$(val N04)" check_eq libnsl2/unbind-twice unbind2-returned "$(val N05)" check_eq libnsl2/unbind-unknown unbind-unknown-returned "$(val N06)" # and the library still answers afterwards check_eq libnsl2/usable-after-bind "Local domain name not set" "$(val N07)" check_eq libnsl2/taddr2host-null-nconf null "$(val N09)" # taddr2host() on 127.0.0.1 goes through getnameinfo() without NI_NUMERICHOST, # i.e. it attempts a reverse lookup. With no resolver it must still answer -- # glibc falls back to the numeric form -- so a NULL here means the fallback is # gone, not that the network is missing. case "$(val N08)" in resolved) bst_ok libnsl2/taddr2host-resolves ;; "") bst_skip libnsl2/taddr2host-resolves "no /etc/netconfig on this image" ;; *) bst_notok libnsl2/taddr2host-resolves "taddr2host() returned NULL for 127.0.0.1" ;; esac # --- every other probe case, against the native x86_64 control --------------- # One BST case per probe case, so a disagreement names the exact construct that # differs rather than reporting "the probe output changed". The expectation is # read from the control file, never written here: nothing outside the blocks # above encodes what a value "should" be, which is what keeps the oracle honest # -- a future edit to the probe must regenerate the control and cannot quietly # move an expectation to match a new result. # # THE JOIN IS ONE awk PASS, NOT ONE PER CASE. A spawn on wasm32 is a fresh module # instantiation rather than a fork; a process per case would cost more than the # whole rest of the suite. The control file holds only the five diffed sections, # so nothing has to be dropped here -- the abi/domain/net ids are simply not in # it, and pass 2 iterates the control. awk ' NR==FNR { # pass 1: the probe output if ($1 != "") { k=$1; $1=""; sub(/^ /,""); got[k]=$0; seen[k]=1 } next } { # pass 2: the native control if ($1 == "") next k=$1; $1=""; sub(/^ /,""); want=$0 if (seen[k]) printf "%s\t%s\t%s\t%s\n", (got[k] == want ? "OK" : "DIFF"), k, want, got[k] else printf "MISS\t%s\t%s\t\n", k, want } ' "$_pout" "$NATIVE" > "$_cmp" _tab=$(printf '\t') while IFS="$_tab" read -r verdict id want got; do [ -n "$id" ] || continue case "$verdict" in OK) bst_ok "libnsl2/$id" ;; DIFF) bst_notok "libnsl2/$id" "want:'$want' got:'$got'" ;; MISS) bst_notok "libnsl2/$id" "no value on target (probe stopped before this case); native='$want'" ;; esac done < "$_cmp" rm -f "$_pout" "$_sout" "$_cmp"