root/sbin/bioctl/bioctl.c
/* $OpenBSD: bioctl.c,v 1.159 2025/04/18 20:58:06 kn Exp $ */

/*
 * Copyright (c) 2004, 2005 Marco Peereboom
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */

#include <sys/param.h>  /* NODEV */
#include <sys/ioctl.h>
#include <sys/dkio.h>
#include <sys/stat.h>
#include <dev/softraidvar.h>
#include <dev/biovar.h>

#include <errno.h>
#include <err.h>
#include <fcntl.h>
#include <util.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <limits.h>
#include <vis.h>
#include <readpassphrase.h>

struct locator {
        int             channel;
        int             target;
        int             lun;
};

struct timing {
        int             interval;
        int             start;
};

static void __dead      usage(void);
const char              *str2locator(const char *, struct locator *);
const char              *str2patrol(const char *, struct timing *);
void                    bio_status(struct bio_status *);
int                     bio_parse_devlist(char *, dev_t *);
void                    bio_kdf_derive(struct sr_crypto_kdfinfo *,
                            struct sr_crypto_pbkdf *, char *, int);
void                    bio_kdf_generate(struct sr_crypto_kdfinfo *);
int                     bcrypt_pbkdf_autorounds(void);
void                    derive_key(u_int32_t, int, u_int8_t *, size_t,
                            u_int8_t *, size_t, char *, int);

void                    bio_inq(char *);
void                    bio_alarm(char *);
int                     bio_getvolbyname(char *);
void                    bio_setstate(char *, int, char *);
void                    bio_setblink(char *, char *, int);
void                    bio_blink(char *, int, int);
void                    bio_createraid(u_int16_t, char *, char *);
void                    bio_deleteraid(char *);
void                    bio_changepass(char *);
u_int32_t               bio_createflags(char *);
char                    *bio_vis(char *);
void                    bio_diskinq(char *);
void                    bio_patrol(char *);

int                     devh = -1;
int                     human;
int                     verbose;
u_int32_t               cflags = 0;
int                     rflag = -1;     /* auto */
char                    *passfile;

void                    *bio_cookie;

int interactive = 1;

int
main(int argc, char *argv[])
{
        struct bio_locate       bl;
        u_int64_t               func = 0;
        char                    *devicename = NULL;
        char                    *realname = NULL, *al_arg = NULL;
        char                    *bl_arg = NULL, *dev_list = NULL;
        char                    *key_disk = NULL;
        const char              *errstr;
        int                     ch, blink = 0, changepass = 0, diskinq = 0;
        int                     ss_func = 0;
        u_int16_t               cr_level = 0;
        int                     biodev = 0;

        if (argc < 2)
                usage();

        while ((ch = getopt(argc, argv, "a:b:C:c:dH:hik:l:O:Pp:qr:R:st:u:v")) !=
            -1) {
                switch (ch) {
                case 'a': /* alarm */
                        func |= BIOC_ALARM;
                        al_arg = optarg;
                        break;
                case 'b': /* blink */
                        func |= BIOC_BLINK;
                        blink = BIOC_SBBLINK;
                        bl_arg = optarg;
                        break;
                case 'C': /* creation flags */
                        cflags = bio_createflags(optarg);
                        break;
                case 'c': /* create */
                        func |= BIOC_CREATERAID;
                        if (strcmp(optarg, "1C") == 0) {
                                cr_level = 0x1C;
                        } else if (isdigit((unsigned char)*optarg)) {
                                cr_level = strtonum(optarg, 0, 10, &errstr);
                                if (errstr != NULL)
                                        errx(1, "Invalid RAID level");
                        } else if (strlen(optarg) == 1) {
                                cr_level = *optarg;
                        } else {
                                errx(1, "Invalid RAID level");
                        }
                        break;
                case 'd':
                        /* delete volume */
                        func |= BIOC_DELETERAID;
                        break;
                case 'u': /* unblink */
                        func |= BIOC_BLINK;
                        blink = BIOC_SBUNBLINK;
                        bl_arg = optarg;
                        break;
                case 'H': /* set hotspare */
                        func |= BIOC_SETSTATE;
                        ss_func = BIOC_SSHOTSPARE;
                        al_arg = optarg;
                        break;
                case 'h':
                        human = 1;
                        break;
                case 'i': /* inquiry */
                        func |= BIOC_INQ;
                        break;
                case 'k': /* Key disk. */
                        key_disk = optarg;
                        break;
                case 'l': /* device list */
                        func |= BIOC_DEVLIST;
                        dev_list = optarg;
                        break;
                case 'P':
                        /* Change passphrase. */
                        changepass = 1;
                        break;
                case 'p':
                        passfile = optarg;
                        break;
                case 'r':
                        if (strcmp(optarg, "auto") == 0) {
                                rflag = -1;
                                break;
                        }
                        rflag = strtonum(optarg, 16, 1<<30, &errstr);
                        if (errstr != NULL)
                                errx(1, "number of KDF rounds is %s: %s",
                                    errstr, optarg);
                        break;
                case 'O':
                        /* set a chunk to offline */
                        func |= BIOC_SETSTATE;
                        ss_func = BIOC_SSOFFLINE;
                        al_arg = optarg;
                        break;
                case 'R':
                        /* rebuild to provided chunk/CTL */
                        func |= BIOC_SETSTATE;
                        ss_func = BIOC_SSREBUILD;
                        al_arg = optarg;
                        break;
                case 's':
                        interactive = 0;
                        break;
                case 't': /* patrol */
                        func |= BIOC_PATROL;
                        al_arg = optarg;
                        break;
                case 'v':
                        verbose = 1;
                        break;
                case 'q':
                        diskinq = 1;
                        break;
                default:
                        usage();
                        /* NOTREACHED */
                }
        }
        argc -= optind;
        argv += optind;

        if (argc != 1 || (changepass && func != 0))
                usage();

        if (func == 0)
                func |= BIOC_INQ;

        devicename = argv[0];
        if (devicename == NULL)
                errx(1, "need device");

        devh = opendev(devicename, O_RDWR, OPENDEV_PART, &realname);
        if (devh == -1) {
                devh = open("/dev/bio", O_RDWR);
                if (devh == -1)
                        err(1, "Can't open %s", "/dev/bio");

                memset(&bl, 0, sizeof(bl));
                bl.bl_name = devicename;
                if (ioctl(devh, BIOCLOCATE, &bl) == -1)
                        errx(1, "Can't locate %s device via %s",
                            bl.bl_name, "/dev/bio");

                bio_cookie = bl.bl_bio.bio_cookie;
                biodev = 1;
                devicename = NULL;
        }

        if (diskinq) {
                bio_diskinq(devicename);
        } else if (changepass && !biodev) {
                bio_changepass(devicename);
        } else if (func & BIOC_INQ) {
                bio_inq(devicename);
        } else if (func == BIOC_ALARM) {
                bio_alarm(al_arg);
        } else if (func == BIOC_BLINK) {
                bio_setblink(devicename, bl_arg, blink);
        } else if (func == BIOC_PATROL) {
                bio_patrol(al_arg);
        } else if (func == BIOC_SETSTATE) {
                bio_setstate(al_arg, ss_func, argv[0]);
        } else if (func == BIOC_DELETERAID && !biodev) {
                bio_deleteraid(devicename);
        } else if (func & BIOC_CREATERAID || func & BIOC_DEVLIST) {
                if (!(func & BIOC_CREATERAID))
                        errx(1, "need -c parameter");
                if (!(func & BIOC_DEVLIST))
                        errx(1, "need -l parameter");
                if (!biodev)
                        errx(1, "must use bio device");
                bio_createraid(cr_level, dev_list, key_disk);
        }

        return (0);
}

