aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArne Jansen <sensille@gmx.net>2012-02-24 22:31:21 +0100
committerArne Jansen <sensille@gmx.net>2012-04-16 21:00:10 +0200
commit99d20432dcbb4f980b59637258ee6a16aaf2e88c (patch)
tree4fe8fedd944249d89b4c5240ef8451207fa08b4c
parent8b7b6c408f8a549a968a8e2e5a7b5103c08a4ed9 (diff)
downloadlinux-btrfs-droptree.tar.gz
btrfs: fix race in readadroptree
Signed-off-by: Arne Jansen <sensille@gmx.net>
-rw-r--r--fs/btrfs/reada.c52
1 files changed, 31 insertions, 21 deletions
diff --git a/fs/btrfs/reada.c b/fs/btrfs/reada.c
index 69a940992865e..a78773cbf303e 100644
--- a/fs/btrfs/reada.c
+++ b/fs/btrfs/reada.c
@@ -280,14 +280,12 @@ static struct reada_zone *reada_find_zone(struct btrfs_fs_info *fs_info,
struct btrfs_bio *bbio)
{
int ret;
- int looped = 0;
struct reada_zone *zone;
struct btrfs_block_group_cache *cache = NULL;
u64 start;
u64 end;
int i;
-again:
zone = NULL;
spin_lock(&fs_info->reada_lock);
ret = radix_tree_gang_lookup(&dev->reada_zones, (void **)&zone,
@@ -304,9 +302,6 @@ again:
spin_unlock(&fs_info->reada_lock);
}
- if (looped)
- return NULL;
-
cache = btrfs_lookup_block_group(fs_info, logical);
if (!cache)
return NULL;
@@ -337,13 +332,14 @@ again:
ret = radix_tree_insert(&dev->reada_zones,
(unsigned long)zone->end >> PAGE_CACHE_SHIFT,
zone);
- spin_unlock(&fs_info->reada_lock);
-
- if (ret) {
+ if (ret == -EEXIST) {
kfree(zone);
- looped = 1;
- goto again;
+ ret = radix_tree_gang_lookup(&dev->reada_zones, (void **)&zone,
+ logical >> PAGE_CACHE_SHIFT, 1);
+ if (ret == 1)
+ kref_get(&zone->refcnt);
}
+ spin_unlock(&fs_info->reada_lock);
return zone;
}
@@ -353,26 +349,26 @@ static struct reada_extent *reada_find_extent(struct btrfs_root *root,
struct btrfs_key *top, int level)
{
int ret;
- int looped = 0;
struct reada_extent *re = NULL;
+ struct reada_extent *re_exist = NULL;
struct btrfs_fs_info *fs_info = root->fs_info;
struct btrfs_mapping_tree *map_tree = &fs_info->mapping_tree;
struct btrfs_bio *bbio = NULL;
struct btrfs_device *dev;
+ struct btrfs_device *prev_dev;
u32 blocksize;
u64 length;
int nzones = 0;
int i;
unsigned long index = logical >> PAGE_CACHE_SHIFT;
-again:
spin_lock(&fs_info->reada_lock);
re = radix_tree_lookup(&fs_info->reada_tree, index);
if (re)
kref_get(&re->refcnt);
spin_unlock(&fs_info->reada_lock);
- if (re || looped)
+ if (re)
return re;
re = kzalloc(sizeof(*re), GFP_NOFS);
@@ -393,8 +389,9 @@ again:
*/
length = blocksize;
ret = btrfs_map_block(map_tree, REQ_WRITE, logical, &length, &bbio, 0);
- if (ret || !bbio || length < blocksize)
+ if (ret || !bbio || length < blocksize) {
goto error;
+}
if (bbio->num_stripes > MAX_MIRRORS) {
printk(KERN_ERR "btrfs readahead: more than %d copies not "
@@ -429,16 +426,31 @@ again:
/* insert extent in reada_tree + all per-device trees, all or nothing */
spin_lock(&fs_info->reada_lock);
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) {
spin_unlock(&fs_info->reada_lock);
- if (ret != -ENOMEM) {
- /* someone inserted the extent in the meantime */
- looped = 1;
- }
goto error;
}
+ prev_dev = NULL;
for (i = 0; i < nzones; ++i) {
dev = bbio->stripes[i].dev;
+ if (dev == prev_dev) {
+ /*
+ * in case of DUP, just add the first zone. As both
+ * are on the same device, there's nothing to gain
+ * from adding both.
+ * Also, it wouldn't work, as the tree is per device
+ * and adding would fail with EEXIST
+ */
+ continue;
+ }
+ prev_dev = dev;
ret = radix_tree_insert(&dev->reada_extents, index, re);
if (ret) {
while (--i >= 0) {
@@ -481,9 +493,7 @@ error:
}
kfree(bbio);
kfree(re);
- if (looped)
- goto again;
- return NULL;
+ return re_exist;
}
static void reada_kref_dummy(struct kref *kr)