# BST-TIMEOUT: 1200 # 78 cases, 159s measured locally; the nightly's browser run still exceeds 300s . "$BST_LIB" # lockdev 1.0.3 -- the device-locking library behind the FSSTND-1.2 and SVr4 # serial-port lock protocols (Fabrizio Polacco's liblockdev, as Debian ships it). # # --------------------------------------------------------------------------- # WHY THIS SUITE IS SHAPED THE WAY IT IS # # lockdev ships NO CLI. The package is an archive, two headers and a man page -- # ${PN} is deliberately empty -- so, exactly as for libutempter next door, every # functional assertion has to come from a LINKED CONSUMER, and data/lockdev-probe # (src/lockdev_probe.c, cross-linked against the very /usr/lib/liblockdev.a that # ships) is it. # # What the probe is really asking about is not the ~870 lines of liblockdev -- a # library that small cannot fail interestingly on its own -- but the VFS and # process primitives the lock protocol stands on, which no sibling port on this # Epic exercises together: # # link(2) THE ATOMIC PRIMITIVE OF THE WHOLE PROTOCOL. dev_lock() writes # LCK..., link()s it to the SVr4 name LCK.C.001.003, then # link()s it again to the FSSTND name LCK..null. Owning the lock # IS having both links succeed, and releasing it is unlinking # them in reverse. So this port asks whether hard links, link # COUNT and same-inode identity work on this VFS -- through a # real consumer rather than a probe written to ask directly. # flock(2) the library's own serialisation: every entry point creat()s # /var/lock/LOCKDEV and takes an exclusive flock on it first. # Upstream tried fcntl for this and left the comment "this does # not work. Why?" above the #ifdef'd-out branch -- and on THIS # platform fcntl record locking is refused outright # (brintos.io/brintos/linux/issues/16, which is what took # libutempter's utmp layer down one ticket earlier), so upstream's # dead code path is precisely the one that would have failed here. # lockdev works BECAUSE it picked flock. That is worth a test. # rename(2) stale-lock reaping renames the lock aside, re-reads it, and # either unlinks it or link()s it straight back. # kill(pid,0) liveness of the recorded owner -- the entire difference between # a held lock and a stale one. # stat st_rdev the SVr4 lock name is LCK... decoded off the # device node, so major/minor out of devtmpfs is load-bearing. # # EXPECTATIONS COME FROM A NATIVE x86_64 CONTROL, never from reading the source. # data/lockdev-native.txt is the verbatim output of the same probe built against # a native build of the identical 1.0.3 source (same three meta-oe patches, same # ttylock.h repair) and run under a private user+mount namespace with a tmpfs # /var/lock. The probe prints NO pids and no path built from one, precisely so # the two runs are comparable byte for byte -- which matters more here than # usual, since writing the caller's pid into a file named after it is the whole # of what lockdev does. # # EVERY case below is scored by comparing the target's value for a tag against # the native control's value for the SAME tag, so a red is always "the platform # answered differently from x86_64" and never "the test author guessed". # --------------------------------------------------------------------------- ARCHIVE=/usr/lib/liblockdev.a HDR=/usr/include/lockdev.h TTYHDR=/usr/include/ttylock.h MAN=/usr/share/man/man3/lockdev.3 PROBE=$BST_DATA/lockdev-probe NATIVE=$BST_DATA/lockdev-native.txt LOCKDIR=/var/lock DEV=/dev/null if [ ! -f "$ARCHIVE" ]; then bst_skip lockdev/present "lockdev not installed on this image" exit 0 fi bst_ok lockdev/present # --- the package is whole ---------------------------------------------------- check lockdev/header test -f "$HDR" check lockdev/ttyheader test -f "$TTYHDR" check lockdev/staticlib test -f "$ARCHIVE" # lockdev.3 goes to lockdev-doc, and this image installs no -doc packages at all, # so its absence is an image policy and not a packaging fault of this port. A # `not ok` here would blame the port for a decision made in the image recipe. if [ -s "$MAN" ]; then bst_ok lockdev/man else bst_skip lockdev/man "lockdev-doc is not installed on this image" fi # ${PN} is empty by design: upstream's only other output is a shared object, and # shared objects are refused distro-wide. A .so appearing here would mean the # bbappend's do_compile/do_install override stopped taking effect. if [ -e /usr/lib/liblockdev.so ] || [ -e /usr/lib/liblockdev.so.1 ] \ || [ -e /usr/lib/liblockdev.so.1.0.3 ]; then bst_notok lockdev/no-shared "a shared liblockdev is installed on a static-only distro" else bst_ok lockdev/no-shared fi # --- the headers declare what the man page promises -------------------------- for fn in dev_testlock dev_lock dev_relock dev_unlock; do if grep -q "$fn" "$HDR" 2>/dev/null; then bst_ok "lockdev/hdr-$fn" else bst_notok "lockdev/hdr-$fn" "$fn missing from $HDR"; fi done # ttylock.h as meta-oe ships it declares its four functions with external linkage # and then defines them `static inline` twenty lines below, which is a constraint # violation -- four hard errors on gcc 9 under -std=gnu89 as much as under gnu17, # so the AIX-compatible API has been uncompilable since 1997. The bbappend patches # the redundant prototypes out; this is the static check that the patch is in the # shipped header, and the probe (which #includes it) is the compile-time one. if grep -q '^int ttylock (char \* devname);' "$TTYHDR" 2>/dev/null; then bst_notok lockdev/ttyheader-includable \ "ttylock.h still carries the extern prototypes that clash with its static inline defs" else bst_ok lockdev/ttyheader-includable fi check_out lockdev/ttyheader-api "ttylock" cat "$TTYHDR" # --- the archive carries the symbols ----------------------------------------- # On this arm the archive is ThinLTO bitcode, so symbol NAMES survive as plain # text but string constants do not -- the same limitation libutempter recorded. if command -v strings >/dev/null 2>&1; then syms=$(strings "$ARCHIVE" 2>/dev/null) check_out lockdev/sym-lock "dev_lock" printf '%s' "$syms" check_out lockdev/sym-unlock "dev_unlock" printf '%s' "$syms" check_out lockdev/sym-testlock "dev_testlock" printf '%s' "$syms" check_out lockdev/sym-relock "dev_relock" printf '%s' "$syms" else bst_skip lockdev/sym-lock "strings not installed on this image" bst_skip lockdev/sym-unlock "strings not installed on this image" bst_skip lockdev/sym-testlock "strings not installed on this image" bst_skip lockdev/sym-relock "strings not installed on this image" fi # --- what the library needs from the image, before it can be measured -------- # LOCK_PATH is a COMPILE-TIME constant: glibc's defines no _PATH_LOCK, # so lockdev.c falls back to "/var/lock" and there is no way to point it # elsewhere at runtime. In this image /var/lock is a symlink to ../run/lock and # NOTHING CREATES THE TARGET -- the rootfs ships an empty /run and mounts none of # the tmpfs filesystems its own /etc/fstab lists. Every lockdev call would fail # at the creat() of the semaphore. Reported as a named skip rather than absorbed # silently, then provisioned so that what the rest of the suite measures is the # LIBRARY and not the image gap. if [ -d "$LOCKDIR" ]; then bst_ok lockdev/lockdir-provisioned else bst_skip lockdev/lockdir-provisioned \ "image provisions no $LOCKDIR at boot (/var/lock -> ../run/lock is dangling); suite creates it" fi # Every creation runs in a SUBSHELL: `:` is a POSIX special builtin, so a failed # redirection on it exits the whole test file outright and the runner reports a # truncated pass list with no failure at all -- which reads as success. That cost # libutempter an iteration one ticket ago; it is not repeated here. # # AND `mkdir -p` DOES NOT PROVISION A DANGLING SYMLINK. /var/lock is a symlink to # ../run/lock whose target does not exist, and mkdir("/var/lock") then fails with # EEXIST -- the symlink itself exists, so -p's "already there" shortcut does not # apply and nothing is created. The directory that has to be made is the symlink's # TARGET. This cost an iteration here: the first on-target run reported every # functional case red with dev_lock() returning -1, which reads exactly like a # platform refusal and is not one. if [ ! -d "$LOCKDIR" ] && [ -L "$LOCKDIR" ]; then tgt=$(readlink -f "$LOCKDIR" 2>/dev/null) [ -n "$tgt" ] && ( mkdir -p "$tgt" ) 2>/dev/null fi ( mkdir -p "$LOCKDIR" ) 2>/dev/null if [ -d "$LOCKDIR" ] && ( : > "$LOCKDIR/.bst-writable" ) 2>/dev/null; then rm -f "$LOCKDIR/.bst-writable" bst_ok lockdev/lockdir-writable else bst_notok lockdev/lockdir-writable "$LOCKDIR is not writable; lockdev cannot function" fi # The SVr4 lock name is built from the device's major:minor, so the probe needs a # real device node. /dev/null is 1:3 on every Linux, which is exactly why it is # the probe's default: LCK.C.001.003 is identical in both runs. if [ -c "$DEV" ]; then bst_ok lockdev/device-present else bst_skip lockdev/device-present "$DEV is not a character device on this image" fi # --- the probe: the library actually running --------------------------------- if [ ! -f "$PROBE" ]; then bst_skip lockdev/probe-runs "data/lockdev-probe absent from the image" exit 0 fi if [ ! -f "$NATIVE" ]; then bst_skip lockdev/probe-runs "data/lockdev-native.txt absent from the image" exit 0 fi # data/* installs 0644, so stage a copy and make it executable. probe=${TMPDIR:-/tmp}/bst-lockdev-probe.$$ pout=${TMPDIR:-/tmp}/bst-lockdev-out.$$ if ! cp "$PROBE" "$probe" 2>/dev/null || ! chmod +x "$probe" 2>/dev/null; then bst_notok lockdev/probe-runs "cannot stage the probe to $probe" exit 0 fi out=$("$probe" "$DEV" 2>&1 >"$pout"); prc=$? if [ "$prc" -eq 0 ]; then bst_ok lockdev/probe-runs else bst_notok lockdev/probe-runs "probe exited rc=$prc $(_bst_1line "$out")"; fi # The probe prints a terminator, so a run that died halfway is distinguishable # from one that merely disagreed -- the truncated-output failure mode again. if grep -q '^LOCKDEV-PROBE-END$' "$pout" 2>/dev/null; then bst_ok lockdev/probe-complete else bst_notok lockdev/probe-complete "probe output has no LOCKDEV-PROBE-END terminator" fi # --- the one platform gap this port found, and what it takes down ------------ # # STALE-LOCK REAPING CANNOT WORK ON THIS PLATFORM, and the cause is one syscall. # lockdev decides whether a recorded lock owner is still alive with # # kill( pid_read, 0) == 0 || errno == EPERM (_dl_check_lock) # # and on wasm32-hwjs kill(2) NEVER FAILS. Measured against a native control with # a scratch diagnostic during this port: # # kill(reaped_child, 0) native -1/ESRCH target 0 (reported ALIVE) # kill(99991, 0) native -1/ESRCH target 0 <- never existed # kill(1, 0) native -1/EPERM target 0 # kill(dead, SIGTERM) native -1 target 0 # # So every lock whose owner has died still reads as held, for ever. The port is # not at fault and neither is the VFS -- link(), rename() and flock() all work, # which is why the other nine groups are green. # # ALREADY TRACKED as brintos.io/brintos/linux/issues/21 ("signal delivery does # not work at all -- kill(2) reports success and nothing is delivered"), whose # comment thread already records the sig==0 QUERY form specifically, with the # same native contrast. Nothing was re-filed: a fourth reproducer of a documented # fact is noise. # # ONE LOUD RED, AND SKIPS THAT NAME IT. testlock-reaps-stale is the assertion # that the reaper works and it stays a hard failure -- that is the finding. The # three cases DOWNSTREAM of it are not independent observations: once the reaper # cannot fire they are all the same fact restated, and they will all flip back # together the day #21 lands, with no change to this file. They are recorded as # skips carrying the issue, which is what ticket_investigate asks for coverage # that legitimately cannot run yet. BLOCKED21=" lock-after-stale-rc owner-is-self-again unlock-after-stale-rc " ISSUE21="blocked by brintos.io/brintos/linux/issues/21 (kill(2) always reports success, so a dead lock owner still reads as alive and the stale-lock reaper never fires)" # Is the block actually in effect on this run? Decided from the measurement, not # assumed -- so that when #21 lands this suite goes green by itself. want27=$(awk '$1 ~ /^L[0-9]*-testlock-reaps-stale$/{print $2; exit}' "$NATIVE") got27=$(awk '$1 ~ /^L[0-9]*-testlock-reaps-stale$/{print $2; exit}' "$pout") if [ "$want27" = "$got27" ]; then BLOCK=""; else BLOCK="$ISSUE21"; fi # Aggregate: every line, compared in the guest against the native control with # cmp(1) -- the BRINTOS-260 lesson that a console dump truncates long lines, so # the comparison has to happen on the target and not in the transcript. if cmp -s "$pout" "$NATIVE"; then bst_ok lockdev/matches-native elif [ -n "$BLOCK" ]; then bst_skip lockdev/matches-native "$BLOCK" else bst_notok lockdev/matches-native \ "probe output differs from the native x86_64 control; see the per-case results below" fi # ...and then case by case, so a red names itself. The expected value for each # tag is READ OUT OF THE NATIVE CONTROL, never written here: that is what makes a # red mean "wasm32-hwjs answered differently from x86_64" and nothing else. while read -r tag want; do [ -n "$tag" ] || continue case "$tag" in LOCKDEV-PROBE-END) continue ;; esac got=$(awk -v t="$tag" '$1==t{print $2; found=1; exit} END{if(!found) print ""}' "$pout") cs=$(printf '%s' "$tag" | sed 's/^L[0-9]*-//') id=lockdev/$cs if [ "$want" = "$got" ]; then bst_ok "$id" else case "$cs" in # The one loud red carries the issue in its OWN detail string, so the # failure is self-attributing in the runner's report and a reader never # has to correlate it with anything else to know what it means. testlock-reaps-stale) bst_notok "$id" "dev_testlock() still reports the dead owner (want:'$want' got:'$got'); $ISSUE21" ;; *) case "$BLOCKED21" in *" $cs "*) bst_skip "$id" "$ISSUE21" ;; *) bst_notok "$id" "want:'$want' got:'$got'" ;; esac ;; esac fi done < "$NATIVE" rm -f "$probe" "$pout"