open_by_handle: add filename to error reports
[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] [-qS]");
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("  -S        : sync file before mapping (via ioctl flags)\n");
49         printf("-m and -r cannot be used together\n");
50         exit(EXIT_FAILURE);
51 }
52
53 static char *
54 generate_file_mapping(int blocks, int prealloc)
55 {
56         char *map;
57         int num_types = 2, cur_block = 0;
58         int i = 0;
59
60         map = malloc(sizeof(char) * blocks);
61         if (!map)
62                 return NULL;
63
64         if (prealloc)
65                 num_types++;
66
67
68         for (i = 0; i < blocks; i++) {
69                 long num = random() % num_types;
70                 switch (num) {
71                 case 0:
72                         map[cur_block] = 'D';
73                         break;
74                 case 1:
75                         map[cur_block] = 'H';
76                         break;
77                 case 2:
78                         map[cur_block] = 'P';
79                         break;
80                 }
81                 cur_block++;
82         }
83
84         return map;
85 }
86
87 static int
88 create_file_from_mapping(int fd, char *map, int blocks, int blocksize)
89 {
90         int cur_offset = 0, ret = 0, bufsize;
91         char *buf;
92         int i = 0;
93
94         bufsize = sizeof(char) * blocksize;
95         if (posix_memalign((void **)&buf, 4096, bufsize))
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, int syncfile)
422 {
423         struct fiemap *fiemap;
424         char *fiebuf;
425         int blocks_to_map, ret, cur_extent = 0, last_data = 0;
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 = syncfile ? FIEMAP_FLAG_SYNC : 0;
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 > fiemap->fm_mapped_extents) {
471                                 i++;
472                                 break;
473                         }
474
475                         switch (map[i]) {
476                         case 'D':
477                                 if (check_data(fiemap, logical_offset,
478                                                blocksize, last_data == i, 0))
479                                         goto error;
480                                 break;
481                         case 'H':
482                                 if (check_hole(fiemap, fd, logical_offset,
483                                                blocksize))
484                                         goto error;
485                                 break;
486                         case 'P':
487                                 if (check_data(fiemap, logical_offset,
488                                                blocksize, last_data == i, 1))
489                                         goto error;
490                                 break;
491                         default:
492                                 printf("ERROR: weird value in map: %c\n",
493                                        map[i]);
494                                 goto error;
495                         }
496                 }
497                 cur_extent = i;
498                 map_start = i * blocksize;
499         } while (cur_extent < blocks);
500
501         ret = 0;
502         return ret;
503 error:
504         printf("map is '%s'\n", map);
505         show_extents(fiemap, blocksize);
506         return -1;
507 }
508
509 int
510 main(int argc, char **argv)
511 {
512         int     blocksize = 0;  /* filesystem blocksize */
513         int     fd;             /* file descriptor */
514         int     opt;
515         int     rc;
516         char    *fname;         /* filename to map */
517         char    *map = NULL;    /* file map to generate */
518         int     runs = -1;      /* the number of runs to have */
519         int     blocks = 0;     /* the number of blocks to generate */
520         int     maxblocks = 0;  /* max # of blocks to create */
521         int     prealloc = 1;   /* whether or not to do preallocation */
522         int     syncfile = 0;   /* whether fiemap should  sync file first */
523         int     seed = 1;
524
525         while ((opt = getopt(argc, argv, "m:r:s:p:qS")) != -1) {
526                 switch(opt) {
527                 case 'm':
528                         map = strdup(optarg);
529                         break;
530                 case 'p':
531                         prealloc = atoi(optarg);;
532 #ifndef HAVE_FALLOCATE
533                         if (prealloc) {
534                                 printf("Not built with preallocation support\n");
535                                 usage();
536                         }
537 #endif
538                         break;
539                 case 'q':
540                         quiet = 1;
541                         break;
542                 case 'r':
543                         runs = atoi(optarg);
544                         break;
545                 case 's':
546                         seed = atoi(optarg);
547                         break;
548                 /* sync file before mapping */
549                 case 'S':
550                         syncfile = 1;
551                         break;
552                 default:
553                         usage();
554                 }
555         }
556
557         if (runs != -1 && map)
558                 usage();
559
560         fname = argv[optind++];
561         if (!fname)
562                 usage();
563
564         fd = open(fname, O_RDWR|O_CREAT|O_TRUNC|O_DIRECT, 0644);
565         if (fd < 0) {
566                 perror("Can't open file");
567                 exit(1);
568         }
569
570         if (ioctl(fd, FIGETBSZ, &blocksize) < 0) {
571                 perror("Can't get filesystem block size");
572                 close(fd);
573                 exit(1);
574         }
575
576 #ifdef HAVE_FALLOCATE
577         /* if fallocate passes, then we can do preallocation, else not */
578         if (prealloc) {
579                 prealloc = !((int)fallocate(fd, 0, 0, blocksize));
580                 if (!prealloc)
581                         printf("preallocation not supported, disabling\n");
582         }
583 #else
584         prealloc = 0;
585 #endif
586
587         if (ftruncate(fd, 0)) {
588                 perror("Can't truncate file");
589                 close(fd);
590                 exit(1);
591         }
592
593         if (map) {
594                 blocks = strlen(map);
595                 runs = 0;
596         }
597
598         srandom(seed);
599
600         /* max file size 2mb / block size */
601         maxblocks = (2 * 1024 * 1024) / blocksize;
602
603         if (runs == -1)
604                 printf("Starting infinite run, if you don't see any output "
605                        "then its working properly.\n");
606         do {
607                 if (!map) {
608                         blocks = random() % maxblocks;
609                         if (blocks == 0) {
610                                 if (!quiet)
611                                         printf("Skipping 0 length file\n");
612                                 continue;
613                         }
614
615                         map = generate_file_mapping(blocks, prealloc);
616                         if (!map) {
617                                 printf("Could not create map\n");
618                                 exit(1);
619                         }
620                 }
621
622                 rc = create_file_from_mapping(fd, map, blocks, blocksize);
623                 if (rc) {
624                         perror("Could not create file\n");
625                         free(map);
626                         close(fd);
627                         exit(1);
628                 }
629
630                 rc = compare_fiemap_and_map(fd, map, blocks, blocksize, syncfile);
631                 if (rc) {
632                         printf("Problem comparing fiemap and map\n");
633                         free(map);
634                         close(fd);
635                         exit(1);
636                 }
637
638                 free(map);
639                 map = NULL;
640
641                 if (ftruncate(fd, 0)) {
642                         perror("Could not truncate file\n");
643                         close(fd);
644                         exit(1);
645                 }
646
647                 if (lseek(fd, 0, SEEK_SET)) {
648                         perror("Could not seek set\n");
649                         close(fd);
650                         exit(1);
651                 }
652
653                 if (runs) runs--;
654         } while (runs != 0);
655
656         close(fd);
657
658         return 0;
659 }