static void __dead
usage(void)
{
        extern char             *__progname;

        fprintf(stderr,
                "usage: %s [-hiqv] [-a alarm-function] "
                "[-b channel:target[.lun]]\n"
                "\t[-H channel:target[.lun]] "
                "[-R chunk | channel:target[.lun]]\n"
                "\t[-t patrol-function] "
                "[-u channel:target[.lun]] "
                "device\n\n"
                "       %s [-dhiPqsv] "
                "[-C flag[,...]] [-c raidlevel] [-k keydisk]\n"
                "\t[-l chunk[,...]] "
                "[-O device | channel:target[.lun]] [-p passfile]\n"
                "\t[-R chunk | channel:target[.lun]] [-r rounds] "
                "device\n", __progname, __progname);

        exit(1);
}

const char *
str2locator(const char *string, struct locator *location)
{
        const char              *errstr;
        char                    parse[80], *targ, *lun;

        strlcpy(parse, string, sizeof parse);
        targ = strchr(parse, ':');
        if (targ == NULL)
                return ("target not specified");
        *targ++ = '\0';

        lun = strchr(targ, '.');
        if (lun != NULL) {
                *lun++ = '\0';
                location->lun = strtonum(lun, 0, 256, &errstr);
                if (errstr)
                        return (errstr);
        } else
                location->lun = 0;

        location->target = strtonum(targ, 0, 256, &errstr);
        if (errstr)
                return (errstr);
        location->channel = strtonum(parse, 0, 256, &errstr);
        if (errstr)
                return (errstr);
        return (NULL);
}

const char *
str2patrol(const char *string, struct timing *timing)
{
        const char              *errstr;
        char                    parse[80], *interval = NULL, *start = NULL;

        timing->interval = 0;
        timing->start = 0;

        strlcpy(parse, string, sizeof parse);

        interval = strchr(parse, '.');
        if (interval != NULL) {
                *interval++ = '\0';
                start = strchr(interval, '.');
                if (start != NULL)
                        *start++ = '\0';
        }
        if (interval != NULL) {
                /* -1 == continuously */
                timing->interval = strtonum(interval, -1, INT_MAX, &errstr);
                if (errstr)
                        return (errstr);
        }
        if (start != NULL) {
                timing->start = strtonum(start, 0, INT_MAX, &errstr);
                if (errstr)
                        return (errstr);
        }

        return (NULL);
}

void
bio_status(struct bio_status *bs)
{
        extern char             *__progname;
        char                    *prefix;
        int                     i;

        if (strlen(bs->bs_controller))
                prefix = bs->bs_controller;
        else
                prefix = __progname;

        for (i = 0; i < bs->bs_msg_count; i++)
                fprintf(bs->bs_msgs[i].bm_type == BIO_MSG_INFO ?
                    stdout : stderr, "%s: %s\n", prefix, bs->bs_msgs[i].bm_msg);

        if (bs->bs_status == BIO_STATUS_ERROR) {
                if (bs->bs_msg_count == 0)
                        errx(1, "unknown error");
                else
                        exit(1);
        }
}

