* Boston, MA 021110-1307, USA.
*/
-#include <linux/module.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include "ctree.h"
static struct workqueue_struct *trans_wq;
#define BTRFS_ROOT_TRANS_TAG 0
+#define BTRFS_ROOT_DEFRAG_TAG 1
static void put_transaction(struct btrfs_transaction *transaction)
{
return 0;
}
+static int record_root_in_trans(struct btrfs_root *root)
+{
+ u64 running_trans_id = root->fs_info->running_transaction->transid;
+ if (root->ref_cows && root->last_trans < running_trans_id) {
+ WARN_ON(root == root->fs_info->extent_root);
+ if (root->root_item.refs != 0) {
+ radix_tree_tag_set(&root->fs_info->fs_roots_radix,
+ (unsigned long)root->root_key.objectid,
+ BTRFS_ROOT_TRANS_TAG);
+ radix_tree_tag_set(&root->fs_info->fs_roots_radix,
+ (unsigned long)root->root_key.objectid,
+ BTRFS_ROOT_DEFRAG_TAG);
+ root->commit_root = root->node;
+ get_bh(root->node);
+ } else {
+ WARN_ON(1);
+ }
+ root->last_trans = running_trans_id;
+ }
+ return 0;
+}
+
struct btrfs_trans_handle *btrfs_start_transaction(struct btrfs_root *root,
int num_blocks)
{
struct btrfs_trans_handle *h =
kmem_cache_alloc(btrfs_trans_handle_cachep, GFP_NOFS);
int ret;
- u64 running_trans_id;
mutex_lock(&root->fs_info->trans_mutex);
ret = join_transaction(root);
BUG_ON(ret);
- running_trans_id = root->fs_info->running_transaction->transid;
- if (root != root->fs_info->tree_root && root->last_trans <
- running_trans_id) {
- radix_tree_tag_set(&root->fs_info->fs_roots_radix,
- (unsigned long)root->root_key.objectid,
- BTRFS_ROOT_TRANS_TAG);
- root->commit_root = root->node;
- get_bh(root->node);
- }
- root->last_trans = running_trans_id;
- h->transid = running_trans_id;
+ record_root_in_trans(root);
+ h->transid = root->fs_info->running_transaction->transid;
h->transaction = root->fs_info->running_transaction;
h->blocks_reserved = num_blocks;
h->blocks_used = 0;
mutex_lock(&root->fs_info->trans_mutex);
cur_trans = root->fs_info->running_transaction;
+ WARN_ON(cur_trans != trans->transaction);
WARN_ON(cur_trans->num_writers < 1);
+ cur_trans->num_writers--;
if (waitqueue_active(&cur_trans->writer_wait))
wake_up(&cur_trans->writer_wait);
- cur_trans->num_writers--;
put_transaction(cur_trans);
mutex_unlock(&root->fs_info->trans_mutex);
memset(trans, 0, sizeof(*trans));
gang[i]);
if (!page)
continue;
+ if (PageWriteback(page)) {
+ if (PageDirty(page))
+ wait_on_page_writeback(page);
+ else {
+ unlock_page(page);
+ page_cache_release(page);
+ continue;
+ }
+ }
err = write_one_page(page, 0);
if (err)
werr = err;
struct btrfs_transaction *commit)
{
DEFINE_WAIT(wait);
+ mutex_lock(&root->fs_info->trans_mutex);
while(!commit->commit_done) {
prepare_to_wait(&commit->commit_wait, &wait,
TASK_UNINTERRUPTIBLE);
schedule();
mutex_lock(&root->fs_info->trans_mutex);
}
+ mutex_unlock(&root->fs_info->trans_mutex);
finish_wait(&commit->commit_wait, &wait);
return 0;
}
struct dirty_root {
struct list_head list;
- struct btrfs_key snap_key;
- struct buffer_head *commit_root;
struct btrfs_root *root;
};
+int btrfs_add_dead_root(struct btrfs_root *root, struct list_head *dead_list)
+{
+ struct dirty_root *dirty;
+
+ dirty = kmalloc(sizeof(*dirty), GFP_NOFS);
+ if (!dirty)
+ return -ENOMEM;
+ dirty->root = root;
+ list_add(&dirty->list, dead_list);
+ return 0;
+}
+
static int add_dirty_roots(struct btrfs_trans_handle *trans,
struct radix_tree_root *radix,
struct list_head *list)
struct btrfs_root *root;
int i;
int ret;
- int err;
+ int err = 0;
+ u32 refs;
+
while(1) {
ret = radix_tree_gang_lookup_tag(radix, (void **)gang, 0,
ARRAY_SIZE(gang),
}
dirty = kmalloc(sizeof(*dirty), GFP_NOFS);
BUG_ON(!dirty);
- memcpy(&dirty->snap_key, &root->root_key,
- sizeof(root->root_key));
- dirty->commit_root = root->commit_root;
+ dirty->root = kmalloc(sizeof(*dirty->root), GFP_NOFS);
+ BUG_ON(!dirty->root);
+
+ memset(&root->root_item.drop_progress, 0,
+ sizeof(struct btrfs_disk_key));
+ root->root_item.drop_level = 0;
+
+ memcpy(dirty->root, root, sizeof(*root));
+ dirty->root->node = root->commit_root;
root->commit_root = NULL;
- dirty->root = root;
+
root->root_key.offset = root->fs_info->generation;
btrfs_set_root_blocknr(&root->root_item,
bh_blocknr(root->node));
err = btrfs_insert_root(trans, root->fs_info->tree_root,
&root->root_key,
&root->root_item);
+ if (err)
+ break;
+
+ refs = btrfs_root_refs(&dirty->root->root_item);
+ btrfs_set_root_refs(&dirty->root->root_item, refs - 1);
+ err = btrfs_update_root(trans, root->fs_info->tree_root,
+ &dirty->root->root_key,
+ &dirty->root->root_item);
+
BUG_ON(err);
- list_add(&dirty->list, list);
+ if (refs == 1) {
+ list_add(&dirty->list, list);
+ } else {
+ WARN_ON(1);
+ kfree(dirty->root);
+ kfree(dirty);
+ }
}
}
- return 0;
+ return err;
+}
+
+int btrfs_defrag_dirty_roots(struct btrfs_fs_info *info)
+{
+ struct btrfs_root *gang[1];
+ struct btrfs_root *root;
+ struct btrfs_root *tree_root = info->tree_root;
+ struct btrfs_trans_handle *trans;
+ int i;
+ int ret;
+ int err = 0;
+ u64 last = 0;
+
+ trans = btrfs_start_transaction(tree_root, 1);
+ while(1) {
+ ret = radix_tree_gang_lookup_tag(&info->fs_roots_radix,
+ (void **)gang, last,
+ ARRAY_SIZE(gang),
+ BTRFS_ROOT_DEFRAG_TAG);
+ if (ret == 0)
+ break;
+ for (i = 0; i < ret; i++) {
+ root = gang[i];
+ last = root->root_key.objectid + 1;
+ radix_tree_tag_clear(&info->fs_roots_radix,
+ (unsigned long)root->root_key.objectid,
+ BTRFS_ROOT_DEFRAG_TAG);
+ if (root->defrag_running)
+ continue;
+
+ while (1) {
+ mutex_lock(&root->fs_info->trans_mutex);
+ record_root_in_trans(root);
+ mutex_unlock(&root->fs_info->trans_mutex);
+
+ root->defrag_running = 1;
+ err = btrfs_defrag_leaves(trans, root, 1);
+ btrfs_end_transaction(trans, tree_root);
+ mutex_unlock(&info->fs_mutex);
+
+ btrfs_btree_balance_dirty(root);
+ cond_resched();
+
+ mutex_lock(&info->fs_mutex);
+ trans = btrfs_start_transaction(tree_root, 1);
+ if (err != -EAGAIN)
+ break;
+ }
+ root->defrag_running = 0;
+ }
+ }
+ btrfs_end_transaction(trans, tree_root);
+ return err;
}
static int drop_dirty_roots(struct btrfs_root *tree_root,
{
struct dirty_root *dirty;
struct btrfs_trans_handle *trans;
- int ret;
+ int ret = 0;
+ int err;
+
while(!list_empty(list)) {
mutex_lock(&tree_root->fs_info->fs_mutex);
dirty = list_entry(list->next, struct dirty_root, list);
list_del_init(&dirty->list);
- trans = btrfs_start_transaction(tree_root, 1);
- ret = btrfs_drop_snapshot(trans, dirty->root,
- dirty->commit_root);
- BUG_ON(ret);
- ret = btrfs_del_root(trans, tree_root, &dirty->snap_key);
+ while(1) {
+ trans = btrfs_start_transaction(tree_root, 1);
+ ret = btrfs_drop_snapshot(trans, dirty->root);
+ if (ret != -EAGAIN) {
+ break;
+ }
+ err = btrfs_update_root(trans,
+ tree_root,
+ &dirty->root->root_key,
+ &dirty->root->root_item);
+ if (err)
+ ret = err;
+ ret = btrfs_end_transaction(trans, tree_root);
+ BUG_ON(ret);
+ mutex_unlock(&tree_root->fs_info->fs_mutex);
+
+ btrfs_btree_balance_dirty(tree_root);
+ schedule();
+
+ mutex_lock(&tree_root->fs_info->fs_mutex);
+ }
BUG_ON(ret);
+ ret = btrfs_del_root(trans, tree_root, &dirty->root->root_key);
+ if (ret)
+ break;
ret = btrfs_end_transaction(trans, tree_root);
BUG_ON(ret);
+
+ kfree(dirty->root);
kfree(dirty);
mutex_unlock(&tree_root->fs_info->fs_mutex);
btrfs_btree_balance_dirty(tree_root);
+ schedule();
}
- return 0;
+ return ret;
}
int btrfs_commit_transaction(struct btrfs_trans_handle *trans,
struct btrfs_transaction *cur_trans;
struct btrfs_transaction *prev_trans = NULL;
struct list_head dirty_fs_roots;
+ struct radix_tree_root pinned_copy;
DEFINE_WAIT(wait);
+ init_bit_radix(&pinned_copy);
INIT_LIST_HEAD(&dirty_fs_roots);
mutex_lock(&root->fs_info->trans_mutex);
if (trans->transaction->in_commit) {
cur_trans = trans->transaction;
trans->transaction->use_count++;
+ mutex_unlock(&root->fs_info->trans_mutex);
btrfs_end_transaction(trans, root);
+
+ mutex_unlock(&root->fs_info->fs_mutex);
ret = wait_for_commit(root, cur_trans);
BUG_ON(ret);
put_transaction(cur_trans);
- mutex_unlock(&root->fs_info->trans_mutex);
+ mutex_lock(&root->fs_info->fs_mutex);
return 0;
}
- cur_trans = trans->transaction;
trans->transaction->in_commit = 1;
+ cur_trans = trans->transaction;
+ if (cur_trans->list.prev != &root->fs_info->trans_list) {
+ prev_trans = list_entry(cur_trans->list.prev,
+ struct btrfs_transaction, list);
+ if (!prev_trans->commit_done) {
+ prev_trans->use_count++;
+ mutex_unlock(&root->fs_info->fs_mutex);
+ mutex_unlock(&root->fs_info->trans_mutex);
+
+ wait_for_commit(root, prev_trans);
+ put_transaction(prev_trans);
+
+ mutex_lock(&root->fs_info->fs_mutex);
+ mutex_lock(&root->fs_info->trans_mutex);
+ }
+ }
while (trans->transaction->num_writers > 1) {
WARN_ON(cur_trans != trans->transaction);
prepare_to_wait(&trans->transaction->writer_wait, &wait,
TASK_UNINTERRUPTIBLE);
if (trans->transaction->num_writers <= 1)
break;
+ mutex_unlock(&root->fs_info->fs_mutex);
mutex_unlock(&root->fs_info->trans_mutex);
schedule();
+ mutex_lock(&root->fs_info->fs_mutex);
mutex_lock(&root->fs_info->trans_mutex);
finish_wait(&trans->transaction->writer_wait, &wait);
}
finish_wait(&trans->transaction->writer_wait, &wait);
WARN_ON(cur_trans != trans->transaction);
- add_dirty_roots(trans, &root->fs_info->fs_roots_radix, &dirty_fs_roots);
+ ret = add_dirty_roots(trans, &root->fs_info->fs_roots_radix,
+ &dirty_fs_roots);
+ BUG_ON(ret);
+
ret = btrfs_commit_tree_roots(trans, root);
BUG_ON(ret);
+
cur_trans = root->fs_info->running_transaction;
root->fs_info->running_transaction = NULL;
- if (cur_trans->list.prev != &root->fs_info->trans_list) {
- prev_trans = list_entry(cur_trans->list.prev,
- struct btrfs_transaction, list);
- if (prev_trans->commit_done)
- prev_trans = NULL;
- else
- prev_trans->use_count++;
- }
+ btrfs_set_super_generation(&root->fs_info->super_copy,
+ cur_trans->transid);
+ btrfs_set_super_root(&root->fs_info->super_copy,
+ bh_blocknr(root->fs_info->tree_root->node));
+ memcpy(root->fs_info->disk_super, &root->fs_info->super_copy,
+ sizeof(root->fs_info->super_copy));
+
+ btrfs_copy_pinned(root, &pinned_copy);
+
mutex_unlock(&root->fs_info->trans_mutex);
mutex_unlock(&root->fs_info->fs_mutex);
ret = btrfs_write_and_wait_transaction(trans, root);
- if (prev_trans) {
- mutex_lock(&root->fs_info->trans_mutex);
- wait_for_commit(root, prev_trans);
- put_transaction(prev_trans);
- mutex_unlock(&root->fs_info->trans_mutex);
- }
- btrfs_set_super_generation(root->fs_info->disk_super,
- cur_trans->transid);
BUG_ON(ret);
write_ctree_super(trans, root);
-
mutex_lock(&root->fs_info->fs_mutex);
- btrfs_finish_extent_commit(trans, root);
+ btrfs_finish_extent_commit(trans, root, &pinned_copy);
mutex_lock(&root->fs_info->trans_mutex);
cur_trans->commit_done = 1;
wake_up(&cur_trans->commit_wait);
goto out;
}
mutex_unlock(&root->fs_info->trans_mutex);
+ btrfs_defrag_dirty_roots(root->fs_info);
trans = btrfs_start_transaction(root, 1);
ret = btrfs_commit_transaction(trans, root);
out: