| // SPDX-License-Identifier: GPL-2.0+ | |
| /* | |
| * Crossported from the same named file of btrfs-progs. | |
| * | |
| * Minor modification to include headers. | |
| */ | |
| #include <linux/kernel.h> | |
| #include <linux/rbtree.h> | |
| #include <linux/errno.h> | |
| #include <linux/bug.h> | |
| #include <stdlib.h> | |
| #include "extent-cache.h" | |
| #include "common/rbtree-utils.h" | |
| struct cache_extent_search_range { | |
| u64 objectid; | |
| u64 start; | |
| u64 size; | |
| }; | |
| static int cache_tree_comp_range(struct rb_node *node, void *data) | |
| { | |
| struct cache_extent *entry; | |
| struct cache_extent_search_range *range; | |
| range = (struct cache_extent_search_range *)data; | |
| entry = rb_entry(node, struct cache_extent, rb_node); | |
| if (entry->start + entry->size <= range->start) | |
| return 1; | |
| else if (range->start + range->size <= entry->start) | |
| return -1; | |
| else | |
| return 0; | |
| } | |
| static int cache_tree_comp_nodes(struct rb_node *node1, struct rb_node *node2) | |
| { | |
| struct cache_extent *entry; | |
| struct cache_extent_search_range range; | |
| entry = rb_entry(node2, struct cache_extent, rb_node); | |
| range.start = entry->start; | |
| range.size = entry->size; | |
| return cache_tree_comp_range(node1, (void *)&range); | |
| } | |
| static int cache_tree_comp_range2(struct rb_node *node, void *data) | |
| { | |
| struct cache_extent *entry; | |
| struct cache_extent_search_range *range; | |
| range = (struct cache_extent_search_range *)data; | |
| entry = rb_entry(node, struct cache_extent, rb_node); | |
| if (entry->objectid < range->objectid) | |
| return 1; | |
| else if (entry->objectid > range->objectid) | |
| return -1; | |
| else if (entry->start + entry->size <= range->start) | |
| return 1; | |
| else if (range->start + range->size <= entry->start) | |
| return -1; | |
| else | |
| return 0; | |
| } | |
| static int cache_tree_comp_nodes2(struct rb_node *node1, struct rb_node *node2) | |
| { | |
| struct cache_extent *entry; | |
| struct cache_extent_search_range range; | |
| entry = rb_entry(node2, struct cache_extent, rb_node); | |
| range.objectid = entry->objectid; | |
| range.start = entry->start; | |
| range.size = entry->size; | |
| return cache_tree_comp_range2(node1, (void *)&range); | |
| } | |
| void cache_tree_init(struct cache_tree *tree) | |
| { | |
| tree->root = RB_ROOT; | |
| } | |
| static struct cache_extent *alloc_cache_extent(u64 start, u64 size) | |
| { | |
| struct cache_extent *pe = malloc(sizeof(*pe)); | |
| if (!pe) | |
| return pe; | |
| pe->objectid = 0; | |
| pe->start = start; | |
| pe->size = size; | |
| return pe; | |
| } | |
| int add_cache_extent(struct cache_tree *tree, u64 start, u64 size) | |
| { | |
| struct cache_extent *pe = alloc_cache_extent(start, size); | |
| int ret; | |
| if (!pe) | |
| return -ENOMEM; | |
| ret = insert_cache_extent(tree, pe); | |
| if (ret) | |
| free(pe); | |
| return ret; | |
| } | |
| int insert_cache_extent(struct cache_tree *tree, struct cache_extent *pe) | |
| { | |
| return rb_insert(&tree->root, &pe->rb_node, cache_tree_comp_nodes); | |
| } | |
| int insert_cache_extent2(struct cache_tree *tree, struct cache_extent *pe) | |
| { | |
| return rb_insert(&tree->root, &pe->rb_node, cache_tree_comp_nodes2); | |
| } | |
| struct cache_extent *lookup_cache_extent(struct cache_tree *tree, | |
| u64 start, u64 size) | |
| { | |
| struct rb_node *node; | |
| struct cache_extent *entry; | |
| struct cache_extent_search_range range; | |
| range.start = start; | |
| range.size = size; | |
| node = rb_search(&tree->root, &range, cache_tree_comp_range, NULL); | |
| if (!node) | |
| return NULL; | |
| entry = rb_entry(node, struct cache_extent, rb_node); | |
| return entry; | |
| } | |
| struct cache_extent *lookup_cache_extent2(struct cache_tree *tree, | |
| u64 objectid, u64 start, u64 size) | |
| { | |
| struct rb_node *node; | |
| struct cache_extent *entry; | |
| struct cache_extent_search_range range; | |
| range.objectid = objectid; | |
| range.start = start; | |
| range.size = size; | |
| node = rb_search(&tree->root, &range, cache_tree_comp_range2, NULL); | |
| if (!node) | |
| return NULL; | |
| entry = rb_entry(node, struct cache_extent, rb_node); | |
| return entry; | |
| } | |
| struct cache_extent *search_cache_extent(struct cache_tree *tree, u64 start) | |
| { | |
| struct rb_node *next; | |
| struct rb_node *node; | |
| struct cache_extent *entry; | |
| struct cache_extent_search_range range; | |
| range.start = start; | |
| range.size = 1; | |
| node = rb_search(&tree->root, &range, cache_tree_comp_range, &next); | |
| if (!node) | |
| node = next; | |
| if (!node) | |
| return NULL; | |
| entry = rb_entry(node, struct cache_extent, rb_node); | |
| return entry; | |
| } | |
| struct cache_extent *search_cache_extent2(struct cache_tree *tree, | |
| u64 objectid, u64 start) | |
| { | |
| struct rb_node *next; | |
| struct rb_node *node; | |
| struct cache_extent *entry; | |
| struct cache_extent_search_range range; | |
| range.objectid = objectid; | |
| range.start = start; | |
| range.size = 1; | |
| node = rb_search(&tree->root, &range, cache_tree_comp_range2, &next); | |
| if (!node) | |
| node = next; | |
| if (!node) | |
| return NULL; | |
| entry = rb_entry(node, struct cache_extent, rb_node); | |
| return entry; | |
| } | |
| struct cache_extent *first_cache_extent(struct cache_tree *tree) | |
| { | |
| struct rb_node *node = rb_first(&tree->root); | |
| if (!node) | |
| return NULL; | |
| return rb_entry(node, struct cache_extent, rb_node); | |
| } | |
| struct cache_extent *last_cache_extent(struct cache_tree *tree) | |
| { | |
| struct rb_node *node = rb_last(&tree->root); | |
| if (!node) | |
| return NULL; | |
| return rb_entry(node, struct cache_extent, rb_node); | |
| } | |
| struct cache_extent *prev_cache_extent(struct cache_extent *pe) | |
| { | |
| struct rb_node *node = rb_prev(&pe->rb_node); | |
| if (!node) | |
| return NULL; | |
| return rb_entry(node, struct cache_extent, rb_node); | |
| } | |
| struct cache_extent *next_cache_extent(struct cache_extent *pe) | |
| { | |
| struct rb_node *node = rb_next(&pe->rb_node); | |
| if (!node) | |
| return NULL; | |
| return rb_entry(node, struct cache_extent, rb_node); | |
| } | |
| void remove_cache_extent(struct cache_tree *tree, struct cache_extent *pe) | |
| { | |
| rb_erase(&pe->rb_node, &tree->root); | |
| } | |
| void cache_tree_free_extents(struct cache_tree *tree, | |
| free_cache_extent free_func) | |
| { | |
| struct cache_extent *ce; | |
| while ((ce = first_cache_extent(tree))) { | |
| remove_cache_extent(tree, ce); | |
| free_func(ce); | |
| } | |
| } | |
| static void free_extent_cache(struct cache_extent *pe) | |
| { | |
| free(pe); | |
| } | |
| void free_extent_cache_tree(struct cache_tree *tree) | |
| { | |
| cache_tree_free_extents(tree, free_extent_cache); | |
| } | |
| int add_merge_cache_extent(struct cache_tree *tree, u64 start, u64 size) | |
| { | |
| struct cache_extent *cache; | |
| struct cache_extent *next = NULL; | |
| struct cache_extent *prev = NULL; | |
| int next_merged = 0; | |
| int prev_merged = 0; | |
| int ret = 0; | |
| if (cache_tree_empty(tree)) | |
| goto insert; | |
| cache = search_cache_extent(tree, start); | |
| if (!cache) { | |
| /* | |
| * Either the tree is completely empty, or the no range after | |
| * start. | |
| * Either way, the last cache_extent should be prev. | |
| */ | |
| prev = last_cache_extent(tree); | |
| } else if (start <= cache->start) { | |
| next = cache; | |
| prev = prev_cache_extent(cache); | |
| } else { | |
| prev = cache; | |
| next = next_cache_extent(cache); | |
| } | |
| /* | |
| * Ensure the range to be inserted won't cover with existings | |
| * Or we will need extra loop to do merge | |
| */ | |
| BUG_ON(next && start + size > next->start); | |
| BUG_ON(prev && prev->start + prev->size > start); | |
| if (next && start + size == next->start) { | |
| next_merged = 1; | |
| next->size = next->start + next->size - start; | |
| next->start = start; | |
| } | |
| if (prev && prev->start + prev->size == start) { | |
| prev_merged = 1; | |
| if (next_merged) { | |
| next->size = next->start + next->size - prev->start; | |
| next->start = prev->start; | |
| remove_cache_extent(tree, prev); | |
| free(prev); | |
| } else { | |
| prev->size = start + size - prev->start; | |
| } | |
| } | |
| insert: | |
| if (!prev_merged && !next_merged) | |
| ret = add_cache_extent(tree, start, size); | |
| return ret; | |
| } |