From: Zheng Liu Subject: [RFC][PATCH 8/8 v2] ext4: introduce lseek SEEK_DATA/SEEK_HOLE support Date: Wed, 22 Aug 2012 14:05:45 +0800 Message-ID: <1345615545-26133-9-git-send-email-wenqing.lz@taobao.com> References: <1345615545-26133-1-git-send-email-wenqing.lz@taobao.com> Cc: Hugh Dickins , Jie Liu , Zheng Liu To: linux-ext4@vger.kernel.org, linux-fsdevel@vger.kernel.org Return-path: Received: from mail-pz0-f46.google.com ([209.85.210.46]:52243 "EHLO mail-pz0-f46.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1757146Ab2HVF4E (ORCPT ); Wed, 22 Aug 2012 01:56:04 -0400 In-Reply-To: <1345615545-26133-1-git-send-email-wenqing.lz@taobao.com> Sender: linux-ext4-owner@vger.kernel.org List-ID: From: Zheng Liu 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. CC: Hugh Dickins CC: Jie Liu Signed-off-by: Zheng Liu --- [I notice that Jeff has an implementation of SEEK_DATA/SEEK_HOLE for ext4. So Cc' to Jeff] [Cc' to Hugh because tmpfs's folks are thinking about whether add this feature or not] fs/ext4/ext4.h | 1 + fs/ext4/file.c | 153 +++++++++++++++++++++++++++++++++++++++++++++++++++- fs/ext4/indirect.c | 1 - 3 files changed, 152 insertions(+), 3 deletions(-) diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h index f1d5087..149851b 100644 --- a/fs/ext4/ext4.h +++ b/fs/ext4/ext4.h @@ -2398,6 +2398,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 3b0e3bd..cfdf774 100644 --- a/fs/ext4/file.c +++ b/fs/ext4/file.c @@ -285,6 +285,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() handles both block-mapped and extent-mapped maxbytes values * by calling generic_file_llseek_size() with the appropriate maxbytes @@ -300,8 +439,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, i_size_read(inode)); + switch (origin) { + case SEEK_END: + case SEEK_CUR: + case SEEK_SET: + return generic_file_llseek_size(file, offset, origin, + maxbytes, i_size_read(inode)); + 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 9db67b1..9917c42 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); }