/*
 * vkbd_unit_leak.c — clean reproduction of the vkbd devfs clone-bitmap
 * unit leak (sys/dev/misc/vkbd/vkbd.c).
 *
 * vkbd_dev_clone() (vkbd.c:225) allocates a clone unit from
 * DEVFS_CLONE_BITMAP(vkbd) with limit=0 (unbounded), but vkbd_dev_close()
 * never calls devfs_clone_bitmap_put() — destroy_dev() removes the node,
 * yet the unit number is consumed forever.
 *
 * Demonstration: record the highest vkbdctl unit in use, run N
 * open/write/close cycles (each fully closing its fd), then record the
 * next unit handed out. The unit number grows by exactly N despite every
 * device having been closed.
 */
#include <sys/fcntl.h>
#include <dirent.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

static int
maxunit(void)
{
	DIR *d = opendir("/dev");
	struct dirent *de;
	int best = -1;

	if (d == NULL) return (-1);
	while ((de = readdir(d)) != NULL) {
		int u;
		if (sscanf(de->d_name, "vkbdctl%d", &u) == 1 && u > best)
			best = u;
	}
	closedir(d);
	return (best);
}

int
main(void)
{
	int before, after, i;
	int two[2] = {0x1e, 0x9e};

	before = maxunit();
	printf("highest vkbdctl unit before: %d\n", before);
	fflush(stdout);

	for (i = 0; i < 1000; i++) {
		int fd = open("/dev/vkbdctl", O_RDWR);
		if (fd < 0) { perror("open"); return 1; }
		if (write(fd, two, 8) < 0) { /* drain may lag; ignore */ }
		close(fd);
	}

	/* open once more to reveal the next allocated unit */
	{
		int fd = open("/dev/vkbdctl", O_RDWR);
		if (fd < 0) { perror("open probe"); return 1; }
		after = maxunit();
		close(fd);
	}
	printf("vkbdctl unit handed out after 1000 full open/close cycles: %d\n",
	    after);
	printf("units leaked: %d (expected 0 if unit numbers were reused)\n",
	    after - before - 1000 > 0 ? after - before : after - before);
	fflush(stdout);
	return (0);
}
