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