root/usr/src/cmd/raidz_test/raidz_test.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright (C) 2016 Gvozden Nešković. All rights reserved.
 * Copyright 2020 Joyent, Inc.
 */

#include <sys/zfs_context.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/zio.h>
#include <umem.h>
#include <sys/vdev_raidz.h>
#include <sys/vdev_raidz_impl.h>
#include <assert.h>
#include <stdio.h>
#include <strings.h>
#include <unistd.h>
#include "raidz_test.h"

static int *rand_data;
raidz_test_opts_t rto_opts;

static char gdb[256];
static const char gdb_tmpl[] = "gdb -ex \"set pagination 0\" -p %d";

#define boot_ncpus      (sysconf(_SC_NPROCESSORS_ONLN))

static size_t ilog2(size_t a)
{
        return (a > 1 ? 1 + ilog2(a >> 1) : 0);
}

static void print_opts(raidz_test_opts_t *opts, boolean_t force)
{
        char *verbose;
        switch (opts->rto_v) {
                case 0:
                        verbose = "no";
                        break;
                case 1:
                        verbose = "info";
                        break;
                default:
                        verbose = "debug";
                        break;
        }

        if (force || opts->rto_v >= D_INFO) {
                (void) fprintf(stdout, DBLSEP "Running with options:\n"
                    "  (-a) zio ashift                   : %zu\n"
                    "  (-o) zio offset                   : 1 << %zu\n"
                    "  (-d) number of raidz data columns : %zu\n"
                    "  (-s) size of DATA                 : 1 << %zu\n"
                    "  (-S) sweep parameters             : %s \n"
                    "  (-v) verbose                      : %s \n\n",
                    opts->rto_ashift,                   /* -a */
                    ilog2(opts->rto_offset),            /* -o */
                    opts->rto_dcols,                    /* -d */
                    ilog2(opts->rto_dsize),             /* -s */
                    opts->rto_sweep ? "yes" : "no",     /* -S */
                    verbose);                           /* -v */
        }
}

static void usage(boolean_t requested)
{
        const raidz_test_opts_t *o = &rto_opts_defaults;

        FILE *fp = requested ? stdout : stderr;

        (void) fprintf(fp, "Usage:\n"
            "\t[-a zio ashift (default: %zu)]\n"
            "\t[-o zio offset, exponent radix 2 (default: %zu)]\n"
            "\t[-d number of raidz data columns (default: %zu)]\n"
            "\t[-s zio size, exponent radix 2 (default: %zu)]\n"
            "\t[-S parameter sweep (default: %s)]\n"
            "\t[-t timeout for parameter sweep test]\n"
            "\t[-B benchmark all raidz implementations]\n"
            "\t[-v increase verbosity (default: %zu)]\n"
            "\t[-h (print help)]\n"
            "\t[-T test the test, see if failure would be detected]\n"
            "\t[-D debug (attach gdb on SIGSEGV)]\n"
            "",
            o->rto_ashift,                              /* -a */
            ilog2(o->rto_offset),                       /* -o */
            o->rto_dcols,                               /* -d */
            ilog2(o->rto_dsize),                        /* -s */
            rto_opts.rto_sweep ? "yes" : "no",          /* -S */
            o->rto_v);                                  /* -d */

        exit(requested ? 0 : 1);
}

static void process_options(int argc, char **argv)
{
        size_t value;
        int opt;

        raidz_test_opts_t *o = &rto_opts;

        bcopy(&rto_opts_defaults, o, sizeof (*o));

        while ((opt = getopt(argc, argv, "TDBSvha:o:d:s:t:")) != -1) {
                value = 0;

                switch (opt) {
                case 'a':
                        value = strtoull(optarg, NULL, 0);
                        o->rto_ashift = MIN(13, MAX(9, value));
                        break;
                case 'o':
                        value = strtoull(optarg, NULL, 0);
                        o->rto_offset = ((1ULL << MIN(12, value)) >> 9) << 9;
                        break;
                case 'd':
                        value = strtoull(optarg, NULL, 0);
                        o->rto_dcols = MIN(255, MAX(1, value));
                        break;
                case 's':
                        value = strtoull(optarg, NULL, 0);
                        o->rto_dsize = 1ULL <<  MIN(SPA_MAXBLOCKSHIFT,
                            MAX(SPA_MINBLOCKSHIFT, value));
                        break;
                case 't':
                        value = strtoull(optarg, NULL, 0);
                        o->rto_sweep_timeout = value;
                        break;
                case 'v':
                        o->rto_v++;
                        break;
                case 'S':
                        o->rto_sweep = 1;
                        break;
                case 'B':
                        o->rto_benchmark = 1;
                        break;
                case 'D':
                        o->rto_gdb = 1;
                        break;
                case 'T':
                        o->rto_sanity = 1;
                        break;
                case 'h':
                        usage(B_TRUE);
                        break;
                case '?':
                default:
                        usage(B_FALSE);
                        break;
                }
        }
}

