btrfs-progs: check: introduce function to find inode_ref
[platform/upstream/btrfs-progs.git] / cmds-check.c
index 6ce6716..e9a4d86 100644 (file)
@@ -41,6 +41,7 @@
 #include "rbtree-utils.h"
 #include "backref.h"
 #include "ulist.h"
+#include "hash.h"
 
 enum task_position {
        TASK_EXTENTS,
@@ -3826,6 +3827,447 @@ out:
        return err;
 }
 
+#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 */
+
+/*
+ * Find DIR_ITEM/DIR_INDEX for the given key and check it with the specified
+ * INODE_REF/INODE_EXTREF match.
+ *
+ * @root:      the root of the fs/file tree
+ * @ref_key:   the key of the INODE_REF/INODE_EXTREF
+ * @key:       the key of the DIR_ITEM/DIR_INDEX
+ * @index:     the index in the INODE_REF/INODE_EXTREF, be used to
+ *             distinguish root_dir between normal dir/file
+ * @name:      the name in the INODE_REF/INODE_EXTREF
+ * @namelen:   the length of name in the INODE_REF/INODE_EXTREF
+ * @mode:      the st_mode of INODE_ITEM
+ *
+ * Return 0 if no error occurred.
+ * Return ROOT_DIR_ERROR if found DIR_ITEM/DIR_INDEX for root_dir.
+ * Return DIR_ITEM_MISSING if couldn't find DIR_ITEM/DIR_INDEX for normal
+ * dir/file.
+ * Return DIR_ITEM_MISMATCH if INODE_REF/INODE_EXTREF and DIR_ITEM/DIR_INDEX
+ * not match for normal dir/file.
+ */
+static int find_dir_item(struct btrfs_root *root, struct btrfs_key *ref_key,
+                        struct btrfs_key *key, u64 index, char *name,
+                        u32 namelen, u32 mode)
+{
+       struct btrfs_path path;
+       struct extent_buffer *node;
+       struct btrfs_dir_item *di;
+       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;
+       int slot;
+       int ret;
+
+       btrfs_init_path(&path);
+       ret = btrfs_search_slot(NULL, root, key, &path, 0, 0);
+       if (ret < 0) {
+               ret = DIR_ITEM_MISSING;
+               goto out;
+       }
+
+       /* Process root dir and goto out*/
+       if (index == 0) {
+               if (ret == 0) {
+                       ret = ROOT_DIR_ERROR;
+                       error(
+                       "root %llu INODE %s[%llu %llu] ROOT_DIR shouldn't have %s",
+                               root->objectid,
+                               ref_key->type == BTRFS_INODE_REF_KEY ?
+                                       "REF" : "EXTREF",
+                               ref_key->objectid, ref_key->offset,
+                               key->type == BTRFS_DIR_ITEM_KEY ?
+                                       "DIR_ITEM" : "DIR_INDEX");
+               } else {
+                       ret = 0;
+               }
+
+               goto out;
+       }
+
+       /* Process normal file/dir */
+       if (ret > 0) {
+               ret = DIR_ITEM_MISSING;
+               error(
+               "root %llu INODE %s[%llu %llu] doesn't have related %s[%llu %llu] namelen %u filename %s filetype %d",
+                       root->objectid,
+                       ref_key->type == BTRFS_INODE_REF_KEY ? "REF" : "EXTREF",
+                       ref_key->objectid, ref_key->offset,
+                       key->type == BTRFS_DIR_ITEM_KEY ?
+                               "DIR_ITEM" : "DIR_INDEX",
+                       key->objectid, key->offset, namelen, name,
+                       imode_to_type(mode));
+               goto out;
+       }
+
+       /* Check whether inode_id/filetype/name match */
+       node = path.nodes[0];
+       slot = path.slots[0];
+       di = btrfs_item_ptr(node, slot, struct btrfs_dir_item);
+       total = btrfs_item_size_nr(node, slot);
+       while (cur < total) {
+               ret = DIR_ITEM_MISMATCH;
+               name_len = btrfs_dir_name_len(node, di);
+               data_len = btrfs_dir_data_len(node, di);
+
+               btrfs_dir_item_key_to_cpu(node, di, &location);
+               if (location.objectid != ref_key->objectid ||
+                   location.type !=  BTRFS_INODE_ITEM_KEY ||
+                   location.offset != 0)
+                       goto next;
+
+               filetype = btrfs_dir_type(node, di);
+               if (imode_to_type(mode) != filetype)
+                       goto next;
+
+               if (name_len <= BTRFS_NAME_LEN) {
+                       len = name_len;
+               } else {
+                       len = BTRFS_NAME_LEN;
+                       warning("root %llu %s[%llu %llu] name too long %u, trimmed",
+                       root->objectid,
+                       key->type == BTRFS_DIR_ITEM_KEY ?
+                       "DIR_ITEM" : "DIR_INDEX",
+                       key->objectid, key->offset, name_len);
+               }
+               read_extent_buffer(node, namebuf, (unsigned long)(di + 1), len);
+               if (len != namelen || strncmp(namebuf, name, len))
+                       goto next;
+
+               ret = 0;
+               goto out;
+next:
+               len = sizeof(*di) + name_len + data_len;
+               di = (struct btrfs_dir_item *)((char *)di + len);
+               cur += len;
+       }
+       if (ret == DIR_ITEM_MISMATCH)
+               error(
+               "root %llu INODE %s[%llu %llu] and %s[%llu %llu] mismatch namelen %u filename %s filetype %d",
+                       root->objectid,
+                       ref_key->type == BTRFS_INODE_REF_KEY ? "REF" : "EXTREF",
+                       ref_key->objectid, ref_key->offset,
+                       key->type == BTRFS_DIR_ITEM_KEY ?
+                               "DIR_ITEM" : "DIR_INDEX",
+                       key->objectid, key->offset, namelen, name,
+                       imode_to_type(mode));
+out:
+       btrfs_release_path(&path);
+       return ret;
+}
+
+/*
+ * Traverse the given INODE_REF 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_REF
+ * @refs:      the count of INODE_REF
+ * @mode:      the st_mode of INODE_ITEM
+ *
+ * Return 0 if no error occurred.
+ */
+static int check_inode_ref(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_ref *ref;
+       char namebuf[BTRFS_NAME_LEN] = {0};
+       u32 total;
+       u32 cur = 0;
+       u32 len;
+       u32 name_len;
+       u64 index;
+       int ret, err = 0;
+
+       ref = btrfs_item_ptr(node, slot, struct btrfs_inode_ref);
+       total = btrfs_item_size_nr(node, slot);
+
+next:
+       /* Update inode ref count */
+       (*refs)++;
+
+       index = btrfs_inode_ref_index(node, ref);
+       name_len = btrfs_inode_ref_name_len(node, ref);
+       if (name_len <= BTRFS_NAME_LEN) {
+               len = name_len;
+       } else {
+               len = BTRFS_NAME_LEN;
+               warning("root %llu INODE_REF[%llu %llu] name too long",
+                       root->objectid, ref_key->objectid, ref_key->offset);
+       }
+
+       read_extent_buffer(node, namebuf, (unsigned long)(ref + 1), len);
+
+       /* Check root dir ref name */
+       if (index == 0 && strncmp(namebuf, "..", name_len)) {
+               error("root %llu INODE_REF[%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 = ref_key->offset;
+       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 = ref_key->offset;
+       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(*ref) + name_len;
+       ref = (struct btrfs_inode_ref *)((char *)ref + len);
+       cur += len;
+       if (cur < total)
+               goto 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;
+}
+
 static int all_backpointers_checked(struct extent_record *rec, int print_errs)
 {
        struct list_head *cur = rec->backrefs.next;