[RFC][PATCH 08/10 v1][RESEND] ext4: introduce lseek SEEK_DATA/SEEK_HOLE support

Zheng Liu
2012-07-22T07:51:36Z

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@taobao.com>
---
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);
}