#define DATA_COL(rm, i) ((rm)->rm_col[raidz_parity(rm) + (i)].rc_abd)
#define DATA_COL_SIZE(rm, i) ((rm)->rm_col[raidz_parity(rm) + (i)].rc_size)

#define CODE_COL(rm, i) ((rm)->rm_col[(i)].rc_abd)
#define CODE_COL_SIZE(rm, i) ((rm)->rm_col[(i)].rc_size)

static int
cmp_code(raidz_test_opts_t *opts, const raidz_map_t *rm, const int parity)
{
        int i, ret = 0;

        VERIFY(parity >= 1 && parity <= 3);

        for (i = 0; i < parity; i++) {
                if (abd_cmp(CODE_COL(rm, i), CODE_COL(opts->rm_golden, i),
                    CODE_COL(rm, i)->abd_size) != 0) {
                        ret++;
                        LOG_OPT(D_DEBUG, opts,
                            "\nParity block [%d] different!\n", i);
                }
        }
        return (ret);
}

static int
cmp_data(raidz_test_opts_t *opts, raidz_map_t *rm)
{
        int i, ret = 0;
        int dcols = opts->rm_golden->rm_cols - raidz_parity(opts->rm_golden);

        for (i = 0; i < dcols; i++) {
                if (abd_cmp(DATA_COL(opts->rm_golden, i), DATA_COL(rm, i),
                    DATA_COL(opts->rm_golden, i)->abd_size) != 0) {
                        ret++;

                        LOG_OPT(D_DEBUG, opts,
                            "\nData block [%d] different!\n", i);
                }
        }
        return (ret);
}

static int
init_rand(void *data, size_t size, void *private)
{
        int i;
        int *dst = (int *)data;

        for (i = 0; i < size / sizeof (int); i++)
                dst[i] = rand_data[i];

        return (0);
}

static void
corrupt_colums(raidz_map_t *rm, const int *tgts, const int cnt)
{
        int i;
        raidz_col_t *col;

        for (i = 0; i < cnt; i++) {
                col = &rm->rm_col[tgts[i]];
                (void) abd_iterate_func(col->rc_abd, 0, col->rc_size,
                    init_rand, NULL);
        }
}

void
init_zio_abd(zio_t *zio)
{
        (void) abd_iterate_func(zio->io_abd, 0, zio->io_size, init_rand, NULL);
}

static void
fini_raidz_map(zio_t **zio, raidz_map_t **rm)
{
        vdev_raidz_map_free(*rm);
        raidz_free((*zio)->io_abd, (*zio)->io_size);
        umem_free(*zio, sizeof (zio_t));

        *zio = NULL;
        *rm = NULL;
}

