ext4: punch out extents

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

- Added end param to ext_ext4_rm_leaf
        This function has been modified to accept an end parameter
        which enables it to punch holes in leafs instead of just
        truncating them.

- 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
        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 "end" param to ext4_ext_remove_space routine
        This function has been modified to accept a stop parameter, which
        is passed through to ext4_ext_rm_leaf.

[ext4 punch hole patch series 3/5 v6] 

Signed-off-by: Allison Henderson <achender@us.ibm.com>
Signed-off-by: "Theodore Ts'o" <tytso@mit.edu>
This commit is contained in:
Allison Henderson 2011-05-25 07:41:43 -04:00 committed by Theodore Ts'o
parent 308488518d
commit d583fb87a3
1 changed files with 141 additions and 19 deletions

View File

@ -46,6 +46,13 @@
#include <trace/events/ext4.h> #include <trace/events/ext4.h>
static int ext4_split_extent(handle_t *handle,
struct inode *inode,
struct ext4_ext_path *path,
struct ext4_map_blocks *map,
int split_flag,
int flags);
static int ext4_ext_truncate_extend_restart(handle_t *handle, static int ext4_ext_truncate_extend_restart(handle_t *handle,
struct inode *inode, struct inode *inode,
int needed) int needed)
@ -2203,8 +2210,16 @@ static int ext4_remove_blocks(handle_t *handle, struct inode *inode,
ext4_free_blocks(handle, inode, NULL, start, num, flags); ext4_free_blocks(handle, inode, NULL, start, num, flags);
} else if (from == le32_to_cpu(ex->ee_block) } else if (from == le32_to_cpu(ex->ee_block)
&& to <= le32_to_cpu(ex->ee_block) + ee_len - 1) { && to <= le32_to_cpu(ex->ee_block) + ee_len - 1) {
printk(KERN_INFO "strange request: removal %u-%u from %u:%u\n", /* head removal */
from, to, le32_to_cpu(ex->ee_block), ee_len); 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 { } else {
printk(KERN_INFO "strange request: removal(2) " printk(KERN_INFO "strange request: removal(2) "
"%u-%u from %u:%u\n", "%u-%u from %u:%u\n",
@ -2213,9 +2228,22 @@ static int ext4_remove_blocks(handle_t *handle, struct inode *inode,
return 0; return 0;
} }
/*
* ext4_ext_rm_leaf() Removes the extents associated with the
* blocks appearing between "start" and "end", and splits the extents
* if "start" and "end" appear in the same extent
*
* @handle: The journal handle
* @inode: The files inode
* @path: The path to the leaf
* @start: The first block to remove
* @end: The last block to remove
*/
static int static int
ext4_ext_rm_leaf(handle_t *handle, struct inode *inode, ext4_ext_rm_leaf(handle_t *handle, struct inode *inode,
struct ext4_ext_path *path, ext4_lblk_t start) struct ext4_ext_path *path, ext4_lblk_t start,
ext4_lblk_t end)
{ {
int err = 0, correct_index = 0; int err = 0, correct_index = 0;
int depth = ext_depth(inode), credits; int depth = ext_depth(inode), credits;
@ -2226,6 +2254,7 @@ ext4_ext_rm_leaf(handle_t *handle, struct inode *inode,
unsigned short ex_ee_len; unsigned short ex_ee_len;
unsigned uninitialized = 0; unsigned uninitialized = 0;
struct ext4_extent *ex; struct ext4_extent *ex;
struct ext4_map_blocks map;
/* the header must be checked already in ext4_ext_remove_space() */ /* the header must be checked already in ext4_ext_remove_space() */
ext_debug("truncate since %u in leaf\n", start); ext_debug("truncate since %u in leaf\n", start);
@ -2255,31 +2284,95 @@ ext4_ext_rm_leaf(handle_t *handle, struct inode *inode,
path[depth].p_ext = ex; path[depth].p_ext = ex;
a = ex_ee_block > start ? ex_ee_block : start; a = ex_ee_block > start ? ex_ee_block : start;
b = ex_ee_block + ex_ee_len - 1 < EXT_MAX_BLOCK ? b = ex_ee_block+ex_ee_len - 1 < end ?
ex_ee_block + ex_ee_len - 1 : EXT_MAX_BLOCK; ex_ee_block+ex_ee_len - 1 : end;
ext_debug(" border %u:%u\n", a, b); ext_debug(" border %u:%u\n", a, b);
if (a != ex_ee_block && b != ex_ee_block + ex_ee_len - 1) { /* If this extent is beyond the end of the hole, skip it */
block = 0; if (end <= ex_ee_block) {
num = 0; ex--;
BUG(); 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 because at least one of the end points
* needs to be on the edge of the extent.
*/
if (end == EXT_MAX_BLOCK) {
ext_debug(" bad truncate %u:%u\n",
start, end);
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{
map.m_pblk = ext4_ext_pblock(ex);
map.m_lblk = ex_ee_block;
map.m_len = b - ex_ee_block;
err = ext4_split_extent(handle,
inode, path, &map, 0,
EXT4_GET_BLOCKS_PUNCH_OUT_EXT |
EXT4_GET_BLOCKS_PRE_IO);
if (err < 0)
goto out;
ex_ee_len = ext4_ext_get_actual_len(ex);
b = ex_ee_block+ex_ee_len - 1 < end ?
ex_ee_block+ex_ee_len - 1 : end;
/* Then remove tail of this extent */
block = ex_ee_block;
num = a - block;
}
} else if (a != ex_ee_block) { } else if (a != ex_ee_block) {
/* remove tail of the extent */ /* remove tail of the extent */
block = ex_ee_block; block = ex_ee_block;
num = a - block; num = a - block;
} else if (b != ex_ee_block + ex_ee_len - 1) { } else if (b != ex_ee_block + ex_ee_len - 1) {
/* remove head of the extent */ /* remove head of the extent */
block = a; block = b;
num = b - a; num = ex_ee_block + ex_ee_len - b;
/* there is no "make a hole" API yet */
BUG(); /*
* If this is a truncate, this condition
* should never happen
*/
if (end == EXT_MAX_BLOCK) {
ext_debug(" bad truncate %u:%u\n",
start, end);
err = -EIO;
goto out;
}
} else { } else {
/* remove whole extent: excellent! */ /* remove whole extent: excellent! */
block = ex_ee_block; block = ex_ee_block;
num = 0; num = 0;
BUG_ON(a != ex_ee_block); if (a != ex_ee_block) {
BUG_ON(b != ex_ee_block + ex_ee_len - 1); ext_debug(" bad truncate %u:%u\n",
start, end);
err = -EIO;
goto out;
}
if (b != ex_ee_block + ex_ee_len - 1) {
ext_debug(" bad truncate %u:%u\n",
start, end);
err = -EIO;
goto out;
}
} }
/* /*
@ -2310,7 +2403,13 @@ ext4_ext_rm_leaf(handle_t *handle, struct inode *inode,
if (num == 0) { if (num == 0) {
/* this extent is removed; mark slot entirely unused */ /* this extent is removed; mark slot entirely unused */
ext4_ext_store_pblock(ex, 0); ext4_ext_store_pblock(ex, 0);
le16_add_cpu(&eh->eh_entries, -1); } else if (block != ex_ee_block) {
/*
* If this was a head removal, then we need to update
* the physical block since it is now at a different
* location
*/
ext4_ext_store_pblock(ex, ext4_ext_pblock(ex) + (b-a));
} }
ex->ee_block = cpu_to_le32(block); ex->ee_block = cpu_to_le32(block);
@ -2326,6 +2425,27 @@ ext4_ext_rm_leaf(handle_t *handle, struct inode *inode,
if (err) if (err)
goto out; goto out;
/*
* If the extent was completely released,
* we need to remove it from the leaf
*/
if (num == 0) {
if (end != EXT_MAX_BLOCK) {
/*
* For hole punching, we need to scoot all the
* extents up when an extent is removed so that
* we dont have blank extents in the middle
*/
memmove(ex, ex+1, (EXT_LAST_EXTENT(eh) - ex) *
sizeof(struct ext4_extent));
/* Now get rid of the one at the end */
memset(EXT_LAST_EXTENT(eh), 0,
sizeof(struct ext4_extent));
}
le16_add_cpu(&eh->eh_entries, -1);
}
ext_debug("new extent: %u:%u:%llu\n", block, num, ext_debug("new extent: %u:%u:%llu\n", block, num,
ext4_ext_pblock(ex)); ext4_ext_pblock(ex));
ex--; ex--;
@ -2366,7 +2486,8 @@ ext4_ext_more_to_rm(struct ext4_ext_path *path)
return 1; 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 end)
{ {
struct super_block *sb = inode->i_sb; struct super_block *sb = inode->i_sb;
int depth = ext_depth(inode); int depth = ext_depth(inode);
@ -2405,7 +2526,8 @@ again:
while (i >= 0 && err == 0) { while (i >= 0 && err == 0) {
if (i == depth) { if (i == depth) {
/* this is leaf block */ /* this is leaf block */
err = ext4_ext_rm_leaf(handle, inode, path, start); err = ext4_ext_rm_leaf(handle, inode, path,
start, end);
/* root level has p_bh == NULL, brelse() eats this */ /* root level has p_bh == NULL, brelse() eats this */
brelse(path[i].p_bh); brelse(path[i].p_bh);
path[i].p_bh = NULL; path[i].p_bh = NULL;
@ -3451,7 +3573,7 @@ void ext4_ext_truncate(struct inode *inode)
last_block = (inode->i_size + sb->s_blocksize - 1) last_block = (inode->i_size + sb->s_blocksize - 1)
>> EXT4_BLOCK_SIZE_BITS(sb); >> EXT4_BLOCK_SIZE_BITS(sb);
err = ext4_ext_remove_space(inode, last_block); err = ext4_ext_remove_space(inode, last_block, EXT_MAX_BLOCK);
/* In a multi-transaction truncate, we only make the final /* In a multi-transaction truncate, we only make the final
* transaction synchronous. * transaction synchronous.