. "$BST_LIB" # BST-TIMEOUT: 500 # 203 cases, ~200 execs, no loops and no corpus. MEASURED at # # elapsed=132s on this host (2026-08-26, # # ==BST-SUITE pps-tools elapsed=132s bound=300s cases=203==) # # and declared at ~3.8x that. Nothing here waits: every case # # either decides in argv without a syscall, is refused at # # open(2), or is refused at ioctl(2), and returns at once. # # The two programs that could block (ppstest, ppswatch) only # # reach their fetch loop through a PPS source, and there is # # none, so they exit during setup. # pps-tools -- the LinuxPPS userland on a machine with no PPS subsystem. # # WHAT THIS MACHINE CAN AND CANNOT TEST. pps-tools is five programs: four C # binaries (ppstest, ppsctl, ppswatch, ppsldisc) and one /bin/sh script # (ppsfind). The four C programs all reach the kernel through timepps.h, whose # entry point time_pps_create() issues ioctl(PPS_GETPARAMS) from linux/pps.h. # This kernel has NO PPS SUBSYSTEM AT ALL: CONFIG_PPS is absent from # arch/wasm32/configs/wasm32_defconfig, so the pps class is never registered -- # there is no /sys/class/pps, no /dev/pps* node, and no ldisc for ppsldisc to # attach. A pulse-per-second line is a wire into a physical pin; a browser # machine has no pins, so this is a property of the target and not a defect. # Whether it is still true is NOT assumed: the probe below reads the live # system, and every gated case runs the real assertion the moment a source # appears. # # WHAT IS THEREFORE ASSERTED FOR REAL, and it is the whole of the reachable # program: # # - THE ARGV LAYER OF ALL FIVE PROGRAMS, WHICH IS WHERE THEY DIFFER MOST. # ppsctl and ppswatch parse with getopt_long and have genuinely different # tables; ppstest has NO OPTION PARSER AT ALL (every argv element is a # device path, so `ppstest -h` tries to open a file called "-h"); ppsldisc # takes exactly one operand and ignores the rest; ppsfind is a shell script # with its own arity check. Each decision is made before any device is # touched and each has its own exit status and its own stream. # # - A THREE-RUNG ERROR LATTICE, AND THE RUNGS ARE DISTINGUISHABLE. The same # argv reaches a different depth depending only on what the path is: # 1. decided in argv -- no syscall at all (missing operand, bad option) # 2. refused at open(2) -- `unable to open device "X" (No such file or # directory)` for an absent path, `(Is a directory)` for a directory # 3. refused at ioctl(2) -- an ordinary FILE opens fine, so execution # carries past open into a REAL ioctl(PPS_GETPARAMS) on a REAL # descriptor. The kernel answers ENOTTY; time_pps_create() maps ANY # nonzero ioctl return to EOPNOTSUPP and the caller reports `cannot # create a PPS source from device "X" (Operation not supported)`. # Rung 3 is the deepest point this machine can reach, and rungs 1 and 2 are # its controls: the ONLY thing that differs between them is how far # execution got. A binary that trapped on its first ioctl would produce # rung 2 and never rung 3, so these cases are also the runtime no-trap # proof. ppsldisc gets the same treatment against ioctl(TIOCSETD). # # - THE STREAM SPLIT, WHICH A COMBINED-OUTPUT CHECK CANNOT SEE. ppsctl and # ppswatch print `No command specified!` / `Device name missing!` on STDOUT # while their usage goes to STDERR, and ppstest prints `trying PPS source` # on stdout before failing on stderr. Every such case is asserted as two # cases against two separately-captured streams. # # EVERY EXPECTATION HERE WAS READ OFF A NATIVE x86-64 BUILD of the same SRCREV # (c50cb7183e252b47308fa22f420e0a877277aa29, meta-oe recipes-support/pps-tools) # during the port -- the exit statuses, the message wording, and which stream # each goes to -- rather than transcribed from the source. # # FOUR UPSTREAM BEHAVIOURS ARE PINNED RATHER THAN PATCHED, because they are # pps-tools' on every architecture and a future change to any should be visible # here rather than silent (pps-tools is a third-party public repository; see # no-public-upstream-reports.md): # - THERE IS NO VERSION OPTION, AND NO VERSION STRING, ANYWHERE IN THE # PACKAGE. No -DVERSION in the Makefile and no version constant in any # source file. `ppsctl -V` and `ppswatch -V` are rejected by getopt as # invalid options; `ppstest -V` is treated as a DEVICE PATH. The cases # below pin all three, so a release that adds one reddens them. # - ppsctl -h / --help and ppswatch -h print their usage on STDERR and exit 0. # - ppsctl's and ppswatch's operand check is `optind == argc - 1`, an # EQUALITY, so `Device name missing!` is also what TOO MANY devices report. # - ppsfind interpolates its argument into `grep` WITHOUT a `--` guard, so an # argument beginning with `-` is eaten by grep as an option: `ppsfind # --version` prints GREP's version banner and exits 0. require ppstest pps-tools/present require ppsctl pps-tools/present-ppsctl require ppswatch pps-tools/present-ppswatch require ppsldisc pps-tools/present-ppsldisc require ppsfind pps-tools/present-ppsfind W=${TMPDIR:-/tmp}/bst-pps.$$ out=$W.out; err=$W.err mkdir -p "$W.d" 2>/dev/null : > "$out"; : > "$err" plain=$W.d/plain; printf 'this is not a pps device\n' > "$plain" empty=$W.d/empty; : > "$empty" adir=$W.d/adir; mkdir -p "$adir" 2>/dev/null absent=$W.d/no-such-device # ------------------------------------------------------------------ probe ---- # Read the live system for a PPS source, by the two routes the kernel would have # to provide first. Nothing below is hardcoded: both are set only from what this # machine actually reports. ppsdev="" for d in /dev/pps*; do [ -c "$d" ] && { ppsdev=$d; break; } done ppsclass="" for c in /sys/class/pps/pps*; do [ -d "$c" ] && { ppsclass=$c; break; } done # --------------------------------------------------------------- identity ---- check_out pps-tools/path-ppstest "/usr/bin/ppstest" command -v ppstest check_out pps-tools/path-ppsctl "/usr/bin/ppsctl" command -v ppsctl check_out pps-tools/path-ppswatch "/usr/bin/ppswatch" command -v ppswatch check_out pps-tools/path-ppsldisc "/usr/bin/ppsldisc" command -v ppsldisc check_out pps-tools/path-ppsfind "/usr/bin/ppsfind" command -v ppsfind # ppsfind is the one shell script in the package; the other four are ELF-free # wasm32 binaries. Assert the script is a script -- if it were ever compiled or # replaced the shebang would go. check pps-tools/ppsfind-is-script sh -c "head -1 \"\$(command -v ppsfind)\" | grep -q '^#!/bin/sh'" # The -dev package ships the PPS API header, which is the package's actual API # surface and the only non-empty subpackage besides ${PN}. check pps-tools/header-present test -f /usr/include/sys/timepps.h check_out pps-tools/header-guard "_SYS_TIMEPPS_H_" cat /usr/include/sys/timepps.h check_out pps-tools/header-linuxpps "define LINUXPPS" cat /usr/include/sys/timepps.h # The header's whole reason to exist on this image: it is the shim over # linux/pps.h, so it must still name the ioctl the four binaries reach through. check_out pps-tools/header-getparams "PPS_GETPARAMS" cat /usr/include/sys/timepps.h check_out pps-tools/header-api "time_pps_create" cat /usr/include/sys/timepps.h # ------------------------------------------------- no version, anywhere ------ # The ticket asked for "version text". There is none: no -DVERSION in the # Makefile and no version constant in any source file. These three cases pin # what each program does with a version flag INSTEAD, and they are three # DIFFERENT behaviours because the three argv layers are different. # ppsctl and ppswatch reject -V through getopt, with getopt's own wording. check_rc pps-tools/ppsctl-V-rc 1 sh -c "ppsctl -V > /dev/null 2>'$err'" check_out pps-tools/ppsctl-V-msg "invalid option -- 'V'" cat "$err" check_rc pps-tools/ppswatch-V-rc 1 sh -c "ppswatch -V > /dev/null 2>'$err'" check_out pps-tools/ppswatch-V-msg "invalid option -- 'V'" cat "$err" # ...and --version through getopt_long, which has its own distinct wording. check_rc pps-tools/ppsctl-longV-rc 1 sh -c "ppsctl --version > /dev/null 2>'$err'" check_out pps-tools/ppsctl-longV-msg "unrecognized option '--version'" cat "$err" check_rc pps-tools/ppswatch-longV-rc 1 sh -c "ppswatch --version > /dev/null 2>'$err'" check_out pps-tools/ppswatch-longV-msg "unrecognized option '--version'" cat "$err" # ppstest has NO option parser at all -- main() hands every argv element # straight to find_source() -- so -V is a PATH, and the diagnostic proves it by # quoting it back as one. This is the case that distinguishes "no version flag" # from "no argument parsing". check_rc pps-tools/ppstest-V-rc 1 sh -c "ppstest -V > '$out' 2>'$err'" check_out pps-tools/ppstest-V-out "trying PPS source \"-V\"" cat "$out" check_out pps-tools/ppstest-V-err "unable to open device \"-V\"" cat "$err" # Same for -h: ppstest has no help either, and says so the same way. check_rc pps-tools/ppstest-h-rc 1 sh -c "ppstest -h > '$out' 2>'$err'" check_out pps-tools/ppstest-h-out "trying PPS source \"-h\"" cat "$out" # ---------------------------------------------------------------- ppstest ---- # No operand: usage on STDERR, exit 1, and nothing on stdout. check_rc pps-tools/test-noargs-rc 1 sh -c "ppstest > '$out' 2>'$err'" check_out pps-tools/test-noargs-usage "usage:" cat "$err" check_out pps-tools/test-noargs-usage-form " [ ...]" cat "$err" check_eq pps-tools/test-noargs-no-stdout "" "$(cat "$out")" # RUNG 2 -- refused at open(2). The path does not exist. check_rc pps-tools/test-absent-rc 1 sh -c "ppstest '$absent' > '$out' 2>'$err'" check_out pps-tools/test-absent-trying "trying PPS source \"$absent\"" cat "$out" check_out pps-tools/test-absent-msg "unable to open device \"$absent\" (No such file or directory)" cat "$err" # ...and a directory, which fails at the same open(2) with a DIFFERENT errno. # Two distinct errnos through one code path is what shows %m is really rendering # the failure rather than a fixed string. check_rc pps-tools/test-dir-rc 1 sh -c "ppstest '$adir' > /dev/null 2>'$err'" check_out pps-tools/test-dir-msg "unable to open device \"$adir\" (Is a directory)" cat "$err" # RUNG 3 -- refused at ioctl(2). An ordinary file OPENS, so execution carries # past open(2) into a real ioctl(PPS_GETPARAMS) on a real descriptor; the kernel # answers ENOTTY and time_pps_create() maps it to EOPNOTSUPP. This is the # deepest point reachable with no PPS subsystem, and the two cases above are its # controls -- the only difference is how far execution got. check_rc pps-tools/test-plain-rc 1 sh -c "ppstest '$plain' > '$out' 2>'$err'" check_out pps-tools/test-plain-trying "trying PPS source \"$plain\"" cat "$out" check_out pps-tools/test-plain-msg "cannot create a PPS source from device \"$plain\" (Operation not supported)" cat "$err" # An EMPTY regular file reaches the same rung -- the ioctl does not read the # file, so content is irrelevant and only the descriptor matters. check_rc pps-tools/test-emptyfile-rc 1 sh -c "ppstest '$empty' > /dev/null 2>'$err'" check_out pps-tools/test-emptyfile-msg "(Operation not supported)" cat "$err" # /dev/null is a character device that is not a PPS device: same rung again, # reached through a real device node rather than a regular file. check_rc pps-tools/test-devnull-rc 1 sh -c "ppstest /dev/null > /dev/null 2>'$err'" check_out pps-tools/test-devnull-msg "cannot create a PPS source from device \"/dev/null\" (Operation not supported)" cat "$err" # The empty string is an operand like any other, and open(2) refuses it. check_rc pps-tools/test-emptyarg-rc 1 sh -c "ppstest '' > '$out' 2>'$err'" check_out pps-tools/test-emptyarg-msg "unable to open device \"\" (No such file or directory)" cat "$err" # find_source() is called in a loop and main() exits on the FIRST failure, so a # second operand is never reached. Asserted by the absence of its diagnostic. check_rc pps-tools/test-two-rc 1 sh -c "ppstest '$absent' /dev/null > /dev/null 2>'$err'" check_out pps-tools/test-two-first "unable to open device \"$absent\"" cat "$err" check_eq pps-tools/test-two-no-second "0" "$(grep -c 'dev/null' "$err")" # ----------------------------------------------------------------- ppsctl ---- # -h and --help print the usage on STDERR and exit 0 -- both spellings, and the # exit status is 0, unlike every other path in this program. check_rc pps-tools/ctl-h-rc 0 sh -c "ppsctl -h > '$out' 2>'$err'" check_out pps-tools/ctl-h-usage "Usage:" cat "$err" check_eq pps-tools/ctl-h-no-stdout "" "$(cat "$out")" check_rc pps-tools/ctl-help-rc 0 sh -c "ppsctl --help > /dev/null 2>'$err'" check_out pps-tools/ctl-help-usage "Usage:" cat "$err" # The usage block is printed from one fprintf, so these assert the option table # itself -- all four commands and both edge options, each with its own text. check_out pps-tools/ctl-usage-synopsis "[-bBfFac] " cat "$err" check_out pps-tools/ctl-usage-b "-b bind kernel PPS consumer" cat "$err" check_out pps-tools/ctl-usage-B "-B unbind kernel PPS consumer" cat "$err" check_out pps-tools/ctl-usage-f "-f set kernel NTP PPS flags" cat "$err" check_out pps-tools/ctl-usage-F "-F unset kernel NTP PPS flags" cat "$err" check_out pps-tools/ctl-usage-a "-a use assert edge" cat "$err" check_out pps-tools/ctl-usage-c "-c use clear edge (default)" cat "$err" check_out pps-tools/ctl-usage-cmds "Commands:" cat "$err" check_out pps-tools/ctl-usage-opts "Options:" cat "$err" # getopt processes left to right and case 'h' exits immediately, so -h WINS over # an earlier command and a following operand is never looked at. check_rc pps-tools/ctl-b-then-h-rc 0 sh -c "ppsctl -b -h '$plain' > /dev/null 2>'$err'" check_out pps-tools/ctl-b-then-h-usage "Usage:" cat "$err" # An unknown short option: getopt's own diagnostic, THEN the usage, both on # stderr, exit 1. check_rc pps-tools/ctl-badopt-rc 1 sh -c "ppsctl -Z > /dev/null 2>'$err'" check_out pps-tools/ctl-badopt-msg "invalid option -- 'Z'" cat "$err" check_out pps-tools/ctl-badopt-usage "Usage:" cat "$err" # ...and an unknown LONG option, which getopt_long words differently. check_rc pps-tools/ctl-badlong-rc 1 sh -c "ppsctl --nosuchopt > /dev/null 2>'$err'" check_out pps-tools/ctl-badlong-msg "unrecognized option '--nosuchopt'" cat "$err" # RUNG 1 -- decided in argv, no syscall. Two distinct refusals with two distinct # messages, and both put the message on STDOUT and the usage on STDERR. check_rc pps-tools/ctl-noargs-rc 1 sh -c "ppsctl > '$out' 2>'$err'" check_out pps-tools/ctl-noargs-msg "No command specified!" cat "$out" check_out pps-tools/ctl-noargs-usage "Usage:" cat "$err" # An edge option is not a command: -a alone still has nothing to do. check_rc pps-tools/ctl-a-only-rc 1 sh -c "ppsctl -a '$plain' > '$out' 2>/dev/null" check_out pps-tools/ctl-a-only-msg "No command specified!" cat "$out" check_rc pps-tools/ctl-c-only-rc 1 sh -c "ppsctl -c '$plain' > '$out' 2>/dev/null" check_out pps-tools/ctl-c-only-msg "No command specified!" cat "$out" # A command with no operand. check_rc pps-tools/ctl-b-nodev-rc 1 sh -c "ppsctl -b > '$out' 2>'$err'" check_out pps-tools/ctl-b-nodev-msg "Device name missing!" cat "$out" check_out pps-tools/ctl-b-nodev-usage "Usage:" cat "$err" # The operand check is `optind == argc - 1`, an EQUALITY -- so TOO MANY operands # report the same "missing" diagnostic. That is the upstream behaviour this # suite pins rather than patches; the case exists so a future fix is visible. check_rc pps-tools/ctl-b-2dev-rc 1 sh -c "ppsctl -b '$plain' '$plain' > '$out' 2>/dev/null" check_out pps-tools/ctl-b-2dev-msg "Device name missing!" cat "$out" check_rc pps-tools/ctl-b-3dev-rc 1 sh -c "ppsctl -b '$plain' '$plain' '$plain' > '$out' 2>/dev/null" check_out pps-tools/ctl-b-3dev-msg "Device name missing!" cat "$out" # RUNG 2 and 3 for each of the four commands, short and long spelling. All four # reach find_source() before doing anything else, so all four produce the same # depth -- which is the point: the command verb is decided in argv and the # device is opened afterwards. check_rc pps-tools/ctl-b-absent-rc 1 sh -c "ppsctl -b '$absent' > '$out' 2>'$err'" check_out pps-tools/ctl-b-absent-trying "trying PPS source \"$absent\"" cat "$out" check_out pps-tools/ctl-b-absent-msg "unable to open device \"$absent\" (No such file or directory)" cat "$err" check_rc pps-tools/ctl-b-dir-rc 1 sh -c "ppsctl -b '$adir' > /dev/null 2>'$err'" check_out pps-tools/ctl-b-dir-msg "unable to open device \"$adir\" (Is a directory)" cat "$err" check_rc pps-tools/ctl-b-plain-rc 1 sh -c "ppsctl -b '$plain' > '$out' 2>'$err'" check_out pps-tools/ctl-b-plain-trying "trying PPS source \"$plain\"" cat "$out" check_out pps-tools/ctl-b-plain-msg "cannot create a PPS source from device \"$plain\" (Operation not supported)" cat "$err" check_rc pps-tools/ctl-B-plain-rc 1 sh -c "ppsctl -B '$plain' > /dev/null 2>'$err'" check_out pps-tools/ctl-B-plain-msg "(Operation not supported)" cat "$err" check_rc pps-tools/ctl-f-plain-rc 1 sh -c "ppsctl -f '$plain' > /dev/null 2>'$err'" check_out pps-tools/ctl-f-plain-msg "(Operation not supported)" cat "$err" check_rc pps-tools/ctl-F-plain-rc 1 sh -c "ppsctl -F '$plain' > /dev/null 2>'$err'" check_out pps-tools/ctl-F-plain-msg "(Operation not supported)" cat "$err" # The four long spellings resolve to the same four commands. check_rc pps-tools/ctl-bind-plain-rc 1 sh -c "ppsctl --bind '$plain' > /dev/null 2>'$err'" check_out pps-tools/ctl-bind-plain-msg "(Operation not supported)" cat "$err" check_rc pps-tools/ctl-unbind-plain-rc 1 sh -c "ppsctl --unbind '$plain' > /dev/null 2>'$err'" check_out pps-tools/ctl-unbind-plain-msg "(Operation not supported)" cat "$err" check_rc pps-tools/ctl-setflags-plain-rc 1 sh -c "ppsctl --set-flags '$plain' > /dev/null 2>'$err'" check_out pps-tools/ctl-setflags-plain-msg "(Operation not supported)" cat "$err" check_rc pps-tools/ctl-unsetflags-plain-rc 1 sh -c "ppsctl --unset-flags '$plain' > /dev/null 2>'$err'" check_out pps-tools/ctl-unsetflags-plain-msg "(Operation not supported)" cat "$err" check_rc pps-tools/ctl-assert-long-rc 1 sh -c "ppsctl --bind --assert '$plain' > /dev/null 2>'$err'" check_out pps-tools/ctl-assert-long-msg "(Operation not supported)" cat "$err" check_rc pps-tools/ctl-clear-long-rc 1 sh -c "ppsctl --bind --clear '$plain' > /dev/null 2>'$err'" check_out pps-tools/ctl-clear-long-msg "(Operation not supported)" cat "$err" # Combined and repeated short options are accepted by getopt and still reach the # device, so the whole option string is consumed rather than only its first # letter. check_rc pps-tools/ctl-bB-plain-rc 1 sh -c "ppsctl -bB '$plain' > /dev/null 2>'$err'" check_out pps-tools/ctl-bB-plain-msg "(Operation not supported)" cat "$err" check_rc pps-tools/ctl-ba-plain-rc 1 sh -c "ppsctl -ba '$plain' > /dev/null 2>'$err'" check_out pps-tools/ctl-ba-plain-msg "(Operation not supported)" cat "$err" check_rc pps-tools/ctl-bc-plain-rc 1 sh -c "ppsctl -bc '$plain' > /dev/null 2>'$err'" check_out pps-tools/ctl-bc-plain-msg "(Operation not supported)" cat "$err" check_rc pps-tools/ctl-bfa-plain-rc 1 sh -c "ppsctl -bfa '$plain' > /dev/null 2>'$err'" check_out pps-tools/ctl-bfa-plain-msg "(Operation not supported)" cat "$err" # The empty string is an operand: the command is valid, so it is open(2) that # refuses, not the argv check. check_rc pps-tools/ctl-b-emptyarg-rc 1 sh -c "ppsctl -b '' > '$out' 2>'$err'" check_out pps-tools/ctl-b-emptyarg-msg "unable to open device \"\" (No such file or directory)" cat "$err" # --------------------------------------------------------------- ppswatch ---- # A DIFFERENT option table from ppsctl's, with a required-argument option that # ppsctl does not have. Same -h-exits-0-on-stderr shape. check_rc pps-tools/watch-h-rc 0 sh -c "ppswatch -h > '$out' 2>'$err'" check_out pps-tools/watch-h-usage "usage:" cat "$err" check_out pps-tools/watch-h-form "[-a | -c] " cat "$err" check_eq pps-tools/watch-h-no-stdout "" "$(cat "$out")" # ppswatch's usage is lowercase `usage:` where ppsctl's is `Usage:` -- asserted # because it is the cheapest way to catch the two programs' blocks being # confused for one another. check_eq pps-tools/watch-usage-lowercase "0" "$(grep -c '^Usage:' "$err")" check_rc pps-tools/watch-badopt-rc 1 sh -c "ppswatch -Z > /dev/null 2>'$err'" check_out pps-tools/watch-badopt-msg "invalid option -- 'Z'" cat "$err" check_rc pps-tools/watch-badlong-rc 1 sh -c "ppswatch --nosuchopt > /dev/null 2>'$err'" check_out pps-tools/watch-badlong-msg "unrecognized option '--nosuchopt'" cat "$err" # ppswatch has no "command" concept, so a bare invocation is an operand error, # not ppsctl's "No command specified!". Same stdout/stderr split. check_rc pps-tools/watch-noargs-rc 1 sh -c "ppswatch > '$out' 2>'$err'" check_out pps-tools/watch-noargs-msg "Device name missing!" cat "$out" check_out pps-tools/watch-noargs-usage "usage:" cat "$err" check_eq pps-tools/watch-noargs-not-ctl "0" "$(grep -c 'No command specified' "$out")" # The same equality check as ppsctl's, so too many operands report "missing". check_rc pps-tools/watch-2dev-rc 1 sh -c "ppswatch '$plain' '$plain' > '$out' 2>/dev/null" check_out pps-tools/watch-2dev-msg "Device name missing!" cat "$out" # -m is the one required-argument option in the package, and its validation is a # strtoul plus a trailing-garbage check. Four rungs, each with its own message. check_rc pps-tools/watch-m-junk-rc 1 sh -c "ppswatch -m abc '$plain' > /dev/null 2>'$err'" check_out pps-tools/watch-m-junk-msg "Error parsing margin value." cat "$err" check_rc pps-tools/watch-m-trail-rc 1 sh -c "ppswatch -m 5x '$plain' > /dev/null 2>'$err'" check_out pps-tools/watch-m-trail-msg "Error parsing margin value." cat "$err" # THE EMPTY STRING IS THE ONE NON-NUMERIC ARGUMENT THAT IS ACCEPTED, and it is # accepted as 0. strtoul("") consumes nothing and leaves endptr AT THE NUL, so # the trailing-garbage test `if (*c)` sees the terminator and passes -- where a # single SPACE is rejected, because strtoul skips the whitespace, still finds no # digits, and leaves endptr on the space itself. The two cases are adjacent on # purpose: they differ by one character of input and take opposite branches, so # together they pin exactly where the boundary is. This expectation was WRONG in # this suite's first draft (it assumed "" was garbage) and was corrected against # the oracle. check_rc pps-tools/watch-m-empty-rc 1 sh -c "ppswatch -m '' '$plain' > /dev/null 2>'$err'" check_out pps-tools/watch-m-empty-msg "(Operation not supported)" cat "$err" check_eq pps-tools/watch-m-empty-not-rejected "0" "$(grep -c 'Error parsing' "$err")" check_rc pps-tools/watch-m-space-rc 1 sh -c "ppswatch -m ' ' '$plain' > /dev/null 2>'$err'" check_out pps-tools/watch-m-space-msg "Error parsing margin value." cat "$err" check_rc pps-tools/watch-m-plus-rc 1 sh -c "ppswatch -m + '$plain' > /dev/null 2>'$err'" check_out pps-tools/watch-m-plus-msg "Error parsing margin value." cat "$err" # A NEGATIVE margin is a separate diagnostic, and reaching it proves the value # really went through strtoul: "-1" parses to ULONG_MAX, which truncates to -1 # in the int the program stores it in, which is what the `margin < 0` test then # catches. A plain atoi() would reach the same message by a different route, but # a parser that rejected the leading '-' as garbage would print the case above. check_rc pps-tools/watch-m-neg-rc 1 sh -c "ppswatch -m -1 '$plain' > /dev/null 2>'$err'" check_out pps-tools/watch-m-neg-msg "Negative margin not supported." cat "$err" check_eq pps-tools/watch-m-neg-not-parse "0" "$(grep -c 'Error parsing' "$err")" # A VALID margin is consumed and execution carries on to the device -- which is # what shows the four cases above are rejections rather than the option being # ignored. strtoul base 0 also takes hex, so 0x10 is accepted too. check_rc pps-tools/watch-m-ok-rc 1 sh -c "ppswatch -m 10 '$plain' > /dev/null 2>'$err'" check_out pps-tools/watch-m-ok-msg "(Operation not supported)" cat "$err" check_rc pps-tools/watch-m-hex-rc 1 sh -c "ppswatch -m 0x10 '$plain' > /dev/null 2>'$err'" check_out pps-tools/watch-m-hex-msg "(Operation not supported)" cat "$err" check_rc pps-tools/watch-m-long-rc 1 sh -c "ppswatch --margin=10 '$plain' > /dev/null 2>'$err'" check_out pps-tools/watch-m-long-msg "(Operation not supported)" cat "$err" check_rc pps-tools/watch-m-zero-rc 1 sh -c "ppswatch -m 0 '$plain' > /dev/null 2>'$err'" check_out pps-tools/watch-m-zero-msg "(Operation not supported)" cat "$err" # The same three rungs ppstest and ppsctl have. check_rc pps-tools/watch-absent-rc 1 sh -c "ppswatch '$absent' > '$out' 2>'$err'" check_out pps-tools/watch-absent-trying "trying PPS source \"$absent\"" cat "$out" check_out pps-tools/watch-absent-msg "unable to open device \"$absent\" (No such file or directory)" cat "$err" check_rc pps-tools/watch-dir-rc 1 sh -c "ppswatch '$adir' > /dev/null 2>'$err'" check_out pps-tools/watch-dir-msg "unable to open device \"$adir\" (Is a directory)" cat "$err" check_rc pps-tools/watch-plain-rc 1 sh -c "ppswatch '$plain' > /dev/null 2>'$err'" check_out pps-tools/watch-plain-msg "cannot create a PPS source from device \"$plain\" (Operation not supported)" cat "$err" check_rc pps-tools/watch-a-plain-rc 1 sh -c "ppswatch -a '$plain' > /dev/null 2>'$err'" check_out pps-tools/watch-a-plain-msg "(Operation not supported)" cat "$err" check_rc pps-tools/watch-c-plain-rc 1 sh -c "ppswatch -c '$plain' > /dev/null 2>'$err'" check_out pps-tools/watch-c-plain-msg "(Operation not supported)" cat "$err" # --------------------------------------------------------------- ppsldisc ---- # ppsldisc reaches a DIFFERENT ioctl from the other three -- TIOCSETD with # N_PPS, not PPS_GETPARAMS -- so it is a second, independent proof that an ioctl # on this target really executes and really returns the kernel's errno. It also # reports through perror(3) rather than %m, which is a different formatting path. check_rc pps-tools/ldisc-noargs-rc 1 sh -c "ppsldisc > '$out' 2>'$err'" check_out pps-tools/ldisc-noargs-usage "usage:" cat "$err" check_out pps-tools/ldisc-noargs-form "" cat "$err" check_eq pps-tools/ldisc-noargs-no-stdout "" "$(cat "$out")" # RUNG 2 -- the control. open(2) refuses, so no ioctl is ever issued, and the # perror label says which call failed. check_rc pps-tools/ldisc-absent-rc 1 sh -c "ppsldisc '$absent' > /dev/null 2>'$err'" check_out pps-tools/ldisc-absent-msg "open: No such file or directory" cat "$err" check_eq pps-tools/ldisc-absent-no-ioctl "0" "$(grep -c 'ioctl' "$err")" check_rc pps-tools/ldisc-dir-rc 1 sh -c "ppsldisc '$adir' > /dev/null 2>'$err'" check_out pps-tools/ldisc-dir-msg "open: Is a directory" cat "$err" # RUNG 3 -- a real ioctl(TIOCSETD) on a real descriptor, refused with ENOTTY # because a regular file is not a tty. The two cases above are the control: the # only difference is that execution got one syscall further. check_rc pps-tools/ldisc-plain-rc 1 sh -c "ppsldisc '$plain' > /dev/null 2>'$err'" check_out pps-tools/ldisc-plain-msg "ioctl(TIOCSETD): Inappropriate ioctl for device" cat "$err" check_eq pps-tools/ldisc-plain-not-open "0" "$(grep -c '^open:' "$err")" check_rc pps-tools/ldisc-empty-rc 1 sh -c "ppsldisc '$empty' > /dev/null 2>'$err'" check_out pps-tools/ldisc-empty-msg "ioctl(TIOCSETD)" cat "$err" # Only argv[1] is used; extra operands are ignored rather than refused. check_rc pps-tools/ldisc-extra-rc 1 sh -c "ppsldisc '$plain' extra > /dev/null 2>'$err'" check_out pps-tools/ldisc-extra-msg "ioctl(TIOCSETD)" cat "$err" # ---------------------------------------------------------------- ppsfind ---- # The one shell script. Its arity check is `-ne 1`, so both too few and too many # are refused, with the usage on stderr and exit 1. check_rc pps-tools/find-noargs-rc 1 sh -c "ppsfind > '$out' 2>'$err'" check_out pps-tools/find-noargs-msg "usage: ppsfind " cat "$err" check_eq pps-tools/find-noargs-no-stdout "" "$(cat "$out")" check_rc pps-tools/find-2args-rc 1 sh -c "ppsfind a b > /dev/null 2>'$err'" check_out pps-tools/find-2args-msg "usage: ppsfind " cat "$err" check_rc pps-tools/find-3args-rc 1 sh -c "ppsfind a b c > /dev/null 2>'$err'" check_out pps-tools/find-3args-msg "usage: ppsfind " cat "$err" # ONE argument on a system with no PPS class. /sys/class/pps does not exist, so # the script's `for dev in $SYS/*` glob does not expand and the loop body runs # once against the literal pattern; both greps fail, the && never fires, and the # script reaches its unconditional `exit 0`. # # The ASSERTION IS EXIT 0 AND AN EMPTY STDOUT, deliberately not the wording of # the greps' complaints on stderr: that text is the grep implementation's, and # this image's grep is not the one the expectation was read off. Exit status and # "found nothing" are the script's own contract and are what a caller acts on. check_rc pps-tools/find-empty-rc 0 sh -c "ppsfind gps > '$out' 2>/dev/null" check_eq pps-tools/find-empty-no-stdout "" "$(cat "$out")" check_rc pps-tools/find-empty-rc2 0 sh -c "ppsfind pps0 > '$out' 2>/dev/null" check_eq pps-tools/find-empty-no-stdout2 "" "$(cat "$out")" # A name that cannot match anything at all reaches the same exit. check_rc pps-tools/find-nomatch-rc 0 sh -c "ppsfind zzz-no-such-source > '$out' 2>/dev/null" check_eq pps-tools/find-nomatch-no-stdout "" "$(cat "$out")" # The script interpolates its argument into `grep` WITHOUT a `--` guard, so an # argument beginning with '-' is consumed by grep as an OPTION rather than # searched for as a name. `--version` therefore makes grep print its own banner # and succeed, which fires the && and produces output on a system with no PPS # devices at all. Pinned, not patched: it is upstream's on every architecture, # and this case is what makes a future `--` guard visible here. # What this case can assert depends on the grep this image ships, so read that # rather than assume it: the leak is only OBSERVABLE through an option the local # grep actually accepts. GNU grep prints a banner and exits 0; busybox grep # rejects --version, both greps fail, and the script's && never fires -- which is # indistinguishable from the guarded behaviour and would assert nothing. if grep --version >/dev/null 2>&1; then check_rc pps-tools/find-dashdash-rc 0 sh -c "ppsfind --version > '$out' 2>/dev/null" check pps-tools/find-dashdash-leaks sh -c "grep -qi 'grep' '$out'" else bst_skip pps-tools/find-dashdash-rc "this image's grep rejects --version, so the unguarded-argument path cannot be driven through it" bst_skip pps-tools/find-dashdash-leaks "this image's grep rejects --version" fi # ------------------------------------------------------- source-gated ---- # Everything above holds with no PPS source. These run the SOURCE-DRIVEN paths # for real the moment one exists, and skip on a probe that measured its absence # -- never on an assumption. if [ -n "$ppsdev" ]; then # A real PPS character device: time_pps_create() must now SUCCEED, which # is the one thing no case above can reach. check_out pps-tools/src-test-found "found PPS source \"$ppsdev\"" sh -c ". \"$BST_LIB\"; bst_bound 10 ppstest '$ppsdev' 2>&1 | head -20" check_out pps-tools/src-test-fetch "assert" sh -c ". \"$BST_LIB\"; bst_bound 10 ppstest '$ppsdev' 2>&1 | head -20" check_rc pps-tools/src-ctl-bind 0 sh -c "ppsctl -b '$ppsdev' > /dev/null 2>&1" check_rc pps-tools/src-ctl-unbind 0 sh -c "ppsctl -B '$ppsdev' > /dev/null 2>&1" check_out pps-tools/src-watch-found "found PPS source" sh -c ". \"$BST_LIB\"; bst_bound 10 ppswatch '$ppsdev' 2>&1 | head -20" else bst_skip pps-tools/src-test-found "no PPS character device: /dev/pps* matched nothing (CONFIG_PPS is not set on this kernel)" bst_skip pps-tools/src-test-fetch "no PPS character device: /dev/pps* matched nothing" bst_skip pps-tools/src-ctl-bind "no PPS character device: /dev/pps* matched nothing" bst_skip pps-tools/src-ctl-unbind "no PPS character device: /dev/pps* matched nothing" bst_skip pps-tools/src-watch-found "no PPS character device: /dev/pps* matched nothing" fi if [ -n "$ppsclass" ]; then # With a registered pps class, ppsfind can actually find something, and # its output format is `: name= path=

