In this patch, we make two things: a) skiplist -> rcu-skiplist This is quite direct, since in skiplist each level is a list, any modification to the skiplist refers to "pointers change", which fits RCU's sematic.
b) use rcu lock for reader side and mutex lock for updater side to protect extent_map instead of rwlock. Signed-off-by: Liu Bo <[email protected]> --- changes v2: - fix a bug reported by David Sterba <[email protected]>, thanks a lot! - use mutex lock to protect extent_map updater side, so that we can make the reclaim code much easier. --- fs/btrfs/compression.c | 8 ++++---- fs/btrfs/disk-io.c | 9 ++++----- fs/btrfs/extent_io.c | 13 ++++++------- fs/btrfs/extent_map.c | 28 ++++++++++++++++++---------- fs/btrfs/extent_map.h | 5 ++--- fs/btrfs/file.c | 11 ++++++----- fs/btrfs/inode.c | 28 ++++++++++++++-------------- fs/btrfs/ioctl.c | 8 ++++---- fs/btrfs/relocation.c | 4 ++-- fs/btrfs/scrub.c | 4 ++-- fs/btrfs/skiplist.c | 11 +++++++---- fs/btrfs/skiplist.h | 25 ++++++++++++++++--------- fs/btrfs/volumes.c | 36 ++++++++++++++++++------------------ 13 files changed, 103 insertions(+), 87 deletions(-) diff --git a/fs/btrfs/compression.c b/fs/btrfs/compression.c index 14f1c5a..bb4ac31 100644 --- a/fs/btrfs/compression.c +++ b/fs/btrfs/compression.c @@ -498,10 +498,10 @@ static noinline int add_ra_bio_pages(struct inode *inode, */ set_page_extent_mapped(page); lock_extent(tree, last_offset, end, GFP_NOFS); - read_lock(&em_tree->lock); + rcu_read_lock(); em = lookup_extent_mapping(em_tree, last_offset, PAGE_CACHE_SIZE); - read_unlock(&em_tree->lock); + rcu_read_unlock(); if (!em || last_offset < em->start || (last_offset + PAGE_CACHE_SIZE > extent_map_end(em)) || @@ -583,11 +583,11 @@ int btrfs_submit_compressed_read(struct inode *inode, struct bio *bio, em_tree = &BTRFS_I(inode)->extent_tree; /* we need the actual starting offset of this extent in the file */ - read_lock(&em_tree->lock); + rcu_read_lock(); em = lookup_extent_mapping(em_tree, page_offset(bio->bi_io_vec->bv_page), PAGE_CACHE_SIZE); - read_unlock(&em_tree->lock); + rcu_read_unlock(); compressed_len = em->block_len; cb = kmalloc(compressed_bio_size(root, compressed_len), GFP_NOFS); diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c index 3f9d555..8e09517 100644 --- a/fs/btrfs/disk-io.c +++ b/fs/btrfs/disk-io.c @@ -191,15 +191,14 @@ static struct extent_map *btree_get_extent(struct inode *inode, struct extent_map *em; int ret; - read_lock(&em_tree->lock); + rcu_read_lock(); em = lookup_extent_mapping(em_tree, start, len); + rcu_read_unlock(); if (em) { em->bdev = BTRFS_I(inode)->root->fs_info->fs_devices->latest_bdev; - read_unlock(&em_tree->lock); goto out; } - read_unlock(&em_tree->lock); em = alloc_extent_map(); if (!em) { @@ -212,7 +211,7 @@ static struct extent_map *btree_get_extent(struct inode *inode, em->block_start = 0; em->bdev = BTRFS_I(inode)->root->fs_info->fs_devices->latest_bdev; - write_lock(&em_tree->lock); + mutex_lock(&em_tree->lock); ret = add_extent_mapping(em_tree, em); if (ret == -EEXIST) { u64 failed_start = em->start; @@ -231,7 +230,7 @@ static struct extent_map *btree_get_extent(struct inode *inode, free_extent_map(em); em = NULL; } - write_unlock(&em_tree->lock); + mutex_unlock(&em_tree->lock); if (ret) em = ERR_PTR(ret); diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c index 49f3c9d..7efa8dd 100644 --- a/fs/btrfs/extent_io.c +++ b/fs/btrfs/extent_io.c @@ -2013,10 +2013,10 @@ static int bio_readpage_error(struct bio *failed_bio, struct page *page, failrec->bio_flags = 0; failrec->in_validation = 0; - read_lock(&em_tree->lock); + rcu_read_lock(); em = lookup_extent_mapping(em_tree, start, failrec->len); + rcu_read_unlock(); if (!em) { - read_unlock(&em_tree->lock); kfree(failrec); return -EIO; } @@ -2025,7 +2025,6 @@ static int bio_readpage_error(struct bio *failed_bio, struct page *page, free_extent_map(em); em = NULL; } - read_unlock(&em_tree->lock); if (!em || IS_ERR(em)) { kfree(failrec); @@ -3286,15 +3285,15 @@ int try_release_extent_mapping(struct extent_map_tree *map, u64 len; while (start <= end) { len = end - start + 1; - write_lock(&map->lock); + mutex_lock(&map->lock); em = lookup_extent_mapping(map, start, len); if (IS_ERR_OR_NULL(em)) { - write_unlock(&map->lock); + mutex_unlock(&map->lock); break; } if (test_bit(EXTENT_FLAG_PINNED, &em->flags) || em->start != start) { - write_unlock(&map->lock); + mutex_unlock(&map->lock); free_extent_map(em); break; } @@ -3307,7 +3306,7 @@ int try_release_extent_mapping(struct extent_map_tree *map, free_extent_map(em); } start = extent_map_end(em); - write_unlock(&map->lock); + mutex_unlock(&map->lock); /* once for us */ free_extent_map(em); diff --git a/fs/btrfs/extent_map.c b/fs/btrfs/extent_map.c index e0a7881..8d40638 100644 --- a/fs/btrfs/extent_map.c +++ b/fs/btrfs/extent_map.c @@ -11,6 +11,7 @@ static struct kmem_cache *extent_map_cache; static LIST_HEAD(maps); +#define MAP_REFS_DEBUG 1 #define MAP_LEAK_DEBUG 1 #if MAP_LEAK_DEBUG static DEFINE_SPINLOCK(map_leak_lock); @@ -67,7 +68,7 @@ void extent_map_tree_init(struct extent_map_tree *tree) { tree->head.start = (-1ULL); sl_init_list(&tree->map, &tree->head.sl_node); - rwlock_init(&tree->lock); + mutex_init(&tree->lock); } /** @@ -100,8 +101,11 @@ struct extent_map *alloc_extent_map(void) return em; } -static inline void __free_extent_map(struct extent_map *em) +static inline void __free_extent_map(struct rcu_head *head) { + struct sl_node *node = container_of(head, struct sl_node, rcu_head); + struct extent_map *em = sl_entry(node, struct extent_map, sl_node); + #if MAP_LEAK_DEBUG unsigned long flags; @@ -129,7 +133,7 @@ void free_extent_map(struct extent_map *em) WARN_ON(atomic_read(&em->refs) == 0); if (atomic_dec_and_test(&em->refs)) - __free_extent_map(em); + call_rcu(&em->sl_node.rcu_head, __free_extent_map); } static inline int in_entry(struct sl_node *node, u64 offset) @@ -166,14 +170,14 @@ static struct sl_node *sl_search(struct sl_list *list, u64 offset, BUG_ON(!list); level = list->level; - p = list->head; + p = rcu_dereference(list->head); BUG_ON(!p); if (sl_empty(p)) return NULL; do { while (entry = next_entry(p, level, &q), entry->start <= offset) - p = q; + p = rcu_dereference(q); if (in_entry(p, offset)) return p; @@ -299,7 +303,7 @@ int unpin_extent_cache(struct extent_map_tree *tree, u64 start, u64 len) int ret = 0; struct extent_map *em; - write_lock(&tree->lock); + mutex_lock(&tree->lock); em = lookup_extent_mapping(tree, start, len); WARN_ON(!em || em->start != start); @@ -312,7 +316,7 @@ int unpin_extent_cache(struct extent_map_tree *tree, u64 start, u64 len) free_extent_map(em); out: - write_unlock(&tree->lock); + mutex_unlock(&tree->lock); return ret; } @@ -326,8 +330,7 @@ out: * into the tree directly, with an additional reference taken, or a * reference dropped if the merge attempt was successful. */ -int add_extent_mapping(struct extent_map_tree *tree, - struct extent_map *em) +int add_extent_mapping(struct extent_map_tree *tree, struct extent_map *em) { int ret = 0; struct sl_node *sl_node; @@ -382,7 +385,12 @@ struct extent_map *__lookup_extent_mapping(struct extent_map_tree *tree, if (strict && !(end > em->start && start < extent_map_end(em))) return NULL; - atomic_inc(&em->refs); +#if MAP_REFS_DEBUG + if (!atomic_read(&em->refs)) + printk(KERN_INFO "Btrfs: Reader gets an em with 0 refs\n"); +#endif + if (!atomic_inc_not_zero(&em->refs)) + return NULL; return em; } diff --git a/fs/btrfs/extent_map.h b/fs/btrfs/extent_map.h index 6d2c247..6563800 100644 --- a/fs/btrfs/extent_map.h +++ b/fs/btrfs/extent_map.h @@ -41,7 +41,7 @@ struct map_head { struct extent_map_tree { struct sl_list map; - rwlock_t lock; + struct mutex lock; struct map_head head; }; @@ -62,8 +62,7 @@ static inline u64 extent_map_block_end(struct extent_map *em) void extent_map_tree_init(struct extent_map_tree *tree); struct extent_map *lookup_extent_mapping(struct extent_map_tree *tree, u64 start, u64 len); -int add_extent_mapping(struct extent_map_tree *tree, - struct extent_map *em); +int add_extent_mapping(struct extent_map_tree *tree, struct extent_map *em); int remove_extent_mapping(struct extent_map_tree *tree, struct extent_map *em); struct extent_map *alloc_extent_map(void); diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c index cc7492c..27c5ff3 100644 --- a/fs/btrfs/file.c +++ b/fs/btrfs/file.c @@ -454,24 +454,25 @@ int btrfs_drop_extent_cache(struct inode *inode, u64 start, u64 end, split2 = alloc_extent_map(); BUG_ON(!split || !split2); - write_lock(&em_tree->lock); + mutex_lock(&em_tree->lock); em = lookup_extent_mapping(em_tree, start, len); if (!em) { - write_unlock(&em_tree->lock); + mutex_unlock(&em_tree->lock); break; } + flags = em->flags; if (skip_pinned && test_bit(EXTENT_FLAG_PINNED, &em->flags)) { if (testend && em->start + em->len >= start + len) { free_extent_map(em); - write_unlock(&em_tree->lock); + mutex_unlock(&em_tree->lock); break; } start = em->start + em->len; if (testend) len = start + len - (em->start + em->len); free_extent_map(em); - write_unlock(&em_tree->lock); + mutex_unlock(&em_tree->lock); continue; } compressed = test_bit(EXTENT_FLAG_COMPRESSED, &em->flags); @@ -524,7 +525,7 @@ int btrfs_drop_extent_cache(struct inode *inode, u64 start, u64 end, free_extent_map(split); split = NULL; } - write_unlock(&em_tree->lock); + mutex_unlock(&em_tree->lock); /* once for us */ free_extent_map(em); diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 13b0542..edf3258 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -675,9 +675,9 @@ retry: set_bit(EXTENT_FLAG_COMPRESSED, &em->flags); while (1) { - write_lock(&em_tree->lock); + mutex_lock(&em_tree->lock); ret = add_extent_mapping(em_tree, em); - write_unlock(&em_tree->lock); + mutex_unlock(&em_tree->lock); if (ret != -EEXIST) { free_extent_map(em); break; @@ -732,7 +732,7 @@ static u64 get_extent_allocation_hint(struct inode *inode, u64 start, struct extent_map *em; u64 alloc_hint = 0; - read_lock(&em_tree->lock); + rcu_read_lock(); em = search_extent_mapping(em_tree, start, num_bytes); if (em) { /* @@ -752,7 +752,7 @@ static u64 get_extent_allocation_hint(struct inode *inode, u64 start, free_extent_map(em); } } - read_unlock(&em_tree->lock); + rcu_read_unlock(); return alloc_hint; } @@ -854,9 +854,9 @@ static noinline int cow_file_range(struct inode *inode, set_bit(EXTENT_FLAG_PINNED, &em->flags); while (1) { - write_lock(&em_tree->lock); + mutex_lock(&em_tree->lock); ret = add_extent_mapping(em_tree, em); - write_unlock(&em_tree->lock); + mutex_unlock(&em_tree->lock); if (ret != -EEXIST) { free_extent_map(em); break; @@ -1207,9 +1207,9 @@ out_check: em->bdev = root->fs_info->fs_devices->latest_bdev; set_bit(EXTENT_FLAG_PINNED, &em->flags); while (1) { - write_lock(&em_tree->lock); + mutex_lock(&em_tree->lock); ret = add_extent_mapping(em_tree, em); - write_unlock(&em_tree->lock); + mutex_unlock(&em_tree->lock); if (ret != -EEXIST) { free_extent_map(em); break; @@ -4950,11 +4950,11 @@ struct extent_map *btrfs_get_extent(struct inode *inode, struct page *page, int compress_type; again: - read_lock(&em_tree->lock); + rcu_read_lock(); em = lookup_extent_mapping(em_tree, start, len); if (em) em->bdev = root->fs_info->fs_devices->latest_bdev; - read_unlock(&em_tree->lock); + rcu_read_unlock(); if (em) { if (em->start > start || em->start + em->len <= start) @@ -5166,7 +5166,7 @@ insert: } err = 0; - write_lock(&em_tree->lock); + mutex_lock(&em_tree->lock); ret = add_extent_mapping(em_tree, em); /* it is possible that someone inserted the extent into the tree * while we had the lock dropped. It is also possible that @@ -5206,7 +5206,7 @@ insert: err = 0; } } - write_unlock(&em_tree->lock); + mutex_unlock(&em_tree->lock); out: trace_btrfs_get_extent(root, em); @@ -5414,9 +5414,9 @@ static struct extent_map *btrfs_new_extent_direct(struct inode *inode, set_bit(EXTENT_FLAG_PINNED, &em->flags); while (insert) { - write_lock(&em_tree->lock); + mutex_lock(&em_tree->lock); ret = add_extent_mapping(em_tree, em); - write_unlock(&em_tree->lock); + mutex_unlock(&em_tree->lock); if (ret != -EEXIST) break; btrfs_drop_extent_cache(inode, start, start + em->len - 1, 0); diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index c04f02c..83fc601 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -673,9 +673,9 @@ static int check_defrag_in_cache(struct inode *inode, u64 offset, int thresh) struct extent_map_tree *em_tree = &BTRFS_I(inode)->extent_tree; u64 end; - read_lock(&em_tree->lock); + rcu_read_lock(); em = lookup_extent_mapping(em_tree, offset, PAGE_CACHE_SIZE); - read_unlock(&em_tree->lock); + rcu_read_unlock(); if (em) { end = extent_map_end(em); @@ -782,9 +782,9 @@ static int should_defrag_range(struct inode *inode, u64 start, u64 len, * hopefully we have this extent in the tree already, try without * the full extent lock */ - read_lock(&em_tree->lock); + rcu_read_lock(); em = lookup_extent_mapping(em_tree, start, len); - read_unlock(&em_tree->lock); + rcu_read_unlock(); if (!em) { /* get the big lock and read metadata off disk */ diff --git a/fs/btrfs/relocation.c b/fs/btrfs/relocation.c index cfb5543..7eb4685 100644 --- a/fs/btrfs/relocation.c +++ b/fs/btrfs/relocation.c @@ -2899,9 +2899,9 @@ int setup_extent_mapping(struct inode *inode, u64 start, u64 end, lock_extent(&BTRFS_I(inode)->io_tree, start, end, GFP_NOFS); while (1) { - write_lock(&em_tree->lock); + mutex_lock(&em_tree->lock); ret = add_extent_mapping(em_tree, em); - write_unlock(&em_tree->lock); + mutex_unlock(&em_tree->lock); if (ret != -EEXIST) { free_extent_map(em); break; diff --git a/fs/btrfs/scrub.c b/fs/btrfs/scrub.c index ddf2c90..5aec748 100644 --- a/fs/btrfs/scrub.c +++ b/fs/btrfs/scrub.c @@ -1374,9 +1374,9 @@ static noinline_for_stack int scrub_chunk(struct scrub_dev *sdev, int i; int ret = -EINVAL; - read_lock(&map_tree->map_tree.lock); + rcu_read_lock(); em = lookup_extent_mapping(&map_tree->map_tree, chunk_offset, 1); - read_unlock(&map_tree->map_tree.lock); + rcu_read_unlock(); if (!em) return -EINVAL; diff --git a/fs/btrfs/skiplist.c b/fs/btrfs/skiplist.c index c803478..621eb7b 100644 --- a/fs/btrfs/skiplist.c +++ b/fs/btrfs/skiplist.c @@ -29,6 +29,7 @@ inline int sl_fill_node(struct sl_node *node, int level, gfp_t mask) struct sl_node **p; struct sl_node **q; int num; + int i; BUG_ON(level > MAXLEVEL); @@ -44,6 +45,9 @@ inline int sl_fill_node(struct sl_node *node, int level, gfp_t mask) return -ENOMEM; } + for (i = 0; i < num; i++) + p[i] = q[i] = NULL; + node->next = p; node->prev = q; node->level = level; @@ -62,7 +66,7 @@ inline void sl_link_node(struct sl_node *node, struct sl_node **backlook, node->next[i] = q; node->prev[i] = p; - p->next[i] = node; + rcu_assign_pointer(p->next[i], node); q->prev[i] = node; i++; @@ -78,14 +82,13 @@ void sl_erase(struct sl_node *node, struct sl_list *list) level = node->level; - for (i = 0; i <= level; i++) { + for (i = level; i >= 0; i--) { prev = node->prev[i]; next = node->next[i]; prev->next[i] = next; next->prev[i] = prev; - node->next[i] = node; - node->prev[i] = node; + node->prev[i] = NULL; } head = list->head; diff --git a/fs/btrfs/skiplist.h b/fs/btrfs/skiplist.h index 3e414b5..2ae997d 100644 --- a/fs/btrfs/skiplist.h +++ b/fs/btrfs/skiplist.h @@ -102,41 +102,48 @@ struct sl_node *sl_insert_node(struct sl_list *list, u64 offset, #define _SKIPLIST_H #include <linux/random.h> +#include <linux/rcupdate.h> #define MAXLEVEL 16 /* double p = 0.25; */ struct sl_node { - struct sl_node **next; - struct sl_node **prev; + struct sl_node __rcu **next; + struct sl_node __rcu **prev; + struct rcu_head rcu_head; unsigned int level; unsigned int head:1; }; struct sl_list { - struct sl_node *head; - struct sl_node *h_next[MAXLEVEL]; - struct sl_node *h_prev[MAXLEVEL]; + struct sl_node __rcu *head; + struct sl_node __rcu *h_next[MAXLEVEL]; + struct sl_node __rcu *h_prev[MAXLEVEL]; unsigned int level; }; -#define sl_entry(ptr, type, member) container_of(ptr, type, member) +#define sl_entry(ptr, type, member) \ + ({ \ + typeof(*ptr) __rcu *__ptr = (typeof(*ptr) __rcu __force *)ptr; \ + container_of((typeof(ptr))rcu_dereference(__ptr), \ + type, member); \ + }) static inline int sl_empty(const struct sl_node *head) { - return head->next[0] == head; + return (rcu_dereference(head->next[0]) == head); } static inline struct sl_node *__sl_next_with_level(struct sl_node *node, int level) { - return node->next[level]; + return rcu_dereference(node->next[level]); } static inline struct sl_node *__sl_prev_with_level(struct sl_node *node, int level) { - return node->prev[level]; + return rcu_dereference(node->prev[level]); } static inline struct sl_node *sl_next(struct sl_node *node) diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c index adaac9e..acc86b4 100644 --- a/fs/btrfs/volumes.c +++ b/fs/btrfs/volumes.c @@ -1955,9 +1955,9 @@ static int btrfs_relocate_chunk(struct btrfs_root *root, * step two, delete the device extents and the * chunk tree entries */ - read_lock(&em_tree->lock); + rcu_read_lock(); em = lookup_extent_mapping(em_tree, chunk_offset, 1); - read_unlock(&em_tree->lock); + rcu_read_unlock(); BUG_ON(em->start > chunk_offset || em->start + em->len < chunk_offset); @@ -1988,9 +1988,9 @@ static int btrfs_relocate_chunk(struct btrfs_root *root, ret = btrfs_remove_block_group(trans, extent_root, chunk_offset); BUG_ON(ret); - write_lock(&em_tree->lock); + mutex_lock(&em_tree->lock); remove_extent_mapping(em_tree, em); - write_unlock(&em_tree->lock); + mutex_unlock(&em_tree->lock); kfree(map); em->bdev = NULL; @@ -2589,9 +2589,9 @@ static int __btrfs_alloc_chunk(struct btrfs_trans_handle *trans, em->block_len = em->len; em_tree = &extent_root->fs_info->mapping_tree.map_tree; - write_lock(&em_tree->lock); + mutex_lock(&em_tree->lock); ret = add_extent_mapping(em_tree, em); - write_unlock(&em_tree->lock); + mutex_unlock(&em_tree->lock); BUG_ON(ret); free_extent_map(em); @@ -2800,9 +2800,9 @@ int btrfs_chunk_readonly(struct btrfs_root *root, u64 chunk_offset) int readonly = 0; int i; - read_lock(&map_tree->map_tree.lock); + rcu_read_lock(); em = lookup_extent_mapping(&map_tree->map_tree, chunk_offset, 1); - read_unlock(&map_tree->map_tree.lock); + rcu_read_unlock(); if (!em) return 1; @@ -2854,9 +2854,9 @@ int btrfs_num_copies(struct btrfs_mapping_tree *map_tree, u64 logical, u64 len) struct extent_map_tree *em_tree = &map_tree->map_tree; int ret; - read_lock(&em_tree->lock); + rcu_read_lock(); em = lookup_extent_mapping(em_tree, logical, len); - read_unlock(&em_tree->lock); + rcu_read_unlock(); BUG_ON(!em); BUG_ON(em->start > logical || em->start + em->len < logical); @@ -2921,9 +2921,9 @@ again: atomic_set(&bbio->error, 0); } - read_lock(&em_tree->lock); + rcu_read_lock(); em = lookup_extent_mapping(em_tree, logical, *length); - read_unlock(&em_tree->lock); + rcu_read_unlock(); if (!em) { printk(KERN_CRIT "unable to find logical %llu len %llu\n", @@ -3187,9 +3187,9 @@ int btrfs_rmap_block(struct btrfs_mapping_tree *map_tree, u64 stripe_nr; int i, j, nr = 0; - read_lock(&em_tree->lock); + rcu_read_lock(); em = lookup_extent_mapping(em_tree, chunk_start, 1); - read_unlock(&em_tree->lock); + rcu_read_unlock(); BUG_ON(!em || em->start != chunk_start); map = (struct map_lookup *)em->bdev; @@ -3472,9 +3472,9 @@ static int read_one_chunk(struct btrfs_root *root, struct btrfs_key *key, logical = key->offset; length = btrfs_chunk_length(leaf, chunk); - read_lock(&map_tree->map_tree.lock); + rcu_read_lock(); em = lookup_extent_mapping(&map_tree->map_tree, logical, 1); - read_unlock(&map_tree->map_tree.lock); + rcu_read_unlock(); /* already mapped? */ if (em && em->start <= logical && em->start + em->len > logical) { @@ -3533,9 +3533,9 @@ static int read_one_chunk(struct btrfs_root *root, struct btrfs_key *key, map->stripes[i].dev->in_fs_metadata = 1; } - write_lock(&map_tree->map_tree.lock); + mutex_lock(&map_tree->map_tree.lock); ret = add_extent_mapping(&map_tree->map_tree, em); - write_unlock(&map_tree->map_tree.lock); + mutex_unlock(&map_tree->map_tree.lock); BUG_ON(ret); free_extent_map(em); -- 1.6.5.2 -- To unsubscribe from this list: send the line "unsubscribe linux-btrfs" in the body of a message to [email protected] More majordomo info at http://vger.kernel.org/majordomo-info.html
