Viewing: checkfiemap.c
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2013 Xyratex Technology Limited
*
* Author: Artem Blagodarenko <Artem_Blagodarenko@xyratex.com>
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <getopt.h>
#include <pthread.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/lustre/lustre_user.h>
#ifndef FS_IOC_FIEMAP
# define FS_IOC_FIEMAP (_IOWR('f', 11, struct fiemap))
#endif
#define ONEMB 1048576
static inline void print_extent_flags(unsigned int flags)
{
if (!flags)
return;
printf("flags (0x%x):", flags);
if (flags & FIEMAP_EXTENT_LAST)
printf(" LAST");
if (flags & FIEMAP_EXTENT_UNKNOWN)
printf(" UNKNOWN");
if (flags & FIEMAP_EXTENT_DELALLOC)
printf(" DELALLOC");
if (flags & FIEMAP_EXTENT_ENCODED)
printf(" ENCODED");
if (flags & FIEMAP_EXTENT_DATA_ENCRYPTED)
printf(" DATA_ENCRYPTED");
if (flags & FIEMAP_EXTENT_NOT_ALIGNED)
printf(" NOT_ALIGNED");
if (flags & FIEMAP_EXTENT_DATA_INLINE)
printf(" DATA_INLINE");
if (flags & FIEMAP_EXTENT_DATA_TAIL)
printf(" DATA_TAIL");
if (flags & FIEMAP_EXTENT_UNWRITTEN)
printf(" UNWRITTEN");
if (flags & FIEMAP_EXTENT_MERGED)
printf(" MERGED");
if (flags & FIEMAP_EXTENT_SHARED)
printf(" SHARED");
if (flags & FIEMAP_EXTENT_NET)
printf(" NET");
printf("\n");
}
/* This test executes fiemap ioctl and check
* a) there are no file ranges marked with FIEMAP_EXTENT_UNWRITTEN
* b) data ranges sizes sum is equal to given in second param
*/
static int check_fiemap(int fd, long long expected_sum,
unsigned int *mapped_extents)
{
/* This buffer is enougth for 1MB length file */
union { struct fiemap f; char c[4096]; } fiemap_buf;
struct fiemap *fiemap = &fiemap_buf.f;
struct fiemap_extent *fm_extents = &fiemap->fm_extents[0];
unsigned int count = (sizeof(fiemap_buf) - sizeof(*fiemap)) /
sizeof(*fm_extents);
unsigned int i = 0;
long long ext_len_sum = 0;
memset(&fiemap_buf, 0, sizeof(fiemap_buf));
fiemap->fm_start = 0;
fiemap->fm_flags = (FIEMAP_FLAG_SYNC | FIEMAP_FLAG_DEVICE_ORDER);
fiemap->fm_extent_count = count;
fiemap->fm_length = FIEMAP_MAX_OFFSET;
if (ioctl(fd, FS_IOC_FIEMAP, fiemap) < 0) {
fprintf(stderr, "error while ioctl %i\n", errno);
return -1;
}
for (i = 0; i < fiemap->fm_mapped_extents; i++) {
printf("extent %d in offset %lu, length %lu\n",
i, (unsigned long)fm_extents[i].fe_logical,
(unsigned long)fm_extents[i].fe_length);
print_extent_flags(fm_extents[i].fe_flags);
if (fm_extents[i].fe_flags & FIEMAP_EXTENT_UNWRITTEN) {
fprintf(stderr, "Unwritten extent\n");
return -2;
}
ext_len_sum += fm_extents[i].fe_length;
}
printf("No unwritten extents, extents number %u, sum of lengths %lli, expected sum %lli\n",
fiemap->fm_mapped_extents,
ext_len_sum, expected_sum);
*mapped_extents = fiemap->fm_mapped_extents;
return ext_len_sum != expected_sum ||
(expected_sum && !*mapped_extents);
}
/* LU-17110
* When userspace uses fiemap with fm_extent_count=0, it means that kernelspace
* should return only the number of extents. So we should always check
* fm_extent_count before accessing to fm_extents array. Otherwise this could
* lead to buffer overflow and slab memory corruption.
*/
struct th_args {
int fd;
int iter_nbr;
int expected_mapped;
};
static void *corruption_th(void *args)
{
int i;
struct th_args *ta = args;
struct fiemap fiemap = {
.fm_start = 0,
.fm_flags = (FIEMAP_FLAG_SYNC | FIEMAP_FLAG_DEVICE_ORDER),
.fm_extent_count = 0,
.fm_length = FIEMAP_MAX_OFFSET };
for (i = 0; i < ta->iter_nbr; i++) {
if (ioctl(ta->fd, FS_IOC_FIEMAP, &fiemap) < 0) {
fprintf(stderr, "error while ioctl: %s\n",
strerror(errno));
return (void *) (long long) -errno;
}
if (ta->expected_mapped != fiemap.fm_mapped_extents) {
fprintf(stderr,
"mapped extents mismatch: expected=%d, returned=%d\n",
ta->expected_mapped, fiemap.fm_mapped_extents);
return (void *) -EINVAL;
}
}
return NULL;
}
int main(int argc, char **argv)
{
int c;
struct option long_opts[] = {
{ .name = "test", .has_arg = no_argument, .val = 't' },
{ .name = "corruption_test", .has_arg = no_argument, .val = 'c' },
{ .name = NULL }
};
int fd;
int rc;
unsigned int mapped_extents = 0;
bool corruption_test = false;
optind = 0;
while ((c = getopt_long(argc, argv, "tc", long_opts, NULL)) != -1) {
switch (c) {
case 't':
return 0;
case 'c':
corruption_test = true;
break;
default:
fprintf(stderr, "error: %s: option '%s' unrecognized\n",
argv[0], argv[optind - 1]);
return -1;
}
}
if (optind != argc - 2) {
fprintf(stderr, "Usage: %s <filename> <filesize>\n", argv[0]);
return -1;
}
fd = open(argv[optind], O_RDONLY);
if (fd < 0) {
fprintf(stderr, "cannot open %s for reading, error %i\n",
argv[optind], errno);
return -1;
}
rc = check_fiemap(fd, atoll(argv[optind + 1]), &mapped_extents);
if (rc)
goto close;
if (corruption_test) {
pthread_t th[200];
int i;
void *rval = NULL;
struct th_args args = {
.fd = fd,
.expected_mapped = mapped_extents,
.iter_nbr = 500
};
for (i = 0; i < 200; i++) {
rc = pthread_create(&th[i], NULL, corruption_th, &args);
if (rc)
goto close;
}
for (i = 0; i < 200; i++) {
rc = pthread_join(th[i], &rval);
if (rc || rval) {
rc = 1;
goto close;
}
}
}
close:
if (close(fd) < 0)
fprintf(stderr, "closing %s, error %i\n", argv[optind], errno);
return rc;
}