/*
 * DF-0001 trigger (ESTALE variant). Uses ftruncate(2) on an open fd whose
 * server-side filehandle has been invalidated (file deleted+recreated on the
 * server while we hold the fd). This forces VOP_GETATTR_FP to issue a fresh
 * RPC (attribute cache aged out) against a STALE filehandle, so the server
 * returns NFSERR_STALE and nfs_getattr() propagates ESTALE -- unlike a
 * dead-server transport failure, which the DragonFly client papers over with
 * cached/local attributes. With vfs_quota_enabled=1 + INVARIANTS, the
 *
 *   KASSERT(error == 0, ("kern_ftruncate(): VOP_GETATTR didn't return 0"));
 *
 * at sys/kern/vfs_syscalls.c:4113 then fires -> kernel panic.
 *
 * Choreography (harness):
 *   1. (server UP) ./estale_trig /mnt/estale_target   <- opens fd, prints READY, sleeps
 *   2. harness: on server, rm /export/estale_target; touch /export/estale_target
 *      (invalidates the client fd's filehandle); wait for attr cache to age.
 *   3. process wakes, fstat() (diagnostic: expect ESTALE), ftruncate() (KASSERT).
 */
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <err.h>

int
main(int argc, char **argv)
{
	const char *path = argc > 1 ? argv[1] : "/mnt/estale_target";
	int fd;
	struct stat st;

	fd = open(path, O_RDWR);
	if (fd < 0)
		err(1, "open %s", path);
	printf("READY fd=%d path=%s -- invalidate server-side handle now\n", fd, path);
	fflush(stdout);

	/* harness invalidates handle here; then we age out the attr cache */
	sleep(8);

	errno = 0;
	if (fstat(fd, &st) < 0)
		warn("DIAG fstat (VOP_GETATTR) returned error: errno");
	else
		printf("DIAG fstat ok: size=%lld\n", (long long)st.st_size);
	fflush(stdout);

	errno = 0;
	if (ftruncate(fd, 0) < 0)
		warn("FTRUNCATE returned error (if GETATTR returned error this line is UNREACHED -- KASSERT panics first): errno");
	else
		printf("FTRUNCATE returned 0\n");
	fflush(stdout);

	close(fd);
	return 0;
}
