XArray: Fix xas_pause at ULONG_MAX
authorMatthew Wilcox (Oracle) <willy@infradead.org>
Fri, 8 Nov 2019 03:49:11 +0000 (22:49 -0500)
committerMatthew Wilcox (Oracle) <willy@infradead.org>
Sat, 9 Nov 2019 04:48:40 +0000 (23:48 -0500)
If we were unlucky enough to call xas_pause() when the index was at
ULONG_MAX (or a multi-slot entry which ends at ULONG_MAX), we would
wrap the index back around to 0 and restart the iteration from the
beginning.  Use the XAS_BOUNDS state to indicate that we should just
stop the iteration.

Signed-off-by: Matthew Wilcox (Oracle) <willy@infradead.org>
lib/test_xarray.c
lib/xarray.c

index 7df4f7f..c477f22 100644 (file)
@@ -1132,6 +1132,27 @@ static noinline void check_move_tiny(struct xarray *xa)
        XA_BUG_ON(xa, !xa_empty(xa));
 }
 
+static noinline void check_move_max(struct xarray *xa)
+{
+       XA_STATE(xas, xa, 0);
+
+       xa_store_index(xa, ULONG_MAX, GFP_KERNEL);
+       rcu_read_lock();
+       XA_BUG_ON(xa, xas_find(&xas, ULONG_MAX) != xa_mk_index(ULONG_MAX));
+       XA_BUG_ON(xa, xas_find(&xas, ULONG_MAX) != NULL);
+       rcu_read_unlock();
+
+       xas_set(&xas, 0);
+       rcu_read_lock();
+       XA_BUG_ON(xa, xas_find(&xas, ULONG_MAX) != xa_mk_index(ULONG_MAX));
+       xas_pause(&xas);
+       XA_BUG_ON(xa, xas_find(&xas, ULONG_MAX) != NULL);
+       rcu_read_unlock();
+
+       xa_erase_index(xa, ULONG_MAX);
+       XA_BUG_ON(xa, !xa_empty(xa));
+}
+
 static noinline void check_move_small(struct xarray *xa, unsigned long idx)
 {
        XA_STATE(xas, xa, 0);
@@ -1240,6 +1261,7 @@ static noinline void check_move(struct xarray *xa)
        xa_destroy(xa);
 
        check_move_tiny(xa);
+       check_move_max(xa);
 
        for (i = 0; i < 16; i++)
                check_move_small(xa, 1UL << i);
index 1237c21..bfaaa2c 100644 (file)
@@ -967,6 +967,7 @@ void xas_pause(struct xa_state *xas)
        if (xas_invalid(xas))
                return;
 
+       xas->xa_node = XAS_RESTART;
        if (node) {
                unsigned int offset = xas->xa_offset;
                while (++offset < XA_CHUNK_SIZE) {
@@ -974,10 +975,11 @@ void xas_pause(struct xa_state *xas)
                                break;
                }
                xas->xa_index += (offset - xas->xa_offset) << node->shift;
+               if (xas->xa_index == 0)
+                       xas->xa_node = XAS_BOUNDS;
        } else {
                xas->xa_index++;
        }
-       xas->xa_node = XAS_RESTART;
 }
 EXPORT_SYMBOL_GPL(xas_pause);
 
@@ -1079,13 +1081,13 @@ void *xas_find(struct xa_state *xas, unsigned long max)
 {
        void *entry;
 
-       if (xas_error(xas))
+       if (xas_error(xas) || xas->xa_node == XAS_BOUNDS)
                return NULL;
 
        if (!xas->xa_node) {
                xas->xa_index = 1;
                return set_bounds(xas);
-       } else if (xas_top(xas->xa_node)) {
+       } else if (xas->xa_node == XAS_RESTART) {
                entry = xas_load(xas);
                if (entry || xas_not_node(xas->xa_node))
                        return entry;