| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (c) 2009 Josef Bacik |
| * All Rights Reserved. |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <stdint.h> |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <string.h> |
| #include <ctype.h> |
| #include <sys/ioctl.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <sys/statfs.h> |
| #include <sys/vfs.h> |
| #include <linux/fs.h> |
| #include <linux/types.h> |
| #include <linux/fiemap.h> |
| |
| /* Global for non-critical message suppression */ |
| int quiet; |
| |
| static void |
| usage(void) |
| { |
| printf("Usage: fiemap-tester [-m map] [-r number of runs] [-s seed] [-qS]"); |
| printf("[-p preallocate (1/0)] "); |
| printf("filename\n"); |
| printf(" -m map : generate a file with the map given and test\n"); |
| printf(" -p 0/1 : turn block preallocation on or off\n"); |
| printf(" -r count : number of runs to execute (default infinity)\n"); |
| printf(" -s seed : seed for random map generator (default 1)\n"); |
| printf(" -q : be quiet about non-errors\n"); |
| printf(" -S : sync file before mapping (via ioctl flags)\n"); |
| printf("-m and -r cannot be used together\n"); |
| exit(EXIT_FAILURE); |
| } |
| |
| static char * |
| generate_file_mapping(int blocks, int prealloc) |
| { |
| char *map; |
| int num_types = 2, cur_block = 0; |
| int i = 0; |
| |
| map = malloc(sizeof(char) * blocks); |
| if (!map) |
| return NULL; |
| |
| if (prealloc) |
| num_types++; |
| |
| |
| for (i = 0; i < blocks; i++) { |
| long num = random() % num_types; |
| switch (num) { |
| case 0: |
| map[cur_block] = 'D'; |
| break; |
| case 1: |
| map[cur_block] = 'H'; |
| break; |
| case 2: |
| map[cur_block] = 'P'; |
| break; |
| } |
| cur_block++; |
| } |
| |
| return map; |
| } |
| |
| static int |
| create_file_from_mapping(int fd, char *map, int blocks, int blocksize) |
| { |
| int cur_offset = 0, ret = 0, bufsize; |
| char *buf; |
| int i = 0; |
| |
| bufsize = sizeof(char) * blocksize; |
| if (posix_memalign((void **)&buf, 4096, bufsize)) |
| return -1; |
| |
| memset(buf, 'a', bufsize); |
| |
| for (i = 0; i < blocks; i++) { |
| switch (map[i]) { |
| case 'D': |
| ret = write(fd, buf, bufsize); |
| if (ret < bufsize) { |
| printf("Short write\n"); |
| ret = -1; |
| goto out; |
| } |
| break; |
| #ifdef HAVE_FALLOCATE |
| case 'P': |
| ret = fallocate(fd, 0, cur_offset, blocksize); |
| if (ret < 0) { |
| printf("Error fallocating\n"); |
| goto out; |
| } |
| /* fallthrough; seek to end of prealloc space */ |
| #endif |
| case 'H': |
| ret = lseek(fd, blocksize, SEEK_CUR); |
| if (ret == (off_t)-1) { |
| printf("Error lseeking\n"); |
| ret = -1; |
| goto out; |
| } |
| break; |
| default: |
| printf("Hrm, unrecognized flag in map\n"); |
| ret = -1; |
| goto out; |
| } |
| cur_offset += blocksize; |
| } |
| |
| ret = 0; |
| out: |
| return ret; |
| } |
| |
| static void |
| show_extent_block(struct fiemap_extent *extent, int blocksize) |
| { |
| __u64 logical = extent->fe_logical; |
| __u64 phys = extent->fe_physical; |
| __u64 length = extent->fe_length; |
| int flags = extent->fe_flags; |
| |
| printf("logical: [%8llu..%8llu] phys: %8llu..%8llu " |
| "flags: 0x%03X tot: %llu\n", |
| logical / blocksize, (logical + length - 1) / blocksize, |
| phys / blocksize, (phys + length - 1) / blocksize, |
| flags, |
| (length / blocksize)); |
| } |
| |
| static void |
| show_extents(struct fiemap *fiemap, int blocksize) |
| { |
| unsigned int i; |
| |
| for (i = 0; i < fiemap->fm_mapped_extents; i++) |
| show_extent_block(&fiemap->fm_extents[i], blocksize); |
| } |
| |
| static int |
| check_flags(struct fiemap *fiemap, int blocksize) |
| { |
| struct fiemap_extent *extent; |
| __u64 aligned_offset, aligned_length; |
| int c; |
| |
| for (c = 0; c < fiemap->fm_mapped_extents; c++) { |
| extent = &fiemap->fm_extents[c]; |
| |
| aligned_offset = extent->fe_physical & ~((__u64)blocksize - 1); |
| aligned_length = extent->fe_length & ~((__u64)blocksize - 1); |
| |
| if ((aligned_offset != extent->fe_physical || |
| aligned_length != extent->fe_length) && |
| !(extent->fe_flags & FIEMAP_EXTENT_NOT_ALIGNED)) { |
| printf("ERROR: FIEMAP_EXTENT_NOT_ALIGNED is not set " |
| "but the extent is unaligned: %llu\n", |
| (unsigned long long) |
| (extent->fe_logical / blocksize)); |
| return -1; |
| } |
| |
| if (extent->fe_flags & FIEMAP_EXTENT_DATA_ENCRYPTED && |
| !(extent->fe_flags & FIEMAP_EXTENT_ENCODED)) { |
| printf("ERROR: FIEMAP_EXTENT_DATA_ENCRYPTED is set, " |
| "but FIEMAP_EXTENT_ENCODED is not set: %llu\n", |
| (unsigned long long) |
| (extent->fe_logical / blocksize)); |
| return -1; |
| } |
| |
| if (extent->fe_flags & FIEMAP_EXTENT_NOT_ALIGNED && |
| aligned_offset == extent->fe_physical && |
| aligned_length == extent->fe_length) { |
| printf("ERROR: FIEMAP_EXTENT_NOT_ALIGNED is set but " |
| "offset and length is blocksize aligned: " |
| "%llu\n", |
| (unsigned long long) |
| (extent->fe_logical / blocksize)); |
| return -1; |
| } |
| |
| if (extent->fe_flags & FIEMAP_EXTENT_LAST && |
| c + 1 < fiemap->fm_mapped_extents) { |
| printf("ERROR: FIEMAP_EXTENT_LAST is set but there are" |
| " more extents left: %llu\n", |
| (unsigned long long) |
| (extent->fe_logical / blocksize)); |
| return -1; |
| } |
| |
| if (extent->fe_flags & FIEMAP_EXTENT_DELALLOC && |
| !(extent->fe_flags & FIEMAP_EXTENT_UNKNOWN)) { |
| printf("ERROR: FIEMAP_EXTENT_DELALLOC is set but " |
| "FIEMAP_EXTENT_UNKNOWN is not set: %llu\n", |
| (unsigned long long) |
| (extent->fe_logical / blocksize)); |
| return -1; |
| } |
| |
| if (extent->fe_flags & FIEMAP_EXTENT_DATA_INLINE && |
| !(extent->fe_flags & FIEMAP_EXTENT_NOT_ALIGNED)) { |
| printf("ERROR: FIEMAP_EXTENT_DATA_INLINE is set but " |
| "FIEMAP_EXTENT_NOT_ALIGNED is not set: %llu\n", |
| (unsigned long long) |
| (extent->fe_logical / blocksize)); |
| return -1; |
| } |
| |
| if (extent->fe_flags & FIEMAP_EXTENT_DATA_TAIL && |
| !(extent->fe_flags & FIEMAP_EXTENT_NOT_ALIGNED)) { |
| printf("ERROR: FIEMAP_EXTENT_DATA_TAIL is set but " |
| "FIEMAP_EXTENT_NOT_ALIGNED is not set: %llu\n", |
| (unsigned long long) |
| (extent->fe_logical / blocksize)); |
| return -1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int |
| check_data(struct fiemap *fiemap, __u64 logical_offset, int blocksize, |
| int last, int prealloc) |
| { |
| struct fiemap_extent *extent; |
| __u64 orig_offset = logical_offset; |
| int c, found = 0; |
| |
| for (c = 0; c < fiemap->fm_mapped_extents; c++) { |
| __u64 start, end; |
| extent = &fiemap->fm_extents[c]; |
| |
| start = extent->fe_logical; |
| end = extent->fe_logical + extent->fe_length; |
| |
| if (logical_offset > end) |
| continue; |
| |
| if (logical_offset + blocksize < start) |
| break; |
| |
| if (logical_offset >= start && |
| logical_offset < end) { |
| if (prealloc && |
| !(extent->fe_flags & FIEMAP_EXTENT_UNWRITTEN)) { |
| printf("ERROR: preallocated extent is not " |
| "marked with FIEMAP_EXTENT_UNWRITTEN: " |
| "%llu\n", |
| (unsigned long long) |
| (start / blocksize)); |
| return -1; |
| } |
| |
| if (logical_offset + blocksize > end) { |
| logical_offset = end+1; |
| continue; |
| } else { |
| found = 1; |
| break; |
| } |
| } |
| } |
| |
| if (!found) { |
| printf("ERROR: couldn't find extent at %llu\n", |
| (unsigned long long)(orig_offset / blocksize)); |
| } else if (last && |
| !(fiemap->fm_extents[c].fe_flags & FIEMAP_EXTENT_LAST)) { |
| printf("ERROR: last extent not marked as last: %llu\n", |
| (unsigned long long)(orig_offset / blocksize)); |
| found = 0; |
| } |
| |
| return (!found) ? -1 : 0; |
| } |
| |
| static int |
| check_weird_fs_hole(int fd, __u64 logical_offset, int blocksize) |
| { |
| static int warning_printed = 0; |
| int block, i; |
| size_t buf_len = sizeof(char) * blocksize; |
| char *buf; |
| |
| block = (int)(logical_offset / blocksize); |
| if (ioctl(fd, FIBMAP, &block) < 0) { |
| perror("Can't fibmap file"); |
| return -1; |
| } |
| |
| if (!block) { |
| printf("ERROR: FIEMAP claimed there was data at a block " |
| "which should be a hole, and FIBMAP confirmend that " |
| "it is in fact a hole, so FIEMAP is wrong: %llu\n", |
| (unsigned long long)(logical_offset / blocksize)); |
| return -1; |
| } |
| |
| buf = malloc(buf_len); |
| if (!buf) { |
| perror("Could not allocate temporary buffer"); |
| return -1; |
| } |
| |
| if (pread(fd, buf, buf_len, (off_t)logical_offset) < 0) { |
| perror("Error reading from file"); |
| free(buf); |
| return -1; |
| } |
| |
| for (i = 0; i < buf_len; i++) { |
| if (buf[i] != 0) { |
| printf("ERROR: FIEMAP claimed there was data (%c) at " |
| "block %llu that should have been a hole, and " |
| "FIBMAP confirmed that it was allocated, but " |
| "it should be filled with 0's, but it was not " |
| "so you have a big problem!\n", |
| buf[i], |
| (unsigned long long)(logical_offset / blocksize)); |
| free(buf); |
| return -1; |
| } |
| } |
| |
| if (warning_printed || quiet) { |
| free(buf); |
| return 0; |
| } |
| |
| printf("HEY FS PERSON: your fs is weird. I specifically wanted a\n" |
| "hole and you allocated a block anyway. FIBMAP confirms that\n" |
| "you allocated a block, and the block is filled with 0's so\n" |
| "everything is kosher, but you still allocated a block when\n" |
| "didn't need to. This may or may not be what you wanted,\n" |
| "which is why I'm only printing this message once, in case\n" |
| "you didn't do it on purpose. This was at block %llu.\n", |
| (unsigned long long)(logical_offset / blocksize)); |
| warning_printed = 1; |
| free(buf); |
| |
| return 0; |
| } |
| |
| static int |
| check_hole(struct fiemap *fiemap, int fd, __u64 logical_offset, int blocksize) |
| { |
| struct fiemap_extent *extent; |
| int c; |
| |
| for (c = 0; c < fiemap->fm_mapped_extents; c++) { |
| __u64 start, end; |
| extent = &fiemap->fm_extents[c]; |
| |
| start = extent->fe_logical; |
| end = extent->fe_logical + extent->fe_length; |
| |
| if (logical_offset > end) |
| continue; |
| if (logical_offset + blocksize < start) |
| break; |
| |
| if (logical_offset >= start && |
| logical_offset < end) { |
| |
| if (check_weird_fs_hole(fd, logical_offset, |
| blocksize) == 0) |
| break; |
| |
| printf("ERROR: found an allocated extent where a hole " |
| "should be: %llu\n", |
| (unsigned long long)(start / blocksize)); |
| return -1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int query_fiemap_count(int fd, int blocks, int blocksize) |
| { |
| struct fiemap fiemap = { 0, }; |
| |
| fiemap.fm_length = blocks * blocksize; |
| |
| if (ioctl(fd, FS_IOC_FIEMAP, (unsigned long)&fiemap) < 0) { |
| perror("FIEMAP query ioctl failed"); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static int |
| compare_fiemap_and_map(int fd, char *map, int blocks, int blocksize, int syncfile) |
| { |
| struct fiemap *fiemap; |
| char *fiebuf; |
| int blocks_to_map, ret, cur_extent = 0, last_data = 0; |
| __u64 map_start, map_length; |
| int i, c; |
| |
| if (query_fiemap_count(fd, blocks, blocksize) < 0) |
| return -1; |
| |
| blocks_to_map = (random() % blocks) + 1; |
| fiebuf = malloc(sizeof(struct fiemap) + |
| (blocks_to_map * sizeof(struct fiemap_extent))); |
| if (!fiebuf) { |
| perror("Could not allocate fiemap buffers"); |
| return -1; |
| } |
| |
| fiemap = (struct fiemap *)fiebuf; |
| map_start = 0; |
| map_length = blocks_to_map * blocksize; |
| |
| for (i = 0; i < blocks; i++) { |
| if (map[i] != 'H') |
| last_data = i; |
| } |
| |
| fiemap->fm_flags = syncfile ? FIEMAP_FLAG_SYNC : 0; |
| fiemap->fm_extent_count = blocks_to_map; |
| fiemap->fm_mapped_extents = 0; |
| |
| do { |
| fiemap->fm_start = map_start; |
| fiemap->fm_length = map_length; |
| |
| ret = ioctl(fd, FS_IOC_FIEMAP, (unsigned long)fiemap); |
| if (ret < 0) { |
| perror("FIEMAP ioctl failed"); |
| free(fiemap); |
| return -1; |
| } |
| |
| if (check_flags(fiemap, blocksize)) |
| goto error; |
| |
| for (i = cur_extent, c = 1; i < blocks; i++, c++) { |
| __u64 logical_offset = i * blocksize; |
| |
| if (c > fiemap->fm_mapped_extents) { |
| i++; |
| break; |
| } |
| |
| switch (map[i]) { |
| case 'D': |
| if (check_data(fiemap, logical_offset, |
| blocksize, last_data == i, 0)) |
| goto error; |
| break; |
| case 'H': |
| if (check_hole(fiemap, fd, logical_offset, |
| blocksize)) |
| goto error; |
| break; |
| case 'P': |
| if (check_data(fiemap, logical_offset, |
| blocksize, last_data == i, 1)) |
| goto error; |
| break; |
| default: |
| printf("ERROR: weird value in map: %c\n", |
| map[i]); |
| goto error; |
| } |
| } |
| cur_extent = i; |
| map_start = i * blocksize; |
| } while (cur_extent < blocks); |
| |
| ret = 0; |
| return ret; |
| error: |
| printf("map is '%s'\n", map); |
| show_extents(fiemap, blocksize); |
| return -1; |
| } |
| |
| int |
| main(int argc, char **argv) |
| { |
| int blocksize = 0; /* filesystem blocksize */ |
| int fd; /* file descriptor */ |
| int opt; |
| int rc; |
| char *fname; /* filename to map */ |
| char *map = NULL; /* file map to generate */ |
| int runs = -1; /* the number of runs to have */ |
| int blocks = 0; /* the number of blocks to generate */ |
| int maxblocks = 0; /* max # of blocks to create */ |
| int prealloc = 1; /* whether or not to do preallocation */ |
| int syncfile = 0; /* whether fiemap should sync file first */ |
| int seed = 1; |
| |
| while ((opt = getopt(argc, argv, "m:r:s:p:qS")) != -1) { |
| switch(opt) { |
| case 'm': |
| map = strdup(optarg); |
| break; |
| case 'p': |
| prealloc = atoi(optarg);; |
| #ifndef HAVE_FALLOCATE |
| if (prealloc) { |
| printf("Not built with preallocation support\n"); |
| usage(); |
| } |
| #endif |
| break; |
| case 'q': |
| quiet = 1; |
| break; |
| case 'r': |
| runs = atoi(optarg); |
| break; |
| case 's': |
| seed = atoi(optarg); |
| break; |
| /* sync file before mapping */ |
| case 'S': |
| syncfile = 1; |
| break; |
| default: |
| usage(); |
| } |
| } |
| |
| if (runs != -1 && map) |
| usage(); |
| |
| fname = argv[optind++]; |
| if (!fname) |
| usage(); |
| |
| fd = open(fname, O_RDWR|O_CREAT|O_TRUNC|O_DIRECT, 0644); |
| if (fd < 0) { |
| perror("Can't open file"); |
| exit(1); |
| } |
| |
| if (ioctl(fd, FIGETBSZ, &blocksize) < 0) { |
| struct statfs buf; |
| |
| if (fstatfs(fd, &buf) == 0) { |
| blocksize = buf.f_bsize; |
| } else { |
| perror("Can't get filesystem block size"); |
| close(fd); |
| exit(1); |
| } |
| } |
| |
| if (blocksize <= 0) { |
| printf("Illegal filesystem block size\n"); |
| close(fd); |
| exit(1); |
| } |
| |
| #ifdef HAVE_FALLOCATE |
| /* if fallocate passes, then we can do preallocation, else not */ |
| if (prealloc) { |
| prealloc = !((int)fallocate(fd, 0, 0, blocksize)); |
| if (!prealloc) |
| printf("preallocation not supported, disabling\n"); |
| } |
| #else |
| prealloc = 0; |
| #endif |
| |
| if (ftruncate(fd, 0)) { |
| perror("Can't truncate file"); |
| close(fd); |
| exit(1); |
| } |
| |
| if (map) { |
| blocks = strlen(map); |
| runs = 0; |
| } |
| |
| srandom(seed); |
| |
| /* max file size 2mb / block size */ |
| maxblocks = (2 * 1024 * 1024) / blocksize; |
| |
| if (runs == -1) |
| printf("Starting infinite run, if you don't see any output " |
| "then its working properly.\n"); |
| do { |
| if (!map) { |
| blocks = random() % maxblocks; |
| if (blocks == 0) { |
| if (!quiet) |
| printf("Skipping 0 length file\n"); |
| continue; |
| } |
| |
| map = generate_file_mapping(blocks, prealloc); |
| if (!map) { |
| printf("Could not create map\n"); |
| exit(1); |
| } |
| } |
| |
| rc = create_file_from_mapping(fd, map, blocks, blocksize); |
| if (rc) { |
| perror("Could not create file\n"); |
| free(map); |
| close(fd); |
| exit(1); |
| } |
| |
| rc = compare_fiemap_and_map(fd, map, blocks, blocksize, syncfile); |
| if (rc) { |
| printf("Problem comparing fiemap and map\n"); |
| free(map); |
| close(fd); |
| exit(1); |
| } |
| |
| free(map); |
| map = NULL; |
| |
| if (ftruncate(fd, 0)) { |
| perror("Could not truncate file\n"); |
| close(fd); |
| exit(1); |
| } |
| |
| if (lseek(fd, 0, SEEK_SET)) { |
| perror("Could not seek set\n"); |
| close(fd); |
| exit(1); |
| } |
| |
| if (runs) runs--; |
| } while (runs != 0); |
| |
| close(fd); |
| |
| return 0; |
| } |