static int
init_raidz_golden_map(raidz_test_opts_t *opts, const int parity)
{
        int err = 0;
        zio_t *zio_test;
        raidz_map_t *rm_test;
        const size_t total_ncols = opts->rto_dcols + parity;

        if (opts->rm_golden) {
                fini_raidz_map(&opts->zio_golden, &opts->rm_golden);
        }

        opts->zio_golden = umem_zalloc(sizeof (zio_t), UMEM_NOFAIL);
        zio_test = umem_zalloc(sizeof (zio_t), UMEM_NOFAIL);

        opts->zio_golden->io_offset = zio_test->io_offset = opts->rto_offset;
        opts->zio_golden->io_size = zio_test->io_size = opts->rto_dsize;

        opts->zio_golden->io_abd = raidz_alloc(opts->rto_dsize);
        zio_test->io_abd = raidz_alloc(opts->rto_dsize);

        init_zio_abd(opts->zio_golden);
        init_zio_abd(zio_test);

        VERIFY0(vdev_raidz_impl_set("original"));

        opts->rm_golden = vdev_raidz_map_alloc(opts->zio_golden,
            opts->rto_ashift, total_ncols, parity);
        rm_test = vdev_raidz_map_alloc(zio_test,
            opts->rto_ashift, total_ncols, parity);

        VERIFY(opts->zio_golden);
        VERIFY(opts->rm_golden);

        vdev_raidz_generate_parity(opts->rm_golden);
        vdev_raidz_generate_parity(rm_test);

        /* sanity check */
        err |= cmp_data(opts, rm_test);
        err |= cmp_code(opts, rm_test, parity);

        if (err)
                ERRMSG("initializing the golden copy ... [FAIL]!\n");

        /* tear down raidz_map of test zio */
        fini_raidz_map(&zio_test, &rm_test);

        return (err);
}

static raidz_map_t *
init_raidz_map(raidz_test_opts_t *opts, zio_t **zio, const int parity)
{
        raidz_map_t *rm = NULL;
        const size_t alloc_dsize = opts->rto_dsize;
        const size_t total_ncols = opts->rto_dcols + parity;
        const int ccols[] = { 0, 1, 2 };

        VERIFY(zio);
        VERIFY(parity <= 3 && parity >= 1);

        *zio = umem_zalloc(sizeof (zio_t), UMEM_NOFAIL);

        (*zio)->io_offset = 0;
        (*zio)->io_size = alloc_dsize;
        (*zio)->io_abd = raidz_alloc(alloc_dsize);
        init_zio_abd(*zio);

        rm = vdev_raidz_map_alloc(*zio, opts->rto_ashift,
            total_ncols, parity);
        VERIFY(rm);

        /* Make sure code columns are destroyed */
        corrupt_colums(rm, ccols, parity);

        return (rm);
}

static int
run_gen_check(raidz_test_opts_t *opts)
{
        char **impl_name;
        int fn, err = 0;
        zio_t *zio_test;
        raidz_map_t *rm_test;

        err = init_raidz_golden_map(opts, PARITY_PQR);
        if (0 != err)
                return (err);

        LOG(D_INFO, DBLSEP);
        LOG(D_INFO, "Testing parity generation...\n");

        for (impl_name = (char **)raidz_impl_names+1; *impl_name != NULL;
            impl_name++) {

                LOG(D_INFO, SEP);
                LOG(D_INFO, "\tTesting [%s] implementation...", *impl_name);

                if (0 != vdev_raidz_impl_set(*impl_name)) {
                        LOG(D_INFO, "[SKIP]\n");
                        continue;
                } else {
                        LOG(D_INFO, "[SUPPORTED]\n");
                }

                for (fn = 0; fn < RAIDZ_GEN_NUM; fn++) {

                        /* Check if should stop */
                        if (rto_opts.rto_should_stop)
                                return (err);

                        /* create suitable raidz_map */
                        rm_test = init_raidz_map(opts, &zio_test, fn+1);
                        VERIFY(rm_test);

                        LOG(D_INFO, "\t\tTesting method [%s] ...",
                            raidz_gen_name[fn]);

                        if (!opts->rto_sanity)
                                vdev_raidz_generate_parity(rm_test);

                        if (cmp_code(opts, rm_test, fn+1) != 0) {
                                LOG(D_INFO, "[FAIL]\n");
                                err++;
                        } else
                                LOG(D_INFO, "[PASS]\n");

                        fini_raidz_map(&zio_test, &rm_test);
                }
        }

        fini_raidz_map(&opts->zio_golden, &opts->rm_golden);

        return (err);
}

