#include <sys/ioctl.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>

#define PATHMAX 1024
struct devfs_rule_ioctl {
	unsigned long rule_type;
	unsigned long rule_cmd;
	char mntpoint[PATHMAX];
	char name[PATHMAX];
	char linkname[PATHMAX];
	unsigned long dev_type;
	unsigned short mode;
	unsigned int uid;
	unsigned int gid;
};
#define DEVFS_RULE_NAME   0x01
#define DEVFS_RULE_LINK   0x01
#define DEVFS_RULE_ADD    _IOWR('d', 221, struct devfs_rule_ioctl)
#define DEVFS_RULE_APPLY  _IOWR('d', 222, struct devfs_rule_ioctl)
#define DEVFS_RULE_CLEAR  _IOWR('d', 223, struct devfs_rule_ioctl)
#define DEVFS_RULE_RESET  _IOWR('d', 224, struct devfs_rule_ioctl)

static void fill(struct devfs_rule_ioctl *r, const char *mnt,
		 const char *name, const char *link)
{
	memset(r, 0, sizeof(*r));
	strlcpy(r->mntpoint, mnt, sizeof(r->mntpoint));
	strlcpy(r->name, name, sizeof(r->name));
	strlcpy(r->linkname, link, sizeof(r->linkname));
}

int main(int argc, char **argv)
{
	struct devfs_rule_ioctl r;
	int fd, i, iters, mode;
	const char *mnt = "/mnt/dt";

	iters = (argc > 1) ? atoi(argv[1]) : 300;
	mode = (argc > 2) ? atoi(argv[2]) : 1;	/* 1 = with RESET (bug), 0 = control */

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

	/* add a link rule: on mount /mnt/dt, node "vn0" gets a link "leaklink" */
	fill(&r, mnt, "vn0", "leaklink");
	r.rule_type = DEVFS_RULE_NAME;
	r.rule_cmd = DEVFS_RULE_LINK;
	if (ioctl(fd, DEVFS_RULE_ADD, &r)) { perror("ADD"); return 1; }

	for (i = 0; i < iters; i++) {
		/*
		 * mount: node creation auto-applies the rule -> Nlink created,
		 *        target->nlinks = 1
		 */
		if (system("mount -t devfs df3006 /mnt/dt 2>/dev/null") != 0) {
			perror("mount"); return 1;
		}
		if (mode) {
			/*
			 * reset: devfs_rule_reset_node does --nlinks (1->0),
			 * then devfs_gc->devfs_unlinkp does nlinks-- (0->SIZE_MAX)
			 */
			if (ioctl(fd, DEVFS_RULE_RESET, &r)) { perror("RESET"); return 1; }
		}
		/*
		 * umount: reaper calls devfs_freep(vn0 node); nlinks != 0
		 * -> DEVFS_NLINKSWAIT -> node + name leaked forever
		 */
		if (system("umount /mnt/dt 2>/dev/null") != 0) { perror("umount"); return 1; }
	}

	/* clean the rule out of the kernel's rule list */
	fill(&r, mnt, "vn0", "leaklink");
	if (ioctl(fd, DEVFS_RULE_CLEAR, &r)) { perror("CLEAR"); return 1; }
	return 0;
}
