Whamcloud - gitweb
54e3499d46e928698cf33959196581389d2a4285
[fs/lustre-release.git] / lustre / utils / llverdev.c
1 /*
2  * GPL HEADER START
3  *
4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License version 2 only,
8  * as published by the Free Software Foundation.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License version 2 for more details (a copy is included
14  * in the LICENSE file that accompanied this code).
15  *
16  * You should have received a copy of the GNU General Public License
17  * version 2 along with this program; If not, see
18  * http://www.gnu.org/licenses/gpl-2.0.html
19  *
20  * GPL HEADER END
21  */
22 /*
23  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
24  * Use is subject to license terms.
25  *
26  * Copyright (c) 2011, Intel Corporation.
27  *
28  */
29 /*
30  * This file is part of Lustre, http://www.lustre.org/
31  * Lustre is a trademark of Sun Microsystems, Inc.
32  *
33  * lustre/utils/llverdev.c
34  *
35  * Large Block Device Verification Tool.
36  * This program is used to test whether the block device is correctly
37  * handling IO beyond 2TB boundary.
38  * This tool have two working modes
39  * 1. full mode
40  * 2. partial mode
41  *
42  * In full mode, the program writes a test pattern on the entire disk.
43  * The test pattern (device offset and timestamp) is written at the
44  * beginning of each 4kB block. When the whole device is full the read
45  * operation is performed to verify that the test pattern is correct.
46  *
47  * In partial mode, the program writes data at the critical locations
48  * of the device such as start of the device, before and after multiple of 1GB
49  * offset and at the end.
50  *
51  * A chunk buffer with default size of 1MB is used to write and read test
52  * pattern in bulk.
53  */
54
55 #ifndef _GNU_SOURCE
56 #define _GNU_SOURCE
57 #endif
58 #ifndef LUSTRE_UTILS
59 #define LUSTRE_UTILS
60 #endif
61 #ifndef _LARGEFILE64_SOURCE
62 #define _LARGEFILE64_SOURCE
63 #endif
64 #ifndef _FILE_OFFSET_BITS
65 #define _FILE_OFFSET_BITS 64
66 #endif
67
68 #include <features.h>
69 #include <stdlib.h>
70 #include <stdio.h>
71 #include <string.h>
72 #include <ctype.h>
73 #include <fcntl.h>
74 #include <unistd.h>
75 #include <limits.h>
76 #include <errno.h>
77 #include <fcntl.h>
78 #include <getopt.h>
79 #include <time.h>
80 #include <sys/types.h>
81 #include <sys/stat.h>
82 #include <sys/ioctl.h>
83 #include <sys/mount.h>
84 #include <sys/time.h>
85 #include <gnu/stubs.h>
86
87 #define ONE_MB (1024 * 1024)
88 #define ONE_GB (1024 * 1024 * 1024)
89 #define HALF_MB (ONE_MB / 2)
90 #define ONE_KB 1024
91 #define HALF_KB (ONE_KB / 2)
92 #define BLOCKSIZE 4096
93
94 /* Structure for writting test pattern */
95 struct block_data {
96         unsigned long long bd_offset;
97         unsigned long long bd_time;
98         unsigned long long bd_inode;
99 };
100 static char *progname;          /* name by which this program was run. */
101 static unsigned verbose = 1;    /* prints offset in kB, operation rate */
102 static int readoption;          /* run test in read-only (verify) mode */
103 static int writeoption;         /* run test in write_only mode */
104 const char *devname;            /* name of device to be tested. */
105 static unsigned full = 1;       /* flag to full check */
106 static int error_count;         /* number of IO errors hit during run */
107 static int isatty_flag;
108
109 static struct option const long_opts[] = {
110         { .val = 'c',   .name = "chunksize",    .has_arg = required_argument },
111         { .val = 'f',   .name = "force",        .has_arg = no_argument },
112         { .val = 'h',   .name = "help",         .has_arg = no_argument },
113         { .val = 'l',   .name = "long",         .has_arg = no_argument },
114         { .val = 'l',   .name = "full",         .has_arg = no_argument },
115         { .val = 'o',   .name = "offset",       .has_arg = required_argument },
116         { .val = 'p',   .name = "partial",      .has_arg = required_argument },
117         { .val = 'q',   .name = "quiet",        .has_arg = required_argument },
118         { .val = 'r',   .name = "read",         .has_arg = no_argument },
119         { .val = 't',   .name = "timestamp",    .has_arg = required_argument },
120         { .val = 'v',   .name = "verbose",      .has_arg = no_argument },
121         { .val = 'w',   .name = "write",        .has_arg = no_argument },
122         { .name = NULL } };
123
124 /*
125  * Usage: displays help information, whenever user supply --help option in
126  * command or enters incorrect command line.
127  */
128 void usage(int status)
129 {
130         if (status != 0) {
131                 printf("\nUsage: %s [OPTION]... <device-name> ...\n",
132                        progname);
133                 printf("Block device verification tool.\n"
134                        "\t-t {seconds}, --timestamp, "
135                        "set test time  (default=current time())\n"
136                        "\t-o {offset}, --offset, "
137                        "offset in kB of start of test, default=0\n"
138                        "\t-r, --read, run in verify mode\n"
139                        "\t-w, --write, run in test-pattern mode, default=rw\n"
140                        "\t-v, --verbose\n"
141                        "\t-q, --quiet\n"
142                        "\t-l, --long, --full check of device\n"
143                        "\t-p, --partial, for partial check (1GB steps)\n"
144                        "\t-c {bytes}, --chunksize, IO size, default=1048576\n"
145                        "\t-f, --force, force test to run without confirmation\n"
146                        "\t-h, --help, display this help and exit\n");
147         }
148         exit(status);
149 }
150
151 /*
152  * Open_dev: Opens device in specified mode and returns fd.
153  */
154 static int open_dev(const char *devname, int mode)
155 {
156         int fd;
157
158         fd = open(devname, mode | O_EXCL | O_LARGEFILE);
159         if (fd < 0) {
160                 fprintf(stderr, "%s: Open failed: %s",progname,strerror(errno));
161                 exit(3);
162         }
163         return fd;
164 }
165
166 /*
167  * sizeof_dev: Returns size of device in bytes
168  */
169 static size_t sizeof_dev(int fd)
170 {
171         size_t numbytes;
172
173 # if defined BLKGETSIZE64       /* in sys/mount.h */
174         if (ioctl(fd, BLKGETSIZE64, &numbytes) >= 0)
175                 goto out;
176 # endif
177 # if defined BLKGETSIZE         /* in sys/mount.h */
178         {
179                 unsigned long sectors;
180
181                 if (ioctl(fd, BLKGETSIZE, &sectors) >= 0) {
182                         numbytes = (loff_t)sectors << 9;
183                         goto out;
184                 }
185         }
186 # endif
187         {
188                 struct stat statbuf;
189
190                 if (fstat(fd, &statbuf) == 0 && S_ISREG(statbuf.st_mode)) {
191                         numbytes = statbuf.st_size;
192                         goto out;
193                 }
194         }
195         fprintf(stderr, "%s: unable to determine size of %s\n",
196                 progname, devname);
197         return 0;
198
199 out:
200         if (verbose)
201                 printf("%s: %s is %llu bytes (%g GB) in size\n",
202                        progname, devname,
203                        (unsigned long long)numbytes, (double)numbytes / ONE_GB);
204
205         return numbytes;
206 }
207
208 /*
209  * Verify_chunk: Verifies test pattern in each 4kB (BLOCKSIZE) is correct.
210  * Returns 0 if test offset and timestamp is correct otherwise 1.
211  */
212 int verify_chunk(char *chunk_buf, const size_t chunksize,
213                  unsigned long long chunk_off, const unsigned long long time_st,
214                  const unsigned long long inode_st, const char *file)
215 {
216         struct block_data *bd;
217         char *chunk_end;
218
219         for (chunk_end = chunk_buf + chunksize - sizeof(*bd);
220              (char *)chunk_buf < chunk_end;
221              chunk_buf += BLOCKSIZE, chunk_off += BLOCKSIZE) {
222                 bd = (struct block_data *)chunk_buf;
223                 if ((bd->bd_offset == chunk_off) && (bd->bd_time == time_st) &&
224                     (bd->bd_inode == inode_st))
225                         continue;
226
227                 fprintf(stderr, "\n%s: verify %s failed offset/timestamp/inode "
228                         "%llu/%llu/%llu: found %llu/%llu/%llu instead\n",
229                         progname, file, chunk_off, time_st, inode_st,
230                         bd->bd_offset, bd->bd_time, bd->bd_inode);
231                 error_count++;
232                 return 1;
233         }
234         return 0;
235 }
236
237 /*
238  * fill_chunk: Fills the chunk with current or user specified timestamp
239  * and offset. The test pattern is filled at the beginning of
240  * each 4kB(BLOCKSIZE) blocks in chunk_buf.
241  */
242 void fill_chunk(char *chunk_buf, size_t chunksize, loff_t chunk_off,
243                 const time_t time_st, const ino_t inode_st)
244 {
245         struct block_data *bd;
246         char *chunk_end;
247
248         for (chunk_end = chunk_buf + chunksize - sizeof(*bd);
249              (char *)chunk_buf < chunk_end;
250              chunk_buf += BLOCKSIZE, chunk_off += BLOCKSIZE) {
251                 bd = (struct block_data *)chunk_buf;
252                 bd->bd_offset = chunk_off;
253                 bd->bd_time = time_st;
254                 bd->bd_inode = inode_st;
255         }
256 }
257
258 void show_rate(char *op, unsigned long long offset, unsigned long long *count)
259 {
260         static time_t last;
261         time_t now;
262         double diff;
263
264         now = time(NULL);
265         diff = now - last;
266
267         if (diff > 4) {
268                 if (last != 0) {
269                         if (isatty_flag)
270                                 printf("\r");
271                         printf("%s offset: %14llukB %5g MB/s            ", op,
272                                offset / ONE_KB, (double)(*count) /ONE_MB /diff);
273                         if (isatty_flag)
274                                 fflush(stdout);
275                         else
276                                 printf("\n");
277
278                         *count = 0;
279                 }
280                 last = now;
281         }
282 }
283
284 /*
285  * Write a chunk to disk, handling errors, interrupted writes, etc.
286  *
287  * If there is an IO error hit during the write, it is possible that
288  * this will just show up as a short write, and a subsequent write
289  * will return the actual error.  We want to continue in the face of
290  * minor media errors so that we can validate the whole device if
291  * possible, but if there are many errors we don't want to loop forever.
292  *
293  * The error count will be returned upon exit to ensure that the
294  * media errors are detected even if nobody is looking at the output.
295  *
296  * Returns 0 on success, or -ve errno on failure.
297  */
298 size_t write_retry(int fd, const char *chunk_buf, size_t nrequested,
299                    unsigned long long offset, const char *file)
300 {
301         long nwritten;
302
303 retry:
304         nwritten = write(fd, chunk_buf, nrequested);
305         if (nwritten < 0) {
306                 if (errno != ENOSPC) {
307                         fprintf(stderr, "\n%s: write %s@%llu+%zi failed: %s\n",
308                                 progname, file, offset, nrequested,
309                                 strerror(errno));
310                         if (error_count++ < 100)
311                                 return 0;
312                 }
313                 return -errno;
314         }
315         if (nwritten < nrequested) {
316                 fprintf(stderr, "\n%s: write %s@%llu+%zi short: %ld written\n",
317                         progname, file, offset, nrequested, nwritten);
318                 offset += nwritten;
319                 chunk_buf += nwritten;
320                 nrequested -= nwritten;
321                 goto retry;
322         }
323
324         return 0;
325 }
326
327 /*
328  * write_chunks: write the chunk_buf on the device. The number of write
329  * operations are based on the parameters write_end, offset, and chunksize.
330  *
331  * Returns 0 on success, or -ve error number on failure.
332  */
333 int write_chunks(int fd, unsigned long long offset,unsigned long long write_end,
334                  char *chunk_buf, size_t chunksize, const time_t time_st,
335                  const ino_t inode_st, const char *file)
336 {
337         unsigned long long stride, count = 0;
338
339         stride = full ? chunksize : (ONE_GB - chunksize);
340         for (offset = offset & ~(chunksize - 1); offset < write_end;
341              offset += stride) {
342                 int ret;
343
344                 if (lseek64(fd, offset, SEEK_SET) == -1) {
345                         fprintf(stderr, "\n%s: lseek64(%s+%llu) failed: %s\n",
346                                 progname, file, offset, strerror(errno));
347                         return -errno;
348                 }
349                 if (offset + chunksize > write_end)
350                         chunksize = write_end - offset;
351                 if (!full && offset > chunksize) {
352                         fill_chunk(chunk_buf, chunksize, offset, time_st,
353                                    inode_st);
354                         ret = write_retry(fd, chunk_buf, chunksize,
355                                           offset, file);
356                         if (ret < 0)
357                                 return ret;
358                         offset += chunksize;
359                         count += chunksize;
360                         if (offset + chunksize > write_end)
361                                 chunksize = write_end - offset;
362                 }
363                 fill_chunk(chunk_buf, chunksize, offset, time_st, inode_st);
364                 ret = write_retry(fd, chunk_buf, chunksize, offset, file);
365                 if (ret < 0)
366                         return ret;
367
368                 count += chunksize;
369                 if (verbose > 1)
370                         show_rate("write", offset, &count);
371         }
372
373         if (verbose > 1) {
374                 show_rate("write", offset, &count);
375                 printf("\nwrite complete\n");
376         }
377         if (fsync(fd) == -1) {
378                 fprintf(stderr, "%s: fsync failed: %s\n", progname,
379                         strerror(errno));
380                 return -errno;
381         }
382         return 0;
383 }
384
385 /*
386  * read_chunk: reads the chunk_buf from the device. The number of read
387  * operations are based on the parameters read_end, offset, and chunksize.
388  */
389 int read_chunks(int fd, unsigned long long offset, unsigned long long read_end,
390                 char *chunk_buf, size_t chunksize, const time_t time_st,
391                 const ino_t inode_st, const char *file)
392 {
393         unsigned long long stride, count = 0;
394
395         if (ioctl(fd, BLKFLSBUF, 0) < 0 && verbose)
396                 fprintf(stderr, "%s: ioctl BLKFLSBUF failed: %s (ignoring)\n",
397                         progname, strerror(errno));
398
399         stride = full ? chunksize : (ONE_GB - chunksize);
400         for (offset = offset & ~(chunksize - 1); offset < read_end;
401              offset += stride) {
402                 ssize_t nread;
403
404                 if (lseek64(fd, offset, SEEK_SET) == -1) {
405                         fprintf(stderr, "\n%s: lseek64(%llu) failed: %s\n",
406                                 progname, offset, strerror(errno));
407                         return 1;
408                 }
409                 if (offset + chunksize > read_end)
410                         chunksize = read_end - offset;
411
412                 if (!full && offset > chunksize) {
413                         nread = read(fd, chunk_buf, chunksize);
414                         if (nread < 0) {
415                                 fprintf(stderr,"\n%s: read %s@%llu+%zi failed: "
416                                         "%s\n", progname, file, offset,
417                                         chunksize, strerror(errno));
418                                 error_count++;
419                                 return 1;
420                         }
421                         if (nread < chunksize) {
422                                 fprintf(stderr, "\n%s: read %s@%llu+%zi short: "
423                                         "%zi read\n", progname, file, offset,
424                                         chunksize, nread);
425                                 error_count++;
426                         }
427                         if (verify_chunk(chunk_buf, nread, offset, time_st,
428                                          inode_st, file) != 0)
429                                 return 1;
430                         offset += chunksize;
431                         count += chunksize;
432
433                         /* Need to reset position after read error */
434                         if (nread < chunksize &&
435                             lseek64(fd, offset, SEEK_SET) == -1) {
436                                 fprintf(stderr,
437                                         "\n%s: lseek64(%s@%llu) failed: %s\n",
438                                         progname, file, offset,strerror(errno));
439                                 return 1;
440                         }
441                         if (offset + chunksize >= read_end)
442                                 chunksize = read_end - offset;
443                 }
444
445                 nread = read(fd, chunk_buf, chunksize);
446                 if (nread < 0) {
447                         fprintf(stderr, "\n%s: read failed: %s\n", progname,
448                                 strerror(errno));
449                         error_count++;
450                         return 1;
451                 }
452                 if (nread < chunksize) {
453                         fprintf(stderr, "\n%s: read %s@%llu+%zi short: "
454                                 "%zi read\n", progname, file, offset,
455                                 chunksize, nread);
456                         error_count++;
457                 }
458
459                 if (verify_chunk(chunk_buf, nread, offset, time_st,
460                                  inode_st, file) != 0)
461                         return 1;
462
463                 count += chunksize;
464                 if (verbose > 1)
465                         show_rate("read", offset, &count);
466         }
467         if (verbose > 1) {
468                 show_rate("read", offset, &count);
469                 printf("\nread complete\n");
470         }
471         return 0;
472 }
473
474 int main(int argc, char **argv)
475 {
476         time_t time_st = 0;             /* Default timestamp */
477         long long offset = 0, offset_orig; /* offset in kB */
478         size_t chunksize = ONE_MB;      /* IO chunk size */
479         char *chunk_buf = NULL;
480         unsigned int force = 0;         /* run test run without confirmation*/
481         unsigned long long dev_size = 0;
482         char yesno[4];
483         int mode = O_RDWR;              /* mode which device should be opened */
484         int fd;
485         int error = 0, c;
486
487         progname = strrchr(argv[0], '/') == NULL ?
488                 argv[0] : strrchr(argv[0], '/') + 1;
489         while ((c = getopt_long(argc, argv, "c:fhlo:pqrt:vw", long_opts,
490                                 NULL)) != -1) {
491                 switch (c) {
492                 case 'c':
493                         chunksize = (strtoul(optarg, NULL, 0) * ONE_MB);
494                         if (!chunksize) {
495                                 fprintf(stderr, "%s: chunk size value should be"
496                                         "nonzero and multiple of 1MB\n",
497                                         progname);
498                                 return -1;
499                         }
500                         break;
501                 case 'f':
502                         force = 1;
503                         break;
504                 case 'l':
505                         full = 1;
506                         break;
507                 case 'o':
508                         offset = strtoull(optarg, NULL, 0) * ONE_KB;
509                         break;
510                 case 'p':
511                         full = 0;
512                         break;
513                 case 'q':
514                         verbose = 0;
515                         break;
516                 case 'r':
517                         readoption = 1;
518                         mode = O_RDONLY;
519                         break;
520                 case 't':
521                         time_st = (time_t)strtoul(optarg, NULL, 0);
522                         break;
523                 case 'v':
524                         verbose++;
525                         break;
526                 case 'w':
527                         writeoption = 1;
528                         mode = O_WRONLY;
529                         break;
530                 case 'h':
531                 default:
532                         usage (1);
533                         return 0;
534                 }
535         }
536         offset_orig = offset;
537         devname = argv[optind];
538         if (!devname) {
539                 fprintf(stderr, "%s: device name not given\n", progname);
540                 usage (1);
541                 return -1;
542         }
543
544         if (readoption && writeoption)
545                 mode = O_RDWR;
546         if (!readoption && !writeoption) {
547                 readoption = 1;
548                 writeoption = 1;
549         }
550
551         if (!force && writeoption) {
552                 printf("%s: permanently overwrite all data on %s (yes/no)? ",
553                        progname, devname);
554                 if (scanf("%3s", yesno) == EOF && ferror(stdin)) {
555                         perror("reading from stdin");
556                         return -1;
557                 }
558                 if (!(strcasecmp("yes", yesno) || strcasecmp("y", yesno))) {
559                         printf("Not continuing due to '%s' response", yesno);
560                         return 0;
561                 }
562         }
563
564         if (!writeoption && time_st == 0) {
565                 fprintf(stderr, "%s: must give timestamp for read-only test\n",
566                         progname);
567                 usage(1);
568         }
569
570         fd = open_dev(devname, mode);
571         dev_size = sizeof_dev(fd);
572         if (!dev_size) {
573                 fprintf(stderr, "%s: cannot test on device size < 1MB\n",
574                         progname);
575                 error = 7;
576                 goto close_dev;
577         }
578
579         if (dev_size < (offset * 2)) {
580                 fprintf(stderr, "%s: device size %llu < offset %llu\n",
581                         progname, dev_size, offset);
582                 error = 6;
583                 goto close_dev;
584         }
585         if (!time_st)
586                 (void)time(&time_st);
587
588         isatty_flag = isatty(STDOUT_FILENO);
589
590         if (verbose)
591                 printf("Timestamp: %lu\n", time_st);
592
593         chunk_buf = (char *)calloc(chunksize, 1);
594         if (chunk_buf == NULL) {
595                 fprintf(stderr, "%s: memory allocation failed for chunk_buf\n",
596                         progname);
597                 error = 4;
598                 goto close_dev;
599         }
600         if (writeoption) {
601                 c = write_chunks(fd, offset, dev_size, chunk_buf, chunksize,
602                                  time_st, 0, devname);
603                 if (c < 0 && c != -ENOSPC) {
604                         error = 3;
605                         goto chunk_buf;
606                 }
607                 if (!full) { /* end of device aligned to a block */
608                         offset = ((dev_size - chunksize + BLOCKSIZE - 1) &
609                                   ~(BLOCKSIZE - 1));
610                         c = write_chunks(fd, offset, dev_size, chunk_buf,
611                                          chunksize, time_st, 0, devname);
612                         if (c < 0 && c != -ENOSPC) {
613                                 error = 3;
614                                 goto chunk_buf;
615                         }
616                 }
617                 offset = offset_orig;
618         }
619         if (readoption) {
620                 if (read_chunks(fd, offset, dev_size, chunk_buf, chunksize,
621                                 time_st, 0, devname)) {
622                         error = 2;
623                         goto chunk_buf;
624                 }
625                 if (!full) { /* end of device aligned to a block */
626                         offset = ((dev_size - chunksize + BLOCKSIZE - 1) &
627                                   ~(BLOCKSIZE - 1));
628                         if (read_chunks(fd, offset, dev_size, chunk_buf,
629                                         chunksize, time_st, 0, devname)) {
630                                 error = 2;
631                                 goto chunk_buf;
632                         }
633                 }
634                 if (verbose)
635                         printf("\n%s: data verified successfully\n", progname);
636         }
637         error = error_count;
638 chunk_buf:
639         free(chunk_buf);
640 close_dev:
641         close(fd);
642         return error;
643 }