Commit 727011e0 authored by Chris Mason's avatar Chris Mason

Btrfs: allow metadata blocks larger than the page size

A few years ago the btrfs code to support blocks lager than
the page size was disabled to fix a few corner cases in the
page cache handling.  This fixes the code to properly support
large metadata blocks again.

Since current kernels will crash early and often with larger
metadata blocks, this adds an incompat bit so that older kernels
can't mount it.

This also does away with different blocksizes for nodes and leaves.
You get a single block size for all tree blocks.
Signed-off-by: default avatarChris Mason <chris.mason@oracle.com>
parent 81c9ad23
...@@ -137,6 +137,12 @@ struct btrfs_ordered_sum; ...@@ -137,6 +137,12 @@ struct btrfs_ordered_sum;
#define BTRFS_EMPTY_SUBVOL_DIR_OBJECTID 2 #define BTRFS_EMPTY_SUBVOL_DIR_OBJECTID 2
/*
* the max metadata block size. This limit is somewhat artificial,
* but the memmove costs go through the roof for larger blocks.
*/
#define BTRFS_MAX_METADATA_BLOCKSIZE 65536
/* /*
* we can actually store much bigger names, but lets not confuse the rest * we can actually store much bigger names, but lets not confuse the rest
* of linux * of linux
...@@ -461,6 +467,19 @@ struct btrfs_super_block { ...@@ -461,6 +467,19 @@ struct btrfs_super_block {
#define BTRFS_FEATURE_INCOMPAT_DEFAULT_SUBVOL (1ULL << 1) #define BTRFS_FEATURE_INCOMPAT_DEFAULT_SUBVOL (1ULL << 1)
#define BTRFS_FEATURE_INCOMPAT_MIXED_GROUPS (1ULL << 2) #define BTRFS_FEATURE_INCOMPAT_MIXED_GROUPS (1ULL << 2)
#define BTRFS_FEATURE_INCOMPAT_COMPRESS_LZO (1ULL << 3) #define BTRFS_FEATURE_INCOMPAT_COMPRESS_LZO (1ULL << 3)
/*
* some patches floated around with a second compression method
* lets save that incompat here for when they do get in
* Note we don't actually support it, we're just reserving the
* number
*/
#define BTRFS_FEATURE_INCOMPAT_COMPRESS_LZOv2 (1ULL << 4)
/*
* older kernels tried to do bigger metadata blocks, but the
* code was pretty buggy. Lets not let them try anymore.
*/
#define BTRFS_FEATURE_INCOMPAT_BIG_METADATA (1ULL << 5)
#define BTRFS_FEATURE_COMPAT_SUPP 0ULL #define BTRFS_FEATURE_COMPAT_SUPP 0ULL
#define BTRFS_FEATURE_COMPAT_RO_SUPP 0ULL #define BTRFS_FEATURE_COMPAT_RO_SUPP 0ULL
...@@ -468,6 +487,7 @@ struct btrfs_super_block { ...@@ -468,6 +487,7 @@ struct btrfs_super_block {
(BTRFS_FEATURE_INCOMPAT_MIXED_BACKREF | \ (BTRFS_FEATURE_INCOMPAT_MIXED_BACKREF | \
BTRFS_FEATURE_INCOMPAT_DEFAULT_SUBVOL | \ BTRFS_FEATURE_INCOMPAT_DEFAULT_SUBVOL | \
BTRFS_FEATURE_INCOMPAT_MIXED_GROUPS | \ BTRFS_FEATURE_INCOMPAT_MIXED_GROUPS | \
BTRFS_FEATURE_INCOMPAT_BIG_METADATA | \
BTRFS_FEATURE_INCOMPAT_COMPRESS_LZO) BTRFS_FEATURE_INCOMPAT_COMPRESS_LZO)
/* /*
...@@ -1555,14 +1575,14 @@ void btrfs_set_##name(struct extent_buffer *eb, type *s, u##bits val); ...@@ -1555,14 +1575,14 @@ void btrfs_set_##name(struct extent_buffer *eb, type *s, u##bits val);
#define BTRFS_SETGET_HEADER_FUNCS(name, type, member, bits) \ #define BTRFS_SETGET_HEADER_FUNCS(name, type, member, bits) \
static inline u##bits btrfs_##name(struct extent_buffer *eb) \ static inline u##bits btrfs_##name(struct extent_buffer *eb) \
{ \ { \
type *p = page_address(eb->first_page); \ type *p = page_address(eb->pages[0]); \
u##bits res = le##bits##_to_cpu(p->member); \ u##bits res = le##bits##_to_cpu(p->member); \
return res; \ return res; \
} \ } \
static inline void btrfs_set_##name(struct extent_buffer *eb, \ static inline void btrfs_set_##name(struct extent_buffer *eb, \
u##bits val) \ u##bits val) \
{ \ { \
type *p = page_address(eb->first_page); \ type *p = page_address(eb->pages[0]); \
p->member = cpu_to_le##bits(val); \ p->member = cpu_to_le##bits(val); \
} }
......
This diff is collapsed.
...@@ -3548,26 +3548,7 @@ int extent_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo, ...@@ -3548,26 +3548,7 @@ int extent_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo,
inline struct page *extent_buffer_page(struct extent_buffer *eb, inline struct page *extent_buffer_page(struct extent_buffer *eb,
unsigned long i) unsigned long i)
{ {
struct page *p; return eb->pages[i];
struct address_space *mapping;
if (i == 0)
return eb->first_page;
i += eb->start >> PAGE_CACHE_SHIFT;
mapping = eb->first_page->mapping;
if (!mapping)
return NULL;
/*
* extent_buffer_page is only called after pinning the page
* by increasing the reference count. So we know the page must
* be in the radix tree.
*/
rcu_read_lock();
p = radix_tree_lookup(&mapping->page_tree, i);
rcu_read_unlock();
return p;
} }
inline unsigned long num_extent_pages(u64 start, u64 len) inline unsigned long num_extent_pages(u64 start, u64 len)
...@@ -3576,6 +3557,19 @@ inline unsigned long num_extent_pages(u64 start, u64 len) ...@@ -3576,6 +3557,19 @@ inline unsigned long num_extent_pages(u64 start, u64 len)
(start >> PAGE_CACHE_SHIFT); (start >> PAGE_CACHE_SHIFT);
} }
static void __free_extent_buffer(struct extent_buffer *eb)
{
#if LEAK_DEBUG
unsigned long flags;
spin_lock_irqsave(&leak_lock, flags);
list_del(&eb->leak_list);
spin_unlock_irqrestore(&leak_lock, flags);
#endif
if (eb->pages && eb->pages != eb->inline_pages)
kfree(eb->pages);
kmem_cache_free(extent_buffer_cache, eb);
}
static struct extent_buffer *__alloc_extent_buffer(struct extent_io_tree *tree, static struct extent_buffer *__alloc_extent_buffer(struct extent_io_tree *tree,
u64 start, u64 start,
unsigned long len, unsigned long len,
...@@ -3608,21 +3602,25 @@ static struct extent_buffer *__alloc_extent_buffer(struct extent_io_tree *tree, ...@@ -3608,21 +3602,25 @@ static struct extent_buffer *__alloc_extent_buffer(struct extent_io_tree *tree,
spin_unlock_irqrestore(&leak_lock, flags); spin_unlock_irqrestore(&leak_lock, flags);
#endif #endif
atomic_set(&eb->refs, 1); atomic_set(&eb->refs, 1);
atomic_set(&eb->pages_reading, 0);
if (len > MAX_INLINE_EXTENT_BUFFER_SIZE) {
struct page **pages;
int num_pages = (len + PAGE_CACHE_SIZE - 1) >>
PAGE_CACHE_SHIFT;
pages = kzalloc(num_pages, mask);
if (!pages) {
__free_extent_buffer(eb);
return NULL;
}
eb->pages = pages;
} else {
eb->pages = eb->inline_pages;
}
return eb; return eb;
} }
static void __free_extent_buffer(struct extent_buffer *eb)
{
#if LEAK_DEBUG
unsigned long flags;
spin_lock_irqsave(&leak_lock, flags);
list_del(&eb->leak_list);
spin_unlock_irqrestore(&leak_lock, flags);
#endif
kmem_cache_free(extent_buffer_cache, eb);
}
/* /*
* Helper for releasing extent buffer page. * Helper for releasing extent buffer page.
*/ */
...@@ -3632,9 +3630,6 @@ static void btrfs_release_extent_buffer_page(struct extent_buffer *eb, ...@@ -3632,9 +3630,6 @@ static void btrfs_release_extent_buffer_page(struct extent_buffer *eb,
unsigned long index; unsigned long index;
struct page *page; struct page *page;
if (!eb->first_page)
return;
index = num_extent_pages(eb->start, eb->len); index = num_extent_pages(eb->start, eb->len);
if (start_idx >= index) if (start_idx >= index)
return; return;
...@@ -3657,8 +3652,7 @@ static inline void btrfs_release_extent_buffer(struct extent_buffer *eb) ...@@ -3657,8 +3652,7 @@ static inline void btrfs_release_extent_buffer(struct extent_buffer *eb)
} }
struct extent_buffer *alloc_extent_buffer(struct extent_io_tree *tree, struct extent_buffer *alloc_extent_buffer(struct extent_io_tree *tree,
u64 start, unsigned long len, u64 start, unsigned long len)
struct page *page0)
{ {
unsigned long num_pages = num_extent_pages(start, len); unsigned long num_pages = num_extent_pages(start, len);
unsigned long i; unsigned long i;
...@@ -3674,7 +3668,7 @@ struct extent_buffer *alloc_extent_buffer(struct extent_io_tree *tree, ...@@ -3674,7 +3668,7 @@ struct extent_buffer *alloc_extent_buffer(struct extent_io_tree *tree,
eb = radix_tree_lookup(&tree->buffer, start >> PAGE_CACHE_SHIFT); eb = radix_tree_lookup(&tree->buffer, start >> PAGE_CACHE_SHIFT);
if (eb && atomic_inc_not_zero(&eb->refs)) { if (eb && atomic_inc_not_zero(&eb->refs)) {
rcu_read_unlock(); rcu_read_unlock();
mark_page_accessed(eb->first_page); mark_page_accessed(eb->pages[0]);
return eb; return eb;
} }
rcu_read_unlock(); rcu_read_unlock();
...@@ -3683,32 +3677,14 @@ struct extent_buffer *alloc_extent_buffer(struct extent_io_tree *tree, ...@@ -3683,32 +3677,14 @@ struct extent_buffer *alloc_extent_buffer(struct extent_io_tree *tree,
if (!eb) if (!eb)
return NULL; return NULL;
if (page0) { for (i = 0; i < num_pages; i++, index++) {
eb->first_page = page0;
i = 1;
index++;
page_cache_get(page0);
mark_page_accessed(page0);
set_page_extent_mapped(page0);
set_page_extent_head(page0, len);
uptodate = PageUptodate(page0);
} else {
i = 0;
}
for (; i < num_pages; i++, index++) {
p = find_or_create_page(mapping, index, GFP_NOFS); p = find_or_create_page(mapping, index, GFP_NOFS);
if (!p) { if (!p) {
WARN_ON(1); WARN_ON(1);
goto free_eb; goto free_eb;
} }
set_page_extent_mapped(p);
mark_page_accessed(p); mark_page_accessed(p);
if (i == 0) { eb->pages[i] = p;
eb->first_page = p;
set_page_extent_head(p, len);
} else {
set_page_private(p, EXTENT_PAGE_PRIVATE);
}
if (!PageUptodate(p)) if (!PageUptodate(p))
uptodate = 0; uptodate = 0;
...@@ -3716,8 +3692,6 @@ struct extent_buffer *alloc_extent_buffer(struct extent_io_tree *tree, ...@@ -3716,8 +3692,6 @@ struct extent_buffer *alloc_extent_buffer(struct extent_io_tree *tree,
* see below about how we avoid a nasty race with release page * see below about how we avoid a nasty race with release page
* and why we unlock later * and why we unlock later
*/ */
if (i != 0)
unlock_page(p);
} }
if (uptodate) if (uptodate)
set_bit(EXTENT_BUFFER_UPTODATE, &eb->bflags); set_bit(EXTENT_BUFFER_UPTODATE, &eb->bflags);
...@@ -3751,15 +3725,23 @@ struct extent_buffer *alloc_extent_buffer(struct extent_io_tree *tree, ...@@ -3751,15 +3725,23 @@ struct extent_buffer *alloc_extent_buffer(struct extent_io_tree *tree,
* after the extent buffer is in the radix tree so * after the extent buffer is in the radix tree so
* it doesn't get lost * it doesn't get lost
*/ */
set_page_extent_mapped(eb->first_page); set_page_extent_mapped(eb->pages[0]);
set_page_extent_head(eb->first_page, eb->len); set_page_extent_head(eb->pages[0], eb->len);
if (!page0) SetPageChecked(eb->pages[0]);
unlock_page(eb->first_page); for (i = 1; i < num_pages; i++) {
p = extent_buffer_page(eb, i);
set_page_extent_mapped(p);
ClearPageChecked(p);
unlock_page(p);
}
unlock_page(eb->pages[0]);
return eb; return eb;
free_eb: free_eb:
if (eb->first_page && !page0) for (i = 0; i < num_pages; i++) {
unlock_page(eb->first_page); if (eb->pages[i])
unlock_page(eb->pages[i]);
}
if (!atomic_dec_and_test(&eb->refs)) if (!atomic_dec_and_test(&eb->refs))
return exists; return exists;
...@@ -3776,7 +3758,7 @@ struct extent_buffer *find_extent_buffer(struct extent_io_tree *tree, ...@@ -3776,7 +3758,7 @@ struct extent_buffer *find_extent_buffer(struct extent_io_tree *tree,
eb = radix_tree_lookup(&tree->buffer, start >> PAGE_CACHE_SHIFT); eb = radix_tree_lookup(&tree->buffer, start >> PAGE_CACHE_SHIFT);
if (eb && atomic_inc_not_zero(&eb->refs)) { if (eb && atomic_inc_not_zero(&eb->refs)) {
rcu_read_unlock(); rcu_read_unlock();
mark_page_accessed(eb->first_page); mark_page_accessed(eb->pages[0]);
return eb; return eb;
} }
rcu_read_unlock(); rcu_read_unlock();
...@@ -3981,8 +3963,8 @@ int read_extent_buffer_pages(struct extent_io_tree *tree, ...@@ -3981,8 +3963,8 @@ int read_extent_buffer_pages(struct extent_io_tree *tree,
int ret = 0; int ret = 0;
int locked_pages = 0; int locked_pages = 0;
int all_uptodate = 1; int all_uptodate = 1;
int inc_all_pages = 0;
unsigned long num_pages; unsigned long num_pages;
unsigned long num_reads = 0;
struct bio *bio = NULL; struct bio *bio = NULL;
unsigned long bio_flags = 0; unsigned long bio_flags = 0;
...@@ -4014,8 +3996,10 @@ int read_extent_buffer_pages(struct extent_io_tree *tree, ...@@ -4014,8 +3996,10 @@ int read_extent_buffer_pages(struct extent_io_tree *tree,
lock_page(page); lock_page(page);
} }
locked_pages++; locked_pages++;
if (!PageUptodate(page)) if (!PageUptodate(page)) {
num_reads++;
all_uptodate = 0; all_uptodate = 0;
}
} }
if (all_uptodate) { if (all_uptodate) {
if (start_i == 0) if (start_i == 0)
...@@ -4023,20 +4007,13 @@ int read_extent_buffer_pages(struct extent_io_tree *tree, ...@@ -4023,20 +4007,13 @@ int read_extent_buffer_pages(struct extent_io_tree *tree,
goto unlock_exit; goto unlock_exit;
} }
atomic_set(&eb->pages_reading, num_reads);
for (i = start_i; i < num_pages; i++) { for (i = start_i; i < num_pages; i++) {
page = extent_buffer_page(eb, i); page = extent_buffer_page(eb, i);
WARN_ON(!PagePrivate(page));
set_page_extent_mapped(page); set_page_extent_mapped(page);
if (i == 0) if (i == 0)
set_page_extent_head(page, eb->len); set_page_extent_head(page, eb->len);
if (inc_all_pages)
page_cache_get(page);
if (!PageUptodate(page)) { if (!PageUptodate(page)) {
if (start_i == 0)
inc_all_pages = 1;
ClearPageError(page); ClearPageError(page);
err = __extent_read_full_page(tree, page, err = __extent_read_full_page(tree, page,
get_extent, &bio, get_extent, &bio,
...@@ -4304,15 +4281,20 @@ static void copy_pages(struct page *dst_page, struct page *src_page, ...@@ -4304,15 +4281,20 @@ static void copy_pages(struct page *dst_page, struct page *src_page,
{ {
char *dst_kaddr = page_address(dst_page); char *dst_kaddr = page_address(dst_page);
char *src_kaddr; char *src_kaddr;
int must_memmove = 0;
if (dst_page != src_page) { if (dst_page != src_page) {
src_kaddr = page_address(src_page); src_kaddr = page_address(src_page);
} else { } else {
src_kaddr = dst_kaddr; src_kaddr = dst_kaddr;
BUG_ON(areas_overlap(src_off, dst_off, len)); if (areas_overlap(src_off, dst_off, len))
must_memmove = 1;
} }
memcpy(dst_kaddr + dst_off, src_kaddr + src_off, len); if (must_memmove)
memmove(dst_kaddr + dst_off, src_kaddr + src_off, len);
else
memcpy(dst_kaddr + dst_off, src_kaddr + src_off, len);
} }
void memcpy_extent_buffer(struct extent_buffer *dst, unsigned long dst_offset, void memcpy_extent_buffer(struct extent_buffer *dst, unsigned long dst_offset,
...@@ -4382,7 +4364,7 @@ void memmove_extent_buffer(struct extent_buffer *dst, unsigned long dst_offset, ...@@ -4382,7 +4364,7 @@ void memmove_extent_buffer(struct extent_buffer *dst, unsigned long dst_offset,
"len %lu len %lu\n", dst_offset, len, dst->len); "len %lu len %lu\n", dst_offset, len, dst->len);
BUG_ON(1); BUG_ON(1);
} }
if (!areas_overlap(src_offset, dst_offset, len)) { if (dst_offset < src_offset) {
memcpy_extent_buffer(dst, dst_offset, src_offset, len); memcpy_extent_buffer(dst, dst_offset, src_offset, len);
return; return;
} }
...@@ -4429,7 +4411,8 @@ int try_release_extent_buffer(struct extent_io_tree *tree, struct page *page) ...@@ -4429,7 +4411,8 @@ int try_release_extent_buffer(struct extent_io_tree *tree, struct page *page)
return ret; return ret;
} }
if (test_bit(EXTENT_BUFFER_DIRTY, &eb->bflags)) { if (atomic_read(&eb->refs) > 1 ||
test_bit(EXTENT_BUFFER_DIRTY, &eb->bflags)) {
ret = 0; ret = 0;
goto out; goto out;
} }
...@@ -4442,7 +4425,6 @@ int try_release_extent_buffer(struct extent_io_tree *tree, struct page *page) ...@@ -4442,7 +4425,6 @@ int try_release_extent_buffer(struct extent_io_tree *tree, struct page *page)
ret = 0; ret = 0;
goto out; goto out;
} }
radix_tree_delete(&tree->buffer, start >> PAGE_CACHE_SHIFT); radix_tree_delete(&tree->buffer, start >> PAGE_CACHE_SHIFT);
out: out:
spin_unlock(&tree->buffer_lock); spin_unlock(&tree->buffer_lock);
......
...@@ -119,16 +119,18 @@ struct extent_state { ...@@ -119,16 +119,18 @@ struct extent_state {
struct list_head leak_list; struct list_head leak_list;
}; };
#define INLINE_EXTENT_BUFFER_PAGES 16
#define MAX_INLINE_EXTENT_BUFFER_SIZE (INLINE_EXTENT_BUFFER_PAGES * PAGE_CACHE_SIZE)
struct extent_buffer { struct extent_buffer {
u64 start; u64 start;
unsigned long len; unsigned long len;
unsigned long map_start; unsigned long map_start;
unsigned long map_len; unsigned long map_len;
struct page *first_page;
unsigned long bflags; unsigned long bflags;
atomic_t refs;
atomic_t pages_reading;
struct list_head leak_list; struct list_head leak_list;
struct rcu_head rcu_head; struct rcu_head rcu_head;
atomic_t refs;
pid_t lock_owner; pid_t lock_owner;
/* count of read lock holders on the extent buffer */ /* count of read lock holders on the extent buffer */
...@@ -152,6 +154,9 @@ struct extent_buffer { ...@@ -152,6 +154,9 @@ struct extent_buffer {
* to unlock * to unlock
*/ */
wait_queue_head_t read_lock_wq; wait_queue_head_t read_lock_wq;
wait_queue_head_t lock_wq;
struct page *inline_pages[INLINE_EXTENT_BUFFER_PAGES];
struct page **pages;
}; };
static inline void extent_set_compress_type(unsigned long *bio_flags, static inline void extent_set_compress_type(unsigned long *bio_flags,
...@@ -251,8 +256,7 @@ int get_state_private(struct extent_io_tree *tree, u64 start, u64 *private); ...@@ -251,8 +256,7 @@ int get_state_private(struct extent_io_tree *tree, u64 start, u64 *private);
void set_page_extent_mapped(struct page *page); void set_page_extent_mapped(struct page *page);
struct extent_buffer *alloc_extent_buffer(struct extent_io_tree *tree, struct extent_buffer *alloc_extent_buffer(struct extent_io_tree *tree,
u64 start, unsigned long len, u64 start, unsigned long len);
struct page *page0);
struct extent_buffer *find_extent_buffer(struct extent_io_tree *tree, struct extent_buffer *find_extent_buffer(struct extent_io_tree *tree,
u64 start, unsigned long len); u64 start, unsigned long len);
void free_extent_buffer(struct extent_buffer *eb); void free_extent_buffer(struct extent_buffer *eb);
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include "ctree.h" #include "ctree.h"
#include "disk-io.h" #include "disk-io.h"
#include "transaction.h" #include "transaction.h"
#include "print-tree.h"
static int find_name_in_backref(struct btrfs_path *path, const char *name, static int find_name_in_backref(struct btrfs_path *path, const char *name,
int name_len, struct btrfs_inode_ref **ref_ret) int name_len, struct btrfs_inode_ref **ref_ret)
......
...@@ -4384,7 +4384,7 @@ int btrfs_read_sys_array(struct btrfs_root *root) ...@@ -4384,7 +4384,7 @@ int btrfs_read_sys_array(struct btrfs_root *root)
* to silence the warning eg. on PowerPC 64. * to silence the warning eg. on PowerPC 64.
*/ */
if (PAGE_CACHE_SIZE > BTRFS_SUPER_INFO_SIZE) if (PAGE_CACHE_SIZE > BTRFS_SUPER_INFO_SIZE)
SetPageUptodate(sb->first_page); SetPageUptodate(sb->pages[0]);
write_extent_buffer(sb, super_copy, 0, BTRFS_SUPER_INFO_SIZE); write_extent_buffer(sb, super_copy, 0, BTRFS_SUPER_INFO_SIZE);
array_size = btrfs_super_sys_array_size(super_copy); array_size = btrfs_super_sys_array_size(super_copy);
......
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