Commit 86479a04 authored by Chris Mason's avatar Chris Mason Committed by David Woodhouse
Browse files

Add support for defragging files via btrfsctl -d. Avoid OOM on extent tree


defrag.

Signed-off-by: default avatarChris Mason <chris.mason@oracle.com>
parent 8e21528f
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -217,6 +217,9 @@ int btrfs_realloc_node(struct btrfs_trans_handle *trans,
		       root->fs_info->generation);
		WARN_ON(1);
	}
	if (buffer_defrag_done(parent))
		return 0;

	parent_node = btrfs_buffer_node(parent);
	parent_nritems = btrfs_header_nritems(&parent_node->header);
	parent_level = btrfs_header_level(&parent_node->header);
@@ -274,6 +277,7 @@ int btrfs_realloc_node(struct btrfs_trans_handle *trans,
		*last_ret = search_start;
		if (parent_level == 1)
			clear_buffer_defrag(tmp_bh);
		set_buffer_defrag_done(tmp_bh);
		brelse(tmp_bh);
	}
	return err;
+2 −0
Original line number Diff line number Diff line
@@ -26,9 +26,11 @@
enum btrfs_bh_state_bits {
	BH_Checked = BH_PrivateStart,
	BH_Defrag,
	BH_DefragDone,
};
BUFFER_FNS(Checked, checked);
BUFFER_FNS(Defrag, defrag);
BUFFER_FNS(DefragDone, defrag_done);

