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