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

login
register
mail settings
Submitter Zheng Liu
Date July 22, 2012, 7:59 a.m.
Message ID <1342943986-12413-9-git-send-email-wenqing.lz@taobao.com>
Download mbox | patch
Permalink /patch/172498/
State Superseded
Headers show

Comments

Zheng Liu - July 22, 2012, 7:59 a.m.
From: Zheng Liu <wenqing.lz@taobao.com>

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(-)

Patch

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);
 }
-