void
bio_inq(char *name)
{
        char                    *status, *cache;
        char                    size[64], scsiname[16], volname[32];
        char                    percent[20], seconds[20];
        int                     i, d, volheader, hotspare, unused;
        char                    encname[16], serial[32];
        struct bioc_inq         bi;
        struct bioc_vol         bv;
        struct bioc_disk        bd;

        memset(&bi, 0, sizeof(bi));

        bi.bi_bio.bio_cookie = bio_cookie;

        if (ioctl(devh, BIOCINQ, &bi) == -1) {
                if (errno == ENOTTY)
                        bio_diskinq(name);
                else
                        err(1, "BIOCINQ");
                return;
        }

        bio_status(&bi.bi_bio.bio_status);

        volheader = 0;
        for (i = 0; i < bi.bi_novol; i++) {
                memset(&bv, 0, sizeof(bv));
                bv.bv_bio.bio_cookie = bio_cookie;
                bv.bv_volid = i;
                bv.bv_percent = -1;
                bv.bv_seconds = 0;

                if (ioctl(devh, BIOCVOL, &bv) == -1)
                        err(1, "BIOCVOL");

                bio_status(&bv.bv_bio.bio_status);

                if (name && strcmp(name, bv.bv_dev) != 0)
                        continue;

                if (!volheader) {
                        volheader = 1;
                        printf("%-11s %-10s %14s %-8s\n",
                            "Volume", "Status", "Size", "Device");
                }

                percent[0] = '\0';
                seconds[0] = '\0';
                if (bv.bv_percent != -1)
                        snprintf(percent, sizeof percent,
                            " %d%% done", bv.bv_percent);
                if (bv.bv_seconds)
                        snprintf(seconds, sizeof seconds,
                            " %u seconds", bv.bv_seconds);
                switch (bv.bv_status) {
                case BIOC_SVONLINE:
                        status = BIOC_SVONLINE_S;
                        break;
                case BIOC_SVOFFLINE:
                        status = BIOC_SVOFFLINE_S;
                        break;
                case BIOC_SVDEGRADED:
                        status = BIOC_SVDEGRADED_S;
                        break;
                case BIOC_SVBUILDING:
                        status = BIOC_SVBUILDING_S;
                        break;
                case BIOC_SVREBUILD:
                        status = BIOC_SVREBUILD_S;
                        break;
                case BIOC_SVSCRUB:
                        status = BIOC_SVSCRUB_S;
                        break;
                case BIOC_SVINVALID:
                default:
                        status = BIOC_SVINVALID_S;
                }
                switch (bv.bv_cache) {
                case BIOC_CVWRITEBACK:
                        cache = BIOC_CVWRITEBACK_S;
                        break;
                case BIOC_CVWRITETHROUGH:
                        cache = BIOC_CVWRITETHROUGH_S;
                        break;
                case BIOC_CVUNKNOWN:
                default:
                        cache = BIOC_CVUNKNOWN_S;
                }

                snprintf(volname, sizeof volname, "%s %u",
                    bi.bi_dev, bv.bv_volid);

                unused = 0;
                hotspare = 0;
                if (bv.bv_level == -1 && bv.bv_nodisk == 1)
                        hotspare = 1;
                else if (bv.bv_level == -2 && bv.bv_nodisk == 1)
                        unused = 1;
                else {
                        if (human)
                                fmt_scaled(bv.bv_size, size);
                        else
                                snprintf(size, sizeof size, "%14llu",
                                    bv.bv_size);
                        printf("%11s %-10s %14s %-7s ",
                            volname, status, size, bv.bv_dev);
                        switch (bv.bv_level) {
                        case 'C':
                                printf("CRYPTO%s%s\n",
                                    percent, seconds);
                                break;
                        case 'c':
                                printf("CONCAT%s%s\n",
                                    percent, seconds);
                                break;
                        case 0x1C:
                        case 0x1E:
                                printf("RAID%X%s%s %s\n",
                                    bv.bv_level, percent, seconds, cache);
                                break;
                        default:
                                printf("RAID%u%s%s %s\n",
                                    bv.bv_level, percent, seconds, cache);
                                break;
                        }
                        
                }

                for (d = 0; d < bv.bv_nodisk; d++) {
                        memset(&bd, 0, sizeof(bd));
                        bd.bd_bio.bio_cookie = bio_cookie;
                        bd.bd_diskid = d;
                        bd.bd_volid = i;
                        bd.bd_patrol.bdp_percent = -1;
                        bd.bd_patrol.bdp_seconds = 0;

                        if (ioctl(devh, BIOCDISK, &bd) == -1)
                                err(1, "BIOCDISK");
                
                        bio_status(&bd.bd_bio.bio_status);

                        switch (bd.bd_status) {
                        case BIOC_SDONLINE:
                                status = BIOC_SDONLINE_S;
                                break;
                        case BIOC_SDOFFLINE:
                                status = BIOC_SDOFFLINE_S;
                                break;
                        case BIOC_SDFAILED:
                                status = BIOC_SDFAILED_S;
                                break;
                        case BIOC_SDREBUILD:
                                status = BIOC_SDREBUILD_S;
                                break;
                        case BIOC_SDHOTSPARE:
                                status = BIOC_SDHOTSPARE_S;
                                break;
                        case BIOC_SDUNUSED:
                                status = BIOC_SDUNUSED_S;
                                break;
                        case BIOC_SDSCRUB:
                                status = BIOC_SDSCRUB_S;
                                break;
                        case BIOC_SDINVALID:
                        default:
                                status = BIOC_SDINVALID_S;
                        }

                        if (hotspare || unused)
                                ;       /* use volname from parent volume */
                        else
                                snprintf(volname, sizeof volname, "    %3u",
                                    bd.bd_diskid);

                        if ((bv.bv_level == 'C' || bv.bv_level == 0x1C) &&
                            bd.bd_size == 0)
                                snprintf(size, sizeof size, "%14s", "key disk");
                        else if (human)
                                fmt_scaled(bd.bd_size, size);
                        else
                                snprintf(size, sizeof size, "%14llu",
                                    bd.bd_size);
                        snprintf(scsiname, sizeof scsiname,
                            "%u:%u.%u",
                            bd.bd_channel, bd.bd_target, bd.bd_lun);
                        if (bd.bd_procdev[0])
                                strlcpy(encname, bd.bd_procdev, sizeof encname);
                        else
                                strlcpy(encname, "noencl", sizeof encname);
                        if (bd.bd_serial[0])
                                strlcpy(serial, bd.bd_serial, sizeof serial);
                        else
                                strlcpy(serial, "unknown serial", sizeof serial);

                        percent[0] = '\0';
                        seconds[0] = '\0';
                        if (bd.bd_patrol.bdp_percent != -1)
                                snprintf(percent, sizeof percent,
                                    " patrol %d%% done", bd.bd_patrol.bdp_percent);
                        if (bd.bd_patrol.bdp_seconds)
                                snprintf(seconds, sizeof seconds,
                                    " %u seconds", bd.bd_patrol.bdp_seconds);

                        printf("%11s %-10s %14s %-7s %-6s <%s>\n",
                            volname, status, size, scsiname, encname,
                            bd.bd_vendor);
                        if (verbose)
                                printf("%11s %-10s %14s %-7s %-6s '%s'%s%s\n",
                                    "", "", "", "", "", serial, percent, seconds);
                }
        }
}

