Subject: [PATCH] ext4-fiemap-kerenl-data The patch clones: ext4_iomap_to_fiemap from iomap_to_fiemap ext4_iomap_fiemap from iomap_fiemap ext4_fiemap_fill_next_extent from fiemap_fill_next_extent and changes ext4_fiemap_fill_next_extent to conditionally use memcpy instead of copy_to_user. Signed-off-by: shaun.tancheff@hpe.com --- fs/ext4/ext4.h | 3 + fs/ext4/extents.c | 239 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 238 insertions(+), 4 deletions(-) diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h index c4d59e5..0739f10 100644 --- a/fs/ext4/ext4.h +++ b/fs/ext4/ext4.h @@ -772,6 +772,9 @@ enum { */ #define EXT4_FIEMAP_EXTENT_HOLE 0x08000000 +/* Otherwise unused fi_flags ext4 use memcpy instead of copy_[to|from]_uiser */ +#define EXT4_FIEMAP_FLAG_MEMCPY 0x80000000 + /* Max physical block we can address w/o extents */ #define EXT4_MAX_BLOCK_FILE_PHYS 0xFFFFFFFF diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c index c148bb9..b97a328 100644 --- a/fs/ext4/extents.c +++ b/fs/ext4/extents.c @@ -2175,6 +2175,238 @@ cleanup: return err; } +#ifdef KERNEL_DS +#define ext4_iomap_fiemap(i, f, s, l, ops) \ + iomap_fiemap((i), (f), (s), (l), (ops)) +#else +/* + * linux: + * ext4_fiemap_fill_next_extent <--- fiemap_fill_next_extent + * _ext4_iomap_fiemap <------------- iomap_fiemap + * ext4_iomap_to_fiemap <----------- iomap_to_fiemap + */ +/** + * ext4_fiemap_fill_next_extent - Fiemap helper function + * @fieinfo: Fiemap context passed into ->fiemap + * @logical: Extent logical start offset, in bytes + * @phys: Extent physical start offset, in bytes + * @len: Extent length, in bytes + * @flags: FIEMAP_EXTENT flags that describe this extent + * + * Called from file system ->fiemap callback. Will populate extent + * info as passed in via arguments and copy to user memory. On + * success, extent count on fieinfo is incremented. + * + * Returns 0 on success, -errno on error, 1 if this was the last + * extent that will fit in user array. + */ +#define SET_UNKNOWN_FLAGS (FIEMAP_EXTENT_DELALLOC) +#define SET_NO_UNMOUNTED_IO_FLAGS (FIEMAP_EXTENT_DATA_ENCRYPTED) +#define SET_NOT_ALIGNED_FLAGS (FIEMAP_EXTENT_DATA_TAIL|FIEMAP_EXTENT_DATA_INLINE) +static int ext4_fiemap_fill_next_extent(struct fiemap_extent_info *fieinfo, + u64 logical, u64 phys, u64 len, + u32 flags) +{ + struct fiemap_extent extent; + struct fiemap_extent __user *dest = fieinfo->fi_extents_start; + + /* only count the extents */ + if (fieinfo->fi_extents_max == 0) { + fieinfo->fi_extents_mapped++; + return (flags & FIEMAP_EXTENT_LAST) ? 1 : 0; + } + + if (fieinfo->fi_extents_mapped >= fieinfo->fi_extents_max) + return 1; + + if (flags & SET_UNKNOWN_FLAGS) + flags |= FIEMAP_EXTENT_UNKNOWN; + if (flags & SET_NO_UNMOUNTED_IO_FLAGS) + flags |= FIEMAP_EXTENT_ENCODED; + if (flags & SET_NOT_ALIGNED_FLAGS) + flags |= FIEMAP_EXTENT_NOT_ALIGNED; + + memset(&extent, 0, sizeof(extent)); + extent.fe_logical = logical; + extent.fe_physical = phys; + extent.fe_length = len; + extent.fe_flags = flags; + + dest += fieinfo->fi_extents_mapped; + if (fieinfo->fi_flags & EXT4_FIEMAP_FLAG_MEMCPY) + memcpy((__force void *)dest, &extent, sizeof(extent)); + else if (copy_to_user(dest, &extent, sizeof(extent))) + return -EFAULT; + + fieinfo->fi_extents_mapped++; + if (fieinfo->fi_extents_mapped == fieinfo->fi_extents_max) + return 1; + return (flags & FIEMAP_EXTENT_LAST) ? 1 : 0; +} + +static int ext4_iomap_to_fiemap(struct fiemap_extent_info *fi, + struct iomap *iomap, u32 flags) +{ + switch (iomap->type) { + case IOMAP_HOLE: + /* skip holes */ + return 0; + case IOMAP_DELALLOC: + flags |= FIEMAP_EXTENT_DELALLOC | FIEMAP_EXTENT_UNKNOWN; + break; + case IOMAP_MAPPED: + break; + case IOMAP_UNWRITTEN: + flags |= FIEMAP_EXTENT_UNWRITTEN; + break; + case IOMAP_INLINE: + flags |= FIEMAP_EXTENT_DATA_INLINE; + break; + } + + if (iomap->flags & IOMAP_F_MERGED) + flags |= FIEMAP_EXTENT_MERGED; + if (iomap->flags & IOMAP_F_SHARED) + flags |= FIEMAP_EXTENT_SHARED; + + return ext4_fiemap_fill_next_extent(fi, iomap->offset, + iomap->addr != IOMAP_NULL_ADDR ? iomap->addr : 0, + iomap->length, flags); +} + +/* iomap_iter */ +static inline int iomap_iter_advance(struct iomap_iter *iter) +{ + /* handle the previous iteration (if any) */ + if (iter->iomap.length) { + if (iter->processed <= 0) + return iter->processed; + if (WARN_ON_ONCE(iter->processed > iomap_length(iter))) + return -EIO; + iter->pos += iter->processed; + iter->len -= iter->processed; + if (!iter->len) + return 0; + } + + /* clear the state for the next iteration */ + iter->processed = 0; + memset(&iter->iomap, 0, sizeof(iter->iomap)); + memset(&iter->srcmap, 0, sizeof(iter->srcmap)); + return 1; +} + +static inline void iomap_iter_done(struct iomap_iter *iter) +{ + WARN_ON_ONCE(iter->iomap.offset > iter->pos); + WARN_ON_ONCE(iter->iomap.length == 0); + WARN_ON_ONCE(iter->iomap.offset + iter->iomap.length <= iter->pos); + WARN_ON_ONCE(iter->srcmap.type != IOMAP_HOLE); +} + +/** + * iomap_iter - iterate over a ranges in a file + * @iter: iteration structue + * @ops: iomap ops provided by the file system + * + * Iterate over filesystem-provided space mappings for the provided file range. + * + * This function handles cleanup of resources acquired for iteration when the + * filesystem indicates there are no more space mappings, which means that this + * function must be called in a loop that continues as long it returns a + * positive value. If 0 or a negative value is returned, the caller must not + * return to the loop body. Within a loop body, there are two ways to break out + * of the loop body: leave @iter.processed unchanged, or set it to a negative + * errno. + */ +int iomap_iter(struct iomap_iter *iter, const struct iomap_ops *ops) +{ + int ret; + + if (iter->iomap.length && ops->iomap_end) { + ret = ops->iomap_end(iter->inode, iter->pos, iomap_length(iter), + iter->processed > 0 ? iter->processed : 0, + iter->flags, &iter->iomap); + if (ret < 0 && !iter->processed) + return ret; + } + + ret = iomap_iter_advance(iter); + if (ret <= 0) + return ret; + + ret = ops->iomap_begin(iter->inode, iter->pos, iter->len, iter->flags, + &iter->iomap, &iter->srcmap); + if (ret < 0) + return ret; + iomap_iter_done(iter); + return 1; +} + +static loff_t iomap_fiemap_iter(const struct iomap_iter *iter, + struct fiemap_extent_info *fi, struct iomap *prev) +{ + int ret; + + if (iter->iomap.type == IOMAP_HOLE) + return iomap_length(iter); + + ret = ext4_iomap_to_fiemap(fi, prev, 0); + *prev = iter->iomap; + switch (ret) { + case 0: /* success */ + return iomap_length(iter); + case 1: /* extent array full */ + return 0; + default: /* error */ + return ret; + } +} + +static +int _ext4_iomap_fiemap(struct inode *inode, struct fiemap_extent_info *fi, + u64 start, u64 len, const struct iomap_ops *ops) +{ + struct iomap_iter iter = { + .inode = inode, + .pos = start, + .len = len, + .flags = IOMAP_REPORT, + }; + struct iomap prev = { + .type = IOMAP_HOLE, + }; + int ret; + bool in_kernel = fi->fi_flags & EXT4_FIEMAP_FLAG_MEMCPY; + + fi->fi_flags &= ~EXT4_FIEMAP_FLAG_MEMCPY; + ret = fiemap_prep(inode, fi, start, &iter.len, 0); + if (in_kernel) + fi->fi_flags |= EXT4_FIEMAP_FLAG_MEMCPY; + if (ret) { + return ret; + } + + while ((ret = iomap_iter(&iter, ops)) > 0) + iter.processed = iomap_fiemap_iter(&iter, fi, &prev); + + if (prev.type != IOMAP_HOLE) { + ret = ext4_iomap_to_fiemap(fi, &prev, FIEMAP_EXTENT_LAST); + if (ret < 0) { + return ret; + } + } + + /* inode with no (attribute) mapping will give ENOENT */ + if (ret < 0 && ret != -ENOENT) + return ret; + return 0; +} + +#define ext4_iomap_fiemap(i, f, s, l, ops) \ + _ext4_iomap_fiemap((i), (f), (s), (l), (ops)) +#endif /* KERNEL_DS */ + static int ext4_fill_es_cache_info(struct inode *inode, ext4_lblk_t block, ext4_lblk_t num, struct fiemap_extent_info *fieinfo) @@ -4959,11 +5191,10 @@ int ext4_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo, if (fieinfo->fi_flags & FIEMAP_FLAG_XATTR) { fieinfo->fi_flags &= ~FIEMAP_FLAG_XATTR; - return iomap_fiemap(inode, fieinfo, start, len, - &ext4_iomap_xattr_ops); + return ext4_iomap_fiemap(inode, fieinfo, start, len, + &ext4_iomap_xattr_ops); } - - return iomap_fiemap(inode, fieinfo, start, len, &ext4_iomap_report_ops); + return ext4_iomap_fiemap(inode, fieinfo, start, len, &ext4_iomap_report_ops); } int ext4_get_es_cache(struct inode *inode, struct fiemap_extent_info *fieinfo, -- 2.34.1