dkctl.c revision 1.22 1 /* $NetBSD: dkctl.c,v 1.22 2014/11/23 15:43:49 christos 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.22 2014/11/23 15:43:49 christos 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 { "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 { "makewedges",
153 "",
154 disk_makewedges,
155 O_RDWR },
156
157 { "strategy",
158 "[name]",
159 disk_strategy,
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