xref: /netbsd-src/sbin/dkctl/dkctl.c (revision b83ebeba7f767758d2778bb0f9d7a76534253621)
1 /*	$NetBSD: dkctl.c,v 1.23 2016/01/06 23:03:13 wiz 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.23 2016/01/06 23:03:13 wiz Exp $");
45 #endif
46 
47 #include <sys/param.h>
48 #include <sys/ioctl.h>
49 #include <sys/dkio.h>
50 #include <sys/disk.h>
51 #include <sys/queue.h>
52 #include <err.h>
53 #include <errno.h>
54 #include <fcntl.h>
55 #include <stdio.h>
56 #include <stdlib.h>
57 #include <stdbool.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 
90 static int dkw_sort(const void *, const void *);
91 static int yesno(const char *);
92 
93 static void	disk_getcache(int, char *[]);
94 static void	disk_setcache(int, char *[]);
95 static void	disk_synccache(int, char *[]);
96 static void	disk_keeplabel(int, char *[]);
97 static void	disk_badsectors(int, char *[]);
98 
99 static void	disk_addwedge(int, char *[]);
100 static void	disk_delwedge(int, char *[]);
101 static void	disk_getwedgeinfo(int, char *[]);
102 static void	disk_listwedges(int, char *[]);
103 static void	disk_makewedges(int, char *[]);
104 static void	disk_strategy(int, char *[]);
105 
106 static struct command commands[] = {
107 	{ "addwedge",
108 	  "name startblk blkcnt ptype",
109 	  disk_addwedge,
110 	  O_RDWR },
111 
112 	{ "badsector",
113 	  "flush | list | retry",
114 	   disk_badsectors,
115 	   O_RDWR },
116 
117 	{ "delwedge",
118 	  "dk",
119 	  disk_delwedge,
120 	  O_RDWR },
121 
122 	{ "getcache",
123 	  "",
124 	  disk_getcache,
125 	  O_RDONLY },
126 
127 	{ "getwedgeinfo",
128 	  "",
129 	  disk_getwedgeinfo,
130 	  O_RDONLY },
131 
132 	{ "keeplabel",
133 	  YESNO_ARG,
134 	  disk_keeplabel,
135 	  O_RDWR },
136 
137 	{ "listwedges",
138 	  "",
139 	  disk_listwedges,
140 	  O_RDONLY },
141 
142 	{ "makewedges",
143 	  "",
144 	  disk_makewedges,
145 	  O_RDWR },
146 
147 	{ "setcache",
148 	  "none | r | w | rw [save]",
149 	  disk_setcache,
150 	  O_RDWR },
151 
152 	{ "strategy",
153 	  "[name]",
154 	  disk_strategy,
155 	  O_RDWR },
156 
157 	{ "synccache",
158 	  "[force]",
159 	  disk_synccache,
160 	  O_RDWR },
161 
162 	{ NULL,
163 	  NULL,
164 	  NULL,
165 	  0 },
166 };
167 
168 int
169 main(int argc, char *argv[])
170 {
171 
172 	/* Must have at least: device command */
173 	if (argc < 2)
174 		usage();
175 
176 	dvname = argv[1];
177 	if (argc == 2)
178 		showall();
179 	else
180 		run(argc - 2, argv + 2);
181 
182 	return EXIT_SUCCESS;
183 }
184 
185 static void
186 run(int argc, char *argv[])
187 {
188 	struct command *command;
189 
190 	command = lookup(argv[0]);
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(EXIT_FAILURE, "%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(EXIT_FAILURE, "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(EXIT_FAILURE);
236 }
237 
238 static void
239 showall(void)
240 {
241 	static const char *cmds[] = { "strategy", "getcache", "listwedges" };
242 	size_t i;
243 	char *args[2];
244 
245 	args[1] = NULL;
246 	for (i = 0; i < __arraycount(cmds); i++) {
247 		printf("%s:\n", cmds[i]);
248 		args[0] = __UNCONST(cmds[i]);
249 		run(1, args);
250 		putchar('\n');
251 	}
252 }
253 
254 static void
255 disk_strategy(int argc, char *argv[])
256 {
257 	struct disk_strategy odks;
258 	struct disk_strategy dks;
259 
260 	memset(&dks, 0, sizeof(dks));
261 	if (ioctl(fd, DIOCGSTRATEGY, &odks) == -1) {
262 		err(EXIT_FAILURE, "%s: DIOCGSTRATEGY", dvname);
263 	}
264 
265 	memset(&dks, 0, sizeof(dks));
266 	switch (argc) {
267 	case 1:
268 		/* show the buffer queue strategy used */
269 		printf("%s: %s\n", dvname, odks.dks_name);
270 		return;
271 	case 2:
272 		/* set the buffer queue strategy */
273 		strlcpy(dks.dks_name, argv[1], sizeof(dks.dks_name));
274 		if (ioctl(fd, DIOCSSTRATEGY, &dks) == -1) {
275 			err(EXIT_FAILURE, "%s: DIOCSSTRATEGY", dvname);
276 		}
277 		printf("%s: %s -> %s\n", dvname, odks.dks_name, argv[1]);
278 		break;
279 	default:
280 		usage();
281 		/* NOTREACHED */
282 	}
283 }
284 
285 static void
286 disk_getcache(int argc, char *argv[])
287 {
288 	int bits;
289 
290 	if (ioctl(fd, DIOCGCACHE, &bits) == -1)
291 		err(EXIT_FAILURE, "%s: getcache", dvname);
292 
293 	if ((bits & (DKCACHE_READ|DKCACHE_WRITE)) == 0)
294 		printf("%s: No caches enabled\n", dvname);
295 	else {
296 		if (bits & DKCACHE_READ)
297 			printf("%s: read cache enabled\n", dvname);
298 		if (bits & DKCACHE_WRITE)
299 			printf("%s: write-back cache enabled\n", dvname);
300 	}
301 
302 	printf("%s: read cache enable is %schangeable\n", dvname,
303 	    (bits & DKCACHE_RCHANGE) ? "" : "not ");
304 	printf("%s: write cache enable is %schangeable\n", dvname,
305 	    (bits & DKCACHE_WCHANGE) ? "" : "not ");
306 
307 	printf("%s: cache parameters are %ssavable\n", dvname,
308 	    (bits & DKCACHE_SAVE) ? "" : "not ");
309 }
310 
311 static void
312 disk_setcache(int argc, char *argv[])
313 {
314 	int bits;
315 
316 	if (argc > 3 || argc == 1)
317 		usage();
318 
319 	if (strcmp(argv[1], "none") == 0)
320 		bits = 0;
321 	else if (strcmp(argv[1], "r") == 0)
322 		bits = DKCACHE_READ;
323 	else if (strcmp(argv[1], "w") == 0)
324 		bits = DKCACHE_WRITE;
325 	else if (strcmp(argv[1], "rw") == 0)
326 		bits = DKCACHE_READ|DKCACHE_WRITE;
327 	else
328 		usage();
329 
330 	if (argc == 3) {
331 		if (strcmp(argv[2], "save") == 0)
332 			bits |= DKCACHE_SAVE;
333 		else
334 			usage();
335 	}
336 
337 	if (ioctl(fd, DIOCSCACHE, &bits) == -1)
338 		err(EXIT_FAILURE, "%s: %s", dvname, argv[0]);
339 }
340 
341 static void
342 disk_synccache(int argc, char *argv[])
343 {
344 	int force;
345 
346 	switch (argc) {
347 	case 1:
348 		force = 0;
349 		break;
350 
351 	case 2:
352 		if (strcmp(argv[1], "force") == 0)
353 			force = 1;
354 		else
355 			usage();
356 		break;
357 
358 	default:
359 		usage();
360 	}
361 
362 	if (ioctl(fd, DIOCCACHESYNC, &force) == -1)
363 		err(EXIT_FAILURE, "%s: %s", dvname, argv[0]);
364 }
365 
366 static void
367 disk_keeplabel(int argc, char *argv[])
368 {
369 	int keep;
370 	int yn;
371 
372 	if (argc != 2)
373 		usage();
374 
375 	yn = yesno(argv[1]);
376 	if (yn < 0)
377 		usage();
378 
379 	keep = yn == YES;
380 
381 	if (ioctl(fd, DIOCKLABEL, &keep) == -1)
382 		err(EXIT_FAILURE, "%s: %s", dvname, argv[0]);
383 }
384 
385 
386 static void
387 disk_badsectors(int argc, char *argv[])
388 {
389 	struct disk_badsectors *dbs, *dbs2, buffer[200];
390 	SLIST_HEAD(, disk_badsectors) dbstop;
391 	struct disk_badsecinfo dbsi;
392 	daddr_t blk, totbad, bad;
393 	u_int32_t count;
394 	struct stat sb;
395 	u_char *block;
396 	time_t tm;
397 
398 	if (argc != 2)
399 		usage();
400 
401 	if (strcmp(argv[1], "list") == 0) {
402 		/*
403 		 * Copy the list of kernel bad sectors out in chunks that fit
404 		 * into buffer[].  Updating dbsi_skip means we don't sit here
405 		 * forever only getting the first chunk that fit in buffer[].
406 		 */
407 		dbsi.dbsi_buffer = (caddr_t)buffer;
408 		dbsi.dbsi_bufsize = sizeof(buffer);
409 		dbsi.dbsi_skip = 0;
410 		dbsi.dbsi_copied = 0;
411 		dbsi.dbsi_left = 0;
412 
413 		do {
414 			if (ioctl(fd, DIOCBSLIST, (caddr_t)&dbsi) == -1)
415 				err(EXIT_FAILURE, "%s: badsectors list", dvname);
416 
417 			dbs = (struct disk_badsectors *)dbsi.dbsi_buffer;
418 			for (count = dbsi.dbsi_copied; count > 0; count--) {
419 				tm = dbs->dbs_failedat.tv_sec;
420 				printf("%s: blocks %" PRIdaddr " - %" PRIdaddr " failed at %s",
421 					dvname, dbs->dbs_min, dbs->dbs_max,
422 					ctime(&tm));
423 				dbs++;
424 			}
425 			dbsi.dbsi_skip += dbsi.dbsi_copied;
426 		} while (dbsi.dbsi_left != 0);
427 
428 	} else if (strcmp(argv[1], "flush") == 0) {
429 		if (ioctl(fd, DIOCBSFLUSH) == -1)
430 			err(EXIT_FAILURE, "%s: badsectors flush", dvname);
431 
432 	} else if (strcmp(argv[1], "retry") == 0) {
433 		/*
434 		 * Enforce use of raw device here because the block device
435 		 * causes access to blocks to be clustered in a larger group,
436 		 * making it impossible to determine which individual sectors
437 		 * are the cause of a problem.
438 		 */
439 		if (fstat(fd, &sb) == -1)
440 			err(EXIT_FAILURE, "fstat");
441 
442 		if (!S_ISCHR(sb.st_mode)) {
443 			fprintf(stderr, "'badsector retry' must be used %s\n",
444 				"with character device");
445 			exit(1);
446 		}
447 
448 		SLIST_INIT(&dbstop);
449 
450 		/*
451 		 * Build up a copy of the in-kernel list in a number of stages.
452 		 * That the list we build up here is in the reverse order to
453 		 * the kernel's is of no concern.
454 		 */
455 		dbsi.dbsi_buffer = (caddr_t)buffer;
456 		dbsi.dbsi_bufsize = sizeof(buffer);
457 		dbsi.dbsi_skip = 0;
458 		dbsi.dbsi_copied = 0;
459 		dbsi.dbsi_left = 0;
460 
461 		do {
462 			if (ioctl(fd, DIOCBSLIST, (caddr_t)&dbsi) == -1)
463 				err(EXIT_FAILURE, "%s: badsectors list", dvname);
464 
465 			dbs = (struct disk_badsectors *)dbsi.dbsi_buffer;
466 			for (count = dbsi.dbsi_copied; count > 0; count--) {
467 				dbs2 = malloc(sizeof *dbs2);
468 				if (dbs2 == NULL)
469 					err(EXIT_FAILURE, NULL);
470 				*dbs2 = *dbs;
471 				SLIST_INSERT_HEAD(&dbstop, dbs2, dbs_next);
472 				dbs++;
473 			}
474 			dbsi.dbsi_skip += dbsi.dbsi_copied;
475 		} while (dbsi.dbsi_left != 0);
476 
477 		/*
478 		 * Just calculate and print out something that will hopefully
479 		 * provide some useful information about what's going to take
480 		 * place next (if anything.)
481 		 */
482 		bad = 0;
483 		totbad = 0;
484 		if ((block = calloc(1, DEV_BSIZE)) == NULL)
485 			err(EXIT_FAILURE, NULL);
486 		SLIST_FOREACH(dbs, &dbstop, dbs_next) {
487 			bad++;
488 			totbad += dbs->dbs_max - dbs->dbs_min + 1;
489 		}
490 
491 		printf("%s: bad sector clusters %"PRIdaddr
492 		    " total sectors %"PRIdaddr"\n", dvname, bad, totbad);
493 
494 		/*
495 		 * Clear out the kernel's list of bad sectors, ready for us
496 		 * to test all those it thought were bad.
497 		 */
498 		if (ioctl(fd, DIOCBSFLUSH) == -1)
499 			err(EXIT_FAILURE, "%s: badsectors flush", dvname);
500 
501 		printf("%s: bad sectors flushed\n", dvname);
502 
503 		/*
504 		 * For each entry we obtained from the kernel, retry each
505 		 * individual sector recorded as bad by seeking to it and
506 		 * attempting to read it in.  Print out a line item for each
507 		 * bad block we verify.
508 		 *
509 		 * PRIdaddr is used here because the type of dbs_max is daddr_t
510 		 * and that may be either a 32bit or 64bit number(!)
511 		 */
512 		SLIST_FOREACH(dbs, &dbstop, dbs_next) {
513 			printf("%s: Retrying %"PRIdaddr" - %"
514 			    PRIdaddr"\n", dvname, dbs->dbs_min, dbs->dbs_max);
515 
516 			for (blk = dbs->dbs_min; blk <= dbs->dbs_max; blk++) {
517 				if (lseek(fd, (off_t)blk * DEV_BSIZE,
518 				    SEEK_SET) == -1) {
519 					warn("%s: lseek block %" PRIdaddr "",
520 					    dvname, blk);
521 					continue;
522 				}
523 				printf("%s: block %"PRIdaddr" - ", dvname, blk);
524 				if (read(fd, block, DEV_BSIZE) != DEV_BSIZE)
525 					printf("failed\n");
526 				else
527 					printf("ok\n");
528 				fflush(stdout);
529 			}
530 		}
531 	}
532 }
533 
534 static void
535 disk_addwedge(int argc, char *argv[])
536 {
537 	struct dkwedge_info dkw;
538 	char *cp;
539 	daddr_t start;
540 	uint64_t size;
541 
542 	if (argc != 5)
543 		usage();
544 
545 	/* XXX Unicode: dkw_wname is supposed to be utf-8 */
546 	if (strlcpy((char *)dkw.dkw_wname, argv[1], sizeof(dkw.dkw_wname)) >=
547 	    sizeof(dkw.dkw_wname))
548 		errx(EXIT_FAILURE, "Wedge name too long; max %zd characters",
549 		    sizeof(dkw.dkw_wname) - 1);
550 
551 	if (strlcpy(dkw.dkw_ptype, argv[4], sizeof(dkw.dkw_ptype)) >=
552 	    sizeof(dkw.dkw_ptype))
553 		errx(EXIT_FAILURE, "Wedge partition type too long; max %zd characters",
554 		    sizeof(dkw.dkw_ptype) - 1);
555 
556 	errno = 0;
557 	start = strtoll(argv[2], &cp, 0);
558 	if (*cp != '\0')
559 		errx(EXIT_FAILURE, "Invalid start block: %s", argv[2]);
560 	if (errno == ERANGE && (start == LLONG_MAX ||
561 				start == LLONG_MIN))
562 		errx(EXIT_FAILURE, "Start block out of range.");
563 	if (start < 0)
564 		errx(EXIT_FAILURE, "Start block must be >= 0.");
565 
566 	errno = 0;
567 	size = strtoull(argv[3], &cp, 0);
568 	if (*cp != '\0')
569 		errx(EXIT_FAILURE, "Invalid block count: %s", argv[3]);
570 	if (errno == ERANGE && (size == ULLONG_MAX))
571 		errx(EXIT_FAILURE, "Block count out of range.");
572 
573 	dkw.dkw_offset = start;
574 	dkw.dkw_size = size;
575 
576 	if (ioctl(fd, DIOCAWEDGE, &dkw) == -1)
577 		err(EXIT_FAILURE, "%s: %s", dvname, argv[0]);
578 	else
579 		printf("%s created successfully.\n", dkw.dkw_devname);
580 
581 }
582 
583 static void
584 disk_delwedge(int argc, char *argv[])
585 {
586 	struct dkwedge_info dkw;
587 
588 	if (argc != 2)
589 		usage();
590 
591 	if (strlcpy(dkw.dkw_devname, argv[1], sizeof(dkw.dkw_devname)) >=
592 	    sizeof(dkw.dkw_devname))
593 		errx(EXIT_FAILURE, "Wedge dk name too long; max %zd characters",
594 		    sizeof(dkw.dkw_devname) - 1);
595 
596 	if (ioctl(fd, DIOCDWEDGE, &dkw) == -1)
597 		err(EXIT_FAILURE, "%s: %s", dvname, argv[0]);
598 }
599 
600 static void
601 disk_getwedgeinfo(int argc, char *argv[])
602 {
603 	struct dkwedge_info dkw;
604 
605 	if (argc != 1)
606 		usage();
607 
608 	if (ioctl(fd, DIOCGWEDGEINFO, &dkw) == -1)
609 		err(EXIT_FAILURE, "%s: getwedgeinfo", dvname);
610 
611 	printf("%s at %s: %s\n", dkw.dkw_devname, dkw.dkw_parent,
612 	    dkw.dkw_wname);	/* XXX Unicode */
613 	printf("%s: %"PRIu64" blocks at %"PRId64", type: %s\n",
614 	    dkw.dkw_devname, dkw.dkw_size, dkw.dkw_offset, dkw.dkw_ptype);
615 }
616 
617 static void
618 disk_listwedges(int argc, char *argv[])
619 {
620 	struct dkwedge_info *dkw;
621 	struct dkwedge_list dkwl;
622 	size_t bufsize;
623 	u_int i;
624 	int c;
625 	bool error, quiet;
626 
627 	optreset = 1;
628 	optind = 1;
629 	quiet = error = false;
630 	while ((c = getopt(argc, argv, "qe")) != -1)
631 		switch (c) {
632 		case 'e':
633 			error = true;
634 			break;
635 		case 'q':
636 			quiet = true;
637 			break;
638 		default:
639 			usage();
640 		}
641 
642 	argc -= optind;
643 	argv += optind;
644 
645 	if (argc != 0)
646 		usage();
647 
648 	dkw = NULL;
649 	dkwl.dkwl_buf = dkw;
650 	dkwl.dkwl_bufsize = 0;
651 
652 	for (;;) {
653 		if (ioctl(fd, DIOCLWEDGES, &dkwl) == -1)
654 			err(EXIT_FAILURE, "%s: listwedges", dvname);
655 		if (dkwl.dkwl_nwedges == dkwl.dkwl_ncopied)
656 			break;
657 		bufsize = dkwl.dkwl_nwedges * sizeof(*dkw);
658 		if (dkwl.dkwl_bufsize < bufsize) {
659 			dkw = realloc(dkwl.dkwl_buf, bufsize);
660 			if (dkw == NULL)
661 				errx(EXIT_FAILURE, "%s: listwedges: unable to "
662 				    "allocate wedge info buffer", dvname);
663 			dkwl.dkwl_buf = dkw;
664 			dkwl.dkwl_bufsize = bufsize;
665 		}
666 	}
667 
668 	if (dkwl.dkwl_nwedges == 0) {
669 		if (!quiet)
670 			printf("%s: no wedges configured\n", dvname);
671 		if (error)
672 			exit(EXIT_FAILURE);
673 		return;
674 	}
675 
676 	qsort(dkw, dkwl.dkwl_nwedges, sizeof(*dkw), dkw_sort);
677 
678 	printf("%s: %u wedge%s:\n", dvname, dkwl.dkwl_nwedges,
679 	    dkwl.dkwl_nwedges == 1 ? "" : "s");
680 	for (i = 0; i < dkwl.dkwl_nwedges; i++) {
681 		printf("%s: %s, %"PRIu64" blocks at %"PRId64", type: %s\n",
682 		    dkw[i].dkw_devname,
683 		    dkw[i].dkw_wname,	/* XXX Unicode */
684 		    dkw[i].dkw_size, dkw[i].dkw_offset, dkw[i].dkw_ptype);
685 	}
686 }
687 
688 static void
689 disk_makewedges(int argc, char *argv[])
690 {
691 	int bits;
692 
693 	if (argc != 1)
694 		usage();
695 
696 	if (ioctl(fd, DIOCMWEDGES, &bits) == -1)
697 		err(EXIT_FAILURE, "%s: %s", dvname, argv[0]);
698 	else
699 		printf("successfully scanned %s.\n", dvname);
700 }
701 
702 static int
703 dkw_sort(const void *a, const void *b)
704 {
705 	const struct dkwedge_info *dkwa = a, *dkwb = b;
706 	const daddr_t oa = dkwa->dkw_offset, ob = dkwb->dkw_offset;
707 
708 	return (oa < ob) ? -1 : (oa > ob) ? 1 : 0;
709 }
710 
711 /*
712  * return YES, NO or -1.
713  */
714 static int
715 yesno(const char *p)
716 {
717 
718 	if (!strcmp(p, YES_STR))
719 		return YES;
720 	if (!strcmp(p, NO_STR))
721 		return NO;
722 	return -1;
723 }
724