/*
 * DF-2568 — aggressive hammer2 flush NULL-deref race (v3: population churn).
 *
 * Approach: repeatedly build up a large population of dirty files in a shared
 * directory, then BURST-delete many of them while concurrently calling sync().
 * The sync triggers a topology-wide flush; the burst unlinks race the flush's
 * unlock/relock windows. Population is rebuilt each round to keep chains fresh
 * and dirty.
 *
 * Also interleaves mkdir/rmdir to churn directory chains, and does large writes
 * to force indirect-block chains (deeper flush trees = more unlock/relock
 * windows at flush.c:662-665).
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <errno.h>

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

#define BLKSIZE 8192

/* Population worker: creates and writes files to dirty chains, then
 * alternates between unlinking and recreating them. */
static void pop_worker(int id, const char *base, long rounds, int popsize)
{
    char path[640];
    char buf[BLKSIZE];
    int r, i;

    memset(buf, (char)(id + 0x41), sizeof(buf));
    for (r = 0; r < rounds && !stop; r++) {
        /* create popsize files, write multi-block (dirty data + indirect) */
        for (i = 0; i < popsize; i++) {
            int fd, b;
            snprintf(path, sizeof(path), "%s/p_%d_%ld_%d", base, id, r, i);
            fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0666);
            if (fd < 0) continue;
            for (b = 0; b < 8; b++) {
                buf[0] = (char)(id ^ r ^ i ^ b);
                if (write(fd, buf, sizeof(buf)) != sizeof(buf)) break;
            }
            close(fd);
        }
        /* now unlink them (races any concurrent flush) */
        for (i = 0; i < popsize; i++) {
            snprintf(path, sizeof(path), "%s/p_%d_%ld_%d", base, id, r, i);
            unlink(path);
        }
    }
    _exit(0);
}

/* Directory churn worker: mkdir/rmdir to churn directory chains */
static void dir_worker(int id, const char *base, long rounds)
{
    char path[640];
    long r;
    for (r = 0; r < rounds && !stop; r++) {
        snprintf(path, sizeof(path), "%s/d_%d_%ld", base, id, r);
        if (mkdir(path, 0777) == 0) {
            /* create a file inside to dirty the dir chain */
            char fp[700];
            int fd;
            snprintf(fp, sizeof(fp), "%s/f", path);
            fd = open(fp, O_CREAT|O_RDWR|O_TRUNC, 0666);
            if (fd >= 0) {
                write(fd, path, sizeof(path));
                fsync(fd);
                close(fd);
            }
            unlink(fp);
            rmdir(path);
        }
    }
    _exit(0);
}

/* Sync hammer: call sync() continuously to drive the flusher */
static void sync_worker(int id, long rounds)
{
    long r;
    for (r = 0; r < rounds && !stop; r++)
        sync();
    _exit(0);
}

int main(int argc, char **argv)
{
    const char *base = NULL;
    int npop = 12, ndir = 8, nsync = 4;
    int popsize = 40;
    long rounds = 5000000;
    int timeout = 0, opt;

    while ((opt = getopt(argc, argv, "d:p:n:i:s:t:r:")) != -1) {
        switch (opt) {
        case 'd': base = optarg; break;
        case 'p': npop = atoi(optarg); break;
        case 'n': ndir = atoi(optarg); break;
        case 'i': popsize = atoi(optarg); break;
        case 's': nsync = atoi(optarg); break;
        case 't': timeout = atoi(optarg); break;
        case 'r': rounds = atol(optarg); break;
        default:
            fprintf(stderr,"usage: %s -d dir [-p pop] [-n dir] [-s sync] "
                    "[-i popsize] [-t sec] [-r rounds]\n", argv[0]);
            return 2;
        }
    }
    if (!base) { fprintf(stderr,"%s: need -d dir\n", argv[0]); return 2; }

    fprintf(stderr,
        "DF-2568 v3 race: base=%s pop=%d dir=%d sync=%d popsize=%d "
        "rounds=%ld timeout=%d\n",
        base, npop, ndir, nsync, popsize, rounds, timeout);

    signal(SIGALRM, on_alarm);
    if (timeout > 0) alarm((unsigned)timeout);

    int nkids = npop + ndir + nsync;
    pid_t *kids = calloc(nkids, sizeof(pid_t));
    if (!kids) { perror("calloc"); return 1; }
    int k, ki = 0;

    for (k = 0; k < npop; k++) {
        pid_t p = fork();
        if (p < 0) { perror("fork"); return 1; }
        if (p == 0) pop_worker(k, base, rounds, popsize);
        kids[ki++] = p;
    }
    for (k = 0; k < ndir; k++) {
        pid_t p = fork();
        if (p < 0) { perror("fork"); return 1; }
        if (p == 0) dir_worker(k, base, rounds);
        kids[ki++] = p;
    }
    for (k = 0; k < nsync; k++) {
        pid_t p = fork();
        if (p < 0) { perror("fork"); return 1; }
        if (p == 0) sync_worker(k, rounds);
        kids[ki++] = p;
    }

    for (k = 0; k < nkids; k++)
        if (kids[k] > 0) waitpid(kids[k], NULL, 0);

    fprintf(stderr, "DF-2568 v3: all children exited (no panic)\n");
    return 0;
}
