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