. "$BST_LIB" # BST-TIMEOUT: 300 # ~45 execs of one 67 718 B wasm module and no corpus to # stage. Only two invocations can wait at all -- the two # `-a' arms, whose usleep(1000000) is one second each -- # and every device arm is individually bounded at 60 s by # bst_probe, so the suite's own bound only has to cover the # sum. MEASURED, batch-1872 round 4, 2026-09-01: # `==BST-SUITE sdmon elapsed=27s bound=300s cases=84=='. # 300 is kept rather than trimmed to 27: the arms that can # actually run long are the ones this machine cannot # reach, so a machine that GAINS a card is the case the # bound has to survive, and it is the one the 27 s does not # describe. The bound is now checked, which is what the # note here used to be asking for. # sdmon 0.8.1+git (meta-openembedded/meta-oe recipes-support/sdmon at SRCREV # ef1aec45, built STOCK -- no meta-brintos bbappend and no patch of ours) -- # Ognian Tschakalov's SD-card health reader. ONE binary, /usr/bin/sdmon, out of # one 616-line sdmon.c: it opens a device node, issues SD CMD56 (GEN_CMD) # through the MMC_IOC_CMD ioctl, and decodes the vendor wear and lifetime # registers it gets back. # # --------------------------------------------------------------------------- # THERE IS NO SD CARD BEHIND THIS MACHINE, AND THAT IS WHAT THIS SUITE MEASURES. # The port ticket says so in as many words: what it establishes is that the tool # opens the device node, fails to find one, and says so. So the ioctl layer -- # CMD56_data_in, CMD56_write, and the Longsys, SanDisk, Adata and Micron # decoders behind them -- is unreachable here, and no substitute node reaches a # decoder either: a node that opens gets its ioctl refused by the char-device # path instead. That is a statement about this port's evidence and it is made # here rather than left implicit. # # THREE THINGS THE PORT TICKET SAYS ABOUT THIS TOOL ARE NOT TRUE OF THE BINARY, # and the suite is written against the binary. They are recorded because a later # reader will otherwise take the ticket's word for them: # # * There is no usage SCREEN. sdmon has no help output and no option table; # what it prints on a misuse is one JSON field, `"usage":"wrong argument # count, sdmon [-a]"`, inside the same JSON object every other run # produces. # * There is no `--json' selector, because the output is ALWAYS JSON -- the # object is opened by main()'s first two printf calls, before argc is even # looked at, so every path below emits `{`, a version line and a date line # whatever else it does. # * There is no `--all'. The only option is `-a', it must be argv[2] exactly, # and it does not change the form of the output at all: it sets addTime, # whose whole effect is a usleep(1000000) between the CMD56 write and the # SMART read. The `"addTime"` field is the only visible difference. # # --------------------------------------------------------------------------- # WHAT CARRIES THE SUITE IS THE WHOLE TRANSCRIPT, COMPARED BYTE FOR BYTE. # # Every arm's complete stdout is asserted against a native x86-64 build of the # same sources with the same -DVERSION, minus the one line that cannot be # constant (the timestamp, whose SHAPE is asserted separately). "it printed an # error", "the output mentions the device" and "it exited 1" are each satisfied # by a build that dropped a field, printed addTime before the device, or emitted # a decoder line it had no data for -- and a hand-rolled JSON emitter driven by # %d over a byte array is exactly the kind of thing an ILP32 build gets subtly # wrong. So this is a two-architecture comparison of one program rather than a # transcript of what wasm32 happened to say. # # THE ONE THING IT CANNOT SETTLE FROM THE SOURCE is the errno the MMC_IOC_CMD # ioctl returns when the fd is not an MMC device. On x86-64 Linux it is ENOTTY, # `Inappropriate ioctl for device', which is the generic char-device answer and # has nothing to do with MMC. MEASURED on wasm32, batch-1872 round 4: this # kernel answers the same, through /dev/null and through an ordinary file, so # sdmon/notsd-errno-enotty is green. It is still asserted in exactly one case, # marked below, so that a LATER change of answer costs one attributable red and # nothing else: every other ioctl-arm case is written against the SHAPE of the # line, or against the two arms AGREEING with each other, both of which hold # whatever the errno turns out to be. # # --------------------------------------------------------------------------- # ONE UPSTREAM WEAKNESS IS ASSERTED AS MEASURED rather than patched. The open(2) # failure path discards errno: # # fd = open(device, O_RDWR); # if (fd < 0) { printf("\"error\":\"device open failed\"\n}\n"); exit(1); } # # so ENOENT (no such node), EISDIR (a directory) and EACCES are one indistinct # message. Upstream is a single-author hobby tool and this does not block the # port, so it is recorded and asserted -- sdmon/open-fail-errno-not-reported # compares two arms with different errnos and requires them to be identical -- # rather than diverged from. A case that flips the day someone fixes it is worth # more than a patch of ours nobody would find again. bst_ok sdmon/suite-canary # first line: absorbs brintos/linux#96, which drops it require sdmon sdmon/present # Invoked by ABSOLUTE PATH throughout. sdmon never prints argv[0], so this is # not about the expected text -- it is so that a second sdmon anywhere on $PATH # could not answer for the packaged one. SD=/usr/bin/sdmon W=${TMPDIR:-/tmp}/bst-sdmon.$$ rm -rf "$W" if mkdir -p "$W" 2>/dev/null; then bst_ok sdmon/workdir else bst_notok sdmon/workdir "cannot create working directory $W" exit 0 fi export W SD nl=' ' 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; } # starts : ok iff begins with . This is what # every errno-bearing line is asserted with, so the field name, its punctuation # and its position are pinned while the strerror text behind them is not. starts() { case "$3" in "$2"*) bst_ok "$1" ;; *) bst_notok "$1" "want prefix:'$(_bst_1line "$2")' got:'$(_bst_1line "$3")'" ;; esac; } line() { sed -n "${1}p" "$2"; } # nodate : the transcript with its line 3 (the timestamp) removed, which # is what every whole-output comparison below is made against. nodate() { sed '3d' "$1"; } # 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 # invocation of sdmon in this suite exits 1, 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 --------------------------------------------------------- # The recipe's do_install is hand-written -- `install -Dm 0755 ${S}/src/sdmon # ${D}${bindir}/sdmon` -- so /usr/bin, not /usr/sbin, and one file with nothing # else in the package. 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 sdmon/install-path test -x /usr/bin/sdmon check_eq sdmon/which "/usr/bin/sdmon" "$(command -v sdmon)" check sdmon/not-in-usr-sbin test ! -e /usr/sbin/sdmon pkglist=$(cat /var/lib/dpkg/info/sdmon.list 2>/dev/null) if [ -n "$pkglist" ]; then bst_ok sdmon/pkg-list hasline() { case "$nl$3$nl" in *"$nl$2$nl"*) bst_ok "$1" ;; *) bst_notok "$1" "not listed: $2" ;; esac; } hasline sdmon/pkg-binary /usr/bin/sdmon "$pkglist" # The recipe installs the binary and nothing else: no man page, no README, # no data. A file list that grew a second entry is a packaging change # nobody made on purpose. check_eq sdmon/pkg-one-file "1" "$(printf '%s\n' "$pkglist" | grep -c '^/usr/bin/')" hasnt sdmon/pkg-no-man "/usr/share/man" "$pkglist" else bst_notok sdmon/pkg-list "no /var/lib/dpkg/info/sdmon.list -- the packaging cases here cannot run" fi # --- 2. the JSON envelope, and the argument-count refusal --------------------- # With no arguments sdmon still opens the object and prints its version and # date, because those two printf calls precede the argc test. Then the usage # field, then the close. Five lines, all on STDOUT -- sdmon has no fprintf to # stderr anywhere, and a build that put a diagnostic there would still satisfy a # content-only comparison. "$W/run" noargs "$SD" check_eq sdmon/noargs-rc "1" "$(cat "$W/noargs.rc")" if [ -s "$W/noargs.err" ]; then bst_notok sdmon/noargs-clean-stderr "stderr not empty: $(_bst_1line "$(cat "$W/noargs.err")")" else bst_ok sdmon/noargs-clean-stderr fi check_eq sdmon/noargs-lines "5" "$(wc -l < "$W/noargs.out")" # BST-REDIRECT-OK: $W is under $TMPDIR, never the rootfs check_eq sdmon/noargs-open-brace "{" "$(line 1 "$W/noargs.out")" check_eq sdmon/noargs-close-brace "}" "$(line 5 "$W/noargs.out")" # The version the recipe means to ship, compiled in through # -DVERSION="\"${PV}\"". A skew between PV and what the binary prints shows up # here and nowhere else on this image. check_eq sdmon/noargs-version-line '"version": "0.8.1+git",' "$(line 2 "$W/noargs.out")" # The timestamp's SHAPE, since its value cannot be constant. localtime(3) on a # guest with no RTC is still required to render four digits of year and a # two-digit everything else, and the trailing `.000Z' is a literal in the format # string -- a Z suffix on a LOCAL time, which is upstream's own confusion and # not something this port introduces. case "$(line 3 "$W/noargs.out")" in '"date": "'[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9]'.000Z",') bst_ok sdmon/noargs-date-shape ;; *) bst_notok sdmon/noargs-date-shape "got:'$(_bst_1line "$(line 3 "$W/noargs.out")")'" ;; esac check_eq sdmon/noargs-usage-line '"usage":"wrong argument count, sdmon [-a]"' "$(line 4 "$W/noargs.out")" # The device and addTime fields are printed AFTER the argc test, so neither can # appear here. That ordering is what the whole-transcript case below rests on. hasnt sdmon/noargs-no-device-key '"device"' "$(cat "$W/noargs.out")" hasnt sdmon/noargs-no-addtime-key '"addTime"' "$(cat "$W/noargs.out")" # The whole transcript, byte for byte against the native build. cat > "$W/noargs.want" <<'EOW' { "version": "0.8.1+git", "usage":"wrong argument count, sdmon [-a]" } EOW check_eq sdmon/noargs-transcript "$(cat "$W/noargs.want")" "$(nodate "$W/noargs.out")" # argc > 3 is the same refusal from the other side of the range. check_rc sdmon/argc4-rc 1 "$SD" a b c check_eq sdmon/argc4-transcript "$(cat "$W/noargs.want")" "$("$SD" a b c 2>/dev/null | sed '3d')" check_rc sdmon/argc5-rc 1 "$SD" a b c d # --- 3. option parsing, which needs no device --------------------------------- # The option is compared against "-a" with strcmp and NOTHING else is accepted: # no --all, no --json, no -A. The refusal is printed after the device echo and # BEFORE the addTime line, and it exits before open(2) -- which is asserted # against a path that does not exist, so a build that opened first would say # "device open failed" here instead. "$W/run" badopt "$SD" /dev/brintos-no-such-sd -x check_eq sdmon/badopt-rc "1" "$(cat "$W/badopt.rc")" check_eq sdmon/badopt-device-echo '"device":"/dev/brintos-no-such-sd",' "$(line 4 "$W/badopt.out")" check_eq sdmon/badopt-usage-line '"usage":"wrong option, sdmon [-a]"' "$(line 5 "$W/badopt.out")" hasnt sdmon/badopt-no-addtime '"addTime"' "$(cat "$W/badopt.out")" hasnt sdmon/badopt-precedes-open "device open failed" "$(cat "$W/badopt.out")" check_eq sdmon/badopt-lines "6" "$(wc -l < "$W/badopt.out")" # BST-REDIRECT-OK: $W is under $TMPDIR, never the rootfs cat > "$W/badopt.want" <<'EOW' { "version": "0.8.1+git", "device":"/dev/brintos-no-such-sd", "usage":"wrong option, sdmon [-a]" } EOW check_eq sdmon/badopt-transcript "$(cat "$W/badopt.want")" "$(nodate "$W/badopt.out")" # The two long spellings the port ticket named, and an empty option word: all # three are strcmp mismatches and land on the identical refusal. for o in --all --json ""; do tag=$(printf '%s' "${o:-empty}" | sed 's/^--//') has "sdmon/badopt-$tag" '"usage":"wrong option, sdmon [-a]"' \ "$("$SD" /dev/brintos-no-such-sd "$o" 2>&1)" done # argv[1] is the DEVICE, unconditionally: there is no option parsing in front of # it, so `sdmon -a' asks for a node literally called -a and fails to open it. "$W/run" dasha "$SD" -a check_eq sdmon/dash-a-as-device-rc "1" "$(cat "$W/dasha.rc")" check_eq sdmon/dash-a-as-device-echo '"device":"-a",' "$(line 4 "$W/dasha.out")" check_eq sdmon/dash-a-as-device-addtime '"addTime": "false",' "$(line 5 "$W/dasha.out")" check_eq sdmon/dash-a-as-device-error '"error":"device open failed"' "$(line 6 "$W/dasha.out")" # --- 4. the absent device node ------------------------------------------------ # THIS IS THE PORT'S CENTRAL CASE. /dev/mmcblk0 is the node an SD card would be # behind and there is none, so open(2) returns ENOENT and sdmon reports it and # stops. Bounded, because it is the arm that touches the filesystem layer, and # because a tool that ever blocked in open(2) here could take the whole suite # down with it. bst_probe sdmon/enoent-bounded "$W/enoent.probe" 60 "$W/run" enoent "$SD" /dev/mmcblk0 check_eq sdmon/enoent-rc "1" "$(cat "$W/enoent.rc")" if [ -s "$W/enoent.err" ]; then bst_notok sdmon/enoent-clean-stderr "stderr not empty: $(_bst_1line "$(cat "$W/enoent.err")")" else bst_ok sdmon/enoent-clean-stderr fi check_eq sdmon/enoent-lines "7" "$(wc -l < "$W/enoent.out")" # BST-REDIRECT-OK: $W is under $TMPDIR, never the rootfs cat > "$W/enoent.want" <<'EOW' { "version": "0.8.1+git", "device":"/dev/mmcblk0", "addTime": "false", "error":"device open failed" } EOW check_eq sdmon/enoent-transcript "$(cat "$W/enoent.want")" "$(nodate "$W/enoent.out")" # It stopped AT the open: none of the ioctl-path fields can appear, so the # absence of CMD56 anywhere in the transcript is the proof that no ioctl was # attempted against a node that is not there. hasnt sdmon/enoent-no-ioctl-attempted "CMD56" "$(cat "$W/enoent.out")" hasnt sdmon/enoent-no-success '"success":true' "$(cat "$W/enoent.out")" # The same answer for the other spellings of "no card here", including a # partition node and a path whose parent directory does not exist. has sdmon/enoent-p1 '"error":"device open failed"' "$("$SD" /dev/mmcblk0p1 2>&1)" has sdmon/enoent-deep '"error":"device open failed"' "$("$SD" /dev/brintos-no-such-dir/mmcblk0 2>&1)" # A directory opens with a DIFFERENT errno (EISDIR) and produces the SAME # message, because the open failure path discards errno. Asserted as measured: # the two transcripts must be identical once the date and the device line are # removed. This is the upstream weakness recorded at the head of this file, and # it is the case that flips if it is ever fixed. "$W/run" eisdir "$SD" "$W" check_eq sdmon/open-fail-dir-rc "1" "$(cat "$W/eisdir.rc")" check_eq sdmon/open-fail-errno-not-reported \ "$(nodate "$W/enoent.out" | sed '3d')" "$(nodate "$W/eisdir.out" | sed '3d')" # --- 5. -a against an absent device ------------------------------------------- # addTime is set before the open, so it shows even on the arm that never reaches # the sleep it controls. Bounded for the same reason as the arm above; the 1 s # usleep is NOT reached here, which is what separates this bound from the one in # section 6. bst_probe sdmon/enoent-a-bounded "$W/enoenta.probe" 60 "$W/run" enoenta "$SD" /dev/mmcblk0 -a check_eq sdmon/enoent-a-rc "1" "$(cat "$W/enoenta.rc")" check_eq sdmon/enoent-a-addtime '"addTime": "true",' "$(line 5 "$W/enoenta.out")" check_eq sdmon/enoent-a-error '"error":"device open failed"' "$(line 6 "$W/enoenta.out")" # --- 6. a node that opens and is not an SD card ------------------------------- # The open SUCCEEDS here and the ioctl is refused, so this is the ioctl gate # itself rather than a failure to reach it -- the furthest into sdmon this # machine can go. Two shapes of non-MMC object, because the refusal comes from # the file's own ioctl handler and nothing else: a character device and an # ordinary file the suite creates. if [ -c /dev/null ]; then bst_ok sdmon/control-chardev-present else bst_notok sdmon/control-chardev-present "/dev/null is not a character device on this image; the ioctl-gate cases below cannot run" fi bst_probe sdmon/notsd-bounded "$W/notsd.probe" 60 "$W/run" notsd "$SD" /dev/null check_eq sdmon/notsd-rc "1" "$(cat "$W/notsd.rc")" if [ -s "$W/notsd.err" ]; then bst_notok sdmon/notsd-clean-stderr "stderr not empty: $(_bst_1line "$(cat "$W/notsd.err")")" else bst_ok sdmon/notsd-clean-stderr fi check_eq sdmon/notsd-lines "10" "$(wc -l < "$W/notsd.out")" # BST-REDIRECT-OK: $W is under $TMPDIR, never the rootfs check_eq sdmon/notsd-device-echo '"device":"/dev/null",' "$(line 4 "$W/notsd.out")" check_eq sdmon/notsd-addtime '"addTime": "false",' "$(line 5 "$W/notsd.out")" # The three errno-bearing lines, asserted by SHAPE -- field name, punctuation # and position -- so that they hold whatever errno this kernel returns. starts sdmon/notsd-read-shape '"read_via_cmd56_arg_1":"not implemented: ' "$(line 6 "$W/notsd.out")" starts sdmon/notsd-error1-shape '"error1":"1st CMD56 CALL FAILED: ' "$(line 8 "$W/notsd.out")" starts sdmon/notsd-error2-shape '"error2":"2nd CMD56 CALL FAILED: ' "$(line 9 "$W/notsd.out")" check_eq sdmon/notsd-close-brace "}" "$(line 10 "$W/notsd.out")" # CMD56_write prints the response register array unconditionally, from an idata # struct it memset to zero and whose ioctl failed -- so it is four zero bytes, # and it is a constant on any machine with no card. It is the one ioctl-path # line that is fully determined here, and a build whose %02x rendered something # else would be an ILP32 formatting defect. check_eq sdmon/notsd-response-line '"idata.response[]":"0x00 0x00 0x00 0x00",' "$(line 7 "$W/notsd.out")" # No decoder ran and the object was NOT closed with success: every vendor branch # is behind `ret == 0', and the second CMD56 read exits 1 before the SMART # decode. hasnt sdmon/notsd-no-success '"success":true' "$(cat "$W/notsd.out")" hasnt sdmon/notsd-no-signature '"signature"' "$(cat "$W/notsd.out")" hasnt sdmon/notsd-no-longsys '"Longsys"' "$(cat "$W/notsd.out")" hasnt sdmon/notsd-no-health '"healthStatusPercentUsed"' "$(cat "$W/notsd.out")" # The three strerror texts must AGREE with each other whatever they are: they # are three calls to the same ioctl on the same fd. This holds on any errno and # is the case that would catch a kernel returning a different refusal from the # read and the write path. e_read=$(line 6 "$W/notsd.out"); e_read=${e_read#*not implemented: }; e_read=${e_read%\",} e_one=$(line 8 "$W/notsd.out"); e_one=${e_one#*CALL FAILED: }; e_one=${e_one%\",} e_two=$(line 9 "$W/notsd.out"); e_two=${e_two#*CALL FAILED: }; e_two=${e_two%\"} check_eq sdmon/notsd-errno-agree-read-write "$e_read" "$e_one" check_eq sdmon/notsd-errno-agree-write-read2 "$e_one" "$e_two" # THE ONE FACT IN THIS SUITE THAT HAD TO BE MEASURED RATHER THAN READ. On # x86-64 Linux MMC_IOC_CMD against a non-MMC fd is refused by the generic # char-device path with ENOTTY; this kernel was measured on 2026-09-01 to answer # the same. If this case is ever the only red in the suite, the port is fine and # the answer is simply a different errno: record what it actually said, and # change this line to it. check_eq sdmon/notsd-errno-enotty "Inappropriate ioctl for device" "$e_two" # An ordinary file takes the identical path, which is the proof that nothing # about the refusal is specific to /dev/null. Compared whole, minus the date and # the device line, so it needs no knowledge of the errno at all. : > "$W/regular" "$W/run" regfile "$SD" "$W/regular" check_eq sdmon/regfile-rc "1" "$(cat "$W/regfile.rc")" check_eq sdmon/regfile-same-as-chardev \ "$(nodate "$W/notsd.out" | sed '3d')" "$(nodate "$W/regfile.out" | sed '3d')" # -a on this arm DOES reach its usleep(1000000), between the CMD56 write and the # SMART read. Bounded at 60 s: the sleep is one second, and a bound that fired # would mean the guest's usleep does not return, which is a platform finding and # not an sdmon one. bst_probe sdmon/notsd-a-bounded "$W/notsda.probe" 60 "$W/run" notsda "$SD" /dev/null -a check_eq sdmon/notsd-a-rc "1" "$(cat "$W/notsda.rc")" check_eq sdmon/notsd-a-addtime '"addTime": "true",' "$(line 5 "$W/notsda.out")" # -a changes the timing and NOTHING about the output but that one field, which # is the whole of the answer to the ticket's "--all changes the form of the # diagnostic": it does not. check_eq sdmon/notsd-a-same-shape \ "$(nodate "$W/notsd.out" | sed '4d')" "$(nodate "$W/notsda.out" | sed '4d')" # --- 7. the no-SD-card controls ----------------------------------------------- # These are the CONTROL for the skip below. They assert the platform state the # skip rests on, so that a machine which gains an SD card turns them red in the # same run in which the skip starts running for real -- rather than leaving a # stale skip nobody re-reads. The probe is at RUNTIME over /dev, never a # constant. # mmcblk only, deliberately: an SD card reaches Linux through the MMC block # driver and appears as /dev/mmcblk, and this control has to stay specific to # the thing sdmon can actually talk to. A wider glob would turn any unrelated # disk node into a false "this machine has a card". mmcnode="" for d in /dev/mmcblk*; do if [ -b "$d" ] || [ -c "$d" ]; then mmcnode="$d"; break; fi done if [ -n "$mmcnode" ]; then bst_notok sdmon/control-no-mmc-node "an SD/MMC node exists ($mmcnode): this machine has a card and the gated case below now runs for real" else bst_ok sdmon/control-no-mmc-node fi if [ -d /sys/class/mmc_host ]; then bst_notok sdmon/control-no-mmc-host "/sys/class/mmc_host exists: this kernel has an MMC host controller" else bst_ok sdmon/control-no-mmc-host fi NOCARD_WHY="no SD card behind this machine: no /dev/mmcblk* node and no /sys/class/mmc_host, so CMD56 cannot be issued to a card and the vendor decoders are unreachable -- the port's own premise, asserted by the two controls above" if [ -n "$mmcnode" ]; then check sdmon/dev-real-card "$SD" "$mmcnode" else bst_skip sdmon/dev-real-card "$NOCARD_WHY" fi # --- 8. the suite's own controls ---------------------------------------------- # A comparator with a bug in it passes everything, and the byte-exact cases # above look identical either way. These prove both comparators discriminate. check_eq sdmon/control-cmp-equal "a" "a" if [ "$(cat "$W/enoent.want")" = "$(nodate "$W/enoent.out")x" ]; then bst_notok sdmon/control-cmp-differs "the comparison used above cannot tell two different strings apart" else bst_ok sdmon/control-cmp-differs fi starts sdmon/control-prefix-matches "abc" "abcdef" case "abcdef" in "xbc"*) bst_notok sdmon/control-prefix-differs "the prefix test used above matches anything" ;; *) bst_ok sdmon/control-prefix-differs ;; esac rm -rf "$W"