void
bio_alarm(char *arg)
{
        struct bioc_alarm       ba;

        memset(&ba, 0, sizeof(ba));
        ba.ba_bio.bio_cookie = bio_cookie;

        switch (arg[0]) {
        case 'q': /* silence alarm */
                /* FALLTHROUGH */
        case 's':
                ba.ba_opcode = BIOC_SASILENCE;
                break;

        case 'e': /* enable alarm */
                ba.ba_opcode = BIOC_SAENABLE;
                break;

        case 'd': /* disable alarm */
                ba.ba_opcode = BIOC_SADISABLE;
                break;

        case 't': /* test alarm */
                ba.ba_opcode = BIOC_SATEST;
                break;

        case 'g': /* get alarm state */
                ba.ba_opcode = BIOC_GASTATUS;
                break;

        default:
                errx(1, "invalid alarm function: %s", arg);
        }

        if (ioctl(devh, BIOCALARM, &ba) == -1)
                err(1, "BIOCALARM");

        bio_status(&ba.ba_bio.bio_status);

        if (arg[0] == 'g')
                printf("alarm is currently %s\n",
                    ba.ba_status ? "enabled" : "disabled");
}

int
bio_getvolbyname(char *name)
{
        int                     id = -1, i;
        struct bioc_inq         bi;
        struct bioc_vol         bv;

        memset(&bi, 0, sizeof(bi));
        bi.bi_bio.bio_cookie = bio_cookie;
        if (ioctl(devh, BIOCINQ, &bi) == -1)
                err(1, "BIOCINQ");

        bio_status(&bi.bi_bio.bio_status);

        for (i = 0; i < bi.bi_novol; i++) {
                memset(&bv, 0, sizeof(bv));
                bv.bv_bio.bio_cookie = bio_cookie;
                bv.bv_volid = i;
                if (ioctl(devh, BIOCVOL, &bv) == -1)
                        err(1, "BIOCVOL");

                bio_status(&bv.bv_bio.bio_status);

                if (name && strcmp(name, bv.bv_dev) != 0)
                        continue;
                id = i;
                break;
        }

        return (id);
}

void
bio_setstate(char *arg, int status, char *devicename)
{
        struct bioc_setstate    bs;
        struct locator          location;
        struct stat             sb;
        const char              *errstr;

        memset(&bs, 0, sizeof(bs));
        if (stat(arg, &sb) == -1) {
                /* use CTL */
                errstr = str2locator(arg, &location);
                if (errstr)
                        errx(1, "Target %s: %s", arg, errstr);
                bs.bs_channel = location.channel;
                bs.bs_target = location.target;
                bs.bs_lun = location.lun;
        } else {
                /* use other id */
                bs.bs_other_id = sb.st_rdev;
                bs.bs_other_id_type = BIOC_SSOTHER_DEVT;
        }

        bs.bs_bio.bio_cookie = bio_cookie;
        bs.bs_status = status;

        if (status != BIOC_SSHOTSPARE) {
                /* make sure user supplied a sd device */
                bs.bs_volid = bio_getvolbyname(devicename);
                if (bs.bs_volid == -1)
                        errx(1, "invalid device %s", devicename);
        }

        if (ioctl(devh, BIOCSETSTATE, &bs) == -1)
                err(1, "BIOCSETSTATE");

        bio_status(&bs.bs_bio.bio_status);
}

