lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Date:	Mon, 28 Feb 2011 20:08:47 -0700
From:	Allison Henderson <achender@...ux.vnet.ibm.com>
To:	linux-ext4@...r.kernel.org
Subject: [Ext4 punch hole 3/5] Ext4 Punch Hole Support: Punch out extents

This patch modifes the truncate routines to support hole punching
Below is a brief summary of the pacthes changes:

- Added new function "ext_ext4_rm_leaf_punch_hole".
        This routine is very similar to ext_ext4_rm_leaf except that
        it punches a hole in a leaf instead of just removing the tail
        or removing the entire leaf all together

- Implemented the "remove head" case in the ext_remove_blocks routine
        This routine is used by ext_ext4_rm_leaf to remove the tail
        of an extent during a truncate.  The new ext_ext4_rm_leaf_punch_hole
        routine will now also use it to remove the head of an extent in the
        case that the hole covers a region of blocks at the beginning
        of an extent.

- Added "stop" param to ext4_ext_remove_space routine
        This function has been modified to accept a stop parameter.  If stop
        is not EXT_MAX_BLOCK, the routine will call ext_ext4_rm_leaf_punch_hole
        instead of ext_ext4_rm_leaf.

- Added new "ext4_ext_release_blocks" routine
        This routine is basically the ext4_ext_truncate routine, but
        modified to accept a "stop" param in addition to "start".  The existing
        ext4_ext_truncate routine has now become a wrapper to this
        function.  The stop parameter just passed through to ext4_ext_remove_space

Signed-off-by: Allison Henderson <achender@...ibm.com>
---
:100644 100644 ab2e42e... efbc3ef... M	fs/ext4/extents.c
 fs/ext4/extents.c |  265 +++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 256 insertions(+), 9 deletions(-)