static int
run_rec_check_impl(raidz_test_opts_t *opts, raidz_map_t *rm, const int fn)
{
        int x0, x1, x2;
        int tgtidx[3];
        int err = 0;
        static const int rec_tgts[7][3] = {
                {1, 2, 3},      /* rec_p:   bad QR & D[0]       */
                {0, 2, 3},      /* rec_q:   bad PR & D[0]       */
                {0, 1, 3},      /* rec_r:   bad PQ & D[0]       */
                {2, 3, 4},      /* rec_pq:  bad R  & D[0][1]    */
                {1, 3, 4},      /* rec_pr:  bad Q  & D[0][1]    */
                {0, 3, 4},      /* rec_qr:  bad P  & D[0][1]    */
                {3, 4, 5}       /* rec_pqr: bad    & D[0][1][2] */
        };

        memcpy(tgtidx, rec_tgts[fn], sizeof (tgtidx));

        if (fn < RAIDZ_REC_PQ) {
                /* can reconstruct 1 failed data disk */
                for (x0 = 0; x0 < opts->rto_dcols; x0++) {
                        if (x0 >= rm->rm_cols - raidz_parity(rm))
                                continue;

                        /* Check if should stop */
                        if (rto_opts.rto_should_stop)
                                return (err);

                        LOG(D_DEBUG, "[%d] ", x0);

                        tgtidx[2] = x0 + raidz_parity(rm);

                        corrupt_colums(rm, tgtidx+2, 1);

                        if (!opts->rto_sanity)
                                (void) vdev_raidz_reconstruct(rm, tgtidx, 3);

                        if (cmp_data(opts, rm) != 0) {
                                err++;
                                LOG(D_DEBUG, "\nREC D[%d]... [FAIL]\n", x0);
                        }
                }

        } else if (fn < RAIDZ_REC_PQR) {
                /* can reconstruct 2 failed data disk */
                for (x0 = 0; x0 < opts->rto_dcols; x0++) {
                        if (x0 >= rm->rm_cols - raidz_parity(rm))
                                continue;
                        for (x1 = x0 + 1; x1 < opts->rto_dcols; x1++) {
                                if (x1 >= rm->rm_cols - raidz_parity(rm))
                                        continue;

                                /* Check if should stop */
                                if (rto_opts.rto_should_stop)
                                        return (err);

                                LOG(D_DEBUG, "[%d %d] ", x0, x1);

                                tgtidx[1] = x0 + raidz_parity(rm);
                                tgtidx[2] = x1 + raidz_parity(rm);

                                corrupt_colums(rm, tgtidx+1, 2);

                                if (!opts->rto_sanity)
                                        (void) vdev_raidz_reconstruct(rm,
                                            tgtidx, 3);

                                if (cmp_data(opts, rm) != 0) {
                                        err++;
                                        LOG(D_DEBUG, "\nREC D[%d %d]... "
                                            "[FAIL]\n", x0, x1);
                                }
                        }
                }
        } else {
                /* can reconstruct 3 failed data disk */
                for (x0 = 0; x0 < opts->rto_dcols; x0++) {
                        if (x0 >= rm->rm_cols - raidz_parity(rm))
                                continue;
                        for (x1 = x0 + 1; x1 < opts->rto_dcols; x1++) {
                                if (x1 >= rm->rm_cols - raidz_parity(rm))
                                        continue;
                                for (x2 = x1 + 1; x2 < opts->rto_dcols; x2++) {
                                        if (x2 >=
                                            rm->rm_cols - raidz_parity(rm))
                                                continue;

                                        /* Check if should stop */
                                        if (rto_opts.rto_should_stop)
                                                return (err);

                                        LOG(D_DEBUG, "[%d %d %d]", x0, x1, x2);

                                        tgtidx[0] = x0 + raidz_parity(rm);
                                        tgtidx[1] = x1 + raidz_parity(rm);
                                        tgtidx[2] = x2 + raidz_parity(rm);

                                        corrupt_colums(rm, tgtidx, 3);

                                        if (!opts->rto_sanity)
                                                (void) vdev_raidz_reconstruct(
                                                    rm, tgtidx, 3);

                                        if (cmp_data(opts, rm) != 0) {
                                                err++;
                                                LOG(D_DEBUG,
                                                    "\nREC D[%d %d %d]... "
                                                    "[FAIL]\n", x0, x1, x2);
                                        }
                                }
                        }
                }
        }
        return (err);
}

