Whamcloud - gitweb
b=20492 allow llverdev/llverfs to handle media errors better
[fs/lustre-release.git] / lustre / utils / llverfs.c
1 /* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*-
2  * vim:expandtab:shiftwidth=8:tabstop=8:
3  *
4  * GPL HEADER START
5  *
6  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License version 2 only,
10  * as published by the Free Software Foundation.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * General Public License version 2 for more details (a copy is included
16  * in the LICENSE file that accompanied this code).
17  *
18  * You should have received a copy of the GNU General Public License
19  * version 2 along with this program; If not, see
20  * http://www.sun.com/software/products/lustre/docs/GPLv2.pdf
21  *
22  * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
23  * CA 95054 USA or visit www.sun.com if you need additional information or
24  * have any questions.
25  *
26  * GPL HEADER END
27  */
28 /*
29  * Copyright  2009 Sun Microsystems, Inc. All rights reserved
30  * Use is subject to license terms.
31  */
32 /*
33  * This file is part of Lustre, http://www.lustre.org/
34  * Lustre is a trademark of Sun Microsystems, Inc.
35  *
36  * lustre/utils/llverfs.c
37  *
38  * Filesystem Verification Tool.
39  * This program tests the correct operation of large filesystems and
40  * the underlying block storage device(s).
41  * This tool have two working modes
42  * 1. full mode
43  * 2. fast mode
44  *
45  * In full mode, the program creates a subdirectory in the test
46  * fileysytem, writes n(files_in_dir, default=16) large(4GB) files to
47  * the directory with the test pattern at the start of each 4kb block.
48  * The test pattern contains timestamp, relative file offset and per
49  * file unique idenfifier(inode number).  This continues until the
50  * whole filesystem is full and then the tool verifies that the data
51  * in all of the test files is correct.
52  *
53  * In fast mode, the tool creates test directories with the
54  * EXT3_TOPDIR_FL flag set (if supported) to spread the directory data
55  * around the block device instead of localizing it in a single place.
56  * The number of directories equals to the number of block groups in the
57  * filesystem (e.g. 65536 directories for 8TB ext3/ext4 filesystem) and
58  * then writes a single 1MB file in each directory. The tool then verifies
59  * that the data in each file is correct.
60  */
61
62 #ifndef _GNU_SOURCE
63 #define _GNU_SOURCE
64 #endif
65 #ifndef LUSTRE_UTILS
66 #define LUSTRE_UTILS
67 #endif
68 #ifndef _LARGEFILE64_SOURCE
69 #define _LARGEFILE64_SOURCE
70 #endif
71 #ifndef _FILE_OFFSET_BITS
72 #define _FILE_OFFSET_BITS 64
73 #endif
74
75 #include <features.h>
76 #include <stdlib.h>
77 #include <stdio.h>
78 #include <string.h>
79 #include <ctype.h>
80 #include <fcntl.h>
81 #include <unistd.h>
82 #include <limits.h>
83 #include <errno.h>
84 #include <fcntl.h>
85 #include <getopt.h>
86 #include <time.h>
87 #include <dirent.h>
88 #include <mntent.h>
89 #include <sys/types.h>
90 #include <sys/stat.h>
91 #include <sys/vfs.h>
92 #include <gnu/stubs.h>
93 #include <gnu/stubs.h>
94
95 #ifdef HAVE_EXT2FS_EXT2FS_H
96 #  include <e2p/e2p.h>
97 #  include <ext2fs/ext2fs.h>
98 #endif
99
100 #define ONE_MB (1024 * 1024)
101 #define ONE_GB ((unsigned long long)(1024 * 1024 * 1024))
102 #define BLOCKSIZE 4096
103
104 /* Structure for writing test pattern */
105 struct block_data {
106         unsigned long long bd_offset;
107         unsigned long long bd_time;
108         unsigned long long bd_inode;
109 };
110 static char *progname;              /* name by which this program was run. */
111 static unsigned verbose = 1;        /* prints offset in kB, operation rate */
112 static int readoption;              /* run test in read-only (verify) mode */
113 static int writeoption;             /* run test in write_only mode */
114 char *testdir;                      /* name of device to be tested. */
115 static unsigned full = 1;           /* flag to full check */
116 static int error_count;             /* number of IO errors hit during run */
117 char filecount[PATH_MAX];           /* file with total number of files written*/
118 static unsigned long num_files;     /* Total number of files for read/write */
119 static loff_t file_size = 4*ONE_GB; /* Size of each file */
120 static unsigned files_in_dir = 32;  /* number of files in each directioy */
121 static unsigned num_dirs = 30000;   /* total number of directories */
122 const int dirmode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
123 static int isatty_flag;
124 static int perms =  S_IRWXU | S_IRGRP | S_IROTH;
125
126 static struct option const longopts[] =
127 {
128         { "chunksize", required_argument, 0, 'c' },
129         { "help", no_argument, 0, 'h' },
130         { "offset", required_argument, 0, 'o' },
131         { "long", no_argument, 0, 'l' },
132         { "full", no_argument, 0, 'l' },
133         { "partial", required_argument, 0, 'p' },
134         { "quiet", required_argument, 0, 'q' },
135         { "read", no_argument, 0, 'r' },
136         { "filesize", no_argument, 0, 's' },
137         { "timestamp", required_argument, 0, 't' },
138         { "verbose", no_argument, 0, 'v' },
139         { "write", no_argument, 0, 'w' },
140         { 0, 0, 0, 0}
141 };
142
143 /*
144  * Usages: displays help information, whenever user supply --help option in
145  * command or enters incorrect command line.
146  */
147 void usage(int status)
148 {
149         if (status != 0) {
150                 printf("\nUsage: %s [OPTION]... <filesystem path> ...\n",
151                        progname);
152                 printf("ext3 filesystem verification tool.\n"
153                        "\t-t {seconds}, --timestamp,  set test time"
154                        "(default=current time())\n"
155                        "\t-o {offset}, --offset, directory starting offset"
156                        " from which tests should start\n"
157                        "\t-r, --read, run in verify mode\n"
158                        "\t-w, --write, run in test-pattern mode, default=rw\n"
159                        "\t-v, --verbose\n"
160                        "\t-p, --partial, for partial check (1MB files)\n"
161                        "\t-l, --long, --full check (4GB file with 4k blocks)\n"
162                        "\t-c, --chunksize, IO chunk size in MB (default=1)\n"
163                        "\t-s, --filesize, file size in MB (default=4096)\n"
164                        "\t-h, --help, display this help and exit\n");
165         }
166         exit(status);
167 }
168
169 /*
170  * open_file: Opens file in specified mode and returns fd.
171  */
172 static int open_file(const char *file, int flag)
173 {
174         int fd = open(file, flag, perms);
175         if (fd < 0) {
176                 fprintf(stderr, "\n%s: Open '%s' failed:%s\n",
177                         progname, file, strerror(errno));
178                 exit(3);
179         }
180         return (fd);
181 }
182
183 /*
184  * Verify_chunk: Verifies test pattern in each 4kB (BLOCKSIZE) is correct.
185  * Returns 0 if test offset and timestamp is correct otherwise 1.
186  */
187 int verify_chunk(char *chunk_buf, const size_t chunksize,
188                  unsigned long long chunk_off, const unsigned long long time_st,
189                  const unsigned long long inode_st, const char *file)
190 {
191         struct block_data *bd;
192         char *chunk_end;
193
194         for (chunk_end = chunk_buf + chunksize - sizeof(*bd);
195              (char *)chunk_buf < chunk_end;
196              chunk_buf += BLOCKSIZE, chunk_off += BLOCKSIZE) {
197                 bd = (struct block_data *)chunk_buf;
198                 if ((bd->bd_offset == chunk_off) && (bd->bd_time == time_st) &&
199                     (bd->bd_inode == inode_st))
200                         continue;
201                 fprintf(stderr, "\n%s: verify %s failed offset/timestamp/inode "
202                         "%llu/%llu/%llu: found %llu/%llu/%llu instead\n",
203                         progname, file, chunk_off, time_st, inode_st,
204                         bd->bd_offset, bd->bd_time, bd->bd_inode);
205                 return 1;
206         }
207         return 0;
208 }
209
210 /*
211  * fill_chunk: Fills the chunk with current or user specified timestamp
212  * and  offset. The test patters is filled at the beginning of
213  * each 4kB(BLOCKSIZE) blocks in chunk_buf.
214  */
215 void fill_chunk(char *chunk_buf, size_t chunksize, loff_t chunk_off,
216                 const time_t time_st, const ino_t inode_st)
217 {
218         struct block_data *bd;
219         char *chunk_end;
220
221         for (chunk_end = chunk_buf + chunksize - sizeof(*bd);
222              (char *)chunk_buf < chunk_end;
223              chunk_buf += BLOCKSIZE, chunk_off += BLOCKSIZE) {
224                 bd = (struct block_data *)chunk_buf;
225                 bd->bd_offset = chunk_off;
226                 bd->bd_time = time_st;
227                 bd->bd_inode = inode_st;
228         }
229 }
230
231 /*
232  * Write a chunk to disk, handling errors, interrupted writes, etc.
233  *
234  * If there is an IO error hit during the write, it is possible that
235  * this will just show up as a short write, and a subsequent write
236  * will return the actual error.  We want to continue in the face of
237  * minor media errors so that we can validate the whole device if
238  * possible, but if there are many errors we don't want to loop forever.
239  *
240  * The error count will be returned upon exit to ensure that the
241  * media errors are detected even if nobody is looking at the output.
242  *
243  * Returns 0 on success, or -ve errno on failure.
244  */
245 int write_retry(int fd, const char *chunk_buf, size_t nrequested,
246                 unsigned long long offset, const char *file)
247 {
248         long nwritten;
249
250 retry:
251         nwritten = write(fd, chunk_buf, nrequested);
252         if (nwritten < 0) {
253                 if (errno != ENOSPC) {
254                         fprintf(stderr, "\n%s: write %s@%llu+%zi failed: %s\n",
255                                 progname, file, offset, nrequested,
256                                 strerror(errno));
257                         if (error_count++ < 100)
258                                 return 0;
259                 }
260                 return -errno;
261         }
262         if (nwritten < nrequested) {
263                 fprintf(stderr, "\n%s: write %s@%llu+%zi short: %ld written\n",
264                         progname, file, offset, nrequested, nwritten);
265                 offset += nwritten;
266                 nrequested -= nwritten;
267                 goto retry;
268         }
269
270         return 0;
271 }
272
273 /*
274  * write_chunks: write the chunk_buf on the device. The number of write
275  * operations are based on the parameters write_end, offset, and chunksize.
276  *
277  * Returns 0 on success, or -ve error number on failure.
278  */
279 int write_chunks(int fd, unsigned long long offset,unsigned long long write_end,
280                  char *chunk_buf, size_t chunksize, const time_t time_st,
281                  const ino_t inode_st, const char *file)
282 {
283         unsigned long long stride;
284
285         stride = full ? chunksize : (ONE_GB - chunksize);
286         for (offset = offset & ~(chunksize - 1); offset < write_end;
287              offset += stride) {
288                 int ret;
289
290                 if (lseek64(fd, offset, SEEK_SET) == -1) {
291                         fprintf(stderr, "\n%s: lseek64(%s+%llu) failed: %s\n",
292                                 progname, file, offset, strerror(errno));
293                         return -errno;
294                 }
295                 if (offset + chunksize > write_end)
296                         chunksize = write_end - offset;
297                 if (!full && offset > chunksize) {
298                         fill_chunk(chunk_buf, chunksize, offset, time_st,
299                                    inode_st);
300                         ret = write_retry(fd, chunk_buf, chunksize,offset,file);
301                         if (ret < 0)
302                                 return ret;
303                         offset += chunksize;
304                         if (offset + chunksize > write_end)
305                                 chunksize = write_end - offset;
306                 }
307                 fill_chunk(chunk_buf, chunksize, offset, time_st, inode_st);
308                 ret = write_retry(fd, chunk_buf, chunksize, offset, file);
309                 if (ret < 0)
310                         return ret;
311         }
312         return 0;
313 }
314
315 /*
316  * read_chunk: reads the chunk_buf from the device. The number of read
317  * operations are based on the parameters read_end, offset, and chunksize.
318  */
319 int read_chunks(int fd, unsigned long long offset, unsigned long long read_end,
320                 char *chunk_buf, size_t chunksize, const time_t time_st,
321                 const ino_t inode_st, const char *file)
322 {
323         unsigned long long stride;
324
325         stride = full ? chunksize : (ONE_GB - chunksize);
326         for (offset = offset & ~(chunksize - 1); offset < read_end;
327              offset += stride) {
328                 ssize_t nread;
329
330                 if (lseek64(fd, offset, SEEK_SET) == -1) {
331                         fprintf(stderr, "\n%s: lseek64(%s+%llu) failed: %s\n",
332                                 progname, file, offset, strerror(errno));
333                         return 1;
334                 }
335                 if (offset + chunksize > read_end)
336                         chunksize = read_end - offset;
337
338                 if (!full && offset > chunksize) {
339                         nread = read(fd, chunk_buf, chunksize);
340                         if (nread < 0) {
341                                 fprintf(stderr,"\n%s: read %s@%llu+%zi failed: "
342                                         "%s\n", progname, file, offset,
343                                         chunksize, strerror(errno));
344                                 error_count++;
345                                 return 1;
346                         }
347                         if (nread < chunksize) {
348                                 fprintf(stderr, "\n%s: read %s@%llu+%zi short: "
349                                         "%zi read\n", progname, file, offset,
350                                         chunksize, nread);
351                                 error_count++;
352                         }
353                         if (verify_chunk(chunk_buf, nread, offset, time_st,
354                                          inode_st, file) != 0) {
355                                 return 1;
356                         }
357                         offset += chunksize;
358
359                         /* Need to reset position after read error */
360                         if (nread < chunksize &&
361                             lseek64(fd, offset, SEEK_SET) == -1) {
362                                 fprintf(stderr,
363                                         "\n%s: lseek64(%s@%llu) failed: %s\n",
364                                         progname, file, offset,strerror(errno));
365                                 return 1;
366                         }
367                         if (offset + chunksize >= read_end)
368                                 chunksize = read_end - offset;
369                 }
370                 nread = read(fd, chunk_buf, chunksize);
371                 if (nread < 0) {
372                         fprintf(stderr, "\n%s: read %s@%llu+%zi failed: %s\n",
373                                 progname, file, offset, chunksize,
374                                 strerror(errno));
375                         error_count++;
376                         return 1;
377                 }
378                 if (nread < chunksize) {
379                         fprintf(stderr, "\n%s: read %s@%llu+%zi short: "
380                                 "%zi read\n", progname, file, offset,
381                                 chunksize, nread);
382                         error_count++;
383                 }
384
385                 if (verify_chunk(chunk_buf, nread, offset, time_st,
386                                  inode_st, file) != 0) {
387                         return 1;
388                 }
389         }
390         return 0;
391 }
392
393 /*
394  * new_file: prepares new filename using file counter and current dir.
395  */
396 char *new_file(char *tempfile, char *cur_dir, int file_num)
397 {
398         sprintf(tempfile, "%s/file%03d", cur_dir, file_num);
399         return tempfile;
400 }
401
402 /*
403  * new_dir: prepares new dir name using dir counters.
404  */
405 char *new_dir(char *tempdir, int dir_num)
406 {
407         sprintf(tempdir, "%s/dir%05d", testdir, dir_num);
408         return tempdir;
409 }
410
411 /*
412  * show_filename: Displays name of current file read/write
413  */
414 void show_filename(char *op, char *filename)
415 {
416         static time_t last;
417         time_t now;
418         double diff;
419
420         now = time(NULL);
421         diff = now - last;
422         if (diff > 4 || verbose > 2) {
423                 if (isatty_flag)
424                         printf("\r");
425                 printf("%s File name: %s          ", op, filename);
426                 if (isatty_flag)
427                         fflush(stdout);
428                 else
429                         printf("\n");
430                 last = now;
431         }
432 }
433
434 /*
435  * dir_write: This function writes directories and files on device.
436  * it works for both full and fast modes.
437  */
438 static int dir_write(char *chunk_buf, size_t chunksize,
439                      time_t time_st, unsigned long dir_num)
440 {
441         char tempfile[PATH_MAX];
442         char tempdir[PATH_MAX];
443         FILE *countfile;
444         struct stat64 file;
445         int file_num = 999999999;
446         ino_t inode_st = 0;
447
448 #ifdef HAVE_EXT2FS_EXT2FS_H
449         if (!full && fsetflags(testdir, EXT2_TOPDIR_FL))
450                 fprintf(stderr,
451                         "\n%s: can't set TOPDIR_FL on %s: %s (ignoring)",
452                         progname, testdir, strerror(errno));
453 #endif
454         countfile = fopen(filecount, "w");
455         if (countfile == NULL) {
456                 fprintf(stderr, "\n%s: creating %s failed :%s\n",
457                         progname, filecount, strerror(errno));
458                 return 5;
459         }
460         /* reserve space for the countfile */
461         if (fprintf(countfile, "%lu", num_files) < 1 ||
462             fflush(countfile) != 0) {
463                 fprintf(stderr, "\n%s: writing %s failed :%s\n",
464                         progname, filecount, strerror(errno));
465                 return 6;
466         }
467         for (; dir_num < num_dirs; num_files++, file_num++) {
468                 int fd, ret;
469
470                 if (file_num >= files_in_dir) {
471                         if (dir_num == num_dirs - 1)
472                                 break;
473
474                         file_num = 0;
475                         if (mkdir(new_dir(tempdir, dir_num), dirmode) < 0) {
476                                 if (errno == ENOSPC)
477                                         break;
478                                 if (errno != EEXIST) {
479                                         fprintf(stderr, "\n%s: mkdir %s : %s\n",
480                                                 progname, tempdir,
481                                                 strerror(errno));
482                                         return 1;
483                                 }
484                         }
485                         dir_num++;
486                 }
487                 fd = open_file(new_file(tempfile, tempdir, file_num),
488                                O_WRONLY | O_CREAT | O_TRUNC | O_LARGEFILE);
489
490                 if (fd >= 0 && fstat64(fd, &file) == 0) {
491                         inode_st = file.st_ino;
492                 } else {
493                         fprintf(stderr, "\n%s: write stat64 to file %s: %s",
494                                 progname, tempfile, strerror(errno));
495                         exit(1);
496                 }
497
498                 if (verbose > 1)
499                         show_filename("write", tempfile);
500
501                 ret = write_chunks(fd, 0, file_size, chunk_buf, chunksize,
502                                    time_st, inode_st, tempfile);
503                 close(fd);
504                 if (ret < 0) {
505                         if (ret != -ENOSPC)
506                                 return 1;
507                         break;
508                 }
509
510                 fseek(countfile, 0, SEEK_SET);
511                 if (fprintf(countfile, "%lu", num_files) < 1 ||
512                     fflush(countfile) != 0) {
513                         fprintf(stderr, "\n%s: writing %s failed :%s\n",
514                                 progname, filecount, strerror(errno));
515                 }
516         }
517         fclose(countfile);
518
519         if (verbose) {
520                 verbose++;
521                 show_filename("write", tempfile);
522                 printf("\nwrite complete\n");
523                 verbose--;
524         }
525
526         return 0;
527 }
528
529 /*
530  * dir_read: This function reads directories and files on device.
531  * it works for both full and fast modes.
532  */
533 static int dir_read(char *chunk_buf, size_t chunksize,
534                     time_t time_st, unsigned long dir_num)
535 {
536         char tempfile[PATH_MAX];
537         char tempdir[PATH_MAX];
538         unsigned long count = 0;
539         struct stat64 file;
540         int file_num = 0;
541         ino_t inode_st = 0;
542
543         for (count = 0; count < num_files && dir_num < num_dirs; count++) {
544                 int fd, ret;
545
546                 if (file_num == 0) {
547                         if (dir_num == num_dirs - 1)
548                                 break;
549
550                         new_dir(tempdir, dir_num);
551                         dir_num++;
552                 }
553
554                 fd = open_file(new_file(tempfile, tempdir, file_num),
555                                O_RDONLY | O_LARGEFILE);
556                 if (fd >= 0 && fstat64(fd, &file) == 0) {
557                         inode_st = file.st_ino;
558                 } else {
559                         fprintf(stderr, "\n%s: read stat64 file '%s': %s\n",
560                                 progname, tempfile, strerror(errno));
561                         return 1;
562                 }
563
564                 if (verbose > 1)
565                         show_filename("read", tempfile);
566
567                 if (count == num_files)
568                         file_size = file.st_size;
569                 ret = read_chunks(fd, 0, file_size, chunk_buf, chunksize,
570                                   time_st, inode_st, tempfile);
571                 close(fd);
572                 if (ret)
573                         return 1;
574
575                 if (++file_num >= files_in_dir)
576                         file_num = 0;
577         }
578         if (verbose > 1){
579                 verbose++;
580                 show_filename("read", tempfile);
581                 printf("\nread complete\n");
582                 verbose--;
583         }
584         return 0;
585 }
586
587 int main(int argc, char **argv)
588 {
589         time_t time_st = 0;             /* Default timestamp */
590         size_t chunksize = ONE_MB;      /* IO chunk size(defailt=1MB) */
591         char *chunk_buf;                /* chunk buffer */
592         int error = 0;
593         FILE *countfile = NULL;
594         unsigned long dir_num = 0, dir_num_orig = 0;/* starting directory */
595         int c;
596
597         progname = strrchr(argv[0], '/') ? strrchr(argv[0], '/') + 1 : argv[0];
598         while ((c = getopt_long(argc, argv, "c:hlo:pqrs:t:vw",
599                                       longopts, NULL)) != -1) {
600                 switch (c) {
601                 case 'c':
602                        chunksize = strtoul(optarg, NULL, 0) * ONE_MB;
603                        if (chunksize == 0) {
604                                fprintf(stderr, "%s: bad chunk size '%s'\n",
605                                        optarg, progname);
606                                 return -1;
607                         }
608                         break;
609                 case 'l':
610                         full = 1;
611                         break;
612                 case 'o': /* offset */
613                         dir_num = strtoul(optarg, NULL, 0);
614                         break;
615                 case 'p':
616                         file_size = ONE_MB;
617                         chunksize = ONE_MB;
618                         files_in_dir = 1;
619                         full = 0;
620                         break;
621                 case 'q':
622                         verbose = 0;
623                         break;
624                 case 'r':
625                         readoption = 1;
626                         break;
627                 case 's':
628                         file_size = strtoul(optarg, NULL, 0) * ONE_MB;
629                         if (file_size == 0) {
630                                 fprintf(stderr, "%s: bad file size '%s'\n",
631                                         optarg, progname);
632                                 return -1;
633                         }
634                         break;
635                 case 't':
636                         time_st = (time_t)strtoul(optarg, NULL, 0);
637                         break;
638                 case 'v':
639                         verbose++;
640                         break;
641                 case 'w':
642                         writeoption = 1;
643                         break;
644
645                 case 'h':
646                 default:
647                         usage(1);
648                         return 0;
649                 }
650         }
651         testdir = argv[optind];
652
653         if (!testdir) {
654                 fprintf(stderr, "%s: pathname not given\n", progname);
655                 usage(1);
656                 return -1;
657         }
658         if (!readoption && !writeoption) {
659                 readoption = 1;
660                 writeoption = 1;
661         }
662         if (!time_st)
663                 (void) time(&time_st);
664         printf("Timestamp: %lu\n", (unsigned long )time_st);
665         isatty_flag = isatty(STDOUT_FILENO);
666
667         if (!full) {
668 #ifdef HAVE_EXT2FS_EXT2FS_H
669                 struct mntent *tempmnt;
670                 FILE *fp = NULL;
671                 ext2_filsys fs;
672
673                 if ((fp = setmntent("/etc/mtab", "r")) == NULL){
674                         fprintf(stderr, "%s: fail to open /etc/mtab in read"
675                                 "mode :%s\n", progname, strerror(errno));
676                         goto guess;
677                 }
678
679                 /* find device name using filesystem */
680                 while ((tempmnt = getmntent(fp)) != NULL) {
681                         if (strcmp(tempmnt->mnt_dir, testdir) == 0)
682                                 break;
683                 }
684
685                 if (tempmnt == NULL) {
686                         fprintf(stderr, "%s: no device found for '%s'\n",
687                                 progname, testdir);
688                         endmntent(fp);
689                         goto guess;
690                 }
691
692                 if (ext2fs_open(tempmnt->mnt_fsname, 0, 0, 0,
693                                 unix_io_manager, &fs)) {
694                         fprintf(stderr, "%s: unable to open ext3 fs on '%s'\n",
695                                 progname, testdir);
696                         endmntent(fp);
697                         goto guess;
698                 }
699                 endmntent(fp);
700
701                 num_dirs = (fs->super->s_blocks_count +
702                             fs->super->s_blocks_per_group - 1) /
703                         fs->super->s_blocks_per_group;
704                 if (verbose)
705                         printf("ext3 block groups: %u, fs blocks: %u "
706                                "blocks per group: %u\n",
707                                num_dirs, fs->super->s_blocks_count,
708                                fs->super->s_blocks_per_group);
709                 ext2fs_close(fs);
710 #else
711                 goto guess;
712 #endif
713                 if (0) { /* ugh */
714                         struct statfs64 statbuf;
715 guess:
716                         if (statfs64(testdir, &statbuf) == 0) {
717                                 num_dirs = (long long)statbuf.f_blocks *
718                                         statbuf.f_bsize / (128ULL << 20);
719                                 if (verbose)
720                                         printf("dirs: %u, fs blocks: %llu\n",
721                                                num_dirs,
722                                                (long long)statbuf.f_blocks);
723                         } else {
724                                 fprintf(stderr, "%s: unable to stat '%s': %s\n",
725                                         progname, testdir, strerror(errno));
726                                 if (verbose)
727                                         printf("dirs: %u\n", num_dirs);
728                         }
729                 }
730         }
731         chunk_buf = (char *)calloc(chunksize, 1);
732         if (chunk_buf == NULL) {
733                 fprintf(stderr, "Memory allocation failed for chunk_buf\n");
734                 return 4;
735         }
736         sprintf(filecount, "%s/%s.filecount", testdir, progname);
737         if (writeoption) {
738                 (void)mkdir(testdir, dirmode);
739
740                 unlink(filecount);
741                 if (dir_num != 0) {
742                         num_files = dir_num * files_in_dir;
743                         if (verbose)
744                                 printf("\n%s: %lu files already written\n",
745                                        progname, num_files);
746                 }
747                 if (dir_write(chunk_buf, chunksize, time_st, dir_num)) {
748                         error = 3;
749                         goto out;
750                 }
751                 dir_num = dir_num_orig;
752         }
753         if (readoption) {
754                 if (!writeoption) {
755                         countfile = fopen(filecount, "r");
756                         if (countfile == NULL ||
757                             fscanf(countfile, "%lu", &num_files) != 1 ||
758                             num_files == 0) {
759                                 fprintf(stderr, "\n%s: reading %s failed :%s\n",
760                                         progname, filecount, strerror(errno));
761                                 num_files = num_dirs * files_in_dir;
762                         } else {
763                                 num_files -= (dir_num * files_in_dir);
764                         }
765                         if (countfile)
766                                 fclose(countfile);
767                 }
768                 if (dir_read(chunk_buf, chunksize, time_st, dir_num)) {
769                         fprintf(stderr, "\n%s: Data verification failed\n",
770                                 progname) ;
771                         error = 2;
772                         goto out;
773                 }
774         }
775         error = error_count;
776 out:
777         free(chunk_buf);
778         return error;
779 }