diff --git a/fs/ext4/extents.c b/fs/ext4/extents.c
index ab2e42e..efbc3ef 100644
--- a/fs/ext4/extents.c
+++ b/fs/ext4/extents.c
@@ -2159,8 +2159,16 @@ static int ext4_remove_blocks(handle_t *handle, struct inode *inode,
 		ext4_free_blocks(handle, inode, 0, start, num, flags);
 	} else if (from == le32_to_cpu(ex->ee_block)
 		   && to <= le32_to_cpu(ex->ee_block) + ee_len - 1) {
-		printk(KERN_INFO "strange request: removal %u-%u from %u:%u\n",
-			from, to, le32_to_cpu(ex->ee_block), ee_len);
+		/* head removal */
+		ext4_lblk_t num;
+		ext4_fsblk_t start;
+
+		num = to - from;
+		start = ext4_ext_pblock(ex);
+
+		ext_debug("free first %u blocks starting %llu\n", num, start);
+		ext4_free_blocks(handle, inode, 0, start, num, flags);
+	
 	} else {
 		printk(KERN_INFO "strange request: removal(2) "
 				"%u-%u from %u:%u\n",
@@ -2401,6 +2409,214 @@ out:
 }
 
 /*
+ * ext4_ext_rm_leaf_punch_hole() Removes the extents associated with the 
+ * blocks appearing between "start" and "stop", and splits the extents
+ * if "start" and "stop" appear in the same extent
+ */
+static int
+ext4_ext_rm_leaf_punch_hole(handle_t *handle, struct inode *inode,
+		struct ext4_ext_path *path, ext4_lblk_t start, ext4_lblk_t stop)
+{
+	int err = 0, correct_index = 0;
+	int depth = ext_depth(inode), credits;
+	struct ext4_extent_header *eh;
+	ext4_lblk_t a, b, block;
+	unsigned int num;
+	ext4_lblk_t ex_ee_block;
+	unsigned short ex_ee_len;
+	unsigned int uninitialized = 0;
+	struct ext4_extent *ex, *i_ex;
+
+	/* the header must be checked already in ext4_ext_remove_space() */
+	ext_debug("truncate since %u in leaf\n", start);
+	if (!path[depth].p_hdr)
+		path[depth].p_hdr = ext_block_hdr(path[depth].p_bh);
+
+	eh = path[depth].p_hdr;
+	if (unlikely(path[depth].p_hdr == NULL)) {
+		EXT4_ERROR_INODE(inode, "path[%d].p_hdr == NULL", depth);
+		return -EIO;
+	}
+
+	/* find where to start removing */
+	ex = EXT_LAST_EXTENT(eh);
+
+	ex_ee_block = le32_to_cpu(ex->ee_block);
+	ex_ee_len = ext4_ext_get_actual_len(ex);
+
+	while (ex >= EXT_FIRST_EXTENT(eh) &&
+			ex_ee_block + ex_ee_len > start ) {
+		if (ext4_ext_is_uninitialized(ex))
+			uninitialized = 1;
+		else
+			uninitialized = 0;
+
+		ext_debug("remove ext %u:[%d]%d\n", ex_ee_block,
+			 uninitialized, ex_ee_len);
+		path[depth].p_ext = ex;
+
+		a = ex_ee_block > start ? ex_ee_block : start;
+		b = ex_ee_block+ex_ee_len - 1 < stop ? ex_ee_block+ex_ee_len - 1 : stop;
+
+		ext_debug("  border %u:%u\n", a, b);
+
+		/* If this extent is beyond the end of the hole, skip it  */
+		if(stop <= ex_ee_block){
+			ex--;
+			ex_ee_block = le32_to_cpu(ex->ee_block);
+			ex_ee_len = ext4_ext_get_actual_len(ex);
+			continue;
+		}
+		else if (a != ex_ee_block && b != ex_ee_block + ex_ee_len - 1) {
+			/*
+			 * If this is a truncate, then this condition should 
+			 * never happen becase at least one of the end points
+			 * needs to be on the edge of the extent.
+			 */
+			if(stop == EXT_MAX_BLOCK){
+				ext_debug("  bad truncate %u:%u\n", start, stop);
+				block = 0;
+				num = 0;
+				err = -EIO;
+				goto out;
+			}
+			/* 
+			 * else this is a hole punch, so the extent needs to 
+			 * be split since neither edge of the hole is on the 
+			 * extent edge
+			 */
+			else{
+				err = ext4_split_extents(handle,inode, b,path,0);
+				if(err)
+					goto out;
+
+				ex_ee_len = ext4_ext_get_actual_len(ex);
+
+				b = ex_ee_block+ex_ee_len - 1 < stop ? ex_ee_block+ex_ee_len - 1 : stop;
+
+				/* Then remove tail of this extent */
+				block = ex_ee_block;
+				num = a - block;
+			}
+		}
+		else if (a != ex_ee_block) {
+			/* remove tail of the extent */
+			block = ex_ee_block;
+			num = a - block;
+		}
+		else if (b != ex_ee_block + ex_ee_len - 1) {
+			/* remove head of the extent */
+			block = b;
+			num =  ex_ee_block+ex_ee_len - b;
+
+			/* If this is a truncate, this condition should never happen */
+			if(stop == EXT_MAX_BLOCK){
+				err = -EIO;
+				goto out;
+			}
+		}
+		else {
+			/* remove whole extent: excellent! */
+			block = ex_ee_block;
+			num = 0;
+			if (a != ex_ee_block){
+				err = -EIO;
+				goto out;
+			}
+
+			if (b != ex_ee_block + ex_ee_len - 1){
+				err = -EIO;
+				goto out;
+			}
+		}
+
+		/*
+		 * 3 for leaf, sb, and inode plus 2 (bmap and group
+		 * descriptor) for each block group; assume two block
+		 * groups plus ex_ee_len/blocks_per_block_group for
+		 * the worst case
+		 */
+		credits = 7 + 2*(ex_ee_len/EXT4_BLOCKS_PER_GROUP(inode->i_sb));
+		if (ex == EXT_FIRST_EXTENT(eh)) {
+			correct_index = 1;
+			credits += (ext_depth(inode)) + 1;
+		}
+		credits += EXT4_MAXQUOTAS_TRANS_BLOCKS(inode->i_sb);
+		err = ext4_ext_truncate_extend_restart(handle, inode, credits);
+		if (err)
+			goto out;
+
+		err = ext4_ext_get_access(handle, inode, path + depth);
+		if (err)
+			goto out;
+		
+		err = ext4_remove_blocks(handle, inode, ex, a, b);
+		if (err)
+			goto out;
+
+		ex->ee_block = cpu_to_le32(block);
+		ex->ee_len = cpu_to_le16(num);
+		
+		/* 
+		 * If this was a head removal, then we need to update 
+		 * the physical block since it is now at a different  
+		 * location   
+		 */ 
+		if (block != ex_ee_block)
+			ext4_ext_store_pblock(ex, ext4_ext_pblock(ex)+(b-a) );
+
+		/*
+		 * Do not mark uninitialized if all the blocks in the
+		 * extent have been removed.
+		 */
+		if (uninitialized && num)
+			ext4_ext_mark_uninitialized(ex);
+
+		err = ext4_ext_dirty(handle, inode, path + depth);
+		if (err)
+			goto out;
+
+		ext_debug("new extent: %u:%u:%llu\n", block, num,
+				ext4_ext_pblock(ex));
+
+		if (num == 0) {
+
+			/* 
+			 * For hole punching, we need to scoot all the   
+			 * extents up so that we dont have blank extents 
+			 * in the middle 
+			 */
+			for(i_ex = ex; i_ex < EXT_LAST_EXTENT(eh); i_ex++){
+				memcpy(i_ex, i_ex+1, sizeof(struct ext4_extent));
+			}
+
+			/* now get rid of the extent at the end  */
+			memset(i_ex, 0, sizeof(struct ext4_extent));
+				
+			le16_add_cpu(&eh->eh_entries, -1);
+		}
+
+		ex--;
+		ex_ee_block = le32_to_cpu(ex->ee_block);
+		ex_ee_len = ext4_ext_get_actual_len(ex);
+
+	}
+
+	if (correct_index && eh->eh_entries)
+		err = ext4_ext_correct_indexes(handle, inode, path);
+
+	/* 
+	 * If this leaf is free, then we should
+	 * remove it from index block above 
+	 */
+	if (err == 0 && eh->eh_entries == 0 && path[depth].p_bh != NULL)
+		err = ext4_ext_rm_idx(handle, inode, path + depth);
+
+out:
+	return err;
+}
+
+/*
  * ext4_ext_more_to_rm:
  * returns 1 if current index has to be freed (even partial)
  */
@@ -2421,7 +2637,7 @@ ext4_ext_more_to_rm(struct ext4_ext_path *path)
 	return 1;
 }
 
