/*
 * DF-2803 / DF-2804 harness: udev(4) event-queue exerciser (root).
 *
 * Modes:
 *   stall <sec>   - open /dev/udev, issue UDEVPROP "getdevs" (this *initiates*
 *                   the reader: marker inserted at queue head), then sleep with
 *                   the fd held open, then close WITHOUT reading anything.
 *   read <sec>    - initiate, then blocking-read events for <sec> seconds,
 *                   print count + first event (control: drains the queue).
 *   probe         - initiate, close immediately, no churn (control).
 *
 * The getdevs ioctl protocol (kernel side prop_dictionary_copyout_ioctl)
 * mmaps the reply into userspace and returns it via the plistref; we munmap it.
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/udev.h>
#include <libprop/proplib.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#define PAGE_ROUND(l)	(((l) + 4095L) & ~4095L)

static int
udev_initiate(int fd)
{
	prop_dictionary_t d;
	struct plistref pr;
	char *xml;
	int rc = -1;

	d = prop_dictionary_create();
	if (d == NULL)
		return -1;
	if (prop_dictionary_set_cstring(d, "command", "getdevs") == false) {
		prop_object_release(d);
		return -1;
	}
	xml = prop_dictionary_externalize(d);
	prop_object_release(d);
	if (xml == NULL) {
		fprintf(stderr, "harness: externalize failed\n");
		return -1;
	}

	memset(&pr, 0, sizeof(pr));
	pr.pref_plist = xml;
	pr.pref_len = strlen(xml) + 1;

	if (ioctl(fd, UDEVPROP, &pr) < 0) {
		perror("harness: ioctl(UDEVPROP, getdevs)");
		goto out;
	}
	/*
	 * Success: kernel replaced pr with an mmap'd reply buffer.
	 * munmap it; we don't need the device list itself.
	 */
	if (pr.pref_plist != NULL)
		munmap(pr.pref_plist, PAGE_ROUND(pr.pref_len));
	rc = 0;
out:
	free(xml);
	return rc;
}

static void
onalarm(int sig __unused)
{
	/* just EINTR the blocking read */
}

int
main(int argc, char **argv)
{
	char buf[65536];
	int fd, sec, n, count = 0;
	long long bytes = 0;

	if (argc < 2) {
		fprintf(stderr, "usage: %s stall <sec>|read <sec>|probe\n",
		    argv[0]);
		return 2;
	}

	fd = open("/dev/udev", O_RDWR);
	if (fd < 0) {
		perror("harness: open /dev/udev");
		return 1;
	}
	if (udev_initiate(fd) < 0) {
		close(fd);
		return 1;
	}

	if (strcmp(argv[1], "probe") == 0) {
		/* initiate + immediate close, no churn: control */
	} else if (strcmp(argv[1], "stall") == 0) {
		sec = atoi(argv[2]);
		printf("harness: initiated; stalling %ds with fd held...\n",
		    sec);
		fflush(stdout);
		sleep(sec);
		printf("harness: closing WITHOUT reading\n");
	} else if (strcmp(argv[1], "read") == 0) {
		struct sigaction sa;
		sec = atoi(argv[2]);
		memset(&sa, 0, sizeof(sa));
		sa.sa_handler = onalarm;
		sigemptyset(&sa.sa_mask);
		sigaction(SIGALRM, &sa, NULL);
		alarm(sec);
		for (;;) {
			n = read(fd, buf, sizeof(buf) - 1);
			if (n < 0) {
				if (errno == EINTR)
					break;
				perror("harness: read");
				break;
			}
			buf[n] = '\0';
			count++;
			bytes += n;
			if (count == 1)
				printf("harness: first event: %s\n", buf);
		}
		printf("harness: read %d events (%lld bytes) in %ds\n",
		    count, bytes, sec);
	} else if (strcmp(argv[1], "stallcount") == 0) {
		/*
		 * Stall like "stall", but before closing, switch to
		 * non-blocking and count/drain the backlog so we can
		 * quantify how much accumulated (draining also releases
		 * the dicts properly, isolating this from DF-2804).
		 */
		int fl;
		sec = atoi(argv[2]);
		printf("harness: initiated; stalling %ds with fd held...\n",
		    sec);
		fflush(stdout);
		sleep(sec);
		fl = fcntl(fd, F_GETFL, 0);
		fcntl(fd, F_SETFL, fl | O_NONBLOCK);
		for (;;) {
			n = read(fd, buf, sizeof(buf) - 1);
			if (n < 0) {
				if (errno == EWOULDBLOCK)
					break;
				perror("harness: drain read");
				break;
			}
			buf[n] = '\0';
			count++;
			bytes += n;
		}
		printf("harness: backlog drained: %d events (%lld bytes)\n",
		    count, bytes);
	} else {
		fprintf(stderr, "harness: unknown mode %s\n", argv[1]);
		close(fd);
		return 2;
	}

	close(fd);
	printf("harness: fd closed\n");
	return 0;
}
