/*
 * DF-4958 trigger KLD: drives the *unmodified* tree code in
 * /usr/src/sys/dev/misc/led/led.c (compiled into this .ko verbatim via
 * SRCS=led.c) through both leak paths of led_parse():
 *
 *   1. kernel API path : hw.ledleak.setn=N  -> N x led_set("leaktest", "s")
 *      (led.c:262 led_parse -> empty spec -> led.c:202-206 leaks the sbuf)
 *   2. cdev path       : userland wr(1) writes "sz" to /dev/led/leaktest
 *      (led.c:222 led_write -> led_parse -> same leak)
 *
 * hw.ledleak.create=1 creates the LED via led_create() so the real
 * /dev/led/leaktest node exists (mode 0600, root) — exactly the node
 * acpi_asus(4)/acpi_thinkpad(4) create on real hardware.
 *
 * Every leaked iteration costs sizeof(struct sbuf) (48 bytes) + the
 * initial 16-byte buffer (subr_sbuf.c:53-54,79,137: SBMALLOC/M_SBUF,
 * SBUF_MINEXTENDSIZE=16) = 64 bytes of M_SBUF per call, none of which
 * is ever sbuf_delete()d.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/conf.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <dev/misc/led/led.h>

static struct cdev *leak_dev;

static void
noop_led(void *priv __unused, int on __unused)
{
	/* the LED framework only needs a callable target */
}

static int
ledleak_create(SYSCTL_HANDLER_ARGS)
{
	int v = 0, error;

	error = sysctl_handle_int(oidp, &v, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	if (leak_dev == NULL) {
		leak_dev = led_create(noop_led, NULL, "leaktest");
		kprintf("ledleak: created /dev/led/leaktest (%p)\n", leak_dev);
	}
	return (0);
}

static int
ledleak_setn(SYSCTL_HANDLER_ARGS)
{
	int v = 0, error, i;

	error = sysctl_handle_int(oidp, &v, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	kprintf("ledleak: calling led_set(\"leaktest\", \"s\") x %d "
	    "(each leaks one sbuf: led_parse led.c:202-206)\n", v);
	for (i = 0; i < v; i++)
		led_set("leaktest", "s");
	kprintf("ledleak: done: %d sbufs leaked\n", v);
	return (0);
}

static int
ledleak_modevent(module_t mod, int type, void *arg)
{
	static struct sysctl_ctx_list ctx;

	switch (type) {
	case MOD_LOAD:
		sysctl_ctx_init(&ctx);
		SYSCTL_ADD_PROC(NULL, SYSCTL_STATIC_CHILDREN(_hw), OID_AUTO,
		    "ledleak_create", CTLTYPE_INT | CTLFLAG_RW,
		    0, 0, ledleak_create, "I", "create /dev/led/leaktest");
		SYSCTL_ADD_PROC(NULL, SYSCTL_STATIC_CHILDREN(_hw), OID_AUTO,
		    "ledleak_setn", CTLTYPE_INT | CTLFLAG_RW,
		    0, 0, ledleak_setn, "I",
		    "call led_set(name,\"s\") N times (leak N sbufs)");
		kprintf("ledleak: loaded (tree led.c compiled in)\n");
		return (0);
	case MOD_UNLOAD:
		if (leak_dev != NULL)
			led_destroy(leak_dev);
		sysctl_ctx_free(&ctx);
		return (0);
	}
	return (0);
}
DEV_MODULE(ledleak, ledleak_modevent, NULL);
