Btrfs: fix locking bugs when defragging leaves
[cascardo/linux.git] / fs / btrfs / tree-defrag.c
index f31db43..cb65089 100644 (file)
@@ -89,6 +89,12 @@ int btrfs_defrag_leaves(struct btrfs_trans_handle *trans,
                goto out;
        }
        btrfs_release_path(path);
+       /*
+        * We don't need a lock on a leaf. btrfs_realloc_node() will lock all
+        * leafs from path->nodes[1], so set lowest_level to 1 to avoid later
+        * a deadlock (attempting to write lock an already write locked leaf).
+        */
+       path->lowest_level = 1;
        wret = btrfs_search_slot(trans, root, &key, path, 0, 1);
 
        if (wret < 0) {
@@ -99,9 +105,12 @@ int btrfs_defrag_leaves(struct btrfs_trans_handle *trans,
                ret = 0;
                goto out;
        }
-       path->slots[1] = btrfs_header_nritems(path->nodes[1]);
-       next_key_ret = btrfs_find_next_key(root, path, &key, 1,
-                                          min_trans);
+       /*
+        * The node at level 1 must always be locked when our path has
+        * keep_locks set and lowest_level is 1, regardless of the value of
+        * path->slots[1].
+        */
+       BUG_ON(path->locks[1] == 0);
        ret = btrfs_realloc_node(trans, root,
                                 path->nodes[1], 0,
                                 &last_ret,
@@ -110,6 +119,18 @@ int btrfs_defrag_leaves(struct btrfs_trans_handle *trans,
                WARN_ON(ret == -EAGAIN);
                goto out;
        }
+       /*
+        * Now that we reallocated the node we can find the next key. Note that
+        * btrfs_find_next_key() can release our path and do another search
+        * without COWing, this is because even with path->keep_locks = 1,
+        * btrfs_search_slot() / ctree.c:unlock_up() does not keeps a lock on a
+        * node when path->slots[node_level - 1] does not point to the last
+        * item or a slot beyond the last item (ctree.c:unlock_up()). Therefore
+        * we search for the next key after reallocating our node.
+        */
+       path->slots[1] = btrfs_header_nritems(path->nodes[1]);
+       next_key_ret = btrfs_find_next_key(root, path, &key, 1,
+                                          min_trans);
        if (next_key_ret == 0) {
                memcpy(&root->defrag_progress, &key, sizeof(key));
                ret = -EAGAIN;