Whamcloud - gitweb
b34519bad3e8a91d80b442ba1753561bedff9dfd
[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 (c) 2007, 2010, Oracle and/or its affiliates. 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         }
179         return (fd);
180 }
181
182 /*
183  * Verify_chunk: Verifies test pattern in each 4kB (BLOCKSIZE) is correct.
184  * Returns 0 if test offset and timestamp is correct otherwise 1.
185  */
186 int verify_chunk(char *chunk_buf, const size_t chunksize,
187                  unsigned long long chunk_off, const unsigned long long time_st,
188                  const unsigned long long inode_st, const char *file)
189 {
190         struct block_data *bd;
191         char *chunk_end;
192
193         for (chunk_end = chunk_buf + chunksize - sizeof(*bd);
194              (char *)chunk_buf < chunk_end;
195              chunk_buf += BLOCKSIZE, chunk_off += BLOCKSIZE) {
196                 bd = (struct block_data *)chunk_buf;
197                 if ((bd->bd_offset == chunk_off) && (bd->bd_time == time_st) &&
198                     (bd->bd_inode == inode_st))
199                         continue;
200                 fprintf(stderr, "\n%s: verify %s failed offset/timestamp/inode "
201                         "%llu/%llu/%llu: found %llu/%llu/%llu instead\n",
202                         progname, file, chunk_off, time_st, inode_st,
203                         bd->bd_offset, bd->bd_time, bd->bd_inode);
204                 return 1;
205         }
206         return 0;
207 }
208
209 /*
210  * fill_chunk: Fills the chunk with current or user specified timestamp
211  * and  offset. The test patters is filled at the beginning of
212  * each 4kB(BLOCKSIZE) blocks in chunk_buf.
213  */
214 void fill_chunk(char *chunk_buf, size_t chunksize, loff_t chunk_off,
215                 const time_t time_st, const ino_t inode_st)
216 {
217         struct block_data *bd;
218         char *chunk_end;
219
220         for (chunk_end = chunk_buf + chunksize - sizeof(*bd);
221              (char *)chunk_buf < chunk_end;
222              chunk_buf += BLOCKSIZE, chunk_off += BLOCKSIZE) {
223                 bd = (struct block_data *)chunk_buf;
224                 bd->bd_offset = chunk_off;
225                 bd->bd_time = time_st;
226                 bd->bd_inode = inode_st;
227         }
228 }
229
230 /*
231  * Write a chunk to disk, handling errors, interrupted writes, etc.
232  *
233  * If there is an IO error hit during the write, it is possible that
234  * this will just show up as a short write, and a subsequent write
235  * will return the actual error.  We want to continue in the face of
236  * minor media errors so that we can validate the whole device if
237  * possible, but if there are many errors we don't want to loop forever.
238  *
239  * The error count will be returned upon exit to ensure that the
240  * media errors are detected even if nobody is looking at the output.
241  *
242  * Returns 0 on success, or -ve errno on failure.
243  */
244 int write_retry(int fd, const char *chunk_buf, size_t nrequested,
245                 unsigned long long offset, const char *file)
246 {
247         long nwritten;
248
249 retry:
250         nwritten = write(fd, chunk_buf, nrequested);
251         if (nwritten < 0) {
252                 if (errno != ENOSPC) {
253                         fprintf(stderr, "\n%s: write %s@%llu+%zi failed: %s\n",
254                                 progname, file, offset, nrequested,
255                                 strerror(errno));
256                         if (error_count++ < 100)
257                                 return 0;
258                 }
259                 return -errno;
260         }
261         if (nwritten < nrequested) {
262                 fprintf(stderr, "\n%s: write %s@%llu+%zi short: %ld written\n",
263                         progname, file, offset, nrequested, nwritten);
264                 offset += nwritten;
265                 nrequested -= nwritten;
266                 goto retry;
267         }
268
269         return 0;
270 }
271
272 /*
273  * write_chunks: write the chunk_buf on the device. The number of write
274  * operations are based on the parameters write_end, offset, and chunksize.
275  *
276  * Returns 0 on success, or -ve error number on failure.
277  */
278 int write_chunks(int fd, unsigned long long offset,unsigned long long write_end,
279                  char *chunk_buf, size_t chunksize, const time_t time_st,
280                  const ino_t inode_st, const char *file)
281 {
282         unsigned long long stride;
283
284         stride = full ? chunksize : (ONE_GB - chunksize);
285         for (offset = offset & ~(chunksize - 1); offset < write_end;
286              offset += stride) {
287                 int ret;
288
289                 if (lseek64(fd, offset, SEEK_SET) == -1) {
290                         fprintf(stderr, "\n%s: lseek64(%s+%llu) failed: %s\n",
291                                 progname, file, offset, strerror(errno));
292                         return -errno;
293                 }
294                 if (offset + chunksize > write_end)
295                         chunksize = write_end - offset;
296                 if (!full && offset > chunksize) {
297                         fill_chunk(chunk_buf, chunksize, offset, time_st,
298                                    inode_st);
299                         ret = write_retry(fd, chunk_buf, chunksize,offset,file);
300                         if (ret < 0)
301                                 return ret;
302                         offset += chunksize;
303                         if (offset + chunksize > write_end)
304                                 chunksize = write_end - offset;
305                 }
306                 fill_chunk(chunk_buf, chunksize, offset, time_st, inode_st);
307                 ret = write_retry(fd, chunk_buf, chunksize, offset, file);
308                 if (ret < 0)
309                         return ret;
310         }
311         return 0;
312 }
313
314 /*
315  * read_chunk: reads the chunk_buf from the device. The number of read
316  * operations are based on the parameters read_end, offset, and chunksize.
317  */
318 int read_chunks(int fd, unsigned long long offset, unsigned long long read_end,
319                 char *chunk_buf, size_t chunksize, const time_t time_st,
320                 const ino_t inode_st, const char *file)
321 {
322         unsigned long long stride;
323
324         stride = full ? chunksize : (ONE_GB - chunksize);
325         for (offset = offset & ~(chunksize - 1); offset < read_end;
326              offset += stride) {
327                 ssize_t nread;
328
329                 if (lseek64(fd, offset, SEEK_SET) == -1) {
330                         fprintf(stderr, "\n%s: lseek64(%s+%llu) failed: %s\n",
331                                 progname, file, offset, strerror(errno));
332                         return 1;
333                 }
334                 if (offset + chunksize > read_end)
335                         chunksize = read_end - offset;
336
337                 if (!full && offset > chunksize) {
338                         nread = read(fd, chunk_buf, chunksize);
339                         if (nread < 0) {
340                                 fprintf(stderr,"\n%s: read %s@%llu+%zi failed: "
341                                         "%s\n", progname, file, offset,
342                                         chunksize, strerror(errno));
343                                 error_count++;
344                                 return 1;
345                         }
346                         if (nread < chunksize) {
347                                 fprintf(stderr, "\n%s: read %s@%llu+%zi short: "
348                                         "%zi read\n", progname, file, offset,
349                                         chunksize, nread);
350                                 error_count++;
351                         }
352                         if (verify_chunk(chunk_buf, nread, offset, time_st,
353                                          inode_st, file) != 0) {
354                                 return 1;
355                         }
356                         offset += chunksize;
357
358                         /* Need to reset position after read error */
359                         if (nread < chunksize &&
360                             lseek64(fd, offset, SEEK_SET) == -1) {
361                                 fprintf(stderr,
362                                         "\n%s: lseek64(%s@%llu) failed: %s\n",
363                                         progname, file, offset,strerror(errno));
364                                 return 1;
365                         }
366                         if (offset + chunksize >= read_end)
367                                 chunksize = read_end - offset;
368                 }
369                 nread = read(fd, chunk_buf, chunksize);
370                 if (nread < 0) {
371                         fprintf(stderr, "\n%s: read %s@%llu+%zi failed: %s\n",
372                                 progname, file, offset, chunksize,
373                                 strerror(errno));
374                         error_count++;
375                         return 1;
376                 }
377                 if (nread < chunksize) {
378                         fprintf(stderr, "\n%s: read %s@%llu+%zi short: "
379                                 "%zi read\n", progname, file, offset,
380                                 chunksize, nread);
381                         error_count++;
382                 }
383
384                 if (verify_chunk(chunk_buf, nread, offset, time_st,
385                                  inode_st, file) != 0) {
386                         return 1;
387                 }
388         }
389         return 0;
390 }
391
392 /*
393  * new_file: prepares new filename using file counter and current dir.
394  */
395 char *new_file(char *tempfile, char *cur_dir, int file_num)
396 {
397         sprintf(tempfile, "%s/file%03d", cur_dir, file_num);
398         return tempfile;
399 }
400
401 /*
402  * new_dir: prepares new dir name using dir counters.
403  */
404 char *new_dir(char *tempdir, int dir_num)
405 {
406         sprintf(tempdir, "%s/dir%05d", testdir, dir_num);
407         return tempdir;
408 }
409
410 /*
411  * show_filename: Displays name of current file read/write
412  */
413 void show_filename(char *op, char *filename)
414 {
415         static time_t last;
416         time_t now;
417         double diff;
418
419         now = time(NULL);
420         diff = now - last;
421         if (diff > 4 || verbose > 2) {
422                 if (isatty_flag)
423                         printf("\r");
424                 printf("%s File name: %s          ", op, filename);
425                 if (isatty_flag)
426                         fflush(stdout);
427                 else
428                         printf("\n");
429                 last = now;
430         }
431 }
432
433 /*
434  * dir_write: This function writes directories and files on device.
435  * it works for both full and fast modes.
436  */
437 static int dir_write(char *chunk_buf, size_t chunksize,
438                      time_t time_st, unsigned long dir_num)
439 {
440         char tempfile[PATH_MAX];
441         char tempdir[PATH_MAX];
442         FILE *countfile;
443         struct stat64 file;
444         int file_num = 999999999;
445         ino_t inode_st = 0;
446
447 #ifdef HAVE_EXT2FS_EXT2FS_H
448         if (!full && fsetflags(testdir, EXT2_TOPDIR_FL))
449                 fprintf(stderr,
450                         "\n%s: can't set TOPDIR_FL on %s: %s (ignoring)",
451                         progname, testdir, strerror(errno));
452 #endif
453         countfile = fopen(filecount, "w");
454         if (countfile == NULL) {
455                 fprintf(stderr, "\n%s: creating %s failed :%s\n",
456                         progname, filecount, strerror(errno));
457                 return 5;
458         }
459         /* reserve space for the countfile */
460         if (fprintf(countfile, "%lu", num_files) < 1 ||
461             fflush(countfile) != 0) {
462                 fprintf(stderr, "\n%s: writing %s failed :%s\n",
463                         progname, filecount, strerror(errno));
464                 return 6;
465         }
466         for (; dir_num < num_dirs; num_files++, file_num++) {
467                 int fd, ret;
468
469                 if (file_num >= files_in_dir) {
470                         if (dir_num == num_dirs - 1)
471                                 break;
472
473                         file_num = 0;
474                         if (mkdir(new_dir(tempdir, dir_num), dirmode) < 0) {
475                                 if (errno == ENOSPC)
476                                         break;
477                                 if (errno != EEXIST) {
478                                         fprintf(stderr, "\n%s: mkdir %s : %s\n",
479                                                 progname, tempdir,
480                                                 strerror(errno));
481                                         return 1;
482                                 }
483                         }
484                         dir_num++;
485                 }
486
487                 fd = open_file(new_file(tempfile, tempdir, file_num),
488                                O_WRONLY | O_CREAT | O_TRUNC | O_LARGEFILE);
489                 if (fd >= 0) {
490                         if (fstat64(fd, &file) == 0) {
491                                 inode_st = file.st_ino;
492                         } else {
493                                 fprintf(stderr, "\n%s: write stat '%s': %s",
494                                         progname, tempfile, strerror(errno));
495                                 close(fd);
496                                 break;
497                         }
498                 } else {
499                         break;
500                 }
501
502                 if (verbose > 1)
503                         show_filename("write", tempfile);
504
505                 ret = write_chunks(fd, 0, file_size, chunk_buf, chunksize,
506                                    time_st, inode_st, tempfile);
507                 close(fd);
508                 if (ret < 0) {
509                         if (ret != -ENOSPC)
510                                 return 1;
511                         break;
512                 }
513
514                 fseek(countfile, 0, SEEK_SET);
515                 if (fprintf(countfile, "%lu", num_files) < 1 ||
516                     fflush(countfile) != 0) {
517                         fprintf(stderr, "\n%s: writing %s failed :%s\n",
518                                 progname, filecount, strerror(errno));
519                 }
520         }
521         fclose(countfile);
522
523         if (verbose) {
524                 verbose++;
525                 show_filename("write", tempfile);
526                 printf("\nwrite complete\n");
527                 verbose--;
528         }
529
530         return 0;
531 }
532
533 /*
534  * dir_read: This function reads directories and files on device.
535  * it works for both full and fast modes.
536  */
537 static int dir_read(char *chunk_buf, size_t chunksize,
538                     time_t time_st, unsigned long dir_num)
539 {
540         char tempfile[PATH_MAX];
541         char tempdir[PATH_MAX];
542         unsigned long count = 0;
543         struct stat64 file;
544         int file_num = 0;
545         ino_t inode_st = 0;
546
547         for (count = 0; count < num_files && dir_num < num_dirs; count++) {
548                 int fd, ret;
549
550                 if (file_num == 0) {
551                         if (dir_num == num_dirs - 1)
552                                 break;
553
554                         new_dir(tempdir, dir_num);
555                         dir_num++;
556                 }
557
558                 fd = open_file(new_file(tempfile, tempdir, file_num),
559                                O_RDONLY | O_LARGEFILE);
560                 if (fd >= 0) {
561                         if (fstat64(fd, &file) == 0) {
562                                 inode_st = file.st_ino;
563                         } else {
564                                 fprintf(stderr, "\n%s: read stat '%s': %s\n",
565                                         progname, tempfile, strerror(errno));
566                                 close(fd);
567                                 return 1;
568                         }
569                 } else {
570                         break;
571                 }
572
573                 if (verbose > 1)
574                         show_filename("read", tempfile);
575
576                 if (count == num_files)
577                         file_size = file.st_size;
578                 ret = read_chunks(fd, 0, file_size, chunk_buf, chunksize,
579                                   time_st, inode_st, tempfile);
580                 close(fd);
581                 if (ret)
582                         return 1;
583
584                 if (++file_num >= files_in_dir)
585                         file_num = 0;
586         }
587         if (verbose > 1){
588                 verbose++;
589                 show_filename("read", tempfile);
590                 printf("\nread complete\n");
591                 verbose--;
592         }
593         return 0;
594 }
595
596 int main(int argc, char **argv)
597 {
598         time_t time_st = 0;             /* Default timestamp */
599         size_t chunksize = ONE_MB;      /* IO chunk size(defailt=1MB) */
600         char *chunk_buf;                /* chunk buffer */
601         int error = 0;
602         FILE *countfile = NULL;
603         unsigned long dir_num = 0, dir_num_orig = 0;/* starting directory */
604         int c;
605
606         progname = strrchr(argv[0], '/') ? strrchr(argv[0], '/') + 1 : argv[0];
607         while ((c = getopt_long(argc, argv, "c:hlo:pqrs:t:vw",
608                                       longopts, NULL)) != -1) {
609                 switch (c) {
610                 case 'c':
611                         chunksize = strtoul(optarg, NULL, 0) * ONE_MB;
612                         if (chunksize == 0) {
613                                 fprintf(stderr, "%s: bad chunk size '%s'\n",
614                                         optarg, progname);
615                                 return -1;
616                         }
617                         break;
618                 case 'l':
619                         full = 1;
620                         break;
621                 case 'o': /* offset */
622                         dir_num = strtoul(optarg, NULL, 0);
623                         break;
624                 case 'p':
625                         file_size = ONE_MB;
626                         chunksize = ONE_MB;
627                         files_in_dir = 1;
628                         full = 0;
629                         break;
630                 case 'q':
631                         verbose = 0;
632                         break;
633                 case 'r':
634                         readoption = 1;
635                         break;
636                 case 's':
637                         file_size = strtoul(optarg, NULL, 0) * ONE_MB;
638                         if (file_size == 0) {
639                                 fprintf(stderr, "%s: bad file size '%s'\n",
640                                         optarg, progname);
641                                 return -1;
642                         }
643                         break;
644                 case 't':
645                         time_st = (time_t)strtoul(optarg, NULL, 0);
646                         break;
647                 case 'v':
648                         verbose++;
649                         break;
650                 case 'w':
651                         writeoption = 1;
652                         break;
653
654                 case 'h':
655                 default:
656                         usage(1);
657                         return 0;
658                 }
659         }
660         testdir = argv[optind];
661
662         if (!testdir) {
663                 fprintf(stderr, "%s: pathname not given\n", progname);
664                 usage(1);
665                 return -1;
666         }
667         if (!readoption && !writeoption) {
668                 readoption = 1;
669                 writeoption = 1;
670         }
671         if (!time_st)
672                 (void) time(&time_st);
673         printf("Timestamp: %lu\n", (unsigned long )time_st);
674         isatty_flag = isatty(STDOUT_FILENO);
675
676         if (!full) {
677 #ifdef HAVE_EXT2FS_EXT2FS_H
678                 struct mntent *tempmnt;
679                 FILE *fp = NULL;
680                 ext2_filsys fs;
681
682                 if ((fp = setmntent("/etc/mtab", "r")) == NULL){
683                         fprintf(stderr, "%s: fail to open /etc/mtab in read"
684                                 "mode :%s\n", progname, strerror(errno));
685                         goto guess;
686                 }
687
688                 /* find device name using filesystem */
689                 while ((tempmnt = getmntent(fp)) != NULL) {
690                         if (strcmp(tempmnt->mnt_dir, testdir) == 0)
691                                 break;
692                 }
693
694                 if (tempmnt == NULL) {
695                         fprintf(stderr, "%s: no device found for '%s'\n",
696                                 progname, testdir);
697                         endmntent(fp);
698                         goto guess;
699                 }
700
701                 if (ext2fs_open(tempmnt->mnt_fsname, 0, 0, 0,
702                                 unix_io_manager, &fs)) {
703                         fprintf(stderr, "%s: unable to open ext3 fs on '%s'\n",
704                                 progname, testdir);
705                         endmntent(fp);
706                         goto guess;
707                 }
708                 endmntent(fp);
709
710                 num_dirs = (fs->super->s_blocks_count +
711                             fs->super->s_blocks_per_group - 1) /
712                         fs->super->s_blocks_per_group;
713                 if (verbose)
714                         printf("ext3 block groups: %u, fs blocks: %u "
715                                "blocks per group: %u\n",
716                                num_dirs, fs->super->s_blocks_count,
717                                fs->super->s_blocks_per_group);
718                 ext2fs_close(fs);
719 #else
720                 goto guess;
721 #endif
722                 if (0) { /* ugh */
723                         struct statfs64 statbuf;
724 guess:
725                         if (statfs64(testdir, &statbuf) == 0) {
726                                 num_dirs = (long long)statbuf.f_blocks *
727                                         statbuf.f_bsize / (128ULL << 20);
728                                 if (verbose)
729                                         printf("dirs: %u, fs blocks: %llu\n",
730                                                num_dirs,
731                                                (long long)statbuf.f_blocks);
732                         } else {
733                                 fprintf(stderr, "%s: unable to stat '%s': %s\n",
734                                         progname, testdir, strerror(errno));
735                                 if (verbose)
736                                         printf("dirs: %u\n", num_dirs);
737                         }
738                 }
739         }
740         chunk_buf = (char *)calloc(chunksize, 1);
741         if (chunk_buf == NULL) {
742                 fprintf(stderr, "Memory allocation failed for chunk_buf\n");
743                 return 4;
744         }
745         sprintf(filecount, "%s/%s.filecount", testdir, progname);
746         if (writeoption) {
747                 (void)mkdir(testdir, dirmode);
748
749                 unlink(filecount);
750                 if (dir_num != 0) {
751                         num_files = dir_num * files_in_dir;
752                         if (verbose)
753                                 printf("\n%s: %lu files already written\n",
754                                        progname, num_files);
755                 }
756                 if (dir_write(chunk_buf, chunksize, time_st, dir_num)) {
757                         error = 3;
758                         goto out;
759                 }
760                 dir_num = dir_num_orig;
761         }
762         if (readoption) {
763                 if (!writeoption) {
764                         countfile = fopen(filecount, "r");
765                         if (countfile == NULL ||
766                             fscanf(countfile, "%lu", &num_files) != 1 ||
767                             num_files == 0) {
768                                 fprintf(stderr, "\n%s: reading %s failed :%s\n",
769                                         progname, filecount, strerror(errno));
770                                 num_files = num_dirs * files_in_dir;
771                         } else {
772                                 num_files -= (dir_num * files_in_dir);
773                         }
774                         if (countfile)
775                                 fclose(countfile);
776                 }
777                 if (dir_read(chunk_buf, chunksize, time_st, dir_num)) {
778                         fprintf(stderr, "\n%s: Data verification failed\n",
779                                 progname) ;
780                         error = 2;
781                         goto out;
782                 }
783         }
784         error = error_count;
785 out:
786         free(chunk_buf);
787         return error;
788 }