/*
 * DF-4533 — drm_open() leaks one drm_device kref per successful open
 * ================================================================
 *
 * Protocol on Linux (which this code was ported from):
 *   drm_open()      : minor = drm_minor_acquire(id)   [+1 dev ref]
 *                     ... on success the ref is intentionally kept
 *                     (it belongs to the open file)
 *   drm_release()   : minor = file_priv->minor;
 *                     drm_minor_release(minor);        [-1 dev ref]
 *
 * On DragonFly the live release path is drm_close(), which instead does:
 *   drm_close()     : minor = drm_minor_acquire(id)   [+1 dev ref]
 *                     ... teardown ...
 *                     drm_minor_release(minor);        [-1 dev ref]
 * i.e. it releases ONLY its own acquire.  The +1 taken by drm_open() at
 * drm_file.c:325 is never dropped by any live code path:
 *   - drm_release() (drm_file.c:595) returns 0 immediately on __DragonFly__
 *   - drm_cdevpriv_dtor() (drm_drv.c:1194), which does device_unbusy and
 *     would be the FreeBSD-style release, is never registered
 *     (no devfs_set_cdevpriv() caller exists in sys/dev/drm)
 *
 * Consequence: dev->ref (kref) grows by 1 per open/close cycle forever.
 * drm_dev_release()/drm_dev_fini() can never run, so at driver unload the
 * whole drm_device (plus everything hanging off it) leaks, and the
 * unbounded int refcount can in principle be wrapped by ~2^31 cycles.
 *
 * trigger: open/close card0 forever; with debugging (hw.dri.debug=2 or
 * dev.ddb tracing) each close shows the kref never returning to its
 * pre-open value. Hardware-gated on the QEMU audit guest.
 */
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int
main(int argc, char **argv)
{
	const char *card = "/dev/dri/card0";
	long cycles = argc > 1 ? atol(argv[1]) : 100000;
	long i;
	int fd = open(card, O_RDWR);

	if (fd < 0) {
		printf("%s: %s — no DRM device on this system; the kref "
		       "accounting cannot be exercised here.\n",
		       card, strerror(errno));
		return (0);
	}
	printf("cycling open/close on %s: %ld iterations\n", card, cycles);
	for (i = 0; i < cycles; i++) {
		close(fd);
		fd = open(card, O_RDWR);
		if (fd < 0) {
			perror("reopen");
			return (1);
		}
	}
	close(fd);
	printf("done — on a vulnerable system each iteration leaked one "
	       "drm_device kref (drm_file.c:325 acquire never released; "
	       "drm_close:445 releases only its own)\n");
	return (0);
}
