/*
 * DF-5686 — udev_getdevs_ioctl() leaks the ctx.cdevs prop_array (and pins
 * every cdev's si_dict) on every UDEVPROP "getdevs" ioctl.
 *   sys/kern/kern_udev.c:985  ctx.cdevs = prop_array_create();
 *   sys/kern/kern_udev.c:1004 prop_dictionary_set(odict, "array", ctx.cdevs);  // retains
 *   sys/kern/kern_udev.c:1013 prop_object_release(odict);                     // -1
 *   -> ctx.cdevs keeps its own +1 reference forever (prop_dictionary_set does
 *      NOT transfer ownership).
 *
 * /dev/udev is 0600 root:wheel (kern_udev.c:1039-1041), so this is driven
 * as root — but devd(8) performs the same ioctl on default systems.
 *
 * Build: cc -O -o udev_getdevs_leak udev_getdevs_leak.c
 * Run:   ./udev_getdevs_leak /dev/udev <iterations>
 * Success criterion: vmstat -m "prop array" InUse grows linearly with the
 * iteration count (one leaked array of N cdev-pointers per call).
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>

struct plistref {
	void   *pref_plist;
	size_t  pref_len;
};

#ifndef UDEVPROP
#define UDEVPROP _IOWR('U', 0xBA, struct plistref)
#endif

static const char xml[] =
	"<plist><dict><key>command</key><string>getdevs</string></dict></plist>";

int
main(int argc, char **argv)
{
	const char *dev = argc > 1 ? argv[1] : "/dev/udev";
	long iters = argc > 2 ? atol(argv[2]) : 500;
	int fd, i;
	struct plistref pref;
	char *buf;

	fd = open(dev, O_RDWR);
	if (fd < 0) { perror(dev); return 1; }

	buf = strdup(xml);

	for (i = 0; i < iters; i++) {
		pref.pref_plist = buf;
		pref.pref_len = strlen(buf) + 1;
		if (ioctl(fd, UDEVPROP, &pref) != 0) {
			perror("ioctl(UDEVPROP)");
			return 1;
		}
		/* kernel copyout mmap'ed a region into us; release it */
		if (munmap(pref.pref_plist, pref.pref_len) != 0)
			perror("munmap");
	}
	printf("did %d UDEVPROP getdevs ioctls on %s\n", i, dev);
	close(fd);
	return 0;
}
