btrfs-progs: check: count dir inode isize again
authorSu Yue <suy.fnst@cn.fujitsu.com>
Tue, 29 Aug 2017 06:11:26 +0000 (14:11 +0800)
committerDavid Sterba <dsterba@suse.com>
Mon, 16 Oct 2017 18:33:00 +0000 (20:33 +0200)
repair_ternary_lowmem() may delete dir_item(s), later traversal can cause
wrong isize of the dirctory inode.
Introduce count_dir_iszie() to count directory isize if any
dir_item(s) in the directory has been repaired.

check_dir_item() now returns DIR_COUNT_AGAIN means the inode should be
counted isize again.

It is unnessary to do recount after check_inode_ref(), since
inode_ref is irrelevant to isize.

Signed-off-by: Su Yue <suy.fnst@cn.fujitsu.com>
Signed-off-by: David Sterba <dsterba@suse.com>
cmds-check.c

index 3ac9b35..437a084 100644 (file)
@@ -132,6 +132,7 @@ struct data_backref {
 #define ROOT_REF_MISMATCH      (1<<17) /* ROOT_REF found but not match */
 #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 */
 
 static inline struct data_backref* to_data_backref(struct extent_backref *back)
 {
@@ -5033,6 +5034,89 @@ static int repair_dir_item(struct btrfs_root *root, u64 dirid, u64 ino,
        return err;
 }
 
+static int __count_dir_isize(struct btrfs_root *root, u64 ino, int type,
+               u64 *size_ret)
+{
+       struct btrfs_key key;
+       struct btrfs_path path;
+       u32 len;
+       struct btrfs_dir_item *di;
+       int ret;
+       int cur = 0;
+       int total = 0;
+
+       ASSERT(size_ret);
+       *size_ret = 0;
+
+       key.objectid = ino;
+       key.type = type;
+       key.offset = (u64)-1;
+
+       btrfs_init_path(&path);
+       ret = btrfs_search_slot(NULL, root, &key, &path, 0, 0);
+       if (ret < 0) {
+               ret = -EIO;
+               goto out;
+       }
+       /* if found, go to spacial case */
+       if (ret == 0)
+               goto special_case;
+
+loop:
+       ret = btrfs_previous_item(root, &path, ino, type);
+
+       if (ret) {
+               ret = 0;
+               goto out;
+       }
+
+special_case:
+       di = btrfs_item_ptr(path.nodes[0], path.slots[0], struct btrfs_dir_item);
+       cur = 0;
+       total = btrfs_item_size_nr(path.nodes[0], path.slots[0]);
+
+       while (cur < total) {
+               len = btrfs_dir_name_len(path.nodes[0], di);
+               if (len > BTRFS_NAME_LEN)
+                       len = BTRFS_NAME_LEN;
+               *size_ret += len;
+
+               len += btrfs_dir_data_len(path.nodes[0], di);
+               len += sizeof(*di);
+               di = (struct btrfs_dir_item *)((char *)di + len);
+               cur += len;
+       }
+       goto loop;
+
+out:
+       btrfs_release_path(&path);
+       return ret;
+}
+
+static int count_dir_isize(struct btrfs_root *root, u64 ino, u64 *size)
+{
+       u64 item_size;
+       u64 index_size;
+       int ret;
+
+       ASSERT(size);
+       ret = __count_dir_isize(root, ino, BTRFS_DIR_ITEM_KEY, &item_size);
+       if (ret)
+               goto out;
+
+       ret = __count_dir_isize(root, ino, BTRFS_DIR_INDEX_KEY, &index_size);
+       if (ret)
+               goto out;
+
+       *size = item_size + index_size;
+
+out:
+       if (ret)
+               error("failed to count root %llu INODE[%llu] root size",
+                     root->objectid, ino);
+       return ret;
+}
+
 /*
  * Traverse the given DIR_ITEM/DIR_INDEX and check related INODE_ITEM and
  * call find_inode_ref() to check related INODE_REF/INODE_EXTREF.
@@ -5044,6 +5128,7 @@ static int repair_dir_item(struct btrfs_root *root, u64 dirid, u64 ino,
  * @ext_ref:   the EXTENDED_IREF feature
  *
  * Return 0 if no error occurred.
+ * Return DIR_COUNT_AGAIN if the isize of the inode should be recalculated.
  */
 static int check_dir_item(struct btrfs_root *root, struct btrfs_key *di_key,
                          struct btrfs_path *path, u64 *size,
@@ -5084,6 +5169,7 @@ begin:
        /* since after repair, path and the dir item may be changed */
        if (need_research) {
                need_research = 0;
+               err |= DIR_COUNT_AGAIN;
                btrfs_release_path(path);
                ret = btrfs_search_slot(NULL, root, di_key, path, 0, 0);
                /* the item was deleted, let path point the last checked item */
@@ -5625,6 +5711,10 @@ static int check_inode_item(struct btrfs_root *root, struct btrfs_path *path,
 out:
        /* verify INODE_ITEM nlink/isize/nbytes */
        if (dir) {
+               if (repair && (err & DIR_COUNT_AGAIN)) {
+                       err &= ~DIR_COUNT_AGAIN;
+                       count_dir_isize(root, inode_id, &size);
+               }
                if (nlink != 1) {
                        err |= LINK_COUNT_ERROR;
                        error("root %llu DIR INODE[%llu] shouldn't have more than one link(%llu)",