#define DIR_INDEX_MISSING (1<<18) /* INODE_INDEX not found */
#define DIR_INDEX_MISMATCH (1<<19) /* INODE_INDEX found but not match */
#define DIR_COUNT_AGAIN (1<<20) /* DIR isize should be recalculated */
+#define BG_ACCOUNTING_ERROR (1<<21) /* Block group accounting error */
static inline struct data_backref* to_data_backref(struct extent_backref *back)
{
}
}
+/*
+ * This function only handles BACKREF_MISSING,
+ * If corresponding extent item exists, increase the ref, else insert an extent
+ * item and backref.
+ *
+ * Returns error bits after repair.
+ */
+static int repair_tree_block_ref(struct btrfs_trans_handle *trans,
+ struct btrfs_root *root,
+ struct extent_buffer *node,
+ struct node_refs *nrefs, int level, int err)
+{
+ struct btrfs_fs_info *fs_info = root->fs_info;
+ struct btrfs_root *extent_root = fs_info->extent_root;
+ struct btrfs_path path;
+ struct btrfs_extent_item *ei;
+ struct btrfs_tree_block_info *bi;
+ struct btrfs_key key;
+ struct extent_buffer *eb;
+ u32 size = sizeof(*ei);
+ u32 node_size = root->fs_info->nodesize;
+ int insert_extent = 0;
+ int skinny_metadata = btrfs_fs_incompat(fs_info, SKINNY_METADATA);
+ int root_level = btrfs_header_level(root->node);
+ int generation;
+ int ret;
+ u64 owner;
+ u64 bytenr;
+ u64 flags = BTRFS_EXTENT_FLAG_TREE_BLOCK;
+ u64 parent = 0;
+
+ if ((err & BACKREF_MISSING) == 0)
+ return err;
+
+ WARN_ON(level > BTRFS_MAX_LEVEL);
+ WARN_ON(level < 0);
+
+ btrfs_init_path(&path);
+ bytenr = btrfs_header_bytenr(node);
+ owner = btrfs_header_owner(node);
+ generation = btrfs_header_generation(node);
+
+ key.objectid = bytenr;
+ key.type = (u8)-1;
+ key.offset = (u64)-1;
+
+ /* Search for the extent item */
+ ret = btrfs_search_slot(NULL, extent_root, &key, &path, 0, 0);
+ if (ret <= 0) {
+ ret = -EIO;
+ goto out;
+ }
+
+ ret = btrfs_previous_extent_item(extent_root, &path, bytenr);
+ if (ret)
+ insert_extent = 1;
+
+ /* calculate if the extent item flag is full backref or not */
+ if (nrefs->full_backref[level] != 0)
+ flags |= BTRFS_BLOCK_FLAG_FULL_BACKREF;
+
+ /* insert an extent item */
+ if (insert_extent) {
+ struct btrfs_disk_key copy_key;
+
+ generation = btrfs_header_generation(node);
+
+ if (level < root_level && nrefs->full_backref[level + 1] &&
+ owner != root->objectid) {
+ flags |= BTRFS_BLOCK_FLAG_FULL_BACKREF;
+ }
+
+ key.objectid = bytenr;
+ if (!skinny_metadata) {
+ key.type = BTRFS_EXTENT_ITEM_KEY;
+ key.offset = node_size;
+ size += sizeof(*bi);
+ } else {
+ key.type = BTRFS_METADATA_ITEM_KEY;
+ key.offset = level;
+ }
+
+ btrfs_release_path(&path);
+ ret = btrfs_insert_empty_item(trans, extent_root, &path, &key,
+ size);
+ if (ret)
+ goto out;
+
+ eb = path.nodes[0];
+ ei = btrfs_item_ptr(eb, path.slots[0], struct btrfs_extent_item);
+
+ btrfs_set_extent_refs(eb, ei, 0);
+ btrfs_set_extent_generation(eb, ei, generation);
+ btrfs_set_extent_flags(eb, ei, flags);
+
+ if (!skinny_metadata) {
+ bi = (struct btrfs_tree_block_info *)(ei + 1);
+ memset_extent_buffer(eb, 0, (unsigned long)bi,
+ sizeof(*bi));
+ btrfs_set_disk_key_objectid(©_key, root->objectid);
+ btrfs_set_disk_key_type(©_key, 0);
+ btrfs_set_disk_key_offset(©_key, 0);
+
+ btrfs_set_tree_block_level(eb, bi, level);
+ btrfs_set_tree_block_key(eb, bi, ©_key);
+ }
+ btrfs_mark_buffer_dirty(eb);
+ printf("Added an extent item [%llu %u]\n", bytenr, node_size);
+ btrfs_update_block_group(trans, extent_root, bytenr, node_size,
+ 1, 0);
+
+ nrefs->refs[level] = 0;
+ nrefs->full_backref[level] =
+ flags & BTRFS_BLOCK_FLAG_FULL_BACKREF;
+ btrfs_release_path(&path);
+ }
+
+ if (level < root_level && nrefs->full_backref[level + 1] &&
+ owner != root->objectid)
+ parent = nrefs->bytenr[level + 1];
+
+ /* increase the ref */
+ ret = btrfs_inc_extent_ref(trans, extent_root, bytenr, node_size,
+ parent, root->objectid, level, 0);
+
+ nrefs->refs[level]++;
+out:
+ btrfs_release_path(&path);
+ if (ret) {
+ error(
+ "failed to repair tree block ref start %llu root %llu due to %s",
+ bytenr, root->objectid, strerror(-ret));
+ } else {
+ printf("Added one tree block ref start %llu %s %llu\n",
+ bytenr, parent ? "parent" : "root",
+ parent ? parent : root->objectid);
+ err &= ~BACKREF_MISSING;
+ }
+
+ return err;
+}
+
static int check_inode_item(struct btrfs_root *root, struct btrfs_path *path,
unsigned int ext_ref);
static int check_tree_block_ref(struct btrfs_root *root,
ret = check_tree_block_ref(root, cur,
btrfs_header_bytenr(cur), btrfs_header_level(cur),
btrfs_header_owner(cur), nrefs);
+
+ if (repair && ret)
+ ret = repair_tree_block_ref(trans, root,
+ path->nodes[*level], nrefs, *level, ret);
err |= ret;
if (check_all && nrefs->need_check[*level] &&
*end, fkey->offset - *end);
if (!repair || ret) {
err |= FILE_EXTENT_ERROR;
- error("root %llu EXTENT_DATA[%llu %llu] interrupt",
- root->objectid, fkey->objectid, fkey->offset);
+ error(
+ "root %llu EXTENT_DATA[%llu %llu] interrupt, should start at %llu",
+ root->objectid, fkey->objectid, fkey->offset, *end);
}
}
struct cache_extent *cache;
int ret = 0;
int had_dups = 0;
+ int err = 0;
if (repair) {
/*
cur_err = 1;
}
+ err = cur_err;
remove_cache_extent(extent_cache, cache);
free_all_extent_backrefs(rec);
if (!init_extent_tree && repair && (!cur_err || fix))
}
return ret;
}
- return 0;
+
+ if (err)
+ err = -EIO;
+ return err;
}
u64 calc_stripe_length(u64 type, u64 length, int num_stripes)
}
}
+/*
+ * Extra (optional) check for dev_item size to report possbile problem on a new
+ * kernel.
+ */
+static void check_dev_size_alignment(u64 devid, u64 total_bytes, u32 sectorsize)
+{
+ if (!IS_ALIGNED(total_bytes, sectorsize)) {
+ warning(
+"unaligned total_bytes detected for devid %llu, have %llu should be aligned to %u",
+ devid, total_bytes, sectorsize);
+ warning(
+"this is OK for older kernel, but may cause kernel warning for newer kernels");
+ warning("this can be fixed by 'btrfs rescue fix-device-size'");
+ }
+}
+
+/*
+ * Unlike device size alignment check above, some super total_bytes check
+ * failure can lead to mount failure for newer kernel.
+ *
+ * So this function will return the error for a fatal super total_bytes problem.
+ */
+static bool is_super_size_valid(struct btrfs_fs_info *fs_info)
+{
+ struct btrfs_device *dev;
+ struct list_head *dev_list = &fs_info->fs_devices->devices;
+ u64 total_bytes = 0;
+ u64 super_bytes = btrfs_super_total_bytes(fs_info->super_copy);
+
+ list_for_each_entry(dev, dev_list, dev_list)
+ total_bytes += dev->total_bytes;
+
+ /* Important check, which can cause unmountable fs */
+ if (super_bytes < total_bytes) {
+ error("super total bytes %llu smaller than real device(s) size %llu",
+ super_bytes, total_bytes);
+ error("mounting this fs may fail for newer kernels");
+ error("this can be fixed by 'btrfs rescue fix-device-size'");
+ return false;
+ }
+
+ /*
+ * Optional check, just to make everything aligned and match with each
+ * other.
+ *
+ * For a btrfs-image restored fs, we don't need to check it anyway.
+ */
+ if (btrfs_super_flags(fs_info->super_copy) &
+ (BTRFS_SUPER_FLAG_METADUMP | BTRFS_SUPER_FLAG_METADUMP_V2))
+ return true;
+ if (!IS_ALIGNED(super_bytes, fs_info->sectorsize) ||
+ !IS_ALIGNED(total_bytes, fs_info->sectorsize) ||
+ super_bytes != total_bytes) {
+ warning("minor unaligned/mismatch device size detected");
+ warning(
+ "recommended to use 'btrfs rescue fix-device-size' to fix it");
+ }
+ return true;
+}
+
/* check btrfs_dev_item -> btrfs_dev_extent */
static int check_devices(struct rb_root *dev_cache,
struct device_extent_tree *dev_extent_cache)
if (err)
ret = err;
+ check_dev_size_alignment(dev_rec->devid, dev_rec->total_byte,
+ global_info->sectorsize);
dev_node = rb_next(dev_node);
}
list_for_each_entry(dext_rec, &dev_extent_cache->no_device_orphans,
goto again;
}
+static int check_extent_inline_ref(struct extent_buffer *eb,
+ struct btrfs_key *key, struct btrfs_extent_inline_ref *iref)
+{
+ int ret;
+ u8 type = btrfs_extent_inline_ref_type(eb, iref);
+
+ switch (type) {
+ case BTRFS_TREE_BLOCK_REF_KEY:
+ case BTRFS_EXTENT_DATA_REF_KEY:
+ case BTRFS_SHARED_BLOCK_REF_KEY:
+ case BTRFS_SHARED_DATA_REF_KEY:
+ ret = 0;
+ break;
+ default:
+ error("extent[%llu %u %llu] has unknown ref type: %d",
+ key->objectid, key->type, key->offset, type);
+ ret = UNKNOWN_TYPE;
+ break;
+ }
+
+ return ret;
+}
+
/*
* Check backrefs of a tree block given by @bytenr or @eb.
*
type = btrfs_extent_inline_ref_type(leaf, iref);
offset = btrfs_extent_inline_ref_offset(leaf, iref);
+ ret = check_extent_inline_ref(leaf, &key, iref);
+ if (ret) {
+ err |= ret;
+ break;
+ }
if (type == BTRFS_TREE_BLOCK_REF_KEY) {
if (offset == root->objectid)
found_ref = 1;
}
/*
+ * If @err contains BACKREF_MISSING then add extent of the
+ * file_extent_data_item.
+ *
+ * Returns error bits after reapir.
+ */
+static int repair_extent_data_item(struct btrfs_trans_handle *trans,
+ struct btrfs_root *root,
+ struct btrfs_path *pathp,
+ struct node_refs *nrefs,
+ int err)
+{
+ struct btrfs_file_extent_item *fi;
+ struct btrfs_key fi_key;
+ struct btrfs_key key;
+ struct btrfs_extent_item *ei;
+ struct btrfs_path path;
+ struct btrfs_root *extent_root = root->fs_info->extent_root;
+ struct extent_buffer *eb;
+ u64 size;
+ u64 disk_bytenr;
+ u64 num_bytes;
+ u64 parent;
+ u64 offset;
+ u64 extent_offset;
+ u64 file_offset;
+ int generation;
+ int slot;
+ int ret = 0;
+
+ eb = pathp->nodes[0];
+ slot = pathp->slots[0];
+ btrfs_item_key_to_cpu(eb, &fi_key, slot);
+ fi = btrfs_item_ptr(eb, slot, struct btrfs_file_extent_item);
+
+ if (btrfs_file_extent_type(eb, fi) == BTRFS_FILE_EXTENT_INLINE ||
+ btrfs_file_extent_disk_bytenr(eb, fi) == 0)
+ return err;
+
+ file_offset = fi_key.offset;
+ generation = btrfs_file_extent_generation(eb, fi);
+ disk_bytenr = btrfs_file_extent_disk_bytenr(eb, fi);
+ num_bytes = btrfs_file_extent_disk_num_bytes(eb, fi);
+ extent_offset = btrfs_file_extent_offset(eb, fi);
+ offset = file_offset - extent_offset;
+
+ /* now repair only adds backref */
+ if ((err & BACKREF_MISSING) == 0)
+ return err;
+
+ /* search extent item */
+ key.objectid = disk_bytenr;
+ key.type = BTRFS_EXTENT_ITEM_KEY;
+ key.offset = num_bytes;
+
+ btrfs_init_path(&path);
+ ret = btrfs_search_slot(NULL, extent_root, &key, &path, 0, 0);
+ if (ret < 0) {
+ ret = -EIO;
+ goto out;
+ }
+
+ /* insert an extent item */
+ if (ret > 0) {
+ key.objectid = disk_bytenr;
+ key.type = BTRFS_EXTENT_ITEM_KEY;
+ key.offset = num_bytes;
+ size = sizeof(*ei);
+
+ btrfs_release_path(&path);
+ ret = btrfs_insert_empty_item(trans, extent_root, &path, &key,
+ size);
+ if (ret)
+ goto out;
+ eb = path.nodes[0];
+ ei = btrfs_item_ptr(eb, path.slots[0], struct btrfs_extent_item);
+
+ btrfs_set_extent_refs(eb, ei, 0);
+ btrfs_set_extent_generation(eb, ei, generation);
+ btrfs_set_extent_flags(eb, ei, BTRFS_EXTENT_FLAG_DATA);
+
+ btrfs_mark_buffer_dirty(eb);
+ ret = btrfs_update_block_group(trans, extent_root, disk_bytenr,
+ num_bytes, 1, 0);
+ btrfs_release_path(&path);
+ }
+
+ if (nrefs->full_backref[0])
+ parent = btrfs_header_bytenr(eb);
+ else
+ parent = 0;
+
+ ret = btrfs_inc_extent_ref(trans, root, disk_bytenr, num_bytes, parent,
+ root->objectid,
+ parent ? BTRFS_FIRST_FREE_OBJECTID : fi_key.objectid,
+ offset);
+ if (ret) {
+ error(
+ "failed to increase extent data backref[%llu %llu] root %llu",
+ disk_bytenr, num_bytes, root->objectid);
+ goto out;
+ } else {
+ printf("Add one extent data backref [%llu %llu]\n",
+ disk_bytenr, num_bytes);
+ }
+
+ err &= ~BACKREF_MISSING;
+out:
+ if (ret)
+ error("can't repair root %llu extent data item[%llu %llu]",
+ root->objectid, disk_bytenr, num_bytes);
+ return err;
+}
+
+/*
* Check EXTENT_DATA item, mainly for its dbackref in extent tree
*
* Return >0 any error found and output error message
type = btrfs_extent_inline_ref_type(leaf, iref);
dref = (struct btrfs_extent_data_ref *)(&iref->offset);
+ ret = check_extent_inline_ref(leaf, &dbref_key, iref);
+ if (ret) {
+ err |= ret;
+ break;
+ }
if (type == BTRFS_EXTENT_DATA_REF_KEY) {
ref_root = btrfs_extent_data_ref_root(leaf, dref);
if (ref_root == root->objectid)
leaf = path.nodes[0];
slot = path.slots[0];
- if (slot >= btrfs_header_nritems(leaf))
+ if (slot >= btrfs_header_nritems(leaf) ||
+ btrfs_header_owner(leaf) != root_id)
goto next;
btrfs_item_key_to_cpu(leaf, &key, slot);
if (key.objectid != objectid || key.type != BTRFS_EXTENT_DATA_KEY)
struct btrfs_path path;
struct btrfs_key key;
struct btrfs_dev_extent *ptr;
+ u64 total_bytes;
u64 dev_id;
u64 used;
u64 total = 0;
dev_item = btrfs_item_ptr(eb, slot, struct btrfs_dev_item);
dev_id = btrfs_device_id(eb, dev_item);
used = btrfs_device_bytes_used(eb, dev_item);
+ total_bytes = btrfs_device_total_bytes(eb, dev_item);
key.objectid = dev_id;
key.type = BTRFS_DEV_EXTENT_KEY;
BTRFS_DEV_EXTENT_KEY, dev_id);
return ACCOUNTING_MISMATCH;
}
+ check_dev_size_alignment(dev_id, total_bytes, fs_info->sectorsize);
+
return 0;
}
error(
"block group[%llu %llu] used %llu but extent items used %llu",
bg_key.objectid, bg_key.offset, used, total);
- err |= ACCOUNTING_MISMATCH;
+ err |= BG_ACCOUNTING_ERROR;
}
return err;
}
switch (type) {
case BTRFS_EXTENT_DATA_KEY:
ret = check_extent_data_item(root, path, nrefs, account_bytes);
+ if (repair && ret)
+ ret = repair_extent_data_item(trans, root, path, nrefs,
+ ret);
err |= ret;
break;
case BTRFS_BLOCK_GROUP_ITEM_KEY:
ret = btrfs_fix_block_accounting(trans, root);
if (ret)
err |= ret;
+ else
+ err &= ~BG_ACCOUNTING_ERROR;
}
if (trans)
else
ret = check_chunks_and_extents(fs_info);
+ /* Also repair device size related problems */
+ if (repair && !ret) {
+ ret = btrfs_fix_device_and_super_size(fs_info);
+ if (ret > 0)
+ ret = 0;
+ }
return ret;
}
goto close_out;
}
+ if (!init_extent_tree) {
+ ret = repair_root_items(info);
+ if (ret < 0) {
+ err = !!ret;
+ error("failed to repair root items: %s", strerror(-ret));
+ goto close_out;
+ }
+ if (repair) {
+ fprintf(stderr, "Fixed %d roots.\n", ret);
+ ret = 0;
+ } else if (ret > 0) {
+ fprintf(stderr,
+ "Found %d roots with an outdated root item.\n",
+ ret);
+ fprintf(stderr,
+ "Please run a filesystem check with the option --repair to fix them.\n");
+ ret = 1;
+ err |= ret;
+ goto close_out;
+ }
+ }
+
ret = do_check_chunks_and_extents(info);
err |= !!ret;
if (ret)
error(
"errors found in extent allocation tree or chunk allocation");
- ret = repair_root_items(info);
- err |= !!ret;
- if (ret < 0) {
- error("failed to repair root items: %s", strerror(-ret));
- goto close_out;
- }
- if (repair) {
- fprintf(stderr, "Fixed %d roots.\n", ret);
- ret = 0;
- } else if (ret > 0) {
- fprintf(stderr,
- "Found %d roots with an outdated root item.\n",
- ret);
- fprintf(stderr,
- "Please run a filesystem check with the option --repair to fix them.\n");
- ret = 1;
- err |= !!ret;
- goto close_out;
- }
+ /* Only re-check super size after we checked and repaired the fs */
+ err |= !is_super_size_valid(info);
if (!ctx.progress_enabled) {
if (btrfs_fs_compat_ro(info, FREE_SPACE_TREE))