1 /* $NetBSD: dkctl.c,v 1.12 2005/12/26 10:38:52 yamt 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.12 2005/12/26 10:38:52 yamt 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 *dbs2 = *dbs; 427 SLIST_INSERT_HEAD(&dbstop, dbs2, dbs_next); 428 dbs++; 429 } 430 dbsi.dbsi_skip += dbsi.dbsi_copied; 431 } while (dbsi.dbsi_left != 0); 432 433 /* 434 * Just calculate and print out something that will hopefully 435 * provide some useful information about what's going to take 436 * place next (if anything.) 437 */ 438 bad = 0; 439 totbad = 0; 440 block = calloc(1, DEV_BSIZE); 441 SLIST_FOREACH(dbs, &dbstop, dbs_next) { 442 bad++; 443 totbad += dbs->dbs_max - dbs->dbs_min + 1; 444 } 445 446 printf("%s: bad sector clusters %"PRIdaddr 447 " total sectors %"PRIdaddr"\n", dvname, bad, totbad); 448 449 /* 450 * Clear out the kernel's list of bad sectors, ready for us 451 * to test all those it thought were bad. 452 */ 453 if (ioctl(fd, DIOCBSFLUSH) == -1) 454 err(1, "%s: badsectors flush", dvname); 455 456 printf("%s: bad sectors flushed\n", dvname); 457 458 /* 459 * For each entry we obtained from the kernel, retry each 460 * individual sector recorded as bad by seeking to it and 461 * attempting to read it in. Print out a line item for each 462 * bad block we verify. 463 * 464 * PRIdaddr is used here because the type of dbs_max is daddr_t 465 * and that may be either a 32bit or 64bit number(!) 466 */ 467 SLIST_FOREACH(dbs, &dbstop, dbs_next) { 468 printf("%s: Retrying %"PRIdaddr" - %" 469 PRIdaddr"\n", dvname, dbs->dbs_min, dbs->dbs_max); 470 471 for (blk = dbs->dbs_min; blk <= dbs->dbs_max; blk++) { 472 if (lseek(fd, (off_t)blk * DEV_BSIZE, 473 SEEK_SET) == -1) { 474 warn("%s: lseek block %" PRIdaddr "", 475 dvname, blk); 476 continue; 477 } 478 printf("%s: block %"PRIdaddr" - ", dvname, blk); 479 if (read(fd, block, DEV_BSIZE) != DEV_BSIZE) 480 printf("failed\n"); 481 else 482 printf("ok\n"); 483 fflush(stdout); 484 } 485 } 486 } 487 } 488 489 void 490 disk_addwedge(int argc, char *argv[]) 491 { 492 struct dkwedge_info dkw; 493 char *cp; 494 daddr_t start; 495 uint64_t size; 496 497 if (argc != 4) 498 usage(); 499 500 /* XXX Unicode. */ 501 if (strlen(argv[0]) > sizeof(dkw.dkw_wname) - 1) 502 errx(1, "Wedge name too long; max %zd characters", 503 sizeof(dkw.dkw_wname) - 1); 504 strcpy(dkw.dkw_wname, argv[0]); 505 506 if (strlen(argv[3]) > sizeof(dkw.dkw_ptype) - 1) 507 errx(1, "Wedge partition type too long; max %zd characters", 508 sizeof(dkw.dkw_ptype) - 1); 509 strcpy(dkw.dkw_ptype, argv[3]); 510 511 errno = 0; 512 start = strtoll(argv[1], &cp, 0); 513 if (*cp != '\0') 514 errx(1, "Invalid start block: %s", argv[1]); 515 if (errno == ERANGE && (start == LLONG_MAX || 516 start == LLONG_MIN)) 517 errx(1, "Start block out of range."); 518 if (start < 0) 519 errx(1, "Start block must be >= 0."); 520 521 errno = 0; 522 size = strtoull(argv[2], &cp, 0); 523 if (*cp != '\0') 524 errx(1, "Invalid block count: %s", argv[2]); 525 if (errno == ERANGE && (size == ULLONG_MAX)) 526 errx(1, "Block count out of range."); 527 528 dkw.dkw_offset = start; 529 dkw.dkw_size = size; 530 531 if (ioctl(fd, DIOCAWEDGE, &dkw) == -1) 532 err(1, "%s: addwedge", dvname); 533 } 534 535 void 536 disk_delwedge(int argc, char *argv[]) 537 { 538 struct dkwedge_info dkw; 539 540 if (argc != 1) 541 usage(); 542 543 if (strlen(argv[0]) > sizeof(dkw.dkw_devname) - 1) 544 errx(1, "Wedge dk name too long; max %zd characters", 545 sizeof(dkw.dkw_devname) - 1); 546 strcpy(dkw.dkw_devname, argv[0]); 547 548 if (ioctl(fd, DIOCDWEDGE, &dkw) == -1) 549 err(1, "%s: delwedge", dvname); 550 } 551 552 void 553 disk_getwedgeinfo(int argc, char *argv[]) 554 { 555 struct dkwedge_info dkw; 556 557 if (argc != 0) 558 usage(); 559 560 if (ioctl(fd, DIOCGWEDGEINFO, &dkw) == -1) 561 err(1, "%s: getwedgeinfo", dvname); 562 563 printf("%s at %s: %s\n", dkw.dkw_devname, dkw.dkw_parent, 564 dkw.dkw_wname); /* XXX Unicode */ 565 printf("%s: %"PRIu64" blocks at %"PRId64", type: %s\n", 566 dkw.dkw_devname, dkw.dkw_size, dkw.dkw_offset, dkw.dkw_ptype); 567 } 568 569 void 570 disk_listwedges(int argc, char *argv[]) 571 { 572 struct dkwedge_info *dkw; 573 struct dkwedge_list dkwl; 574 size_t bufsize; 575 u_int i; 576 577 if (argc != 0) 578 usage(); 579 580 dkw = NULL; 581 dkwl.dkwl_buf = dkw; 582 dkwl.dkwl_bufsize = 0; 583 584 for (;;) { 585 if (ioctl(fd, DIOCLWEDGES, &dkwl) == -1) 586 err(1, "%s: listwedges", dvname); 587 if (dkwl.dkwl_nwedges == dkwl.dkwl_ncopied) 588 break; 589 bufsize = dkwl.dkwl_nwedges * sizeof(*dkw); 590 if (dkwl.dkwl_bufsize < bufsize) { 591 dkw = realloc(dkwl.dkwl_buf, bufsize); 592 if (dkw == NULL) 593 errx(1, "%s: listwedges: unable to " 594 "allocate wedge info buffer", dvname); 595 dkwl.dkwl_buf = dkw; 596 dkwl.dkwl_bufsize = bufsize; 597 } 598 } 599 600 if (dkwl.dkwl_nwedges == 0) { 601 printf("%s: no wedges configured\n", dvname); 602 return; 603 } 604 605 printf("%s: %u wedge%s:\n", dvname, dkwl.dkwl_nwedges, 606 dkwl.dkwl_nwedges == 1 ? "" : "s"); 607 for (i = 0; i < dkwl.dkwl_nwedges; i++) { 608 printf("%s: %s, %"PRIu64" blocks at %"PRId64", type: %s\n", 609 dkw[i].dkw_devname, 610 dkw[i].dkw_wname, /* XXX Unicode */ 611 dkw[i].dkw_size, dkw[i].dkw_offset, dkw[i].dkw_ptype); 612 } 613 } 614 615 /* 616 * return YES, NO or -1. 617 */ 618 int 619 yesno(const char *p) 620 { 621 622 if (!strcmp(p, YES_STR)) 623 return YES; 624 if (!strcmp(p, NO_STR)) 625 return NO; 626 return -1; 627 } 628