+struct file_extent_hole {
+ struct rb_node node;
+ u64 start;
+ u64 len;
+};
+
+/* Compatible function to allow reuse of old codes */
+static u64 first_extent_gap(struct rb_root *holes)
+{
+ struct file_extent_hole *hole;
+
+ if (RB_EMPTY_ROOT(holes))
+ return (u64)-1;
+
+ hole = rb_entry(rb_first(holes), struct file_extent_hole, node);
+ return hole->start;
+}
+
+static int compare_hole(struct rb_node *node1, struct rb_node *node2)
+{
+ struct file_extent_hole *hole1;
+ struct file_extent_hole *hole2;
+
+ hole1 = rb_entry(node1, struct file_extent_hole, node);
+ hole2 = rb_entry(node2, struct file_extent_hole, node);
+
+ if (hole1->start > hole2->start)
+ return -1;
+ if (hole1->start < hole2->start)
+ return 1;
+ /* Now hole1->start == hole2->start */
+ if (hole1->len >= hole2->len)
+ /*
+ * Hole 1 will be merge center
+ * Same hole will be merged later
+ */
+ return -1;
+ /* Hole 2 will be merge center */
+ return 1;
+}
+
+/*
+ * Add a hole to the record
+ *
+ * This will do hole merge for copy_file_extent_holes(),
+ * which will ensure there won't be continuous holes.
+ */
+static int add_file_extent_hole(struct rb_root *holes,
+ u64 start, u64 len)
+{
+ struct file_extent_hole *hole;
+ struct file_extent_hole *prev = NULL;
+ struct file_extent_hole *next = NULL;
+
+ hole = malloc(sizeof(*hole));
+ if (!hole)
+ return -ENOMEM;
+ hole->start = start;
+ hole->len = len;
+ /* Since compare will not return 0, no -EEXIST will happen */
+ rb_insert(holes, &hole->node, compare_hole);
+
+ /* simple merge with previous hole */
+ if (rb_prev(&hole->node))
+ prev = rb_entry(rb_prev(&hole->node), struct file_extent_hole,
+ node);
+ if (prev && prev->start + prev->len >= hole->start) {
+ hole->len = hole->start + hole->len - prev->start;
+ hole->start = prev->start;
+ rb_erase(&prev->node, holes);
+ free(prev);
+ prev = NULL;
+ }
+
+ /* iterate merge with next holes */
+ while (1) {
+ if (!rb_next(&hole->node))
+ break;
+ next = rb_entry(rb_next(&hole->node), struct file_extent_hole,
+ node);
+ if (hole->start + hole->len >= next->start) {
+ if (hole->start + hole->len <= next->start + next->len)
+ hole->len = next->start + next->len -
+ hole->start;
+ rb_erase(&next->node, holes);
+ free(next);
+ next = NULL;
+ } else
+ break;
+ }
+ return 0;
+}
+
+static int compare_hole_range(struct rb_node *node, void *data)
+{
+ struct file_extent_hole *hole;
+ u64 start;
+
+ hole = (struct file_extent_hole *)data;
+ start = hole->start;
+
+ hole = rb_entry(node, struct file_extent_hole, node);
+ if (start < hole->start)
+ return -1;
+ if (start >= hole->start && start < hole->start + hole->len)
+ return 0;
+ return 1;
+}
+
+/*
+ * Delete a hole in the record
+ *
+ * This will do the hole split and is much restrict than add.
+ */
+static int del_file_extent_hole(struct rb_root *holes,
+ u64 start, u64 len)
+{
+ struct file_extent_hole *hole;
+ struct file_extent_hole tmp;
+ u64 prev_start = 0;
+ u64 prev_len = 0;
+ u64 next_start = 0;
+ u64 next_len = 0;
+ struct rb_node *node;
+ int have_prev = 0;
+ int have_next = 0;
+ int ret = 0;
+
+ tmp.start = start;
+ tmp.len = len;
+ node = rb_search(holes, &tmp, compare_hole_range, NULL);
+ if (!node)
+ return -EEXIST;
+ hole = rb_entry(node, struct file_extent_hole, node);
+ if (start + len > hole->start + hole->len)
+ return -EEXIST;
+
+ /*
+ * Now there will be no overflap, delete the hole and re-add the
+ * split(s) if they exists.
+ */
+ if (start > hole->start) {
+ prev_start = hole->start;
+ prev_len = start - hole->start;
+ have_prev = 1;
+ }
+ if (hole->start + hole->len > start + len) {
+ next_start = start + len;
+ next_len = hole->start + hole->len - start - len;
+ have_next = 1;
+ }
+ rb_erase(node, holes);
+ free(hole);
+ if (have_prev) {
+ ret = add_file_extent_hole(holes, prev_start, prev_len);
+ if (ret < 0)
+ return ret;
+ }
+ if (have_next) {
+ ret = add_file_extent_hole(holes, next_start, next_len);
+ if (ret < 0)
+ return ret;
+ }
+ return 0;
+}
+
+static int copy_file_extent_holes(struct rb_root *dst,
+ struct rb_root *src)
+{
+ struct file_extent_hole *hole;
+ struct rb_node *node;
+ int ret = 0;
+
+ node = rb_first(src);
+ while (node) {
+ hole = rb_entry(node, struct file_extent_hole, node);
+ ret = add_file_extent_hole(dst, hole->start, hole->len);
+ if (ret)
+ break;
+ node = rb_next(node);
+ }
+ return ret;
+}
+
+static void free_file_extent_holes(struct rb_root *holes)
+{
+ struct rb_node *node;
+ struct file_extent_hole *hole;
+
+ node = rb_first(holes);
+ while (node) {
+ hole = rb_entry(node, struct file_extent_hole, node);
+ rb_erase(node, holes);
+ free(hole);
+ node = rb_first(holes);
+ }
+}
+