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