diff options
author | Arne Jansen <sensille@gmx.net> | 2012-02-24 22:31:21 +0100 |
---|---|---|
committer | Arne Jansen <sensille@gmx.net> | 2012-04-16 21:00:10 +0200 |
commit | 99d20432dcbb4f980b59637258ee6a16aaf2e88c (patch) | |
tree | 4fe8fedd944249d89b4c5240ef8451207fa08b4c | |
parent | 8b7b6c408f8a549a968a8e2e5a7b5103c08a4ed9 (diff) | |
download | linux-btrfs-droptree.tar.gz |
btrfs: fix race in readadroptree
Signed-off-by: Arne Jansen <sensille@gmx.net>
-rw-r--r-- | fs/btrfs/reada.c | 52 |
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) |