/*
 * DF-5788 rigger v3 - coordinated stacks.
 *
 * K independent tmpfs stacks, each with a ~600-deep chain of 1-char
 * directory names (a getcwd() walk from the bottom hits ERANGE after
 * ~500 hops, and those hops contend with dounmount's cache_inval on the
 * same chain, stretching the walk into the ms range).
 *
 * flapper: dwells in stack i (walkers hammer walks through i's chain),
 *          then chdir()'s OUT and immediately marks stack i "fired".
 * churn:   for each fired stack: umount -f (clean: nobody pins it),
 *          remount, rebuild the chain, mark ready.
 * walkers: getcwd() in a tight loop (4 threads for lock contention).
 *
 * The walk-phase UAF needs: a walk in-flight across stack i when i's
 * struct mount is kfree()d.  Walks only start while the fd is deep, the
 * free only happens after the fd leaves - the in-flight walks (lengthened
 * by inval contention) bridge the gap.
 */
#include <sys/param.h>
#include <sys/mount.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
#include <time.h>

#define K 6
#define DEPTH 600

static char g_base[256];
static char g_out[256];
static char g_deep[K][1024];
static volatile int g_stop;
static volatile int st_fired[K];	/* flapper left it, churn may umount */
static volatile int st_ready[K];	/* churn rebuilt it */
static volatile long n_getcwd, n_enoent, n_err, n_flips, n_cycles;

static void
mkdir_p1(const char *p)
{
	if (mkdir(p, 0777) == 0 || errno == EEXIST)
		;
}

static int
build_chain(char *p, size_t len, int depth)
{
	size_t l = strlen(p);
	int i;

	for (i = 0; i < depth; i++) {
		if (l + 3 >= len)
			break;
		p[l++] = '/';
		p[l++] = 'a' + (i % 26);
		p[l] = 0;
		mkdir_p1(p);
	}
	return i;
}

static int
setup_stack(int i)
{
	char p[1024];

	snprintf(p, sizeof(p), "%s/s%d", g_base, i);
	mkdir_p1(p);
	if (mount("tmpfs", p, 0, NULL) && errno != EBUSY)
		return -1;
	snprintf(g_deep[i], sizeof(g_deep[i]), "%s", p);
	build_chain(g_deep[i], sizeof(g_deep[i]), DEPTH);
	return 0;
}

static void
teardown_stack(int i)
{
	char p[1024];

	snprintf(p, sizeof(p), "%s/s%d", g_base, i);
	unmount(p, MNT_FORCE);
}

static void *
thr_walker(void *arg)
{
	char buf[MAXPATHLEN];

	while (!g_stop) {
		if (getcwd(buf, sizeof(buf)) == NULL) {
			if (errno == ENOENT)
				n_enoent++;
			else
				n_err++;
		}
		n_getcwd++;
	}
	return NULL;
}

static void *
thr_flapper(void *arg)
{
	int i;
	useconds_t dwell = 400;

	while (!g_stop) {
		for (i = 0; i < K && !g_stop; i++) {
			if (!st_ready[i])
				continue;
			if (chdir(g_deep[i]) != 0)
				continue;
			usleep(dwell);		/* walkers hammer stack i */
			if (chdir(g_out) != 0)
				chdir("/");	/* pin off regardless */
			st_fired[i] = 1;	/* churn: umount it now */
			n_flips++;
			usleep(200);		/* let dounmount start */
		}
	}
	return NULL;
}

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

	while (!g_stop) {
		for (i = 0; i < K && !g_stop; i++) {
			if (!st_fired[i])
				continue;
			st_fired[i] = 0;
			st_ready[i] = 0;
			teardown_stack(i);
			if (setup_stack(i) == 0) {
				n_cycles++;
				st_ready[i] = 1;
			}
		}
		usleep(50);
	}
	return NULL;
}

int
main(int argc, char **argv)
{
	pthread_t w[4], tf, tc;
	int secs = 120, i;

	if (argc > 1)
		snprintf(g_base, sizeof(g_base), "%s", argv[1]);
	else
		snprintf(g_base, sizeof(g_base), "/tmp/df5788");
	if (argc > 2)
		secs = atoi(argv[2]);

	mkdir(g_base, 0777);
	snprintf(g_out, sizeof(g_out), "%s/out", g_base);
	mkdir(g_out, 0777);
	for (i = 0; i < K; i++) {
		if (setup_stack(i) != 0) {
			perror("setup_stack");
			return 2;
		}
		st_ready[i] = 1;
	}
	printf("K=%d DEPTH=%d base=%s\n", K, DEPTH, g_base);
	for (i = 0; i < 4; i++)
		pthread_create(&w[i], NULL, thr_walker, NULL);
	pthread_create(&tf, NULL, thr_flapper, NULL);
	pthread_create(&tc, NULL, thr_churn, NULL);

	sleep(secs);
	g_stop = 1;
	for (i = 0; i < 4; i++)
		pthread_join(w[i], NULL);
	pthread_join(tf, NULL);
	pthread_join(tc, NULL);
	printf("getcwd=%ld enoent=%ld err=%ld flips=%ld cycles=%ld\n",
	       n_getcwd, n_enoent, n_err, n_flips, n_cycles);
	for (i = 0; i < K; i++)
		teardown_stack(i);
	printf("DONE rc=0\n");
	return 0;
}
