#!/bin/sh
# DF-3093 run v5: sub-word free-run granularity.
#
# v4 proved (kernel DFDBG counters) that clusteralloc critical sections DO
# overlap (66 events in one campaign) but 0 double-allocations: 256-cluster
# free runs are word-aligned (256 % 32 == 0) so concurrent chainalloc()
# commits never RMW the same pm_inusemap word, and pm_nxtfree is advanced
# at commit START, which steers later scanners away from the frontier.
#
# v5 geometry: free runs of exactly 4 clusters (half a bitmap word), so
# concurrent commits of ADJACENT free runs read-modify-write the SAME
# pm_inusemap word non-atomically (msdosfs_fat.c:385 `|=`, no lock, no
# atomics).  A lost update leaves an ALLOCATED cluster's bit clear ("free")
# -> the next allocation hands that cluster to a second file: cross-link.
#
# volume: 320 MiB FAT32, 4 KiB clusters (81920 clusters)
#   fill: 8192 x 16 KiB files (4-cluster runs) + one big file for the tail
#   delete every other small file -> 4096 free runs of 4 clusters, no tail
#   race: 6 pinned procs x N rounds x 2 MiB (512-cluster) writes;
#         each write = 128 fallback-scan iterations, each committing a
#         4-cluster run (4 non-atomic word RMWs inside shared words).
#
# usage: run5.sh [cycles] [procs] [rounds] [chunk]
set -u
ID=DF-3093
MNT=/mnt/df3093
VND=vn0
IMG=/root/df3093.img
CYCLES=${1:-2}
PROCS=${2:-6}
ROUNDS=${3:-3}
CHUNK=${4:-2097152}
DIR="$(dirname "$0")"

cleanup() {
	umount $MNT 2>/dev/null || true
	vnconfig -u $VND 2>/dev/null || true
}
trap cleanup EXIT

cd "$DIR" || exit 1
cc -O2 -o race2 race2.c || exit 1
cc -O2 -o fat_scan fat_scan.c || exit 1

c=1
while [ $c -le $CYCLES ]; do
	echo "=== cycle $c/$CYCLES: half-word fragment + ${PROCS}p x ${ROUNDS}r x ${CHUNK}B ==="
	cleanup
	rm -f $IMG
	newfs_msdos -C 320m -c 8 $IMG >/dev/null 2>&1 || { echo newfs fail; exit 1; }
	vnconfig -c $VND $IMG
	mkdir -p $MNT
	mount_msdos -m 644 /dev/$VND $MNT || { echo mount fail; exit 1; }
	sysctl -n debug.df3093_overlaps debug.df3093_double > /tmp/dfdbg.before 2>/dev/null

	# fill: 8192 small files (4-cluster runs) then one tail file
	i=0
	while [ $i -lt 8192 ]; do
		dd if=/dev/zero of=$MNT/S$(printf %05d $i) bs=16k count=1 \
		    >/dev/null 2>&1
		i=$((i + 1))
	done
	dd if=/dev/zero of=$MNT/TAIL bs=1m count=180 >/dev/null 2>&1
	# free every other small run
	i=1
	while [ $i -lt 8192 ]; do
		rm -f $MNT/S$(printf %05d $i)
		i=$((i + 2))
	done
	echo "fragmented: $(df $MNT | tail -1 | awk '{print $4}') 512B-secs free"

	./race2 $MNT $PROCS $ROUNDS $CHUNK 1
	sync
	echo "df after race: $(df $MNT | tail -1 | awk '{print $4}') free"
	umount $MNT
	vnconfig -u $VND
	echo "DFDBG before: $(cat /tmp/dfdbg.before | tr '\n' ' ')"
	echo "DFDBG after:  $(sysctl -n debug.df3093_overlaps debug.df3093_double | tr '\n' ' ')"
	echo "DFDBG dmesg lines: $(dmesg | grep -c DFDBG)"
	dmesg | grep DFDBG | head -5
	if ./fat_scan $IMG; then
		:
	else
		echo "=== CYCLE $c: FAT SCAN FOUND CROSS-LINKS ==="
		cp $IMG /root/df3093-hit-$c.img
		exit 42
	fi
	c=$((c + 1))
done
echo "RESULT $ID: run5 complete"