`. check_out pps-tools/src-find-name "name=" sh -c "ppsfind \"\$(cat '$ppsclass/name' 2>/dev/null)\" 2>/dev/null" check_rc pps-tools/src-find-rc 0 sh -c "ppsfind pps > /dev/null 2>&1" else bst_skip pps-tools/src-find-name "no pps class: /sys/class/pps/pps* matched nothing (CONFIG_PPS is not set on this kernel)" bst_skip pps-tools/src-find-rc "no pps class: /sys/class/pps/pps* matched nothing" fi # ppsldisc's success path needs a real serial tty AND a registered N_PPS line # discipline. Attaching a discipline to this machine's own console would take # the console away from the runner, so the gate is a real serial port that is # NOT the console -- which this kernel has none of (CONFIG_SERIAL_8250 is not # set; USB_SERIAL is built but binds nothing without a device). # The probe requires the node to be OPENABLE, not merely present: a tty this # process cannot open fails at open(2) and would report the control's message # rather than the ioctl's, which is a property of the permissions and not of the # port. (On an ordinary build host /dev/ttyS0 is crw--w---- root:tty and does # exactly that.) ldisc_tty="" for t in /dev/ttyS[0-9]* /dev/ttyUSB[0-9]*; do [ -c "$t" ] && [ -r "$t" ] && [ -w "$t" ] && { ldisc_tty=$t; break; } done if [ -n "$ldisc_tty" ]; then check_out pps-tools/src-ldisc "ioctl(TIOCSETD)" sh -c ". \"$BST_LIB\"; bst_bound 5 ppsldisc '$ldisc_tty' 2>&1" else bst_skip pps-tools/src-ldisc "no openable non-console serial tty: /dev/ttyS* and /dev/ttyUSB* matched nothing this process may open" fi rm -rf "$W.d" "$out" "$err" 2>/dev/null