void
bio_setblink(char *name, char *arg, int blink)
{
        struct locator          location;
        struct bioc_blink       bb;
        struct bioc_inq         bi;
        struct bioc_vol         bv;
        struct bioc_disk        bd;
        const char              *errstr;
        int                     v, d, rv;

        errstr = str2locator(arg, &location);
        if (errstr)
                errx(1, "Target %s: %s", arg, errstr);

        /* try setting blink on the device directly */
        memset(&bb, 0, sizeof(bb));
        bb.bb_bio.bio_cookie = bio_cookie;
        bb.bb_status = blink;
        bb.bb_target = location.target;
        bb.bb_channel = location.channel;
        rv = ioctl(devh, BIOCBLINK, &bb);

        if (rv == 0 && bb.bb_bio.bio_status.bs_status == BIO_STATUS_UNKNOWN)
                return;

        if (rv == 0 && bb.bb_bio.bio_status.bs_status == BIO_STATUS_SUCCESS) {
                bio_status(&bb.bb_bio.bio_status);
                return;
        }

        /* if the blink didn't work, try to find something that will */

        memset(&bi, 0, sizeof(bi));
        bi.bi_bio.bio_cookie = bio_cookie;
        if (ioctl(devh, BIOCINQ, &bi) == -1)
                err(1, "BIOCINQ");

        bio_status(&bi.bi_bio.bio_status);

        for (v = 0; v < bi.bi_novol; v++) {
                memset(&bv, 0, sizeof(bv));
                bv.bv_bio.bio_cookie = bio_cookie;
                bv.bv_volid = v;
                if (ioctl(devh, BIOCVOL, &bv) == -1)
                        err(1, "BIOCVOL");

                bio_status(&bv.bv_bio.bio_status);

                if (name && strcmp(name, bv.bv_dev) != 0)
                        continue;

                for (d = 0; d < bv.bv_nodisk; d++) {
                        memset(&bd, 0, sizeof(bd));
                        bd.bd_bio.bio_cookie = bio_cookie;
                        bd.bd_volid = v;
                        bd.bd_diskid = d;

                        if (ioctl(devh, BIOCDISK, &bd) == -1)
                                err(1, "BIOCDISK");

                        bio_status(&bd.bd_bio.bio_status);

                        if (bd.bd_channel == location.channel &&
                            bd.bd_target == location.target &&
                            bd.bd_lun == location.lun) {
                                if (bd.bd_procdev[0] != '\0')
                                        bio_blink(bd.bd_procdev,
                                            location.target, blink);
                                else
                                        warnx("Disk %s is not in an enclosure",
                                            arg);
                                return;
                        }
                }
        }

        warnx("Disk %s does not exist", arg);
}

void
bio_blink(char *enclosure, int target, int blinktype)
{
        int                     bioh;
        struct bio_locate       bl;
        struct bioc_blink       blink;

        bioh = open("/dev/bio", O_RDWR);
        if (bioh == -1)
                err(1, "Can't open %s", "/dev/bio");

        memset(&bl, 0, sizeof(bl));
        bl.bl_name = enclosure;
        if (ioctl(bioh, BIOCLOCATE, &bl) == -1)
                errx(1, "Can't locate %s device via %s", enclosure, "/dev/bio");

        memset(&blink, 0, sizeof(blink));
        blink.bb_bio.bio_cookie = bio_cookie;
        blink.bb_status = blinktype;
        blink.bb_target = target;

        if (ioctl(bioh, BIOCBLINK, &blink) == -1)
                err(1, "BIOCBLINK");

        bio_status(&blink.bb_bio.bio_status);

        close(bioh);
}

void
bio_createraid(u_int16_t level, char *dev_list, char *key_disk)
{
        struct bioc_createraid  create;
        struct sr_crypto_kdfinfo kdfinfo;
        struct sr_crypto_pbkdf  kdfhint;
        struct stat             sb;
        int                     rv, no_dev, fd;
        dev_t                   *dt;
        u_int16_t               min_disks = 0;

        if (!dev_list)
                errx(1, "no devices specified");

        dt = calloc(1, BIOC_CRMAXLEN);
        if (!dt)
                err(1, "not enough memory for dev_t list");

        no_dev = bio_parse_devlist(dev_list, dt);

        switch (level) {
        case 0:
                min_disks = 2;
                break;
        case 1:
                min_disks = 2;
                break;
        case 5:
                min_disks = 3;
                break;
        case 'C':
        case 0x1C:
                min_disks = 1;
                break;
        case 'c':
                min_disks = 1;
                break;
        default:
                errx(1, "unsupported RAID level");
        }

        if (no_dev < min_disks)
                errx(1, "not enough disks");

        /* for crypto raid we only allow one single chunk */
        if (level == 'C' && no_dev != min_disks)
                errx(1, "not exactly one partition");

        memset(&create, 0, sizeof(create));
        create.bc_bio.bio_cookie = bio_cookie;
        create.bc_level = level;
        create.bc_dev_list_len = no_dev * sizeof(dev_t);
        create.bc_dev_list = dt;
        create.bc_flags = BIOC_SCDEVT | cflags;
        create.bc_key_disk = NODEV;

        if ((level == 'C' || level == 0x1C) && key_disk == NULL) {

                memset(&kdfinfo, 0, sizeof(kdfinfo));
                memset(&kdfhint, 0, sizeof(kdfhint));

                create.bc_flags |= BIOC_SCNOAUTOASSEMBLE;

                create.bc_opaque = &kdfhint;
                create.bc_opaque_size = sizeof(kdfhint);
                create.bc_opaque_flags = BIOC_SOOUT;

                /* try to get KDF hint */
                if (ioctl(devh, BIOCCREATERAID, &create) == -1)
                        err(1, "ioctl");

                bio_status(&create.bc_bio.bio_status);

                if (create.bc_opaque_status == BIOC_SOINOUT_OK) {
                        bio_kdf_derive(&kdfinfo, &kdfhint, "Passphrase: ", 0);
                        memset(&kdfhint, 0, sizeof(kdfhint));
                } else {
                        bio_kdf_generate(&kdfinfo);
                }

                create.bc_opaque = &kdfinfo;
                create.bc_opaque_size = sizeof(kdfinfo);
                create.bc_opaque_flags = BIOC_SOIN;

        } else if ((level == 'C' || level == 0x1C) && key_disk != NULL) {

                /* Get device number for key disk. */
                fd = opendev(key_disk, O_RDONLY, OPENDEV_BLCK, NULL);
                if (fd == -1)
                        err(1, "could not open %s", key_disk);
                if (fstat(fd, &sb) == -1) {
                        int saved_errno = errno;
                        close(fd);
                        errc(1, saved_errno, "could not stat %s", key_disk);
                }
                close(fd);
                create.bc_key_disk = sb.st_rdev;

                memset(&kdfinfo, 0, sizeof(kdfinfo));

                kdfinfo.genkdf.len = sizeof(kdfinfo.genkdf);
                kdfinfo.genkdf.type = SR_CRYPTOKDFT_KEYDISK;
                kdfinfo.len = sizeof(kdfinfo);
                kdfinfo.flags = SR_CRYPTOKDF_HINT;

                create.bc_opaque = &kdfinfo;
                create.bc_opaque_size = sizeof(kdfinfo);
                create.bc_opaque_flags = BIOC_SOIN;

        }

        rv = ioctl(devh, BIOCCREATERAID, &create);
        explicit_bzero(&kdfinfo, sizeof(kdfinfo));
        if (rv == -1)
                err(1, "BIOCCREATERAID");

        bio_status(&create.bc_bio.bio_status);

        free(dt);
}

