Commit 8c9c2bf7 authored by Arne Jansen's avatar Arne Jansen Committed by David Sterba

btrfs: fix race in reada

When inserting into the radix tree returns EEXIST, get the existing
entry without giving up the spinlock in between.
There was a race for both the zones trees and the extent tree.
Signed-off-by: default avatarArne Jansen <sensille@gmx.net>
parent 848cce0d
...@@ -4332,7 +4332,13 @@ static int btrfs_real_readdir(struct file *filp, void *dirent, ...@@ -4332,7 +4332,13 @@ static int btrfs_real_readdir(struct file *filp, void *dirent,
} }
no_dentry: no_dentry:
/* is this a reference to our own snapshot? If so /* is this a reference to our own snapshot? If so
* skip it * skip it.
*
* In contrast to old kernels, we insert the snapshot's
* dir item and dir index after it has been created, so
* we won't find a reference to our own snapshot. We
* still keep the following code for backward
* compatibility.
*/ */
if (location.type == BTRFS_ROOT_ITEM_KEY && if (location.type == BTRFS_ROOT_ITEM_KEY &&
location.objectid == root->root_key.objectid) { location.objectid == root->root_key.objectid) {
......
...@@ -250,14 +250,12 @@ static struct reada_zone *reada_find_zone(struct btrfs_fs_info *fs_info, ...@@ -250,14 +250,12 @@ static struct reada_zone *reada_find_zone(struct btrfs_fs_info *fs_info,
struct btrfs_bio *bbio) struct btrfs_bio *bbio)
{ {
int ret; int ret;
int looped = 0;
struct reada_zone *zone; struct reada_zone *zone;
struct btrfs_block_group_cache *cache = NULL; struct btrfs_block_group_cache *cache = NULL;
u64 start; u64 start;
u64 end; u64 end;
int i; int i;
again:
zone = NULL; zone = NULL;
spin_lock(&fs_info->reada_lock); spin_lock(&fs_info->reada_lock);
ret = radix_tree_gang_lookup(&dev->reada_zones, (void **)&zone, ret = radix_tree_gang_lookup(&dev->reada_zones, (void **)&zone,
...@@ -274,9 +272,6 @@ static struct reada_zone *reada_find_zone(struct btrfs_fs_info *fs_info, ...@@ -274,9 +272,6 @@ static struct reada_zone *reada_find_zone(struct btrfs_fs_info *fs_info,
spin_unlock(&fs_info->reada_lock); spin_unlock(&fs_info->reada_lock);
} }
if (looped)
return NULL;
cache = btrfs_lookup_block_group(fs_info, logical); cache = btrfs_lookup_block_group(fs_info, logical);
if (!cache) if (!cache)
return NULL; return NULL;
...@@ -307,13 +302,15 @@ static struct reada_zone *reada_find_zone(struct btrfs_fs_info *fs_info, ...@@ -307,13 +302,15 @@ static struct reada_zone *reada_find_zone(struct btrfs_fs_info *fs_info,
ret = radix_tree_insert(&dev->reada_zones, ret = radix_tree_insert(&dev->reada_zones,
(unsigned long)(zone->end >> PAGE_CACHE_SHIFT), (unsigned long)(zone->end >> PAGE_CACHE_SHIFT),
zone); zone);
spin_unlock(&fs_info->reada_lock);
if (ret) { if (ret == -EEXIST) {
kfree(zone); kfree(zone);
looped = 1; ret = radix_tree_gang_lookup(&dev->reada_zones, (void **)&zone,
goto again; logical >> PAGE_CACHE_SHIFT, 1);
if (ret == 1)
kref_get(&zone->refcnt);
} }
spin_unlock(&fs_info->reada_lock);
return zone; return zone;
} }
...@@ -323,8 +320,8 @@ static struct reada_extent *reada_find_extent(struct btrfs_root *root, ...@@ -323,8 +320,8 @@ static struct reada_extent *reada_find_extent(struct btrfs_root *root,
struct btrfs_key *top, int level) struct btrfs_key *top, int level)
{ {
int ret; int ret;
int looped = 0;
struct reada_extent *re = NULL; struct reada_extent *re = NULL;
struct reada_extent *re_exist = NULL;
struct btrfs_fs_info *fs_info = root->fs_info; struct btrfs_fs_info *fs_info = root->fs_info;
struct btrfs_mapping_tree *map_tree = &fs_info->mapping_tree; struct btrfs_mapping_tree *map_tree = &fs_info->mapping_tree;
struct btrfs_bio *bbio = NULL; struct btrfs_bio *bbio = NULL;
...@@ -335,14 +332,13 @@ static struct reada_extent *reada_find_extent(struct btrfs_root *root, ...@@ -335,14 +332,13 @@ static struct reada_extent *reada_find_extent(struct btrfs_root *root,
int i; int i;
unsigned long index = logical >> PAGE_CACHE_SHIFT; unsigned long index = logical >> PAGE_CACHE_SHIFT;
again:
spin_lock(&fs_info->reada_lock); spin_lock(&fs_info->reada_lock);
re = radix_tree_lookup(&fs_info->reada_tree, index); re = radix_tree_lookup(&fs_info->reada_tree, index);
if (re) if (re)
kref_get(&re->refcnt); kref_get(&re->refcnt);
spin_unlock(&fs_info->reada_lock); spin_unlock(&fs_info->reada_lock);
if (re || looped) if (re)
return re; return re;
re = kzalloc(sizeof(*re), GFP_NOFS); re = kzalloc(sizeof(*re), GFP_NOFS);
...@@ -398,12 +394,15 @@ static struct reada_extent *reada_find_extent(struct btrfs_root *root, ...@@ -398,12 +394,15 @@ static struct reada_extent *reada_find_extent(struct btrfs_root *root,
/* insert extent in reada_tree + all per-device trees, all or nothing */ /* insert extent in reada_tree + all per-device trees, all or nothing */
spin_lock(&fs_info->reada_lock); spin_lock(&fs_info->reada_lock);
ret = radix_tree_insert(&fs_info->reada_tree, index, re); ret = radix_tree_insert(&fs_info->reada_tree, index, re);
if (ret == -EEXIST) {
re_exist = radix_tree_lookup(&fs_info->reada_tree, index);
BUG_ON(!re_exist);
kref_get(&re_exist->refcnt);
spin_unlock(&fs_info->reada_lock);
goto error;
}
if (ret) { if (ret) {
spin_unlock(&fs_info->reada_lock); spin_unlock(&fs_info->reada_lock);
if (ret != -ENOMEM) {
/* someone inserted the extent in the meantime */
looped = 1;
}
goto error; goto error;
} }
for (i = 0; i < nzones; ++i) { for (i = 0; i < nzones; ++i) {
...@@ -450,9 +449,7 @@ static struct reada_extent *reada_find_extent(struct btrfs_root *root, ...@@ -450,9 +449,7 @@ static struct reada_extent *reada_find_extent(struct btrfs_root *root,
} }
kfree(bbio); kfree(bbio);
kfree(re); kfree(re);
if (looped) return re_exist;
goto again;
return NULL;
} }
static void reada_kref_dummy(struct kref *kr) static void reada_kref_dummy(struct kref *kr)
......
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