summaryrefslogtreecommitdiff
path: root/cmds-check.c
diff options
context:
space:
mode:
authorJosef Bacik <jbacik@fusionio.com>2013-04-04 09:57:50 -0400
committerDavid Sterba <dsterba@suse.cz>2013-04-23 18:56:21 +0200
commitd93cad2677613b0a31315e428ead5339612a4b1c (patch)
treebc78b1d7300f611929b4978920ac10b66fe49ebd /cmds-check.c
parentbee9403728ce017c0b9a8384a0bcb342b331688a (diff)
Btrfs-progs: add a free space cache checker to fsck V2
In trying to track down a weird tree log problem I wanted to make sure that the free space cache was actually valid, which we currently have no way of doing. So this patch adds a bunch of support for the free space cache code and then a checker to fsck. Basically we go through and if we can actually load the free space cache then we will walk the extent tree and verify that the free space cache exactly matches what is in the extent tree. Hopefully this will always be correct, the only time it wouldn't is if the extent tree is corrupt or we have some sort of awful bug in the free space cache. Thanks, Signed-off-by: Josef Bacik <jbacik@fusionio.com>
Diffstat (limited to 'cmds-check.c')
-rw-r--r--cmds-check.c223
1 files changed, 223 insertions, 0 deletions
diff --git a/cmds-check.c b/cmds-check.c
index 6622ea8c..d39e7972 100644
--- a/cmds-check.c
+++ b/cmds-check.c
@@ -38,6 +38,7 @@
#include "version.h"
#include "utils.h"
#include "commands.h"
+#include "free-space-cache.h"
static u64 bytes_used = 0;
static u64 total_csum_bytes = 0;
@@ -2605,6 +2606,223 @@ out:
return 0;
}
+static int check_cache_range(struct btrfs_root *root,
+ struct btrfs_block_group_cache *cache,
+ u64 offset, u64 bytes)
+{
+ struct btrfs_free_space *entry;
+ u64 *logical;
+ u64 bytenr;
+ int stripe_len;
+ int i, nr, ret;
+
+ for (i = 0; i < BTRFS_SUPER_MIRROR_MAX; i++) {
+ bytenr = btrfs_sb_offset(i);
+ ret = btrfs_rmap_block(&root->fs_info->mapping_tree,
+ cache->key.objectid, bytenr, 0,
+ &logical, &nr, &stripe_len);
+ if (ret)
+ return ret;
+
+ while (nr--) {
+ if (logical[nr] + stripe_len <= offset)
+ continue;
+ if (offset + bytes <= logical[nr])
+ continue;
+ if (logical[nr] == offset) {
+ if (stripe_len >= bytes) {
+ kfree(logical);
+ return 0;
+ }
+ bytes -= stripe_len;
+ offset += stripe_len;
+ } else if (logical[nr] < offset) {
+ if (logical[nr] + stripe_len >=
+ offset + bytes) {
+ kfree(logical);
+ return 0;
+ }
+ bytes = (offset + bytes) -
+ (logical[nr] + stripe_len);
+ offset = logical[nr] + stripe_len;
+ } else {
+ /*
+ * Could be tricky, the super may land in the
+ * middle of the area we're checking. First
+ * check the easiest case, it's at the end.
+ */
+ if (logical[nr] + stripe_len >=
+ bytes + offset) {
+ bytes = logical[nr] - offset;
+ continue;
+ }
+
+ /* Check the left side */
+ ret = check_cache_range(root, cache,
+ offset,
+ logical[nr] - offset);
+ if (ret) {
+ kfree(logical);
+ return ret;
+ }
+
+ /* Now we continue with the right side */
+ bytes = (offset + bytes) -
+ (logical[nr] + stripe_len);
+ offset = logical[nr] + stripe_len;
+ }
+ }
+
+ kfree(logical);
+ }
+
+ entry = btrfs_find_free_space(cache->free_space_ctl, offset, bytes);
+ if (!entry) {
+ fprintf(stderr, "There is no free space entry for %Lu-%Lu\n",
+ offset, offset+bytes);
+ return -EINVAL;
+ }
+
+ if (entry->offset != offset) {
+ fprintf(stderr, "Wanted offset %Lu, found %Lu\n", offset,
+ entry->offset);
+ return -EINVAL;
+ }
+
+ if (entry->bytes != bytes) {
+ fprintf(stderr, "Wanted bytes %Lu, found %Lu for off %Lu\n",
+ bytes, entry->bytes, offset);
+ return -EINVAL;
+ }
+
+ unlink_free_space(cache->free_space_ctl, entry);
+ free(entry);
+ return 0;
+}
+
+static int verify_space_cache(struct btrfs_root *root,
+ struct btrfs_block_group_cache *cache)
+{
+ struct btrfs_path *path;
+ struct extent_buffer *leaf;
+ struct btrfs_key key;
+ u64 last;
+ int ret = 0;
+
+ path = btrfs_alloc_path();
+ if (!path)
+ return -ENOMEM;
+
+ root = root->fs_info->extent_root;
+
+ last = max_t(u64, cache->key.objectid, BTRFS_SUPER_INFO_OFFSET);
+
+ key.objectid = last;
+ key.offset = 0;
+ key.type = BTRFS_EXTENT_ITEM_KEY;
+
+ ret = btrfs_search_slot(NULL, root, &key, path, 0, 0);
+ if (ret < 0)
+ return ret;
+ ret = 0;
+ while (1) {
+ if (path->slots[0] >= btrfs_header_nritems(path->nodes[0])) {
+ ret = btrfs_next_leaf(root, path);
+ if (ret < 0)
+ return ret;
+ if (ret > 0) {
+ ret = 0;
+ break;
+ }
+ }
+ leaf = path->nodes[0];
+ btrfs_item_key_to_cpu(leaf, &key, path->slots[0]);
+ if (key.objectid >= cache->key.offset + cache->key.objectid)
+ break;
+ if (key.type != BTRFS_EXTENT_ITEM_KEY &&
+ key.type != BTRFS_METADATA_ITEM_KEY) {
+ path->slots[0]++;
+ continue;
+ }
+
+ if (last == key.objectid) {
+ last = key.objectid + key.offset;
+ path->slots[0]++;
+ continue;
+ }
+
+ ret = check_cache_range(root, cache, last,
+ key.objectid - last);
+ if (ret)
+ break;
+ if (key.type == BTRFS_EXTENT_ITEM_KEY)
+ last = key.objectid + key.offset;
+ else
+ last = key.objectid + root->leafsize;
+ path->slots[0]++;
+ }
+
+ if (last < cache->key.objectid + cache->key.offset)
+ ret = check_cache_range(root, cache, last,
+ cache->key.objectid +
+ cache->key.offset - last);
+ btrfs_free_path(path);
+
+ if (!ret &&
+ !RB_EMPTY_ROOT(&cache->free_space_ctl->free_space_offset)) {
+ fprintf(stderr, "There are still entries left in the space "
+ "cache\n");
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int check_space_cache(struct btrfs_root *root)
+{
+ struct btrfs_block_group_cache *cache;
+ u64 start = BTRFS_SUPER_INFO_OFFSET + BTRFS_SUPER_INFO_SIZE;
+ int ret;
+ int error = 0;
+
+ if (btrfs_super_generation(root->fs_info->super_copy) !=
+ btrfs_super_cache_generation(root->fs_info->super_copy)) {
+ printf("cache and super generation don't match, space cache "
+ "will be invalidated\n");
+ return 0;
+ }
+
+ while (1) {
+ cache = btrfs_lookup_first_block_group(root->fs_info, start);
+ if (!cache)
+ break;
+
+ start = cache->key.objectid + cache->key.offset;
+ if (!cache->free_space_ctl) {
+ if (btrfs_init_free_space_ctl(cache,
+ root->leafsize)) {
+ ret = -ENOMEM;
+ break;
+ }
+ } else {
+ btrfs_remove_free_space_cache(cache);
+ }
+
+ ret = load_free_space_cache(root->fs_info, cache);
+ if (!ret)
+ continue;
+
+ ret = verify_space_cache(root, cache);
+ if (ret) {
+ fprintf(stderr, "cache appears valid but isnt %Lu\n",
+ cache->key.objectid);
+ error++;
+ }
+ }
+
+ return error ? -EINVAL : 0;
+}
+
static int run_next_block(struct btrfs_root *root,
struct block_info *bits,
int bits_nr,
@@ -3675,6 +3893,11 @@ int cmd_check(int argc, char **argv)
if (ret)
fprintf(stderr, "Errors found in extent allocation tree\n");
+ fprintf(stderr, "checking free space cache\n");
+ ret = check_space_cache(root);
+ if (ret)
+ goto out;
+
fprintf(stderr, "checking fs roots\n");
ret = check_fs_roots(root, &root_cache);
if (ret)