void
bio_kdf_derive(struct sr_crypto_kdfinfo *kdfinfo, struct sr_crypto_pbkdf
    *kdfhint, char* prompt, int verify)
{
        if (!kdfinfo)
                errx(1, "invalid KDF info");
        if (!kdfhint)
                errx(1, "invalid KDF hint");

        if (kdfhint->generic.len != sizeof(*kdfhint))
                errx(1, "KDF hint has invalid size");

        kdfinfo->flags = SR_CRYPTOKDF_KEY;
        kdfinfo->len = sizeof(*kdfinfo);

        derive_key(kdfhint->generic.type, kdfhint->rounds,
            kdfinfo->maskkey, sizeof(kdfinfo->maskkey),
            kdfhint->salt, sizeof(kdfhint->salt),
            prompt, verify);
}

void
bio_kdf_generate(struct sr_crypto_kdfinfo *kdfinfo)
{
        if (!kdfinfo)
                errx(1, "invalid KDF info");

        if (rflag == -1)
                rflag = bcrypt_pbkdf_autorounds();

        kdfinfo->pbkdf.generic.len = sizeof(kdfinfo->pbkdf);
        kdfinfo->pbkdf.generic.type = SR_CRYPTOKDFT_BCRYPT_PBKDF;
        kdfinfo->pbkdf.rounds = rflag;

        kdfinfo->flags = SR_CRYPTOKDF_KEY | SR_CRYPTOKDF_HINT;
        kdfinfo->len = sizeof(*kdfinfo);

        /* generate salt */
        arc4random_buf(kdfinfo->pbkdf.salt, sizeof(kdfinfo->pbkdf.salt));

        derive_key(kdfinfo->pbkdf.generic.type, kdfinfo->pbkdf.rounds,
            kdfinfo->maskkey, sizeof(kdfinfo->maskkey),
            kdfinfo->pbkdf.salt, sizeof(kdfinfo->pbkdf.salt),
            "New passphrase: ", interactive);
}

int
bio_parse_devlist(char *lst, dev_t *dt)
{
        char                    *s, *e;
        u_int32_t               sz = 0;
        int                     no_dev = 0, i, x;
        struct stat             sb;
        char                    dev[PATH_MAX];
        int                     fd;

        if (!lst)
                errx(1, "invalid device list");

        s = e = lst;
        /* make sure we have a valid device list like /dev/sdNa,/dev/sdNNa */
        while (*e != '\0') {
                if (*e == ',')
                        s = e + 1;
                else if (*(e + 1) == '\0' || *(e + 1) == ',') {
                        /* got one */
                        sz = e - s + 1;
                        strlcpy(dev, s, sz + 1);
                        fd = opendev(dev, O_RDONLY, OPENDEV_BLCK, NULL);
                        if (fd == -1)
                                err(1, "could not open %s", dev);
                        if (fstat(fd, &sb) == -1) {
                                int saved_errno = errno;
                                close(fd);
                                errc(1, saved_errno, "could not stat %s", dev);
                        }
                        close(fd);
                        dt[no_dev] = sb.st_rdev;
                        no_dev++;
                        if (no_dev > (int)(BIOC_CRMAXLEN / sizeof(dev_t)))
                                errx(1, "too many devices on device list");
                }
                e++;
        }

        for (i = 0; i < no_dev; i++)
                for (x = 0; x < no_dev; x++)
                        if (dt[i] == dt[x] && x != i)
                                errx(1, "duplicate device in list");

        return (no_dev);
}

u_int32_t
bio_createflags(char *lst)
{
        char                    *s, *e, fs[32];
        u_int32_t               sz = 0;
        u_int32_t               flags = 0;

        if (!lst)
                errx(1, "invalid flags list");

        s = e = lst;
        /* make sure we have a valid flags list like force,noassemeble */
        while (*e != '\0') {
                if (*e == ',')
                        s = e + 1;
                else if (*(e + 1) == '\0' || *(e + 1) == ',') {
                        /* got one */
                        sz = e - s + 1;
                        switch (s[0]) {
                        case 'f':
                                flags |= BIOC_SCFORCE;
                                break;
                        case 'n':
                                flags |= BIOC_SCNOAUTOASSEMBLE;
                                break;
                        default:
                                strlcpy(fs, s, sz + 1);
                                errx(1, "invalid flag %s", fs);
                        }
                }
                e++;
        }

        return (flags);
}

