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