}
/*
- * ext4_es_cache_extent() inserts information into the extent status
- * tree if and only if there isn't information about the range in
- * question already.
+ * ext4_es_cache_extent() inserts information into the extent status tree
+ * only if there is no existing information about the specified range or
+ * if the existing extents have the same status.
+ *
+ * Note that this interface is only used for caching on-disk extent
+ * information and cannot be used to convert existing extents in the extent
+ * status tree. To convert existing extents, use ext4_es_insert_extent()
+ * instead.
*/
void ext4_es_cache_extent(struct inode *inode, ext4_lblk_t lblk,
ext4_lblk_t len, ext4_fsblk_t pblk,
unsigned int status)
{
struct extent_status *es;
- struct extent_status newes;
+ struct extent_status chkes, newes;
ext4_lblk_t end = lblk + len - 1;
+ bool conflict = false;
+ int err;
if (EXT4_SB(inode->i_sb)->s_mount_state & EXT4_FC_REPLAY)
return;
BUG_ON(end < lblk);
write_lock(&EXT4_I(inode)->i_es_lock);
-
es = __es_tree_search(&EXT4_I(inode)->i_es_tree.root, lblk);
- if (!es || es->es_lblk > end)
- __es_insert_extent(inode, &newes, NULL);
+ if (es && es->es_lblk <= end) {
+ /* Found an extent that covers the entire range. */
+ if (es->es_lblk <= lblk && es->es_lblk + es->es_len > end) {
+ if (__es_check_extent_status(es, status, &chkes))
+ conflict = true;
+ goto unlock;
+ }
+ /* Check and remove all extents in range. */
+ err = __es_remove_extent(inode, lblk, end, status, NULL,
+ &chkes, NULL);
+ if (err) {
+ if (err == -EINVAL)
+ conflict = true;
+ goto unlock;
+ }
+ }
+ __es_insert_extent(inode, &newes, NULL);
+unlock:
write_unlock(&EXT4_I(inode)->i_es_lock);
+ if (!conflict)
+ return;
+ /*
+ * A hole in the on-disk extent but a delayed extent in the extent
+ * status tree, is allowed.
+ */
+ if (status == EXTENT_STATUS_HOLE &&
+ ext4_es_type(&chkes) == EXTENT_STATUS_DELAYED)
+ return;
+
+ ext4_warning_inode(inode,
+ "ES cache extent failed: add [%d,%d,%llu,0x%x] conflict with existing [%d,%d,%llu,0x%x]\n",
+ lblk, len, pblk, status, chkes.es_lblk, chkes.es_len,
+ ext4_es_pblock(&chkes), ext4_es_status(&chkes));
}
/*