static int
run_rec_check(raidz_test_opts_t *opts)
{
        char **impl_name;
        unsigned fn, err = 0;
        zio_t *zio_test;
        raidz_map_t *rm_test;

        err = init_raidz_golden_map(opts, PARITY_PQR);
        if (0 != err)
                return (err);

        LOG(D_INFO, DBLSEP);
        LOG(D_INFO, "Testing data reconstruction...\n");

        for (impl_name = (char **)raidz_impl_names+1; *impl_name != NULL;
            impl_name++) {

                LOG(D_INFO, SEP);
                LOG(D_INFO, "\tTesting [%s] implementation...", *impl_name);

                if (vdev_raidz_impl_set(*impl_name) != 0) {
                        LOG(D_INFO, "[SKIP]\n");
                        continue;
                } else
                        LOG(D_INFO, "[SUPPORTED]\n");


                /* create suitable raidz_map */
                rm_test = init_raidz_map(opts, &zio_test, PARITY_PQR);
                /* generate parity */
                vdev_raidz_generate_parity(rm_test);

                for (fn = 0; fn < RAIDZ_REC_NUM; fn++) {

                        LOG(D_INFO, "\t\tTesting method [%s] ...",
                            raidz_rec_name[fn]);

                        if (run_rec_check_impl(opts, rm_test, fn) != 0) {
                                LOG(D_INFO, "[FAIL]\n");
                                err++;

                        } else
                                LOG(D_INFO, "[PASS]\n");

                }
                /* tear down test raidz_map */
                fini_raidz_map(&zio_test, &rm_test);
        }

        fini_raidz_map(&opts->zio_golden, &opts->rm_golden);

        return (err);
}

static int
run_test(raidz_test_opts_t *opts)
{
        int err = 0;

        if (opts == NULL)
                opts = &rto_opts;

        print_opts(opts, B_FALSE);

        err |= run_gen_check(opts);
        err |= run_rec_check(opts);

        return (err);
}

#define SWEEP_RUNNING   0
#define SWEEP_FINISHED  1
#define SWEEP_ERROR     2
#define SWEEP_TIMEOUT   3

static int sweep_state = 0;
static raidz_test_opts_t failed_opts;

static kmutex_t sem_mtx;
static kcondvar_t sem_cv;
static int max_free_slots;
static int free_slots;

static void
sweep_thread(void *arg)
{
        int err = 0;
        raidz_test_opts_t *opts = (raidz_test_opts_t *)arg;
        VERIFY(opts != NULL);

        err = run_test(opts);

        if (rto_opts.rto_sanity) {
                /* 25% chance that a sweep test fails */
                if (rand() < (RAND_MAX/4))
                        err = 1;
        }

        if (0 != err) {
                mutex_enter(&sem_mtx);
                memcpy(&failed_opts, opts, sizeof (raidz_test_opts_t));
                sweep_state = SWEEP_ERROR;
                mutex_exit(&sem_mtx);
        }

        umem_free(opts, sizeof (raidz_test_opts_t));

        /* signal the next thread */
        mutex_enter(&sem_mtx);
        free_slots++;
        cv_signal(&sem_cv);
        mutex_exit(&sem_mtx);

        thread_exit();
}

