1 /* $NetBSD: dkctl.c,v 1.7 2003/06/23 11:53:37 agc 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.7 2003/06/23 11:53:37 agc 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 int main(int, char *[]); 82 void usage(void); 83 84 int fd; /* file descriptor for device */ 85 const char *dvname; /* device name */ 86 char dvname_store[MAXPATHLEN]; /* for opendisk(3) */ 87 const char *cmdname; /* command user issued */ 88 const char *argnames; /* helpstring; expected arguments */ 89 90 int yesno(const char *); 91 92 void disk_getcache(int, char *[]); 93 void disk_setcache(int, char *[]); 94 void disk_synccache(int, char *[]); 95 void disk_keeplabel(int, char *[]); 96 void disk_badsectors(int, char *[]); 97 98 struct command commands[] = { 99 { "getcache", 100 "", 101 disk_getcache, 102 O_RDONLY }, 103 104 { "setcache", 105 "none | r | w | rw [save]", 106 disk_setcache, 107 O_RDWR }, 108 109 { "synccache", 110 "[force]", 111 disk_synccache, 112 O_RDWR }, 113 114 { "keeplabel", 115 YESNO_ARG, 116 disk_keeplabel, 117 O_RDWR }, 118 119 { "badsector", 120 "flush | list | retry", 121 disk_badsectors, 122 O_RDWR }, 123 124 { NULL, 125 NULL, 126 NULL, 127 0 }, 128 }; 129 130 int 131 main(int argc, char *argv[]) 132 { 133 int i; 134 135 /* Must have at least: device command */ 136 if (argc < 3) 137 usage(); 138 139 /* Skip program name, get and skip device name and command. */ 140 dvname = argv[1]; 141 cmdname = argv[2]; 142 argv += 3; 143 argc -= 3; 144 145 /* Look up and call the command. */ 146 for (i = 0; commands[i].cmd_name != NULL; i++) 147 if (strcmp(cmdname, commands[i].cmd_name) == 0) 148 break; 149 if (commands[i].cmd_name == NULL) 150 errx(1, "unknown command: %s", cmdname); 151 152 argnames = commands[i].arg_names; 153 154 /* Open the device. */ 155 fd = opendisk(dvname, commands[i].open_flags, dvname_store, 156 sizeof(dvname_store), 0); 157 if (fd == -1) 158 err(1, "%s", dvname); 159 160 dvname = dvname_store; 161 162 (*commands[i].cmd_func)(argc, argv); 163 exit(0); 164 } 165 166 void 167 usage() 168 { 169 int i; 170 171 fprintf(stderr, "Usage: %s device command [arg [...]]\n", 172 getprogname()); 173 174 fprintf(stderr, " Available commands:\n"); 175 for (i = 0; commands[i].cmd_name != NULL; i++) 176 fprintf(stderr, "\t%s %s\n", commands[i].cmd_name, 177 commands[i].arg_names); 178 179 exit(1); 180 } 181 182 void 183 disk_getcache(int argc, char *argv[]) 184 { 185 int bits; 186 187 if (ioctl(fd, DIOCGCACHE, &bits) == -1) 188 err(1, "%s: getcache", dvname); 189 190 if ((bits & (DKCACHE_READ|DKCACHE_WRITE)) == 0) 191 printf("%s: No caches enabled\n", dvname); 192 else { 193 if (bits & DKCACHE_READ) 194 printf("%s: read cache enabled\n", dvname); 195 if (bits & DKCACHE_WRITE) 196 printf("%s: write-back cache enabled\n", dvname); 197 } 198 199 printf("%s: read cache enable is %schangeable\n", dvname, 200 (bits & DKCACHE_RCHANGE) ? "" : "not "); 201 printf("%s: write cache enable is %schangeable\n", dvname, 202 (bits & DKCACHE_WCHANGE) ? "" : "not "); 203 204 printf("%s: cache parameters are %ssavable\n", dvname, 205 (bits & DKCACHE_SAVE) ? "" : "not "); 206 } 207 208 void 209 disk_setcache(int argc, char *argv[]) 210 { 211 int bits; 212 213 if (argc > 2 || argc == 0) 214 usage(); 215 216 if (strcmp(argv[0], "none") == 0) 217 bits = 0; 218 else if (strcmp(argv[0], "r") == 0) 219 bits = DKCACHE_READ; 220 else if (strcmp(argv[0], "w") == 0) 221 bits = DKCACHE_WRITE; 222 else if (strcmp(argv[0], "rw") == 0) 223 bits = DKCACHE_READ|DKCACHE_WRITE; 224 else 225 usage(); 226 227 if (argc == 2) { 228 if (strcmp(argv[1], "save") == 0) 229 bits |= DKCACHE_SAVE; 230 else 231 usage(); 232 } 233 234 if (ioctl(fd, DIOCSCACHE, &bits) == -1) 235 err(1, "%s: setcache", dvname); 236 } 237 238 void 239 disk_synccache(int argc, char *argv[]) 240 { 241 int force; 242 243 switch (argc) { 244 case 0: 245 force = 0; 246 break; 247 248 case 1: 249 if (strcmp(argv[0], "force") == 0) 250 force = 1; 251 else 252 usage(); 253 break; 254 255 default: 256 usage(); 257 } 258 259 if (ioctl(fd, DIOCCACHESYNC, &force) == -1) 260 err(1, "%s: sync cache", dvname); 261 } 262 263 void 264 disk_keeplabel(int argc, char *argv[]) 265 { 266 int keep; 267 int yn; 268 269 if (argc != 1) 270 usage(); 271 272 yn = yesno(argv[0]); 273 if (yn < 0) 274 usage(); 275 276 keep = yn == YES; 277 278 if (ioctl(fd, DIOCKLABEL, &keep) == -1) 279 err(1, "%s: keep label", dvname); 280 } 281 282 283 void 284 disk_badsectors(int argc, char *argv[]) 285 { 286 struct disk_badsectors *dbs, *dbs2, buffer[200]; 287 SLIST_HEAD(, disk_badsectors) dbstop; 288 struct disk_badsecinfo dbsi; 289 daddr_t blk, totbad, bad; 290 u_int32_t count; 291 struct stat sb; 292 u_char *block; 293 time_t tm; 294 295 if (argc != 1) 296 usage(); 297 298 if (strcmp(argv[0], "list") == 0) { 299 /* 300 * Copy the list of kernel bad sectors out in chunks that fit 301 * into buffer[]. Updating dbsi_skip means we don't sit here 302 * forever only getting the first chunk that fit in buffer[]. 303 */ 304 dbsi.dbsi_buffer = (caddr_t)buffer; 305 dbsi.dbsi_bufsize = sizeof(buffer); 306 dbsi.dbsi_skip = 0; 307 dbsi.dbsi_copied = 0; 308 dbsi.dbsi_left = 0; 309 310 do { 311 if (ioctl(fd, DIOCBSLIST, (caddr_t)&dbsi) == -1) 312 err(1, "%s: badsectors list", dvname); 313 314 dbs = (struct disk_badsectors *)dbsi.dbsi_buffer; 315 for (count = dbsi.dbsi_copied; count > 0; count--) { 316 tm = dbs->dbs_failedat.tv_sec; 317 printf("%s: blocks %" PRIdaddr " - %" PRIdaddr " failed at %s", 318 dvname, dbs->dbs_min, dbs->dbs_max, 319 ctime(&tm)); 320 dbs++; 321 } 322 dbsi.dbsi_skip += dbsi.dbsi_copied; 323 } while (dbsi.dbsi_left != 0); 324 325 } else if (strcmp(argv[0], "flush") == 0) { 326 if (ioctl(fd, DIOCBSFLUSH) == -1) 327 err(1, "%s: badsectors flush", dvname); 328 329 } else if (strcmp(argv[0], "retry") == 0) { 330 /* 331 * Enforce use of raw device here because the block device 332 * causes access to blocks to be clustered in a larger group, 333 * making it impossible to determine which individual sectors 334 * are the cause of a problem. 335 */ 336 if (fstat(fd, &sb) == -1) 337 err(1, "fstat"); 338 339 if (!S_ISCHR(sb.st_mode)) { 340 fprintf(stderr, "'badsector retry' must be used %s\n", 341 "with character device"); 342 exit(1); 343 } 344 345 SLIST_INIT(&dbstop); 346 347 /* 348 * Build up a copy of the in-kernel list in a number of stages. 349 * That the list we build up here is in the reverse order to 350 * the kernel's is of no concern. 351 */ 352 dbsi.dbsi_buffer = (caddr_t)buffer; 353 dbsi.dbsi_bufsize = sizeof(buffer); 354 dbsi.dbsi_skip = 0; 355 dbsi.dbsi_copied = 0; 356 dbsi.dbsi_left = 0; 357 358 do { 359 if (ioctl(fd, DIOCBSLIST, (caddr_t)&dbsi) == -1) 360 err(1, "%s: badsectors list", dvname); 361 362 dbs = (struct disk_badsectors *)dbsi.dbsi_buffer; 363 for (count = dbsi.dbsi_copied; count > 0; count--) { 364 dbs2 = malloc(sizeof(*dbs2)); 365 *dbs2 = *dbs; 366 SLIST_INSERT_HEAD(&dbstop, dbs2, dbs_next); 367 dbs++; 368 } 369 dbsi.dbsi_skip += dbsi.dbsi_copied; 370 } while (dbsi.dbsi_left != 0); 371 372 /* 373 * Just calculate and print out something that will hopefully 374 * provide some useful information about what's going to take 375 * place next (if anything.) 376 */ 377 bad = 0; 378 totbad = 0; 379 block = calloc(1, DEV_BSIZE); 380 SLIST_FOREACH(dbs, &dbstop, dbs_next) { 381 bad++; 382 totbad += dbs->dbs_max - dbs->dbs_min + 1; 383 } 384 385 printf("%s: bad sector clusters %"PRIdaddr 386 " total sectors %"PRIdaddr"\n", dvname, bad, totbad); 387 388 /* 389 * Clear out the kernel's list of bad sectors, ready for us 390 * to test all those it thought were bad. 391 */ 392 if (ioctl(fd, DIOCBSFLUSH) == -1) 393 err(1, "%s: badsectors flush", dvname); 394 395 printf("%s: bad sectors flushed\n", dvname); 396 397 /* 398 * For each entry we obtained from the kernel, retry each 399 * individual sector recorded as bad by seeking to it and 400 * attempting to read it in. Print out a line item for each 401 * bad block we verify. 402 * 403 * PRIdaddr is used here because the type of dbs_max is daddr_t 404 * and that may be either a 32bit or 64bit number(!) 405 */ 406 SLIST_FOREACH(dbs, &dbstop, dbs_next) { 407 printf("%s: Retrying %"PRIdaddr" - %" 408 PRIdaddr"\n", dvname, dbs->dbs_min, dbs->dbs_max); 409 410 for (blk = dbs->dbs_min; blk <= dbs->dbs_max; blk++) { 411 if (lseek(fd, (off_t)blk * DEV_BSIZE, 412 SEEK_SET) == -1) { 413 warn("%s: lseek block %" PRIdaddr "", 414 dvname, blk); 415 continue; 416 } 417 printf("%s: block %"PRIdaddr" - ", dvname, blk); 418 if (read(fd, block, DEV_BSIZE) != DEV_BSIZE) 419 printf("failed\n"); 420 else 421 printf("ok\n"); 422 fflush(stdout); 423 } 424 } 425 } 426 } 427 428 /* 429 * return YES, NO or -1. 430 */ 431 int 432 yesno(const char *p) 433 { 434 435 if (!strcmp(p, YES_STR)) 436 return YES; 437 if (!strcmp(p, NO_STR)) 438 return NO; 439 return -1; 440 } 441