. "$BST_LIB" # mtools 4.0.49, part 3 of 3 — the FILE operations: the copy/list/type round trip, # the directory and rename verbs, attributes, long filenames, the clash contract # and text mode. Parts 1 and 2 (65-mtools.test, 65-mtools-fmt.test) cover the # payload, the option surface, the codepage path and mformat. All three emit # mtools/* ids. # # THE HAZARD THIS FILE IS BUILT AROUND: mtools asks before it clobbers, and # ask_confirmation() reads /dev/tty DIRECTLY (tty.c) — not stdin. On this image # /dev/tty exists and nothing is typing at it, so any clash-prompting call BLOCKS # FOREVER; redirecting stdin does not help. It is not a port defect (mtools is # behaving as designed for a terminal that has a user), but it means every case # below that provokes a clash must either pass an explicit -D clash option or run # under `setsid`, which drops the controlling terminal so that open("/dev/tty") # fails and mtools takes its documented no-answer default. A case written without # that hangs the suite until the runner's 60 s bound kills it — measured. # # Three further expectations were measured rather than assumed: # * mcopy REFUSES an existing DOS target (rc 1, silently, target untouched) # unless given -D o. -D s skips and STILL exits 1; -D a autorenames to # "-1". # * -n does NOT resolve that clash — it governs the UNIX side, where there is # nothing to resolve anyway because mcopy overwrites a Unix file silently. # * mcopy -t appends a DOS EOF (0x1a) after translating LF to CRLF, so a # text-mode round trip is deliberately not byte-identical to the source. require mcopy mtools/files-present LC_ALL=C; export LC_ALL T=${TMPDIR:-/tmp}/bst-mtoolsf.$$ rm -rf "$T"; mkdir -p "$T" || { bst_notok mtools/files-tmpdir "cannot create $T"; exit 0; } mk() { dd if=/dev/zero of="$1" bs=1024 count="$2" >/dev/null 2>&1; } # noask — run detached from the controlling terminal, so a clash prompt # cannot block. Only for calls that deliberately provoke one. noask() { setsid "$@"; } 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=$T/a.img mk "$A" 1440 mformat -i "$A" :: >/dev/null 2>&1 || { bst_notok mtools/files-setup "mformat failed"; rm -rf "$T"; exit 0; } printf 'hello wasm\n' > "$T/f.txt" # ------------------------------------------------------- A. the round trip -- check "mtools/copy-in" mcopy -i "$A" "$T/f.txt" ::/ check_out "mtools/dir-shows" "f txt" mdir -i "$A" :: check_out "mtools/dir-size" "11 bytes" mdir -i "$A" :: check_out "mtools/type-content" "hello wasm" mtype -i "$A" ::/f.txt check "mtools/copy-out" mcopy -i "$A" ::/f.txt "$T/out.txt" check "mtools/copy-out-identical" cmp -s "$T/f.txt" "$T/out.txt" rm -f "$T/out.txt" # mdir's alternate renderings check_eq "mtools/dir-bare" "::/f.txt" "$(mdir -i "$A" -b :: 2>/dev/null)" check_out "mtools/dir-wide" "f.txt" mdir -i "$A" -w :: check_out "mtools/dir-free" "bytes free" mdir -i "$A" :: # mtools formats the free-space figure ITSELF, grouping with spaces — this is not # localeconv and does not change with LC_ALL, so the exact shape is assertable. check_out "mtools/dir-free-grouped" "1 457 152 bytes free" mdir -i "$A" :: check_out "mtools/dir-no-label" "has no label" mdir -i "$A" :: # the same figure as an exact number: 2847 data clusters of 512 B = 1457664 B, # less the one cluster f.txt holds check_eq "mtools/dir-free-value" "1457152" \ "$(mdir -i "$A" :: 2>/dev/null | grep 'bytes free' | tr -dc 0-9)" # ------------------------------------------------------------ B. the verbs -- check "mtools/mmd" mmd -i "$A" ::/sub check_out "mtools/mmd-listed" "sub" mdir -i "$A" :: check "mtools/mmd-nested" mmd -i "$A" ::/sub/deep check_out "mtools/mmd-subdir" "deep" mdir -i "$A" ::/sub # A directory that already exists is a CLASH, so it prompts — hence noask. The # no-answer default is refuse: rc 1 with no message at all. check_rc "mtools/mmd-dup-rc" 1 noask mmd -i "$A" ::/sub check_eq "mtools/mmd-dup-quiet" "" "$(noask mmd -i "$A" ::/sub 2>&1)" # ...and given an explicit clash option it refuses without needing a terminal check_rc "mtools/mmd-dup-D-s" 1 mmd -i "$A" -D s ::/sub check "mtools/mrd" mrd -i "$A" ::/sub/deep check "mtools/copy-into-sub" mcopy -i "$A" "$T/f.txt" ::/sub/ check_out "mtools/sub-listed" "f txt" mdir -i "$A" ::/sub check "mtools/mren" mren -i "$A" ::/f.txt ::/r.txt check_eq "mtools/mren-effect" "::/r.txt" "$(mdir -i "$A" -b :: 2>/dev/null | grep 'r.txt')" check "mtools/mmove" mmove -i "$A" ::/r.txt ::/sub/ check_out "mtools/mmove-effect" "r txt" mdir -i "$A" ::/sub check "mtools/mdel" mdel -i "$A" ::/sub/r.txt check_rc "mtools/mdel-effect" 1 mtype -i "$A" ::/sub/r.txt check "mtools/mdeltree" mdeltree -i "$A" ::/sub check_out "mtools/mdeltree-effect" "No files" mdir -i "$A" :: # the not-found path is one message and one status across the verbs mcopy -i "$A" "$T/f.txt" ::/ >/dev/null 2>&1 for v in mtype mdel mattrib; do check_rc "mtools/notfound-rc-$v" 1 "$v" -i "$A" ::/nope.txt check_msg "mtools/notfound-msg-$v" 'not found' "$v" -i "$A" ::/nope.txt done # ------------------------------------------ C. attributes, clusters, du -- check "mtools/mattrib-read" mattrib -i "$A" ::/f.txt check_out "mtools/mattrib-archive" "A " mattrib -i "$A" ::/f.txt check "mtools/mattrib-set-r" mattrib -i "$A" +r ::/f.txt check_out "mtools/mattrib-shows-r" "R" mattrib -i "$A" ::/f.txt check "mtools/mattrib-clear-r" mattrib -i "$A" -r ::/f.txt check "mtools/mattrib-set-h" mattrib -i "$A" +h ::/f.txt check_out "mtools/mattrib-shows-h" "H" mattrib -i "$A" ::/f.txt check "mtools/mattrib-clear-h" mattrib -i "$A" -h ::/f.txt # an 11-byte file occupies exactly one 512 B cluster; mshowfat names it check_out "mtools/mshowfat" "::/f.txt" mshowfat -i "$A" ::/f.txt check_out "mtools/mdu-root" "::/ 1" mdu -i "$A" :: check_eq "mtools/mshortname" "::/F.TXT" "$(mshortname -i "$A" ::/f.txt 2>/dev/null)" # ------------------------------------------------------- D. long filenames -- # VFAT long names are the codepage-sensitive path; an ASCII one must survive whole # while its 8.3 alias is generated. printf 'LFN\n' > "$T/a very long filename.txt" check "mtools/lfn-copy" mcopy -i "$A" "$T/a very long filename.txt" ::/ check_out "mtools/lfn-listed" "a very long filename.txt" mdir -i "$A" :: check_out "mtools/lfn-alias" "AVERYL~1 TXT" mdir -i "$A" :: check_eq "mtools/lfn-short" "::/AVERYL~1.TXT" \ "$(mshortname -i "$A" "::/a very long filename.txt" 2>/dev/null)" check_out "mtools/lfn-type-by-long" "LFN" mtype -i "$A" "::/a very long filename.txt" check_out "mtools/lfn-type-by-short" "LFN" mtype -i "$A" ::/AVERYL~1.TXT check "mtools/lfn-del" mdel -i "$A" "::/a very long filename.txt" # ------------------------------------------------- E. the clash contract -- printf 'REPLACED\n' > "$T/f2.txt" # bare: refuse, silently, target untouched (prompt suppressed by noask) check_rc "mtools/clash-refused-rc" 1 noask mcopy -i "$A" "$T/f2.txt" ::/f.txt check_out "mtools/clash-untouched" "hello wasm" mtype -i "$A" ::/f.txt # -n reads like an overwrite switch and is not one: it governs the UNIX side, so # against a DOS target it changes nothing — same refusal, same untouched content. check_rc "mtools/n-not-dos-clash" 1 noask mcopy -i "$A" -n "$T/f2.txt" ::/f.txt check_out "mtools/n-leaves-target" "hello wasm" mtype -i "$A" ::/f.txt # the explicit options, which need no terminal at all check "mtools/clash-D-o" mcopy -i "$A" -D o "$T/f2.txt" ::/f.txt check_out "mtools/clash-D-o-effect" "REPLACED" mtype -i "$A" ::/f.txt check_rc "mtools/clash-D-s-rc" 1 mcopy -i "$A" -D s "$T/f.txt" ::/f.txt check_out "mtools/clash-D-s-effect" "REPLACED" mtype -i "$A" ::/f.txt check "mtools/clash-D-a" mcopy -i "$A" -D a "$T/f.txt" ::/f.txt check_out "mtools/clash-D-a-effect" "f.txt-1" mdir -i "$A" -b :: check "mtools/clash-D-a-content" cmp -s "$T/f.txt" "$T/f.txt" mdel -i "$A" ::/f.txt-1 >/dev/null 2>&1 # ...and on the UNIX side there is nothing to resolve: mcopy out overwrites an # existing file silently with or without -n. Pinned so the asymmetry is stated. printf 'old\n' > "$T/u.txt" check "mtools/unix-clash-silent" noask mcopy -i "$A" ::/f.txt "$T/u.txt" check "mtools/unix-clash-n" mcopy -i "$A" -n ::/f.txt "$T/u.txt" check_eq "mtools/unix-clash-effect" "REPLACED" "$(cat "$T/u.txt")" # restore known content for the text-mode section mcopy -i "$A" -D o "$T/f.txt" ::/f.txt >/dev/null 2>&1 # ------------------------------------------------------------ F. text mode -- printf 'a\nb\n' > "$T/t.txt" check "mtools/text-copy" mcopy -i "$A" -t "$T/t.txt" ::/t.txt check_eq "mtools/text-crlf" "610d0a620d0a1a" \ "$(mtype -i "$A" ::/t.txt 2>/dev/null | od -An -tx1 | tr -d ' \n')" check_eq "mtools/text-size" "7" \ "$(mdir -i "$A" :: 2>/dev/null | sed -n 's/^t txt *\([0-9]*\).*/\1/p')" # binary mode is the default and IS byte-identical check "mtools/binary-copy" mcopy -i "$A" "$T/t.txt" ::/b.txt check_eq "mtools/binary-identical" "610a620a" \ "$(mtype -i "$A" ::/b.txt 2>/dev/null | od -An -tx1 | tr -d ' \n')" rm -rf "$T"