#include <unistd.h>
#include <getopt.h>
#include <uuid/uuid.h>
-#include "kerncompat.h"
#include "ctree.h"
#include "volumes.h"
#include "repair.h"
#include "disk-io.h"
#include "print-tree.h"
#include "transaction.h"
-#include "list.h"
#include "version.h"
#include "utils.h"
#include "commands.h"
#include "free-space-cache.h"
+#include "btrfsck.h"
static u64 bytes_used = 0;
static u64 total_csum_bytes = 0;
#undef S_SHIFT
}
+static int device_record_compare(struct rb_node *node1, struct rb_node *node2)
+{
+ struct device_record *rec1;
+ struct device_record *rec2;
+
+ rec1 = rb_entry(node1, struct device_record, node);
+ rec2 = rb_entry(node2, struct device_record, node);
+ if (rec1->devid > rec2->devid)
+ return -1;
+ else if (rec1->devid < rec2->devid)
+ return 1;
+ else
+ return 0;
+}
+
static struct inode_record *clone_inode_rec(struct inode_record *orig_rec)
{
struct inode_record *rec;
return ret;
}
+static void free_chunk_record(struct cache_extent *cache)
+{
+ struct chunk_record *rec;
+
+ rec = container_of(cache, struct chunk_record, cache);
+ free(rec);
+}
+
+FREE_EXTENT_CACHE_BASED_TREE(chunk_cache, free_chunk_record);
+
+static void free_device_record(struct rb_node *node)
+{
+ struct device_record *rec;
+
+ rec = container_of(node, struct device_record, node);
+ free(rec);
+}
+
+FREE_RB_BASED_TREE(device_cache, free_device_record);
+
+static void block_group_tree_init(struct block_group_tree *tree)
+{
+ cache_tree_init(&tree->tree);
+ INIT_LIST_HEAD(&tree->block_groups);
+}
+
+static int insert_block_group_record(struct block_group_tree *tree,
+ struct block_group_record *bg_rec)
+{
+ int ret;
+
+ ret = insert_cache_extent(&tree->tree, &bg_rec->cache);
+ if (ret)
+ return ret;
+
+ list_add_tail(&bg_rec->list, &tree->block_groups);
+ return 0;
+}
+
+static void free_block_group_record(struct cache_extent *cache)
+{
+ struct block_group_record *rec;
+
+ rec = container_of(cache, struct block_group_record, cache);
+ free(rec);
+}
+
+static void free_block_group_tree(struct block_group_tree *tree)
+{
+ cache_tree_free_extents(&tree->tree, free_block_group_record);
+}
+
+static void device_extent_tree_init(struct device_extent_tree *tree)
+{
+ cache_tree_init(&tree->tree);
+ INIT_LIST_HEAD(&tree->no_chunk_orphans);
+ INIT_LIST_HEAD(&tree->no_device_orphans);
+}
+
+static int insert_device_extent_record(struct device_extent_tree *tree,
+ struct device_extent_record *de_rec)
+{
+ int ret;
+
+ /*
+ * Device extent is a bit different from the other extents, because
+ * the extents which belong to the different devices may have the
+ * same start and size, so we need use the special extent cache
+ * search/insert functions.
+ */
+ ret = insert_cache_extent2(&tree->tree, &de_rec->cache);
+ if (ret)
+ return ret;
+
+ list_add_tail(&de_rec->chunk_list, &tree->no_chunk_orphans);
+ list_add_tail(&de_rec->device_list, &tree->no_device_orphans);
+ return 0;
+}
+
+static void free_device_extent_record(struct cache_extent *cache)
+{
+ struct device_extent_record *rec;
+
+ rec = container_of(cache, struct device_extent_record, cache);
+ free(rec);
+}
+
+static void free_device_extent_tree(struct device_extent_tree *tree)
+{
+ cache_tree_free_extents(&tree->tree, free_device_extent_record);
+}
+
#ifdef BTRFS_COMPAT_EXTENT_TREE_V0
static int process_extent_ref_v0(struct cache_tree *extent_cache,
struct extent_buffer *leaf, int slot)
}
#endif
+static inline unsigned long chunk_record_size(int num_stripes)
+{
+ return sizeof(struct chunk_record) +
+ sizeof(struct stripe) * num_stripes;
+}
+
+static int process_chunk_item(struct cache_tree *chunk_cache,
+ struct btrfs_key *key, struct extent_buffer *eb, int slot)
+{
+ struct btrfs_chunk *ptr;
+ struct chunk_record *rec;
+ int num_stripes, i;
+ int ret = 0;
+
+ ptr = btrfs_item_ptr(eb,
+ slot, struct btrfs_chunk);
+
+ num_stripes = btrfs_chunk_num_stripes(eb, ptr);
+
+ rec = malloc(chunk_record_size(num_stripes));
+ if (!rec) {
+ fprintf(stderr, "memory allocation failed\n");
+ return -ENOMEM;
+ }
+
+ rec->cache.start = key->offset;
+ rec->cache.size = btrfs_chunk_length(eb, ptr);
+
+ rec->objectid = key->objectid;
+ rec->type = key->type;
+ rec->offset = key->offset;
+
+ rec->length = rec->cache.size;
+ rec->type_flags = btrfs_chunk_type(eb, ptr);
+ rec->num_stripes = num_stripes;
+ rec->sub_stripes = btrfs_chunk_sub_stripes(eb, ptr);
+
+ for (i = 0; i < rec->num_stripes; ++i) {
+ rec->stripes[i].devid =
+ btrfs_stripe_devid_nr(eb, ptr, i);
+ rec->stripes[i].offset =
+ btrfs_stripe_offset_nr(eb, ptr, i);
+ }
+
+ ret = insert_cache_extent(chunk_cache, &rec->cache);
+ if (ret) {
+ fprintf(stderr, "Chunk[%llu, %llu] existed.\n",
+ rec->offset, rec->length);
+ free(rec);
+ }
+
+ return ret;
+}
+
+static int process_device_item(struct rb_root *dev_cache,
+ struct btrfs_key *key, struct extent_buffer *eb, int slot)
+{
+ struct btrfs_dev_item *ptr;
+ struct device_record *rec;
+ int ret = 0;
+
+ ptr = btrfs_item_ptr(eb,
+ slot, struct btrfs_dev_item);
+
+ rec = malloc(sizeof(*rec));
+ if (!rec) {
+ fprintf(stderr, "memory allocation failed\n");
+ return -ENOMEM;
+ }
+
+ rec->devid = key->offset;
+
+ rec->objectid = key->objectid;
+ rec->type = key->type;
+ rec->offset = key->offset;
+
+ rec->devid = btrfs_device_id(eb, ptr);
+ rec->total_byte = btrfs_device_total_bytes(eb, ptr);
+ rec->byte_used = btrfs_device_bytes_used(eb, ptr);
+
+ ret = rb_insert(dev_cache, &rec->node, device_record_compare);
+ if (ret) {
+ fprintf(stderr, "Device[%llu] existed.\n", rec->devid);
+ free(rec);
+ }
+
+ return ret;
+}
+
+static int process_block_group_item(struct block_group_tree *block_group_cache,
+ struct btrfs_key *key, struct extent_buffer *eb, int slot)
+{
+ struct btrfs_block_group_item *ptr;
+ struct block_group_record *rec;
+ int ret = 0;
+
+ ptr = btrfs_item_ptr(eb, slot,
+ struct btrfs_block_group_item);
+
+ rec = malloc(sizeof(*rec));
+ if (!rec) {
+ fprintf(stderr, "memory allocation failed\n");
+ return -ENOMEM;
+ }
+
+ rec->cache.start = key->objectid;
+ rec->cache.size = key->offset;
+
+ rec->objectid = key->objectid;
+ rec->type = key->type;
+ rec->offset = key->offset;
+ rec->flags = btrfs_disk_block_group_flags(eb, ptr);
+
+ ret = insert_block_group_record(block_group_cache, rec);
+ if (ret) {
+ fprintf(stderr, "Block Group[%llu, %llu] existed.\n",
+ rec->objectid, rec->offset);
+ free(rec);
+ }
+
+ return ret;
+}
+
+static int
+process_device_extent_item(struct device_extent_tree *dev_extent_cache,
+ struct btrfs_key *key, struct extent_buffer *eb,
+ int slot)
+{
+ int ret = 0;
+
+ struct btrfs_dev_extent *ptr;
+ struct device_extent_record *rec;
+
+ ptr = btrfs_item_ptr(eb,
+ slot, struct btrfs_dev_extent);
+
+ rec = malloc(sizeof(*rec));
+ if (!rec) {
+ fprintf(stderr, "memory allocation failed\n");
+ return -ENOMEM;
+ }
+
+ rec->cache.objectid = key->objectid;
+ rec->cache.start = key->offset;
+
+ rec->objectid = key->objectid;
+ rec->type = key->type;
+ rec->offset = key->offset;
+
+ rec->chunk_objecteid =
+ btrfs_dev_extent_chunk_objectid(eb, ptr);
+ rec->chunk_offset =
+ btrfs_dev_extent_chunk_offset(eb, ptr);
+ rec->length = btrfs_dev_extent_length(eb, ptr);
+ rec->cache.size = rec->length;
+
+ ret = insert_device_extent_record(dev_extent_cache, rec);
+ if (ret) {
+ fprintf(stderr, "Device extent[%llu, %llu, %llu] existed.\n",
+ rec->objectid, rec->offset, rec->length);
+ free(rec);
+ }
+
+ return ret;
+}
+
static int process_extent_item(struct btrfs_root *root,
struct cache_tree *extent_cache,
struct extent_buffer *eb, int slot)
struct cache_tree *seen,
struct cache_tree *reada,
struct cache_tree *nodes,
- struct cache_tree *extent_cache)
+ struct cache_tree *extent_cache,
+ struct cache_tree *chunk_cache,
+ struct rb_root *dev_cache,
+ struct block_group_tree *block_group_cache,
+ struct device_extent_tree *dev_extent_cache)
{
struct extent_buffer *buf;
u64 bytenr;
btrfs_item_size_nr(buf, i);
continue;
}
+ if (key.type == BTRFS_CHUNK_ITEM_KEY) {
+ process_chunk_item(chunk_cache, &key, buf, i);
+ continue;
+ }
+ if (key.type == BTRFS_DEV_ITEM_KEY) {
+ process_device_item(dev_cache, &key, buf, i);
+ continue;
+ }
if (key.type == BTRFS_BLOCK_GROUP_ITEM_KEY) {
+ process_block_group_item(block_group_cache,
+ &key, buf, i);
+ continue;
+ }
+ if (key.type == BTRFS_DEV_EXTENT_KEY) {
+ process_device_extent_item(dev_extent_cache,
+ &key, buf, i);
continue;
+
}
if (key.type == BTRFS_EXTENT_REF_V0_KEY) {
#ifdef BTRFS_COMPAT_EXTENT_TREE_V0
return err;
}
-static int check_extents(struct btrfs_root *root, int repair)
+static u64 calc_stripe_length(struct chunk_record *chunk_rec)
+{
+ u64 stripe_size;
+
+ if (chunk_rec->type_flags & BTRFS_BLOCK_GROUP_RAID0) {
+ stripe_size = chunk_rec->length;
+ stripe_size /= chunk_rec->num_stripes;
+ } else if (chunk_rec->type_flags & BTRFS_BLOCK_GROUP_RAID10) {
+ stripe_size = chunk_rec->length * 2;
+ stripe_size /= chunk_rec->num_stripes;
+ } else if (chunk_rec->type_flags & BTRFS_BLOCK_GROUP_RAID5) {
+ stripe_size = chunk_rec->length;
+ stripe_size /= (chunk_rec->num_stripes - 1);
+ } else if (chunk_rec->type_flags & BTRFS_BLOCK_GROUP_RAID6) {
+ stripe_size = chunk_rec->length;
+ stripe_size /= (chunk_rec->num_stripes - 2);
+ } else {
+ stripe_size = chunk_rec->length;
+ }
+ return stripe_size;
+}
+
+static int check_chunk_refs(struct chunk_record *chunk_rec,
+ struct block_group_tree *block_group_cache,
+ struct device_extent_tree *dev_extent_cache)
+{
+ struct cache_extent *block_group_item;
+ struct block_group_record *block_group_rec;
+ struct cache_extent *dev_extent_item;
+ struct device_extent_record *dev_extent_rec;
+ u64 devid;
+ u64 offset;
+ u64 length;
+ int i;
+ int ret = 0;
+
+ block_group_item = lookup_cache_extent(&block_group_cache->tree,
+ chunk_rec->offset,
+ chunk_rec->length);
+ if (block_group_item) {
+ block_group_rec = container_of(block_group_item,
+ struct block_group_record,
+ cache);
+ if (chunk_rec->length != block_group_rec->offset ||
+ chunk_rec->offset != block_group_rec->objectid ||
+ chunk_rec->type_flags != block_group_rec->flags) {
+ fprintf(stderr,
+ "Chunk[%llu, %u, %llu]: length(%llu), offset(%llu), type(%llu) mismatch with block group[%llu, %u, %llu]: offset(%llu), objectid(%llu), flags(%llu)\n",
+ chunk_rec->objectid,
+ chunk_rec->type,
+ chunk_rec->offset,
+ chunk_rec->length,
+ chunk_rec->offset,
+ chunk_rec->type_flags,
+ block_group_rec->objectid,
+ block_group_rec->type,
+ block_group_rec->offset,
+ block_group_rec->offset,
+ block_group_rec->objectid,
+ block_group_rec->flags);
+ ret = -1;
+ }
+ list_del(&block_group_rec->list);
+ } else {
+ fprintf(stderr,
+ "Chunk[%llu, %u, %llu]: length(%llu), offset(%llu), type(%llu) is not found in block group\n",
+ chunk_rec->objectid,
+ chunk_rec->type,
+ chunk_rec->offset,
+ chunk_rec->length,
+ chunk_rec->offset,
+ chunk_rec->type_flags);
+ ret = -1;
+ }
+
+ length = calc_stripe_length(chunk_rec);
+ for (i = 0; i < chunk_rec->num_stripes; ++i) {
+ devid = chunk_rec->stripes[i].devid;
+ offset = chunk_rec->stripes[i].offset;
+ dev_extent_item = lookup_cache_extent2(&dev_extent_cache->tree,
+ devid, offset, length);
+ if (dev_extent_item) {
+ dev_extent_rec = container_of(dev_extent_item,
+ struct device_extent_record,
+ cache);
+ if (dev_extent_rec->objectid != devid ||
+ dev_extent_rec->offset != offset ||
+ dev_extent_rec->chunk_offset != chunk_rec->offset ||
+ dev_extent_rec->length != length) {
+ fprintf(stderr,
+ "Chunk[%llu, %u, %llu] stripe[%llu, %llu] dismatch dev extent[%llu, %llu, %llu]\n",
+ chunk_rec->objectid,
+ chunk_rec->type,
+ chunk_rec->offset,
+ chunk_rec->stripes[i].devid,
+ chunk_rec->stripes[i].offset,
+ dev_extent_rec->objectid,
+ dev_extent_rec->offset,
+ dev_extent_rec->length);
+ ret = -1;
+ }
+ list_del(&dev_extent_rec->chunk_list);
+ } else {
+ fprintf(stderr,
+ "Chunk[%llu, %u, %llu] stripe[%llu, %llu] is not found in dev extent\n",
+ chunk_rec->objectid,
+ chunk_rec->type,
+ chunk_rec->offset,
+ chunk_rec->stripes[i].devid,
+ chunk_rec->stripes[i].offset);
+ ret = -1;
+ }
+ }
+ return ret;
+}
+
+/* check btrfs_chunk -> btrfs_dev_extent / btrfs_block_group_item */
+static int check_chunks(struct cache_tree *chunk_cache,
+ struct block_group_tree *block_group_cache,
+ struct device_extent_tree *dev_extent_cache)
{
+ struct cache_extent *chunk_item;
+ struct chunk_record *chunk_rec;
+ struct block_group_record *bg_rec;
+ struct device_extent_record *dext_rec;
+ int err;
+ int ret = 0;
+
+ chunk_item = first_cache_extent(chunk_cache);
+ while (chunk_item) {
+ chunk_rec = container_of(chunk_item, struct chunk_record,
+ cache);
+ err = check_chunk_refs(chunk_rec, block_group_cache,
+ dev_extent_cache);
+ if (err)
+ ret = err;
+
+ chunk_item = next_cache_extent(chunk_item);
+ }
+
+ list_for_each_entry(bg_rec, &block_group_cache->block_groups, list) {
+ fprintf(stderr,
+ "Block group[%llu, %llu] (flags = %llu) didn't find the relative chunk.\n",
+ bg_rec->objectid, bg_rec->offset, bg_rec->flags);
+ if (!ret)
+ ret = 1;
+ }
+
+ list_for_each_entry(dext_rec, &dev_extent_cache->no_chunk_orphans,
+ chunk_list) {
+ fprintf(stderr,
+ "Device extent[%llu, %llu, %llu] didn't find the relative chunk.\n",
+ dext_rec->objectid, dext_rec->offset, dext_rec->length);
+ if (!ret)
+ ret = 1;
+ }
+ return ret;
+}
+
+
+static int check_device_used(struct device_record *dev_rec,
+ struct device_extent_tree *dext_cache)
+{
+ struct cache_extent *cache;
+ struct device_extent_record *dev_extent_rec;
+ u64 total_byte = 0;
+
+ cache = search_cache_extent2(&dext_cache->tree, dev_rec->devid, 0);
+ while (cache) {
+ dev_extent_rec = container_of(cache,
+ struct device_extent_record,
+ cache);
+ if (dev_extent_rec->objectid != dev_rec->devid)
+ break;
+
+ list_del(&dev_extent_rec->device_list);
+ total_byte += dev_extent_rec->length;
+ cache = next_cache_extent(cache);
+ }
+
+ if (total_byte != dev_rec->byte_used) {
+ fprintf(stderr,
+ "Dev extent's total-byte(%llu) is not equal to byte-used(%llu) in dev[%llu, %u, %llu]\n",
+ total_byte, dev_rec->byte_used, dev_rec->objectid,
+ dev_rec->type, dev_rec->offset);
+ return -1;
+ } else {
+ return 0;
+ }
+}
+
+/* check btrfs_dev_item -> btrfs_dev_extent */
+static int check_devices(struct rb_root *dev_cache,
+ struct device_extent_tree *dev_extent_cache)
+{
+ struct rb_node *dev_node;
+ struct device_record *dev_rec;
+ struct device_extent_record *dext_rec;
+ int err;
+ int ret = 0;
+
+ dev_node = rb_first(dev_cache);
+ while (dev_node) {
+ dev_rec = container_of(dev_node, struct device_record, node);
+ err = check_device_used(dev_rec, dev_extent_cache);
+ if (err)
+ ret = err;
+
+ dev_node = rb_next(dev_node);
+ }
+ list_for_each_entry(dext_rec, &dev_extent_cache->no_device_orphans,
+ device_list) {
+ fprintf(stderr,
+ "Device extent[%llu, %llu, %llu] didn't find its device.\n",
+ dext_rec->objectid, dext_rec->offset, dext_rec->length);
+ if (!ret)
+ ret = 1;
+ }
+ return ret;
+}
+
+static int check_chunks_and_extents(struct btrfs_root *root, int repair)
+{
+ struct rb_root dev_cache;
+ struct cache_tree chunk_cache;
+ struct block_group_tree block_group_cache;
+ struct device_extent_tree dev_extent_cache;
struct cache_tree extent_cache;
struct cache_tree seen;
struct cache_tree pending;
struct btrfs_path path;
struct btrfs_key key;
struct btrfs_key found_key;
- int ret;
+ int ret, err = 0;
u64 last = 0;
struct block_info *bits;
int bits_nr;
int slot;
struct btrfs_root_item ri;
+ dev_cache = RB_ROOT;
+ cache_tree_init(&chunk_cache);
+ block_group_tree_init(&block_group_cache);
+ device_extent_tree_init(&dev_extent_cache);
+
cache_tree_init(&extent_cache);
cache_tree_init(&seen);
cache_tree_init(&pending);
btrfs_release_path(root, &path);
while(1) {
ret = run_next_block(root, bits, bits_nr, &last, &pending,
- &seen, &reada, &nodes, &extent_cache);
+ &seen, &reada, &nodes, &extent_cache,
+ &chunk_cache, &dev_cache,
+ &block_group_cache, &dev_extent_cache);
if (ret != 0)
break;
}
- ret = check_extent_refs(trans, root, &extent_cache, repair);
+ ret = check_extent_refs(trans, root, &extent_cache, repair);
if (ret == -EAGAIN) {
ret = btrfs_commit_transaction(trans, root);
if (ret)
goto again;
}
+ err = check_chunks(&chunk_cache, &block_group_cache,
+ &dev_extent_cache);
+ if (err && !ret)
+ ret = err;
+
+ err = check_devices(&dev_cache, &dev_extent_cache);
+ if (err && !ret)
+ ret = err;
+
if (trans) {
int err;
root->fs_info->corrupt_blocks = NULL;
}
free(bits);
+ free_chunk_cache_tree(&chunk_cache);
+ free_device_cache_tree(&dev_cache);
+ free_block_group_tree(&block_group_cache);
+ free_device_extent_tree(&dev_extent_cache);
return ret;
}
exit(1);
goto out;
}
- ret = check_extents(root, repair);
+ ret = check_chunks_and_extents(root, repair);
if (ret)
- fprintf(stderr, "Errors found in extent allocation tree\n");
+ fprintf(stderr, "Errors found in extent allocation tree or chunk allocation\n");
fprintf(stderr, "checking free space cache\n");
ret = check_space_cache(root);