-static int ext4_ext_remove_space(struct inode *inode, ext4_lblk_t start)
+static int ext4_ext_remove_space(struct inode *inode, ext4_lblk_t start, ext4_lblk_t stop)
 {
 	struct super_block *sb = inode->i_sb;
 	int depth = ext_depth(inode);
@@ -2460,7 +2676,10 @@ again:
 	while (i >= 0 && err == 0) {
 		if (i == depth) {
 			/* this is leaf block */
-			err = ext4_ext_rm_leaf(handle, inode, path, start);
+			if(stop == EXT_MAX_BLOCK)
+				err = ext4_ext_rm_leaf(handle, inode, path, start);
+			else
+				err = ext4_ext_rm_leaf_punch_hole(handle, inode, path, start, stop);
 			/* root level has p_bh == NULL, brelse() eats this */
 			brelse(path[i].p_bh);
 			path[i].p_bh = NULL;
@@ -3627,11 +3846,23 @@ out2:
 	return err ? err : allocated;
 }
 
-void ext4_ext_truncate(struct inode *inode)
+/*
+ * ext4_ext_release_blocks
+ *
+ * Releases the blocks in a file starting at block "start"
+ * and ending at block "stop".  Pass EXT_MAX_BLOCK
+ * for "stop" to just truncate the file to the
+ * "start" block
+ *
+ * @inode: The inode of the file to release blocks from 
+ * @start: The starting block of the hole
+ * @stop:  The ending block of the hole
+ *
+ */  
+static void ext4_ext_release_blocks(struct inode *inode, ext4_lblk_t start, ext4_lblk_t stop)
 {
 	struct address_space *mapping = inode->i_mapping;
 	struct super_block *sb = inode->i_sb;
-	ext4_lblk_t last_block;
 	handle_t *handle;
 	int err = 0;
 
@@ -3670,9 +3901,7 @@ void ext4_ext_truncate(struct inode *inode)
 	EXT4_I(inode)->i_disksize = inode->i_size;
 	ext4_mark_inode_dirty(handle, inode);
 
-	last_block = (inode->i_size + sb->s_blocksize - 1)
-			>> EXT4_BLOCK_SIZE_BITS(sb);
-	err = ext4_ext_remove_space(inode, last_block);
+	err = ext4_ext_remove_space(inode, start, stop);
 
 	/* In a multi-transaction truncate, we only make the final
 	 * transaction synchronous.
@@ -3698,6 +3927,24 @@ out_stop:
 }
 
 /*
+ * ext4_ext_truncate
+ *
+ * Truncate the file to the current i_size
+ *
+ * @inode: The file inode
+ */
+void ext4_ext_truncate(struct inode *inode)
+{
+	struct super_block *sb = inode->i_sb;
+	ext4_lblk_t last_block;
+
+	last_block = (inode->i_size + sb->s_blocksize - 1)
+			>> EXT4_BLOCK_SIZE_BITS(sb);
+
+	ext4_ext_release_blocks(inode, last_block, EXT_MAX_BLOCK);
+
+}
+/*
  * ext4_ext_convert_blocks_uninit()
  * Converts a range of blocks to uninitialized
  * 
-- 
1.7.1


--
To unsubscribe from this list: send the line "unsubscribe linux-ext4" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