From: Zheng Liu <wenqing.lz@xxxxxxxxxx> This patch makes ext4 really support SEEK_DATA/SEEK_HOLE flags. Extent-based and block-based files are implemented together because ext4_map_blocks hides this difference. Signed-off-by: Zheng Liu <wenqing.lz@xxxxxxxxxx> --- fs/ext4/ext4.h | 1 + fs/ext4/file.c | 152 +++++++++++++++++++++++++++++++++++++++++++++++++++- fs/ext4/indirect.c | 1 - 3 files changed, 152 insertions(+), 2 deletions(-) diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h index f64b471..0957309 100644 --- a/fs/ext4/ext4.h +++ b/fs/ext4/ext4.h @@ -2400,6 +2400,7 @@ extern int ext4_map_blocks(handle_t *handle, struct inode *inode, struct ext4_map_blocks *map, int flags); extern int ext4_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo, __u64 start, __u64 len); + /* move_extent.c */ extern int ext4_move_extents(struct file *o_filp, struct file *d_filp, __u64 start_orig, __u64 start_donor, diff --git a/fs/ext4/file.c b/fs/ext4/file.c index 8c7642a..38f4d97 100644 --- a/fs/ext4/file.c +++ b/fs/ext4/file.c @@ -210,6 +210,145 @@ static int ext4_file_open(struct inode * inode, struct file * filp) return dquot_file_open(inode, filp); } +static loff_t ext4_seek_data(struct file *file, loff_t offset, loff_t maxsize) +{ + struct inode *inode = file->f_mapping->host; + struct ext4_map_blocks map; + struct extent_status es; + ext4_lblk_t start_block, end_block, len, delblk; + loff_t dataoff, isize; + int blkbits; + int ret = 0; + + mutex_lock(&inode->i_mutex); + + blkbits = inode->i_sb->s_blocksize_bits; + isize = i_size_read(inode); + start_block = offset >> blkbits; + end_block = isize >> blkbits; + len = isize - offset + 1; + + do { + map.m_lblk = start_block; + map.m_len = len >> blkbits; + + ret = ext4_map_blocks(NULL, inode, &map, 0); + + if (ret > 0) { + dataoff = start_block << blkbits; + break; + } else { + /* search in extent status */ + es.start = start_block; + down_read(&EXT4_I(inode)->i_data_sem); + delblk = ext4_es_find_extent(inode, &es); + up_read(&EXT4_I(inode)->i_data_sem); + + if (start_block >= es.start && + start_block < es.start + es.len) { + dataoff = start_block << blkbits; + break; + } + + start_block++; + + /* + * currently hole punching doesn't change the size of + * file. So after hole punching, maybe there has a + * hole at the end of file. + */ + if (start_block > end_block) { + dataoff = -ENXIO; + break; + } + } + } while (1); + + mutex_unlock(&inode->i_mutex); + return dataoff; +} + +static loff_t ext4_seek_hole(struct file *file, loff_t offset, loff_t maxsize) +{ + struct inode *inode = file->f_mapping->host; + struct ext4_map_blocks map; + struct extent_status es; + ext4_lblk_t start_block, end_block, len, delblk; + loff_t holeoff, isize; + int blkbits; + int ret = 0; + + mutex_lock(&inode->i_mutex); + + blkbits = inode->i_sb->s_blocksize_bits; + isize = i_size_read(inode); + start_block = offset >> blkbits; + end_block = isize >> blkbits; + len = isize - offset + 1; + + do { + map.m_lblk = start_block; + map.m_len = len >> blkbits; + + ret = ext4_map_blocks(NULL, inode, &map, 0); + + if (ret > 0) { + /* skip this extent */ + start_block += ret; + if (start_block > end_block) { + holeoff = isize; + break; + } + } else { + /* search in extent status */ + es.start = start_block; + down_read(&EXT4_I(inode)->i_data_sem); + delblk = ext4_es_find_extent(inode, &es); + up_read(&EXT4_I(inode)->i_data_sem); + + if (start_block >= es.start && + start_block < es.start + es.len) { + /* skip this delay extent */ + start_block = es.start + es.len; + continue; + } + + holeoff = start_block << blkbits; + break; + } + } while (1); + + mutex_unlock(&inode->i_mutex); + return holeoff; +} + +static loff_t ext4_seek_data_hole(struct file *file, loff_t offset, + int origin, loff_t maxsize) +{ + struct inode *inode = file->f_mapping->host; + + BUG_ON((origin != SEEK_DATA) && (origin != SEEK_HOLE)); + + if (offset >= i_size_read(inode)) + return -ENXIO; + + if (offset < 0 && !(file->f_mode & FMODE_UNSIGNED_OFFSET)) + return -EINVAL; + if (offset > maxsize) + return -EINVAL; + + switch (origin) { + case SEEK_DATA: + return ext4_seek_data(file, offset, maxsize); + case SEEK_HOLE: + return ext4_seek_hole(file, offset, maxsize); + default: + ext4_error(inode->i_sb, "Unknown origin"); + } + + return -EINVAL; +} + /* * ext4_llseek() copied from generic_file_llseek() to handle both * block-mapped and extent-mapped maxbytes values. This should @@ -225,7 +364,18 @@ loff_t ext4_llseek(struct file *file, loff_t offset, int origin) else maxbytes = inode->i_sb->s_maxbytes; - return generic_file_llseek_size(file, offset, origin, maxbytes); + switch (origin) { + case SEEK_END: + case SEEK_CUR: + case SEEK_SET: + return generic_file_llseek_size(file, offset, + origin, maxbytes); + case SEEK_DATA: + case SEEK_HOLE: + return ext4_seek_data_hole(file, offset, origin, maxbytes); + } + + return -EINVAL; } const struct file_operations ext4_file_operations = { diff --git a/fs/ext4/indirect.c b/fs/ext4/indirect.c index e190427..b22b86b 100644 --- a/fs/ext4/indirect.c +++ b/fs/ext4/indirect.c @@ -1502,4 +1502,3 @@ out_stop: ext4_journal_stop(handle); trace_ext4_truncate_exit(inode); } - -- 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