/* DF-2564 sibling trigger -- DF-2560 exact path, herd edition.
 * 6 BLOCKING sender threads pile in ssb_wait; a periodic partial drain
 * clears SSB_STOP; the whole herd passes sosend's space check (STOP clear),
 * then serializes on so2->so_rcv token in uipc_send: first appends succeed,
 * later ones see ssb_space(peer)<=0 -> ssb_appendcontrol()==0 ->
 * the SOCK_STREAM SCM_RIGHTS branch drops control WITHOUT unp_dispose()
 * (uipc_usrreq.c:766-787 + cleanup :808-814): 8 leaked file refs each,
 * sendmsg() reports success.
 *
 * usage: scm_leak5 [rounds]
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include <signal.h>

#define NTH 4
static int sv[2], fds[8];
static volatile int stop_flag;
static long ops_ok[NTH], ops_fail[NTH];
static pthread_t th[NTH];
static char big[24576];

static int
sysctl_int(const char *name)
{
	int v;
	size_t l = sizeof(v);
	return (sysctlbyname(name, &v, &l, NULL, 0) < 0) ? -1 : v;
}

static void *
sender(void *arg)
{
	long id = (long)arg;
	struct msghdr msg;
	struct iovec iov;
	char cbuf[CMSG_SPACE(sizeof(fds))];
	struct cmsghdr *cm;

	memset(&msg, 0, sizeof(msg));
	iov.iov_base = big;
	iov.iov_len = 24000;
	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;
	msg.msg_control = cbuf;
	msg.msg_controllen = CMSG_SPACE(sizeof(fds));
	cm = CMSG_FIRSTHDR(&msg);
	cm->cmsg_len = CMSG_LEN(sizeof(fds));
	cm->cmsg_level = SOL_SOCKET;
	cm->cmsg_type = SCM_RIGHTS;
	memcpy(CMSG_DATA(cm), fds, sizeof(fds));

	while (!stop_flag) {
		ssize_t rc = sendmsg(sv[0], &msg, MSG_DONTWAIT);
		if (rc >= 0) ops_ok[id]++;
		else { ops_fail[id]++; usleep(50); }
	}
	return (NULL);
}

int
main(int argc, char **argv)
{
	int rounds = (argc > 1) ? atoi(argv[1]) : 200;
	int i, r, infl0, infl1, ok = 0, fail = 0;
	setvbuf(stdout, NULL, _IONBF, 0);
	char b[65536];

	infl0 = sysctl_int("net.local.inflight");
	printf("before: net.local.inflight=%d\n", infl0);

	for (i = 0; i < 8; i++) {
		char p[64];
		snprintf(p, sizeof(p), "/tmp/df2560e_%d", i);
		unlink(p);
		fds[i] = open(p, O_RDWR | O_CREAT, 0600);
		if (fds[i] < 0) { perror("open"); return 1; }
	}
	if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0) { perror("socketpair"); return 1; }
	{ struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = SIG_IGN;
	  sigaction(SIGUSR1, &sa, NULL); }
	fcntl(sv[1], F_SETFL, O_NONBLOCK);
	memset(big, 'D', sizeof(big));

	for (i = 0; i < NTH; i++)
		pthread_create(&th[i], NULL, sender, (void *)(long)i);

	/* rounds: partial drain to wake the herd */
	for (r = 0; r < rounds; r++) {
		usleep(1000);
		recv(sv[1], b, 8192, MSG_DONTWAIT);	/* small drain: keep rcv near-full */
	}
	stop_flag = 1;
	shutdown(sv[0], SHUT_RDWR);	/* break blocking sends with EPIPE */
	for (i = 0; i < NTH; i++)
		pthread_join(th[i], NULL);

	for (i = 0; i < NTH; i++) { ok += ops_ok[i]; fail += ops_fail[i]; }
	infl1 = sysctl_int("net.local.inflight");
	printf("senders: ok=%d fail=%d\n", ok, fail);
	printf("during : inflight=%d (delta %+d)\n", infl1, infl1 - infl0);

	for (;;) if (recv(sv[1], b, sizeof(b), MSG_DONTWAIT) <= 0) break;
	close(sv[0]); close(sv[1]);
	for (i = 0; i < 8; i++) close(fds[i]);
	infl1 = sysctl_int("net.local.inflight");
	printf("after close-all: inflight delta %+d\n", infl1 - infl0);
	if (infl1 - infl0 > 0)
		printf("DF2560: LEAK CONFIRMED: %d rights leaked permanently\n",
		    infl1 - infl0);
	else
		printf("DF2560: no leak this run\n");
	return 0;
}
