btrfs-progs: Introduce function to fix super block total bytes
[platform/upstream/btrfs-progs.git] / cmds-check.c
index 595b663..af8c62c 100644 (file)
@@ -133,6 +133,7 @@ struct data_backref {
 #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)
 {
@@ -10828,6 +10829,7 @@ static int check_extent_refs(struct btrfs_root *root,
        struct cache_extent *cache;
        int ret = 0;
        int had_dups = 0;
+       int err = 0;
 
        if (repair) {
                /*
@@ -10971,6 +10973,7 @@ static int check_extent_refs(struct btrfs_root *root,
                        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))
@@ -11003,7 +11006,10 @@ repair_abort:
                }
                return ret;
        }
-       return 0;
+
+       if (err)
+               err = -EIO;
+       return err;
 }
 
 u64 calc_stripe_length(u64 type, u64 length, int num_stripes)
@@ -11574,6 +11580,29 @@ loop:
        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.
  *
@@ -11708,6 +11737,11 @@ static int check_tree_block_ref(struct btrfs_root *root,
                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;
@@ -11767,6 +11801,120 @@ out:
 }
 
 /*
+ * 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
@@ -11871,6 +12019,11 @@ static int check_extent_data_item(struct btrfs_root *root,
                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)
@@ -12783,7 +12936,7 @@ out:
                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;
 }
@@ -13003,6 +13156,9 @@ again:
        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:
@@ -13174,6 +13330,8 @@ out:
                ret = btrfs_fix_block_accounting(trans, root);
                if (ret)
                        err |= ret;
+               else
+                       err &= ~BG_ACCOUNTING_ERROR;
        }
 
        if (trans)
@@ -14686,32 +14844,34 @@ int cmd_check(int argc, char **argv)
                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;
-       }
-
        if (!ctx.progress_enabled) {
                if (btrfs_fs_compat_ro(info, FREE_SPACE_TREE))
                        fprintf(stderr, "checking free space tree\n");