]> git.kernelconcepts.de Git - karo-tx-linux.git/commitdiff
Btrfs: fix protection between walking backrefs and root deletion
authorWang Shilong <wangsl.fnst@cn.fujitsu.com>
Thu, 23 Jan 2014 05:47:48 +0000 (13:47 +0800)
committerChris Mason <clm@fb.com>
Wed, 29 Jan 2014 15:06:23 +0000 (07:06 -0800)
There is a race condition between resolving indirect ref and root deletion,
and we should gurantee that root can not be destroyed to avoid accessing
broken tree here.

Here we fix it by holding @subvol_srcu, and we will release it as soon
as we have held root node lock.

Signed-off-by: Wang Shilong <wangsl.fnst@cn.fujitsu.com>
Signed-off-by: Josef Bacik <jbacik@fb.com>
Signed-off-by: Chris Mason <clm@fb.com>
fs/btrfs/backref.c

index 15384968a84a882ebd6081179eed5e92fbbe0290..10ae5700ab1eae1bb3c1b8bc72be03c6918e2324 100644 (file)
@@ -301,23 +301,34 @@ static int __resolve_indirect_ref(struct btrfs_fs_info *fs_info,
        int ret = 0;
        int root_level;
        int level = ref->level;
+       int index;
 
        root_key.objectid = ref->root_id;
        root_key.type = BTRFS_ROOT_ITEM_KEY;
        root_key.offset = (u64)-1;
+
+       index = srcu_read_lock(&fs_info->subvol_srcu);
+
        root = btrfs_read_fs_root_no_name(fs_info, &root_key);
        if (IS_ERR(root)) {
+               srcu_read_unlock(&fs_info->subvol_srcu, index);
                ret = PTR_ERR(root);
                goto out;
        }
 
        root_level = btrfs_old_root_level(root, time_seq);
 
-       if (root_level + 1 == level)
+       if (root_level + 1 == level) {
+               srcu_read_unlock(&fs_info->subvol_srcu, index);
                goto out;
+       }
 
        path->lowest_level = level;
        ret = btrfs_search_old_slot(root, &ref->key_for_search, path, time_seq);
+
+       /* root node has been locked, we can release @subvol_srcu safely here */
+       srcu_read_unlock(&fs_info->subvol_srcu, index);
+
        pr_debug("search slot in root %llu (level %d, ref count %d) returned "
                 "%d for key (%llu %u %llu)\n",
                 ref->root_id, level, ref->count, ret,