xfs: teach xfs_btree_has_record to return false if there are gaps
The current implementation of xfs_btree_has_record returns true if it
finds /any/ record within the given range. Unfortunately, that's not
what the predicate is supposed to do -- it's supposed to test if the
/entire/ range is covered by records.
Therefore, enhance the routine to check that the first record it
encounters starts earlier or at the same point as the low key, the last
record ends at or after the same point as the high key, and that there
aren't any gaps in the records.
Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
diff --git a/libxfs/xfs_alloc.c b/libxfs/xfs_alloc.c
index 92f61fa..5119aea 100644
--- a/libxfs/xfs_alloc.c
+++ b/libxfs/xfs_alloc.c
@@ -3374,6 +3374,18 @@
return xfs_btree_query_all(cur, xfs_alloc_query_range_helper, &query);
}
+static bool
+xfs_alloc_has_key_gap(
+ struct xfs_btree_cur *cur,
+ const union xfs_btree_key *key1,
+ const union xfs_btree_key *key2)
+{
+ xfs_agblock_t next;
+
+ next = be32_to_cpu(key1->alloc.ar_startblock) + 1;
+ return next != be32_to_cpu(key2->alloc.ar_startblock);
+}
+
/* Is there a record covering a given extent? */
int
xfs_alloc_has_record(
@@ -3390,7 +3402,8 @@
memset(&high, 0xFF, sizeof(high));
high.a.ar_startblock = bno + len - 1;
- return xfs_btree_has_record(cur, &low, &high, exists);
+ return xfs_btree_has_record(cur, &low, &high, xfs_alloc_has_key_gap,
+ exists);
}
/*
diff --git a/libxfs/xfs_btree.c b/libxfs/xfs_btree.c
index a408aa4..b255d34 100644
--- a/libxfs/xfs_btree.c
+++ b/libxfs/xfs_btree.c
@@ -4872,6 +4872,23 @@
return (int64_t)be32_to_cpu(a->s) - be32_to_cpu(b->s);
}
+struct xbtree_hasrec {
+ xfs_btree_key_gap_fn has_gap;
+
+ /* Keys for the start and end of the range we want to know about. */
+ union xfs_btree_key start_key;
+ union xfs_btree_key end_key;
+
+ /* Highest record key we've seen so far. */
+ union xfs_btree_key high_key;
+
+ /* Are we processing the first record? */
+ bool first_rec;
+
+ /* Did we see any records at all? */
+ bool saw_anything;
+};
+
/* If there's an extent, we're done. */
STATIC int
xfs_btree_has_record_helper(
@@ -4879,7 +4896,35 @@
union xfs_btree_rec *rec,
void *priv)
{
- return -ECANCELED;
+ union xfs_btree_key rec_low_key;
+ union xfs_btree_key rec_high_key;
+ struct xbtree_hasrec *info = priv;
+ int64_t res;
+
+ cur->bc_ops->init_key_from_rec(&rec_low_key, rec);
+ if (info->first_rec) {
+ /* Bail if the first record starts after the start key. */
+ res = cur->bc_ops->diff_two_keys(cur, &info->start_key,
+ &rec_low_key);
+ if (res < 0)
+ return -ECANCELED;
+
+ info->first_rec = false;
+ } else {
+ /* Bail if there's a gap with the previous record. */
+ if (info->has_gap(cur, &info->high_key, &rec_low_key))
+ return -ECANCELED;
+ }
+
+ info->saw_anything = true;
+
+ /* If the current record is higher than what we've seen, remember it. */
+ cur->bc_ops->init_high_key_from_rec(&rec_high_key, rec);
+ res = cur->bc_ops->diff_two_keys(cur, &rec_high_key, &info->high_key);
+ if (res > 0)
+ info->high_key = rec_high_key; /* struct copy */
+
+ return 0;
}
/* Is there a record covering a given range of keys? */
@@ -4888,18 +4933,42 @@
struct xfs_btree_cur *cur,
union xfs_btree_irec *low,
union xfs_btree_irec *high,
+ xfs_btree_key_gap_fn has_gap,
bool *exists)
{
+ struct xbtree_hasrec info = {.first_rec = true, .has_gap = has_gap};
+ union xfs_btree_rec rec;
+ int64_t res;
int error;
+ cur->bc_rec = *low;
+ cur->bc_ops->init_rec_from_cur(cur, &rec);
+ cur->bc_ops->init_key_from_rec(&info.start_key, &rec);
+
+ cur->bc_rec = *high;
+ cur->bc_ops->init_rec_from_cur(cur, &rec);
+ cur->bc_ops->init_key_from_rec(&info.end_key, &rec);
+
error = xfs_btree_query_range(cur, low, high,
- &xfs_btree_has_record_helper, NULL);
+ &xfs_btree_has_record_helper, &info);
if (error == -ECANCELED) {
- *exists = true;
+ /* Bailing out early means we found an uncovered area. */
+ *exists = false;
return 0;
}
- *exists = false;
- return error;
+ if (error)
+ return error;
+
+ if (!info.saw_anything) {
+ *exists = false;
+ } else {
+ /* Did the record set go at least as far as the end? */
+ res = cur->bc_ops->diff_two_keys(cur, &info.high_key,
+ &info.end_key);
+ *exists = (res >= 0);
+ }
+
+ return 0;
}
/* Are there more records in this btree? */
diff --git a/libxfs/xfs_btree.h b/libxfs/xfs_btree.h
index 0f79023..e81c23b 100644
--- a/libxfs/xfs_btree.h
+++ b/libxfs/xfs_btree.h
@@ -508,8 +508,12 @@
struct xfs_btree_block *block, union xfs_btree_key *key);
union xfs_btree_key *xfs_btree_high_key_from_key(struct xfs_btree_cur *cur,
union xfs_btree_key *key);
+typedef bool (*xfs_btree_key_gap_fn)(struct xfs_btree_cur *cur,
+ const union xfs_btree_key *key1,
+ const union xfs_btree_key *key2);
int xfs_btree_has_record(struct xfs_btree_cur *cur, union xfs_btree_irec *low,
- union xfs_btree_irec *high, bool *exists);
+ union xfs_btree_irec *high, xfs_btree_key_gap_fn has_gap,
+ bool *exists);
bool xfs_btree_has_more_records(struct xfs_btree_cur *cur);
struct xfs_ifork *xfs_btree_ifork_ptr(struct xfs_btree_cur *cur);
diff --git a/libxfs/xfs_refcount.c b/libxfs/xfs_refcount.c
index 723c903..7bd0f7f 100644
--- a/libxfs/xfs_refcount.c
+++ b/libxfs/xfs_refcount.c
@@ -1768,6 +1768,18 @@
return error;
}
+static bool
+xfs_refcount_has_key_gap(
+ struct xfs_btree_cur *cur,
+ const union xfs_btree_key *key1,
+ const union xfs_btree_key *key2)
+{
+ xfs_agblock_t next;
+
+ next = be32_to_cpu(key1->refc.rc_startblock) + 1;
+ return next != be32_to_cpu(key2->refc.rc_startblock);
+}
+
/* Is there a record covering a given extent? */
int
xfs_refcount_has_record(
@@ -1784,5 +1796,6 @@
memset(&high, 0xFF, sizeof(high));
high.rc.rc_startblock = bno + len - 1;
- return xfs_btree_has_record(cur, &low, &high, exists);
+ return xfs_btree_has_record(cur, &low, &high, xfs_refcount_has_key_gap,
+ exists);
}
diff --git a/libxfs/xfs_rmap.c b/libxfs/xfs_rmap.c
index 6205b8e..1261632 100644
--- a/libxfs/xfs_rmap.c
+++ b/libxfs/xfs_rmap.c
@@ -2631,6 +2631,18 @@
return 0;
}
+static bool
+xfs_rmap_has_key_gap(
+ struct xfs_btree_cur *cur,
+ const union xfs_btree_key *key1,
+ const union xfs_btree_key *key2)
+{
+ xfs_agblock_t next;
+
+ next = be32_to_cpu(key1->rmap.rm_startblock) + 1;
+ return next != be32_to_cpu(key2->rmap.rm_startblock);
+}
+
/* Is there a record covering a given extent? */
int
xfs_rmap_has_record(
@@ -2647,7 +2659,8 @@
memset(&high, 0xFF, sizeof(high));
high.r.rm_startblock = bno + len - 1;
- return xfs_btree_has_record(cur, &low, &high, exists);
+ return xfs_btree_has_record(cur, &low, &high, xfs_rmap_has_key_gap,
+ exists);
}
/*