. "$BST_LIB" # dosfstools 4.2, part 2 — the BEHAVIOUR of fatlabel and fsck.fat. # # Split from 65-dosfstools.test (which covers the payload, the option surface and # mkfs.fat) purely for the runner's 60 s per-suite wall-clock bound: the two # halves together exceed it on this target, where a process spawn dominates each # case. Both files emit dosfstools/* ids, so `brintos-selftest dosfstools` still # selects the whole surface and the report reads as one suite. # # As in part 1, every expectation was MEASURED against a native dosfstools 4.2 # built with the recipe's options (--enable-compat-symlinks --without-iconv), and # the two counter-intuitive ones live here: # * fsck.fat returns 1 for "errors were FOUND" — including under -n, which # changes nothing. 1 is not "repair happened"; only the re-check returns 0. # * lowercase labels are accepted (with a warning) and read back lowercase. require mkfs.fat dosfstools/fsck-present SBIN=/usr/sbin # Pin the locale. dosfstools decodes a label with mbstowcs() before encoding it to # CP850, so the *diagnostic* for a non-decodable label is chosen by the locale, not # by the tool: under C it fails in the decode ("Cannot convert input string"), # under a UTF-8 locale it decodes and then fails validation ("characters below # 0x20"). Both reject with rc 1. The image ships no locale data today, so C is what # runs here anyway — stating it keeps the assertion true if that ever changes. LC_ALL=C; export LC_ALL T=${TMPDIR:-/tmp}/bst-dosfs.$$ rm -rf "$T"; mkdir -p "$T" || { bst_notok dosfstools/tmpdir "cannot create $T"; exit 0; } # raw bytes / hex at a byte offset — the on-disk shape is the real contract rdb() { dd if="$1" bs=1 skip="$2" count="$3" 2>/dev/null; } rdx() { dd if="$1" bs=1 skip="$2" count="$3" 2>/dev/null | od -An -tx1 | tr -d ' \n'; } # zero — corrupt in place without printf escapes zero() { dd if=/dev/zero of="$1" bs=1 seek="$2" count="$3" conv=notrunc >/dev/null 2>&1; } # leading integer of the `fsck.fat -n -v` line matching $2 (e.g. "bytes per FAT") geom() { fsck.fat -n -v "$1" 2>&1 | sed -n "s/^ *\([0-9][0-9]*\) $2.*/\1/p" | head -1; } fat1off() { fsck.fat -n -v "$1" 2>&1 | sed -n 's/^First FAT starts at byte \([0-9]*\).*/\1/p' | head -1; } md5() { md5sum "$1" | cut -d' ' -f1; } # check_out demands rc 0, so it cannot assert the text of an ERROR path. This is # its sibling: assert the message, whatever the status (the status is asserted # separately by check_rc, so the two together pin both halves). check_msg() { _i=$1; _n=$2; shift 2 _o=$("$@" 2>&1) case "$_o" in *"$_n"*) bst_ok "$_i" ;; *) bst_notok "$_i" "want:'$_n' got:'$(_bst_1line "$_o")'" ;; esac } # ------------------------------------------------------- E. fatlabel semantics -- LB=$T/lab.img mkfs.fat -C "$LB" 512 >/dev/null 2>&1 # a fresh filesystem carries the literal sentinel "NO NAME " on disk, and # fatlabel reports that as an EMPTY label with rc 0 — not as an error, and not as # the string "NO NAME". A caller grepping the output sees nothing at all. check_eq "dosfstools/fresh-sentinel-on-disk" "NO NAME" "$(rdb "$LB" 43 11 | cut -c1-7)" check_eq "dosfstools/fresh-label-empty" "" "$(fatlabel "$LB" 2>/dev/null)" check_rc "dosfstools/fresh-label-rc" 0 fatlabel "$LB" check "dosfstools/label-set" fatlabel "$LB" BSTVOL check_eq "dosfstools/label-read" "BSTVOL" "$(fatlabel "$LB" 2>/dev/null)" check_eq "dosfstools/label-on-disk" "BSTVOL" "$(rdb "$LB" 43 11 | cut -c1-6)" # a label is ALSO written as a root-directory volume entry (attribute 0x08), which # is why fsck then counts it as a file where an unlabelled fs counts none. mkfs.fat -C "$T/nolbl.img" 512 >/dev/null 2>&1 mkfs.fat -C -n L "$T/haslbl.img" 512 >/dev/null 2>&1 check_out "dosfstools/label-not-a-file" "0 files" fsck.fat -n "$T/nolbl.img" check_out "dosfstools/label-is-a-file" "1 files" fsck.fat -n "$T/haslbl.img" check_out "dosfstools/label-listed" "/L" fsck.fat -n -l "$T/haslbl.img" rm -f "$T/nolbl.img" "$T/haslbl.img" # the rules, each measured check_rc "dosfstools/label-too-long-rc" 1 fatlabel "$LB" TOOLONGLABEL12 check_msg "dosfstools/label-too-long-msg" "no longer than 11 characters" fatlabel "$LB" TOOLONGLABEL12 check_rc "dosfstools/label-empty-rc" 1 fatlabel "$LB" "" check_msg "dosfstools/label-empty-msg" "can't be empty" fatlabel "$LB" "" check_rc "dosfstools/label-dot-rc" 1 fatlabel "$LB" "A.B" check_msg "dosfstools/label-dot-msg" "are not allowed" fatlabel "$LB" "A.B" check "dosfstools/label-11-exact" fatlabel "$LB" ABCDEFGHIJK check_eq "dosfstools/label-11-read" "ABCDEFGHIJK" "$(fatlabel "$LB" 2>/dev/null)" check "dosfstools/label-inner-space" fatlabel "$LB" "BST 123" check_eq "dosfstools/label-inner-space-read" "BST 123" "$(fatlabel "$LB" 2>/dev/null)" # lowercase is ACCEPTED with a warning and read back lowercase — dosfstools does # not upcase it. Expecting an upcased read-back is the second classic false red. check "dosfstools/label-lower-accepted" fatlabel "$LB" lower check_out "dosfstools/label-lower-warns" "lowercase labels might not work" fatlabel "$LB" lower check_eq "dosfstools/label-lower-preserved" "lower" "$(fatlabel "$LB" 2>/dev/null)" # --without-iconv: the label codec is the bundled CP850 table, fed by mbstowcs(). # Under the C locale set above, a UTF-8 label does not decode at all, so the # rejection comes from the CONVERTER, not the validator — the same message a # native build gives under LC_ALL=C. check_rc "dosfstools/label-multibyte-rc" 1 fatlabel "$LB" "$(printf 'CAF\303\211')" check_msg "dosfstools/label-multibyte-msg" "Cannot convert input string" fatlabel "$LB" "$(printf 'CAF\303\211')" check_msg "dosfstools/label-multibyte-err" "error when processing label" fatlabel "$LB" "$(printf 'CAF\303\211')" # a rejected set must leave the previous label intact check_eq "dosfstools/label-reject-atomic" "lower" "$(fatlabel "$LB" 2>/dev/null)" # -r resets the label back to the sentinel check "dosfstools/label-reset" fatlabel -r "$LB" check_eq "dosfstools/label-reset-empty" "" "$(fatlabel "$LB" 2>/dev/null)" check_eq "dosfstools/label-reset-sentinel" "NO NAME" "$(rdb "$LB" 43 11 | cut -c1-7)" # serial: -i reads and writes it; a non-hex value is refused; -i -r regenerates it check "dosfstools/serial-set" fatlabel -i "$LB" 0badf00d check_eq "dosfstools/serial-read" "0badf00d" "$(fatlabel -i "$LB" 2>/dev/null)" check_rc "dosfstools/serial-nonhex-rc" 1 fatlabel -i "$LB" zzzz check_msg "dosfstools/serial-nonhex-msg" "must be a hexadecimal number" fatlabel -i "$LB" zzzz check_eq "dosfstools/serial-reject-atomic" "0badf00d" "$(fatlabel -i "$LB" 2>/dev/null)" fatlabel -i -r "$LB" >/dev/null 2>&1 check_rc "dosfstools/serial-reset" 1 test "0badf00d" = "$(fatlabel -i "$LB" 2>/dev/null)" rm -f "$LB" # ------------------------------------------------ F. fsck.fat: repair contract -- # On a clean filesystem all four non-interactive modes agree on rc 0. CL=$T/clean.img mkfs.fat -C --invariant "$CL" 512 >/dev/null 2>&1 for m in -n -a -p -y; do check_rc "dosfstools/fsck-clean$m" 0 fsck.fat "$m" "$CL"; done check_out "dosfstools/fsck-clean-summary" "0 files, 0/247 clusters" fsck.fat -n "$CL" check_out "dosfstools/fsck-verify-pass" "Starting verification pass" fsck.fat -n -V "$CL" check_rc "dosfstools/fsck-boot-only" 0 fsck.fat -n -b "$CL" rm -f "$CL" # Corruption fixture: clobber the head of the SECOND FAT so the two copies differ. # The offset is DERIVED from the filesystem's own geometry rather than hardcoded, # which keeps the fixture correct if the geometry ever moves and simultaneously # proves fsck's verbose report is parseable. BR=$T/broken.img mkfs.fat -C --invariant "$BR" 512 >/dev/null 2>&1 FAT1=$(fat1off "$BR"); FLEN=$(geom "$BR" 'bytes per FAT') check_eq "dosfstools/geom-fat1-offset" "512" "$FAT1" check_eq "dosfstools/geom-fat-length" "512" "$FLEN" zero "$BR" $((FAT1 + FLEN)) 3 BEFORE=$(md5 "$BR") # -n FINDS the error and returns 1 while changing nothing. rc 1 therefore means # "errors were found", not "errors were repaired" — and the image must be # byte-identical afterwards, the half a status-only assertion would miss. check_rc "dosfstools/fsck-broken-n-rc" 1 fsck.fat -n "$BR" check_msg "dosfstools/fsck-broken-n-msg" "FATs differ" fsck.fat -n "$BR" check_eq "dosfstools/fsck-n-readonly" "$BEFORE" "$(md5 "$BR")" # -a repairs, says so, and ALSO returns 1. Status and message come from ONE run: # a second -a would find the filesystem already clean and report neither. REPOUT=$(fsck.fat -a "$BR" 2>&1); REPRC=$? check_eq "dosfstools/fsck-repair-rc" "1" "$REPRC" case "$REPOUT" in *"Filesystem was changed"*) bst_ok dosfstools/fsck-repair-msg ;; *) bst_notok dosfstools/fsck-repair-msg "want:'Filesystem was changed' got:'$(_bst_1line "$REPOUT")'" ;; esac check_rc "dosfstools/fsck-repair-wrote" 1 test "$BEFORE" = "$(md5 "$BR")" # only the RE-CHECK comes back 0 — the assertion that proves the repair stuck check_rc "dosfstools/fsck-repaired-clean" 0 fsck.fat -n "$BR" rm -f "$BR" # A second, independent corruption class: the FAT32 FSInfo free-cluster summary. FS=$T/fsinfo.img mkfs.fat -C -F 32 --invariant "$FS" 1024 >/dev/null 2>&1 zero "$FS" 1000 4 check_rc "dosfstools/fsinfo-detect-rc" 1 fsck.fat -n "$FS" check_msg "dosfstools/fsinfo-detect-msg" "Free cluster summary wrong" fsck.fat -n "$FS" check_rc "dosfstools/fsinfo-repair" 1 fsck.fat -a "$FS" check_rc "dosfstools/fsinfo-clean" 0 fsck.fat -n "$FS" rm -f "$FS" # Error routing: three different inputs, three different statuses. : > "$T/empty.img" check_rc "dosfstools/fsck-empty-rc" 1 fsck.fat -n "$T/empty.img" check_msg "dosfstools/fsck-empty-msg" "Got 0 bytes instead of 512" fsck.fat -n "$T/empty.img" dd if="$SBIN/mkfs.fat" of="$T/rand.img" bs=1024 count=64 >/dev/null 2>&1 check_rc "dosfstools/fsck-garbage" 1 fsck.fat -n "$T/rand.img" check_rc "dosfstools/fatlabel-garbage" 1 fatlabel "$T/rand.img" # a MISSING device is rc 6 — fsck.fat's own status, unlike the other two tools check_rc "dosfstools/fsck-missing-rc" 6 fsck.fat -n "$T/does-not-exist.img" check_msg "dosfstools/fsck-missing-msg" "No such file or directory" fsck.fat -n "$T/does-not-exist.img" rm -f "$T/empty.img" "$T/rand.img" # What fsck.fat does NOT check, recorded so a future reader does not build a # fixture on it: the 0x55AA boot signature and the media byte are both written by # mkfs.fat and neither is validated on the check path. NC=$T/nocheck.img mkfs.fat -C --invariant "$NC" 512 >/dev/null 2>&1 zero "$NC" 510 2 check_rc "dosfstools/bootsig-not-checked" 0 fsck.fat -n "$NC" zero "$NC" 21 1 check_rc "dosfstools/mediabyte-not-checked" 0 fsck.fat -n "$NC" rm -rf "$T"