xfsqa: Add fiemap exerciser
[xfstests-dev.git] / src / fiemap-tester.c
1 /*
2  * Copyright (c) 2009 Josef Bacik
3  * All Rights Reserved.
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License V2
7  * as published by the Free Software Foundation.
8  *
9  * This program is distributed in the hope that it would be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write the Free Software Foundation,
16  * Inc.,  51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17  */
18
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include <fcntl.h>
23 #include <string.h>
24 #include <ctype.h>
25 #include <sys/ioctl.h>
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <sys/vfs.h>
29 #include <linux/fs.h>
30 #include <linux/types.h>
31 #include <linux/fiemap.h>
32
33 /* Global for non-critical message suppression */
34 int quiet;
35
36 static void
37 usage(void)
38 {
39         printf("Usage: fiemap-tester [-m map] [-r number of runs] [-s seed] [-q]");
40 #ifdef HAVE_FALLOCATE
41         printf("[-p preallocate (1/0)] ");
42 #endif
43         printf("filename\n");
44         printf("  -m map    : generate a file with the map given and test\n");
45 #ifdef HAVE_FALLOCATE
46         printf("  -p 0/1    : turn block preallocation on or off\n");
47 #endif
48         printf("  -r count  : number of runs to execute (default infinity)\n");
49         printf("  -s seed   : seed for random map generator (default 1)\n");
50         printf("  -q        : be quiet about non-errors\n");
51         printf("-m and -r cannot be used together\n");
52         exit(EXIT_FAILURE);
53 }
54
55 static char *
56 generate_file_mapping(int blocks, int prealloc)
57 {
58         char *map;
59         int num_types = 2, cur_block = 0;
60         int i = 0;
61
62         map = malloc(sizeof(char) * blocks);
63         if (!map)
64                 return NULL;
65
66         if (prealloc)
67                 num_types++;
68
69
70         for (i = 0; i < blocks; i++) {
71                 long num = random() % num_types;
72                 switch (num) {
73                 case 0:
74                         map[cur_block] = 'D';
75                         break;
76                 case 1:
77                         map[cur_block] = 'H';
78                         break;
79                 case 2:
80                         map[cur_block] = 'P';
81                         break;
82                 }
83                 cur_block++;
84         }
85
86         return map;
87 }
88
89 static int
90 create_file_from_mapping(int fd, char *map, int blocks, int blocksize)
91 {
92         int cur_offset = 0, ret = 0, bufsize;
93         char *buf;
94         int i = 0;
95
96         bufsize = sizeof(char) * blocksize;
97         buf = malloc(bufsize);
98         if (!buf)
99                 return -1;
100
101         memset(buf, 'a', bufsize);
102
103         for (i = 0; i < blocks; i++) {
104                 switch (map[i]) {
105                 case 'D':
106                         ret = write(fd, buf, bufsize);
107                         if (ret < bufsize) {
108                                 printf("Short write\n");
109                                 ret = -1;
110                                 goto out;
111                         }
112                         break;
113 #ifdef HAVE_FALLOCATE
114                 case 'P':
115                         ret = fallocate(fd, 0, cur_offset, blocksize);
116                         if (ret < 0) {
117                                 printf("Error fallocating\n");
118                                 goto out;
119                         }
120                         /* fallthrough; seek to end of prealloc space */
121 #endif
122                 case 'H':
123                         ret = lseek(fd, blocksize, SEEK_CUR);
124                         if (ret == (off_t)-1) {
125                                 printf("Error lseeking\n");
126                                 ret = -1;
127                                 goto out;
128                         }
129                         break;
130                 default:
131                         printf("Hrm, unrecognized flag in map\n");
132                         ret = -1;
133                         goto out;
134                 }
135                 cur_offset += blocksize;
136         }
137
138         ret = 0;
139 out:
140         return ret;
141 }
142
143 static void
144 show_extent_block(struct fiemap_extent *extent, int blocksize)
145 {
146         __u64   logical = extent->fe_logical;
147         __u64   phys = extent->fe_physical;
148         __u64   length = extent->fe_length;
149         int     flags = extent->fe_flags;
150
151         printf("logical: [%8llu..%8llu] phys: %8llu..%8llu "
152                "flags: 0x%03X tot: %llu\n",
153                 logical / blocksize, (logical + length - 1) / blocksize,
154                 phys / blocksize, (phys + length - 1) / blocksize,
155                 flags,
156                 (length / blocksize));
157 }
158
159 static void
160 show_extents(struct fiemap *fiemap, int blocksize)
161 {
162         unsigned int i;
163
164         for (i = 0; i < fiemap->fm_mapped_extents; i++)
165                 show_extent_block(&fiemap->fm_extents[i], blocksize);
166 }
167
168 static int
169 check_flags(struct fiemap *fiemap, int blocksize)
170 {
171         struct fiemap_extent *extent;
172         __u64 aligned_offset, aligned_length;
173         int c;
174
175         for (c = 0; c < fiemap->fm_mapped_extents; c++) {
176                 extent = &fiemap->fm_extents[c];
177
178                 aligned_offset = extent->fe_physical & ~((__u64)blocksize - 1);
179                 aligned_length = extent->fe_length & ~((__u64)blocksize - 1);
180
181                 if ((aligned_offset != extent->fe_physical ||
182                      aligned_length != extent->fe_length) &&
183                     !(extent->fe_flags & FIEMAP_EXTENT_NOT_ALIGNED)) {
184                         printf("ERROR: FIEMAP_EXTENT_NOT_ALIGNED is not set "
185                                "but the extent is unaligned: %llu\n",
186                                (unsigned long long)
187                                (extent->fe_logical / blocksize));
188                         return -1;
189                 }
190
191                 if (extent->fe_flags & FIEMAP_EXTENT_DATA_ENCRYPTED &&
192                     !(extent->fe_flags & FIEMAP_EXTENT_ENCODED)) {
193                         printf("ERROR: FIEMAP_EXTENT_DATA_ENCRYPTED is set, "
194                                "but FIEMAP_EXTENT_ENCODED is not set: %llu\n",
195                                (unsigned long long)
196                                (extent->fe_logical / blocksize));
197                         return -1;
198                 }
199
200                 if (extent->fe_flags & FIEMAP_EXTENT_NOT_ALIGNED &&
201                     aligned_offset == extent->fe_physical &&
202                     aligned_length == extent->fe_length) {
203                         printf("ERROR: FIEMAP_EXTENT_NOT_ALIGNED is set but "
204                                "offset and length is blocksize aligned: "
205                                "%llu\n",
206                                (unsigned long long)
207                                (extent->fe_logical / blocksize));
208                         return -1;
209                 }
210
211                 if (extent->fe_flags & FIEMAP_EXTENT_LAST &&
212                     c + 1 < fiemap->fm_mapped_extents) {
213                         printf("ERROR: FIEMAP_EXTENT_LAST is set but there are"
214                                " more extents left: %llu\n",
215                                (unsigned long long)
216                                (extent->fe_logical / blocksize));
217                         return -1;
218                 }
219
220                 if (extent->fe_flags & FIEMAP_EXTENT_DELALLOC &&
221                     !(extent->fe_flags & FIEMAP_EXTENT_UNKNOWN)) {
222                         printf("ERROR: FIEMAP_EXTENT_DELALLOC is set but "
223                                "FIEMAP_EXTENT_UNKNOWN is not set: %llu\n",
224                                (unsigned long long)
225                                (extent->fe_logical / blocksize));
226                         return -1;
227                 }
228
229                 if (extent->fe_flags & FIEMAP_EXTENT_DATA_INLINE &&
230                     !(extent->fe_flags & FIEMAP_EXTENT_NOT_ALIGNED)) {
231                         printf("ERROR: FIEMAP_EXTENT_DATA_INLINE is set but "
232                                "FIEMAP_EXTENT_NOT_ALIGNED is not set: %llu\n",
233                                (unsigned long long)
234                                (extent->fe_logical / blocksize));
235                         return -1;
236                 }
237
238                 if (extent->fe_flags & FIEMAP_EXTENT_DATA_TAIL &&
239                     !(extent->fe_flags & FIEMAP_EXTENT_NOT_ALIGNED)) {
240                         printf("ERROR: FIEMAP_EXTENT_DATA_TAIL is set but "
241                                "FIEMAP_EXTENT_NOT_ALIGNED is not set: %llu\n",
242                                (unsigned long long)
243                                (extent->fe_logical / blocksize));
244                         return -1;
245                 }
246         }
247
248         return 0;
249 }
250
251 static int
252 check_data(struct fiemap *fiemap, __u64 logical_offset, int blocksize,
253            int last, int prealloc)
254 {
255         struct fiemap_extent *extent;
256         __u64 orig_offset = logical_offset;
257         int c, found = 0;
258
259         for (c = 0; c < fiemap->fm_mapped_extents; c++) {
260                 __u64 start, end;
261                 extent = &fiemap->fm_extents[c];
262
263                 start = extent->fe_logical;
264                 end = extent->fe_logical + extent->fe_length;
265
266                 if (logical_offset > end)
267                         continue;
268
269                 if (logical_offset + blocksize < start)
270                         break;
271
272                 if (logical_offset >= start &&
273                     logical_offset < end) {
274                         if (prealloc &&
275                             !(extent->fe_flags & FIEMAP_EXTENT_UNWRITTEN)) {
276                                 printf("ERROR: preallocated extent is not "
277                                        "marked with FIEMAP_EXTENT_UNWRITTEN: "
278                                        "%llu\n",
279                                        (unsigned long long)
280                                        (start / blocksize));
281                                 return -1;
282                         }
283
284                         if (logical_offset + blocksize > end) {
285                                 logical_offset = end+1;
286                                 continue;
287                         } else {
288                                 found = 1;
289                                 break;
290                         }
291                 }
292         }
293
294         if (!found) {
295                 printf("ERROR: couldn't find extent at %llu\n",
296                        (unsigned long long)(orig_offset / blocksize));
297         } else if (last &&
298                    !(fiemap->fm_extents[c].fe_flags & FIEMAP_EXTENT_LAST)) {
299                 printf("ERROR: last extent not marked as last: %llu\n",
300                        (unsigned long long)(orig_offset / blocksize));
301                 found = 0;
302         }
303
304         return (!found) ? -1 : 0;
305 }
306
307 static int
308 check_weird_fs_hole(int fd, __u64 logical_offset, int blocksize)
309 {
310         static int warning_printed = 0;
311         int block, i;
312         size_t buf_len = sizeof(char) * blocksize;
313         char *buf;
314
315         block = (int)(logical_offset / blocksize);
316         if (ioctl(fd, FIBMAP, &block) < 0) {
317                 perror("Can't fibmap file");
318                 return -1;
319         }
320
321         if (!block) {
322                 printf("ERROR: FIEMAP claimed there was data at a block "
323                        "which should be a hole, and FIBMAP confirmend that "
324                        "it is in fact a hole, so FIEMAP is wrong: %llu\n",
325                        (unsigned long long)(logical_offset / blocksize));
326                 return -1;
327         }
328
329         buf = malloc(buf_len);
330         if (!buf) {
331                 perror("Could not allocate temporary buffer");
332                 return -1;
333         }
334
335         if (pread(fd, buf, buf_len, (off_t)logical_offset) < 0) {
336                 perror("Error reading from file");
337                 free(buf);
338                 return -1;
339         }
340
341         for (i = 0; i < buf_len; i++) {
342                 if (buf[i] != 0) {
343                         printf("ERROR: FIEMAP claimed there was data (%c) at "
344                                "block %llu that should have been a hole, and "
345                                "FIBMAP confirmed that it was allocated, but "
346                                "it should be filled with 0's, but it was not "
347                                "so you have a big problem!\n",
348                                buf[i],
349                                (unsigned long long)(logical_offset / blocksize));
350                         free(buf);
351                         return -1;
352                 }
353         }
354
355         if (warning_printed || quiet) {
356                 free(buf);
357                 return 0;
358         }
359
360         printf("HEY FS PERSON: your fs is weird.  I specifically wanted a\n"
361                "hole and you allocated a block anyway.  FIBMAP confirms that\n"
362                "you allocated a block, and the block is filled with 0's so\n"
363                "everything is kosher, but you still allocated a block when\n"
364                "didn't need to.  This may or may not be what you wanted,\n"
365                "which is why I'm only printing this message once, in case\n"
366                "you didn't do it on purpose. This was at block %llu.\n",
367                (unsigned long long)(logical_offset / blocksize));
368         warning_printed = 1;
369         free(buf);
370
371         return 0;
372 }
373
374 static int
375 check_hole(struct fiemap *fiemap, int fd, __u64 logical_offset, int blocksize)
376 {
377         struct fiemap_extent *extent;
378         int c;
379
380         for (c = 0; c < fiemap->fm_mapped_extents; c++) {
381                 __u64 start, end;
382                 extent = &fiemap->fm_extents[c];
383
384                 start = extent->fe_logical;
385                 end = extent->fe_logical + extent->fe_length;
386
387                 if (logical_offset > end)
388                         continue;
389                 if (logical_offset + blocksize < start)
390                         break;
391
392                 if (logical_offset >= start &&
393                     logical_offset < end) {
394
395                         if (check_weird_fs_hole(fd, logical_offset,
396                                                 blocksize) == 0)
397                                 break;
398
399                         printf("ERROR: found an allocated extent where a hole "
400                                "should be: %llu\n",
401                                (unsigned long long)(start / blocksize));
402                         return -1;
403                 }
404         }
405
406         return 0;
407 }
408
409 static int
410 compare_fiemap_and_map(int fd, char *map, int blocks, int blocksize)
411 {
412         struct fiemap *fiemap;
413         char *fiebuf;
414         int blocks_to_map, ret, cur_extent = 0, last_data;
415         __u64 map_start, map_length;
416         int i, c;
417
418         blocks_to_map = (random() % blocks) + 1;
419         fiebuf = malloc(sizeof(struct fiemap) +
420                         (blocks_to_map * sizeof(struct fiemap_extent)));
421         if (!fiebuf) {
422                 perror("Could not allocate fiemap buffers");
423                 return -1;
424         }
425
426         fiemap = (struct fiemap *)fiebuf;
427         map_start = 0;
428         map_length = blocks_to_map * blocksize;
429
430         for (i = 0; i < blocks; i++) {
431                 if (map[i] != 'H')
432                         last_data = i;
433         }
434
435         fiemap->fm_flags = FIEMAP_FLAG_SYNC;
436         fiemap->fm_extent_count = blocks_to_map;
437         fiemap->fm_mapped_extents = 0;
438
439         do {
440                 fiemap->fm_start = map_start;
441                 fiemap->fm_length = map_length;
442
443                 ret = ioctl(fd, FS_IOC_FIEMAP, (unsigned long)fiemap);
444                 if (ret < 0) {
445                         perror("FIEMAP ioctl failed");
446                         free(fiemap);
447                         return -1;
448                 }
449
450                 if (check_flags(fiemap, blocksize))
451                         goto error;
452
453                 for (i = cur_extent, c = 1; i < blocks; i++, c++) {
454                         __u64 logical_offset = i * blocksize;
455
456                         if (c > blocks_to_map)
457                                 break;
458
459                         switch (map[i]) {
460                         case 'D':
461                                 if (check_data(fiemap, logical_offset,
462                                                blocksize, last_data == i, 0))
463                                         goto error;
464                                 break;
465                         case 'H':
466                                 if (check_hole(fiemap, fd, logical_offset,
467                                                blocksize))
468                                         goto error;
469                                 break;
470                         case 'P':
471                                 if (check_data(fiemap, logical_offset,
472                                                blocksize, last_data == i, 1))
473                                         goto error;
474                                 break;
475                         default:
476                                 printf("ERROR: weird value in map: %c\n",
477                                        map[i]);
478                                 goto error;
479                         }
480                 }
481                 cur_extent = i;
482                 map_start = i * blocksize;
483         } while (cur_extent < blocks);
484
485         ret = 0;
486         return ret;
487 error:
488         printf("map is '%s'\n", map);
489         show_extents(fiemap, blocksize);
490         return -1;
491 }
492
493 int
494 main(int argc, char **argv)
495 {
496         int     blocksize = 0;  /* filesystem blocksize */
497         int     fd;             /* file descriptor */
498         int     opt;
499         int     rc;
500         char    *fname;         /* filename to map */
501         char    *map = NULL;    /* file map to generate */
502         int     runs = -1;      /* the number of runs to have */
503         int     blocks = 0;     /* the number of blocks to generate */
504         int     maxblocks = 0;  /* max # of blocks to create */
505         int     prealloc = 1;   /* whether or not to do preallocation */
506         int     seed = 1;
507
508         while ((opt = getopt(argc, argv, "m:r:s:p:q")) != -1) {
509                 switch(opt) {
510                 case 'm':
511                         map = strdup(optarg);
512                         break;
513                 case 'p':
514                         prealloc = atoi(optarg);;
515 #ifndef HAVE_FALLOCATE
516                         if (prealloc)
517                                 printf("Not built with preallocation support\n");
518                         usage();
519 #endif
520                         break;
521                 case 'q':
522                         quiet = 1;
523                         break;
524                 /* sync file before mapping */
525                 case 'r':
526                         runs = atoi(optarg);
527                         break;
528                 case 's':
529                         seed = atoi(optarg);
530                         break;
531                 default:
532                         usage();
533                 }
534         }
535
536         if (runs != -1 && map)
537                 usage();
538
539         fname = argv[optind++];
540         if (!fname)
541                 usage();
542
543         fd = open(fname, O_RDWR|O_CREAT|O_TRUNC, 0644);
544         if (fd < 0) {
545                 perror("Can't open file");
546                 exit(1);
547         }
548
549         if (ioctl(fd, FIGETBSZ, &blocksize) < 0) {
550                 perror("Can't get filesystem block size");
551                 close(fd);
552                 exit(1);
553         }
554
555 #ifdef HAVE_FALLOCATE
556         /* if fallocate passes, then we can do preallocation, else not */
557         if (prealloc) {
558                 prealloc = !((int)fallocate(fd, 0, 0, blocksize));
559                 if (!prealloc)
560                         printf("preallocation not supported, disabling\n");
561         }
562 #else
563         prealloc = 0;
564 #endif
565
566         if (ftruncate(fd, 0)) {
567                 perror("Can't truncate file");
568                 close(fd);
569                 exit(1);
570         }
571
572         if (map) {
573                 blocks = strlen(map);
574                 runs = 0;
575         }
576
577         srandom(seed);
578
579         /* max file size 2mb / block size */
580         maxblocks = (2 * 1024 * 1024) / blocksize;
581
582         if (runs == -1)
583                 printf("Starting infinite run, if you don't see any output "
584                        "then its working properly.\n");
585         do {
586                 if (!map) {
587                         blocks = random() % maxblocks;
588                         if (blocks == 0) {
589                                 if (!quiet)
590                                         printf("Skipping 0 length file\n");
591                                 continue;
592                         }
593
594                         map = generate_file_mapping(blocks, prealloc);
595                         if (!map) {
596                                 printf("Could not create map\n");
597                                 exit(1);
598                         }
599                 }
600
601                 rc = create_file_from_mapping(fd, map, blocks, blocksize);
602                 if (rc) {
603                         perror("Could not create file\n");
604                         free(map);
605                         close(fd);
606                         exit(1);
607                 }
608
609                 rc = compare_fiemap_and_map(fd, map, blocks, blocksize);
610                 if (rc) {
611                         printf("Problem comparing fiemap and map\n");
612                         free(map);
613                         close(fd);
614                         exit(1);
615                 }
616
617                 free(map);
618                 map = NULL;
619
620                 if (ftruncate(fd, 0)) {
621                         perror("Could not truncate file\n");
622                         close(fd);
623                         exit(1);
624                 }
625
626                 if (lseek(fd, 0, SEEK_SET)) {
627                         perror("Could not seek set\n");
628                         close(fd);
629                         exit(1);
630                 }
631
632                 if (runs) runs--;
633         } while (runs != 0);
634
635         close(fd);
636
637         return 0;
638 }