From f82a9901b0321feb4b4dea1583c51bd75707dd4e Mon Sep 17 00:00:00 2001 From: Filipe Manana Date: Sun, 1 Jun 2014 01:50:28 +0100 Subject: [PATCH] Btrfs: fix clone to deal with holes when NO_HOLES feature is enabled If the NO_HOLES feature is enabled holes don't have file extent items in the btree that represent them anymore. This made the clone operation ignore the gaps that exist between consecutive file extent items and therefore not create the holes at the destination. When not using the NO_HOLES feature, the holes were created at the destination. A test case for xfstests follows. Signed-off-by: Filipe David Borba Manana Reviewed-by: Liu Bo Signed-off-by: Chris Mason --- fs/btrfs/ioctl.c | 108 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 83 insertions(+), 25 deletions(-) diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index 55f4d717d541..44dcfd054ca6 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -3012,6 +3012,37 @@ static int check_ref(struct btrfs_trans_handle *trans, struct btrfs_root *root, return ret; } +static int clone_finish_inode_update(struct btrfs_trans_handle *trans, + struct inode *inode, + u64 endoff, + const u64 destoff, + const u64 olen) +{ + struct btrfs_root *root = BTRFS_I(inode)->root; + int ret; + + inode_inc_iversion(inode); + inode->i_mtime = inode->i_ctime = CURRENT_TIME; + /* + * We round up to the block size at eof when determining which + * extents to clone above, but shouldn't round up the file size. + */ + if (endoff > destoff + olen) + endoff = destoff + olen; + if (endoff > inode->i_size) + btrfs_i_size_write(inode, endoff); + + ret = btrfs_update_inode(trans, root, inode); + if (ret) { + btrfs_abort_transaction(trans, root, ret); + btrfs_end_transaction(trans, root); + goto out; + } + ret = btrfs_end_transaction(trans, root); +out: + return ret; +} + /** * btrfs_clone() - clone a range from inode file to another * @@ -3024,7 +3055,8 @@ static int check_ref(struct btrfs_trans_handle *trans, struct btrfs_root *root, * @destoff: Offset within @inode to start clone */ static int btrfs_clone(struct inode *src, struct inode *inode, - u64 off, u64 olen, u64 olen_aligned, u64 destoff) + const u64 off, const u64 olen, const u64 olen_aligned, + const u64 destoff) { struct btrfs_root *root = BTRFS_I(inode)->root; struct btrfs_path *path = NULL; @@ -3036,8 +3068,9 @@ static int btrfs_clone(struct inode *src, struct inode *inode, int slot; int ret; int no_quota; - u64 len = olen_aligned; + const u64 len = olen_aligned; u64 last_disko = 0; + u64 last_dest_end = destoff; ret = -ENOMEM; buf = vmalloc(btrfs_level_size(root, 0)); @@ -3105,7 +3138,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode, u64 disko = 0, diskl = 0; u64 datao = 0, datal = 0; u8 comp; - u64 endoff; + u64 drop_start; extent = btrfs_item_ptr(leaf, slot, struct btrfs_file_extent_item); @@ -3153,6 +3186,18 @@ static int btrfs_clone(struct inode *src, struct inode *inode, else new_key.offset = destoff; + /* + * Deal with a hole that doesn't have an extent item + * that represents it (NO_HOLES feature enabled). + * This hole is either in the middle of the cloning + * range or at the beginning (fully overlaps it or + * partially overlaps it). + */ + if (new_key.offset != last_dest_end) + drop_start = last_dest_end; + else + drop_start = new_key.offset; + /* * 1 - adjusting old extent (we may have to split it) * 1 - add new extent @@ -3182,7 +3227,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode, } ret = btrfs_drop_extents(trans, root, inode, - new_key.offset, + drop_start, new_key.offset + datal, 1); if (ret) { @@ -3283,7 +3328,7 @@ static int btrfs_clone(struct inode *src, struct inode *inode, aligned_end = ALIGN(new_key.offset + datal, root->sectorsize); ret = btrfs_drop_extents(trans, root, inode, - new_key.offset, + drop_start, aligned_end, 1); if (ret) { @@ -3321,27 +3366,12 @@ static int btrfs_clone(struct inode *src, struct inode *inode, btrfs_mark_buffer_dirty(leaf); btrfs_release_path(path); - inode_inc_iversion(inode); - inode->i_mtime = inode->i_ctime = CURRENT_TIME; - - /* - * we round up to the block size at eof when - * determining which extents to clone above, - * but shouldn't round up the file size - */ - endoff = new_key.offset + datal; - if (endoff > destoff+olen) - endoff = destoff+olen; - if (endoff > inode->i_size) - btrfs_i_size_write(inode, endoff); - - ret = btrfs_update_inode(trans, root, inode); - if (ret) { - btrfs_abort_transaction(trans, root, ret); - btrfs_end_transaction(trans, root); + last_dest_end = new_key.offset + datal; + ret = clone_finish_inode_update(trans, inode, + last_dest_end, + destoff, olen); + if (ret) goto out; - } - ret = btrfs_end_transaction(trans, root); if (new_key.offset + datal >= destoff + len) break; } @@ -3350,6 +3380,34 @@ static int btrfs_clone(struct inode *src, struct inode *inode, } ret = 0; + if (last_dest_end < destoff + len) { + /* + * We have an implicit hole (NO_HOLES feature is enabled) that + * fully or partially overlaps our cloning range at its end. + */ + btrfs_release_path(path); + + /* + * 1 - remove extent(s) + * 1 - inode update + */ + trans = btrfs_start_transaction(root, 2); + if (IS_ERR(trans)) { + ret = PTR_ERR(trans); + goto out; + } + ret = btrfs_drop_extents(trans, root, inode, + last_dest_end, destoff + len, 1); + if (ret) { + if (ret != -EOPNOTSUPP) + btrfs_abort_transaction(trans, root, ret); + btrfs_end_transaction(trans, root); + goto out; + } + ret = clone_finish_inode_update(trans, inode, destoff + len, + destoff, olen); + } + out: btrfs_free_path(path); vfree(buf); -- GitLab