. "$BST_LIB" # dosfstools 4.2, part 1 — the shipped PAYLOAD, the option surface, and mkfs.fat. # # fatlabel's and fsck.fat's behaviour is part 2, 65-dosfstools-fsck.test. The # split is forced by the runner's 60 s per-suite wall-clock bound, which the two # halves together exceed on this target (a process spawn dominates each case, so # cost tracks the case count, not the image sizes). Both files emit dosfstools/* # ids, so `brintos-selftest dosfstools` selects the whole surface. # # Every expectation below was measured against a native dosfstools 4.2 built from # the same tarball with the recipe's own options (--enable-compat-symlinks # --without-iconv), NOT assumed. Three of part 1's are counter-intuitive enough # that a suite written from expectation would have shipped false reds: # * only fatlabel has a version switch. mkfs.fat -V is an ERROR, and fsck.fat -V # means "verification pass", not "version". # * the three tools share no exit convention: fsck.fat uses 2 for a usage error # where mkfs.fat and fatlabel use 1. # * a 64 MB image still defaults to FAT16. FAT32 is never chosen by size here, # and -F 32 is accepted far below its cluster minimum where -F 16 is refused. require mkfs.fat dosfstools/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 } # ---------------------------------------------------------------- A. payload -- for t in mkfs.fat fsck.fat fatlabel; do check "dosfstools/bin-$t" test -f "$SBIN/$t" check_eq "dosfstools/wasm-$t" "0061736d" "$(rdx "$SBIN/$t" 0 4)" done # the six plain --enable-compat-symlinks names, and what each must resolve to for pair in dosfsck:fsck.fat fsck.msdos:fsck.fat fsck.vfat:fsck.fat \ mkdosfs:mkfs.fat mkfs.msdos:mkfs.fat dosfslabel:fatlabel; do n=${pair%:*}; want=${pair#*:} check_eq "dosfstools/symlink-$n" "$want" "$(readlink "$SBIN/$n")" done # mkfs.vfat is the ONE name registered through update-alternatives, so it reaches # mkfs.fat via the renamed .dosfstools target rather than directly. A one-hop link # here would mean the alternative was never registered at rootfs time. check_eq "dosfstools/alt-mkfs.vfat" "mkfs.vfat.dosfstools" "$(readlink "$SBIN/mkfs.vfat")" check_eq "dosfstools/alt-mkfs.vfat-tgt" "mkfs.fat" "$(readlink "$SBIN/mkfs.vfat.dosfstools")" # noinst_PROGRAMS must not have leaked into the package check_rc "dosfstools/no-testdevinfo" 1 test -e "$SBIN/testdevinfo" # every spelling resolves by bare name => /usr/sbin is on PATH for an exec'd helper for t in mkfs.fat fsck.fat fatlabel mkdosfs mkfs.vfat dosfslabel; do check "dosfstools/path-$t" command -v "$t" done # ------------------------------------------------- B. version / option surface -- # fatlabel is the only one of the three with a version switch. check_out "dosfstools/fatlabel-V" "fatlabel 4.2" fatlabel -V check_out "dosfstools/fatlabel-version" "fatlabel 4.2" fatlabel --version # mkfs.fat -V / --version are NOT version switches — they are unrecognised options. check_rc "dosfstools/mkfs-V-is-error" 1 mkfs.fat -V check_rc "dosfstools/mkfs-version-is-error" 1 mkfs.fat --version # fsck.fat -V is the VERIFICATION PASS (rc 0 on a good fs, asserted in F); # --version is not a switch at all, and fsck.fat's usage-error status is 2. check_rc "dosfstools/fsck-version-is-error" 2 fsck.fat --version check_out "dosfstools/mkfs-help" "Create file TARGET then create filesystem" mkfs.fat --help check_out "dosfstools/fsck-help" "automatically repair the filesystem" fsck.fat --help check_out "dosfstools/fatlabel-help" "--volume-id" fatlabel --help # fsck.fat's default mode is INTERACTIVE (-r), so every call here passes a mode # flag. Pinned so the contract is documented rather than folklore. check_out "dosfstools/fsck-default-interactive" "interactively repair the filesystem (default)" fsck.fat --help # the three tools' usage-error statuses differ; pin each check_rc "dosfstools/mkfs-badopt" 1 mkfs.fat --invalid-option check_rc "dosfstools/fsck-badopt" 2 fsck.fat --invalid-option check_rc "dosfstools/fatlabel-badopt" 1 fatlabel --invalid-option check_rc "dosfstools/mkfs-noargs" 1 mkfs.fat check_rc "dosfstools/fsck-noargs" 2 fsck.fat check_rc "dosfstools/fatlabel-noargs" 1 fatlabel check_msg "dosfstools/mkfs-nodevice" "No device specified." mkfs.fat check_rc "dosfstools/mkfs-C-needs-size" 1 mkfs.fat -C "$T/nosize.img" check_msg "dosfstools/mkfs-C-needs-size-msg" "Need intended size with -C." mkfs.fat -C "$T/nosize.img" # the version banner goes to STDOUT, not stderr mkfs.fat -C "$T/stream.img" 64 2>/dev/null >"$T/stream.out" mkfs.fat -C "$T/stream2.img" 64 2>"$T/stream.err" >/dev/null check_out "dosfstools/banner-on-stdout" "mkfs.fat 4.2" cat "$T/stream.out" check_eq "dosfstools/stderr-quiet" "" "$(cat "$T/stream.err")" rm -f "$T/stream.img" "$T/stream2.img" "$T/stream.out" "$T/stream.err" # ----------------------------------------------- C. mkfs.fat: create + shape -- FD=$T/fd.img check "dosfstools/mkfs-create" mkfs.fat -C "$FD" 1440 check_eq "dosfstools/mkfs-create-size" "1474560" "$(stat -c %s "$FD")" check_eq "dosfstools/boot-signature" "55aa" "$(rdx "$FD" 510 2)" check_eq "dosfstools/oem-name" "mkfs.fat" "$(rdb "$FD" 3 8)" check_eq "dosfstools/media-byte" "f0" "$(rdx "$FD" 21 1)" check_eq "dosfstools/fstype-fat12" "FAT12" "$(rdb "$FD" 54 8 | cut -c1-5)" # FAT width is chosen by SIZE, and 12/16 are the only widths ever chosen by # default: a 64 MB image still comes out FAT16. Expecting FAT32 is the classic # false red here. mkfs.fat -C "$T/sz16.img" 16384 >/dev/null 2>&1 mkfs.fat -C "$T/sz64.img" 65536 >/dev/null 2>&1 check_out "dosfstools/default-fat12-1440" "12 bit entries" fsck.fat -n -v "$FD" check_out "dosfstools/default-fat16-16m" "16 bit entries" fsck.fat -n -v "$T/sz16.img" check_out "dosfstools/default-fat16-64m" "16 bit entries" fsck.fat -n -v "$T/sz64.img" check_eq "dosfstools/fstype-fat16-default" "FAT16" "$(rdb "$T/sz16.img" 54 8 | cut -c1-5)" rm -f "$T/sz16.img" "$T/sz64.img" # FAT32 must be ASKED for, and is then accepted far below its cluster minimum with # a WARNING and rc 0 — not a refusal. F32=$T/f32.img check "dosfstools/mkfs-F32" mkfs.fat -C -F 32 "$F32" 1024 check_out "dosfstools/F32-undersize-warns" "less then suggested minimum" mkfs.fat -C -F 32 "$T/w32.img" 256 check_out "dosfstools/F32-entries" "32 bit entries" fsck.fat -n -v "$F32" check_eq "dosfstools/fstype-fat32" "FAT32" "$(rdb "$F32" 82 8 | cut -c1-5)" rm -f "$T/w32.img" "$F32" # -F 16, unlike -F 32, IS refused below its own minimum (~9 MB). Note -C creates # the file BEFORE it validates the size, and then refuses to overwrite one that # exists — so the second probe needs a clean slate or it fails for the wrong reason. check_rc "dosfstools/F16-too-small-rc" 1 mkfs.fat -C -F 16 "$T/s16.img" 1024 rm -f "$T/s16.img" check_msg "dosfstools/F16-too-small-msg" "too small or a too large filesystem" mkfs.fat -C -F 16 "$T/s16.img" 1024 check_rc "dosfstools/bad-fat-type-rc" 1 mkfs.fat -C -F 99 "$T/b99.img" 1440 check_msg "dosfstools/bad-fat-type-msg" "Bad FAT type : 99" mkfs.fat -C -F 99 "$T/b99.img" 1440 rm -f "$T/s16.img" "$T/b99.img" # A real FAT16 needs ~9 MB of scratch and /tmp here is a RAM tmpfs, so size the # decision first. NOT with df: statfs is ENOSYS on wasm32-hwjs, so `df` exits # "Function not implemented" and any parse of it silently yields nothing. `free` # does work. If even that gives no number, run the case rather than skip it — a # skip caused by a broken probe hides coverage, where a genuine shortage still # surfaces as the mkfs failing loudly. FREE=$(free 2>/dev/null | awk '/^Mem:/{print $NF}') case "$FREE" in ''|*[!0-9]*) FREE=-1 ;; esac if [ "$FREE" -eq -1 ] || [ "$FREE" -ge 20480 ]; then check "dosfstools/mkfs-F16" mkfs.fat -C -F 16 "$T/r16.img" 9000 check_eq "dosfstools/fstype-fat16" "FAT16" "$(rdb "$T/r16.img" 54 8 | cut -c1-5)" check_rc "dosfstools/fsck-F16" 0 fsck.fat -n "$T/r16.img" rm -f "$T/r16.img" else bst_skip dosfstools/mkfs-F16 "a FAT16 minimum needs ~9 MB scratch; only ${FREE}K available" fi # geometry knobs reach the on-disk structures mkfs.fat -C -s 4 -R 4 -f 1 -r 512 "$T/geo.img" 4096 >/dev/null 2>&1 check_eq "dosfstools/geom-cluster" "2048" "$(geom "$T/geo.img" 'bytes per cluster')" check_eq "dosfstools/geom-reserved" "4" "$(geom "$T/geo.img" 'reserved sector')" check_eq "dosfstools/geom-rootents" "512" "$(geom "$T/geo.img" 'root directory entries')" check_out "dosfstools/geom-one-fat" "1 FATs" fsck.fat -n -v "$T/geo.img" mkfs.fat -C -S 1024 "$T/ss.img" 512 >/dev/null 2>&1 check_eq "dosfstools/geom-sector" "1024" "$(geom "$T/ss.img" 'bytes per logical sector')" rm -f "$T/geo.img" "$T/ss.img" # without -C the file must already be sized, and mkfs must not resize it dd if=/dev/zero of="$T/pre.img" bs=1024 count=2048 >/dev/null 2>&1 check "dosfstools/mkfs-no-C" mkfs.fat "$T/pre.img" check_eq "dosfstools/mkfs-no-C-size" "2097152" "$(stat -c %s "$T/pre.img")" check_rc "dosfstools/fsck-no-C" 0 fsck.fat -n "$T/pre.img" rm -f "$T/pre.img" check_rc "dosfstools/mkfs-bad-path" 1 mkfs.fat -C "$T/no/such/dir/x.img" 512 # ------------------------------------- D. determinism + the compat spellings -- # --invariant replaces every random/time-derived value with a constant, so two # runs are byte-identical; without it the volume id is time-derived and they are # not. Both halves are asserted — either alone proves nothing. mkfs.fat -C --invariant "$T/i1.img" 512 >/dev/null 2>&1 mkfs.fat -C --invariant "$T/i2.img" 512 >/dev/null 2>&1 check "dosfstools/invariant-identical" cmp -s "$T/i1.img" "$T/i2.img" check_eq "dosfstools/invariant-volid" "1234abcd" "$(fatlabel -i "$T/i1.img" 2>/dev/null)" mkfs.fat -C -i aaaa0001 "$T/v1.img" 512 >/dev/null 2>&1 mkfs.fat -C -i aaaa0002 "$T/v2.img" 512 >/dev/null 2>&1 check_rc "dosfstools/volid-differs" 1 cmp -s "$T/v1.img" "$T/v2.img" check_eq "dosfstools/volid-set" "aaaa0001" "$(fatlabel -i "$T/v1.img" 2>/dev/null)" # the volume id is stored little-endian at offset 39 check_eq "dosfstools/volid-le" "0100aaaa" "$(rdx "$T/v1.img" 39 4)" rm -f "$T/i1.img" "$T/i2.img" "$T/v1.img" "$T/v2.img" # every mkfs spelling is the SAME program: same args => byte-identical output. # Unlike flex/flex++, argv[0] does not select behaviour here. mkfs.fat -C --invariant -n EQ "$T/e-base.img" 512 >/dev/null 2>&1 for t in mkdosfs mkfs.msdos mkfs.vfat; do "$t" -C --invariant -n EQ "$T/e-$t.img" 512 >/dev/null 2>&1 check "dosfstools/spelling-$t" cmp -s "$T/e-base.img" "$T/e-$t.img" rm -f "$T/e-$t.img" done for t in dosfsck fsck.msdos fsck.vfat; do check_rc "dosfstools/spelling-$t" 0 "$t" -n "$T/e-base.img" done check_eq "dosfstools/spelling-dosfslabel" "EQ" "$(dosfslabel "$T/e-base.img" 2>/dev/null)" rm -f "$T/e-base.img"