void
bio_deleteraid(char *dev)
{
        struct bioc_deleteraid  bd;
        memset(&bd, 0, sizeof(bd));

        bd.bd_bio.bio_cookie = bio_cookie;
        /* XXX make this a dev_t instead of a string */
        strlcpy(bd.bd_dev, dev, sizeof bd.bd_dev);
        if (ioctl(devh, BIOCDELETERAID, &bd) == -1)
                err(1, "BIOCDELETERAID");

        bio_status(&bd.bd_bio.bio_status);
}

void
bio_changepass(char *dev)
{
        struct bioc_discipline bd;
        struct sr_crypto_kdfpair kdfpair;
        struct sr_crypto_kdfinfo kdfinfo1, kdfinfo2;
        struct sr_crypto_pbkdf kdfhint;
        int rv;

        memset(&bd, 0, sizeof(bd));
        memset(&kdfhint, 0, sizeof(kdfhint));
        memset(&kdfinfo1, 0, sizeof(kdfinfo1));
        memset(&kdfinfo2, 0, sizeof(kdfinfo2));

        /* XXX use dev_t instead of string. */
        strlcpy(bd.bd_dev, dev, sizeof(bd.bd_dev));
        bd.bd_cmd = SR_IOCTL_GET_KDFHINT;
        bd.bd_size = sizeof(kdfhint);
        bd.bd_data = &kdfhint;

        if (ioctl(devh, BIOCDISCIPLINE, &bd) == -1)
                err(1, "BIOCDISCIPLINE");

        bio_status(&bd.bd_bio.bio_status);

        /* Current passphrase. */
        bio_kdf_derive(&kdfinfo1, &kdfhint, "Old passphrase: ", 0);

        if (rflag == -1) {
                rflag = bcrypt_pbkdf_autorounds();

                /* Use previous number of rounds for the same KDF if higher. */
                if (kdfhint.generic.type == SR_CRYPTOKDFT_BCRYPT_PBKDF &&
                    rflag < kdfhint.rounds)
                        rflag = kdfhint.rounds;
        }

        /* New passphrase. */
        bio_kdf_generate(&kdfinfo2);

        kdfpair.kdfinfo1 = &kdfinfo1;
        kdfpair.kdfsize1 = sizeof(kdfinfo1);
        kdfpair.kdfinfo2 = &kdfinfo2;
        kdfpair.kdfsize2 = sizeof(kdfinfo2);

        bd.bd_cmd = SR_IOCTL_CHANGE_PASSPHRASE;
        bd.bd_size = sizeof(kdfpair);
        bd.bd_data = &kdfpair;

        rv = ioctl(devh, BIOCDISCIPLINE, &bd);

        memset(&kdfhint, 0, sizeof(kdfhint));
        explicit_bzero(&kdfinfo1, sizeof(kdfinfo1));
        explicit_bzero(&kdfinfo2, sizeof(kdfinfo2));

        if (rv == -1)
                err(1, "BIOCDISCIPLINE");

        bio_status(&bd.bd_bio.bio_status);
}

#define BIOCTL_VIS_NBUF         4
#define BIOCTL_VIS_BUFLEN       80

char *
bio_vis(char *s)
{
        static char      rbuf[BIOCTL_VIS_NBUF][BIOCTL_VIS_BUFLEN];
        static uint      idx = 0;
        char            *buf;

        buf = rbuf[idx++];
        if (idx == BIOCTL_VIS_NBUF)
                idx = 0;

        strnvis(buf, s, BIOCTL_VIS_BUFLEN, VIS_NL|VIS_CSTYLE);
        return (buf);
}

void
bio_diskinq(char *sd_dev)
{
        struct dk_inquiry       di;

        if (ioctl(devh, DIOCINQ, &di) == -1)
                err(1, "DIOCINQ");

        printf("%s: <%s, %s, %s>, serial %s\n", sd_dev, bio_vis(di.vendor),
            bio_vis(di.product), bio_vis(di.revision), bio_vis(di.serial));
}

void
bio_patrol(char *arg)
{
        struct bioc_patrol      bp;
        struct timing           timing;
        const char              *errstr;

        memset(&bp, 0, sizeof(bp));
        bp.bp_bio.bio_cookie = bio_cookie;

        switch (arg[0]) {
        case 'a':
                bp.bp_opcode = BIOC_SPAUTO;
                break;

        case 'm':
                bp.bp_opcode = BIOC_SPMANUAL;
                break;

        case 'd':
                bp.bp_opcode = BIOC_SPDISABLE;
                break;

        case 'g': /* get patrol state */
                bp.bp_opcode = BIOC_GPSTATUS;
                break;

        case 's': /* start/stop patrol */
                if (strncmp("sta", arg, 3) == 0)
                        bp.bp_opcode = BIOC_SPSTART;
                else
                        bp.bp_opcode = BIOC_SPSTOP;
                break;

        default:
                errx(1, "invalid patrol function: %s", arg);
        }

        switch (arg[0]) {
        case 'a':
                errstr = str2patrol(arg, &timing);
                if (errstr)
                        errx(1, "Patrol %s: %s", arg, errstr);
                bp.bp_autoival = timing.interval;
                bp.bp_autonext = timing.start;
                break;
        }

        if (ioctl(devh, BIOCPATROL, &bp) == -1)
                err(1, "BIOCPATROL");

        bio_status(&bp.bp_bio.bio_status);

        if (arg[0] == 'g') {
                const char *mode, *status;
                char interval[40];

                interval[0] = '\0';

                switch (bp.bp_mode) {
                case BIOC_SPMAUTO:
                        mode = "auto";
                        snprintf(interval, sizeof interval,
                            " interval=%d next=%d", bp.bp_autoival,
                            bp.bp_autonext - bp.bp_autonow);
                        break;
                case BIOC_SPMMANUAL:
                        mode = "manual";
                        break;
                case BIOC_SPMDISABLED:
                        mode = "disabled";
                        break;
                default:
                        mode = "unknown";
                        break;
                }
                switch (bp.bp_status) {
                case BIOC_SPSSTOPPED:
                        status = "stopped";
                        break;
                case BIOC_SPSREADY:
                        status = "ready";
                        break;
                case BIOC_SPSACTIVE:
                        status = "active";
                        break;
                case BIOC_SPSABORTED:
                        status = "aborted";
                        break;
                default:
                        status = "unknown";
                        break;
                }
                printf("patrol mode: %s%s\n", mode, interval);
                printf("patrol status: %s\n", status);
        }
}

