. "$BST_LIB" # BST-TIMEOUT: 300 # # 77 cases, one blktool exec apiece and no corpus to stage; the two device # arms are individually bounded at 60 s by bst_probe, so the suite's own bound # only has to cover the sum. 300 is well above that and well under the point at # which the bound stops working as a hang detector. # # blktool 4-7.1 on wasm32-hwjs glibc -- Jeff Garzik's low-level block-device # parameter tool, installed as /usr/sbin/blktool. It queries and sets a drive's # cache, DMA, readahead, geometry and read-only flags through ioctl(2), and it # is poky's own recipe (meta/recipes-extended/blktool) rather than meta-oe's -- # built stock, with no meta-brintos bbappend and no patch of ours. # # --------------------------------------------------------------------------- # THIS KERNEL HAS NO BLOCK LAYER, AND blktool CHECKS FOR ONE BEFORE IT DOES # ANYTHING. That second half is what shapes this suite, and it is the one place # blktool differs from every sibling port on this image. # # arch/wasm32/configs/wasm32_defconfig:278 "# CONFIG_BLOCK is not set" # # sits under that file's own heading "What we explicitly DO NOT want", beside # CONFIG_MODULES -- a deliberate, annotated distro choice, not a gap waiting to # be filled (the same fact 89-hdparm, 89-eject and 99-hddtemp record). So there # is no /proc/partitions, no /sys/block, and no block-special node anywhere in # /dev. # # hdparm opens whatever path it is handed and issues the ioctls anyway, which is # what lets 89-hdparm reach the kernel and collect ENOTTY from it. blktool does # NOT: # # if (!S_ISBLK(st.st_mode)) { # fprintf(stderr, MSG_NOT_BLKDEV, fn); # close(fd); # exit(1); # } # # open_blkdev() fstat()s the fd and refuses anything that is not S_ISBLK, before # the command word is even looked up. On this machine no path can satisfy that # test, so EVERY ioctl in blktool -- all 22 command-table entries and the ATA # and SCSI class handlers behind them -- is unreachable, and no substitute node # gets a case past it. That is a statement about this port's evidence and it is # made here rather than left implicit: what is exercised below is the packaging, # the whole of the pre-open surface, and both refusal paths; what is not # exercised is the ioctl layer, and there is no honest way to exercise it here. # # The device-driven cases are therefore gated on a RUNTIME probe of /dev, not on # a constant, so they run the real assertion the day this platform gains a block # layer instead of reporting a stale skip -- and the no-block-layer controls at # the end go red in the same run, so the two say why together. # # --------------------------------------------------------------------------- # WHAT CARRIES THE SUITE IS THE USAGE TABLE, AND IT IS COMPARED BYTE FOR BYTE. # # usage() prints blktool's entire command table: 11 boolean commands with their # two state words, 11 integer commands, 10 void commands, each prefixed by the # device class it requires through a "%-8s" field. That is 35 lines rendered # from three static tables and it is the largest deterministic output blktool # produces on a machine with no disk. # # It is asserted whole rather than sampled. "usage ran", "the output mentions # wcache" and "it printed 35 lines" are each satisfied by a table that dropped a # command, mislabelled a class, or swapped a pair of state words -- and a table # is exactly the kind of thing an ILP32 build gets subtly wrong. The expected # text below was taken from a native x86-64 build of the SAME sources with the # same four poky patches applied, so this is a two-architecture comparison of # one program and not a transcription of what the wasm32 build happened to say. # # --------------------------------------------------------------------------- # TWO BEHAVIOURS BELOW ARE UPSTREAM DEFECTS, ASSERTED AS MEASURED. # # `-t ide` is documented by blktool.8 ("where class is one of 'ide', 'ata', # 'scsi', 'i2o', or 'auto'") and is REFUSED by the binary. main() reads # # if (!strcmp(optarg, "ide")) # dev_class = dc_ata; # if (!strcmp(optarg, "ata")) # dev_class = dc_ata; # else if (!strcmp(optarg, "scsi")) # ... # else # usage(prog_name); # # -- the "ide" arm is a separate `if`, so "ide" sets the class and then falls # into the second chain, matches nothing, and hits usage(). The other four # spellings work. Upstream is dead (poky's own patches say so), the defect is # not ours and does not block this port, so it is recorded and asserted rather # than patched: a case that flips the day someone fixes it is worth more than a # divergence of ours that nobody would find again. # # `blktool version` alone ALSO prints the usage rather than the version, because # the argument-count guard runs first: # # if ((optind + 1) >= argc) # usage(prog_name); # if (!strcmp(argv[optind], "version")) { handle_version(...); exit(0); } # # so the version fast path needs TWO positional words. `blktool # version` does print it, without opening -- which is asserted below # against a path that does not exist and against a character device, and is the # ordering proof that the fast path really does precede open_blkdev(). bst_ok blktool/suite-canary # first line: absorbs brintos/linux#96, which drops it require blktool blktool/present # Invoked by ABSOLUTE PATH throughout: usage() prints argv[0] verbatim, so the # expected text below is only well-defined if the invocation is. BT=/usr/sbin/blktool W=${TMPDIR:-/tmp}/bst-blktool.$$ rm -rf "$W" if mkdir -p "$W" 2>/dev/null; then bst_ok blktool/workdir else bst_notok blktool/workdir "cannot create working directory $W" exit 0 fi export W BT nl=' ' # has : ok iff contains the literal . has() { case "$3" in *"$2"*) bst_ok "$1" ;; *) bst_notok "$1" "want:'$(_bst_1line "$2")' got:'$(_bst_1line "$3")'" ;; esac; } hasnt() { case "$3" in *"$2"*) bst_notok "$1" "present and must not be: $(_bst_1line "$2")" ;; *) bst_ok "$1" ;; esac; } # run : run a command, keeping its stdout, stderr and status as # $W/.out, $W/.err and $W/.rc. Always exits 0 itself, so # bst_probe can bound it while the command's own status stays readable -- every # device path below is expected to FAIL, and bst_probe scores a non-zero child # as a red. cat > "$W/run" <<'EOS' #!/bin/sh tag=$1; shift "$@" >"$W/$tag.out" 2>"$W/$tag.err" printf '%s\n' $? >"$W/$tag.rc" exit 0 EOS chmod +x "$W/run" # --- 1. the packaging --------------------------------------------------------- # sbin_PROGRAMS in Makefile.am, so the single program lands in ${sbindir} and # NOT in /usr/bin -- pinned here because it is the one packaging fact a reader # is most likely to assume wrongly. Asserted against dpkg's own file list as # well as by stat: a binary that reached the image by some other route satisfies # the stat and does not satisfy the claim. check blktool/install-path test -x /usr/sbin/blktool check_eq blktool/which "/usr/sbin/blktool" "$(command -v blktool)" check blktool/not-in-usr-bin test ! -e /usr/bin/blktool pkglist=$(cat /var/lib/dpkg/info/blktool.list 2>/dev/null) if [ -n "$pkglist" ]; then bst_ok blktool/pkg-list hasline() { case "$nl$3$nl" in *"$nl$2$nl"*) bst_ok "$1" ;; *) bst_notok "$1" "not listed: $2" ;; esac; } hasline blktool/pkg-binary /usr/sbin/blktool "$pkglist" # blktool.8 goes to blktool-doc by the default FILES split and this image # installs no -doc packages. Its absence from the runtime list is the # packaging working, not a missing file. hasnt blktool/pkg-man-split "/usr/share/man/man8/blktool.8" "$pkglist" else bst_notok blktool/pkg-list "no /var/lib/dpkg/info/blktool.list -- the packaging cases here cannot run" fi # --- 2. the usage table, byte for byte ---------------------------------------- # With no arguments blktool prints its banner, its usage line and its whole # command table to STDERR and exits 1. Both facts are asserted separately from # the text, because a build that printed the right table on the wrong stream, or # exited 0, would still satisfy a content-only comparison. cat > "$W/usage.want" <<'EOW' blktool version 4 usage: /usr/sbin/blktool [options] DEVICE COMMAND [args...] command list: ATA defect-mgmt { off | on } ATA dev-keep-settings { off | on } any dev-read-ahead { off | on } ATA dma { off | on } ATA keep-settings { off | on } any media { unlock | lock } ATA no-write-err { off | on } ATA pio-data { 16-bit | 32-bit } any readonly { off | on } ATA unmask-irq { off | on } any wcache { off | on } ATA acoustic-mgmt nnn any block-sz nnn ATA bus-state nnn any cd-speed nnn I2O i2o-rcache nnn I2O i2o-wcache nnn ATA multiple-count nnn ATA pio-mode nnn ATA pm-mode nnn ATA queue-depth nnn any read-ahead nnn SCSI bus-id any class any geometry any id any reread-part ATA reset any sector-sz ATA sleep any standby any version EOW ( "$BT" >"$W/usage.stdout" 2>"$W/usage.got" ) usage_rc=$? check_eq blktool/usage-rc "1" "$usage_rc" # Nothing on stdout: usage() writes every line with fprintf(stderr, ...), and a # build that had them on stdout would pass every content case below. if [ -s "$W/usage.stdout" ]; then bst_notok blktool/usage-stderr-only "usage wrote to stdout: $(_bst_1line "$(cat "$W/usage.stdout")")" else bst_ok blktool/usage-stderr-only fi want=$(cat "$W/usage.want"); got=$(cat "$W/usage.got") check_eq blktool/usage-lines "35" "$(wc -l < "$W/usage.got")" # BST-REDIRECT-OK: $W is under $TMPDIR, never the rootfs if [ "$want" = "$got" ]; then bst_ok blktool/usage-identical else # Name the FIRST line that differs rather than dumping 35 into a 100-char # detail field, where the divergence would be truncated away. n=1; firstbad= while [ "$n" -le 35 ]; do a=$(sed -n "${n}p" "$W/usage.want"); b=$(sed -n "${n}p" "$W/usage.got") if [ "$a" != "$b" ]; then firstbad="line $n want:'$a' got:'$b'"; break; fi n=$((n + 1)) done bst_notok blktool/usage-identical "${firstbad:-line counts differ}" fi # A handful of the table's own rows, asserted by name as well, so a red in the # whole-table case comes with the specific claims that survived it. has blktool/table-class-any-wcache "any wcache { off | on }" "$got" has blktool/table-class-ata-dma "ATA dma { off | on }" "$got" has blktool/table-class-scsi-bus-id "SCSI bus-id" "$got" has blktool/table-class-i2o-rcache "I2O i2o-rcache nnn" "$got" has blktool/table-pio-data-states "ATA pio-data { 16-bit | 32-bit }" "$got" has blktool/table-media-states "any media { unlock | lock }" "$got" has blktool/table-readonly "any readonly { off | on }" "$got" # --- 3. the version fast path ------------------------------------------------- # Two positional words are required, and the SECOND may be anything: the guard # is on argc, and the "version" test is on argv[optind]. `blktool version` alone # is therefore the usage, which is the upstream ordering recorded above. check_rc blktool/version-alone-rc 1 "$BT" version has blktool/version-alone-usage "usage: /usr/sbin/blktool" "$("$BT" version 2>&1)" # ...and with a device word in front it prints, WITHOUT opening that word. Both # arms use a path open_blkdev() would have refused -- one that does not exist, # and one that exists and is not a block device -- so a build that opened first # would fail here with a different message instead of printing the banner. check_rc blktool/version-rc 0 "$BT" /dev/brintos-no-such-disk version check_eq blktool/version-text "blktool version 4" "$("$BT" /dev/brintos-no-such-disk version 2>&1)" check_rc blktool/version-chardev-rc 0 "$BT" /dev/null version check_eq blktool/version-chardev-text "blktool version 4" "$("$BT" /dev/null version 2>&1)" # The banner is on STDOUT here, unlike the identical line inside usage() on # stderr. Same string, two streams, decided by which path printed it. "$BT" /dev/null version >"$W/v.out" 2>"$W/v.err" check_eq blktool/version-on-stdout "blktool version 4" "$(cat "$W/v.out")" if [ -s "$W/v.err" ]; then bst_notok blktool/version-clean-stderr "stderr not empty: $(_bst_1line "$(cat "$W/v.err")")" else bst_ok blktool/version-clean-stderr fi # The version the recipe means to ship. AC_INIT(blktool, 4) is what the binary # prints; 4-7.1 is the Debian revision poky's PV carries. A skew between the two # shows up here and nowhere else on this image. check_eq blktool/version-value "4" "$("$BT" /dev/null version 2>&1 | sed -n 's/^blktool version //p')" # --- 4. argument handling, which needs no device ------------------------------ # Everything in this block runs before open_blkdev(), so it is the part of # blktool that is fully exercisable on this machine. check_rc blktool/no-args-rc 1 "$BT" check_rc blktool/one-arg-rc 1 "$BT" /dev/null has blktool/one-arg-usage "command list:" "$("$BT" /dev/null 2>&1)" # -f is accepted and consumed by getopt; it reaches the device path rather than # the usage. has blktool/opt-force "/dev/null is not a block device" "$("$BT" -f /dev/null geometry 2>&1)" # The four -t spellings that work, each carried past option parsing and into the # S_ISBLK refusal -- which is how a suite with no disk proves the option was # accepted at all. for cls in ata scsi i2o auto; do has "blktool/opt-t-$cls" "/dev/null is not a block device" "$("$BT" -t "$cls" /dev/null geometry 2>&1)" done # ...and the two that do not. `bogus` is the intended refusal; `ide` is the # upstream defect documented at the head of this file, asserted as measured. has blktool/opt-t-bogus "command list:" "$("$BT" -t bogus /dev/null geometry 2>&1)" check_rc blktool/opt-t-bogus-rc 1 "$BT" -t bogus /dev/null geometry has blktool/opt-t-ide-refused "command list:" "$("$BT" -t ide /dev/null geometry 2>&1)" check_rc blktool/opt-t-ide-rc 1 "$BT" -t ide /dev/null geometry hasnt blktool/opt-t-ide-no-blkdev-msg "is not a block device" "$("$BT" -t ide /dev/null geometry 2>&1)" # An unknown option is getopt's own refusal, and it prints getopt's message # ahead of blktool's usage. check_rc blktool/opt-unknown-rc 1 "$BT" -Z /dev/null geometry has blktool/opt-unknown-usage "command list:" "$("$BT" -Z /dev/null geometry 2>&1)" # --- 5. the absent device node ------------------------------------------------ # open(2) fails before any fstat, and pdie() reports it through perror(3) with # the path as the prefix -- a DIFFERENT failure mode from the S_ISBLK refusal # below, and reported as such. Bounded, because this is a path that touches the # filesystem layer. "$W/run" enoent "$BT" /dev/brintos-no-such-disk geometry bst_probe blktool/enoent-bounded "$W/enoent.probe" 60 "$W/run" enoent2 "$BT" /dev/brintos-no-such-disk geometry check_eq blktool/enoent-rc "1" "$(cat "$W/enoent.rc")" check_eq blktool/enoent-msg "/dev/brintos-no-such-disk: No such file or directory" "$(cat "$W/enoent.err")" if [ -s "$W/enoent.out" ]; then bst_notok blktool/enoent-clean-stdout "stdout not empty: $(_bst_1line "$(cat "$W/enoent.out")")" else bst_ok blktool/enoent-clean-stdout fi # A path under a directory that does not exist is ENOENT too, and a path whose # parent is a file is ENOTDIR -- both reach the same pdie(), which is what makes # the message the errno's and not a constant. check_eq blktool/enoent-deep-msg "/dev/brintos-no-such-dir/disk0: No such file or directory" \ "$("$BT" /dev/brintos-no-such-dir/disk0 geometry 2>&1)" # --- 6. the S_ISBLK gate ------------------------------------------------------ # The open SUCCEEDS in each of these and the fstat refuses it, so this is the # gate itself rather than a failure to reach it. Three shapes of non-block # object, because the gate is on the mode bits and nothing else: a character # device, a directory, and an ordinary file the suite creates. : > "$W/regular" check_eq blktool/notblk-chardev "/dev/null is not a block device" "$("$BT" /dev/null geometry 2>&1)" check_eq blktool/notblk-dir "$W is not a block device" "$("$BT" "$W" geometry 2>&1)" check_eq blktool/notblk-regular "$W/regular is not a block device" "$("$BT" "$W/regular" geometry 2>&1)" check_rc blktool/notblk-rc 1 "$BT" /dev/null geometry # The gate is ahead of the command lookup: an unknown command word behind a # non-block path produces the S_ISBLK message, not "unknown command". That # ordering is why no malformed-command case in this suite can reach the parser # on this machine, and it is asserted rather than assumed. check_eq blktool/notblk-precedes-cmd "/dev/null is not a block device" \ "$("$BT" /dev/null no-such-command 2>&1)" check_eq blktool/notblk-precedes-badarg "/dev/null is not a block device" \ "$("$BT" /dev/null wcache not-a-boolean 2>&1)" # Bounded, for the same reason as the ENOENT arm: a tool that ever blocked in # open(2) on this filesystem could otherwise take the whole suite down with it. bst_probe blktool/notblk-bounded "$W/notblk.probe" 60 "$W/run" notblk "$BT" /dev/null geometry check_eq blktool/notblk-bounded-rc "1" "$(cat "$W/notblk.rc")" # --- 7. the no-block-layer controls ------------------------------------------- # These are the CONTROL for every skip below. They assert the platform state the # skips rest on, so that a machine which gains a block layer turns them red in # the same run in which the skips start running for real -- rather than leaving # a stale skip that nobody re-reads. blkdev="" for d in /dev/*; do if [ -b "$d" ]; then blkdev="$d"; break; fi done if [ -n "$blkdev" ]; then bst_notok blktool/control-no-blockdev "a block-special node exists ($blkdev): CONFIG_BLOCK is no longer off, and the gated cases below now run for real" else bst_ok blktool/control-no-blockdev fi if [ -e /proc/partitions ]; then bst_notok blktool/control-no-partitions "/proc/partitions exists: this kernel has a block layer" else bst_ok blktool/control-no-partitions fi if [ -d /sys/block ]; then bst_notok blktool/control-no-sys-block "/sys/block exists: this kernel has a block layer" else bst_ok blktool/control-no-sys-block fi # --- 8. the device-driven commands -------------------------------------------- # Gated on the probe above, never on a constant. With a block device present # each of these is the real assertion; without one each is a skip that names the # defconfig line it rests on. BLK_WHY="no block layer on this kernel (arch/wasm32/configs/wasm32_defconfig: '# CONFIG_BLOCK is not set', under 'What we explicitly DO NOT want'): no /proc/partitions, no /sys/block, no block-special node in /dev -- and blktool's open_blkdev() refuses a non-block path at S_ISBLK, so no substitute node reaches this ioctl" for c in class geometry sector-sz id; do if [ -n "$blkdev" ]; then check "blktool/dev-$c" "$BT" "$blkdev" "$c" else bst_skip "blktool/dev-$c" "$BLK_WHY" fi done # The query half of the read/write commands: each is a read ioctl whose value # blktool prints on stdout. for c in readonly block-sz read-ahead wcache dev-read-ahead; do if [ -n "$blkdev" ]; then check "blktool/dev-$c" "$BT" "$blkdev" "$c" else bst_skip "blktool/dev-$c" "$BLK_WHY" fi done # The ATA-class commands, which additionally need the device to BE ATA. Probed # the same way and skipped for the same reason; the class case above is what # would say whether they are applicable once a device exists. for c in dma multiple-count acoustic-mgmt queue-depth; do if [ -n "$blkdev" ]; then check "blktool/dev-$c" "$BT" -t ata "$blkdev" "$c" else bst_skip "blktool/dev-$c" "$BLK_WHY" fi done # reread-part and reset MUTATE the device. They are not run even when one # exists: a suite that repartitioned or reset the machine's own disk would be a # worse failure than the coverage it bought. bst_skip blktool/dev-reread-part "mutating command (BLKRRPART); not run against a live device by this suite" bst_skip blktool/dev-reset "mutating command (HDIO_DRIVE_RESET); not run against a live device by this suite" # --- 9. the man page ---------------------------------------------------------- # blktool.8 ships in blktool-doc and this image installs no -doc packages. Not a # defect, and it flips to the real assertion by itself if that ever changes. if [ -f /usr/share/man/man8/blktool.8 ] || [ -f /usr/share/man/man8/blktool.8.gz ]; then check_out blktool/man-page "BLKTOOL" sh -c "zcat -f /usr/share/man/man8/blktool.8* 2>/dev/null | head -n 40" else bst_skip blktool/man-page "blktool.8 ships in blktool-doc; this image installs no -doc packages" fi # --- 10. the suite's own controls --------------------------------------------- # A comparator with a bug in it passes everything, and the byte-exact case above # looks identical either way. These two prove the pair actually discriminates. check_eq blktool/control-cmp-equal "a" "a" if [ "$(cat "$W/usage.want")" = "$(cat "$W/usage.got")x" ]; then bst_notok blktool/control-cmp-differs "the comparison used above cannot tell two different strings apart" else bst_ok blktool/control-cmp-differs fi rm -rf "$W"