static inline struct btrfs_node *btrfs_buffer_node(struct buffer_head *bh)
{
+10 −6
Original line number Diff line number Diff line
@@ -10,6 +10,12 @@
#include <linux/blkdev.h>
#include "extent_map.h"

/* temporary define until extent_map moves out of btrfs */
struct kmem_cache *btrfs_cache_create(const char *name, size_t size,
				       unsigned long extra_flags,
				       void (*ctor)(void *, struct kmem_cache *,
						    unsigned long));

static struct kmem_cache *extent_map_cache;
static struct kmem_cache *extent_state_cache;

@@ -32,14 +38,12 @@ struct tree_entry {

void __init extent_map_init(void)
{
	extent_map_cache = kmem_cache_create("extent_map",
					    sizeof(struct extent_map), 0,
					    SLAB_RECLAIM_ACCOUNT |
	extent_map_cache = btrfs_cache_create("extent_map",
					    sizeof(struct extent_map),
					    SLAB_DESTROY_BY_RCU,
					    NULL);
	extent_state_cache = kmem_cache_create("extent_state",
					    sizeof(struct extent_state), 0,
					    SLAB_RECLAIM_ACCOUNT |
	extent_state_cache = btrfs_cache_create("extent_state",
					    sizeof(struct extent_state),
					    SLAB_DESTROY_BY_RCU,
					    NULL);
}
+81 −12
Original line number Diff line number Diff line
@@ -1904,6 +1904,70 @@ fail:
	return ret;
}

static unsigned long force_ra(struct address_space *mapping,
			      struct file_ra_state *ra, struct file *file,
			      pgoff_t offset, pgoff_t last_index)
{
	pgoff_t req_size;

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,23)
	req_size = last_index - offset + 1;
	offset = page_cache_readahead(mapping, ra, file, offset, req_size);
	return offset;
#else
	req_size = min(last_index - offset + 1, (pgoff_t)128);
	page_cache_sync_readahead(mapping, ra, file, offset, req_size);
	return offset + req_size;
#endif
}

int btrfs_defrag_file(struct file *file) {
	struct inode *inode = file->f_path.dentry->d_inode;
	struct extent_map_tree *em_tree = &BTRFS_I(inode)->extent_tree;
	struct page *page;
	unsigned long last_index;
	unsigned long ra_index = 0;
	u64 page_start;
	u64 page_end;
	unsigned long i;

	mutex_lock(&inode->i_mutex);
	last_index = inode->i_size >> PAGE_CACHE_SHIFT;
	for (i = 0; i <= last_index; i++) {
		if (i == ra_index) {
			ra_index = force_ra(inode->i_mapping, &file->f_ra,
					    file, ra_index, last_index);
		}
		page = grab_cache_page(inode->i_mapping, i);
		if (!page)
			goto out_unlock;
		if (!PageUptodate(page)) {
			btrfs_readpage(NULL, page);
			lock_page(page);
			if (!PageUptodate(page)) {
				unlock_page(page);
				page_cache_release(page);
				goto out_unlock;
			}
		}
		page_start = page->index << PAGE_CACHE_SHIFT;
		page_end = page_start + PAGE_CACHE_SIZE - 1;

		lock_extent(em_tree, page_start, page_end, GFP_NOFS);
		set_extent_delalloc(em_tree, page_start,
				    page_end, GFP_NOFS);
		unlock_extent(em_tree, page_start, page_end, GFP_NOFS);
		set_page_dirty(page);
		unlock_page(page);
		page_cache_release(page);
		balance_dirty_pages_ratelimited_nr(inode->i_mapping, 1);
	}

out_unlock:
	mutex_unlock(&inode->i_mutex);
	return 0;
}

int btrfs_ioctl(struct inode *inode, struct file *filp, unsigned int
		cmd, unsigned long arg)
{
@@ -1948,10 +2012,14 @@ int btrfs_ioctl(struct inode *inode, struct file *filp, unsigned int
		break;

	case BTRFS_IOC_DEFRAG:
		if (S_ISDIR(inode->i_mode)) {
			mutex_lock(&root->fs_info->fs_mutex);
			btrfs_defrag_root(root, 0);
			btrfs_defrag_root(root->fs_info->extent_root, 0);
			mutex_unlock(&root->fs_info->fs_mutex);
		} else if (S_ISREG(inode->i_mode)) {
			btrfs_defrag_file(filp);
		}
		ret = 0;
		break;
	default:
@@ -2018,7 +2086,7 @@ void btrfs_destroy_cachep(void)
		kmem_cache_destroy(btrfs_path_cachep);
}

static struct kmem_cache *cache_create(const char *name, size_t size,
struct kmem_cache *btrfs_cache_create(const char *name, size_t size,
				       unsigned long extra_flags,
				       void (*ctor)(void *, struct kmem_cache *,
						    unsigned long))
@@ -2033,27 +2101,28 @@ static struct kmem_cache *cache_create(const char *name, size_t size,

int btrfs_init_cachep(void)
{
	btrfs_inode_cachep = cache_create("btrfs_inode_cache",
	btrfs_inode_cachep = btrfs_cache_create("btrfs_inode_cache",
					  sizeof(struct btrfs_inode),
					  0, init_once);
	if (!btrfs_inode_cachep)
		goto fail;
	btrfs_trans_handle_cachep = cache_create("btrfs_trans_handle_cache",
	btrfs_trans_handle_cachep =
			btrfs_cache_create("btrfs_trans_handle_cache",
					   sizeof(struct btrfs_trans_handle),
					   0, NULL);
	if (!btrfs_trans_handle_cachep)
		goto fail;
	btrfs_transaction_cachep = cache_create("btrfs_transaction_cache",
	btrfs_transaction_cachep = btrfs_cache_create("btrfs_transaction_cache",
					     sizeof(struct btrfs_transaction),
					     0, NULL);
	if (!btrfs_transaction_cachep)
		goto fail;
	btrfs_path_cachep = cache_create("btrfs_path_cache",
	btrfs_path_cachep = btrfs_cache_create("btrfs_path_cache",
					 sizeof(struct btrfs_transaction),
					 0, NULL);
	if (!btrfs_path_cachep)
		goto fail;
	btrfs_bit_radix_cachep = cache_create("btrfs_radix", 256,
	btrfs_bit_radix_cachep = btrfs_cache_create("btrfs_radix", 256,
					      SLAB_DESTROY_BY_RCU, NULL);
	if (!btrfs_bit_radix_cachep)
		goto fail;
+3 −0
Original line number Diff line number Diff line
@@ -113,6 +113,8 @@ static int defrag_walk_down(struct btrfs_trans_handle *trans,
	}
	WARN_ON(*level < 0);
	WARN_ON(*level >= BTRFS_MAX_LEVEL);
	clear_buffer_defrag(path->nodes[*level]);
	clear_buffer_defrag_done(path->nodes[*level]);
	btrfs_block_release(root, path->nodes[*level]);
	path->nodes[*level] = NULL;
	*level += 1;
@@ -143,6 +145,7 @@ static int defrag_walk_up(struct btrfs_trans_handle *trans,
			return 0;
		} else {
			clear_buffer_defrag(path->nodes[*level]);
			clear_buffer_defrag_done(path->nodes[*level]);
			btrfs_block_release(root, path->nodes[*level]);
			path->nodes[*level] = NULL;
			*level = i + 1;