Btrfs progs v4.14
[platform/upstream/btrfs-progs.git] / cmds-check.c
index 595b663..a93ac2c 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)
 {
@@ -5866,8 +5867,9 @@ static int check_file_extent(struct btrfs_root *root, struct btrfs_key *fkey,
                                                *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);
                }
        }
 
@@ -10828,6 +10830,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 +10974,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 +11007,10 @@ repair_abort:
                }
                return ret;
        }
-       return 0;
+
+       if (err)
+               err = -EIO;
+       return err;
 }
 
 u64 calc_stripe_length(u64 type, u64 length, int num_stripes)
@@ -11232,6 +11239,66 @@ static int check_device_used(struct device_record *dev_rec,
        }
 }
 
+/*
+ * 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)
@@ -11249,6 +11316,8 @@ static int check_devices(struct rb_root *dev_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,
@@ -11574,6 +11643,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 +11800,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 +11864,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 +12082,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)
@@ -12252,7 +12468,8 @@ static int check_extent_data_backref(struct btrfs_fs_info *fs_info,
                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)
@@ -12604,6 +12821,7 @@ static int check_dev_item(struct btrfs_fs_info *fs_info,
        struct btrfs_path path;
        struct btrfs_key key;
        struct btrfs_dev_extent *ptr;
+       u64 total_bytes;
        u64 dev_id;
        u64 used;
        u64 total = 0;
@@ -12612,6 +12830,7 @@ static int check_dev_item(struct btrfs_fs_info *fs_info,
        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;
@@ -12656,6 +12875,8 @@ next:
                        BTRFS_DEV_EXTENT_KEY, dev_id);
                return ACCOUNTING_MISMATCH;
        }
+       check_dev_size_alignment(dev_id, total_bytes, fs_info->sectorsize);
+
        return 0;
 }
 
@@ -12783,7 +13004,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 +13224,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 +13398,8 @@ out:
                ret = btrfs_fix_block_accounting(trans, root);
                if (ret)
                        err |= ret;
+               else
+                       err &= ~BG_ACCOUNTING_ERROR;
        }
 
        if (trans)
@@ -13195,6 +13421,12 @@ static int do_check_chunks_and_extents(struct btrfs_fs_info *fs_info)
        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;
 }
 
@@ -14686,31 +14918,36 @@ 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;
-       }
+       /* 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))