summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fs/btrfs/tree-checker.c66
1 files changed, 55 insertions, 11 deletions
diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c
index 0ce91396b517..622334ffd240 100644
--- a/fs/btrfs/tree-checker.c
+++ b/fs/btrfs/tree-checker.c
@@ -163,6 +163,12 @@ static void dir_item_err(const struct extent_buffer *eb, int slot,
va_end(args);
}
+/* Record info for the last hit inode. */
+struct saved_inode_info {
+ u64 ino;
+ u32 mode;
+};
+
/*
* This functions checks prev_key->objectid, to ensure current key and prev_key
* share the same objectid as inode number.
@@ -204,15 +210,41 @@ static bool check_prev_ino(struct extent_buffer *leaf,
prev_key->objectid, key->objectid);
return false;
}
+
+static bool can_have_extent_data(struct extent_buffer *leaf,
+ struct btrfs_key *key, int slot, u8 fi_type,
+ const struct saved_inode_info *inode_info)
+{
+ /* No inode item in this leaf. */
+ if (inode_info->ino != key->objectid)
+ return true;
+ if (S_ISREG(inode_info->mode))
+ return true;
+ if (S_ISLNK(inode_info->mode)) {
+ /* For a symlink, the file extent item should always be inlined. */
+ if (unlikely(fi_type != BTRFS_FILE_EXTENT_INLINE))
+ return false;
+ return true;
+ }
+
+ /*
+ * The rest are special files, e.g. block/FIFO files, which cannnot
+ * have any file extent.
+ */
+ return false;
+}
+
static int check_extent_data_item(struct extent_buffer *leaf,
struct btrfs_key *key, int slot,
- struct btrfs_key *prev_key)
+ struct btrfs_key *prev_key,
+ const struct saved_inode_info *inode_info)
{
struct btrfs_fs_info *fs_info = leaf->fs_info;
struct btrfs_file_extent_item *fi;
u32 sectorsize = fs_info->sectorsize;
u32 item_size = btrfs_item_size(leaf, slot);
u64 extent_end;
+ u8 fi_type;
if (unlikely(!IS_ALIGNED(key->offset, sectorsize))) {
file_extent_err(leaf, slot,
@@ -243,12 +275,18 @@ static int check_extent_data_item(struct extent_buffer *leaf,
SZ_4K);
return -EUCLEAN;
}
- if (unlikely(btrfs_file_extent_type(leaf, fi) >=
- BTRFS_NR_FILE_EXTENT_TYPES)) {
+ fi_type = btrfs_file_extent_type(leaf, fi);
+ if (unlikely(fi_type >= BTRFS_NR_FILE_EXTENT_TYPES)) {
file_extent_err(leaf, slot,
"invalid type for file extent, have %u expect range [0, %u]",
- btrfs_file_extent_type(leaf, fi),
- BTRFS_NR_FILE_EXTENT_TYPES - 1);
+ fi_type, BTRFS_NR_FILE_EXTENT_TYPES - 1);
+ return -EUCLEAN;
+ }
+
+ if (unlikely(!can_have_extent_data(leaf, key, slot, fi_type, inode_info))) {
+ file_extent_err(leaf, slot,
+ "unexpected file extent item type %u for inode mode 0%o",
+ fi_type, inode_info->mode);
return -EUCLEAN;
}
@@ -270,7 +308,8 @@ static int check_extent_data_item(struct extent_buffer *leaf,
btrfs_file_extent_encryption(leaf, fi));
return -EUCLEAN;
}
- if (btrfs_file_extent_type(leaf, fi) == BTRFS_FILE_EXTENT_INLINE) {
+
+ if (fi_type == BTRFS_FILE_EXTENT_INLINE) {
/* Inline extent must have 0 as key offset */
if (unlikely(key->offset)) {
file_extent_err(leaf, slot,
@@ -1206,7 +1245,8 @@ static int check_dev_item(struct extent_buffer *leaf,
}
static int check_inode_item(struct extent_buffer *leaf,
- struct btrfs_key *key, int slot)
+ struct btrfs_key *key, int slot,
+ struct saved_inode_info *inode_info)
{
struct btrfs_fs_info *fs_info = leaf->fs_info;
struct btrfs_inode_item *iitem;
@@ -1291,6 +1331,8 @@ static int check_inode_item(struct extent_buffer *leaf,
ro_flags);
return -EUCLEAN;
}
+ inode_info->ino = key->objectid;
+ inode_info->mode = mode;
return 0;
}
@@ -2319,14 +2361,15 @@ static int check_free_space_bitmap(struct extent_buffer *leaf,
static enum btrfs_tree_block_status check_leaf_item(struct extent_buffer *leaf,
struct btrfs_key *key,
int slot,
- struct btrfs_key *prev_key)
+ struct btrfs_key *prev_key,
+ struct saved_inode_info *inode_info)
{
int ret = 0;
struct btrfs_chunk *chunk;
switch (key->type) {
case BTRFS_EXTENT_DATA_KEY:
- ret = check_extent_data_item(leaf, key, slot, prev_key);
+ ret = check_extent_data_item(leaf, key, slot, prev_key, inode_info);
break;
case BTRFS_EXTENT_CSUM_KEY:
ret = check_csum_item(leaf, key, slot, prev_key);
@@ -2356,7 +2399,7 @@ static enum btrfs_tree_block_status check_leaf_item(struct extent_buffer *leaf,
ret = check_dev_extent_item(leaf, key, slot, prev_key);
break;
case BTRFS_INODE_ITEM_KEY:
- ret = check_inode_item(leaf, key, slot);
+ ret = check_inode_item(leaf, key, slot, inode_info);
break;
case BTRFS_ROOT_ITEM_KEY:
ret = check_root_item(leaf, key, slot);
@@ -2404,6 +2447,7 @@ static enum btrfs_tree_block_status check_leaf_item(struct extent_buffer *leaf,
enum btrfs_tree_block_status __btrfs_check_leaf(struct extent_buffer *leaf)
{
struct btrfs_fs_info *fs_info = leaf->fs_info;
+ struct saved_inode_info inode_info = { 0 };
/* No valid key type is 0, so all key should be larger than this key */
struct btrfs_key prev_key = {0, 0, 0};
struct btrfs_key key;
@@ -2539,7 +2583,7 @@ enum btrfs_tree_block_status __btrfs_check_leaf(struct extent_buffer *leaf)
}
/* Check if the item size and content meet other criteria. */
- ret = check_leaf_item(leaf, &key, slot, &prev_key);
+ ret = check_leaf_item(leaf, &key, slot, &prev_key, &inode_info);
if (unlikely(ret != BTRFS_TREE_BLOCK_CLEAN))
return ret;