# DF-0928 — PoC: UFS inode-hash dual-insert race

## Goal

Force a dual `ufs_ihashins` store for the same `(dev,ino)` by racing
N threads' `open()` calls on a file whose vnode has just been evicted
from the cache. The loser inode is orphaned (`IN_HASHED` set but not on
the chain). On `INVARIANTS` kernels the orphan trips
`KKASSERT(ip == iq)` at `ufs_ihash.c:184` when reclaimed; on production
kernels it produces two vnodes for one inode with divergent cached
metadata.

## Build & run (DragonFlyBSD guest with UFS-mounted /tmp)

```
cc -O2 -pthread -o race_ufs_ihash race_ufs_ihash.c
./race_ufs_ihash
```

Optional (root): `sysctl vfs.maxvnodes=256` to make reproduction
near-deterministic.

## Expected output

### INVARIANTS kernel

```
Kernel panic: ufs_ihashrem
...
ufs_ihashrem(...) at ufs_ihashrem+0x...   (ufs_ihash.c:184 KKASSERT)
ufs_reclaim(...)  at ufs_reclaim+0x...
vclean(...)       at vclean+0x...
vrecycle(...)     at vrecycle+0x...
...
```

### Production (non-INVARIANTS) kernel

No panic; instead observe two vnodes for the same `(dev, ino)` via
`fstat | awk '$2 == <ino>'`, or run a side-channel check that
`chmod(04755)`-then-`chmod(0644)` from racing threads leaves a vnode
still reporting the setuid bit.

## Notes

- The remote variant: export `/srv` via NFS, then from two client hosts
  run `find /mnt -exec true {} +` (`READDIRPLUS`-heavy) in a tight loop;
  the server eventually panics the same way.
- This is the same concurrency bug class as the FFS-era inode-hash races
  fixed in FreeBSD/NetBSD years ago; DragonFlyBSD's `ext2_ihash.c` has
  the fix (a `lwkt_token`), `ufs_ihash.c` does not.
