dkctl.c revision 1.7 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