/* DF-4800 trigger: N processes tight-looping the VINUM_SETSTATE
 * (object_up) ioctl that drives revive_block() on the same subdisk.
 *
 * revive_block() (sys/dev/raid/vinum/vinumrevive.c) reads sd->revived
 * at entry (line 89: size/offset computation) and advances it only
 * AFTER the data write completes (line 187).  With two or more
 * concurrent callers, both read the same cursor, both copy the same
 * block, and both advance -- the block in between is never copied.
 * The subdisk is then brought `up` with content that was never revived.
 *
 * The stock `vinum start` userland sleeps between ioctls (revive
 * interval), which is why the race needs a tight loop to show: each
 * child here issues back-to-back ioctls with no delay, so the
 * read-cursor/advance windows overlap constantly.
 *
 * Build: cc -O -o /tmp/racer /tmp/racer.c
 * Run (after setup): /tmp/racer 4     # 4 concurrent revivers
 */
#include <sys/ioccom.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/wait.h>

#define L 'F'
#define MAX_IOCTL_REPLY 256

struct vimsg {
    int index;
    int type;            /* 1 = sd_object */
    int state;           /* 3 = object_up */
    int op;
    int force;
    int recurse;
    int verify;
    int otherobject;
    int rename;
    long long offset;
    int blocksize;
};

struct virep { int error; char msg[MAX_IOCTL_REPLY - sizeof(int)]; };

#define VINUM_SETSTATE_DF _IOC(IOC_IN | IOC_OUT, L, 76, MAX_IOCTL_REPLY)

int
main(int argc, char **argv)
{
    int nproc = (argc > 1) ? atoi(argv[1]) : 4;
    int sdno = (argc > 2) ? atoi(argv[2]) : 1;
    int maxioctl = (argc > 3) ? atoi(argv[3]) : 0;    /* 0 = until done */
    int i, fd;
    long total = 0;

    for (i = 0; i < nproc; i++) {
        pid_t p = fork();
        if (p == 0) {
            struct vimsg msg;
            struct virep *rep = (struct virep *)&msg;
            long count = 0;
            fd = open("/dev/vinum/controld", O_RDWR);
            if (fd < 0) { perror("controld"); _exit(1); }
            for (;;) {
                memset(&msg, 0, sizeof(msg));
                msg.index = sdno; msg.type = 1; msg.state = 3;
                msg.blocksize = 4096;	    /* 4k blocks: skip = 4k */
                ioctl(fd, VINUM_SETSTATE_DF, &msg);
                count++;
                if (rep->error != 35) {    /* EAGAIN = keep going */
                    fprintf(stderr, "child %d: %ld ioctls, final error=%d msg=%s\n",
                            getpid(), count, rep->error, rep->msg);
                    break;
                }
                if (maxioctl && count >= maxioctl) {
                    fprintf(stderr, "child %d: stopped after %ld ioctls (bounded)\n",
                            getpid(), count);
                    break;
                }
            }
            _exit(0);
        }
    }
    for (i = 0; i < nproc; i++)
        wait(NULL);
    fprintf(stderr, "all racers done (%ld)\n", total);
    return 0;
}
