Home | History | Annotate | Line # | Download | only in dkctl
dkctl.c revision 1.20
      1 /*	$NetBSD: dkctl.c,v 1.20 2011/08/27 16:34:38 joerg Exp $	*/
      2 
      3 /*
      4  * Copyright 2001 Wasabi Systems, Inc.
      5  * All rights reserved.
      6  *
      7  * Written by Jason R. Thorpe for Wasabi Systems, Inc.
      8  *
      9  * Redistribution and use in source and binary forms, with or without
     10  * modification, are permitted provided that the following conditions
     11  * are met:
     12  * 1. Redistributions of source code must retain the above copyright
     13  *    notice, this list of conditions and the following disclaimer.
     14  * 2. Redistributions in binary form must reproduce the above copyright
     15  *    notice, this list of conditions and the following disclaimer in the
     16  *    documentation and/or other materials provided with the distribution.
     17  * 3. All advertising materials mentioning features or use of this software
     18  *    must display the following acknowledgement:
     19  *	This product includes software developed for the NetBSD Project by
     20  *	Wasabi Systems, Inc.
     21  * 4. The name of Wasabi Systems, Inc. may not be used to endorse
     22  *    or promote products derived from this software without specific prior
     23  *    written permission.
     24  *
     25  * THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``AS IS'' AND
     26  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     27  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     28  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL WASABI SYSTEMS, INC
     29  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     30  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     31  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     32  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     33  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     34  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     35  * POSSIBILITY OF SUCH DAMAGE.
     36  */
     37 
     38 /*
     39  * dkctl(8) -- a program to manipulate disks.
     40  */
     41 #include <sys/cdefs.h>
     42 
     43 #ifndef lint
     44 __RCSID("$NetBSD: dkctl.c,v 1.20 2011/08/27 16:34:38 joerg Exp $");
     45 #endif
     46 
     47 
     48 #include <sys/param.h>
     49 #include <sys/ioctl.h>
     50 #include <sys/dkio.h>
     51 #include <sys/disk.h>
     52 #include <sys/queue.h>
     53 #include <err.h>
     54 #include <errno.h>
     55 #include <fcntl.h>
     56 #include <stdio.h>
     57 #include <stdlib.h>
     58 #include <string.h>
     59 #include <unistd.h>
     60 #include <util.h>
     61 
     62 #define	YES	1
     63 #define	NO	0
     64 
     65 /* I don't think nl_langinfo is suitable in this case */
     66 #define	YES_STR	"yes"
     67 #define	NO_STR	"no"
     68 #define YESNO_ARG	YES_STR " | " NO_STR
     69 
     70 #ifndef PRIdaddr
     71 #define PRIdaddr PRId64
     72 #endif
     73 
     74 struct command {
     75 	const char *cmd_name;
     76 	const char *arg_names;
     77 	void (*cmd_func)(int, char *[]);
     78 	int open_flags;
     79 };
     80 
     81 static struct command *lookup(const char *);
     82 __dead static void	usage(void);
     83 static void	run(int, char *[]);
     84 static void	showall(void);
     85 
     86 static int	fd;				/* file descriptor for device */
     87 static const	char *dvname;			/* device name */
     88 static char	dvname_store[MAXPATHLEN];	/* for opendisk(3) */
     89 static const	char *cmdname;			/* command user issued */
     90 
     91 static int dkw_sort(const void *, const void *);
     92 static int yesno(const char *);
     93 
     94 static void	disk_getcache(int, char *[]);
     95 static void	disk_setcache(int, char *[]);
     96 static void	disk_synccache(int, char *[]);
     97 static void	disk_keeplabel(int, char *[]);
     98 static void	disk_badsectors(int, char *[]);
     99 
    100 static void	disk_addwedge(int, char *[]);
    101 static void	disk_delwedge(int, char *[]);
    102 static void	disk_getwedgeinfo(int, char *[]);
    103 static void	disk_listwedges(int, char *[]);
    104 static void	disk_strategy(int, char *[]);
    105 
    106 static struct command commands[] = {
    107 	{ "getcache",
    108 	  "",
    109 	  disk_getcache,
    110 	  O_RDONLY },
    111 
    112 	{ "setcache",
    113 	  "none | r | w | rw [save]",
    114 	  disk_setcache,
    115 	  O_RDWR },
    116 
    117 	{ "synccache",
    118 	  "[force]",
    119 	  disk_synccache,
    120 	  O_RDWR },
    121 
    122 	{ "keeplabel",
    123 	  YESNO_ARG,
    124 	  disk_keeplabel,
    125 	  O_RDWR },
    126 
    127 	{ "badsector",
    128 	  "flush | list | retry",
    129 	   disk_badsectors,
    130 	   O_RDWR },
    131 
    132 	{ "addwedge",
    133 	  "name startblk blkcnt ptype",
    134 	  disk_addwedge,
    135 	  O_RDWR },
    136 
    137 	{ "delwedge",
    138 	  "dk",
    139 	  disk_delwedge,
    140 	  O_RDWR },
    141 
    142 	{ "getwedgeinfo",
    143 	  "",
    144 	  disk_getwedgeinfo,
    145 	  O_RDONLY },
    146 
    147 	{ "listwedges",
    148 	  "",
    149 	  disk_listwedges,
    150 	  O_RDONLY },
    151 
    152 	{ "strategy",
    153 	  "[name]",
    154 	  disk_strategy,
    155 	  O_RDWR },
    156 
    157 	{ NULL,
    158 	  NULL,
    159 	  NULL,
    160 	  0 },
    161 };
    162 
    163 int
    164 main(int argc, char *argv[])
    165 {
    166 
    167 	/* Must have at least: device command */
    168 	if (argc < 2)
    169 		usage();
    170 
    171 	dvname = argv[1];
    172 	if (argc == 2)
    173 		showall();
    174 	else {
    175 		/* Skip program name, get and skip device name and command. */
    176 		cmdname = argv[2];
    177 		argv += 3;
    178 		argc -= 3;
    179 		run(argc, argv);
    180 	}
    181 
    182 	exit(0);
    183 }
    184 
    185 static void
    186 run(int argc, char *argv[])
    187 {
    188 	struct command *command;
    189 
    190 	command = lookup(cmdname);
    191 
    192 	/* Open the device. */
    193 	fd = opendisk(dvname, command->open_flags, dvname_store,
    194 	    sizeof(dvname_store), 0);
    195 	if (fd == -1)
    196 		err(1, "%s", dvname);
    197 	dvname = dvname_store;
    198 
    199 	(*command->cmd_func)(argc, argv);
    200 
    201 	/* Close the device. */
    202 	(void)close(fd);
    203 }
    204 
    205 static struct command *
    206 lookup(const char *name)
    207 {
    208 	int i;
    209 
    210 	/* Look up the command. */
    211 	for (i = 0; commands[i].cmd_name != NULL; i++)
    212 		if (strcmp(name, commands[i].cmd_name) == 0)
    213 			break;
    214 	if (commands[i].cmd_name == NULL)
    215 		errx(1, "unknown command: %s", name);
    216 
    217 	return &commands[i];
    218 }
    219 
    220 static void
    221 usage(void)
    222 {
    223 	int i;
    224 
    225 	fprintf(stderr,
    226 	    "usage: %s device\n"
    227 	    "       %s device command [arg [...]]\n",
    228 	    getprogname(), getprogname());
    229 
    230 	fprintf(stderr, "   Available commands:\n");
    231 	for (i = 0; commands[i].cmd_name != NULL; i++)
    232 		fprintf(stderr, "\t%s %s\n", commands[i].cmd_name,
    233 		    commands[i].arg_names);
    234 
    235 	exit(1);
    236 }
    237 
    238 static void
    239 showall(void)
    240 {
    241 	printf("strategy:\n");
    242 	cmdname = "strategy";
    243 	run(0, NULL);
    244 
    245 	putchar('\n');
    246 
    247 	printf("cache:\n");
    248 	cmdname = "getcache";
    249 	run(0, NULL);
    250 
    251 	putchar('\n');
    252 
    253 	printf("wedges:\n");
    254 	cmdname = "listwedges";
    255 	run(0, NULL);
    256 }
    257 
    258 static void
    259 disk_strategy(int argc, char *argv[])
    260 {
    261 	struct disk_strategy odks;
    262 	struct disk_strategy dks;
    263 
    264 	memset(&dks, 0, sizeof(dks));
    265 	if (ioctl(fd, DIOCGSTRATEGY, &odks) == -1) {
    266 		err(EXIT_FAILURE, "%s: DIOCGSTRATEGY", dvname);
    267 	}
    268 
    269 	memset(&dks, 0, sizeof(dks));
    270 	switch (argc) {
    271 	case 0:
    272 		/* show the buffer queue strategy used */
    273 		printf("%s: %s\n", dvname, odks.dks_name);
    274 		return;
    275 	case 1:
    276 		/* set the buffer queue strategy */
    277 		strlcpy(dks.dks_name, argv[0], sizeof(dks.dks_name));
    278 		if (ioctl(fd, DIOCSSTRATEGY, &dks) == -1) {
    279 			err(EXIT_FAILURE, "%s: DIOCSSTRATEGY", dvname);
    280 		}
    281 		printf("%s: %s -> %s\n", dvname, odks.dks_name, argv[0]);
    282 		break;
    283 	default:
    284 		usage();
    285 		/* NOTREACHED */
    286 	}
    287 }
    288 
    289 static void
    290 disk_getcache(int argc, char *argv[])
    291 {
    292 	int bits;
    293 
    294 	if (ioctl(fd, DIOCGCACHE, &bits) == -1)
    295 		err(1, "%s: getcache", dvname);
    296 
    297 	if ((bits & (DKCACHE_READ|DKCACHE_WRITE)) == 0)
    298 		printf("%s: No caches enabled\n", dvname);
    299 	else {
    300 		if (bits & DKCACHE_READ)
    301 			printf("%s: read cache enabled\n", dvname);
    302 		if (bits & DKCACHE_WRITE)
    303 			printf("%s: write-back cache enabled\n", dvname);
    304 	}
    305 
    306 	printf("%s: read cache enable is %schangeable\n", dvname,
    307 	    (bits & DKCACHE_RCHANGE) ? "" : "not ");
    308 	printf("%s: write cache enable is %schangeable\n", dvname,
    309 	    (bits & DKCACHE_WCHANGE) ? "" : "not ");
    310 
    311 	printf("%s: cache parameters are %ssavable\n", dvname,
    312 	    (bits & DKCACHE_SAVE) ? "" : "not ");
    313 }
    314 
    315 static void
    316 disk_setcache(int argc, char *argv[])
    317 {
    318 	int bits;
    319 
    320 	if (argc > 2 || argc == 0)
    321 		usage();
    322 
    323 	if (strcmp(argv[0], "none") == 0)
    324 		bits = 0;
    325 	else if (strcmp(argv[0], "r") == 0)
    326 		bits = DKCACHE_READ;
    327 	else if (strcmp(argv[0], "w") == 0)
    328 		bits = DKCACHE_WRITE;
    329 	else if (strcmp(argv[0], "rw") == 0)
    330 		bits = DKCACHE_READ|DKCACHE_WRITE;
    331 	else
    332 		usage();
    333 
    334 	if (argc == 2) {
    335 		if (strcmp(argv[1], "save") == 0)
    336 			bits |= DKCACHE_SAVE;
    337 		else
    338 			usage();
    339 	}
    340 
    341 	if (ioctl(fd, DIOCSCACHE, &bits) == -1)
    342 		err(1, "%s: setcache", dvname);
    343 }
    344 
    345 static void
    346 disk_synccache(int argc, char *argv[])
    347 {
    348 	int force;
    349 
    350 	switch (argc) {
    351 	case 0:
    352 		force = 0;
    353 		break;
    354 
    355 	case 1:
    356 		if (strcmp(argv[0], "force") == 0)
    357 			force = 1;
    358 		else
    359 			usage();
    360 		break;
    361 
    362 	default:
    363 		usage();
    364 	}
    365 
    366 	if (ioctl(fd, DIOCCACHESYNC, &force) == -1)
    367 		err(1, "%s: sync cache", dvname);
    368 }
    369 
    370 static void
    371 disk_keeplabel(int argc, char *argv[])
    372 {
    373 	int keep;
    374 	int yn;
    375 
    376 	if (argc != 1)
    377 		usage();
    378 
    379 	yn = yesno(argv[0]);
    380 	if (yn < 0)
    381 		usage();
    382 
    383 	keep = yn == YES;
    384 
    385 	if (ioctl(fd, DIOCKLABEL, &keep) == -1)
    386 		err(1, "%s: keep label", dvname);
    387 }
    388 
    389 
    390 static void
    391 disk_badsectors(int argc, char *argv[])
    392 {
    393 	struct disk_badsectors *dbs, *dbs2, buffer[200];
    394 	SLIST_HEAD(, disk_badsectors) dbstop;
    395 	struct disk_badsecinfo dbsi;
    396 	daddr_t blk, totbad, bad;
    397 	u_int32_t count;
    398 	struct stat sb;
    399 	u_char *block;
    400 	time_t tm;
    401 
    402 	if (argc != 1)
    403 		usage();
    404 
    405 	if (strcmp(argv[0], "list") == 0) {
    406 		/*
    407 		 * Copy the list of kernel bad sectors out in chunks that fit
    408 		 * into buffer[].  Updating dbsi_skip means we don't sit here
    409 		 * forever only getting the first chunk that fit in buffer[].
    410 		 */
    411 		dbsi.dbsi_buffer = (caddr_t)buffer;
    412 		dbsi.dbsi_bufsize = sizeof(buffer);
    413 		dbsi.dbsi_skip = 0;
    414 		dbsi.dbsi_copied = 0;
    415 		dbsi.dbsi_left = 0;
    416 
    417 		do {
    418 			if (ioctl(fd, DIOCBSLIST, (caddr_t)&dbsi) == -1)
    419 				err(1, "%s: badsectors list", dvname);
    420 
    421 			dbs = (struct disk_badsectors *)dbsi.dbsi_buffer;
    422 			for (count = dbsi.dbsi_copied; count > 0; count--) {
    423 				tm = dbs->dbs_failedat.tv_sec;
    424 				printf("%s: blocks %" PRIdaddr " - %" PRIdaddr " failed at %s",
    425 					dvname, dbs->dbs_min, dbs->dbs_max,
    426 					ctime(&tm));
    427 				dbs++;
    428 			}
    429 			dbsi.dbsi_skip += dbsi.dbsi_copied;
    430 		} while (dbsi.dbsi_left != 0);
    431 
    432 	} else if (strcmp(argv[0], "flush") == 0) {
    433 		if (ioctl(fd, DIOCBSFLUSH) == -1)
    434 			err(1, "%s: badsectors flush", dvname);
    435 
    436 	} else if (strcmp(argv[0], "retry") == 0) {
    437 		/*
    438 		 * Enforce use of raw device here because the block device
    439 		 * causes access to blocks to be clustered in a larger group,
    440 		 * making it impossible to determine which individual sectors
    441 		 * are the cause of a problem.
    442 		 */
    443 		if (fstat(fd, &sb) == -1)
    444 			err(1, "fstat");
    445 
    446 		if (!S_ISCHR(sb.st_mode)) {
    447 			fprintf(stderr, "'badsector retry' must be used %s\n",
    448 				"with character device");
    449 			exit(1);
    450 		}
    451 
    452 		SLIST_INIT(&dbstop);
    453 
    454 		/*
    455 		 * Build up a copy of the in-kernel list in a number of stages.
    456 		 * That the list we build up here is in the reverse order to
    457 		 * the kernel's is of no concern.
    458 		 */
    459 		dbsi.dbsi_buffer = (caddr_t)buffer;
    460 		dbsi.dbsi_bufsize = sizeof(buffer);
    461 		dbsi.dbsi_skip = 0;
    462 		dbsi.dbsi_copied = 0;
    463 		dbsi.dbsi_left = 0;
    464 
    465 		do {
    466 			if (ioctl(fd, DIOCBSLIST, (caddr_t)&dbsi) == -1)
    467 				err(1, "%s: badsectors list", dvname);
    468 
    469 			dbs = (struct disk_badsectors *)dbsi.dbsi_buffer;
    470 			for (count = dbsi.dbsi_copied; count > 0; count--) {
    471 				dbs2 = malloc(sizeof *dbs2);
    472 				if (dbs2 == NULL)
    473 					err(1, NULL);
    474 				*dbs2 = *dbs;
    475 				SLIST_INSERT_HEAD(&dbstop, dbs2, dbs_next);
    476 				dbs++;
    477 			}
    478 			dbsi.dbsi_skip += dbsi.dbsi_copied;
    479 		} while (dbsi.dbsi_left != 0);
    480 
    481 		/*
    482 		 * Just calculate and print out something that will hopefully
    483 		 * provide some useful information about what's going to take
    484 		 * place next (if anything.)
    485 		 */
    486 		bad = 0;
    487 		totbad = 0;
    488 		if ((block = calloc(1, DEV_BSIZE)) == NULL)
    489 			err(1, NULL);
    490 		SLIST_FOREACH(dbs, &dbstop, dbs_next) {
    491 			bad++;
    492 			totbad += dbs->dbs_max - dbs->dbs_min + 1;
    493 		}
    494 
    495 		printf("%s: bad sector clusters %"PRIdaddr
    496 		    " total sectors %"PRIdaddr"\n", dvname, bad, totbad);
    497 
    498 		/*
    499 		 * Clear out the kernel's list of bad sectors, ready for us
    500 		 * to test all those it thought were bad.
    501 		 */
    502 		if (ioctl(fd, DIOCBSFLUSH) == -1)
    503 			err(1, "%s: badsectors flush", dvname);
    504 
    505 		printf("%s: bad sectors flushed\n", dvname);
    506 
    507 		/*
    508 		 * For each entry we obtained from the kernel, retry each
    509 		 * individual sector recorded as bad by seeking to it and
    510 		 * attempting to read it in.  Print out a line item for each
    511 		 * bad block we verify.
    512 		 *
    513 		 * PRIdaddr is used here because the type of dbs_max is daddr_t
    514 		 * and that may be either a 32bit or 64bit number(!)
    515 		 */
    516 		SLIST_FOREACH(dbs, &dbstop, dbs_next) {
    517 			printf("%s: Retrying %"PRIdaddr" - %"
    518 			    PRIdaddr"\n", dvname, dbs->dbs_min, dbs->dbs_max);
    519 
    520 			for (blk = dbs->dbs_min; blk <= dbs->dbs_max; blk++) {
    521 				if (lseek(fd, (off_t)blk * DEV_BSIZE,
    522 				    SEEK_SET) == -1) {
    523 					warn("%s: lseek block %" PRIdaddr "",
    524 					    dvname, blk);
    525 					continue;
    526 				}
    527 				printf("%s: block %"PRIdaddr" - ", dvname, blk);
    528 				if (read(fd, block, DEV_BSIZE) != DEV_BSIZE)
    529 					printf("failed\n");
    530 				else
    531 					printf("ok\n");
    532 				fflush(stdout);
    533 			}
    534 		}
    535 	}
    536 }
    537 
    538 static void
    539 disk_addwedge(int argc, char *argv[])
    540 {
    541 	struct dkwedge_info dkw;
    542 	char *cp;
    543 	daddr_t start;
    544 	uint64_t size;
    545 
    546 	if (argc != 4)
    547 		usage();
    548 
    549 	/* XXX Unicode: dkw_wname is supposed to be utf-8 */
    550 	if (strlcpy((char *)dkw.dkw_wname, argv[0], sizeof(dkw.dkw_wname)) >=
    551 	    sizeof(dkw.dkw_wname))
    552 		errx(1, "Wedge name too long; max %zd characters",
    553 		    sizeof(dkw.dkw_wname) - 1);
    554 
    555 	if (strlcpy(dkw.dkw_ptype, argv[3], sizeof(dkw.dkw_ptype)) >=
    556 	    sizeof(dkw.dkw_ptype))
    557 		errx(1, "Wedge partition type too long; max %zd characters",
    558 		    sizeof(dkw.dkw_ptype) - 1);
    559 
    560 	errno = 0;
    561 	start = strtoll(argv[1], &cp, 0);
    562 	if (*cp != '\0')
    563 		errx(1, "Invalid start block: %s", argv[1]);
    564 	if (errno == ERANGE && (start == LLONG_MAX ||
    565 				start == LLONG_MIN))
    566 		errx(1, "Start block out of range.");
    567 	if (start < 0)
    568 		errx(1, "Start block must be >= 0.");
    569 
    570 	errno = 0;
    571 	size = strtoull(argv[2], &cp, 0);
    572 	if (*cp != '\0')
    573 		errx(1, "Invalid block count: %s", argv[2]);
    574 	if (errno == ERANGE && (size == ULLONG_MAX))
    575 		errx(1, "Block count out of range.");
    576 
    577 	dkw.dkw_offset = start;
    578 	dkw.dkw_size = size;
    579 
    580 	if (ioctl(fd, DIOCAWEDGE, &dkw) == -1)
    581 		err(1, "%s: addwedge", dvname);
    582 	else
    583 		printf("%s created successfully.\n", dkw.dkw_devname);
    584 
    585 }
    586 
    587 static void
    588 disk_delwedge(int argc, char *argv[])
    589 {
    590 	struct dkwedge_info dkw;
    591 
    592 	if (argc != 1)
    593 		usage();
    594 
    595 	if (strlcpy(dkw.dkw_devname, argv[0], sizeof(dkw.dkw_devname)) >=
    596 	    sizeof(dkw.dkw_devname))
    597 		errx(1, "Wedge dk name too long; max %zd characters",
    598 		    sizeof(dkw.dkw_devname) - 1);
    599 
    600 	if (ioctl(fd, DIOCDWEDGE, &dkw) == -1)
    601 		err(1, "%s: delwedge", dvname);
    602 }
    603 
    604 static void
    605 disk_getwedgeinfo(int argc, char *argv[])
    606 {
    607 	struct dkwedge_info dkw;
    608 
    609 	if (argc != 0)
    610 		usage();
    611 
    612 	if (ioctl(fd, DIOCGWEDGEINFO, &dkw) == -1)
    613 		err(1, "%s: getwedgeinfo", dvname);
    614 
    615 	printf("%s at %s: %s\n", dkw.dkw_devname, dkw.dkw_parent,
    616 	    dkw.dkw_wname);	/* XXX Unicode */
    617 	printf("%s: %"PRIu64" blocks at %"PRId64", type: %s\n",
    618 	    dkw.dkw_devname, dkw.dkw_size, dkw.dkw_offset, dkw.dkw_ptype);
    619 }
    620 
    621 static void
    622 disk_listwedges(int argc, char *argv[])
    623 {
    624 	struct dkwedge_info *dkw;
    625 	struct dkwedge_list dkwl;
    626 	size_t bufsize;
    627 	u_int i;
    628 
    629 	if (argc != 0)
    630 		usage();
    631 
    632 	dkw = NULL;
    633 	dkwl.dkwl_buf = dkw;
    634 	dkwl.dkwl_bufsize = 0;
    635 
    636 	for (;;) {
    637 		if (ioctl(fd, DIOCLWEDGES, &dkwl) == -1)
    638 			err(1, "%s: listwedges", dvname);
    639 		if (dkwl.dkwl_nwedges == dkwl.dkwl_ncopied)
    640 			break;
    641 		bufsize = dkwl.dkwl_nwedges * sizeof(*dkw);
    642 		if (dkwl.dkwl_bufsize < bufsize) {
    643 			dkw = realloc(dkwl.dkwl_buf, bufsize);
    644 			if (dkw == NULL)
    645 				errx(1, "%s: listwedges: unable to "
    646 				    "allocate wedge info buffer", dvname);
    647 			dkwl.dkwl_buf = dkw;
    648 			dkwl.dkwl_bufsize = bufsize;
    649 		}
    650 	}
    651 
    652 	if (dkwl.dkwl_nwedges == 0) {
    653 		printf("%s: no wedges configured\n", dvname);
    654 		return;
    655 	}
    656 
    657 	qsort(dkw, dkwl.dkwl_nwedges, sizeof(*dkw), dkw_sort);
    658 
    659 	printf("%s: %u wedge%s:\n", dvname, dkwl.dkwl_nwedges,
    660 	    dkwl.dkwl_nwedges == 1 ? "" : "s");
    661 	for (i = 0; i < dkwl.dkwl_nwedges; i++) {
    662 		printf("%s: %s, %"PRIu64" blocks at %"PRId64", type: %s\n",
    663 		    dkw[i].dkw_devname,
    664 		    dkw[i].dkw_wname,	/* XXX Unicode */
    665 		    dkw[i].dkw_size, dkw[i].dkw_offset, dkw[i].dkw_ptype);
    666 	}
    667 }
    668 
    669 static int
    670 dkw_sort(const void *a, const void *b)
    671 {
    672 	const struct dkwedge_info *dkwa = a, *dkwb = b;
    673 	const daddr_t oa = dkwa->dkw_offset, ob = dkwb->dkw_offset;
    674 
    675 	return (oa < ob) ? -1 : (oa > ob) ? 1 : 0;
    676 }
    677 
    678 /*
    679  * return YES, NO or -1.
    680  */
    681 static int
    682 yesno(const char *p)
    683 {
    684 
    685 	if (!strcmp(p, YES_STR))
    686 		return YES;
    687 	if (!strcmp(p, NO_STR))
    688 		return NO;
    689 	return -1;
    690 }
    691