DF-3181 / umount_dataloss.sh
#!/bin/sh # DF-3181 — failed (non-forced) umount of tmpfs destroys regular-file data. # # tmpfs_unmount (sys/vfs/tmpfs/tmpfs_vfsops.c:284-334) truncates EVERY VREG # node to length 0 BEFORE calling vflush(). If vflush() then fails (EBUSY — # a vnode still referenced), unmount returns the error and the filesystem # stays mounted with every file's contents already gone. # # Note: dounmount() only reaches VFS_UNMOUNT when the mount's namecache refs # are drained (kern/vfs_syscalls.c dounmount retry loop), so the trigger # must keep the vnode busy WITHOUT a namecache entry: an open fd on an # already-UNLINKED file. # # Expect: umount exits EBUSY; reading the still-open fd then returns EOF # (size 0) although the mount survived and the fd was never # truncated by the application. set -e MNT=/mnt/df3181 FIFO=/tmp/df3181_report mkdir -p $MNT mount -t tmpfs dummy $MNT dd if=/dev/urandom of=$MNT/file bs=1m count=4 2>/dev/null echo "created: $(ls -l $MNT/file | awk '{print $5}') bytes" rm -f $FIFO mkfifo $FIFO # holder: open the file, then unlink it, keep fd; report remaining bytes on # request over the fifo ( exec 3<$MNT/file; rm -f $MNT/file; echo holder-ready > $FIFO; \ while :; do read line < $FIFO; \ n=$(dd bs=64k count=256 <&3 2>/dev/null | wc -c); \ echo "still-readable-bytes=$n" > $FIFO; done ) & HOLDER=$! read x < $FIFO echo "holder: $x (file unlinked, fd open)" set +e umount $MNT RC=$? set -e echo "umount rc=$RC (nonzero == EBUSY family)" echo "mount still present: $(mount | grep -c $MNT)" echo poll > $FIFO read y < $FIFO echo "after failed umount: $y" kill $HOLDER 2>/dev/null || true wait $HOLDER 2>/dev/null || true rm -f $FIFO umount $MNT 2>/dev/null || true rmdir $MNT 2>/dev/null || true |