summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSu Yue <suy.fnst@cn.fujitsu.com>2017-08-29 14:11:26 +0800
committerDavid Sterba <dsterba@suse.com>2017-10-16 20:33:00 +0200
commit2194fab07178e2bc3d6b62f8e4247baaf6308620 (patch)
tree45713324e36627458109d99d34a0758396e074b5
parent5da13ab9b5894f48c993405e0ad97a8c1fca6336 (diff)
btrfs-progs: check: count dir inode isize again
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>
-rw-r--r--cmds-check.c90
1 files changed, 90 insertions, 0 deletions
diff --git a/cmds-check.c b/cmds-check.c
index 3ac9b355..437a0842 100644
--- a/cmds-check.c
+++ b/cmds-check.c
@@ -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)",