# Test Case: FreeBSD NFSv3 Export Basic Functionality

**Test ID**: TC131-nfs-export-basic
**Category**: NFS Export
**Priority**: Critical
**Regression**: EROFS `VOP_VPTOFH` / `VFS_FHTOVP` / export updates

## Objective

Verify that FreeBSD 15 mountd can install an export on a read-only EROFS
mount, that nfsd can resolve EROFS file handles, and that regular files,
directories, symlinks, and FIFOs are visible through a real NFSv3 client.

## Preconditions

- FreeBSD 15 amd64 server/client, optionally the same host over loopback.
- Root privilege for module, md, NFS service, `getfh`, and `fhopen` operations.
- `erofs.ko`, `test-nfs.erofs`, and `tests/nfs_fh_tool.c` available in the
  guest.
- The fixture contains `basic/regular.txt`, `basic/subdir`,
  `basic/link-to-regular`, and `basic/test.fifo`.

## Procedure

1. Build the module and helper:

   ```sh
   ./build.sh
   cc -O2 -Wall -Wextra -std=c17 tests/nfs_fh_tool.c -o nfs_fh_tool
   ```

2. Load and mount EROFS on a fixed md unit:

   ```sh
   kldload ./erofs.ko
   mdconfig -a -t vnode -f test-nfs.erofs -u 42
   mkdir -p /mnt/repo22-erofs
   mount -t erofs -o ro /dev/md42 /mnt/repo22-erofs
   mount -v | grep /mnt/repo22-erofs
   ```

   The line must show `read-only` and must not show `NFS exported` before
   mountd processes `/etc/exports`.

3. Configure the real FreeBSD mountd/nfsd flow:

   ```sh
   cp -p /etc/exports /tmp/exports.before 2>/dev/null || true
   printf '%s\n' \
     '/mnt/repo22-erofs -ro -maproot=root -network 127.0.0.0 -mask 255.0.0.0' \
     > /etc/exports
   service rpcbind onestart
   service mountd onestart
   service nfsd onestart
   service mountd onereload
   rpcinfo -p 127.0.0.1
   showmount -e 127.0.0.1
   mount -v | grep /mnt/repo22-erofs
   ```

   After reload, the EROFS mount line must show `NFS exported`. The flag is
   installed by the generic FreeBSD export layer after the filesystem accepts
   the export-only `MNT_UPDATE`; EROFS must not set it during the initial mount.

4. Mount the export through NFSv3 and confirm the negotiated options:

   ```sh
   mkdir -p /mnt/repo22-nfs
   mount_nfs -o nfsv3,tcp,rdirplus 127.0.0.1:/mnt/repo22-erofs \
     /mnt/repo22-nfs
   nfsstat -m
   ```

5. Verify all required vnode types and read-only behavior:

   ```sh
   cmp /mnt/repo22-nfs/basic/regular.txt \
     /mnt/repo22-erofs/basic/regular.txt
   test -d /mnt/repo22-nfs/basic/subdir
   test "$(readlink /mnt/repo22-nfs/basic/link-to-regular)" = regular.txt
   test -p /mnt/repo22-nfs/basic/test.fifo
   test "$(stat -f %i /mnt/repo22-nfs/basic/regular.txt)" = \
     "$(stat -f %i /mnt/repo22-erofs/basic/regular.txt)"
   ! touch /mnt/repo22-nfs/write-must-fail
   ```

6. Exercise both shared- and exclusive-lock file-handle resolution locally:

   ```sh
   ./nfs_fh_tool capture /mnt/repo22-erofs/basic/regular.txt regular.fh
   ./nfs_fh_tool stat regular.fh
   ./nfs_fh_tool cat regular.fh regular.out
   cmp regular.out /mnt/repo22-erofs/basic/regular.txt
   ```

   `fhstat` requests a shared lock and `fhopen` requests an exclusive lock.

7. Confirm that NFSv3 used READDIRPLUS and did not issue writes:

   ```sh
   nfsstat -c
   nfsstat -s
   ```

## Expected Results

- mountd accepts the EROFS export-only update.
- `showmount -e` lists the EROFS path and `mount -v` shows `NFS exported` only
  after mountd reloads exports.
- NFSv3 regular reads, directory traversal, symlink lookup, FIFO metadata, and
  inode numbers match the direct EROFS mount.
- Writes fail with `EROFS`/`Read-only file system`.
- `fhstat` and `fhopen` both resolve the same 64-bit EROFS NID.
- NFS statistics show READDIRPLUS traffic and zero successful write RPCs.

## Cleanup

Always remove clients before stopping the loopback server:

```sh
umount /mnt/repo22-nfs
: > /etc/exports
service mountd onereload
service nfsd onestop
service mountd onestop
service rpcbind onestop
umount /mnt/repo22-erofs
mdconfig -d -u 42
kldunload erofs
rm -f /etc/exports
```

Restore a pre-existing `/etc/exports` instead of removing it when applicable.

## Notes

- The EROFS FreeBSD file-handle payload is 16 bytes:
  `len`, `pad`, `nid_hi`, `nid_lo`, and a nonzero superblock-seeded per-inode
  generation matching `va_gen`.
- Do not use Linux `exportfs`; FreeBSD mountd installs exports through a mount
  update carrying the `export` option.
- Throughput is recorded for information only; correctness has no fixed MB/s
  threshold.
