Whamcloud - gitweb
ChangeLog, tune2fs.c:
[tools/e2fsprogs.git] / misc / badblocks.c
1 /*
2  * badblocks.c          - Bad blocks checker
3  *
4  * Copyright (C) 1992, 1993, 1994  Remy Card <card@masi.ibp.fr>
5  *                                 Laboratoire MASI, Institut Blaise Pascal
6  *                                 Universite Pierre et Marie Curie (Paris VI)
7  *
8  * Copyright 1995, 1996, 1997 by Theodore Ts'o
9  * Copyright 1999 by David Beattie
10  *
11  * This file is based on the minix file system programs fsck and mkfs
12  * written and copyrighted by Linus Torvalds <Linus.Torvalds@cs.helsinki.fi>
13  * 
14  * %Begin-Header%
15  * This file may be redistributed under the terms of the GNU Public
16  * License.
17  * %End-Header%
18  */
19
20 /*
21  * History:
22  * 93/05/26     - Creation from e2fsck
23  * 94/02/27     - Made a separate bad blocks checker
24  * 99/06/30...99/07/26 - Added non-destructive write-testing,
25  *                       and a whole host of other features.
26  */
27
28 #include <errno.h>
29 #include <fcntl.h>
30 #ifdef HAVE_GETOPT_H
31 #include <getopt.h>
32 #endif
33 #include <signal.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <unistd.h>
38 #include <setjmp.h>
39
40 #include <sys/ioctl.h>
41 #include <sys/types.h>
42
43 #if HAVE_LINUX_FS_H
44 #include <linux/fd.h>
45 #include <linux/fs.h>
46 #endif
47
48 #include "et/com_err.h"
49 #include "ext2fs/ext2_io.h"
50 #include <linux/ext2_fs.h>
51 #include "ext2fs/ext2fs.h"
52
53 const char * program_name = "badblocks";
54 const char * done_string = "done                        \n";
55
56 int v_flag = 0;                 /* verbose */
57 int w_flag = 0;                 /* do r/w test: 0=no, 1=yes, 2=non-destructive */
58 int s_flag = 0;                 /* show progress of test */
59
60 static void usage(void)
61 {
62         fprintf (stderr, "Usage: %s [-b block_size] [-i input_file] [-o output_file] [-svwn]\n [-c blocks_at_once] [-p num_passes] device blocks_count [start_count]\n",
63                  program_name);
64         exit (1);
65 }
66
67 static unsigned long currently_testing = 0;
68 static unsigned long num_blocks = 0;
69 static ext2_badblocks_list bb_list = NULL;
70 static FILE *out;
71 static blk_t next_bad = 0;
72 static ext2_badblocks_iterate bb_iter = NULL;
73
74 static void bb_output (unsigned long bad)
75 {
76         errcode_t errcode;
77
78         fprintf (out, "%lu\n", bad);
79
80         errcode = ext2fs_badblocks_list_add (bb_list, bad);
81         if (errcode) {
82                 com_err (program_name, errcode, "adding to in-memory bad block list");
83                 exit (1);
84         }
85
86         /* kludge:
87            increment the iteration through the bb_list if 
88            an element was just added before the current iteration
89            position.  This should not cause next_bad to change. */
90         if (bb_iter && bad < next_bad)
91                 ext2fs_badblocks_list_iterate (bb_iter, &next_bad);
92 }
93
94 static void print_status (void)
95 {
96         fprintf(stderr, "%9ld/%9ld", currently_testing, num_blocks);
97         fprintf(stderr, "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b");
98         fflush (stderr);
99 }
100
101 static void alarm_intr (int alnum)
102 {
103         signal (SIGALRM, alarm_intr);
104         alarm(1);
105         if (!num_blocks)
106                 return;
107         fprintf(stderr, "%9ld/%9ld", currently_testing, num_blocks);
108         fprintf(stderr, "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b");
109         fflush (stderr);
110 }
111
112 static void *terminate_addr = NULL;
113
114 static void terminate_intr (int signo)
115 {
116         if (terminate_addr)
117                 longjmp(terminate_addr,1);
118         exit(1);
119 }
120
121 static void capture_terminate (jmp_buf term_addr)
122 {
123         terminate_addr = term_addr;
124         signal (SIGHUP, terminate_intr);
125         signal (SIGINT, terminate_intr);
126         signal (SIGPIPE, terminate_intr);
127         signal (SIGTERM, terminate_intr);
128         signal (SIGUSR1, terminate_intr);
129         signal (SIGUSR2, terminate_intr);
130 }
131
132 /*
133  * Perform a read of a sequence of blocks; return the number of blocks
134  *    successfully sequentially read.
135  */
136 static long do_read (int dev, char * buffer, int try, unsigned long block_size,
137                      unsigned long current_block)
138 {
139         long got;
140
141         if (v_flag > 1)
142                 print_status();
143
144         /* Seek to the correct loc. */
145         if (ext2fs_llseek (dev, (ext2_loff_t) current_block * block_size,
146                          SEEK_SET) != (ext2_loff_t) current_block * block_size)
147                 com_err (program_name, errno, "during seek");
148
149         /* Try the read */
150         got = read (dev, buffer, try * block_size);
151         if (got < 0)
152                 got = 0;        
153         if (got & 511)
154                 fprintf (stderr,
155                          "Weird value (%ld) in do_read\n", got);
156         got /= block_size;
157         return got;
158 }
159
160 /*
161  * Perform a write of a sequence of blocks; return the number of blocks
162  *    successfully sequentially written.
163  */
164 static long do_write (int dev, char * buffer, int try, unsigned long block_size,
165                      unsigned long current_block)
166 {
167         long got;
168
169         if (v_flag > 1)
170                 print_status();
171
172         /* Seek to the correct loc. */
173         if (ext2fs_llseek (dev, (ext2_loff_t) current_block * block_size,
174                          SEEK_SET) != (ext2_loff_t) current_block * block_size)
175                 com_err (program_name, errno, "during seek");
176
177         /* Try the write */
178         got = write (dev, buffer, try * block_size);
179         if (got < 0)
180                 got = 0;        
181         if (got & 511)
182                 fprintf (stderr,
183                          "Weird value (%ld) in do_write\n", got);
184         got /= block_size;
185         return got;
186 }
187
188 static int host_dev;
189
190 static void flush_bufs (int dev, int sync)
191 {
192   if (v_flag
193 #if !defined (BLKFLSBUF) && !defined (FDFLUSH)
194       && sync
195 #endif
196       )
197     fprintf (stderr, "Flushing buffers\n");
198
199   if (sync && fdatasync (dev) == -1)
200     com_err (program_name, errno, "during fsync");
201
202 #ifdef BLKFLSBUF
203   ioctl (host_dev, BLKFLSBUF, 0);   /* In case this is a HD */
204 #endif
205 #ifdef FDFLUSH
206   ioctl (host_dev, FDFLUSH, 0);   /* In case this is floppy */
207 #endif
208 }
209
210 static unsigned int test_ro (int dev, unsigned long blocks_count,
211                      unsigned long block_size,
212                      unsigned long from_count, unsigned long blocks_at_once)
213 {
214         char * blkbuf;
215         int try;
216         long got;
217         unsigned int bb_count = 0;
218         errcode_t errcode;
219
220         errcode = ext2fs_badblocks_list_iterate_begin(bb_list,&bb_iter);
221         if (errcode) {
222                 com_err (program_name, errcode, "while beginning bad block list iteration");
223                 exit (1);
224         }
225         do {
226                 ext2fs_badblocks_list_iterate (bb_iter, &next_bad);
227         } while (next_bad && next_bad < from_count);
228
229         blkbuf = malloc (blocks_at_once * block_size);
230         if (!blkbuf)
231         {
232                 com_err (program_name, ENOMEM, "while allocating buffers");
233                 exit (1);
234         }
235         flush_bufs (dev, 0);
236         if (v_flag) {
237             fprintf (stderr,
238                      "Checking for bad blocks in read-only mode\n");
239             fprintf (stderr, "From block %lu to %lu\n", from_count, blocks_count);
240         }
241         try = blocks_at_once;
242         currently_testing = from_count;
243         num_blocks = blocks_count;
244         if (s_flag || v_flag > 1) {
245                 fprintf(stderr, "Checking for bad blocks (read-only test): ");
246                 if (v_flag <= 1)
247                         alarm_intr(SIGALRM);
248         }
249         while (currently_testing < blocks_count)
250         {
251                 if (next_bad) {
252                         if (currently_testing == next_bad) {
253                                 /* fprintf (out, "%lu\n", nextbad); */
254                                 ext2fs_badblocks_list_iterate (bb_iter, &next_bad);
255                                 currently_testing++;
256                                 continue;
257                         }
258                         else if (currently_testing + try > next_bad)
259                                 try = next_bad - currently_testing;
260                 }
261                 if (currently_testing + try > blocks_count)
262                         try = blocks_count - currently_testing;
263                 got = do_read (dev, blkbuf, try, block_size, currently_testing);
264                 currently_testing += got;
265                 if (got == try) {
266                         try = blocks_at_once;
267                         continue;
268                 }
269                 else
270                         try = 1;
271                 if (got == 0) {
272                         bb_output (currently_testing++), ++bb_count;
273                 }
274         }
275         num_blocks = 0;
276         alarm(0);
277         if (s_flag || v_flag > 1)
278                 fprintf(stderr, done_string);
279
280         fflush (stderr);
281         free (blkbuf);
282
283         ext2fs_badblocks_list_iterate_end(bb_iter);
284
285         return bb_count;
286 }
287
288 static unsigned int test_rw (int dev, unsigned long blocks_count,
289                      unsigned long block_size,
290                      unsigned long from_count, unsigned long blocks_at_once)
291 {
292         int i;
293         char * buffer;
294         unsigned char pattern[] = {0xaa, 0x55, 0xff, 0x00};
295         unsigned int bb_count = 0;
296
297         buffer = malloc (2 * block_size);
298         if (!buffer)
299         {
300                 com_err (program_name, ENOMEM, "while allocating buffers");
301                 exit (1);
302         }
303
304         flush_bufs (dev, 0);
305
306         if (v_flag) {
307                 fprintf(stderr,
308                         "Checking for bad blocks in read-write mode\n");
309                 fprintf(stderr, "From block %lu to %lu\n",
310                          from_count, blocks_count);
311         }
312         for (i = 0; i < sizeof (pattern); i++) {
313                 memset (buffer, pattern[i], block_size);
314                 if (s_flag | v_flag)
315                         fprintf (stderr, "Writing pattern 0x%08x: ",
316                                  *((int *) buffer));
317                 num_blocks = blocks_count;
318                 currently_testing = from_count;
319                 if (s_flag && v_flag <= 1)
320                         alarm_intr(SIGALRM);
321                 for (;
322                      currently_testing < blocks_count;
323                      currently_testing++)
324                 {
325                         if (ext2fs_llseek (dev, (ext2_loff_t) currently_testing *
326                                          block_size, SEEK_SET) !=
327                             (ext2_loff_t) currently_testing * block_size)
328                                 com_err (program_name, errno,
329                                          "during seek on block %d",
330                                          currently_testing);
331                         if (v_flag > 1)
332                                 print_status();
333                         write (dev, buffer, block_size);
334                 }
335                 num_blocks = 0;
336                 alarm (0);
337                 if (s_flag | v_flag)
338                         fprintf(stderr, done_string);
339                 flush_bufs (dev, 1);
340                 if (s_flag | v_flag)
341                         fprintf (stderr, "Reading and comparing: ");
342                 num_blocks = blocks_count;
343                 currently_testing = from_count;
344                 if (s_flag && v_flag <= 1)
345                         alarm_intr(SIGALRM);
346                 for (;
347                      currently_testing < blocks_count;
348                      currently_testing++)
349                 {
350                         if (ext2fs_llseek (dev, (ext2_loff_t) currently_testing *
351                                          block_size, SEEK_SET) !=
352                             (ext2_loff_t) currently_testing * block_size)
353                                 com_err (program_name, errno,
354                                          "during seek on block %d",
355                                          currently_testing);
356                         if (v_flag > 1)
357                                 print_status();
358                         if (read (dev, buffer + block_size, block_size) < block_size)
359
360                                 ext2fs_badblocks_list_test (bb_list, currently_testing)
361                                 || ( bb_output (currently_testing), ++bb_count );
362
363                         else if (memcmp (buffer, buffer + block_size, block_size))
364
365                                 ext2fs_badblocks_list_test (bb_list, currently_testing)
366                                 || ( bb_output (currently_testing), ++bb_count );
367
368                 }
369                 num_blocks = 0;
370                 alarm (0);
371                 if (s_flag | v_flag)
372                         fprintf(stderr, done_string);
373                 flush_bufs (dev, 0);
374         }
375
376         return bb_count;
377 }
378
379 static unsigned int test_nd (int dev, unsigned long blocks_count,
380                      unsigned long block_size,
381                      unsigned long from_count, unsigned long blocks_at_once)
382 {
383         char * blkbuf;
384         char * ptr;
385         int try;
386         long got;
387         long buf_used;
388         unsigned long bufblk[blocks_at_once];
389         unsigned long bufblks[blocks_at_once];
390         jmp_buf terminate_env;
391         unsigned int bb_count = 0;
392         errcode_t errcode;
393
394         errcode = ext2fs_badblocks_list_iterate_begin(bb_list,&bb_iter);
395         if (errcode) {
396                 com_err (program_name, errcode, "while beginning bad block list iteration");
397                 exit (1);
398         }
399         do {
400                 ext2fs_badblocks_list_iterate (bb_iter, &next_bad);
401         } while (next_bad && next_bad < from_count);
402
403         blkbuf = malloc (3 * blocks_at_once * block_size);
404         if (!blkbuf)
405         {
406                 com_err (program_name, ENOMEM, "while allocating buffers");
407                 exit (1);
408         }
409
410         /* inititalize the test data randomly: */
411         if (v_flag) {
412                 fprintf (stderr, "Initializing random test data\n");
413         }
414         for(ptr = blkbuf + blocks_at_once * block_size;
415                ptr < blkbuf + 2 * blocks_at_once * block_size;
416                   ++ptr) {
417                 (*ptr) = random() % (1 << sizeof(char));
418         }
419
420         flush_bufs (dev, 0);
421         if (v_flag) {
422             fprintf (stderr,
423                      "Checking for bad blocks in non-destructive read-write mode\n");
424             fprintf (stderr, "From block %lu to %lu\n", from_count, blocks_count);
425         }
426         buf_used = 0;
427         currently_testing = from_count;
428         num_blocks = blocks_count;
429         if (s_flag || v_flag > 1) {
430                 fprintf (stderr, "Checking for bad blocks (non-destructive read-write test): ");
431                 if (v_flag <= 1)
432                         alarm_intr(SIGALRM);
433         }
434         if (! setjmp(terminate_env)) {
435                 /* set up abend handler */
436                 capture_terminate(terminate_env);
437
438                 while (currently_testing < blocks_count)
439                 {
440                         try = blocks_at_once - buf_used;
441                         if (next_bad) {
442                                 if (currently_testing == next_bad) {
443                                         /* fprintf (out, "%lu\n", nextbad); */
444                                         ext2fs_badblocks_list_iterate (bb_iter, &next_bad);
445                                         bufblk[buf_used] = currently_testing++;
446                                         goto test_full_buf;
447                                 }
448                                 else if (currently_testing + try > next_bad)
449                                         try = next_bad - currently_testing;
450                         }
451                         if (currently_testing + try > blocks_count)
452                                 try = blocks_count - currently_testing;
453                         got = do_read (dev, blkbuf + buf_used * block_size, try,
454                                                         block_size, currently_testing);
455
456                         /* if reading succeeded, write the test data */
457                         if (got) {
458                                 long written;
459
460                                 written = do_write (dev, blkbuf + (buf_used + blocks_at_once) * block_size,
461                                                              got, block_size, currently_testing);
462                                 if (written != got)
463                                         com_err (program_name, errno, "during test data write, block %lu",
464                                                                           currently_testing + written);
465                         }
466
467                         bufblk[buf_used] = currently_testing;
468                         bufblks[buf_used] = got;
469                         buf_used += got;
470                         currently_testing += got;
471                         if (got != try)
472                                 bb_output (currently_testing++), ++bb_count;
473
474                         test_full_buf:
475
476                         if (buf_used == blocks_at_once || currently_testing == blocks_count) {
477                                 if (s_flag || v_flag > 1)
478                                         fprintf(stderr, "\n");
479                                 flush_bufs (dev, 1);
480
481                                 for (buf_used = 0, currently_testing = bufblk[0];
482                                         buf_used < blocks_at_once &&
483                                         currently_testing < blocks_count &&
484                     (currently_testing = bufblk[buf_used]) < blocks_count;
485                                            buf_used += bufblks[buf_used])
486                                         {
487                                                 /* for each contiguous block that we read into the buffer
488                                                    (and wrote test data into afterwards), read it back
489                                                    (looping if necessary, to get past newly discovered
490                                                    unreadable blocks, of which there should be none, but with
491                                                    a hard drive which is unreliable, it has happened), and
492                                                    compare with the test data that was written; output to
493                                                    the bad block list if it doesn't match. */
494
495                                                 int offset;
496
497                                                 while ((offset = currently_testing - bufblk[buf_used]) <
498                                                                                           bufblks[buf_used]) {
499                                                         int i;
500
501                                                         try = bufblks[buf_used] - offset;
502                                                         got = do_read (dev, blkbuf + (2 * blocks_at_once + buf_used + offset) * block_size,
503                                                                        try, block_size, currently_testing);
504
505                                                         /* test the comparison between all the blocks successfully read */
506                                                         for (i = 0; i < got; ++i)
507                                                                 if (memcmp (blkbuf + (blocks_at_once + buf_used + offset + i) * block_size,
508                                                                             blkbuf + (2 * blocks_at_once + buf_used + offset + i) * block_size,
509                                                                             block_size))
510                                                                         bb_output (currently_testing + i), ++bb_count;
511
512                                                         currently_testing += got;
513                                                         if (got != try)
514                                                                 bb_output (currently_testing++), ++bb_count;
515                                                 }
516
517                                                 /* when done, write back original data */
518                                                 do_write (dev, blkbuf + buf_used * block_size,
519                                                                bufblks[buf_used], block_size, bufblk[buf_used]);
520                                         }
521
522                                 /* empty the buffer so it can be reused */
523                                 buf_used = 0;
524                         }
525                 }
526                 num_blocks = 0;
527                 alarm(0);
528                 if (s_flag || v_flag > 1)
529                         fprintf(stderr, "done               \n");
530
531         } else {
532                 /* abnormal termination by a signal is handled here */
533                 /* logic is: write back the data that is in the buffer,
534                    so that we can maintain data integrity on disk.  Then
535                    abort. */
536                 long buf_written;
537
538                 fprintf(stderr, "Interrupt caught, cleaning up\n");
539
540                 for (buf_written = 0;
541                         buf_written < buf_used;
542                            buf_written += bufblks[buf_written])
543                         do_write (dev, blkbuf + buf_written * block_size,
544                                        bufblks[buf_written], block_size, bufblk[buf_written]);
545
546                 fflush (out);
547
548                 abort ();
549         }
550
551         fflush (stderr);
552         free (blkbuf);
553
554         ext2fs_badblocks_list_iterate_end(bb_iter);
555
556         return bb_count;
557 }
558
559 int main (int argc, char ** argv)
560 {
561         int c;
562         char * tmp;
563         char * device_name;
564         char * host_device_name = NULL;
565         char * input_file = NULL;
566         char * output_file = NULL;
567         FILE * in = NULL;
568         unsigned long block_size = 1024;
569         unsigned long blocks_at_once = 16;
570         unsigned long blocks_count, from_count;
571         int num_passes = 0;
572         int passes_clean = 0;
573         int dev;
574         errcode_t errcode;
575
576         setbuf(stdout, NULL);
577         setbuf(stderr, NULL);
578         if (argc && *argv)
579                 program_name = *argv;
580         while ((c = getopt (argc, argv, "b:i:o:svwnc:p:h:")) != EOF) {
581                 switch (c) {
582                 case 'b':
583                         block_size = strtoul (optarg, &tmp, 0);
584                         if (*tmp || block_size > 4096) {
585                                 com_err (program_name, 0,
586                                          "bad block size - %s", optarg);
587                                 exit (1);
588                         }
589                         break;
590                 case 'i':
591                         input_file = optarg;
592                         break;
593                 case 'o':
594                         output_file = optarg;
595                         break;
596                 case 's':
597                         s_flag = 1;
598                         break;
599                 case 'v':
600                         v_flag++;
601                         break;
602                 case 'w':
603                         if (! w_flag)
604                                 w_flag = 1;
605                         break;
606                 case 'n':
607                         w_flag = 2;
608                         break;
609                 case 'c':
610                         blocks_at_once = strtoul (optarg, &tmp, 0);
611                         if (*tmp) {
612                                 com_err (program_name, 0,
613                                          "bad simultaneous block count - %s", optarg);
614                                 exit (1);
615                         }
616                         break;
617                 case 'p':
618                         num_passes = strtoul (optarg, &tmp, 0);
619                         if (*tmp) {
620                                 com_err (program_name, 0,
621                                     "bad number of clean passes - %s", optarg);
622                                 exit (1);
623                         }
624                         break;
625                 case 'h':
626                         host_device_name = optarg;
627                         break;
628                 default:
629                         usage();
630                 }
631         }
632         if (optind > argc - 1)
633                 usage();
634         device_name = argv[optind++];
635         if (optind > argc - 1)
636                 usage();
637         blocks_count = strtoul (argv[optind], &tmp, 0);
638         if (*tmp)
639         {
640                 com_err (program_name, 0, "bad blocks count - %s", argv[optind]);
641                 exit (1);
642         }
643         if (++optind <= argc-1) {
644                 from_count = strtoul (argv[optind], &tmp, 0);
645         } else from_count = 0;
646         if (from_count >= blocks_count) {
647             com_err (program_name, 0, "bad blocks range: %lu-%lu",
648                      from_count, blocks_count);
649             exit (1);
650         }
651         dev = open (device_name, w_flag ? O_RDWR : O_RDONLY);
652         if (dev == -1)
653         {
654                 com_err (program_name, errno, "while trying to open %s",
655                          device_name);
656                 exit (1);
657         }
658         if (host_device_name) {
659                 host_dev = open (host_device_name, O_RDONLY);
660                 if (host_dev == -1)
661                 {
662                         com_err (program_name, errno, "while trying to open %s",
663                             host_device_name);
664                         exit (1);
665                 }
666         } else
667                 host_dev = dev;
668         if (input_file)
669                 if (strcmp (input_file, "-") == 0)
670                         in = stdin;
671                 else {
672                         in = fopen (input_file, "r");
673                         if (in == NULL)
674                         {
675                                 com_err (program_name, errno,"while trying to open %s",
676                                          input_file);
677                                 exit (1);
678                         }
679                 }
680         if (output_file && strcmp (output_file, "-") != 0)
681         {
682                 out = fopen (output_file, "w");
683                 if (out == NULL)
684                 {
685                         com_err (program_name, errno,"while trying to open %s",
686                                  output_file);
687                         exit (1);
688                 }
689         }
690         else
691                 out = stdout;
692
693         errcode = ext2fs_badblocks_list_create(&bb_list,0);
694         if (errcode) {
695                 com_err (program_name, errcode, "creating in-memory bad blocks list");
696                 exit (1);
697         }
698
699         if (in) {
700                 for(;;) {
701                         switch(fscanf (in, "%lu\n", &next_bad)) {
702                                 case 0:
703                                         com_err (program_name, 0, "input file - bad format");
704                                         exit (1);
705                                 case EOF:
706                                         break;
707                                 default:
708                                         errcode = ext2fs_badblocks_list_add(bb_list,next_bad);
709                                         if (errcode) {
710                                                 com_err (program_name, errcode, "adding to in-memory bad block list");
711                                                 exit (1);
712                                         }
713                                         continue;
714                         }
715                         break;
716                 }
717
718                 if (in != stdin)
719                         fclose (in);
720         }
721
722         do {
723                 unsigned int bb_count;
724
725                 (bb_count =
726
727                 (w_flag == 2 ? test_nd
728                     : w_flag ? test_rw
729                              : test_ro)
730                                         (dev, blocks_count, block_size, from_count,
731                                               blocks_at_once)
732                 )       ? passes_clean = 0 : ++passes_clean;
733
734                 if (v_flag)
735                         fprintf(stderr,"Pass completed, %u bad blocks found.\n", bb_count);
736
737         } while (passes_clean < num_passes);
738
739         close (dev);
740         if (out != stdout)
741                 fclose (out);
742         return 0;
743 }