/*
 * DF-0764 — Stale worklist_tail in add_to_worklist (FFS softdep)
 *
 * Stress harness v2: heavy concurrent create/unlink/stat churn on a softdep-
 * enabled FFS filesystem, intended to push num_on_worklist past
 * max_softdeps/10 (10000) and drive request_cleanup() ->
 * process_worklist_item(NULL,LK_NOWAIT). Half the workers hammer stat() to
 * keep vnodes locked so the LK_NOWAIT scan in process_worklist_item skips
 * D_DIRREM items at the head, raising the chance the *tail* item gets
 * selected+removed (which makes the static worklist_tail go stale -> orphan
 * chain -> softdep_flushfiles "looping" panic at unmount).
 *
 * The race is genuinely narrow (needs: worklist >10k items + several head
 * items being locked D_DIRREM + tail selected + concurrent add during the
 * FREE_LOCK..WORKITEM_FREE window). This harness is best-effort; the
 * authoritative evidence for DF-0764 is the code-level trace in VERDICT.md.
 *
 * Run as root on a softdep FFS mount.
 *   ./softdep_churn <mountpoint> <seconds> <nworkers>
 */
#include <sys/param.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <sys/wait.h>

static volatile sig_atomic_t stop = 0;
static void onterm(int s) { stop = 1; }

/* unlink churn: generates D_DIRREM workitems as fast as possible */
static int
churner(const char *mp, int idx)
{
    char dir[256];
    snprintf(dir, sizeof(dir), "%s/c%d", mp, idx);
    mkdir(dir, 0777);
    signal(SIGTERM, onterm);
    signal(SIGINT, onterm);
    long it = 0;
    while (!stop) {
        for (int i = 0; i < 64; i++) {
            char f[300];
            snprintf(f, sizeof(f), "%s/%d", dir, i);
            int fd = open(f, O_WRONLY|O_CREAT|O_TRUNC, 0644);
            if (fd >= 0) { write(fd, "z", 1); close(fd); }
        }
        for (int i = 0; i < 64; i++) {
            char f[300];
            snprintf(f, sizeof(f), "%s/%d", dir, i);
            unlink(f);
        }
        it++;
    }
    fprintf(stderr, "churner %d: %ld iterations\n", idx, it);
    return 0;
}

/* lock churn: keep vnodes locked via stat/open to make the LK_NOWAIT scan
 * skip head D_DIRREM items */
static int
locker(const char *mp, int idx)
{
    char dir[256];
    snprintf(dir, sizeof(dir), "%s/c%d", mp, idx % 4);
    signal(SIGTERM, onterm);
    signal(SIGINT, onterm);
    struct stat sb;
    long it = 0;
    while (!stop) {
        for (int i = 0; i < 64; i++) {
            char f[300];
            snprintf(f, sizeof(f), "%s/%d", dir, i);
            /* stat + open read -> brief vnode lock churn */
            if (stat(f, &sb) == 0) {
                int fd = open(f, O_RDONLY);
                if (fd >= 0) {
                    read(fd, f, 1);
                    close(fd);
                }
            }
            it++;
        }
    }
    fprintf(stderr, "locker %d: %ld iterations\n", idx, it);
    return 0;
}

int
main(int argc, char **argv)
{
    const char *mp = argc > 1 ? argv[1] : "/mnt/ffs";
    int secs  = argc > 2 ? atoi(argv[2]) : 60;
    int nw    = argc > 3 ? atoi(argv[3]) : 16;

    signal(SIGALRM, onterm);
    signal(SIGTERM, onterm);

    int half = nw / 2;
    int *pids = calloc(nw, sizeof(int));
    for (int i = 0; i < nw; i++) {
        pid_t p = fork();
        if (p == 0) {
            if (i < half) _exit(churner(mp, i));
            else          _exit(locker(mp, i - half));
        }
        pids[i] = p;
    }
    alarm(secs);
    /* parent: drive the syncer to drain the worklist & trigger request_cleanup */
    while (!stop) { sync(); usleep(20000); }
    /* signal all children */
    for (int i = 0; i < nw; i++) kill(pids[i], SIGTERM);
    for (int i = 0; i < nw; i++) waitpid(pids[i], NULL, 0);
    free(pids);
    printf("churn complete\n");
    return 0;
}
