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