1029 lines
24 KiB
C
1029 lines
24 KiB
C
/* $NetBSD: bioctl.c,v 1.17 2015/01/16 20:12:28 christos Exp $ */
|
|
/* $OpenBSD: bioctl.c,v 1.52 2007/03/20 15:26:06 jmc Exp $ */
|
|
|
|
/*
|
|
* Copyright (c) 2007, 2008 Juan Romero Pardines
|
|
* 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/cdefs.h>
|
|
|
|
#ifndef lint
|
|
__RCSID("$NetBSD: bioctl.c,v 1.17 2015/01/16 20:12:28 christos Exp $");
|
|
#endif
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/param.h>
|
|
#include <sys/queue.h>
|
|
#include <dev/biovar.h>
|
|
|
|
#include <errno.h>
|
|
#include <err.h>
|
|
#include <fcntl.h>
|
|
#include <util.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <ctype.h>
|
|
#include <util.h>
|
|
|
|
struct command {
|
|
const char *cmd_name;
|
|
const char *arg_names;
|
|
void (*cmd_func)(int, int, char **);
|
|
};
|
|
|
|
struct biotmp {
|
|
struct bioc_inq *bi;
|
|
struct bioc_vol *bv;
|
|
char volname[64];
|
|
int fd;
|
|
int volid;
|
|
int diskid;
|
|
bool format;
|
|
bool show_disknovol;
|
|
};
|
|
|
|
struct locator {
|
|
int channel;
|
|
int target;
|
|
int lun;
|
|
};
|
|
|
|
__dead static void usage(void);
|
|
static void bio_alarm(int, int, char **);
|
|
static void bio_show_common(int, int, char **);
|
|
static int bio_show_volumes(struct biotmp *);
|
|
static void bio_show_disks(struct biotmp *);
|
|
static void bio_setblink(int, int, char **);
|
|
static void bio_blink(int, char *, int, int);
|
|
static void bio_setstate_hotspare(int, int, char **);
|
|
static void bio_setstate_passthru(int, int, char **);
|
|
static void bio_setstate_common(int, char *, struct bioc_setstate *,
|
|
struct locator *);
|
|
static void bio_setstate_consistency(int, int, char **);
|
|
static void bio_volops_create(int, int, char **);
|
|
#ifdef notyet
|
|
static void bio_volops_modify(int, int, char **);
|
|
#endif
|
|
static void bio_volops_remove(int, int, char **);
|
|
|
|
static const char *str2locator(const char *, struct locator *);
|
|
|
|
static struct bio_locate bl;
|
|
static struct command commands[] = {
|
|
{
|
|
"show",
|
|
"[disks] | [volumes]",
|
|
bio_show_common },
|
|
{
|
|
"alarm",
|
|
"[enable] | [disable] | [silence] | [test]",
|
|
bio_alarm },
|
|
{
|
|
"blink",
|
|
"start [channel:target[.lun]] | stop [channel:target[.lun]]",
|
|
bio_setblink },
|
|
{
|
|
"hotspare",
|
|
"add channel:target.lun | remove channel:target.lun",
|
|
bio_setstate_hotspare },
|
|
{
|
|
"passthru",
|
|
"add DISKID channel:target.lun | remove channel:target.lun",
|
|
bio_setstate_passthru },
|
|
{
|
|
"check",
|
|
"start VOLID | stop VOLID",
|
|
bio_setstate_consistency },
|
|
{
|
|
"create",
|
|
"volume VOLID DISKIDs [SIZE] STRIPE RAID_LEVEL channel:target.lun",
|
|
bio_volops_create },
|
|
#ifdef notyet
|
|
{
|
|
"modify",
|
|
"volume VOLID STRIPE RAID_LEVEL channel:target.lun",
|
|
bio_volops_modify },
|
|
#endif
|
|
{
|
|
"remove",
|
|
"volume VOLID channel:target.lun",
|
|
bio_volops_remove },
|
|
|
|
{ NULL, NULL, NULL }
|
|
};
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
char *dvname;
|
|
const char *cmdname;
|
|
int fd = 0, i;
|
|
|
|
/* Must have at least: device command */
|
|
if (argc < 3)
|
|
usage();
|
|
|
|
/* Skip program name, get and skip device name and command */
|
|
setprogname(*argv);
|
|
dvname = argv[1];
|
|
cmdname = argv[2];
|
|
argv += 3;
|
|
argc -= 3;
|
|
|
|
/* Look up and call the command */
|
|
for (i = 0; commands[i].cmd_name != NULL; i++)
|
|
if (strcmp(cmdname, commands[i].cmd_name) == 0)
|
|
break;
|
|
if (commands[i].cmd_name == NULL)
|
|
errx(EXIT_FAILURE, "unknown command: %s", cmdname);
|
|
|
|
/* Locate the device by issuing the BIOCLOCATE ioctl */
|
|
fd = open("/dev/bio", O_RDWR);
|
|
if (fd == -1)
|
|
err(EXIT_FAILURE, "Can't open /dev/bio");
|
|
|
|
bl.bl_name = dvname;
|
|
if (ioctl(fd, BIOCLOCATE, &bl) == -1)
|
|
errx(EXIT_FAILURE, "Can't locate %s device via /dev/bio",
|
|
bl.bl_name);
|
|
|
|
/* and execute the command */
|
|
(*commands[i].cmd_func)(fd, argc, argv);
|
|
|
|
(void)close(fd);
|
|
exit(EXIT_SUCCESS);
|
|
}
|
|
|
|
static void
|
|
usage(void)
|
|
{
|
|
int i;
|
|
|
|
(void)fprintf(stderr, "usage: %s device command [arg [...]]\n",
|
|
getprogname());
|
|
|
|
(void)fprintf(stderr, "Available commands:\n");
|
|
for (i = 0; commands[i].cmd_name != NULL; i++)
|
|
(void)fprintf(stderr, " %s %s\n", commands[i].cmd_name,
|
|
commands[i].arg_names);
|
|
|
|
exit(EXIT_FAILURE);
|
|
/* NOTREACHED */
|
|
}
|
|
|
|
static 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;
|
|
}
|
|
|
|
/*
|
|
* Shows info about available RAID volumes.
|
|
*/
|
|
static int
|
|
bio_show_volumes(struct biotmp *bt)
|
|
{
|
|
struct bioc_vol bv;
|
|
const char *status, *rtypestr, *stripestr;
|
|
char size[64], percent[16], seconds[20];
|
|
char rtype[16], stripe[16], tmp[32];
|
|
|
|
rtypestr = stripestr = NULL;
|
|
|
|
memset(&bv, 0, sizeof(bv));
|
|
bv.bv_cookie = bl.bl_cookie;
|
|
bv.bv_volid = bt->volid;
|
|
bv.bv_percent = -1;
|
|
bv.bv_seconds = -1;
|
|
|
|
if (ioctl(bt->fd, BIOCVOL, &bv) == -1)
|
|
err(EXIT_FAILURE, "BIOCVOL");
|
|
|
|
percent[0] = '\0';
|
|
seconds[0] = '\0';
|
|
if (bv.bv_percent != -1)
|
|
snprintf(percent, sizeof(percent),
|
|
" %3.2f%% done", bv.bv_percent / 10.0);
|
|
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_SVMIGRATING:
|
|
status = BIOC_SVMIGRATING_S;
|
|
break;
|
|
case BIOC_SVSCRUB:
|
|
status = BIOC_SVSCRUB_S;
|
|
break;
|
|
case BIOC_SVCHECKING:
|
|
status = BIOC_SVCHECKING_S;
|
|
break;
|
|
case BIOC_SVINVALID:
|
|
default:
|
|
status = BIOC_SVINVALID_S;
|
|
break;
|
|
}
|
|
|
|
snprintf(bt->volname, sizeof(bt->volname), "%u", bv.bv_volid);
|
|
if (bv.bv_vendor[0])
|
|
snprintf(tmp, sizeof(tmp), "%s %s", bv.bv_dev, bv.bv_vendor);
|
|
else
|
|
snprintf(tmp, sizeof(tmp), "%s", bv.bv_dev);
|
|
|
|
switch (bv.bv_level) {
|
|
case BIOC_SVOL_HOTSPARE:
|
|
rtypestr = "Hot spare";
|
|
stripestr = "N/A";
|
|
break;
|
|
case BIOC_SVOL_PASSTHRU:
|
|
rtypestr = "Pass through";
|
|
stripestr = "N/A";
|
|
break;
|
|
case BIOC_SVOL_RAID01:
|
|
rtypestr = "RAID 0+1";
|
|
break;
|
|
case BIOC_SVOL_RAID10:
|
|
rtypestr = "RAID 1+0";
|
|
break;
|
|
default:
|
|
snprintf(rtype, sizeof(rtype), "RAID %u", bv.bv_level);
|
|
if (bv.bv_level == 1 || bv.bv_stripe_size == 0)
|
|
stripestr = "N/A";
|
|
break;
|
|
}
|
|
|
|
if (rtypestr)
|
|
strlcpy(rtype, rtypestr, sizeof(rtype));
|
|
if (stripestr)
|
|
strlcpy(stripe, stripestr, sizeof(stripe));
|
|
else
|
|
snprintf(stripe, sizeof(stripe), "%uK", bv.bv_stripe_size);
|
|
|
|
humanize_number(size, 5, (int64_t)bv.bv_size, "", HN_AUTOSCALE,
|
|
HN_B | HN_NOSPACE | HN_DECIMAL);
|
|
|
|
printf("%6s %-12s %4s %20s %8s %6s %s%s\n",
|
|
bt->volname, status, size, tmp,
|
|
rtype, stripe, percent, seconds);
|
|
|
|
bt->bv = &bv;
|
|
|
|
return bv.bv_nodisk;
|
|
}
|
|
|
|
/*
|
|
* Shows info about physical disks.
|
|
*/
|
|
static void
|
|
bio_show_disks(struct biotmp *bt)
|
|
{
|
|
struct bioc_disk bd;
|
|
const char *status;
|
|
char size[64], serial[32], scsiname[16];
|
|
|
|
memset(&bd, 0, sizeof(bd));
|
|
bd.bd_cookie = bl.bl_cookie;
|
|
bd.bd_diskid = bt->diskid;
|
|
bd.bd_volid = bt->volid;
|
|
|
|
if (bt->show_disknovol) {
|
|
if (ioctl(bt->fd, BIOCDISK_NOVOL, &bd) == -1)
|
|
err(EXIT_FAILURE, "BIOCDISK_NOVOL");
|
|
if (!bd.bd_disknovol)
|
|
return;
|
|
} else {
|
|
if (ioctl(bt->fd, BIOCDISK, &bd) == -1)
|
|
err(EXIT_FAILURE, "BIOCDISK");
|
|
}
|
|
|
|
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_SDPASSTHRU:
|
|
status = BIOC_SDPASSTHRU_S;
|
|
break;
|
|
case BIOC_SDINVALID:
|
|
default:
|
|
status = BIOC_SDINVALID_S;
|
|
break;
|
|
}
|
|
|
|
if (bt->format)
|
|
snprintf(bt->volname, sizeof(bt->volname),
|
|
"%u:%u", bt->bv->bv_volid, bd.bd_diskid);
|
|
|
|
humanize_number(size, 5, bd.bd_size, "", HN_AUTOSCALE,
|
|
HN_B | HN_NOSPACE | HN_DECIMAL);
|
|
|
|
if (bd.bd_procdev[0])
|
|
snprintf(scsiname, sizeof(scsiname), "%u:%u.%u %s",
|
|
bd.bd_channel, bd.bd_target, bd.bd_lun,
|
|
bd.bd_procdev);
|
|
else
|
|
snprintf(scsiname, sizeof(scsiname), "%u:%u.%u noencl",
|
|
bd.bd_channel, bd.bd_target, bd.bd_lun);
|
|
|
|
if (bd.bd_serial[0])
|
|
strlcpy(serial, bd.bd_serial, sizeof(serial));
|
|
else
|
|
strlcpy(serial, "unknown serial", sizeof(serial));
|
|
|
|
if (bt->format)
|
|
printf("%6s %-12s %4s %20s <%s>\n",
|
|
bt->volname, status, size, scsiname,
|
|
bd.bd_vendor);
|
|
else
|
|
printf("%5d [%-28s] %-12s %-6s %12s\n",
|
|
bt->diskid, bd.bd_vendor, status, size, scsiname);
|
|
|
|
}
|
|
|
|
/*
|
|
* Shows info about volumes/disks.
|
|
*/
|
|
static void
|
|
bio_show_common(int fd, int argc, char **argv)
|
|
{
|
|
struct biotmp *biot;
|
|
struct bioc_inq bi;
|
|
int i, d, ndisks;
|
|
bool show_all, show_disks;
|
|
bool show_vols, show_caps;
|
|
|
|
show_all = show_disks = show_vols = show_caps = false;
|
|
|
|
if (argc > 1)
|
|
usage();
|
|
|
|
if (argv[0]) {
|
|
if (strcmp(argv[0], "disks") == 0)
|
|
show_disks = true;
|
|
else if (strcmp(argv[0], "volumes") == 0)
|
|
show_vols = true;
|
|
else
|
|
usage();
|
|
} else
|
|
show_all = true;
|
|
|
|
memset(&bi, 0, sizeof(bi));
|
|
bi.bi_cookie = bl.bl_cookie;
|
|
|
|
if (ioctl(fd, BIOCINQ, &bi) == -1)
|
|
err(EXIT_FAILURE, "BIOCINQ");
|
|
|
|
/*
|
|
* If there are volumes there's no point to continue.
|
|
*/
|
|
if (show_all || show_vols) {
|
|
if (!bi.bi_novol) {
|
|
warnx("no volumes available");
|
|
return;
|
|
}
|
|
}
|
|
|
|
biot = calloc(1, sizeof(*biot));
|
|
if (!biot)
|
|
err(EXIT_FAILURE, "biotemp calloc");
|
|
|
|
biot->fd = fd;
|
|
biot->bi = &bi;
|
|
/*
|
|
* Go to the disks section if that was specified.
|
|
*/
|
|
if (show_disks)
|
|
goto disks;
|
|
|
|
/*
|
|
* Common code to show only info about volumes and disks
|
|
* associated to them.
|
|
*/
|
|
printf("%6s %-12s %4s %20s %8s %6s\n",
|
|
"Volume", "Status", "Size", "Device/Label",
|
|
"Level", "Stripe");
|
|
printf("=============================================="
|
|
"===============\n");
|
|
|
|
for (i = 0; i < bi.bi_novol; i++) {
|
|
biot->format = true;
|
|
biot->volid = i;
|
|
ndisks = bio_show_volumes(biot);
|
|
if (show_vols)
|
|
continue;
|
|
|
|
for (d = 0; d < ndisks; d++) {
|
|
biot->diskid = d;
|
|
bio_show_disks(biot);
|
|
}
|
|
|
|
}
|
|
goto out;
|
|
|
|
disks:
|
|
/*
|
|
* show info about all disks connected to the raid controller,
|
|
* even if they aren't associated with a volume or raid set.
|
|
*/
|
|
if (show_disks) {
|
|
printf("%5s %-30s %-12s %-6s %12s\n",
|
|
"Disk", "Model/Serial", "Status", "Size", "Location");
|
|
printf("==============================================="
|
|
"======================\n");
|
|
for (d = 0; d < bi.bi_nodisk; d++) {
|
|
biot->show_disknovol = true;
|
|
biot->diskid = d;
|
|
bio_show_disks(biot);
|
|
}
|
|
}
|
|
out:
|
|
free(biot);
|
|
}
|
|
|
|
/*
|
|
* To handle the alarm feature.
|
|
*/
|
|
static void
|
|
bio_alarm(int fd, int argc, char **argv)
|
|
{
|
|
struct bioc_alarm ba;
|
|
bool show = false;
|
|
|
|
memset(&ba, 0, sizeof(ba));
|
|
ba.ba_cookie = bl.bl_cookie;
|
|
|
|
if (argc > 1)
|
|
usage();
|
|
|
|
if (argc == 0) {
|
|
/* show alarm status */
|
|
ba.ba_opcode = BIOC_GASTATUS;
|
|
show = true;
|
|
} else if (strcmp(argv[0], "silence") == 0) {
|
|
/* silence alarm */
|
|
ba.ba_opcode = BIOC_SASILENCE;
|
|
} else if (strcmp(argv[0], "enable") == 0) {
|
|
/* enable alarm */
|
|
ba.ba_opcode = BIOC_SAENABLE;
|
|
} else if (strcmp(argv[0], "disable") == 0) {
|
|
/* disable alarm */
|
|
ba.ba_opcode = BIOC_SADISABLE;
|
|
} else if (strcmp(argv[0], "test") == 0) {
|
|
/* test alarm */
|
|
ba.ba_opcode = BIOC_SATEST;
|
|
} else
|
|
usage();
|
|
|
|
if (ioctl(fd, BIOCALARM, &ba) == -1)
|
|
err(EXIT_FAILURE, "BIOCALARM");
|
|
|
|
if (show)
|
|
printf("alarm is currently %s\n",
|
|
ba.ba_status ? "enabled" : "disabled");
|
|
}
|
|
|
|
/*
|
|
* To add/remove a hotspare disk.
|
|
*/
|
|
static void
|
|
bio_setstate_hotspare(int fd, int argc, char **argv)
|
|
{
|
|
struct bioc_setstate bs;
|
|
struct locator location;
|
|
|
|
memset(&bs, 0, sizeof(bs));
|
|
|
|
if (argc != 2)
|
|
usage();
|
|
|
|
if (strcmp(argv[0], "add") == 0)
|
|
bs.bs_status = BIOC_SSHOTSPARE;
|
|
else if (strcmp(argv[0], "remove") == 0)
|
|
bs.bs_status = BIOC_SSDELHOTSPARE;
|
|
else
|
|
usage();
|
|
|
|
bio_setstate_common(fd, argv[1], &bs, &location);
|
|
}
|
|
|
|
/*
|
|
* To add/remove a pass through disk.
|
|
*/
|
|
static void
|
|
bio_setstate_passthru(int fd, int argc, char **argv)
|
|
{
|
|
struct bioc_setstate bs;
|
|
struct locator location;
|
|
char *endptr;
|
|
bool rem = false;
|
|
|
|
if (argc < 2 || argc > 3)
|
|
usage();
|
|
|
|
memset(&bs, 0, sizeof(bs));
|
|
|
|
if (strcmp(argv[0], "add") == 0) {
|
|
if (argv[1] == NULL || argv[2] == NULL)
|
|
usage();
|
|
|
|
bs.bs_status = BIOC_SSPASSTHRU;
|
|
} else if (strcmp(argv[0], "remove") == 0) {
|
|
if (argv[1] == NULL)
|
|
usage();
|
|
|
|
bs.bs_status = BIOC_SSDELPASSTHRU;
|
|
rem = true;
|
|
} else
|
|
usage();
|
|
|
|
if (rem)
|
|
bio_setstate_common(fd, argv[1], &bs, &location);
|
|
else {
|
|
bs.bs_other_id = (unsigned int)strtoul(argv[1], &endptr, 10);
|
|
if (*endptr != '\0')
|
|
errx(EXIT_FAILURE, "Invalid Volume ID value");
|
|
|
|
bio_setstate_common(fd, argv[2], &bs, &location);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* To start/stop a consistency check in a RAID volume.
|
|
*/
|
|
static void
|
|
bio_setstate_consistency(int fd, int argc, char **argv)
|
|
{
|
|
struct bioc_setstate bs;
|
|
char *endptr;
|
|
|
|
if (argc != 2)
|
|
usage();
|
|
|
|
memset(&bs, 0, sizeof(bs));
|
|
|
|
if (strcmp(argv[0], "start") == 0)
|
|
bs.bs_status = BIOC_SSCHECKSTART_VOL;
|
|
else if (strcmp(argv[0], "stop") == 0)
|
|
bs.bs_status = BIOC_SSCHECKSTOP_VOL;
|
|
else
|
|
usage();
|
|
|
|
bs.bs_volid = (unsigned int)strtoul(argv[1], &endptr, 10);
|
|
if (*endptr != '\0')
|
|
errx(EXIT_FAILURE, "Invalid Volume ID value");
|
|
|
|
bio_setstate_common(fd, NULL, &bs, NULL);
|
|
}
|
|
|
|
static void
|
|
bio_setstate_common(int fd, char *arg, struct bioc_setstate *bs,
|
|
struct locator *location)
|
|
{
|
|
const char *errstr;
|
|
|
|
if (!arg || !location)
|
|
goto send;
|
|
|
|
errstr = str2locator(arg, location);
|
|
if (errstr)
|
|
errx(EXIT_FAILURE, "Target %s: %s", arg, errstr);
|
|
|
|
bs->bs_channel = location->channel;
|
|
bs->bs_target = location->target;
|
|
bs->bs_lun = location->lun;
|
|
|
|
send:
|
|
bs->bs_cookie = bl.bl_cookie;
|
|
|
|
if (ioctl(fd, BIOCSETSTATE, bs) == -1)
|
|
err(EXIT_FAILURE, "BIOCSETSTATE");
|
|
}
|
|
|
|
/*
|
|
* To create a RAID volume.
|
|
*/
|
|
static void
|
|
bio_volops_create(int fd, int argc, char **argv)
|
|
{
|
|
struct bioc_volops bc;
|
|
struct bioc_inq bi;
|
|
struct bioc_disk bd;
|
|
struct locator location;
|
|
uint64_t total_size = 0, disksize = 0;
|
|
int64_t volsize = 0;
|
|
const char *errstr;
|
|
char *endptr, *stripe, levelstr[32];
|
|
char *scsiname, *raid_level, size[64];
|
|
int disk_first = 0, disk_end = 0;
|
|
int i, nfreedisks = 0;
|
|
int user_disks = 0;
|
|
|
|
if (argc < 6 || argc > 7)
|
|
usage();
|
|
|
|
if (strcmp(argv[0], "volume") != 0)
|
|
usage();
|
|
|
|
/*
|
|
* No size requested, use max size depending on RAID level.
|
|
*/
|
|
if (argc == 6) {
|
|
stripe = argv[3];
|
|
raid_level = argv[4];
|
|
scsiname = argv[5];
|
|
} else {
|
|
stripe = argv[4];
|
|
raid_level = argv[5];
|
|
scsiname = argv[6];
|
|
}
|
|
|
|
memset(&bd, 0, sizeof(bd));
|
|
memset(&bc, 0, sizeof(bc));
|
|
memset(&bi, 0, sizeof(bi));
|
|
|
|
bc.bc_cookie = bd.bd_cookie = bi.bi_cookie = bl.bl_cookie;
|
|
bc.bc_opcode = BIOC_VCREATE_VOLUME;
|
|
|
|
bc.bc_volid = (unsigned int)strtoul(argv[1], &endptr, 10);
|
|
if (*endptr != '\0')
|
|
errx(EXIT_FAILURE, "Invalid Volume ID value");
|
|
|
|
if (argc == 7)
|
|
if (dehumanize_number(argv[3], &volsize) == -1
|
|
|| volsize < 0)
|
|
errx(EXIT_FAILURE, "Invalid SIZE value");
|
|
|
|
bc.bc_stripe = (unsigned int)strtoul(stripe, &endptr, 10);
|
|
if (*endptr != '\0')
|
|
errx(EXIT_FAILURE, "Invalid STRIPE size value");
|
|
|
|
bc.bc_level = (unsigned int)strtoul(raid_level, &endptr, 10);
|
|
if (*endptr != '\0')
|
|
errx(EXIT_FAILURE, "Invalid RAID_LEVEL value");
|
|
|
|
errstr = str2locator(scsiname, &location);
|
|
if (errstr)
|
|
errx(EXIT_FAILURE, "Target %s: %s", scsiname, errstr);
|
|
|
|
/*
|
|
* Parse the device list that will be used for the volume,
|
|
* by using a bit field for the disks.
|
|
*/
|
|
if ((isdigit((unsigned char)argv[2][0]) == 0) || argv[2][1] != '-' ||
|
|
(isdigit((unsigned char)argv[2][2]) == 0))
|
|
errx(EXIT_FAILURE, "Invalid DISKIDs value");
|
|
|
|
disk_first = atoi(&argv[2][0]);
|
|
disk_end = atoi(&argv[2][2]);
|
|
|
|
for (i = disk_first; i < disk_end + 1; i++) {
|
|
bc.bc_devmask |= (1 << i);
|
|
user_disks++;
|
|
}
|
|
|
|
/*
|
|
* Find out how many disks are free and how much size we
|
|
* have available for the new volume.
|
|
*/
|
|
if (ioctl(fd, BIOCINQ, &bi) == -1)
|
|
err(EXIT_FAILURE, "BIOCINQ");
|
|
|
|
for (i = 0; i < bi.bi_nodisk; i++) {
|
|
bd.bd_diskid = i;
|
|
if (ioctl(fd, BIOCDISK_NOVOL, &bd) == -1)
|
|
err(EXIT_FAILURE, "BIOCDISK_NOVOL");
|
|
|
|
if (bd.bd_status == BIOC_SDUNUSED) {
|
|
if (i == 0)
|
|
disksize = bd.bd_size;
|
|
|
|
total_size += bd.bd_size;
|
|
nfreedisks++;
|
|
}
|
|
}
|
|
|
|
if (user_disks > nfreedisks)
|
|
errx(EXIT_FAILURE, "specified disks number is higher than "
|
|
"available free disks");
|
|
|
|
/*
|
|
* Basic checks to be sure we don't do something stupid.
|
|
*/
|
|
if (nfreedisks == 0)
|
|
errx(EXIT_FAILURE, "No free disks available");
|
|
|
|
switch (bc.bc_level) {
|
|
case 0: /* RAID 0 requires at least one disk */
|
|
if (argc == 7) {
|
|
if ((uint64_t)volsize > (disksize * user_disks))
|
|
errx(EXIT_FAILURE, "volume size specified "
|
|
"is larger than available on free disks");
|
|
bc.bc_size = (uint64_t)volsize;
|
|
} else
|
|
bc.bc_size = disksize * user_disks;
|
|
|
|
break;
|
|
case 1: /* RAID 1 requires two disks and size is total / 2 */
|
|
if (nfreedisks < 2 || user_disks < 2)
|
|
errx(EXIT_FAILURE, "2 disks are required at least for "
|
|
"this RAID level");
|
|
|
|
/* RAID 1+0 requires three disks at least */
|
|
if (nfreedisks > 2 && user_disks > 2)
|
|
bc.bc_level = BIOC_SVOL_RAID10;
|
|
|
|
if (argc == 7) {
|
|
if ((uint64_t)volsize > ((disksize * user_disks) / 2))
|
|
errx(EXIT_FAILURE, "volume size specified "
|
|
"is larger than available on free disks");
|
|
bc.bc_size = (uint64_t)volsize;
|
|
} else
|
|
bc.bc_size = ((disksize * user_disks) / 2);
|
|
|
|
break;
|
|
case 3: /* RAID 3/5 requires three disks and size is total - 1 disk */
|
|
case 5:
|
|
if (nfreedisks < 3 || user_disks < 3)
|
|
errx(EXIT_FAILURE, "3 disks are required at least for "
|
|
"this RAID level");
|
|
|
|
if (argc == 7) {
|
|
if ((uint64_t)volsize > (disksize * (user_disks - 1)))
|
|
errx(EXIT_FAILURE, "volume size specified "
|
|
"is larger than available on free disks");
|
|
bc.bc_size = (uint64_t)volsize;
|
|
} else
|
|
bc.bc_size = (disksize * (user_disks - 1));
|
|
|
|
break;
|
|
case 6: /* RAID 6 requires four disks and size is total - 2 disks */
|
|
if (nfreedisks < 4 || user_disks < 4)
|
|
errx(EXIT_FAILURE, "4 disks are required at least for "
|
|
"this RAID level");
|
|
|
|
if (argc == 7) {
|
|
if ((uint64_t)volsize >
|
|
((disksize * user_disks) - (disksize * 2)))
|
|
err(EXIT_FAILURE, "volume size specified "
|
|
"is larger than available on free disks");
|
|
bc.bc_size = (uint64_t)volsize;
|
|
} else
|
|
bc.bc_size =
|
|
(((disksize * user_disks) - (disksize * 2)));
|
|
|
|
break;
|
|
default:
|
|
errx(EXIT_FAILURE, "Unsupported RAID level");
|
|
}
|
|
|
|
bc.bc_channel = location.channel;
|
|
bc.bc_target = location.target;
|
|
bc.bc_lun = location.lun;
|
|
|
|
if (ioctl(fd, BIOCVOLOPS, &bc) == -1)
|
|
err(EXIT_FAILURE, "BIOCVOLOPS");
|
|
|
|
humanize_number(size, 5, bc.bc_size, "", HN_AUTOSCALE,
|
|
HN_B | HN_NOSPACE | HN_DECIMAL);
|
|
|
|
if (bc.bc_level == BIOC_SVOL_RAID10)
|
|
snprintf(levelstr, sizeof(levelstr), "1+0");
|
|
else
|
|
snprintf(levelstr, sizeof(levelstr), "%u", bc.bc_level);
|
|
|
|
printf("Created volume %u size: %s stripe: %uK level: %s "
|
|
"SCSI location: %u:%u.%u\n", bc.bc_volid, size, bc.bc_stripe,
|
|
levelstr, bc.bc_channel, bc.bc_target, bc.bc_lun);
|
|
}
|
|
|
|
#ifdef notyet
|
|
/*
|
|
* To modify a RAID volume.
|
|
*/
|
|
static void
|
|
bio_volops_modify(int fd, int argc, char **argv)
|
|
{
|
|
/* XTRAEME: TODO */
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* To remove a RAID volume.
|
|
*/
|
|
static void
|
|
bio_volops_remove(int fd, int argc, char **argv)
|
|
{
|
|
struct bioc_volops bc;
|
|
struct locator location;
|
|
const char *errstr;
|
|
char *endptr;
|
|
|
|
if (argc != 3 || strcmp(argv[0], "volume") != 0)
|
|
usage();
|
|
|
|
memset(&bc, 0, sizeof(bc));
|
|
bc.bc_cookie = bl.bl_cookie;
|
|
bc.bc_opcode = BIOC_VREMOVE_VOLUME;
|
|
|
|
bc.bc_volid = (unsigned int)strtoul(argv[1], &endptr, 10);
|
|
if (*endptr != '\0')
|
|
errx(EXIT_FAILURE, "Invalid Volume ID value");
|
|
|
|
errstr = str2locator(argv[2], &location);
|
|
if (errstr)
|
|
errx(EXIT_FAILURE, "Target %s: %s", argv[2], errstr);
|
|
|
|
bc.bc_channel = location.channel;
|
|
bc.bc_target = location.target;
|
|
bc.bc_lun = location.lun;
|
|
|
|
if (ioctl(fd, BIOCVOLOPS, &bc) == -1)
|
|
err(EXIT_FAILURE, "BIOCVOLOPS");
|
|
|
|
printf("Removed volume %u at SCSI location %u:%u.%u\n",
|
|
bc.bc_volid, bc.bc_channel, bc.bc_target, bc.bc_lun);
|
|
}
|
|
|
|
/*
|
|
* To blink/unblink a disk in enclosures.
|
|
*/
|
|
static void
|
|
bio_setblink(int fd, int argc, char **argv)
|
|
{
|
|
struct locator location;
|
|
struct bioc_inq bi;
|
|
struct bioc_vol bv;
|
|
struct bioc_disk bd;
|
|
struct bioc_blink bb;
|
|
const char *errstr;
|
|
int v, d, rv, blink = 0;
|
|
|
|
if (argc != 2)
|
|
usage();
|
|
|
|
if (strcmp(argv[0], "start") == 0)
|
|
blink = BIOC_SBBLINK;
|
|
else if (strcmp(argv[0], "stop") == 0)
|
|
blink = BIOC_SBUNBLINK;
|
|
else
|
|
usage();
|
|
|
|
errstr = str2locator(argv[1], &location);
|
|
if (errstr)
|
|
errx(EXIT_FAILURE, "Target %s: %s", argv[1], errstr);
|
|
|
|
/* try setting blink on the device directly */
|
|
memset(&bb, 0, sizeof(bb));
|
|
bb.bb_cookie = bl.bl_cookie;
|
|
bb.bb_status = blink;
|
|
bb.bb_target = location.target;
|
|
bb.bb_channel = location.channel;
|
|
rv = ioctl(fd, BIOCBLINK, &bb);
|
|
if (rv == 0)
|
|
return;
|
|
|
|
/* if the blink didnt work, try to find something that will */
|
|
memset(&bi, 0, sizeof(bi));
|
|
bi.bi_cookie = bl.bl_cookie;
|
|
rv = ioctl(fd, BIOCINQ, &bi);
|
|
if (rv == -1)
|
|
err(EXIT_FAILURE, "BIOCINQ");
|
|
|
|
for (v = 0; v < bi.bi_novol; v++) {
|
|
memset(&bv, 0, sizeof(bv));
|
|
bv.bv_cookie = bl.bl_cookie;
|
|
bv.bv_volid = v;
|
|
rv = ioctl(fd, BIOCVOL, &bv);
|
|
if (rv == -1)
|
|
err(EXIT_FAILURE, "BIOCVOL");
|
|
|
|
for (d = 0; d < bv.bv_nodisk; d++) {
|
|
memset(&bd, 0, sizeof(bd));
|
|
bd.bd_cookie = bl.bl_cookie;
|
|
bd.bd_volid = v;
|
|
bd.bd_diskid = d;
|
|
|
|
rv = ioctl(fd, BIOCDISK, &bd);
|
|
if (rv == -1)
|
|
err(EXIT_FAILURE, "BIOCDISK");
|
|
|
|
if (bd.bd_channel == location.channel &&
|
|
bd.bd_target == location.target &&
|
|
bd.bd_lun == location.lun) {
|
|
if (bd.bd_procdev[0] != '\0') {
|
|
bio_blink(fd, bd.bd_procdev,
|
|
location.target, blink);
|
|
} else
|
|
warnx("Disk %s is not in an enclosure",
|
|
argv[1]);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
warnx("Disk %s does not exist", argv[1]);
|
|
}
|
|
|
|
static void
|
|
bio_blink(int fd, char *enclosure, int target, int blinktype)
|
|
{
|
|
struct bio_locate bio;
|
|
struct bioc_blink blink;
|
|
|
|
bio.bl_name = enclosure;
|
|
if (ioctl(fd, BIOCLOCATE, &bio) == -1)
|
|
errx(EXIT_FAILURE,
|
|
"Can't locate %s device via /dev/bio", enclosure);
|
|
|
|
memset(&blink, 0, sizeof(blink));
|
|
blink.bb_cookie = bio.bl_cookie;
|
|
blink.bb_status = blinktype;
|
|
blink.bb_target = target;
|
|
|
|
if (ioctl(fd, BIOCBLINK, &blink) == -1)
|
|
err(EXIT_FAILURE, "BIOCBLINK");
|
|
}
|