# Test Case: NFS Per-Inode Generation on Same-Superblock Replacement

**Test ID**: TC154-nfs-per-inode-generation
**Category**: NFS Export
**Priority**: Critical
**Regression**: A mount-wide superblock hash aliases a replacement inode at the same NID

## Objective

Verify that an EROFS file handle keeps the existing 16-byte FreeBSD FID ABI but
uses a stable per-inode generation. Remounting an unchanged image must preserve
the complete handle. Replacing it with an image whose superblock block, UUID,
NID, and file content are identical but whose raw inode metadata differs must
make the old handle return `ESTALE`.

Also verify that a valid-NID handle resolving to a malformed replacement inode
returns the positive inode-read errno instead of a negative Linux errno or a
successful alias.

## Fixture

Generate all review fixtures on the host:

```sh
python3 tests/review_fixtures.py make \
  --output /work/build/repo22-review-fixes-fixtures
```

For `nfs-inode-a.erofs`, `nfs-inode-b.erofs`, and
`nfs-inode-corrupt.erofs`, require the helper evidence to prove:

- target `/zz-identity.txt` has the same NID and inode offset;
- the inode lies after the complete superblock checksum block;
- the complete checksum block and declared superblock are byte-identical;
- A and B differ only in the compact inode `i_mtime` field;
- the helper's superblock-seeded raw-inode FNV generations are nonzero and
  different;
- all three images retain a valid superblock checksum.

## Procedure

1. Compile `tests/nfs_fh_tool.c` natively on FreeBSD 15.
2. Attach image A to an explicit md unit, mount it, capture `a.fh`, and compare
   `nfs_fh_tool describe`, `nfs_fh_tool stat`, and `stat -f '%v'`. The FID and
   `st_gen` values must equal `generation_a` in `fixture-manifest.json`.
3. Unmount and detach image A, then reattach the unchanged image to the same md
   unit. Capture `a-remount.fh`, require a byte-for-byte handle match, and read
   through `a.fh`.
4. Replace A with image B on the same md unit. Capture `b.fh`, require the same
   fsid and NID but `generation_b`, then run:

   ```sh
   nfs_fh_tool expect-stat a.fh ESTALE
   nfs_fh_tool expect-open a.fh ESTALE
   nfs_fh_tool stat b.fh
   ```

5. Replace B with `nfs-inode-corrupt.erofs` on the same md unit. Resolving
   `a.fh` must return positive FreeBSD `EOPNOTSUPP` (45) from inode decoding:

   ```sh
   nfs_fh_tool expect-stat a.fh 45
   nfs_fh_tool expect-open a.fh 45
   ```

6. Compare pre/post dmesg, unmount, detach the md unit, and unload the exact
   module under test.

## Expected Results

- Image A has an identical handle and generation across remounts.
- Image B's same-NID replacement has a different generation and both old-handle
  operations return `ESTALE`.
- The new handle resolves successfully and `va_gen == FID.gen`.
- The malformed inode propagates errno 45 as a positive error and never returns
  a vnode.
- The FID remains exactly 16 bytes with unchanged field offsets.
