/*
 * DF-0019 trigger — switch to the bsd4 scheduler, set queue_checks=0,
 * then run CPU-bound workload to hit bsd4_chooseproc_locked_cache_coherent.
 *
 * The default DragonFlyBSD user scheduler is "dfly" (kern_usched.c:72),
 * so the bsd4 code path is dormant unless processes explicitly switch
 * via usched_set(USCHED_SET_SCHEDULER, "bsd4").
 *
 * Must be run as root: (a) to write kern.usched_bsd4.queue_checks sysctl,
 * (b) usched_set requires SYSCAP_NOSCHED.
 *
 * Build:  cc -O2 -o df0019 df0019.c
 * Run:    ./df0019
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/syscall.h>

/* usched_set(2) — DragonFlyBSD syscall 481 */
#define DFLY_USCHED_SET        481
#define USCHED_SET_SCHEDULER   0

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

static int dfly_usched_set(pid_t pid, int cmd, const char *name)
{
    return syscall(DFLY_USCHED_SET, pid, cmd, name, (int)strlen(name)+1);
}

static void set_sysctl(const char *name, const char *val)
{
    char cmd[256];
    snprintf(cmd, sizeof(cmd), "sysctl %s=%s", name, val);
    system(cmd);
}

int main(int argc, char **argv)
{
    int nkids = (argc > 1) ? atoi(argv[1]) : 24;
    int i;
    pid_t *kids;

    kids = calloc(nkids, sizeof(pid_t));
    if (!kids) { perror("calloc"); return 2; }

    /* Step 1: switch THIS process to the bsd4 scheduler.
     * After this, all forked children inherit bsd4 scheduling. */
    fprintf(stderr, "[*] switching pid %d to bsd4 scheduler...\n", (int)getpid());
    if (dfly_usched_set(0, USCHED_SET_SCHEDULER, "bsd4") < 0) {
        fprintf(stderr, "[!] usched_set failed: %s\n", strerror(errno));
        fprintf(stderr, "[!] cannot switch to bsd4 scheduler — bug unreachable.\n");
        return 3;
    }
    fprintf(stderr, "[+] now running under bsd4 scheduler.\n");

    /* Step 2: set queue_checks=0 (root sysctl).
     * The sysctl write is global and affects all bsd4-scheduled processes. */
    fprintf(stderr, "[*] setting kern.usched_bsd4.queue_checks=0...\n");
    set_sysctl("kern.usched_bsd4.queue_checks", "0");

    /* Step 3: fork CPU-bound children to populate the bsd4 runqueues and
     * force the scheduler through bsd4_release_curproc ->
     * bsd4_select_curproc -> bsd4_chooseproc_locked_cache_coherent.
     * With queue_checks=0 the while loop is skipped, min_level_lwp stays
     * NULL, and KASSERT(lp) at usched_bsd4.c:1548 fires -> panic. */
    fprintf(stderr, "[*] forking %d CPU-bound bsd4 kids...\n", nkids);
    signal(SIGALRM, handler);
    for (i = 0; i < nkids; i++) {
        pid_t p = fork();
        if (p == 0) {
            /* child: tight CPU spin with short sleeps to force
             * acquire/release cycles through the scheduler */
            alarm(30);
            while (!stop) {
                volatile unsigned long x = 0;
                for (volatile int j = 0; j < 50000; j++) x += j;
                usleep(50);   /* blocking syscall -> lwkt_passive_release */
            }
            _exit(0);
        }
        kids[i] = p;
    }

    /* parent also spins */
    alarm(20);
    while (!stop) {
        volatile unsigned long x = 0;
        for (volatile int j = 0; j < 500000; j++) x += j;
    }

    /* If we survive, clean up. On a fixed kernel (queue_checks clamped or
     * sysctl rejected) we should reach here cleanly. */
    fprintf(stderr, "[*] parent survived, reaping kids...\n");
    for (i = 0; i < nkids; i++)
        kill(kids[i], SIGTERM);
    for (i = 0; i < nkids; i++) {
        int st;
        waitpid(kids[i], &st, 0);
    }
    free(kids);
    fprintf(stderr, "[*] survived — no panic (FIXED kernel or path not hit). exit 0.\n");
    return 0;
}
