[BACK]Return to bioctl.c CVS log [TXT][DIR] Up to [local] / src / sbin / bioctl

File: [local] / src / sbin / bioctl / bioctl.c (download)

Revision 1.157, Sat Oct 7 12:20:10 2023 UTC (8 months ago) by kn
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, HEAD
Changes since 1.156: +7 -1 lines

Retry on empty passphrase

They must not be empty, or else creation/unlock fails (and boot loaders
would not be able to abort and drop back to the boot> prompt).

[-p passfile] handles this with "invalid passphrase length", so align
the interactive prompt and retry there.

-s remains a one-shot whilst getting a better error message.

This is user friendlier and fixes the last installer "bug" on my list
wrt. disk encryption where hitting Enter twice at the passphrase prompt
would abort bioctl(8) and thus the installation.

OK deraadt

/* $OpenBSD: bioctl.c,v 1.157 2023/10/07 12:20:10 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"
		"       %s [-dhiPqsv] "
		"[-C flag[,...]] [-c raidlevel] [-k keydisk]\n"
		"\t[-l chunk[,...]] "
		"[-O device | channel:target[.lun]]\n"
		"\t[-p passfile] [-R chunk | channel:target[.lun]]\n"
		"\t[-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' && 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));
}