btrfs-progs: check: introduce function to check dir_item
[platform/upstream/btrfs-progs.git] / cmds-check.c
index 8f1a287..dd8926d 100644 (file)
@@ -3830,6 +3830,9 @@ out:
 #define ROOT_DIR_ERROR         (1<<1)  /* bad ROOT_DIR */
 #define DIR_ITEM_MISSING       (1<<2)  /* DIR_ITEM not found */
 #define DIR_ITEM_MISMATCH      (1<<3)  /* DIR_ITEM found but not match */
+#define INODE_REF_MISSING      (1<<4)  /* INODE_REF/INODE_EXTREF not found */
+#define INODE_ITEM_MISSING     (1<<5)  /* INODE_ITEM not found */
+#define INODE_ITEM_MISMATCH    (1<<6)  /* INODE_ITEM found but not match */
 
 /*
  * Find DIR_ITEM/DIR_INDEX for the given key and check it with the specified
@@ -4041,6 +4044,355 @@ next:
        return err;
 }
 
+/*
+ * Traverse the given INODE_EXTREF and call find_dir_item() to find related
+ * DIR_ITEM/DIR_INDEX.
+ *
+ * @root:      the root of the fs/file tree
+ * @ref_key:   the key of the INODE_EXTREF
+ * @refs:      the count of INODE_EXTREF
+ * @mode:      the st_mode of INODE_ITEM
+ *
+ * Return 0 if no error occurred.
+ */
+static int check_inode_extref(struct btrfs_root *root,
+                             struct btrfs_key *ref_key,
+                             struct extent_buffer *node, int slot, u64 *refs,
+                             int mode)
+{
+       struct btrfs_key key;
+       struct btrfs_inode_extref *extref;
+       char namebuf[BTRFS_NAME_LEN] = {0};
+       u32 total;
+       u32 cur = 0;
+       u32 len;
+       u32 name_len;
+       u64 index;
+       u64 parent;
+       int ret;
+       int err = 0;
+
+       extref = btrfs_item_ptr(node, slot, struct btrfs_inode_extref);
+       total = btrfs_item_size_nr(node, slot);
+
+next:
+       /* update inode ref count */
+       (*refs)++;
+       name_len = btrfs_inode_extref_name_len(node, extref);
+       index = btrfs_inode_extref_index(node, extref);
+       parent = btrfs_inode_extref_parent(node, extref);
+       if (name_len <= BTRFS_NAME_LEN) {
+               len = name_len;
+       } else {
+               len = BTRFS_NAME_LEN;
+               warning("root %llu INODE_EXTREF[%llu %llu] name too long",
+                       root->objectid, ref_key->objectid, ref_key->offset);
+       }
+       read_extent_buffer(node, namebuf, (unsigned long)(extref + 1), len);
+
+       /* Check root dir ref name */
+       if (index == 0 && strncmp(namebuf, "..", name_len)) {
+               error("root %llu INODE_EXTREF[%llu %llu] ROOT_DIR name shouldn't be %s",
+                     root->objectid, ref_key->objectid, ref_key->offset,
+                     namebuf);
+               err |= ROOT_DIR_ERROR;
+       }
+
+       /* find related dir_index */
+       key.objectid = parent;
+       key.type = BTRFS_DIR_INDEX_KEY;
+       key.offset = index;
+       ret = find_dir_item(root, ref_key, &key, index, namebuf, len, mode);
+       err |= ret;
+
+       /* find related dir_item */
+       key.objectid = parent;
+       key.type = BTRFS_DIR_ITEM_KEY;
+       key.offset = btrfs_name_hash(namebuf, len);
+       ret = find_dir_item(root, ref_key, &key, index, namebuf, len, mode);
+       err |= ret;
+
+       len = sizeof(*extref) + name_len;
+       extref = (struct btrfs_inode_extref *)((char *)extref + len);
+       cur += len;
+
+       if (cur < total)
+               goto next;
+
+       return err;
+}
+
+/*
+ * Find INODE_REF/INODE_EXTREF for the given key and check it with the specified
+ * DIR_ITEM/DIR_INDEX match.
+ *
+ * @root:      the root of the fs/file tree
+ * @key:       the key of the INODE_REF/INODE_EXTREF
+ * @name:      the name in the INODE_REF/INODE_EXTREF
+ * @namelen:   the length of name in the INODE_REF/INODE_EXTREF
+ * @index:     the index in the INODE_REF/INODE_EXTREF, for DIR_ITEM set index
+ * to (u64)-1
+ * @ext_ref:   the EXTENDED_IREF feature
+ *
+ * Return 0 if no error occurred.
+ * Return >0 for error bitmap
+ */
+static int find_inode_ref(struct btrfs_root *root, struct btrfs_key *key,
+                         char *name, int namelen, u64 index,
+                         unsigned int ext_ref)
+{
+       struct btrfs_path path;
+       struct btrfs_inode_ref *ref;
+       struct btrfs_inode_extref *extref;
+       struct extent_buffer *node;
+       char ref_namebuf[BTRFS_NAME_LEN] = {0};
+       u32 total;
+       u32 cur = 0;
+       u32 len;
+       u32 ref_namelen;
+       u64 ref_index;
+       u64 parent;
+       u64 dir_id;
+       int slot;
+       int ret;
+
+       btrfs_init_path(&path);
+       ret = btrfs_search_slot(NULL, root, key, &path, 0, 0);
+       if (ret) {
+               ret = INODE_REF_MISSING;
+               goto extref;
+       }
+
+       node = path.nodes[0];
+       slot = path.slots[0];
+
+       ref = btrfs_item_ptr(node, slot, struct btrfs_inode_ref);
+       total = btrfs_item_size_nr(node, slot);
+
+       /* Iterate all entry of INODE_REF */
+       while (cur < total) {
+               ret = INODE_REF_MISSING;
+
+               ref_namelen = btrfs_inode_ref_name_len(node, ref);
+               ref_index = btrfs_inode_ref_index(node, ref);
+               if (index != (u64)-1 && index != ref_index)
+                       goto next_ref;
+
+               if (ref_namelen <= BTRFS_NAME_LEN) {
+                       len = ref_namelen;
+               } else {
+                       len = BTRFS_NAME_LEN;
+                       warning("root %llu INODE %s[%llu %llu] name too long",
+                               root->objectid,
+                               key->type == BTRFS_INODE_REF_KEY ?
+                                       "REF" : "EXTREF",
+                               key->objectid, key->offset);
+               }
+               read_extent_buffer(node, ref_namebuf, (unsigned long)(ref + 1),
+                                  len);
+
+               if (len != namelen || strncmp(ref_namebuf, name, len))
+                       goto next_ref;
+
+               ret = 0;
+               goto out;
+next_ref:
+               len = sizeof(*ref) + ref_namelen;
+               ref = (struct btrfs_inode_ref *)((char *)ref + len);
+               cur += len;
+       }
+
+extref:
+       /* Skip if not support EXTENDED_IREF feature */
+       if (!ext_ref)
+               goto out;
+
+       btrfs_release_path(&path);
+       btrfs_init_path(&path);
+
+       dir_id = key->offset;
+       key->type = BTRFS_INODE_EXTREF_KEY;
+       key->offset = btrfs_extref_hash(dir_id, name, namelen);
+
+       ret = btrfs_search_slot(NULL, root, key, &path, 0, 0);
+       if (ret) {
+               ret = INODE_REF_MISSING;
+               goto out;
+       }
+
+       node = path.nodes[0];
+       slot = path.slots[0];
+
+       extref = btrfs_item_ptr(node, slot, struct btrfs_inode_extref);
+       cur = 0;
+       total = btrfs_item_size_nr(node, slot);
+
+       /* Iterate all entry of INODE_EXTREF */
+       while (cur < total) {
+               ret = INODE_REF_MISSING;
+
+               ref_namelen = btrfs_inode_extref_name_len(node, extref);
+               ref_index = btrfs_inode_extref_index(node, extref);
+               parent = btrfs_inode_extref_parent(node, extref);
+               if (index != (u64)-1 && index != ref_index)
+                       goto next_extref;
+
+               if (parent != dir_id)
+                       goto next_extref;
+
+               if (ref_namelen <= BTRFS_NAME_LEN) {
+                       len = ref_namelen;
+               } else {
+                       len = BTRFS_NAME_LEN;
+                       warning("Warning: root %llu INODE %s[%llu %llu] name too long\n",
+                               root->objectid,
+                               key->type == BTRFS_INODE_REF_KEY ?
+                                       "REF" : "EXTREF",
+                               key->objectid, key->offset);
+               }
+               read_extent_buffer(node, ref_namebuf,
+                                  (unsigned long)(extref + 1), len);
+
+               if (len != namelen || strncmp(ref_namebuf, name, len))
+                       goto next_extref;
+
+               ret = 0;
+               goto out;
+
+next_extref:
+               len = sizeof(*extref) + ref_namelen;
+               extref = (struct btrfs_inode_extref *)((char *)extref + len);
+               cur += len;
+
+       }
+out:
+       btrfs_release_path(&path);
+       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.
+ *
+ * @root:      the root of the fs/file tree
+ * @key:       the key of the INODE_REF/INODE_EXTREF
+ * @size:      the st_size of the INODE_ITEM
+ * @ext_ref:   the EXTENDED_IREF feature
+ *
+ * Return 0 if no error occurred.
+ */
+static int check_dir_item(struct btrfs_root *root, struct btrfs_key *key,
+                         struct extent_buffer *node, int slot, u64 *size,
+                         unsigned int ext_ref)
+{
+       struct btrfs_dir_item *di;
+       struct btrfs_inode_item *ii;
+       struct btrfs_path path;
+       struct btrfs_key location;
+       char namebuf[BTRFS_NAME_LEN] = {0};
+       u32 total;
+       u32 cur = 0;
+       u32 len;
+       u32 name_len;
+       u32 data_len;
+       u8 filetype;
+       u32 mode;
+       u64 index;
+       int ret;
+       int err = 0;
+
+       /*
+        * For DIR_ITEM set index to (u64)-1, so that find_inode_ref
+        * ignore index check.
+        */
+       index = (key->type == BTRFS_DIR_INDEX_KEY) ? key->offset : (u64)-1;
+
+       di = btrfs_item_ptr(node, slot, struct btrfs_dir_item);
+       total = btrfs_item_size_nr(node, slot);
+
+       while (cur < total) {
+               data_len = btrfs_dir_data_len(node, di);
+               if (data_len)
+                       error("root %llu %s[%llu %llu] data_len shouldn't be %u",
+                             root->objectid, key->type == BTRFS_DIR_ITEM_KEY ?
+                             "DIR_ITEM" : "DIR_INDEX",
+                             key->objectid, key->offset, data_len);
+
+               name_len = btrfs_dir_name_len(node, di);
+               if (name_len <= BTRFS_NAME_LEN) {
+                       len = name_len;
+               } else {
+                       len = BTRFS_NAME_LEN;
+                       warning("root %llu %s[%llu %llu] name too long",
+                               root->objectid,
+                               key->type == BTRFS_DIR_ITEM_KEY ?
+                               "DIR_ITEM" : "DIR_INDEX",
+                               key->objectid, key->offset);
+               }
+               (*size) += name_len;
+
+               read_extent_buffer(node, namebuf, (unsigned long)(di + 1), len);
+               filetype = btrfs_dir_type(node, di);
+
+               btrfs_init_path(&path);
+               btrfs_dir_item_key_to_cpu(node, di, &location);
+
+               /* Ignore related ROOT_ITEM check */
+               if (location.type == BTRFS_ROOT_ITEM_KEY)
+                       goto next;
+
+               /* Check relative INODE_ITEM(existence/filetype) */
+               ret = btrfs_search_slot(NULL, root, &location, &path, 0, 0);
+               if (ret) {
+                       err |= INODE_ITEM_MISSING;
+                       error("root %llu %s[%llu %llu] couldn't find relative INODE_ITEM[%llu] namelen %u filename %s filetype %x",
+                             root->objectid, key->type == BTRFS_DIR_ITEM_KEY ?
+                             "DIR_ITEM" : "DIR_INDEX", key->objectid,
+                             key->offset, location.objectid, name_len,
+                             namebuf, filetype);
+                       goto next;
+               }
+
+               ii = btrfs_item_ptr(path.nodes[0], path.slots[0],
+                                   struct btrfs_inode_item);
+               mode = btrfs_inode_mode(path.nodes[0], ii);
+
+               if (imode_to_type(mode) != filetype) {
+                       err |= INODE_ITEM_MISMATCH;
+                       error("root %llu %s[%llu %llu] relative INODE_ITEM filetype mismatch namelen %u filename %s filetype %d",
+                             root->objectid, key->type == BTRFS_DIR_ITEM_KEY ?
+                             "DIR_ITEM" : "DIR_INDEX", key->objectid,
+                             key->offset, name_len, namebuf, filetype);
+               }
+
+               /* Check relative INODE_REF/INODE_EXTREF */
+               location.type = BTRFS_INODE_REF_KEY;
+               location.offset = key->objectid;
+               ret = find_inode_ref(root, &location, namebuf, len,
+                                      index, ext_ref);
+               err |= ret;
+               if (ret & INODE_REF_MISSING)
+                       error("root %llu %s[%llu %llu] relative INODE_REF missing namelen %u filename %s filetype %d",
+                             root->objectid, key->type == BTRFS_DIR_ITEM_KEY ?
+                             "DIR_ITEM" : "DIR_INDEX", key->objectid,
+                             key->offset, name_len, namebuf, filetype);
+
+next:
+               btrfs_release_path(&path);
+               len = sizeof(*di) + name_len + data_len;
+               di = (struct btrfs_dir_item *)((char *)di + len);
+               cur += len;
+
+               if (key->type == BTRFS_DIR_INDEX_KEY && cur < total) {
+                       error("root %llu DIR_INDEX[%llu %llu] should contain only one entry",
+                             root->objectid, key->objectid, key->offset);
+                       break;
+               }
+       }
+
+       return err;
+}
+
 static int all_backpointers_checked(struct extent_record *rec, int print_errs)
 {
        struct list_head *cur = rec->backrefs.next;