/*
 * Isolated reproducer for the panic in PR 282596, fixed by D58178, as a bare
 * syscall sequence: nmount(2) of a single-file nullfs mount, execve(2) of the
 * mount point, rename(2) of the containing directory, then kern.proc.pathname
 * on the resulting process.
 *
 * cc -o filemount_panic filemount_panic.c
 * ./filemount_panic			# as root, on an INVARIANTS kernel
 *
 *	panic: condition vp->v_type == VDIR || VN_IS_DOOMED(vp) not met
 *	       at ../../../kern/vfs_cache.c:NNNN (vn_fullpath_dir)
 *	vn_fullpath_dir()
 *	vn_fullpath_hardlink()
 *	proc_get_binpath()
 *	sysctl_kern_proc_pathname()
 *
 * The mount root of a single-file nullfs mount is a VREG vnode with VV_ROOT
 * set, so namei() has no directory to hand back as ni_dvp and returns the
 * covered vnode, which for a file mount is itself a regular file.  execve(2)
 * stores that as p_textdvp; the sysctl below reaches vn_fullpath_hardlink(),
 * which restarts the walk from p_textdvp assuming it is a directory, and
 * vn_fullpath_dir() then trips VNPASS(vp->v_type == VDIR || VN_IS_DOOMED(vp)).
 *
 * vn_fullpath_hardlink() only reaches vn_fullpath_dir() once the lock-free
 * namecache reverse walk it tries first has failed, so the cover file must not
 * be in the namecache.  The rename(2) evicts it: cache_vop_rename() purges the
 * renamed directory, dropping the entries that name its children.
 */

#include <sys/param.h>
#include <sys/mount.h>
#include <sys/sysctl.h>
#include <sys/uio.h>

#include <err.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static void
opt(struct iovec *iov, int *n, const char *name, const char *val)
{
	iov[*n].iov_base = __DECONST(char *, name);
	iov[(*n)++].iov_len = strlen(name) + 1;
	iov[*n].iov_base = __DECONST(char *, val);
	iov[(*n)++].iov_len = strlen(val) + 1;
}

int
main(void)
{
	char dir[] = "/tmp/filemount.XXXXXX";
	char cover[MAXPATHLEN], newdir[MAXPATHLEN], path[MAXPATHLEN];
	struct iovec iov[6];
	size_t len;
	pid_t pid;
	int mib[4], n, fd;

	if (mkdtemp(dir) == NULL)
		err(1, "mkdtemp");
	snprintf(cover, sizeof(cover), "%s/cover", dir);
	snprintf(newdir, sizeof(newdir), "%s.renamed", dir);

	/* The file to mount over: a regular file with one hardlink. */
	if ((fd = open(cover, O_WRONLY | O_CREAT, 0755)) < 0)
		err(1, "open %s", cover);
	close(fd);

	n = 0;
	opt(iov, &n, "fstype", "nullfs");
	opt(iov, &n, "fspath", cover);
	opt(iov, &n, "target", "/bin/sleep");
	if (nmount(iov, n, 0) != 0)
		err(1, "nmount /bin/sleep over %s", cover);
	printf("mounted /bin/sleep over %s\n", cover);

	switch ((pid = fork())) {
	case -1:
		err(1, "fork");
	case 0:
		/* execve() stores the covered vnode as p_textdvp. */
		execl(cover, "cover", "300", NULL);
		err(1, "execl %s", cover);
	}
	sleep(1);

	/* Evict the cover file's namecache entry. */
	if (rename(dir, newdir) != 0)
		err(1, "rename %s", dir);

	printf("querying kern.proc.pathname for pid %d\n", (int)pid);
	fflush(stdout);

	mib[0] = CTL_KERN;
	mib[1] = KERN_PROC;
	mib[2] = KERN_PROC_PATHNAME;
	mib[3] = pid;
	len = sizeof(path);
	if (sysctl(mib, 4, path, &len, NULL, 0) != 0)
		warn("sysctl kern.proc.pathname");
	else
		printf("pathname = %s\n", path);
	return (0);
}
