Btrfs: send, fix race with transaction commits that create snapshots
[sfrench/cifs-2.6.git] / fs / btrfs / ctree.c
index 539901fb516503a5767fadd3bd278447a9263800..4252e89df6ae0c1b060dcc576f4eff6d5623ec46 100644 (file)
@@ -12,6 +12,7 @@
 #include "transaction.h"
 #include "print-tree.h"
 #include "locking.h"
+#include "volumes.h"
 
 static int split_node(struct btrfs_trans_handle *trans, struct btrfs_root
                      *root, struct btrfs_path *path, int level);
@@ -224,7 +225,7 @@ int btrfs_copy_root(struct btrfs_trans_handle *trans,
        else
                btrfs_set_header_owner(cow, new_root_objectid);
 
-       write_extent_buffer_fsid(cow, fs_info->fsid);
+       write_extent_buffer_fsid(cow, fs_info->fs_devices->metadata_uuid);
 
        WARN_ON(btrfs_header_generation(buf) > trans->transid);
        if (new_root_objectid == BTRFS_TREE_RELOC_OBJECTID)
@@ -1050,7 +1051,7 @@ static noinline int __btrfs_cow_block(struct btrfs_trans_handle *trans,
        else
                btrfs_set_header_owner(cow, root->root_key.objectid);
 
-       write_extent_buffer_fsid(cow, fs_info->fsid);
+       write_extent_buffer_fsid(cow, fs_info->fs_devices->metadata_uuid);
 
        ret = update_ref_for_cow(trans, root, buf, cow, &last_ref);
        if (ret) {
@@ -1290,7 +1291,6 @@ tree_mod_log_rewind(struct btrfs_fs_info *fs_info, struct btrfs_path *path,
        btrfs_tree_read_unlock_blocking(eb);
        free_extent_buffer(eb);
 
-       extent_buffer_get(eb_rewin);
        btrfs_tree_read_lock(eb_rewin);
        __tree_mod_log_rewind(fs_info, eb_rewin, time_seq, tm);
        WARN_ON(btrfs_header_nritems(eb_rewin) >
@@ -1362,7 +1362,6 @@ get_old_root(struct btrfs_root *root, u64 time_seq)
 
        if (!eb)
                return NULL;
-       extent_buffer_get(eb);
        btrfs_tree_read_lock(eb);
        if (old_root) {
                btrfs_set_header_bytenr(eb, eb->start);
@@ -1441,6 +1440,10 @@ noinline int btrfs_cow_block(struct btrfs_trans_handle *trans,
        u64 search_start;
        int ret;
 
+       if (test_bit(BTRFS_ROOT_DELETING, &root->state))
+               btrfs_err(fs_info,
+                       "COW'ing blocks on a fs root that's being dropped");
+
        if (trans->transaction != fs_info->running_transaction)
                WARN(1, KERN_CRIT "trans %llu running %llu\n",
                       trans->transid,
@@ -2584,14 +2587,27 @@ static struct extent_buffer *btrfs_search_slot_get_root(struct btrfs_root *root,
        root_lock = BTRFS_READ_LOCK;
 
        if (p->search_commit_root) {
-               /* The commit roots are read only so we always do read locks */
-               if (p->need_commit_sem)
+               /*
+                * The commit roots are read only so we always do read locks,
+                * and we always must hold the commit_root_sem when doing
+                * searches on them, the only exception is send where we don't
+                * want to block transaction commits for a long time, so
+                * we need to clone the commit root in order to avoid races
+                * with transaction commits that create a snapshot of one of
+                * the roots used by a send operation.
+                */
+               if (p->need_commit_sem) {
                        down_read(&fs_info->commit_root_sem);
-               b = root->commit_root;
-               extent_buffer_get(b);
-               level = btrfs_header_level(b);
-               if (p->need_commit_sem)
+                       b = btrfs_clone_extent_buffer(root->commit_root);
                        up_read(&fs_info->commit_root_sem);
+                       if (!b)
+                               return ERR_PTR(-ENOMEM);
+
+               } else {
+                       b = root->commit_root;
+                       extent_buffer_get(b);
+               }
+               level = btrfs_header_level(b);
                /*
                 * Ensure that all callers have set skip_locking when
                 * p->search_commit_root = 1.
@@ -2717,6 +2733,10 @@ int btrfs_search_slot(struct btrfs_trans_handle *trans, struct btrfs_root *root,
 again:
        prev_cmp = -1;
        b = btrfs_search_slot_get_root(root, p, write_lock_level);
+       if (IS_ERR(b)) {
+               ret = PTR_ERR(b);
+               goto done;
+       }
 
        while (b) {
                level = btrfs_header_level(b);
@@ -5390,7 +5410,6 @@ int btrfs_compare_trees(struct btrfs_root *left_root,
                ret = -ENOMEM;
                goto out;
        }
-       extent_buffer_get(left_path->nodes[left_level]);
 
        right_level = btrfs_header_level(right_root->commit_root);
        right_root_level = right_level;
@@ -5401,7 +5420,6 @@ int btrfs_compare_trees(struct btrfs_root *left_root,
                ret = -ENOMEM;
                goto out;
        }
-       extent_buffer_get(right_path->nodes[right_level]);
        up_read(&fs_info->commit_root_sem);
 
        if (left_level == 0)