# DF-3133 — dirfs_node_chsize() destroys the guest page cache before the host
# truncate() is known to succeed (and ignores absolute_path()==NULL)

## What

`dirfs_node_chsize` (sys/vfs/dirfs/dirfs_subr.c:778-793) runs
`nvtruncbuf()`/`nvextendbuf()` on the vnode **first**, and only then attempts
the host-side `truncate(tmp, nsize)`. If that truncate fails —
deterministically `EFAULT` when `dirfs_node_absolute_path()` returned NULL
(unlinked-but-open node with `dn_parent` already cleared by
dirfs_nremove/nrmdir/nrename, or absolute path > MAXPATHLEN), or `EPERM`/
`EACCES`/`ENOSPC` from the host — the damage is already done and is never
rolled back:

* `nvtruncbuf` **destroys every buffer at/beyond the truncation point without
  flushing** (`B_INVAL|B_RELBUF|B_NOCACHE`, sys/kern/vfs_vm.c:300-302) —
  including `B_DELWRI` buffers holding `write(2)`-acknowledged data → the
  acknowledged writes are silently lost and subsequent reads resurrect the
  OLD host data;
* the VM object is resized (`nvnode_pager_setsize`) to `nsize` while the host
  file is unchanged → mmap view desyncs (SIGBUS on data that `read(2)` still
  returns);
* `chsize` has no `dn_flags & (IMMUTABLE|APPEND)` guard (unlike chtimes
  :656-657) and the setattr size branch has no ownership check
  (dirfs_vnops.c:491-503 only checks MNT_RDONLY), so any guest user can
  inflict this on the shared cached view of **any** dirfs file whose host
  truncate then fails.

`tmp` (the `dirfs_node_absolute_path()` return, :778) is never NULL-checked —
unlike the sibling `ch*` wrappers whose `KKASSERT(tmp)` family is DF-0855.

## Reproduce

dirfs is vkernel-only, so proof is by deterministic harness transcription
(DF-3054/DF-0806/DF-0807 precedent) — verbatim `absolute_path` + `chsize`
control flow, `nvtruncbuf`/`nvextendbuf` stubbed to the documented
vfs_vm.c semantics, real host files and real `truncate()`/`pread` so all
errno outcomes are real.

```
ssh dfbsd-maxx        # unprivileged (uid 1001)
cd poc/DF-3133
./build.sh && ./run.sh   # expect rc=2: 9/9 BUG confirmations + FIXED variants
```

Expected (see run.log):
* A) unlinked-but-open: chsize → EFAULT **after** the dirty acknowledged
  write was destroyed and the pager truncated; host data still alive through
  the open fd; guest re-read resurrects OLD data.
* B) >MAXPATHLEN absolute path — tree really constructed on the host via
  fd-relative openat() (the dirfs fd-cache trick); same damage; the 4x240
  boundary path truncates normally (sanity).
* C) valid path, root-owned file: EPERM from the host **after** the guest
  buffers/pager for that file were destroyed; host file verified untouched.
* FIXED variant (NULL check + host-truncate-first): ENAMETOOLONG / errno
  propagated with ZERO damage in every scenario.

3/3 runs byte-identical (run.log, run.2.log, run.3.log — same MD5).

## Fix

`fix.diff` (also validated): NULL check → `ENAMETOOLONG`, host
`truncate()` first, buffer/pager updates only after success. Applies cleanly
(`git apply --check` RC=0 local tree + guest /usr/src), compile-neutral
(`fix_base_cc.log` vs `fix_patched_cc.log`: identical first error — the
pre-existing vkernel-env `errno.h` breakage documented in DF-3054).
Live vkernel boot not available on this guest → boot validation not_testable
(same as DF-3054).
