# BST-TIMEOUT: 300 # MEASURED: ==BST-SUITE nvme-cli elapsed=59s cases=76== on a # # host carrying two other builds (2026-08-25). The cost is # # ~35 execs of a 17 MB wasm32 binary; the scoring itself is # # shell string compares against output already on tmpfs. # # 300 is ~5x that, and is the bound the ticket gates and the # # nightly pass anyway. Re-read the ==BST-SUITE== line rather # # than raising this if the suite drifts toward it. . "$BST_LIB" # nvme-cli 2.8 -- the NVMe management command-line tool, 104 built-in # subcommands plus 25 vendor plugins over libnvme 1.8 (79-libnvme.test) and # json-c. The binary is /usr/sbin/nvme: upstream's meson.build installs it with # `install_dir: sbindir`, so a caller resolving it through a PATH without # /usr/sbin finds nothing and reads the port as ABSENT rather than broken. PATH # is extended before the `require` below, and the install path asserted # directly, so that failure cannot pass as coverage. # # --------------------------------------------------------------------------- # WHAT IS REACHABLE HERE, AND WHY MOST OF THIS SUITE DRIVES A SECOND BINARY. # # nvme-cli's real work is turning a raw NVMe structure into text. On this guest # almost none of it is reachable through the shipped binary: EXACTLY ONE # built-in decoder takes its input from a file (`nbft show --nbft-path`, group # N below), and every other one -- id-ctrl, id-ns, smart-log, error-log, # fw-log -- reaches its structure through nvme.c's get_dev() -> open_dev_direct(), # which refuses anything that is not a character or block device and then issues # an ioctl. There is no --raw / --from-file input path in 2.8 for any of them. # A suite over the shipped binary alone could assert `--help`, the version and # a key generator, and would read GREEN over a tool that decodes nothing. # # So the decoder groups drive /usr/libexec/nvme-cli-tests/nvme-fixture, which is # nvme-cli's OWN object files relinked in the recipe against one extra # translation unit that DEFINES ioctl() (files/nvme_fixture_mock.c). On a # --disable-shared link the linker resolves nvme-cli's and libnvme's calls there # and never pulls libc's ioctl.o, so the binary differs from the shipped nvme in # exactly one symbol: the argument parsing, libnvme's command construction and # nvme-cli's rendering below it are the shipped code. It is relinked every build # against that build's own objects, so it cannot go stale against a tool that # moved under it. This is upstream's own test/ioctl/mock.c technique, done the # way a static image with no runtime loader can. # # --------------------------------------------------------------------------- # THE ORACLE, AND WHY A TARGET-ONLY RUN COULD NOT SUPPLY IT. # # Every sha and every field below is pinned from a NATIVE x86-64 build of this # same 2.8 source against libnvme 1.8 and json-c 0.17, relinked with the same # mock, over the same fixture bytes. NVMe structures are DMA layouts the # specification fixes IN BYTES -- 4096 for an identify page, 512 for a SMART # log, 64 for an error-log entry -- and every scalar on them is little-endian by # spec on every host. So the two architectures MUST agree exactly and a # disagreement is a real defect, never an expected difference. # # That is what a target-only run cannot give. Decoding a page this build itself # encoded would prove only that its writer and its reader agree. # # The fixtures are data/nvme-cli-*.bin, generated once by # src/nvme_cli_mkfixture.c THROUGH LIBNVME'S OWN STRUCTS -- so a hand-typed # offset is a compile error rather than a fixture that decodes "correctly" into # the wrong fields on both arms while the comparison still agrees. # # data/nvme-cli-nbft-table.bin is NOT synthetic: it is a real ACPI NBFT table # captured from a Dell PowerEdge R760, taken verbatim from libnvme 1.8's own # test corpus (libnvme/tests/NBFT, LGPL-2.1-or-later), so group N decodes a # saved binary response from real firmware. # # --------------------------------------------------------------------------- # THE EXPECTED RED: GROUP D, ONE CAUSE, LEFT RED ON PURPOSE. # # D03 runs the shipped binary against /dev/nvme0. This guest has no NVMe # controller, so it cannot succeed here; it is reported FAILING rather than # skipped, so it flips green by itself the day the platform gains one -- that is # the regression signal the group is bought for. D01 and D02 report whether the # device node and the sysfs class exist at all, which is what makes the # attribution readable rather than asserted. PATH="$PATH:/usr/sbin:/sbin" export PATH # THE LOCALE IS PINNED, AND THAT IS AN ASSERTION RATHER THAN TIDINESS. # nvme.c calls setlocale(LC_ALL, "") before dispatching, and the -H renderings # print 128-bit capacities through localeconv()->thousands_sep -- so tnvmcap # comes out "1,099,511,627,776" under a grouping locale and "1099511627776" # under C. The guest sets no locale and ships no /usr/lib/locale, so its # setlocale("") resolves to C; the build host that produced the oracle was # running under en_US.UTF-8, and the two -H cases were the only ones that # disagreed. Pinning C on both sides makes the comparison about the DECODER # instead of about whoever's shell ran it, and keeps it true if the image later # gains locales. LC_ALL=C export LC_ALL require nvme nvme-cli/present NF=/usr/libexec/nvme-cli-tests/nvme-fixture W=${TMPDIR:-/tmp}/bst-nvme-cli.$$ mkdir -p "$W/fx" "$W/nbft" # The rootfs is read-only at browser runtime and the mock selects its payload by # a per-command key, so the fixtures are copied to tmpfs under the names the # mock looks for (data/ is one flat directory shared by every suite, hence the # prefix on the shipped names). fx_ok=1 for k in identify-cns00 identify-cns01 identify-cns02 \ getlog-lid01 getlog-lid02 getlog-lid03; do cp "$BST_DATA/nvme-cli-$k.bin" "$W/fx/$k.bin" 2>/dev/null || fx_ok=0 done cp "$BST_DATA/nvme-cli-nbft-table.bin" "$W/nbft/NBFT" 2>/dev/null || fx_ok=0 NVME_CLI_FIXTURE_DIR="$W/fx" export NVME_CLI_FIXTURE_DIR if [ "$fx_ok" = 1 ]; then bst_ok nvme-cli/fixtures-staged else bst_notok nvme-cli/fixtures-staged "could not stage $BST_DATA/nvme-cli-*.bin into $W"; fi # --------------------------------------------------------------------------- # Helpers. No stdin redirect anywhere: every reader is given a path. sha_of() { sha256sum "$1" 2>/dev/null | cut -d' ' -f1; } size_of() { wc -c "$1" 2>/dev/null | awk '{print $1}'; } # want_sha -- the byte-for-byte comparison. A mismatch # carries the size and the head of the output, because a sha alone says nothing # about what differed; the field cases below localise it. # # The head is filtered to printable ASCII first. Half the files scored here are # `-o binary` dumps, so an unfiltered detail puts raw NVMe structure bytes on # the `BST not ok` line -- which is the line the host parser reads, and which a # mutation run showed reaching the report as a binary blob. A red that corrupts # the report is worse than no detail at all. want_sha() { _g=$(sha_of "$3") if [ "$_g" = "$2" ]; then bst_ok "$1" else _h=$(head -c 120 "$3" 2>/dev/null | tr -cd '\11\40-\176') bst_notok "$1" "sha $_g != $2 ($(size_of "$3") bytes) $(_bst_1line "$_h")" fi } # want_line -- an exact whole-line match, so a # decoder that changed a field's width or its units is a named red rather than # a sha that moved. want_line() { if grep -qxF "$3" "$2" 2>/dev/null; then bst_ok "$1" else bst_notok "$1" "no line '$3'; got $(_bst_1line "$(grep -m1 "$(printf '%s' "$3" | awk '{print $1}')" "$2" 2>/dev/null)")" fi } # fx -- run the fixture binary, capturing stdout+stderr. fx() { _n=$1; shift; "$NF" "$@" > "$W/$_n.out" 2>&1; } # --------------------------------------------------------------------------- # A -- THE SHIPPED BINARY: identity and the device-free helpers. check nvme-cli/A01-sbin-path test -x /usr/sbin/nvme check_out nvme-cli/A02-version "nvme version 2.8" nvme version check_out nvme-cli/A03-libnvme "libnvme version 1.8" nvme version check_out nvme-cli/A04-help-smart "smart-log" nvme help check_out nvme-cli/A05-help-nbft "nbft" nvme help # gen-hostnqn draws a random UUID, so only its SHAPE can be pinned -- and the # shape is the deliverable: a malformed NQN is what a broken uuid/format path # produces. The recipe's pkg_postinst_ontarget writes this into # /etc/nvme/hostnqn on first boot. HNQN=$(nvme gen-hostnqn 2>&1) case "$HNQN" in nqn.2014-08.org.nvmexpress:uuid:????????-????-????-????-????????????) bst_ok nvme-cli/A06-gen-hostnqn ;; *) bst_notok nvme-cli/A06-gen-hostnqn "$(_bst_1line "$HNQN")" ;; esac # gen-dhchap-key derives the key from --secret when one is given, so with a # fixed secret the whole path is DETERMINISTIC and can be pinned byte for byte # against the native run rather than merely shape-checked. Four arms, because # they exercise different code: -m 0 is nvme-cli's OWN base64 and crc32 with no # crypto library involved, and -m 1|2|3 add openssl's HMAC-SHA256/384/512 over # the host NQN. A key that came out right on one arm and wrong on another names # which. DK_SECRET=000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f DK_NQN=nqn.2014-08.org.nvmexpress:uuid:00112233-4455-6677-8899-aabbccddeeff check_eq nvme-cli/A07-gen-dhchap-key-plain \ "DHHC-1:00:AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh+KfiaR:" \ "$(nvme gen-dhchap-key -s "$DK_SECRET" -n "$DK_NQN" -m 0 2>&1)" # --------------------------------------------------------------------------- # THE HMAC ARMS. These used to pin a REFUSAL -- "HMAC transformation not # supported; recompile with OpenSSL support." -- because meta-oe's # libnvme_1.8.bb passes -Dopenssl=disabled and libnvme then compiles the stub # arm of src/nvme/linux.c. BRINTOS-1517 enabled it in libnvme's bbappend, so # all three now answer a key, and the key is what is asserted. # # WHERE THE ORACLE COMES FROM, AND WHY IT IS NOT A RECORDING OF OUR OWN RUN. # nvme_gen_dhchap_key() is HMAC(key = the raw secret, message = hostnqn # concatenated with the literal "NVMe-over-Fabrics"), and nvme-cli then appends # the crc32 of THAT derived key as four little-endian bytes and base64-encodes # the 36/52/68-byte result behind a `DHHC-1:0:` prefix. Every step is a # published standard, so the expected values below were computed from the # specification with an independent implementation rather than captured from a # target run -- a recording would only prove the library agrees with itself. # The -m 1 value also matches the native x86-64 control quoted on BRINTOS-1517, # which is the second, independent confirmation. # # The three lengths are a free structural check on top: nvme-cli's own # check-dhchap-key REFUSES anything but 59, 83 and 103 characters for hmac # 1, 2 and 3, so a key of the wrong shape cannot reach A12-A14 green. # # EACH ARM NEEDS ITS OWN SECRET, AND THAT IS NOT A DETAIL OF THE FIXTURE. # The secret is the HMAC *key*, and nvme-cli requires it to be exactly the # digest length: gen_dhchap_key() defaults --key-length to 32/48/64 from -m and # then refuses a --secret that decodes to anything else. So -m 2 and -m 3 # cannot reuse $DK_SECRET, and a suite that tried would be measuring nvme-cli's # argument validation rather than the derivation (which is exactly what the # first draft of these three cases did, 2026-08-26 -- both went red with # "Invalid key length (32 bytes)" and neither had reached a crypto call). # A08e below pins that refusal deliberately, so the distinction stays visible. DK_SECRET48=${DK_SECRET}202122232425262728292a2b2c2d2e2f DK_SECRET64=${DK_SECRET48}303132333435363738393a3b3c3d3e3f check_eq nvme-cli/A08-gen-dhchap-key-hmac-sha256 \ "DHHC-1:01:KNFPU24JZOISJCWaOhLvQjURtJOUTGJi+JPAg8X3usoaWesB:" \ "$(nvme gen-dhchap-key -s "$DK_SECRET" -n "$DK_NQN" -m 1 2>&1)" check_eq nvme-cli/A08b-gen-dhchap-key-hmac-sha384 \ "DHHC-1:02:PyXb9oo+OVOoQoGKwDUr8IG3crvNlwr3oUvcimmJjT5xPxVHs+heFy4LCnurXUAt5Zyevg==:" \ "$(nvme gen-dhchap-key -s "$DK_SECRET48" -n "$DK_NQN" -m 2 2>&1)" check_eq nvme-cli/A08c-gen-dhchap-key-hmac-sha512 \ "DHHC-1:03:YrjiOXCnfQ8drPZ6IKWVTrawuufFb6+pX6VhgOzbhw3cRwX5/M3BzDrBTuLTJvLlQNcDSUPpyoXMY22g9F4+cRNhJeA=:" \ "$(nvme gen-dhchap-key -s "$DK_SECRET64" -n "$DK_NQN" -m 3 2>&1)" # THE EXIT STATUS IS ITS OWN CASE, because a printf of the right key beside a # non-zero exit is a real defect that a stdout compare cannot see -- and it is # exactly the shape the refusal had (it printed to stderr and exited non-zero). nvme gen-dhchap-key -s "$DK_SECRET" -n "$DK_NQN" -m 1 > "$W/dkhmac.out" 2>&1 DK_HMAC_RC=$? if [ "$DK_HMAC_RC" -eq 0 ]; then bst_ok nvme-cli/A08d-gen-dhchap-key-hmac-rc else bst_notok nvme-cli/A08d-gen-dhchap-key-hmac-rc "exited $DK_HMAC_RC: $(_bst_1line "$(cat "$W/dkhmac.out")")" fi # A NEGATIVE CONTROL FOR THE THREE ABOVE, and the reason it is worth a case: # BEFORE BRINTOS-1517 the -m 1|2|3 arms refused too, and a suite that only # asserts "a key came out" cannot tell a library that lost OpenSSL again from # one that is merely being handed the wrong argument. This pins the OTHER # refusal -- a 32-byte secret offered to SHA-384 -- so the two are named # differently. If OpenSSL is ever lost, A08b goes red with the OpenSSL message # while A08e stays green on this one; if nvme-cli's validation changes, only # A08e moves. # Redirected first and compared through `cat`, not run under check_out # directly: check_out asserts a ZERO exit as well as the output, and a refusal # exits 1 by design -- so the direct form fails with want and got identical, # which is a confusing way to say "the message was right". Same shape as A11 # next door, and the same reason the refusal this case replaces was written # that way. nvme gen-dhchap-key -s "$DK_SECRET" -n "$DK_NQN" -m 2 > "$W/dkshort.out" 2>&1 DK_SHORT_RC=$? check_out nvme-cli/A08e-gen-dhchap-key-short-secret \ "Invalid key length (32 bytes)" cat "$W/dkshort.out" if [ "$DK_SHORT_RC" -ne 0 ]; then bst_ok nvme-cli/A08f-gen-dhchap-key-short-secret-rc else bst_notok nvme-cli/A08f-gen-dhchap-key-short-secret-rc "refused but exited 0" fi DK_GOOD="DHHC-1:00:AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh+KfiaR:" check_out nvme-cli/A09-check-dhchap-key \ "Key is valid (HMAC 0, length 32, CRC 91267e8a)" nvme check-dhchap-key -k "$DK_GOOD" # One payload character changed must be REFUSED, and refused BY THE CRC -- the # validator could return 0 unconditionally and A09 alone would still be green. DK_BAD="DHHC-1:00:BAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh+KfiaR:" check_rc nvme-cli/A10-check-dhchap-key-bad-rc 1 nvme check-dhchap-key -k "$DK_BAD" nvme check-dhchap-key -k "$DK_BAD" > "$W/dkbad.out" 2>&1 check_out nvme-cli/A11-check-dhchap-key-bad-crc \ "CRC mismatch (key 91267e8a, crc e0ac64a0)" cat "$W/dkbad.out" # ROUND TRIP: each key A08* just generated is handed straight back to the # validator. This is the case that would notice a derivation which produced a # well-formed key of the right length for the WRONG digest -- the reported HMAC # id and CRC are read out of the key itself, so all three fields have to agree # with the arm that made it. It also pins the two remaining key lengths (48 and # 64), which the -m 0 cases above cannot reach. check_out nvme-cli/A12-check-dhchap-key-sha256 \ "Key is valid (HMAC 1, length 32, CRC 01eb591a)" \ nvme check-dhchap-key -k "DHHC-1:01:KNFPU24JZOISJCWaOhLvQjURtJOUTGJi+JPAg8X3usoaWesB:" check_out nvme-cli/A13-check-dhchap-key-sha384 \ "Key is valid (HMAC 2, length 48, CRC be9e9ce5)" \ nvme check-dhchap-key -k "DHHC-1:02:PyXb9oo+OVOoQoGKwDUr8IG3crvNlwr3oUvcimmJjT5xPxVHs+heFy4LCnurXUAt5Zyevg==:" check_out nvme-cli/A14-check-dhchap-key-sha512 \ "Key is valid (HMAC 3, length 64, CRC e0256113)" \ nvme check-dhchap-key -k "DHHC-1:03:YrjiOXCnfQ8drPZ6IKWVTrawuufFb6+pX6VhgOzbhw3cRwX5/M3BzDrBTuLTJvLlQNcDSUPpyoXMY22g9F4+cRNhJeA=:" # --------------------------------------------------------------------------- # N -- THE ONE FILE-DRIVEN DECODER THE SHIPPED BINARY HAS. # # The rendered table names the file it came from, so the staged path is stripped # before comparison; everything else is byte-for-byte against the native run. nvme nbft show --nbft-path="$W/nbft" > "$W/nbft-normal.raw" 2>&1 sed "s|$W/nbft/||g" "$W/nbft-normal.raw" > "$W/nbft-normal.out" want_sha nvme-cli/N01-nbft-normal-bytes \ d645444f95636b9d8dd2ed929d0d5d16dae89a2d94bde833b9dcdb2bc03fc3c8 "$W/nbft-normal.out" want_line nvme-cli/N02-nbft-nqn "$W/nbft-normal.out" \ "1 |nqn.1988-11.com.dell:powerstore:00:2a64abf1c5b81F6C4549|tcp |100.71.103.48|4420 |1 " want_line nvme-cli/N03-nbft-hfi "$W/nbft-normal.out" \ "1 |tcp |0:40:0.0 |b0:26:28:e8:7c:0e|yes |100.71.245.232|24 |100.71.245.254|100.64.0.5" want_line nvme-cli/N04-nbft-discovery "$W/nbft-normal.out" \ "1 |nvme+tcp://100.71.103.50:8009/|nqn.2014-08.org.nvmexpress.discovery" nvme nbft show --nbft-path="$W/nbft" -o json > "$W/nbft-json.raw" 2>&1 sed "s|$W/nbft/||g" "$W/nbft-json.raw" > "$W/nbft-json.out" want_sha nvme-cli/N05-nbft-json-bytes \ 9d601d0359862a8c4fbd1942e4d4c5768f6fb7b8215d008a05a2b2d703626fdb "$W/nbft-json.out" # --------------------------------------------------------------------------- # C -- COMMAND CONSTRUCTION, captured before it would have reached a device. # # The mock records every field libnvme composed. These lines are the ioctl # transport nvme-cli depends on, scored against the same lines from the native # build -- and C02 is the one that pins a COUPLING rather than a constant: # error-log first issues identify (CNS 01h) to read elpe, then asks for # elpe+1 entries, so cdw10 carries NUMDL 0x03ff (1024 dwords = 4096 bytes) # beside LID 01h. A decoder that ignored the identify page would ask for a # different length and this line would move. NVME_CLI_MOCK_TRACE="$W/tr-idctrl.txt" "$NF" id-ctrl /dev/null > /dev/null 2>&1 want_sha nvme-cli/C01-trace-id-ctrl \ 1b6a68dee714fa93ec7a7e821baa692022c7c285fb8a9f242aba04837e02dc79 "$W/tr-idctrl.txt" NVME_CLI_MOCK_TRACE="$W/tr-error.txt" "$NF" error-log /dev/null > /dev/null 2>&1 want_sha nvme-cli/C02-trace-error-log \ 3536c340c187ae709af47951a0a4ac8e20228def150e872df2f84695365a2d71 "$W/tr-error.txt" want_line nvme-cli/C03-trace-error-numdl "$W/tr-error.txt" \ "admin op=0x02 flags=0x00 nsid=0xffffffff data_len=4096 metadata_len=0 timeout=0 cdw10=0x03ff0001 cdw11=0x00000000 cdw12=0x00000000 cdw13=0x00000000 cdw14=0x00000000 cdw15=0x00000000 key=getlog-lid01" NVME_CLI_MOCK_TRACE="$W/tr-smart.txt" "$NF" smart-log /dev/null > /dev/null 2>&1 want_sha nvme-cli/C04-trace-smart-log \ 1a3794621d18396312a21fde1cf8b43379cc538d24ef526629117a9cc533236b "$W/tr-smart.txt" NVME_CLI_MOCK_TRACE="$W/tr-idns.txt" "$NF" id-ns /dev/null -n 1 > /dev/null 2>&1 want_sha nvme-cli/C05-trace-id-ns \ ec8f9415fa6767496da36f1178e178b560db1465b38cb09febadd387b6418ec0 "$W/tr-idns.txt" # The status tables: a controller answering 0x2002 must decode to the # specification's own wording, not to a number. NVME_CLI_MOCK_STATUS=0x2002 "$NF" id-ctrl /dev/null > "$W/status.out" 2>&1 check_out nvme-cli/C06-status-string \ "NVMe status: Invalid Field in Command" cat "$W/status.out" # --------------------------------------------------------------------------- # I -- IDENTIFY CONTROLLER (CNS 01h), all four renderings. fx idctrl-normal id-ctrl /dev/null -o normal want_sha nvme-cli/I01-id-ctrl-normal-bytes \ 36afc6a92d7835c349e2019d74081722edd29cab461616de22cbced40b63c5d0 "$W/idctrl-normal.out" want_line nvme-cli/I02-id-ctrl-sn "$W/idctrl-normal.out" "sn : BRINTOS1426FIXTURE " want_line nvme-cli/I03-id-ctrl-mn "$W/idctrl-normal.out" "mn : brintOS wasm32 fixture controller " want_line nvme-cli/I04-id-ctrl-ieee "$W/idctrl-normal.out" "ieee : 002538" want_line nvme-cli/I05-id-ctrl-ver "$W/idctrl-normal.out" "ver : 0x10400" want_line nvme-cli/I06-id-ctrl-fguid "$W/idctrl-normal.out" "fguid : 10111213-1415-1617-1819-1a1b1c1d1e1f" # A 128-bit field rendered as a decimal: the one on this page most likely to # come out wrong on a 32-bit target. want_line nvme-cli/I07-id-ctrl-tnvmcap "$W/idctrl-normal.out" "tnvmcap : 1099511627776" want_line nvme-cli/I08-id-ctrl-subnqn "$W/idctrl-normal.out" "subnqn : nqn.2026-08.io.brintos:fixture:nvme-cli-1426" fx idctrl-json id-ctrl /dev/null -o json want_sha nvme-cli/I09-id-ctrl-json-bytes \ 8fd7942a147d534bf1ac7d950d76f72d7d3f2df4ee962fd61c0396ebc77050cc "$W/idctrl-json.out" want_line nvme-cli/I10-id-ctrl-json-tnvmcap "$W/idctrl-json.out" ' "tnvmcap":1099511627776,' want_line nvme-cli/I11-id-ctrl-json-sn "$W/idctrl-json.out" ' "sn":"BRINTOS1426FIXTURE ",' # -H expands each bit field against the specification's wording -- 8335 bytes of # it, and the group most sensitive to a shift done in the wrong width. fx idctrl-human id-ctrl /dev/null -H want_sha nvme-cli/I12-id-ctrl-human-bytes \ 8949ad1f6f21cd6151aa9c72411a3e7adb75f96890cc335d659141850acc5d5e "$W/idctrl-human.out" want_line nvme-cli/I13-id-ctrl-human-oacs "$W/idctrl-human.out" " [4:4] : 0x1 Device Self-test Supported" # -o binary must hand back the controller's bytes unchanged. This is the # strongest single case here: it is the fixture's own sha, so a transport that # truncated, padded or byte-swapped the transfer cannot pass it. fx idctrl-binary id-ctrl /dev/null -o binary want_sha nvme-cli/I14-id-ctrl-binary-roundtrip \ c6dda58febf65583a88ece1fa3945a2b1c59c26de05b1b624e476936c68bf16e "$W/idctrl-binary.out" # --------------------------------------------------------------------------- # S -- IDENTIFY NAMESPACE (CNS 00h) and the active-namespace list (CNS 02h). fx idns-normal id-ns /dev/null -n 1 -o normal want_sha nvme-cli/S01-id-ns-normal-bytes \ cad3ad87a353ee29cdad94f434a9b551408fc72af0ab3d417047443909e2851c "$W/idns-normal.out" want_line nvme-cli/S02-id-ns-nsze "$W/idns-normal.out" "nsze : 0x200000" want_line nvme-cli/S03-id-ns-nvmcap "$W/idns-normal.out" "nvmcap : 1099511627776" want_line nvme-cli/S04-id-ns-nguid "$W/idns-normal.out" "nguid : 202122232425262728292a2b2c2d2e2f" # The LBA-format table: two formats, and the in-use marker comes from flbas # rather than from the row's own bytes. want_line nvme-cli/S05-id-ns-lbaf0 "$W/idns-normal.out" "lbaf 0 : ms:0 lbads:9 rp:0x2 (in use)" want_line nvme-cli/S06-id-ns-lbaf1 "$W/idns-normal.out" "lbaf 1 : ms:0 lbads:12 rp:0 " fx idns-json id-ns /dev/null -n 1 -o json want_sha nvme-cli/S07-id-ns-json-bytes \ b678a1681eaa67a18739b077bca8b4702ce8fba3b9319f3032c965b4cb500406 "$W/idns-json.out" fx idns-human id-ns /dev/null -n 1 -H want_sha nvme-cli/S08-id-ns-human-bytes \ b57ead0c92a0876dba216ea703e7d586808f0aa5f5e168cdea78911c28d04115 "$W/idns-human.out" fx idns-binary id-ns /dev/null -n 1 -o binary want_sha nvme-cli/S09-id-ns-binary-roundtrip \ 14fba2b75d6bacfd211491e54fdafef0b1bf9bd1bc1dee4743d2484eb40f36dc "$W/idns-binary.out" fx nslist-normal list-ns /dev/null -o normal want_sha nvme-cli/S10-list-ns-normal-bytes \ 0774bb53ff0269b0f8b34ff25590013d2f86e4d2f5d042f6c2607e36b58cf58a "$W/nslist-normal.out" want_line nvme-cli/S11-list-ns-last "$W/nslist-normal.out" "[ 3]:0x4" fx nslist-json list-ns /dev/null -o json want_sha nvme-cli/S12-list-ns-json-bytes \ 7c4eae7e1375c98f5a103f2f0d26aad03ad324ff108566a1738aad21d8e0413d "$W/nslist-json.out" # --------------------------------------------------------------------------- # L -- THE LOG PAGES: SMART (LID 02h), error (01h), firmware slot (03h). fx smart-normal smart-log /dev/null -o normal want_sha nvme-cli/L01-smart-normal-bytes \ f23cda41077e558485293c8b8bc4ff4e54fa7f5ec815f82b17f4ea21e5fe5104 "$W/smart-normal.out" # A Kelvin field rendered in both units -- an arithmetic conversion, not a copy. want_line nvme-cli/L02-smart-temperature "$W/smart-normal.out" \ "temperature : 47 °C (320 K)" # 128-bit counters, one of them scaled to a human unit through floating point. want_line nvme-cli/L03-smart-units-read "$W/smart-normal.out" \ "Data Units Read : 12345678 (6.32 TB)" want_line nvme-cli/L04-smart-units-written "$W/smart-normal.out" \ "Data Units Written : 87654321 (44.88 TB)" want_line nvme-cli/L05-smart-poh "$W/smart-normal.out" \ "power_on_hours : 9001" want_line nvme-cli/L06-smart-sensor2 "$W/smart-normal.out" \ "Temperature Sensor 2 : 49 °C (322 K)" fx smart-json smart-log /dev/null -o json want_sha nvme-cli/L07-smart-json-bytes \ eaa778a28bd2a55fba54bab39e40dd675c03367d0b03ced792175f3b4e1ff292 "$W/smart-json.out" want_line nvme-cli/L08-smart-json-units-read "$W/smart-json.out" ' "data_units_read":12345678,' fx smart-binary smart-log /dev/null -o binary want_sha nvme-cli/L09-smart-binary-roundtrip \ c5f311a2db33bacc8efe8bad366c9ee046bc5d59f24d125213a267c1656364b4 "$W/smart-binary.out" # 64 entries, three of them carrying a real error and the rest zero -- the case # that separates a decoder honouring elpe from one that stops at the first zero. fx error-normal error-log /dev/null -o normal want_sha nvme-cli/L10-error-normal-bytes \ 9437a3226bb347d6362824eb685334ce539a0e67d06847bfe976150c8b27da74 "$W/error-normal.out" # The header states the count it asked for and the last entry proves it decoded # that many: elpe=63 on the identify page (C02/C03) becomes 64 entries here. want_line nvme-cli/L11-error-entry-header "$W/error-normal.out" \ "Error Log Entries for device:null entries:64" check_out nvme-cli/L11b-error-last-entry "Entry[63]" cat "$W/error-normal.out" fx error-json error-log /dev/null -o json want_sha nvme-cli/L12-error-json-bytes \ 3e11d5ad7e7d5de6ecb6cbb327bed8b960eefbeac28cf7a41c50df6a886e0620 "$W/error-json.out" fx error-binary error-log /dev/null -o binary want_sha nvme-cli/L13-error-binary-roundtrip \ 831952be0b2936ae8e52f82b4a0abb271cac55af5806243a46a608425d10a05f "$W/error-binary.out" # The firmware-slot page is where an endianness mistake shows FIRST: each slot # revision is an 8-byte ASCII field that nvme-cli renders as a 64-bit # little-endian word AND as its text, side by side. The two disagree the moment # the load width or the byte order is wrong. fx fw-normal fw-log /dev/null -o normal want_sha nvme-cli/L14-fw-normal-bytes \ 89993b95e6ef2bed70af6c5d27d8abd8455cccad3b50956351f19a6d19de3a33 "$W/fw-normal.out" want_line nvme-cli/L15-fw-afi "$W/fw-normal.out" "afi : 0x12" want_line nvme-cli/L16-fw-frs1 "$W/fw-normal.out" "frs1 : 0x78662d302e332e31 (1.3.0-fx)" want_line nvme-cli/L17-fw-frs2 "$W/fw-normal.out" "frs2 : 0x78662d302e342e31 (1.4.0-fx)" fx fw-json fw-log /dev/null -o json want_sha nvme-cli/L18-fw-json-bytes \ bee6c849a5f59021cc5606dd0eaafee9230655c89e66d405b06886cc4a3fac0c "$W/fw-json.out" fx fw-binary fw-log /dev/null -o binary want_sha nvme-cli/L19-fw-binary-roundtrip \ a84854551016859c53594f0e3e67d5f249a29459a1928f968e2c52e1661531cd "$W/fw-binary.out" # --------------------------------------------------------------------------- # D -- THE DEVICE PATH. D01/D02 are the prerequisites, D03 the expected red. if [ -e /dev/nvme0 ]; then bst_notok nvme-cli/D01-dev-nvme0-absent "/dev/nvme0 exists -- D03 should now pass and this case is the stale one" else bst_ok nvme-cli/D01-dev-nvme0-absent fi if [ -d /sys/class/nvme ]; then bst_notok nvme-cli/D02-sysfs-nvme-absent "/sys/class/nvme exists -- the guest has an NVMe class after all" else bst_ok nvme-cli/D02-sysfs-nvme-absent fi # EXPECTED RED, attributed: this guest has no NVMe controller, so the shipped # binary cannot reach a real one. Reported failing rather than skipped so it # flips green by itself the day the platform gains a device. Held by # D01/D02 above -- there is no brintos.io defect here to fix, only hardware # this runtime does not present. nvme id-ctrl /dev/nvme0 > "$W/dev.out" 2>&1 if [ $? -eq 0 ]; then bst_ok nvme-cli/D03-id-ctrl-real-device else bst_notok nvme-cli/D03-id-ctrl-real-device \ "EXPECTED RED held by D01/D02 (no NVMe controller in this guest): $(_bst_1line "$(cat "$W/dev.out")")" fi # `list` walks sysfs rather than opening a device, and on a host with NO NVMe # class at all it REFUSES rather than printing an empty inventory. That is # upstream behaviour, not a brintOS gap: a native x86-64 build of this same 2.8 # source, run with /sys/class masked by a tmpfs in a user namespace, prints the # identical line and exits 1. So the refusal is pinned -- in both renderings, # because the json arm wraps it as an error object and is the one that would # silently emit an empty document if it stopped agreeing. nvme list > "$W/list.out" 2>&1 LIST_RC=$? check_out nvme-cli/D04-list-no-topology \ "Failed to scan topology: No such file or directory" cat "$W/list.out" check_eq nvme-cli/D05-list-rc 1 "$LIST_RC" nvme list -o json > "$W/list-json.out" 2>&1 check_out nvme-cli/D06-list-json-error \ '"error":"Failed to scan topology: No such file or directory"' cat "$W/list-json.out" rm -rf "$W"