summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris Mason <chris.mason@oracle.com>2007-09-10 23:58:16 (GMT)
committerDavid Woodhouse <dwmw2@hera.kernel.org>2007-09-10 23:58:16 (GMT)
commit86479a04eef8f304a13aeb8b64bcc8e506a68268 (patch)
treecda7e66d469bcdc2f41144ea91405aeb96d1f777
parent8e21528f87854314792aaef4d279bc9e5a9be997 (diff)
downloadlinux-86479a04eef8f304a13aeb8b64bcc8e506a68268.tar.xz
Add support for defragging files via btrfsctl -d. Avoid OOM on extent tree
defrag. Signed-off-by: Chris Mason <chris.mason@oracle.com>
-rw-r--r--fs/btrfs/ctree.c4
-rw-r--r--fs/btrfs/disk-io.h2
-rw-r--r--fs/btrfs/extent_map.c16
-rw-r--r--fs/btrfs/inode.c93
-rw-r--r--fs/btrfs/tree-defrag.c3
5 files changed, 100 insertions, 18 deletions
diff --git a/fs/btrfs/ctree.c b/fs/btrfs/ctree.c
index 89e282c..b41f48a 100644
--- a/fs/btrfs/ctree.c
+++ b/fs/btrfs/ctree.c
@@ -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;
diff --git a/fs/btrfs/disk-io.h b/fs/btrfs/disk-io.h
index da6bb72..714fa87 100644
--- a/fs/btrfs/disk-io.h
+++ b/fs/btrfs/disk-io.h
@@ -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)
{
diff --git a/fs/btrfs/extent_map.c b/fs/btrfs/extent_map.c
index 8ad6f8e..33f7a18 100644
--- a/fs/btrfs/extent_map.c
+++ b/fs/btrfs/extent_map.c
@@ -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);
}
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 64710fa..6b3e440 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -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:
- 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);
+ 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",
- sizeof(struct btrfs_trans_handle),
- 0, NULL);
+ 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;
diff --git a/fs/btrfs/tree-defrag.c b/fs/btrfs/tree-defrag.c
index 35fd20d..4205971 100644
--- a/fs/btrfs/tree-defrag.c
+++ b/fs/btrfs/tree-defrag.c
@@ -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;