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