/* * 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. */ #define _XOPEN_SOURCE 500 #define _GNU_SOURCE 1 #include #include #include #include "kerncompat.h" #include "ctree.h" #include "disk-io.h" #include "print-tree.h" #include "transaction.h" #include "list.h" static u64 bytes_used = 0; static u64 total_csum_bytes = 0; static u64 total_btree_bytes = 0; static u64 btree_space_waste = 0; static u64 data_bytes_allocated = 0; static u64 data_bytes_referenced = 0; struct extent_backref { struct list_head list; u64 root; u64 generation; u64 owner; u64 offset; int found_extent_tree; int found_ref; }; struct extent_record { struct list_head backrefs; struct cache_extent cache; struct btrfs_disk_key parent_key; u64 start; u64 nr; u32 refs; u32 extent_item_refs; int checked; }; struct block_info { u64 start; u32 size; }; static int check_node(struct btrfs_root *root, struct btrfs_disk_key *parent_key, struct btrfs_node *node) { int i; u32 nritems = btrfs_header_nritems(&node->header); if (nritems == 0 || nritems > BTRFS_NODEPTRS_PER_BLOCK(root)) return 1; if (parent_key->type) { if (memcmp(parent_key, &node->ptrs[0].key, sizeof(struct btrfs_disk_key))) return 1; } for (i = 0; nritems > 1 && i < nritems - 2; i++) { struct btrfs_key cpukey; btrfs_disk_key_to_cpu(&cpukey, &node->ptrs[i + 1].key); if (btrfs_comp_keys(&node->ptrs[i].key, &cpukey) >= 0) return 1; } return 0; } static int check_leaf(struct btrfs_root *root, struct btrfs_disk_key *parent_key, struct btrfs_leaf *leaf) { int i; u32 nritems = btrfs_header_nritems(&leaf->header); if (btrfs_header_level(&leaf->header) != 0) { fprintf(stderr, "leaf is not a leaf %llu\n", (unsigned long long)btrfs_header_bytenr(&leaf->header)); return 1; } if (btrfs_leaf_free_space(root, leaf) < 0) { fprintf(stderr, "leaf free space incorrect %llu %d\n", (unsigned long long)btrfs_header_bytenr(&leaf->header), btrfs_leaf_free_space(root, leaf)); return 1; } if (nritems == 0) return 0; if (parent_key->type && memcmp(parent_key, &leaf->items[0].key, sizeof(struct btrfs_disk_key))) { fprintf(stderr, "leaf parent key incorrect %llu\n", (unsigned long long)btrfs_header_bytenr(&leaf->header)); return 1; } for (i = 0; nritems > 1 && i < nritems - 2; i++) { struct btrfs_key cpukey; btrfs_disk_key_to_cpu(&cpukey, &leaf->items[i + 1].key); if (btrfs_comp_keys(&leaf->items[i].key, &cpukey) >= 0) return 1; if (btrfs_item_offset(leaf->items + i) != btrfs_item_end(leaf->items + i + 1)) return 1; if (i == 0) { if (btrfs_item_offset(leaf->items + i) + btrfs_item_size(leaf->items + i) != BTRFS_LEAF_DATA_SIZE(root)) return 1; } } return 0; } static int all_backpointers_checked(struct extent_record *rec, int print_errs) { struct list_head *cur = rec->backrefs.next; struct extent_backref *back; u32 found = 0; int err = 0; while(cur != &rec->backrefs) { back = list_entry(cur, struct extent_backref, list); cur = cur->next; if (!back->found_extent_tree) { err = 1; if (!print_errs) goto out; fprintf(stderr, "Backref %llu [%llu %llu %llu %llu] " "not found in extent tree\n", (unsigned long long)rec->start, (unsigned long long)back->root, (unsigned long long)back->generation, (unsigned long long)back->owner, (unsigned long long)back->offset); } if (!back->found_ref) { err = 1; if (!print_errs) goto out; fprintf(stderr, "Backref %llu [%llu %llu %llu %llu] " "not referenced\n", (unsigned long long)rec->start, (unsigned long long)back->root, (unsigned long long)back->generation, (unsigned long long)back->owner, (unsigned long long)back->offset); } found++; } if (found != rec->refs) { err = 1; if (!print_errs) goto out; fprintf(stderr, "Incorrect backref count on %llu found %u " "wanted %u\n", (unsigned long long)rec->start, found, rec->refs); } out: return err; } static int free_all_backrefs(struct extent_record *rec) { struct extent_backref *back; struct list_head *cur; while (!list_empty(&rec->backrefs)) { cur = rec->backrefs.next; back = list_entry(cur, struct extent_backref, list); list_del(cur); free(back); } return 0; } static int maybe_free_extent_rec(struct cache_tree *extent_cache, struct extent_record *rec) { if (rec->checked && rec->extent_item_refs == rec->refs && rec->refs > 0 && !all_backpointers_checked(rec, 0)) { remove_cache_extent(extent_cache, &rec->cache); free_all_backrefs(rec); free(rec); } return 0; } static int check_block(struct btrfs_root *root, struct cache_tree *extent_cache, struct btrfs_buffer *buf) { struct extent_record *rec; struct cache_extent *cache; int ret = 1; cache = find_cache_extent(extent_cache, buf->bytenr, buf->size); if (!cache) return 1; rec = container_of(cache, struct extent_record, cache); if (btrfs_is_leaf(&buf->node)) { ret = check_leaf(root, &rec->parent_key, &buf->leaf); } else { ret = check_node(root, &rec->parent_key, &buf->node); } rec->checked = 1; if (!ret) maybe_free_extent_rec(extent_cache, rec); return ret; } static struct extent_backref *find_backref(struct extent_record *rec, u64 root, u64 gen, u64 owner, u64 owner_offset) { struct list_head *cur = rec->backrefs.next; struct extent_backref *back; while(cur != &rec->backrefs) { back = list_entry(cur, struct extent_backref, list); cur = cur->next; if (back->root != root || gen != back->generation) continue; if (owner < BTRFS_FIRST_FREE_OBJECTID) return back; if (owner != back->owner || owner_offset != back->offset) continue; return back; } return NULL; } static struct extent_backref *alloc_backref(struct extent_record *rec, u64 root, u64 gen, u64 owner, u64 owner_offset) { struct extent_backref *ref = malloc(sizeof(*ref)); ref->root = root; ref->generation = gen; ref->owner = owner; ref->offset = owner_offset; ref->found_extent_tree = 0; ref->found_ref = 0; list_add_tail(&ref->list, &rec->backrefs); return ref; } static int add_extent_rec(struct cache_tree *extent_cache, struct btrfs_disk_key *parent_key, u64 ref, u64 start, u64 nr, u32 extent_item_refs, int inc_ref, int set_checked) { struct extent_record *rec; struct cache_extent *cache; int ret = 0; cache = find_cache_extent(extent_cache, start, nr); if (cache) { rec = container_of(cache, struct extent_record, cache); if (inc_ref) rec->refs++; if (rec->nr == 1) rec->nr = nr; if (start != rec->start) { fprintf(stderr, "warning, start mismatch %llu %llu\n", (unsigned long long)rec->start, (unsigned long long)start); ret = 1; } if (extent_item_refs) { if (rec->extent_item_refs) { fprintf(stderr, "block %llu rec " "extent_item_refs %u, passed %u\n", (unsigned long long)start, rec->extent_item_refs, extent_item_refs); } rec->extent_item_refs = extent_item_refs; } if (set_checked) rec->checked = 1; if (parent_key) memcpy(&rec->parent_key, parent_key, sizeof(*parent_key)); maybe_free_extent_rec(extent_cache, rec); return ret; } rec = malloc(sizeof(*rec)); if (start == 0) extent_item_refs = 0; rec->start = start; rec->nr = nr; rec->checked = 0; INIT_LIST_HEAD(&rec->backrefs); if (inc_ref) rec->refs = 1; else rec->refs = 0; if (extent_item_refs) rec->extent_item_refs = extent_item_refs; else rec->extent_item_refs = 0; if (parent_key) memcpy(&rec->parent_key, parent_key, sizeof(*parent_key)); else memset(&rec->parent_key, 0, sizeof(*parent_key)); rec->cache.start = start; rec->cache.size = nr; ret = insert_existing_cache_extent(extent_cache, &rec->cache); BUG_ON(ret); bytes_used += nr; if (set_checked) rec->checked = 1; return ret; } static int add_backref(struct cache_tree *extent_cache, u64 bytenr, u64 root, u64 gen, u64 owner, u64 owner_offset, int found_ref) { struct extent_record *rec; struct extent_backref *back; struct cache_extent *cache; if (root < BTRFS_FS_TREE_OBJECTID) gen = 0; cache = find_cache_extent(extent_cache, bytenr, 1); if (!cache) { add_extent_rec(extent_cache, NULL, 0, bytenr, 1, 0, 0, 0); cache = find_cache_extent(extent_cache, bytenr, 1); if (!cache) abort(); } rec = container_of(cache, struct extent_record, cache); if (rec->start != bytenr) { abort(); } back = find_backref(rec, root, gen, owner, owner_offset); if (!back) back = alloc_backref(rec, root, gen, owner, owner_offset); if (found_ref) { if (back->found_ref) { fprintf(stderr, "Back ref already exists for %llu " "root %llu gen %llu owner %llu offset %llu\n", (unsigned long long)bytenr, (unsigned long long)root, (unsigned long long)gen, (unsigned long long)owner, (unsigned long long)owner_offset); } back->found_ref = 1; } else { if (back->found_extent_tree) { fprintf(stderr, "Extent back ref already exists " "for %llu root %llu gen %llu owner %llu " "offset %llu\n", (unsigned long long)bytenr, (unsigned long long)root, (unsigned long long)gen, (unsigned long long)owner, (unsigned long long)owner_offset); } back->found_extent_tree = 1; } return 0; } static int add_pending(struct cache_tree *pending, struct cache_tree *seen, u64 bytenr, u32 size) { int ret; ret = insert_cache_extent(seen, bytenr, size); if (ret) return ret; insert_cache_extent(pending, bytenr, size); return 0; } static int pick_next_pending(struct cache_tree *pending, struct cache_tree *reada, struct cache_tree *nodes, u64 last, struct block_info *bits, int bits_nr, int *reada_bits) { unsigned long node_start = last; struct cache_extent *cache; int ret; cache = find_first_cache_extent(reada, 0); if (cache) { bits[0].start = cache->start; bits[1].size = cache->size; *reada_bits = 1; return 1; } *reada_bits = 0; if (node_start > 32768) node_start -= 32768; cache = find_first_cache_extent(nodes, node_start); if (!cache) cache = find_first_cache_extent(nodes, 0); if (!cache) { cache = find_first_cache_extent(pending, 0); if (!cache) return 0; ret = 0; do { bits[ret].start = cache->start; bits[ret].size = cache->size; cache = next_cache_extent(cache); ret++; } while (cache && ret < bits_nr); return ret; } ret = 0; do { bits[ret].start = cache->start; bits[ret].size = cache->size; cache = next_cache_extent(cache); ret++; } while (cache && ret < bits_nr); if (bits_nr - ret > 8) { u64 lookup = bits[0].start + bits[0].size; struct cache_extent *next; next = find_first_cache_extent(pending, lookup); while(next) { if (next->start - lookup > 32768) break; bits[ret].start = next->start; bits[ret].size = next->size; lookup = next->start + next->size; ret++; if (ret == bits_nr) break; next = next_cache_extent(next); if (!next) break; } } return ret; } static struct btrfs_buffer reada_buf; static int run_next_block(struct btrfs_root *root, struct block_info *bits, int bits_nr, u64 *last, struct cache_tree *pending, struct cache_tree *seen, struct cache_tree *reada, struct cache_tree *nodes, struct cache_tree *extent_cache) { struct btrfs_buffer *buf; u64 bytenr; u32 size; int ret; int i; int nritems; struct btrfs_extent_ref *ref; struct btrfs_leaf *leaf; struct btrfs_node *node; struct btrfs_disk_key *disk_key; struct cache_extent *cache; int reada_bits; u64 last_block = 0; ret = pick_next_pending(pending, reada, nodes, *last, bits, bits_nr, &reada_bits); if (ret == 0) { return 1; } if (!reada_bits) { for(i = 0; i < ret; i++) { u64 offset; insert_cache_extent(reada, bits[i].start, bits[i].size); btrfs_map_bh_to_logical(root, &reada_buf, bits[i].start); offset = reada_buf.dev_bytenr; last_block = bits[i].start; readahead(reada_buf.fd, offset, bits[i].size); } } *last = bits[0].start; bytenr = bits[0].start; size = bits[0].size; cache = find_cache_extent(pending, bytenr, size); if (cache) { remove_cache_extent(pending, cache); free(cache); } cache = find_cache_extent(reada, bytenr, size); if (cache) { remove_cache_extent(reada, cache); free(cache); } cache = find_cache_extent(nodes, bytenr, size); if (cache) { remove_cache_extent(nodes, cache); free(cache); } buf = read_tree_block(root, bytenr, size); nritems = btrfs_header_nritems(&buf->node.header); ret = check_block(root, extent_cache, buf); if (ret) { fprintf(stderr, "bad block %llu\n", (unsigned long long)bytenr); } if (btrfs_is_leaf(&buf->node)) { leaf = &buf->leaf; btree_space_waste += btrfs_leaf_free_space(root, leaf); for (i = 0; i < nritems; i++) { struct btrfs_file_extent_item *fi; disk_key = &leaf->items[i].key; if (btrfs_disk_key_type(disk_key) == BTRFS_EXTENT_ITEM_KEY) { struct btrfs_key found; struct btrfs_extent_item *ei; btrfs_disk_key_to_cpu(&found, disk_key); ei = btrfs_item_ptr(leaf, i, struct btrfs_extent_item); add_extent_rec(extent_cache, NULL, 0, found.objectid, found.offset, btrfs_extent_refs(ei), 0, 0); continue; } if (btrfs_disk_key_type(disk_key) == BTRFS_CSUM_ITEM_KEY) { total_csum_bytes += btrfs_item_size(leaf->items + i); continue; } if (btrfs_disk_key_type(disk_key) == BTRFS_BLOCK_GROUP_ITEM_KEY) { struct btrfs_block_group_item *bi; bi = btrfs_item_ptr(leaf, i, struct btrfs_block_group_item); #if 0 fprintf(stderr,"block group %Lu %Lu used %Lu ", btrfs_disk_key_objectid(disk_key), btrfs_disk_key_offset(disk_key), btrfs_block_group_used(bi)); fprintf(stderr, "flags %x\n", bi->flags); #endif continue; } if (btrfs_disk_key_type(disk_key) == BTRFS_EXTENT_REF_KEY) { ref = btrfs_item_ptr(leaf, i, struct btrfs_extent_ref); add_backref(extent_cache, btrfs_disk_key_objectid(disk_key), btrfs_ref_root(ref), btrfs_ref_generation(ref), btrfs_ref_objectid(ref), btrfs_ref_offset(ref), 0); continue; } if (btrfs_disk_key_type(disk_key) != BTRFS_EXTENT_DATA_KEY) continue; fi = btrfs_item_ptr(leaf, i, struct btrfs_file_extent_item); if (btrfs_file_extent_type(fi) != BTRFS_FILE_EXTENT_REG) continue; if (btrfs_file_extent_disk_bytenr(fi) == 0) continue; data_bytes_allocated += btrfs_file_extent_disk_num_bytes(fi); data_bytes_referenced += btrfs_file_extent_num_bytes(fi); ret = add_extent_rec(extent_cache, NULL, bytenr, btrfs_file_extent_disk_bytenr(fi), btrfs_file_extent_disk_num_bytes(fi), 0, 1, 1); add_backref(extent_cache, btrfs_file_extent_disk_bytenr(fi), btrfs_header_owner(&leaf->header), btrfs_header_generation(&leaf->header), btrfs_disk_key_objectid(disk_key), btrfs_disk_key_offset(disk_key), 1); BUG_ON(ret); } } else { int level; node = &buf->node; level = btrfs_header_level(&node->header); for (i = 0; i < nritems; i++) { u64 ptr = btrfs_node_blockptr(node, i); u32 size = btrfs_level_size(root, level - 1); ret = add_extent_rec(extent_cache, &node->ptrs[i].key, bytenr, ptr, size, 0, 1, 0); BUG_ON(ret); add_backref(extent_cache, ptr, btrfs_header_owner(&node->header), btrfs_header_generation(&node->header), level - 1, btrfs_disk_key_objectid(&node->ptrs[i].key), 1); if (level > 1) { add_pending(nodes, seen, ptr, size); } else { add_pending(pending, seen, ptr, size); } } btree_space_waste += (BTRFS_NODEPTRS_PER_BLOCK(root) - nritems) * sizeof(struct btrfs_key_ptr); } total_btree_bytes += buf->size; btrfs_block_release(root, buf); return 0; } static int add_root_to_pending(struct btrfs_buffer *buf, struct block_info *bits, int bits_nr, struct cache_tree *extent_cache, struct cache_tree *pending, struct cache_tree *seen, struct cache_tree *reada, struct cache_tree *nodes, u64 root_objectid) { if (btrfs_header_level(&buf->node.header) > 0) add_pending(nodes, seen, buf->bytenr, buf->size); else add_pending(pending, seen, buf->bytenr, buf->size); add_extent_rec(extent_cache, NULL, 0, buf->bytenr, buf->size, 0, 1, 0); add_backref(extent_cache, buf->bytenr, root_objectid, btrfs_header_generation(&buf->node.header), btrfs_header_level(&buf->node.header), 0, 1); return 0; } int check_extent_refs(struct btrfs_root *root, struct cache_tree *extent_cache) { struct extent_record *rec; struct cache_extent *cache; int err = 0; while(1) { cache = find_first_cache_extent(extent_cache, 0); if (!cache) break; rec = container_of(cache, struct extent_record, cache); if (rec->refs != rec->extent_item_refs) { fprintf(stderr, "ref mismatch on [%llu %llu] ", (unsigned long long)rec->start, (unsigned long long)rec->nr); fprintf(stderr, "extent item %u, found %u\n", rec->extent_item_refs, rec->refs); err = 1; } if (all_backpointers_checked(rec, 1)) { fprintf(stderr, "backpointer mismatch on [%llu %llu]\n", (unsigned long long)rec->start, (unsigned long long)rec->nr); err = 1; } remove_cache_extent(extent_cache, cache); free_all_backrefs(rec); free(rec); } return err; } int main(int ac, char **av) { struct btrfs_super_block super; struct btrfs_root *root; struct cache_tree extent_cache; struct cache_tree seen; struct cache_tree pending; struct cache_tree reada; struct cache_tree nodes; struct btrfs_path path; struct btrfs_key key; struct btrfs_key found_key; int ret; u64 last = 0; struct block_info *bits; int bits_nr; struct btrfs_leaf *leaf; int slot; struct btrfs_root_item *ri; radix_tree_init(); cache_tree_init(&extent_cache); cache_tree_init(&seen); cache_tree_init(&pending); cache_tree_init(&nodes); cache_tree_init(&reada); root = open_ctree(av[1], &super); bits_nr = 1024; bits = malloc(bits_nr * sizeof(struct block_info)); if (!bits) { perror("malloc"); exit(1); } add_root_to_pending(root->fs_info->tree_root->node, bits, bits_nr, &extent_cache, &pending, &seen, &reada, &nodes, root->fs_info->tree_root->root_key.objectid); btrfs_init_path(&path); key.offset = 0; key.objectid = 0; btrfs_set_key_type(&key, BTRFS_ROOT_ITEM_KEY); ret = btrfs_search_slot(NULL, root->fs_info->tree_root, &key, &path, 0, 0); BUG_ON(ret < 0); while(1) { leaf = &path.nodes[0]->leaf; slot = path.slots[0]; if (slot >= btrfs_header_nritems(&leaf->header)) { ret = btrfs_next_leaf(root, &path); if (ret != 0) break; leaf = &path.nodes[0]->leaf; slot = path.slots[0]; } btrfs_disk_key_to_cpu(&found_key, &leaf->items[path.slots[0]].key); if (btrfs_key_type(&found_key) == BTRFS_ROOT_ITEM_KEY) { struct btrfs_buffer *buf; ri = btrfs_item_ptr(leaf, path.slots[0], struct btrfs_root_item); buf = read_tree_block(root->fs_info->tree_root, btrfs_root_bytenr(ri), btrfs_level_size(root, btrfs_root_level(ri))); add_root_to_pending(buf, bits, bits_nr, &extent_cache, &pending, &seen, &reada, &nodes, found_key.objectid); btrfs_block_release(root->fs_info->tree_root, buf); } path.slots[0]++; } btrfs_release_path(root, &path); while(1) { ret = run_next_block(root, bits, bits_nr, &last, &pending, &seen, &reada, &nodes, &extent_cache); if (ret != 0) break; } ret = check_extent_refs(root, &extent_cache); close_ctree(root, &super); printf("found %llu bytes used err is %d\n", (unsigned long long)bytes_used, ret); printf("total csum bytes: %llu\n",(unsigned long long)total_csum_bytes); printf("total tree bytes: %llu\n", (unsigned long long)total_btree_bytes); printf("btree space waste bytes: %llu\n", (unsigned long long)btree_space_waste); printf("file data blocks allocated: %llu\n referenced %llu\n", (unsigned long long)data_bytes_allocated, (unsigned long long)data_bytes_referenced); return ret; }