/*
 * DF-0847 — concurrent dquot hash/free-list race harness (cross-mount).
 *
 * NON-DEFAULT-CONFIG CHARACTERIZATION ONLY.
 *
 * The global ufs_dqhashtbl / ufs_dqfreelist are mutated with NO lock in
 * ufs_dqget()/ufs_dqrele()/ufs_dqflush() (ufs_quota.c).  This harness drives
 * concurrent cache-miss/recycle + free-list races to expose it.
 *
 * IMPORTANT: vfs_quotactl() takes the per-mount mnt_token (UFS is not
 * MNTK_MPSAFE), so quotactl calls on a SINGLE mount are serialized and cannot
 * race.  The dquot lists are GLOBAL across mounts, so the race is only
 * reachable by hammering TWO (or more) quota'd UFS mounts concurrently.
 * This harness therefore splits its worker threads across two mount points.
 *
 * Each worker calls quotactl(Q_GETQUOTA, its-mount, uid=cycling, ...) in a
 * tight loop.  Q_GETQUOTA -> ufs_getquota -> ufs_dqget + ufs_dqrele.  With a
 * small uid range, workers collide on the same cached dquot that is
 * momentarily on the free list (dq_cnt==0) and both TAILQ_REMOVE it -> list
 * corruption.  With a large range, the cache-miss recycle path
 * (TAILQ_FIRST + TAILQ_REMOVE + LIST_REMOVE + LIST_INSERT_HEAD) is exercised.
 *
 * Privilege: Q_GETQUOTA for arbitrary uid requires root
 * (caps_priv_check RESTRICTEDROOT).  This is a root-driven characterization
 * of the unlocked list mutation — NOT an unprivileged escalation.  An
 * unprivileged user can only cause a cache-miss for their own uid (a single
 * dquot), giving a negligible race window.
 *
 * Build:  cc -O2 -pthread -o dq_race dq_race.c
 * Run (as root, options-QUOTA kernel, TWO quotaon'd UFS mounts):
 *        ./dq_race /mnt/q0 /mnt/q1 12 60 50
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/syscall.h>

#define Q_GETQUOTA 0x0300
#define USRQUOTA   0
#define SUBCMDSHIFT 8

struct dqblk { char _pad[512]; };

static const char *g_mp[2];
static int        g_secs;
static unsigned   uidrange = 50;
static volatile int g_stop = 0;

static void *
worker(void *arg)
{
    unsigned seed = (unsigned)(size_t)arg ^ (unsigned)getpid();
    const char *mp = g_mp[(unsigned)(size_t)arg & 1];   /* even->mp0, odd->mp1 */
    long iter = 0;
    struct dqblk db;
    int cmd = (Q_GETQUOTA << SUBCMDSHIFT) | USRQUOTA;

    while (!g_stop) {
        unsigned uid = (rand_r(&seed) % uidrange) + 1;
        syscall(SYS_quotactl, mp, cmd, uid, &db);
        iter++;
    }
    return (void *)iter;
}

int
main(int argc, char **argv)
{
    int nthreads = (argc > 3) ? atoi(argv[3]) : 12;
    int secs     = (argc > 4) ? atoi(argv[4]) : 60;
    if (argc > 5) uidrange = (unsigned)atoi(argv[5]);
    g_mp[0] = (argc > 1) ? argv[1] : "/mnt/q0";
    g_mp[1] = (argc > 2) ? argv[2] : "/mnt/q1";
    g_secs  = secs;

    fprintf(stderr, "DF-0847 race harness: mp0=%s mp1=%s threads=%d secs=%d uidrange=1..%u\n",
            g_mp[0], g_mp[1], nthreads, secs, uidrange);
    fprintf(stderr, "driving CROSS-MOUNT concurrent Q_GETQUOTA cache/freelist races...\n");

    pthread_t th[128];
    if (nthreads > 128) nthreads = 128;
    for (int i = 0; i < nthreads; i++)
        pthread_create(&th[i], NULL, worker, (void *)(size_t)(i + 1));

    sleep(secs);
    g_stop = 1;

    long total = 0;
    for (int i = 0; i < nthreads; i++) {
        long it; pthread_join(th[i], (void **)&it); total += it;
    }
    fprintf(stderr, "survived: %ld quotactl iterations across %d threads\n",
            total, nthreads);
    fprintf(stderr, "If the kernel panicked, see boot.log for the signature.\n");
    return 0;
}
