Commit ee103930 authored by Linus Torvalds's avatar Linus Torvalds

Merge git://git.kernel.org/pub/scm/linux/kernel/git/mason/btrfs-unstable

* git://git.kernel.org/pub/scm/linux/kernel/git/mason/btrfs-unstable:
  Btrfs: fix checks in BTRFS_IOC_CLONE_RANGE
  Btrfs: fix CLONE ioctl destination file size expansion to block boundary
  Btrfs: fix split_leaf double split corner case
parents d0c6f625 2ebc3464
...@@ -2304,12 +2304,17 @@ noinline int btrfs_leaf_free_space(struct btrfs_root *root, ...@@ -2304,12 +2304,17 @@ noinline int btrfs_leaf_free_space(struct btrfs_root *root,
return ret; return ret;
} }
/*
* min slot controls the lowest index we're willing to push to the
* right. We'll push up to and including min_slot, but no lower
*/
static noinline int __push_leaf_right(struct btrfs_trans_handle *trans, static noinline int __push_leaf_right(struct btrfs_trans_handle *trans,
struct btrfs_root *root, struct btrfs_root *root,
struct btrfs_path *path, struct btrfs_path *path,
int data_size, int empty, int data_size, int empty,
struct extent_buffer *right, struct extent_buffer *right,
int free_space, u32 left_nritems) int free_space, u32 left_nritems,
u32 min_slot)
{ {
struct extent_buffer *left = path->nodes[0]; struct extent_buffer *left = path->nodes[0];
struct extent_buffer *upper = path->nodes[1]; struct extent_buffer *upper = path->nodes[1];
...@@ -2327,7 +2332,7 @@ static noinline int __push_leaf_right(struct btrfs_trans_handle *trans, ...@@ -2327,7 +2332,7 @@ static noinline int __push_leaf_right(struct btrfs_trans_handle *trans,
if (empty) if (empty)
nr = 0; nr = 0;
else else
nr = 1; nr = max_t(u32, 1, min_slot);
if (path->slots[0] >= left_nritems) if (path->slots[0] >= left_nritems)
push_space += data_size; push_space += data_size;
...@@ -2469,10 +2474,14 @@ static noinline int __push_leaf_right(struct btrfs_trans_handle *trans, ...@@ -2469,10 +2474,14 @@ static noinline int __push_leaf_right(struct btrfs_trans_handle *trans,
* *
* returns 1 if the push failed because the other node didn't have enough * returns 1 if the push failed because the other node didn't have enough
* room, 0 if everything worked out and < 0 if there were major errors. * room, 0 if everything worked out and < 0 if there were major errors.
*
* this will push starting from min_slot to the end of the leaf. It won't
* push any slot lower than min_slot
*/ */
static int push_leaf_right(struct btrfs_trans_handle *trans, struct btrfs_root static int push_leaf_right(struct btrfs_trans_handle *trans, struct btrfs_root
*root, struct btrfs_path *path, int data_size, *root, struct btrfs_path *path,
int empty) int min_data_size, int data_size,
int empty, u32 min_slot)
{ {
struct extent_buffer *left = path->nodes[0]; struct extent_buffer *left = path->nodes[0];
struct extent_buffer *right; struct extent_buffer *right;
...@@ -2514,8 +2523,8 @@ static int push_leaf_right(struct btrfs_trans_handle *trans, struct btrfs_root ...@@ -2514,8 +2523,8 @@ static int push_leaf_right(struct btrfs_trans_handle *trans, struct btrfs_root
if (left_nritems == 0) if (left_nritems == 0)
goto out_unlock; goto out_unlock;
return __push_leaf_right(trans, root, path, data_size, empty, return __push_leaf_right(trans, root, path, min_data_size, empty,
right, free_space, left_nritems); right, free_space, left_nritems, min_slot);
out_unlock: out_unlock:
btrfs_tree_unlock(right); btrfs_tree_unlock(right);
free_extent_buffer(right); free_extent_buffer(right);
...@@ -2525,12 +2534,17 @@ static int push_leaf_right(struct btrfs_trans_handle *trans, struct btrfs_root ...@@ -2525,12 +2534,17 @@ static int push_leaf_right(struct btrfs_trans_handle *trans, struct btrfs_root
/* /*
* push some data in the path leaf to the left, trying to free up at * push some data in the path leaf to the left, trying to free up at
* least data_size bytes. returns zero if the push worked, nonzero otherwise * least data_size bytes. returns zero if the push worked, nonzero otherwise
*
* max_slot can put a limit on how far into the leaf we'll push items. The
* item at 'max_slot' won't be touched. Use (u32)-1 to make us do all the
* items
*/ */
static noinline int __push_leaf_left(struct btrfs_trans_handle *trans, static noinline int __push_leaf_left(struct btrfs_trans_handle *trans,
struct btrfs_root *root, struct btrfs_root *root,
struct btrfs_path *path, int data_size, struct btrfs_path *path, int data_size,
int empty, struct extent_buffer *left, int empty, struct extent_buffer *left,
int free_space, int right_nritems) int free_space, u32 right_nritems,
u32 max_slot)
{ {
struct btrfs_disk_key disk_key; struct btrfs_disk_key disk_key;
struct extent_buffer *right = path->nodes[0]; struct extent_buffer *right = path->nodes[0];
...@@ -2549,9 +2563,9 @@ static noinline int __push_leaf_left(struct btrfs_trans_handle *trans, ...@@ -2549,9 +2563,9 @@ static noinline int __push_leaf_left(struct btrfs_trans_handle *trans,
slot = path->slots[1]; slot = path->slots[1];
if (empty) if (empty)
nr = right_nritems; nr = min(right_nritems, max_slot);
else else
nr = right_nritems - 1; nr = min(right_nritems - 1, max_slot);
for (i = 0; i < nr; i++) { for (i = 0; i < nr; i++) {
item = btrfs_item_nr(right, i); item = btrfs_item_nr(right, i);
...@@ -2712,10 +2726,14 @@ static noinline int __push_leaf_left(struct btrfs_trans_handle *trans, ...@@ -2712,10 +2726,14 @@ static noinline int __push_leaf_left(struct btrfs_trans_handle *trans,
/* /*
* push some data in the path leaf to the left, trying to free up at * push some data in the path leaf to the left, trying to free up at
* least data_size bytes. returns zero if the push worked, nonzero otherwise * least data_size bytes. returns zero if the push worked, nonzero otherwise
*
* max_slot can put a limit on how far into the leaf we'll push items. The
* item at 'max_slot' won't be touched. Use (u32)-1 to make us push all the
* items
*/ */
static int push_leaf_left(struct btrfs_trans_handle *trans, struct btrfs_root static int push_leaf_left(struct btrfs_trans_handle *trans, struct btrfs_root
*root, struct btrfs_path *path, int data_size, *root, struct btrfs_path *path, int min_data_size,
int empty) int data_size, int empty, u32 max_slot)
{ {
struct extent_buffer *right = path->nodes[0]; struct extent_buffer *right = path->nodes[0];
struct extent_buffer *left; struct extent_buffer *left;
...@@ -2761,8 +2779,9 @@ static int push_leaf_left(struct btrfs_trans_handle *trans, struct btrfs_root ...@@ -2761,8 +2779,9 @@ static int push_leaf_left(struct btrfs_trans_handle *trans, struct btrfs_root
goto out; goto out;
} }
return __push_leaf_left(trans, root, path, data_size, return __push_leaf_left(trans, root, path, min_data_size,
empty, left, free_space, right_nritems); empty, left, free_space, right_nritems,
max_slot);
out: out:
btrfs_tree_unlock(left); btrfs_tree_unlock(left);
free_extent_buffer(left); free_extent_buffer(left);
...@@ -2854,6 +2873,64 @@ static noinline int copy_for_split(struct btrfs_trans_handle *trans, ...@@ -2854,6 +2873,64 @@ static noinline int copy_for_split(struct btrfs_trans_handle *trans,
return ret; return ret;
} }
/*
* double splits happen when we need to insert a big item in the middle
* of a leaf. A double split can leave us with 3 mostly empty leaves:
* leaf: [ slots 0 - N] [ our target ] [ N + 1 - total in leaf ]
* A B C
*
* We avoid this by trying to push the items on either side of our target
* into the adjacent leaves. If all goes well we can avoid the double split
* completely.
*/
static noinline int push_for_double_split(struct btrfs_trans_handle *trans,
struct btrfs_root *root,
struct btrfs_path *path,
int data_size)
{
int ret;
int progress = 0;
int slot;
u32 nritems;
slot = path->slots[0];
/*
* try to push all the items after our slot into the
* right leaf
*/
ret = push_leaf_right(trans, root, path, 1, data_size, 0, slot);
if (ret < 0)
return ret;
if (ret == 0)
progress++;
nritems = btrfs_header_nritems(path->nodes[0]);
/*
* our goal is to get our slot at the start or end of a leaf. If
* we've done so we're done
*/
if (path->slots[0] == 0 || path->slots[0] == nritems)
return 0;
if (btrfs_leaf_free_space(root, path->nodes[0]) >= data_size)
return 0;
/* try to push all the items before our slot into the next leaf */
slot = path->slots[0];
ret = push_leaf_left(trans, root, path, 1, data_size, 0, slot);
if (ret < 0)
return ret;
if (ret == 0)
progress++;
if (progress)
return 0;
return 1;
}
/* /*
* split the path's leaf in two, making sure there is at least data_size * split the path's leaf in two, making sure there is at least data_size
* available for the resulting leaf level of the path. * available for the resulting leaf level of the path.
...@@ -2876,6 +2953,7 @@ static noinline int split_leaf(struct btrfs_trans_handle *trans, ...@@ -2876,6 +2953,7 @@ static noinline int split_leaf(struct btrfs_trans_handle *trans,
int wret; int wret;
int split; int split;
int num_doubles = 0; int num_doubles = 0;
int tried_avoid_double = 0;
l = path->nodes[0]; l = path->nodes[0];
slot = path->slots[0]; slot = path->slots[0];
...@@ -2884,12 +2962,14 @@ static noinline int split_leaf(struct btrfs_trans_handle *trans, ...@@ -2884,12 +2962,14 @@ static noinline int split_leaf(struct btrfs_trans_handle *trans,
return -EOVERFLOW; return -EOVERFLOW;
/* first try to make some room by pushing left and right */ /* first try to make some room by pushing left and right */
if (data_size && ins_key->type != BTRFS_DIR_ITEM_KEY) { if (data_size) {
wret = push_leaf_right(trans, root, path, data_size, 0); wret = push_leaf_right(trans, root, path, data_size,
data_size, 0, 0);
if (wret < 0) if (wret < 0)
return wret; return wret;
if (wret) { if (wret) {
wret = push_leaf_left(trans, root, path, data_size, 0); wret = push_leaf_left(trans, root, path, data_size,
data_size, 0, (u32)-1);
if (wret < 0) if (wret < 0)
return wret; return wret;
} }
...@@ -2923,6 +3003,8 @@ static noinline int split_leaf(struct btrfs_trans_handle *trans, ...@@ -2923,6 +3003,8 @@ static noinline int split_leaf(struct btrfs_trans_handle *trans,
if (mid != nritems && if (mid != nritems &&
leaf_space_used(l, mid, nritems - mid) + leaf_space_used(l, mid, nritems - mid) +
data_size > BTRFS_LEAF_DATA_SIZE(root)) { data_size > BTRFS_LEAF_DATA_SIZE(root)) {
if (data_size && !tried_avoid_double)
goto push_for_double;
split = 2; split = 2;
} }
} }
...@@ -2939,6 +3021,8 @@ static noinline int split_leaf(struct btrfs_trans_handle *trans, ...@@ -2939,6 +3021,8 @@ static noinline int split_leaf(struct btrfs_trans_handle *trans,
if (mid != nritems && if (mid != nritems &&
leaf_space_used(l, mid, nritems - mid) + leaf_space_used(l, mid, nritems - mid) +
data_size > BTRFS_LEAF_DATA_SIZE(root)) { data_size > BTRFS_LEAF_DATA_SIZE(root)) {
if (data_size && !tried_avoid_double)
goto push_for_double;
split = 2 ; split = 2 ;
} }
} }
...@@ -3019,6 +3103,13 @@ static noinline int split_leaf(struct btrfs_trans_handle *trans, ...@@ -3019,6 +3103,13 @@ static noinline int split_leaf(struct btrfs_trans_handle *trans,
} }
return ret; return ret;
push_for_double:
push_for_double_split(trans, root, path, data_size);
tried_avoid_double = 1;
if (btrfs_leaf_free_space(root, path->nodes[0]) >= data_size)
return 0;
goto again;
} }
static noinline int setup_leaf_for_split(struct btrfs_trans_handle *trans, static noinline int setup_leaf_for_split(struct btrfs_trans_handle *trans,
...@@ -3915,13 +4006,15 @@ int btrfs_del_items(struct btrfs_trans_handle *trans, struct btrfs_root *root, ...@@ -3915,13 +4006,15 @@ int btrfs_del_items(struct btrfs_trans_handle *trans, struct btrfs_root *root,
extent_buffer_get(leaf); extent_buffer_get(leaf);
btrfs_set_path_blocking(path); btrfs_set_path_blocking(path);
wret = push_leaf_left(trans, root, path, 1, 1); wret = push_leaf_left(trans, root, path, 1, 1,
1, (u32)-1);
if (wret < 0 && wret != -ENOSPC) if (wret < 0 && wret != -ENOSPC)
ret = wret; ret = wret;
if (path->nodes[0] == leaf && if (path->nodes[0] == leaf &&
btrfs_header_nritems(leaf)) { btrfs_header_nritems(leaf)) {
wret = push_leaf_right(trans, root, path, 1, 1); wret = push_leaf_right(trans, root, path, 1,
1, 1, 0);
if (wret < 0 && wret != -ENOSPC) if (wret < 0 && wret != -ENOSPC)
ret = wret; ret = wret;
} }
......
...@@ -1458,7 +1458,7 @@ static noinline long btrfs_ioctl_clone(struct file *file, unsigned long srcfd, ...@@ -1458,7 +1458,7 @@ static noinline long btrfs_ioctl_clone(struct file *file, unsigned long srcfd,
*/ */
/* the destination must be opened for writing */ /* the destination must be opened for writing */
if (!(file->f_mode & FMODE_WRITE)) if (!(file->f_mode & FMODE_WRITE) || (file->f_flags & O_APPEND))
return -EINVAL; return -EINVAL;
ret = mnt_want_write(file->f_path.mnt); ret = mnt_want_write(file->f_path.mnt);
...@@ -1511,7 +1511,7 @@ static noinline long btrfs_ioctl_clone(struct file *file, unsigned long srcfd, ...@@ -1511,7 +1511,7 @@ static noinline long btrfs_ioctl_clone(struct file *file, unsigned long srcfd,
/* determine range to clone */ /* determine range to clone */
ret = -EINVAL; ret = -EINVAL;
if (off >= src->i_size || off + len > src->i_size) if (off + len > src->i_size || off + len < off)
goto out_unlock; goto out_unlock;
if (len == 0) if (len == 0)
olen = len = src->i_size - off; olen = len = src->i_size - off;
...@@ -1578,6 +1578,7 @@ static noinline long btrfs_ioctl_clone(struct file *file, unsigned long srcfd, ...@@ -1578,6 +1578,7 @@ static noinline long btrfs_ioctl_clone(struct file *file, unsigned long srcfd,
u64 disko = 0, diskl = 0; u64 disko = 0, diskl = 0;
u64 datao = 0, datal = 0; u64 datao = 0, datal = 0;
u8 comp; u8 comp;
u64 endoff;
size = btrfs_item_size_nr(leaf, slot); size = btrfs_item_size_nr(leaf, slot);
read_extent_buffer(leaf, buf, read_extent_buffer(leaf, buf,
...@@ -1712,9 +1713,18 @@ static noinline long btrfs_ioctl_clone(struct file *file, unsigned long srcfd, ...@@ -1712,9 +1713,18 @@ static noinline long btrfs_ioctl_clone(struct file *file, unsigned long srcfd,
btrfs_release_path(root, path); btrfs_release_path(root, path);
inode->i_mtime = inode->i_ctime = CURRENT_TIME; inode->i_mtime = inode->i_ctime = CURRENT_TIME;
if (new_key.offset + datal > inode->i_size)
btrfs_i_size_write(inode, /*
new_key.offset + datal); * 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 > off+olen)
endoff = off+olen;
if (endoff > inode->i_size)
btrfs_i_size_write(inode, endoff);
BTRFS_I(inode)->flags = BTRFS_I(src)->flags; BTRFS_I(inode)->flags = BTRFS_I(src)->flags;
ret = btrfs_update_inode(trans, root, inode); ret = btrfs_update_inode(trans, root, inode);
BUG_ON(ret); BUG_ON(ret);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment