diff mbox

[RFC,8/8,v2] ext4: introduce lseek SEEK_DATA/SEEK_HOLE support

Message ID 1345615545-26133-9-git-send-email-wenqing.lz@taobao.com
State Accepted, archived
Headers show

Commit Message

Zheng Liu Aug. 22, 2012, 6:05 a.m. UTC
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.

CC: Hugh Dickins <hughd@google.com>
CC: Jie Liu <jeff.liu@oracle.com>
Signed-off-by: Zheng Liu <wenqing.lz@taobao.com>
---
[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 mbox

Patch

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