/*
 * Measure this system's performance by measuring the time for 100 rounds.
 * We are aiming for something that takes around 1s.
 */
int
bcrypt_pbkdf_autorounds(void)
{
        struct timespec before, after;
        char buf[SR_CRYPTO_MAXKEYBYTES], salt[128];
        int r = 100;
        int duration;

        clock_gettime(CLOCK_THREAD_CPUTIME_ID, &before);
        if (bcrypt_pbkdf("testpassword", strlen("testpassword"),
            salt, sizeof(salt), buf, sizeof(buf), r) != 0)
                errx(1, "bcrypt pbkdf failed");
        clock_gettime(CLOCK_THREAD_CPUTIME_ID, &after);

        duration = after.tv_sec - before.tv_sec;
        duration *= 1000000;
        duration += (after.tv_nsec - before.tv_nsec) / 1000;

        duration /= r;
        r = 1000000 / duration;

        if (r < 16)
                r = 16;

        return r;
}

void
derive_key(u_int32_t type, int rounds, u_int8_t *key, size_t keysz,
    u_int8_t *salt, size_t saltsz, char *prompt, int verify)
{
        FILE            *f;
        size_t          pl;
        struct stat     sb;
        char            passphrase[1024], verifybuf[1024];
        int             rpp_flag = RPP_ECHO_OFF;

        if (!key)
                errx(1, "Invalid key");
        if (!salt)
                errx(1, "Invalid salt");

        if (type != SR_CRYPTOKDFT_PKCS5_PBKDF2 &&
            type != SR_CRYPTOKDFT_BCRYPT_PBKDF)
                errx(1, "unknown KDF type %d", type);

        if (rounds < (type == SR_CRYPTOKDFT_PKCS5_PBKDF2 ? 1000 : 16))
                errx(1, "number of KDF rounds is too small: %d", rounds);

        /* get passphrase */
        if (passfile) {
                if ((f = fopen(passfile, "r")) == NULL)
                        err(1, "invalid passphrase file");

                if (fstat(fileno(f), &sb) == -1)
                        err(1, "can't stat passphrase file");
                if (sb.st_uid != 0)
                        errx(1, "passphrase file must be owned by root");
                if ((sb.st_mode & ~S_IFMT) != (S_IRUSR | S_IWUSR))
                        errx(1, "passphrase file has the wrong permissions");

                if (fgets(passphrase, sizeof(passphrase), f) == NULL)
                        err(1, "can't read passphrase file");
                pl = strlen(passphrase);
                if (pl > 0 && passphrase[pl - 1] == '\n')
                        passphrase[pl - 1] = '\0';
                else
                        errx(1, "invalid passphrase length");

                fclose(f);
        } else {
                rpp_flag |= interactive ? RPP_REQUIRE_TTY : RPP_STDIN;

 retry:
                if (readpassphrase(prompt, passphrase, sizeof(passphrase),
                    rpp_flag) == NULL)
                        err(1, "unable to read passphrase");
                if (*passphrase == '\0') {
                        warnx("invalid passphrase length");
                        if (interactive)
                                goto retry;
                        exit(1);
                }
        }

        if (verify && !passfile) {
                /* request user to re-type it */
                if (readpassphrase("Re-type passphrase: ", verifybuf,
                    sizeof(verifybuf), rpp_flag) == NULL) {
                        explicit_bzero(passphrase, sizeof(passphrase));
                        err(1, "unable to read passphrase");
                }
                if ((strlen(passphrase) != strlen(verifybuf)) ||
                    (strcmp(passphrase, verifybuf) != 0)) {
                        explicit_bzero(passphrase, sizeof(passphrase));
                        explicit_bzero(verifybuf, sizeof(verifybuf));
                        if (interactive) {
                                warnx("Passphrases did not match, try again");
                                goto retry;
                        }
                        errx(1, "Passphrases did not match");
                }
                /* forget the re-typed one */
                explicit_bzero(verifybuf, sizeof(verifybuf));
        }

        /* derive key from passphrase */
        if (type == SR_CRYPTOKDFT_PKCS5_PBKDF2) {
                if (verbose)
                        printf("Deriving key using PKCS#5 PBKDF2 with %i rounds...\n",
                            rounds);
                if (pkcs5_pbkdf2(passphrase, strlen(passphrase), salt, saltsz,
                    key, keysz, rounds) != 0)
                        errx(1, "pkcs5_pbkdf2 failed");
        } else if (type == SR_CRYPTOKDFT_BCRYPT_PBKDF) {
                if (verbose)
                        printf("Deriving key using bcrypt PBKDF with %i rounds...\n",
                            rounds);
                if (bcrypt_pbkdf(passphrase, strlen(passphrase), salt, saltsz,
                    key, keysz, rounds) != 0)
                        errx(1, "bcrypt_pbkdf failed");
        } else {
                errx(1, "unknown KDF type %d", type);
        }

        /* forget passphrase */
        explicit_bzero(passphrase, sizeof(passphrase));
}