1/* $NetBSD: dkctl.c,v 1.19 2011/08/06 16:34:40 dholland 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.19 2011/08/06 16:34:40 dholland 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 74struct command { 75 const char *cmd_name; 76 const char *arg_names; 77 void (*cmd_func)(int, char *[]); 78 int open_flags; 79}; 80 81static struct command *lookup(const char *); 82__dead static void usage(void); 83static void run(int, char *[]); 84static void showall(void); 85 86static int fd; /* file descriptor for device */ 87static const char *dvname; /* device name */ 88static char dvname_store[MAXPATHLEN]; /* for opendisk(3) */ 89static const char *cmdname; /* command user issued */ 90 91static int dkw_sort(const void *, const void *); 92static int yesno(const char *); 93 94static void disk_getcache(int, char *[]); 95static void disk_setcache(int, char *[]); 96static void disk_synccache(int, char *[]); 97static void disk_keeplabel(int, char *[]); 98static void disk_badsectors(int, char *[]); 99 100static void disk_addwedge(int, char *[]); 101static void disk_delwedge(int, char *[]); 102static void disk_getwedgeinfo(int, char *[]); 103static void disk_listwedges(int, char *[]); 104static void disk_strategy(int, char *[]); 105 106static 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 163int 164main(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 185static void 186run(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 205static struct command * 206lookup(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 220static void 221usage(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 238static void 239showall(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 258static void 259disk_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 289static void 290disk_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 315static void 316disk_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 345static void 346disk_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 370static void 371disk_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 390static void 391disk_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 538static void 539disk_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 587static void 588disk_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 604static void 605disk_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 621static void 622disk_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 669static int 670dkw_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 */ 681static int 682yesno(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