Add SEEK_HOLE/SEEK_DATA support. Signed-off-by: Jie Liu <jeff.liu@xxxxxxxxxx> --- fs/ext4/ext4.h | 4 ++ fs/ext4/ext4_extents.h | 8 +++++ fs/ext4/extents.c | 75 ++++++++++++++++++++++++++++++++++++++++++++++++ fs/ext4/file.c | 15 +++++---- 4 files changed, 95 insertions(+), 7 deletions(-) diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h index e717dfd..9eda477 100644 --- a/fs/ext4/ext4.h +++ b/fs/ext4/ext4.h @@ -2222,6 +2222,10 @@ extern int ext4_move_extents(struct file *o_filp, struct file *d_filp, __u64 start_orig, __u64 start_donor, __u64 len, __u64 *moved_len); +/* extents.c */ +extern int ext4_find_desired_extent(struct inode *inode, loff_t *offset, + int origin); + /* page-io.c */ extern int __init ext4_init_pageio(void); extern void ext4_exit_pageio(void); diff --git a/fs/ext4/ext4_extents.h b/fs/ext4/ext4_extents.h index 095c36f..ca7a339 100644 --- a/fs/ext4/ext4_extents.h +++ b/fs/ext4/ext4_extents.h @@ -116,6 +116,14 @@ struct ext4_ext_path { }; /* + * Structure for retrieving extent offset for SEEK_DATA/SEEK_HOLE. + */ +struct ext4_extent_seek_info { + unsigned int origin; /* SEEK_DATA or SEEK_HOLE */ + loff_t offset; /* input/output offset */ +}; + +/* * structure for external API */ diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c index 06e88d4..4c78061 100644 --- a/fs/ext4/extents.c +++ b/fs/ext4/extents.c @@ -4122,6 +4122,81 @@ static int ext4_ext_fiemap_cb(struct inode *inode, ext4_lblk_t next, return EXT_CONTINUE; } +/* + * Callback function called for each extent to gather SEEK_DATA/SEEK_HOLE + * information. + */ +static int ext4_ext_seek_cb(struct inode *inode, ext4_lblk_t next, + struct ext4_ext_cache *newex, + struct ext4_extent *ex, void *data) +{ + __u64 logical; + int ret = 0; + unsigned char blksize_bits; + struct ext4_extent_seek_info *ext_seek_info = data; + + blksize_bits = inode->i_sb->s_blocksize_bits; + logical = (__u64)newex->ec_block << blksize_bits; + + if (newex->ec_start == 0) { + ret = ext4_get_delayed_extent(inode, next, newex, &logical, 0); + if (ret == EXT_CONTINUE) { + /* + * A hole found, return EXT_BREAK and fill ext_seek_info + * ->offset with logical for SEEK_HOLE, or just return. + */ + if (ext_seek_info->origin == SEEK_HOLE) { + ext_seek_info->offset = logical; + return EXT_BREAK; + } else { + return ret; + } + } + /* found a delayed extent */ + } + + if (ext_seek_info->origin == SEEK_DATA) { + ext_seek_info->offset = logical; + return EXT_BREAK; + } + + return EXT_CONTINUE; +} + +int ext4_find_desired_extent(struct inode *inode, loff_t *offset, int origin) +{ + ext4_lblk_t start_blk; + ext4_lblk_t end_blk; + ext4_lblk_t len_blks; + struct ext4_extent_seek_info ext_seek_info; + unsigned int blkbits = inode->i_sb->s_blocksize_bits; + int error = 0; + + /* + * FIXME: do we need to support old block based file? + * Generally, we would like to gather extents info over + * SEEK_DATA/SEEK_HOLE interface for efficient sparse file + * read only. + */ + if (!(ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS))) + return -ENOTSUPP; + + start_blk = *offset >> blkbits; + if (start_blk >= EXT_MAX_BLOCKS) + return -ENXIO; + + end_blk = i_size_read(inode) >> blkbits; + len_blks = ((ext4_lblk_t)end_blk) - start_blk + 1; + + ext_seek_info.origin = origin; + error = ext4_ext_walk_space(inode, start_blk, len_blks, + ext4_ext_seek_cb, &ext_seek_info); + if (!error) + *offset = ext_seek_info.offset; + + return error; +} + /* fiemap flags we can handle specified here */ #define EXT4_FIEMAP_FLAGS (FIEMAP_FLAG_SYNC|FIEMAP_FLAG_XATTR) diff --git a/fs/ext4/file.c b/fs/ext4/file.c index e4095e9..58c8c0a 100644 --- a/fs/ext4/file.c +++ b/fs/ext4/file.c @@ -219,6 +219,7 @@ loff_t ext4_llseek(struct file *file, loff_t offset, int origin) { struct inode *inode = file->f_mapping->host; loff_t maxbytes; + int ret; if (!(ext4_test_inode_flag(inode, EXT4_INODE_EXTENTS))) maxbytes = EXT4_SB(inode->i_sb)->s_bitmap_maxbytes; @@ -241,21 +242,21 @@ loff_t ext4_llseek(struct file *file, loff_t offset, int origin) * In the generic case the entire file is data, so as long as * offset isn't at the end of the file then the offset is data. */ - if (offset >= inode->i_size) { - mutex_unlock(&inode->i_mutex); - return -ENXIO; - } - break; case SEEK_HOLE: /* * There is a virtual hole at the end of the file, so as long as * offset isn't i_size or larger, return i_size. */ - if (offset >= inode->i_size) { + if (offset >= i_size_read(inode)) { mutex_unlock(&inode->i_mutex); return -ENXIO; } - offset = inode->i_size; + + ret = ext4_find_desired_extent(inode, &offset, origin); + if (ret) { + mutex_unlock(&inode->i_mutex); + return ret; + } break; } -- 1.7.4.1 -- To unsubscribe from this list: send the line "unsubscribe linux-ext4" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html