Btrfs: Fix clone ioctl to not hold the path over inserts

Signed-off-by: Chris Mason <chris.mason@oracle.com>
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 5204599..f7beb9b 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -3101,7 +3101,7 @@
 	return ret;
 }
 
-void dup_item_to_inode(struct btrfs_trans_handle *trans,
+int dup_item_to_inode(struct btrfs_trans_handle *trans,
 		       struct btrfs_root *root,
 		       struct btrfs_path *path,
 		       struct extent_buffer *leaf,
@@ -3109,19 +3109,22 @@
 		       struct btrfs_key *key,
 		       u64 destino)
 {
-	struct btrfs_path *cpath = btrfs_alloc_path();
+	char *dup;
 	int len = btrfs_item_size_nr(leaf, slot);
-	int dstoff;
 	struct btrfs_key ckey = *key;
-	int ret;
+	int ret = 0;
+
+	dup = kmalloc(len, GFP_NOFS);
+	if (!dup)
+		return -ENOMEM;
+
+	read_extent_buffer(leaf, dup, btrfs_item_ptr_offset(leaf, slot), len);
+	btrfs_release_path(root, path);
 
 	ckey.objectid = destino;
-	ret = btrfs_insert_empty_item(trans, root, cpath, &ckey, len);
-	dstoff = btrfs_item_ptr_offset(cpath->nodes[0], cpath->slots[0]);
-	copy_extent_buffer(cpath->nodes[0], leaf, dstoff,
-			   btrfs_item_ptr_offset(leaf, slot),
-			   len);
-	btrfs_release_path(root, cpath);
+	ret = btrfs_insert_item(trans, root, &ckey, dup, len);
+	kfree(dup);
+	return ret;
 }
 
 long btrfs_ioctl_clone(struct file *file, unsigned long src_fd)
@@ -3137,7 +3140,6 @@
 	struct btrfs_key key;
 	struct extent_buffer *leaf;
 	u32 nritems;
-	int nextret;
 	int slot;
 
 	src_file = fget(src_fd);
@@ -3174,20 +3176,32 @@
 	mutex_lock(&root->fs_info->fs_mutex);
 	trans = btrfs_start_transaction(root, 0);
 	path = btrfs_alloc_path();
+	if (!path) {
+		ret = -ENOMEM;
+		goto out;
+	}
+	key.offset = 0;
+	key.type = BTRFS_EXTENT_DATA_KEY;
+	key.objectid = src->i_ino;
 	pos = 0;
+	path->reada = 2;
+
 	while (1) {
-		ret = btrfs_lookup_file_extent(trans, root, path, src->i_ino,
-					       pos, 0);
+		/*
+		 * note the key will change type as we walk through the
+		 * tree.
+		 */
+		ret = btrfs_search_slot(trans, root, &key, path, 0, 0);
 		if (ret < 0)
 			goto out;
-		if (ret > 0) {
-			if (path->slots[0] == 0) {
-				ret = 0;
+
+		if (path->slots[0] >= btrfs_header_nritems(path->nodes[0])) {
+			ret = btrfs_next_leaf(root, path);
+			if (ret < 0)
 				goto out;
-			}
-			path->slots[0]--;
+			if (ret > 0)
+				break;
 		}
-next_slot:
 		leaf = path->nodes[0];
 		slot = path->slots[0];
 		btrfs_item_key_to_cpu(leaf, &key, slot);
@@ -3195,7 +3209,8 @@
 
 		if (btrfs_key_type(&key) > BTRFS_CSUM_ITEM_KEY ||
 		    key.objectid != src->i_ino)
-			goto out;
+			break;
+
 		if (btrfs_key_type(&key) == BTRFS_EXTENT_DATA_KEY) {
 			struct btrfs_file_extent_item *extent;
 			int found_type;
@@ -3225,28 +3240,28 @@
 				}
 				pos = key.offset + len;
 			} else if (found_type == BTRFS_FILE_EXTENT_INLINE) {
-				dup_item_to_inode(trans, root, path, leaf, slot,
-						  &key, inode->i_ino);
+				ret = dup_item_to_inode(trans, root, path,
+							leaf, slot, &key,
+							inode->i_ino);
+				if (ret)
+					goto out;
 				pos = key.offset + btrfs_item_size_nr(leaf,
 								      slot);
 			}
-		} else if (btrfs_key_type(&key) == BTRFS_CSUM_ITEM_KEY)
-			dup_item_to_inode(trans, root, path, leaf, slot, &key,
-					  inode->i_ino);
+		} else if (btrfs_key_type(&key) == BTRFS_CSUM_ITEM_KEY) {
+			ret = dup_item_to_inode(trans, root, path, leaf,
+						slot, &key, inode->i_ino);
 
-		if (slot >= nritems - 1) {
-			nextret = btrfs_next_leaf(root, path);
-			if (nextret)
+			if (ret)
 				goto out;
-		} else {
-			path->slots[0]++;
 		}
-		goto next_slot;
+		key.offset++;
+		btrfs_release_path(root, path);
 	}
 
+	ret = 0;
 out:
 	btrfs_free_path(path);
-	ret = 0;
 
 	inode->i_blocks = src->i_blocks;
 	i_size_write(inode, src->i_size);