mirror of
https://github.com/kdave/btrfs-progs
synced 2024-12-27 16:42:17 +00:00
819 lines
21 KiB
C
819 lines
21 KiB
C
/*
|
|
* 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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <fcntl.h>
|
|
#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 extent_buffer *buf)
|
|
{
|
|
int i;
|
|
struct btrfs_key cpukey;
|
|
struct btrfs_disk_key key;
|
|
u32 nritems = btrfs_header_nritems(buf);
|
|
|
|
if (nritems == 0 || nritems > BTRFS_NODEPTRS_PER_BLOCK(root))
|
|
return 1;
|
|
if (parent_key->type) {
|
|
btrfs_node_key(buf, &key, 0);
|
|
if (memcmp(parent_key, &key, sizeof(key)))
|
|
return 1;
|
|
}
|
|
for (i = 0; nritems > 1 && i < nritems - 2; i++) {
|
|
btrfs_node_key(buf, &key, i);
|
|
btrfs_node_key_to_cpu(buf, &cpukey, i + 1);
|
|
if (btrfs_comp_keys(&key, &cpukey) >= 0)
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int check_leaf(struct btrfs_root *root,
|
|
struct btrfs_disk_key *parent_key,
|
|
struct extent_buffer *buf)
|
|
{
|
|
int i;
|
|
struct btrfs_key cpukey;
|
|
struct btrfs_disk_key key;
|
|
u32 nritems = btrfs_header_nritems(buf);
|
|
|
|
if (btrfs_header_level(buf) != 0) {
|
|
fprintf(stderr, "leaf is not a leaf %llu\n",
|
|
(unsigned long long)btrfs_header_bytenr(buf));
|
|
return 1;
|
|
}
|
|
if (btrfs_leaf_free_space(root, buf) < 0) {
|
|
fprintf(stderr, "leaf free space incorrect %llu %d\n",
|
|
(unsigned long long)btrfs_header_bytenr(buf),
|
|
btrfs_leaf_free_space(root, buf));
|
|
return 1;
|
|
}
|
|
|
|
if (nritems == 0)
|
|
return 0;
|
|
|
|
btrfs_item_key(buf, &key, 0);
|
|
if (parent_key->type && memcmp(parent_key, &key, sizeof(key))) {
|
|
fprintf(stderr, "leaf parent key incorrect %llu\n",
|
|
(unsigned long long)btrfs_header_bytenr(buf));
|
|
return 1;
|
|
}
|
|
for (i = 0; nritems > 1 && i < nritems - 2; i++) {
|
|
btrfs_item_key(buf, &key, i);
|
|
btrfs_item_key_to_cpu(buf, &cpukey, i + 1);
|
|
if (btrfs_comp_keys(&key, &cpukey) >= 0) {
|
|
fprintf(stderr, "bad key ordering %d %d\n", i, i+1);
|
|
return 1;
|
|
}
|
|
if (btrfs_item_offset_nr(buf, i) !=
|
|
btrfs_item_end_nr(buf, i + 1)) {
|
|
fprintf(stderr, "incorrect offsets %u %u\n",
|
|
btrfs_item_offset_nr(buf, i),
|
|
btrfs_item_end_nr(buf, i + 1));
|
|
return 1;
|
|
}
|
|
if (i == 0 && btrfs_item_end_nr(buf, i) !=
|
|
BTRFS_LEAF_DATA_SIZE(root)) {
|
|
fprintf(stderr, "bad item end %u wanted %lu\n",
|
|
btrfs_item_end_nr(buf, 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 extent_buffer *buf)
|
|
{
|
|
struct extent_record *rec;
|
|
struct cache_extent *cache;
|
|
int ret = 1;
|
|
|
|
cache = find_cache_extent(extent_cache, buf->start, buf->len);
|
|
if (!cache)
|
|
return 1;
|
|
rec = container_of(cache, struct extent_record, cache);
|
|
if (btrfs_is_leaf(buf)) {
|
|
ret = check_leaf(root, &rec->parent_key, buf);
|
|
} else {
|
|
ret = check_node(root, &rec->parent_key, buf);
|
|
}
|
|
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 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 extent_buffer *buf;
|
|
u64 bytenr;
|
|
u32 size;
|
|
int ret;
|
|
int i;
|
|
int nritems;
|
|
struct btrfs_extent_ref *ref;
|
|
struct btrfs_disk_key disk_key;
|
|
struct cache_extent *cache;
|
|
int reada_bits;
|
|
|
|
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++) {
|
|
insert_cache_extent(reada, bits[i].start,
|
|
bits[i].size);
|
|
readahead_tree_block(root, bits[i].start, 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);
|
|
ret = check_block(root, extent_cache, buf);
|
|
if (ret) {
|
|
fprintf(stderr, "bad block %llu\n",
|
|
(unsigned long long)bytenr);
|
|
}
|
|
if (btrfs_is_leaf(buf)) {
|
|
btree_space_waste += btrfs_leaf_free_space(root, buf);
|
|
for (i = 0; i < nritems; i++) {
|
|
struct btrfs_file_extent_item *fi;
|
|
btrfs_item_key(buf, &disk_key, i);
|
|
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(buf, i,
|
|
struct btrfs_extent_item);
|
|
add_extent_rec(extent_cache, NULL, 0,
|
|
found.objectid,
|
|
found.offset,
|
|
btrfs_extent_refs(buf, ei),
|
|
0, 0);
|
|
continue;
|
|
}
|
|
if (btrfs_disk_key_type(&disk_key) ==
|
|
BTRFS_CSUM_ITEM_KEY) {
|
|
total_csum_bytes +=
|
|
btrfs_item_size_nr(buf, i);
|
|
continue;
|
|
}
|
|
if (btrfs_disk_key_type(&disk_key) ==
|
|
BTRFS_BLOCK_GROUP_ITEM_KEY) {
|
|
struct btrfs_block_group_item *bi;
|
|
bi = btrfs_item_ptr(buf, 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(buf, i,
|
|
struct btrfs_extent_ref);
|
|
|
|
add_backref(extent_cache,
|
|
btrfs_disk_key_objectid(&disk_key),
|
|
btrfs_ref_root(buf, ref),
|
|
btrfs_ref_generation(buf, ref),
|
|
btrfs_ref_objectid(buf, ref),
|
|
btrfs_ref_offset(buf, ref), 0);
|
|
continue;
|
|
}
|
|
if (btrfs_disk_key_type(&disk_key) !=
|
|
BTRFS_EXTENT_DATA_KEY)
|
|
continue;
|
|
fi = btrfs_item_ptr(buf, i,
|
|
struct btrfs_file_extent_item);
|
|
if (btrfs_file_extent_type(buf, fi) !=
|
|
BTRFS_FILE_EXTENT_REG)
|
|
continue;
|
|
if (btrfs_file_extent_disk_bytenr(buf, fi) == 0)
|
|
continue;
|
|
|
|
data_bytes_allocated +=
|
|
btrfs_file_extent_disk_num_bytes(buf, fi);
|
|
data_bytes_referenced +=
|
|
btrfs_file_extent_num_bytes(buf, fi);
|
|
ret = add_extent_rec(extent_cache, NULL, bytenr,
|
|
btrfs_file_extent_disk_bytenr(buf, fi),
|
|
btrfs_file_extent_disk_num_bytes(buf, fi),
|
|
0, 1, 1);
|
|
add_backref(extent_cache,
|
|
btrfs_file_extent_disk_bytenr(buf, fi),
|
|
btrfs_header_owner(buf),
|
|
btrfs_header_generation(buf),
|
|
btrfs_disk_key_objectid(&disk_key),
|
|
btrfs_disk_key_offset(&disk_key), 1);
|
|
BUG_ON(ret);
|
|
}
|
|
} else {
|
|
int level;
|
|
level = btrfs_header_level(buf);
|
|
for (i = 0; i < nritems; i++) {
|
|
u64 ptr = btrfs_node_blockptr(buf, i);
|
|
u32 size = btrfs_level_size(root, level - 1);
|
|
btrfs_node_key(buf, &disk_key, i);
|
|
ret = add_extent_rec(extent_cache,
|
|
&disk_key,
|
|
bytenr, ptr, size,
|
|
0, 1, 0);
|
|
BUG_ON(ret);
|
|
|
|
add_backref(extent_cache, ptr,
|
|
btrfs_header_owner(buf),
|
|
btrfs_header_generation(buf),
|
|
level - 1,
|
|
btrfs_disk_key_objectid(&disk_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->len;
|
|
free_extent_buffer(buf);
|
|
return 0;
|
|
}
|
|
|
|
static int add_root_to_pending(struct extent_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) > 0)
|
|
add_pending(nodes, seen, buf->start, buf->len);
|
|
else
|
|
add_pending(pending, seen, buf->start, buf->len);
|
|
add_extent_rec(extent_cache, NULL, 0, buf->start, buf->len,
|
|
0, 1, 0);
|
|
|
|
add_backref(extent_cache, buf->start, root_objectid,
|
|
btrfs_header_generation(buf),
|
|
btrfs_header_level(buf), 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;
|
|
}
|
|
|
|
void print_usage(void) {
|
|
fprintf(stderr, "usage: btrfsck dev\n");
|
|
exit(1);
|
|
}
|
|
|
|
int main(int ac, char **av) {
|
|
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 extent_buffer *leaf;
|
|
int slot;
|
|
struct btrfs_root_item ri;
|
|
|
|
if (ac < 2)
|
|
print_usage();
|
|
|
|
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], 0);
|
|
|
|
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);
|
|
|
|
add_root_to_pending(root->fs_info->chunk_root->node, bits, bits_nr,
|
|
&extent_cache, &pending, &seen, &reada, &nodes,
|
|
root->fs_info->chunk_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];
|
|
slot = path.slots[0];
|
|
if (slot >= btrfs_header_nritems(path.nodes[0])) {
|
|
ret = btrfs_next_leaf(root, &path);
|
|
if (ret != 0)
|
|
break;
|
|
leaf = path.nodes[0];
|
|
slot = path.slots[0];
|
|
}
|
|
btrfs_item_key_to_cpu(leaf, &found_key, path.slots[0]);
|
|
if (btrfs_key_type(&found_key) == BTRFS_ROOT_ITEM_KEY) {
|
|
unsigned long offset;
|
|
struct extent_buffer *buf;
|
|
|
|
offset = btrfs_item_ptr_offset(leaf, path.slots[0]);
|
|
read_extent_buffer(leaf, &ri, offset, sizeof(ri));
|
|
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);
|
|
free_extent_buffer(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);
|
|
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;
|
|
}
|