/* * Copyright (C) 2007 Oracle. All rights reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License v2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 021110-1307, USA. */ #include #include "ctree.h" #include "disk-io.h" #include "print-tree.h" #include "transaction.h" static void reada_defrag(struct btrfs_root *root, struct extent_buffer *node) { int i; u32 nritems; u64 bytenr; u32 blocksize; int ret; blocksize = btrfs_level_size(root, btrfs_header_level(node) - 1); nritems = btrfs_header_nritems(node); for (i = 0; i < nritems; i++) { bytenr = btrfs_node_blockptr(node, i); ret = readahead_tree_block(root, bytenr, blocksize); if (ret) break; } } static int defrag_walk_down(struct btrfs_trans_handle *trans, struct btrfs_root *root, struct btrfs_path *path, int *level, int cache_only, u64 *last_ret) { struct extent_buffer *next; struct extent_buffer *cur; u64 bytenr; int ret = 0; int is_extent = 0; WARN_ON(*level < 0); WARN_ON(*level >= BTRFS_MAX_LEVEL); if (root->fs_info->extent_root == root) is_extent = 1; while(*level > 0) { WARN_ON(*level < 0); WARN_ON(*level >= BTRFS_MAX_LEVEL); cur = path->nodes[*level]; if (!cache_only && *level > 1 && path->slots[*level] == 0) reada_defrag(root, cur); if (btrfs_header_level(cur) != *level) WARN_ON(1); if (path->slots[*level] >= btrfs_header_nritems(cur)) break; if (*level == 1) { ret = btrfs_realloc_node(trans, root, path->nodes[*level], cache_only, last_ret); if (is_extent) btrfs_extent_post_op(trans, root); break; } bytenr = btrfs_node_blockptr(cur, path->slots[*level]); if (cache_only) { next = btrfs_find_tree_block(root, bytenr, btrfs_level_size(root, *level - 1)); /* FIXME, test for defrag */ if (!next || !btrfs_buffer_uptodate(next)) { free_extent_buffer(next); path->slots[*level]++; continue; } } else { next = read_tree_block(root, bytenr, btrfs_level_size(root, *level - 1)); } ret = btrfs_cow_block(trans, root, next, path->nodes[*level], path->slots[*level], &next); BUG_ON(ret); ret = btrfs_realloc_node(trans, root, next, cache_only, last_ret); BUG_ON(ret); if (is_extent) btrfs_extent_post_op(trans, root); WARN_ON(*level <= 0); if (path->nodes[*level-1]) free_extent_buffer(path->nodes[*level-1]); path->nodes[*level-1] = next; *level = btrfs_header_level(next); path->slots[*level] = 0; } WARN_ON(*level < 0); WARN_ON(*level >= BTRFS_MAX_LEVEL); btrfs_clear_buffer_defrag(path->nodes[*level]); btrfs_clear_buffer_defrag_done(path->nodes[*level]); free_extent_buffer(path->nodes[*level]); path->nodes[*level] = NULL; *level += 1; WARN_ON(ret); return 0; } static int defrag_walk_up(struct btrfs_trans_handle *trans, struct btrfs_root *root, struct btrfs_path *path, int *level, int cache_only) { int i; int slot; struct extent_buffer *node; for(i = *level; i < BTRFS_MAX_LEVEL - 1 && path->nodes[i]; i++) { slot = path->slots[i]; if (slot < btrfs_header_nritems(path->nodes[i]) - 1) { path->slots[i]++; *level = i; node = path->nodes[i]; WARN_ON(i == 0); btrfs_node_key_to_cpu(node, &root->defrag_progress, path->slots[i]); root->defrag_level = i; return 0; } else { btrfs_clear_buffer_defrag(path->nodes[*level]); btrfs_clear_buffer_defrag_done(path->nodes[*level]); free_extent_buffer(path->nodes[*level]); path->nodes[*level] = NULL; *level = i + 1; } } return 1; } int btrfs_defrag_leaves(struct btrfs_trans_handle *trans, struct btrfs_root *root, int cache_only) { struct btrfs_path *path = NULL; struct extent_buffer *tmp; int ret = 0; int wret; int level; int orig_level; int i; int is_extent = 0; u64 last_ret = 0; if (root->fs_info->extent_root == root) is_extent = 1; if (root->ref_cows == 0 && !is_extent) goto out; path = btrfs_alloc_path(); if (!path) return -ENOMEM; level = btrfs_header_level(root->node); orig_level = level; if (level == 0) { goto out; } if (root->defrag_progress.objectid == 0) { extent_buffer_get(root->node); ret = btrfs_cow_block(trans, root, root->node, NULL, 0, &tmp); BUG_ON(ret); ret = btrfs_realloc_node(trans, root, root->node, cache_only, &last_ret); BUG_ON(ret); path->nodes[level] = root->node; path->slots[level] = 0; if (is_extent) btrfs_extent_post_op(trans, root); } else { level = root->defrag_level; path->lowest_level = level; wret = btrfs_search_slot(trans, root, &root->defrag_progress, path, 0, 1); if (is_extent) btrfs_extent_post_op(trans, root); if (wret < 0) { ret = wret; goto out; } while(level > 0 && !path->nodes[level]) level--; if (!path->nodes[level]) { ret = 0; goto out; } } while(1) { wret = defrag_walk_down(trans, root, path, &level, cache_only, &last_ret); if (wret > 0) break; if (wret < 0) ret = wret; wret = defrag_walk_up(trans, root, path, &level, cache_only); if (wret > 0) break; if (wret < 0) ret = wret; ret = -EAGAIN; break; } for (i = 0; i <= orig_level; i++) { if (path->nodes[i]) { free_extent_buffer(path->nodes[i]); path->nodes[i] = NULL; } } out: if (path) btrfs_free_path(path); if (ret != -EAGAIN) { memset(&root->defrag_progress, 0, sizeof(root->defrag_progress)); } return ret; }