static int
run_sweep(void)
{
        static const size_t dcols_v[] = { 1, 2, 3, 4, 5, 6, 7, 8, 12, 15, 16 };
        static const size_t ashift_v[] = { 9, 12, 14 };
        static const size_t size_v[] = { 1 << 9, 21 * (1 << 9), 13 * (1 << 12),
                1 << 17, (1 << 20) - (1 << 12), SPA_MAXBLOCKSIZE };

        (void) setvbuf(stdout, NULL, _IONBF, 0);

        ulong_t total_comb = ARRAY_SIZE(size_v) * ARRAY_SIZE(ashift_v) *
            ARRAY_SIZE(dcols_v);
        ulong_t tried_comb = 0;
        hrtime_t time_diff, start_time = gethrtime();
        raidz_test_opts_t *opts;
        int a, d, s;

        max_free_slots = free_slots = MAX(2, boot_ncpus);

        mutex_init(&sem_mtx, NULL, MUTEX_DEFAULT, NULL);
        cv_init(&sem_cv, NULL, CV_DEFAULT, NULL);

        for (s = 0; s < ARRAY_SIZE(size_v); s++)
        for (a = 0; a < ARRAY_SIZE(ashift_v); a++)
        for (d = 0; d < ARRAY_SIZE(dcols_v); d++) {

                if (size_v[s] < (1 << ashift_v[a])) {
                        total_comb--;
                        continue;
                }

                if (++tried_comb % 20 == 0)
                        LOG(D_ALL, "%lu/%lu... ", tried_comb, total_comb);

                /* wait for signal to start new thread */
                mutex_enter(&sem_mtx);
                while (cv_timedwait_sig(&sem_cv, &sem_mtx,
                    ddi_get_lbolt() + hz)) {

                        /* check if should stop the test (timeout) */
                        time_diff = (gethrtime() - start_time) / NANOSEC;
                        if (rto_opts.rto_sweep_timeout > 0 &&
                            time_diff >= rto_opts.rto_sweep_timeout) {
                                sweep_state = SWEEP_TIMEOUT;
                                rto_opts.rto_should_stop = B_TRUE;
                                mutex_exit(&sem_mtx);
                                goto exit;
                        }

                        /* check if should stop the test (error) */
                        if (sweep_state != SWEEP_RUNNING) {
                                mutex_exit(&sem_mtx);
                                goto exit;
                        }

                        /* exit loop if a slot is available */
                        if (free_slots > 0) {
                                break;
                        }
                }

                free_slots--;
                mutex_exit(&sem_mtx);

                opts = umem_zalloc(sizeof (raidz_test_opts_t), UMEM_NOFAIL);
                opts->rto_ashift = ashift_v[a];
                opts->rto_dcols = dcols_v[d];
                opts->rto_offset = (1 << ashift_v[a]) * rand();
                opts->rto_dsize = size_v[s];
                opts->rto_v = 0; /* be quiet */

                VERIFY3P(thread_create(NULL, 0, sweep_thread, (void *) opts,
                    0, NULL, TS_RUN, maxclsyspri), !=, NULL);
        }

exit:
        LOG(D_ALL, "\nWaiting for test threads to finish...\n");
        mutex_enter(&sem_mtx);
        VERIFY(free_slots <= max_free_slots);
        while (free_slots < max_free_slots) {
                (void) cv_wait(&sem_cv, &sem_mtx);
        }
        mutex_exit(&sem_mtx);

        if (sweep_state == SWEEP_ERROR) {
                ERRMSG("Sweep test failed! Failed option: \n");
                print_opts(&failed_opts, B_TRUE);
        } else {
                if (sweep_state == SWEEP_TIMEOUT)
                        LOG(D_ALL, "Test timeout (%lus). Stopping...\n",
                            (ulong_t)rto_opts.rto_sweep_timeout);

                LOG(D_ALL, "Sweep test succeeded on %lu raidz maps!\n",
                    (ulong_t)tried_comb);
        }

        mutex_destroy(&sem_mtx);

        return (sweep_state == SWEEP_ERROR ? SWEEP_ERROR : 0);
}

int
main(int argc, char **argv)
{
        size_t i;
        int err = 0;

        /* init gdb string early */
        (void) sprintf(gdb, gdb_tmpl, getpid());

        (void) setvbuf(stdout, NULL, _IOLBF, 0);

        dprintf_setup(&argc, argv);

        process_options(argc, argv);

        kernel_init(FREAD);

        /* setup random data because rand() is not reentrant */
        rand_data = (int *)umem_alloc(SPA_MAXBLOCKSIZE, UMEM_NOFAIL);
        srand((unsigned)time(NULL) * getpid());
        for (i = 0; i < SPA_MAXBLOCKSIZE / sizeof (int); i++)
                rand_data[i] = rand();

        mprotect((void *)rand_data, SPA_MAXBLOCKSIZE, PROT_READ);

        if (rto_opts.rto_benchmark) {
                run_raidz_benchmark();
        } else if (rto_opts.rto_sweep) {
                err = run_sweep();
        } else {
                err = run_test(NULL);
        }

        umem_free(rand_data, SPA_MAXBLOCKSIZE);
        kernel_fini();

        return (err);
}