/*
 * DF-3158 hammer — deget() duplicate-denode SMP race.
 *
 * msdosfs_denode.c deget(): msdosfs_hashins() publishes the denode in the
 * global hash (releasing dehash_token) at :308, but de_refcnt is set to 1
 * only at :323, outside the token. A concurrent deget() for the same
 * (dev, dirclust, diroffset) key on another CPU sees the entry with
 * de_refcnt == 0, skips it (msdosfs_hashget :131), and inserts its own
 * second denode for the same dirent (msdosfs_hashins :174-183 only treats
 * refcnt>0 entries as collisions). Result: two vnodes/denodes for one
 * directory entry.
 *
 * Detection: both threads open the SAME file after a barrier (racing
 * degets). T1 then ftruncates to a per-round value and T2 fstats. With a
 * single denode the size always agrees. With duplicated denodes T2 sees a
 * stale size forever after -- no timing needed post-hit.
 *
 * build: cc -O2 -pthread -o hammer hammer.c
 * usage: ./hammer /fat/RACEFILE seconds
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <pthread.h>

static volatile int stop;
static volatile long flag;	/* round# whose truncate has completed */
static volatile long hits, rounds;
static const char *path;

#define NT 6

static void *worker(void *arg)
{
	int me = (int)(intptr_t)arg;
	long local_hits = 0, local_rounds = 0;

	while (!__atomic_load_n(&stop, __ATOMIC_SEQ_CST)) {
		int fd = open(path, O_RDWR);
		if (fd < 0) { perror("open"); break; }
		if (me == 0) {
			long sz = 100000 + (local_rounds & 1) * 4096;
			ftruncate(fd, sz);
			__atomic_store_n(&flag, local_rounds, __ATOMIC_SEQ_CST);
		} else {
			long fl1 = __atomic_load_n(&flag, __ATOMIC_SEQ_CST);
			struct stat st;
			if (fstat(fd, &st) == 0) {
				long fl2 = __atomic_load_n(&flag, __ATOMIC_SEQ_CST);
				if (fl1 == fl2) {
					long want = 100000 + (fl1 & 1) * 4096;
					if ((long)st.st_size != want) {
						/* candidate: confirm by persistent divergence */
						int fd2 = open(path, O_RDWR);
						int ok = 0;
						if (fd2 >= 0) {
							long sz2 = (st.st_size == 100000) ? 104096 : 100000;
							if (ftruncate(fd2, sz2) == 0) {
								struct stat st2;
								if (fstat(fd, &st2) == 0 &&
								    (long)st2.st_size == (long)st.st_size &&
								    (long)st.st_size != sz2)
									ok = 1;	/* never converged */
							}
							close(fd2);
						}
						fprintf(stderr,
							"%s t%d round %ld: size %ld want %ld\n",
							ok ? "[CONFIRMED-DUP]" : "[transient]",
							me, local_rounds, (long)st.st_size, want);
						if (ok) local_hits++;
					}
				}
			}
		}
		close(fd);
		local_rounds++;
		__atomic_add_fetch(&rounds, 1, __ATOMIC_SEQ_CST);
	}
	__atomic_add_fetch(&hits, local_hits, __ATOMIC_SEQ_CST);
	return NULL;
}

int main(int argc, char **argv)
{
	if (argc < 3) { fprintf(stderr, "usage: %s file seconds\n", argv[0]); return 1; }
	path = argv[1];
	int secs = atoi(argv[2]);

	int fd = open(path, O_RDWR | O_CREAT, 0644);
	if (fd < 0) { perror("create"); return 1; }
	ftruncate(fd, 100000);
	close(fd);

	pthread_t a, b;
	pthread_create(&a, NULL, worker, (void*)0);
	pthread_create(&b, NULL, worker, (void*)1);
	sleep(secs);
	__atomic_store_n(&stop, 1, __ATOMIC_SEQ_CST);
	pthread_join(a, NULL);
	pthread_join(b, NULL);
	printf("rounds(thread0 approx)=%ld hits=%ld -> %s\n",
	       __atomic_load_n(&rounds, __ATOMIC_SEQ_CST), __atomic_load_n(&hits, __ATOMIC_SEQ_CST),
	       __atomic_load_n(&hits, __ATOMIC_SEQ_CST) ? "DUPLICATE DENODES OBSERVED" : "no hit");
	return 0;
}
