/*
 * DF-2568 — aggressive hammer2 flush NULL-deref race (v2).
 *
 * Same bug as v1 but tuned for a tighter race window:
 *   - ALL workers share ONE directory, so a single flush of the dir races
 *     against many concurrent unlinks/recreates.
 *   - Workers also do rename() to create extra chain-topology churn.
 *   - Syncers call sync() and fsync() as fast as possible.
 *   - Files are written multi-block to force indirect-block chains (deeper
 *     flush trees = more chains traversing the :662-665 unlock window).
 */
#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; }

static void worker(int id, const char *base, long iters)
{
    char path[640], path2[640];
    char buf[8192];
    long i;

    memset(buf, (char)(id|0x40), sizeof(buf));
    for (i = 0; i < iters && !stop; i++) {
        int f, fd;

        snprintf(path, sizeof(path), "%s/%d_%ld", base, id, i);
        fd = open(path, O_RDWR|O_CREAT|O_TRUNC, 0666);
        if (fd < 0) continue;
        /* write several blocks -> dirties data + indirect chains */
        for (f = 0; f < 4; f++)
            if (write(fd, buf, sizeof(buf)) != (ssize_t)sizeof(buf)) break;
        fsync(fd);
        close(fd);

        /* rename half the time (topology churn on the parent dir chain) */
        if (i & 1) {
            snprintf(path2, sizeof(path2), "%s/%d_%ld.r", base, id, i);
            rename(path, path2);
            unlink(path2);
        } else {
            unlink(path);
        }
    }
    _exit(0);
}

static void syncer(int id, const char *base, long iters)
{
    long i;
    char path[640];
    int fd;
    for (i = 0; i < iters && !stop; i++) {
        sync();
        /* drive per-inode flush via fsync on a scratch file */
        snprintf(path, sizeof(path), "%s/.s%d", base, id);
        fd = open(path, O_RDWR|O_CREAT|O_TRUNC, 0666);
        if (fd >= 0) { fsync(fd); close(fd); unlink(path); }
    }
    _exit(0);
}

int main(int argc, char **argv)
{
    const char *base = NULL;
    int nworkers = 20, nsyncers = 6;
    long iters = 5000000;
    int timeout = 0, opt;

    while ((opt = getopt(argc, argv, "d:w:s:i:t:")) != -1) {
        switch (opt){
        case 'd': base=optarg; break;
        case 'w': nworkers=atoi(optarg); break;
        case 's': nsyncers=atoi(optarg); break;
        case 'i': iters=atol(optarg); break;
        case 't': timeout=atoi(optarg); break;
        default: fprintf(stderr,"usage: %s -d dir [-w N] [-s N] [-i N] [-t sec]\n",argv[0]); return 2;
        }
    }
    if (!base){ fprintf(stderr,"%s: need -d dir\n",argv[0]); return 2; }

    fprintf(stderr,"DF-2568 v2 race: base=%s workers=%d syncers=%d iters=%ld timeout=%d\n",
            base, nworkers, nsyncers, iters, timeout);
    signal(SIGALRM, on_alarm);
    if (timeout>0) alarm((unsigned)timeout);

    int nkids = nworkers + nsyncers;
    pid_t *kids = calloc(nkids, sizeof(pid_t));
    if (!kids){perror("calloc");return 1;}
    int k;
    for (k=0;k<nworkers;k++){ pid_t p=fork(); if(p<0){perror("fork");return 1;} if(p==0)worker(k,base,iters); kids[k]=p; }
    for (k=0;k<nsyncers;k++){ pid_t p=fork(); if(p<0){perror("fork");return 1;} if(p==0)syncer(k,base,iters); kids[nworkers+k]=p; }
    for (k=0;k<nkids;k++) if(kids[k]>0) waitpid(kids[k],NULL,0);
    fprintf(stderr,"DF-2568 v2: all children exited (no panic)\n");
    return 0;
}
