diff --git a/fs/btrfs/extent_map.c b/fs/btrfs/extent_map.c index aeb178c22cb97f71fd4d08b018382716cc1b8e52..6092a4eedc923608509adc81fc062c7d1f447062 100644 --- a/fs/btrfs/extent_map.c +++ b/fs/btrfs/extent_map.c @@ -702,11 +702,11 @@ static void drop_all_extent_maps_fast(struct extent_map_tree *tree) void btrfs_drop_extent_map_range(struct btrfs_inode *inode, u64 start, u64 end, bool skip_pinned) { - struct extent_map *split = NULL; - struct extent_map *split2 = NULL; + struct extent_map *split; + struct extent_map *split2; + struct extent_map *em; struct extent_map_tree *em_tree = &inode->extent_tree; u64 len = end - start + 1; - bool testend = true; WARN_ON(end < start); if (end == (u64)-1) { @@ -715,57 +715,73 @@ void btrfs_drop_extent_map_range(struct btrfs_inode *inode, u64 start, u64 end, return; } len = (u64)-1; - testend = false; + } else { + /* Make end offset exclusive for use in the loop below. */ + end++; } - while (1) { - struct extent_map *em; - u64 em_end; + + /* + * It's ok if we fail to allocate the extent maps, see the comment near + * the bottom of the loop below. We only need two spare extent maps in + * the worst case, where the first extent map that intersects our range + * starts before the range and the last extent map that intersects our + * range ends after our range (and they might be the same extent map), + * because we need to split those two extent maps at the boundaries. + */ + split = alloc_extent_map(); + split2 = alloc_extent_map(); + + write_lock(&em_tree->lock); + em = lookup_extent_mapping(em_tree, start, len); + + while (em) { + /* extent_map_end() returns exclusive value (last byte + 1). */ + const u64 em_end = extent_map_end(em); + struct extent_map *next_em = NULL; u64 gen; unsigned long flags; - bool ends_after_range = false; - bool no_splits = false; bool modified; bool compressed; - if (!split) - split = alloc_extent_map(); - if (!split2) - split2 = alloc_extent_map(); - if (!split || !split2) - no_splits = true; - - write_lock(&em_tree->lock); - em = lookup_extent_mapping(em_tree, start, len); - if (!em) { - write_unlock(&em_tree->lock); - break; + if (em_end < end) { + next_em = next_extent_map(em); + if (next_em) { + if (next_em->start < end) + refcount_inc(&next_em->refs); + else + next_em = NULL; + } } - em_end = extent_map_end(em); - if (testend && em_end > start + len) - ends_after_range = true; + if (skip_pinned && test_bit(EXTENT_FLAG_PINNED, &em->flags)) { - if (ends_after_range) { - free_extent_map(em); - write_unlock(&em_tree->lock); - break; - } start = em_end; - if (testend) + if (end != (u64)-1) len = start + len - em_end; - free_extent_map(em); - write_unlock(&em_tree->lock); - continue; + goto next; } - flags = em->flags; - gen = em->generation; - compressed = test_bit(EXTENT_FLAG_COMPRESSED, &em->flags); + clear_bit(EXTENT_FLAG_PINNED, &em->flags); clear_bit(EXTENT_FLAG_LOGGING, &flags); modified = !list_empty(&em->list); - if (no_splits) - goto next; + + /* + * The extent map does not cross our target range, so no need to + * split it, we can remove it directly. + */ + if (em->start >= start && em_end <= end) + goto remove_em; + + flags = em->flags; + gen = em->generation; + compressed = test_bit(EXTENT_FLAG_COMPRESSED, &em->flags); if (em->start < start) { + if (!split) { + split = split2; + split2 = NULL; + if (!split) + goto remove_em; + } split->start = em->start; split->len = start - em->start; @@ -796,7 +812,13 @@ void btrfs_drop_extent_map_range(struct btrfs_inode *inode, u64 start, u64 end, split = split2; split2 = NULL; } - if (ends_after_range) { + if (em_end > end) { + if (!split) { + split = split2; + split2 = NULL; + if (!split) + goto remove_em; + } split->start = start + len; split->len = em_end - (start + len); split->block_start = em->block_start; @@ -842,7 +864,7 @@ void btrfs_drop_extent_map_range(struct btrfs_inode *inode, u64 start, u64 end, free_extent_map(split); split = NULL; } -next: +remove_em: if (extent_map_in_tree(em)) { /* * If the extent map is still in the tree it means that @@ -864,20 +886,27 @@ void btrfs_drop_extent_map_range(struct btrfs_inode *inode, u64 start, u64 end, * full fsync, otherwise a fast fsync will miss this * extent if it's new and needs to be logged. */ - if ((em->start < start || ends_after_range) && modified) { - ASSERT(no_splits); + if ((em->start < start || em_end > end) && modified) { + ASSERT(!split); btrfs_set_inode_full_sync(inode); } remove_extent_mapping(em_tree, em); } - write_unlock(&em_tree->lock); - /* Once for us. */ + /* + * Once for the tree reference (we replaced or removed the + * extent map from the tree). + */ free_extent_map(em); - /* And once for the tree. */ +next: + /* Once for us (for our lookup reference). */ free_extent_map(em); + + em = next_em; } + write_unlock(&em_tree->lock); + free_extent_map(split); free_extent_map(split2); }