/*
 * scstress.c - concurrency stress for the syscons mode-change state machine
 * (scvidctl.c sc_set_text_mode) vs the tty output path (scstart/sc_puts).
 *
 * Claim under test: sc_set_text_mode() mutates/reallocates scp->vtb and
 * scp->history (free + kmalloc of the shadow screen buffer and the history
 * ring) while holding ONLY vga_token, while the output path writes those
 * same buffers holding ONLY syscons_lock -> no common lock -> UAF write /
 * torn-size OOB write window on every CONS_SET.
 *
 * Also churns the pixel-mode gate (KDRASTER/KD_PIXEL - expected ENODEV on
 * this guest: no VESA, text-only) and VT switching.
 */
#include <sys/ioctl.h>
#include <sys/consio.h>
#include <sys/fbio.h>
#include <fcntl.h>
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

static volatile int stop_flag;
static int secs = 60;
static int mode = 7;	/* 1=ioctl 2=writer 4=vt */

#define NVT 4

struct targ {
	int  vt;			/* VT number 2..5 -> /dev/ttyv1..4 */
	long ioctls, writes, eperm, enodev, eother;
};

static void *ioctl_thr(void *arg)
{
	struct targ *t = arg;
	char dev[32];
	int fd, i = 0, r;
	int modes[] = { M_VGA_C80x25, M_VGA_C80x50, M_VGA_C80x30, M_VGA_C80x60 };
	scr_size_t sz;

	snprintf(dev, sizeof(dev), "/dev/ttyv%d", t->vt - 1);
	fd = open(dev, O_RDWR | O_NONBLOCK);
	if (fd < 0) { perror(dev); return NULL; }
	memset(&sz, 0, sizeof(sz));
	sz.scr_size[0] = 80; sz.scr_size[1] = 25; sz.scr_size[2] = 16;

	while (!stop_flag) {
		int m = modes[i % 4];
		r = ioctl(fd, CONS_SET, &m);
		t->ioctls++;
		if (r < 0) {
			if (errno == ENODEV) t->enodev++;
			else if (errno == EPERM) t->eperm++;
			else t->eother++;
		}
		if (!(mode & 8)) {
			/* KD_TEXT1: another sc_set_text-family mutator */
			ioctl(fd, KDSETMODE, (void *)(uintptr_t)KD_TEXT1);
			t->ioctls++;
			/* pixel-mode gate: ENODEV on text-only adapter */
			ioctl(fd, KDRASTER, &sz);
			t->ioctls++;
		}
		i++;
	}
	close(fd);
	return NULL;
}

static void *writer_thr(void *arg)
{
	struct targ *t = arg;
	char dev[32];
	int fd;
	char buf[512];
	int i;

	snprintf(dev, sizeof(dev), "/dev/ttyv%d", t->vt - 1);
	fd = open(dev, O_WRONLY | O_NONBLOCK);
	if (fd < 0) { perror(dev); return NULL; }

	for (i = 0; i < (int)sizeof(buf) - 1; i++)
		buf[i] = (i % 16 == 15) ? '\n' : ('A' + (i % 26));
	buf[sizeof(buf) - 1] = '\0';

	while (!stop_flag) {
		ssize_t n = write(fd, buf, sizeof(buf) - 1);
		t->writes++;
		if (n < 0) {
			if (errno == ENODEV) t->enodev++;
			else if (errno == EPERM) t->eperm++;
			else t->eother++;
			usleep(100);
		}
	}
	close(fd);
	return NULL;
}

static void *vt_thr(void *arg)
{
	int fd, vt = 2;
	(void)arg;
	fd = open("/dev/ttyv1", O_RDWR | O_NONBLOCK);
	if (fd < 0) { perror("vt /dev/ttyv1"); return NULL; }
	while (!stop_flag) {
		ioctl(fd, VT_ACTIVATE, (void *)(uintptr_t)vt);
		vt = 2 + (vt - 1) % NVT;
		usleep(2000);
	}
	close(fd);
	return NULL;
}

int main(int argc, char **argv)
{
	struct targ targs[NVT];
	pthread_t io[NVT], wr[NVT], vt;
	int i;

	if (argc > 1) secs = atoi(argv[1]);
	mode = (argc > 2) ? atoi(argv[2]) : 7;
	setvbuf(stdout, NULL, _IONBF, 0);
	printf("scstress: %d vt, mode=%d (1=ioctl 2=writer 4=vt), %ds\n",
	       NVT, mode, secs);

	for (i = 0; i < NVT; i++) {
		memset(&targs[i], 0, sizeof(targs[i]));
		targs[i].vt = 2 + i;	/* ttyv1..ttyv4 */
		if (mode & 1 || mode & 8)
			pthread_create(&io[i], NULL, ioctl_thr, &targs[i]);
		if (mode & 2)
			pthread_create(&wr[i], NULL, writer_thr, &targs[i]);
	}
	if (mode & 4)
		pthread_create(&vt, NULL, vt_thr, NULL);

	sleep(secs);
	stop_flag = 1;
	for (i = 0; i < NVT; i++) {
		if (mode & 1 || mode & 8) pthread_join(io[i], NULL);
		if (mode & 2) pthread_join(wr[i], NULL);
	}
	if (mode & 4) pthread_join(vt, NULL);

	for (i = 0; i < NVT; i++)
		printf("ttyv%d: ioctls=%ld writes=%ld enodev=%ld eperm=%ld eother=%ld\n",
		       targs[i].vt - 1, targs[i].ioctls, targs[i].writes,
		       targs[i].enodev, targs[i].eperm, targs[i].eother);
	printf("scstress: done, guest still alive\n");
	return 0;
}
