[PATCH nf-next v4 01/13] rhashtable: add rhashtable_flush_and_free helper

Florian Westphal <[email protected]>
Newsgroups gmane.comp.security.firewalls.netfilter.devel,gmane.linux.kernel.cryptoapi
Message-ID <[email protected]>
Will be used by upcoming ipset rhashtable conversion.

"walk rht with unlink+free" triggers LLM reject pattern:
"possible softirq CPU stall".

"walk rht with unlink+free + cond_resched" triggers
"possibly skipped elements".

Add a helper to detach current hash backend storage from the
rhashtable, then iterate and flush all contained elements.

Cc: [email protected]
Cc: [email protected]
Link: https://sashiko.dev/#/patchset/20260828152256.8759-1-fw%40strlen.de
Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Florian Westphal <[email protected]>
---
 Herbert: If you prefer to take this via the crypto tree, please
 let me know.
 Otherwise, an explicit Ack would be appreciated, so this can
 be handled via nf-next.  Thanks.

 net/ipv6/ila/ could be converted to use this helper too.

 include/linux/rhashtable.h |  19 ++++++
 lib/rhashtable.c           | 127 +++++++++++++++++++++++++++++++++++++
 2 files changed, 146 insertions(+)

diff --git a/include/linux/rhashtable.h b/include/linux/rhashtable.h
index 57a2a29bef0e..213e1cb77d45 100644
--- a/include/linux/rhashtable.h
+++ b/include/linux/rhashtable.h
@@ -255,6 +255,10 @@ void rhashtable_free_and_destroy(struct rhashtable *ht,
 				 void *arg);
 void rhashtable_destroy(struct rhashtable *ht);
 
+void rhashtable_flush_and_free(struct rhashtable *ht,
+			       void (*free_fn)(void *ptr, void *arg),
+			       void *arg);
+
 struct rhash_lock_head __rcu **rht_bucket_nested(
 	const struct bucket_table *tbl, unsigned int hash);
 struct rhash_lock_head __rcu **__rht_bucket_nested(
@@ -1335,4 +1339,19 @@ static inline void rhltable_destroy(struct rhltable *hlt)
 	rhltable_free_and_destroy(hlt, NULL, NULL);
 }
 
+/**
+ * rhltable_flush_and_free - unlink and free all elements in the hash list table
+ * @hlt:	the hash list table to destroy
+ * @free_fn:	callback to release resources of element
+ * @arg:	pointer passed to free_fn
+ *
+ * See documentation for rhashtable_flush_and_free.
+ */
+static inline void rhltable_flush_and_free(struct rhltable *hlt,
+					   void (*free_fn)(void *ptr,
+							   void *arg),
+					   void *arg)
+{
+	rhashtable_flush_and_free(&hlt->ht, free_fn, arg);
+}
 #endif /* _LINUX_RHASHTABLE_H */
diff --git a/lib/rhashtable.c b/lib/rhashtable.c
index 6362896e4f09..656c5021d8b2 100644
--- a/lib/rhashtable.c
+++ b/lib/rhashtable.c
@@ -1339,6 +1339,133 @@ void rhashtable_destroy(struct rhashtable *ht)
 }
 EXPORT_SYMBOL_GPL(rhashtable_destroy);
 
+struct rht_flush_arg {
+	struct rhashtable *ht;
+	void (*free_fn)(void *ptr, void *arg);
+	void *arg;
+};
+
+static void flush_cb(void *ptr, void *arg)
+{
+	struct rht_flush_arg *fa = arg;
+
+	atomic_dec(&fa->ht->nelems);
+	if (fa->free_fn)
+		fa->free_fn(ptr, fa->arg);
+}
+
+static void rhashtable_flush_one(struct rhashtable *ht, struct rhash_head *obj,
+				 void (*free_fn)(void *ptr, void *arg),
+				 void *arg)
+{
+	struct rht_flush_arg fa = {
+		.ht = ht,
+		.free_fn = free_fn,
+		.arg = arg,
+	};
+
+	rhashtable_free_one(ht, obj, flush_cb, &fa);
+}
+
+static void rhashtable_flush_chain(struct rhashtable *ht,
+				   struct bucket_table *tbl,
+				   unsigned int hash,
+				   void (*free_fn)(void *ptr, void *arg),
+				   void *arg)
+{
+	struct rhash_lock_head __rcu **bkt = rht_bucket_var(tbl, hash);
+	struct rhash_head *pos, *next;
+	unsigned long flags;
+
+	if (!bkt)
+		return;
+
+	flags = rht_lock(tbl, bkt);
+	pos = rht_ptr(bkt, tbl, hash);
+	rht_assign_unlock(tbl, bkt, NULL, flags);
+
+	/* Nothing can reach @pos through @tbl any more: the bucket has
+	 * been emptied above, and @tbl itself is unreachable from ht->tbl
+	 * (see rhashtable_flush_and_free()). Walk it the same way
+	 * rhashtable_free_and_destroy() walks a table it exclusively
+	 * owns.
+	 */
+	while (!rht_is_a_nulls(pos)) {
+		next = rcu_dereference_raw(pos->next);
+		rhashtable_flush_one(ht, pos, free_fn, arg);
+		pos = next;
+	}
+}
+
+/**
+ * rhashtable_flush_and_free - detach and discard all current elements
+ * @ht:		the hash table to flush
+ * @free_fn:	callback to release resources of an element, may be %NULL
+ * @arg:	pointer passed to free_fn
+ *
+ * Swaps the bucket table backing @ht for a new, empty table.
+ *
+ * The detached table is then walked and every element found is
+ * unlinked, and, if @free_fn is given, handed to it for release.
+ * Note that RCU protected readers may still be accessing the elements.
+ * Releasing of resources must occur in a compatible manner.
+ *
+ * Unlike rhashtable_destroy(), @ht is left fully initialized and may
+ * continue to be used for lookups, insertions, and removals.
+ *
+ * This function may sleep, it cannot be called from atomic context or
+ * RCU read-side critical sections.
+ */
+void rhashtable_flush_and_free(struct rhashtable *ht,
+			       void (*free_fn)(void *ptr, void *arg),
+			       void *arg)
+{
+	struct bucket_table *tbl, *old_tbl, *last_tbl, *new_tbl;
+	struct rhashtable_walker *walker;
+	unsigned int i;
+
+	new_tbl = bucket_table_alloc(ht, rounded_hashtable_size(&ht->p),
+				     GFP_KERNEL);
+	if (!new_tbl)
+		new_tbl = bucket_table_alloc(ht, ht->p.min_size,
+					     GFP_KERNEL | __GFP_NOFAIL);
+
+	mutex_lock(&ht->mutex);
+
+	/* Splice the new, empty table onto the tail of the live table ... */
+	old_tbl = rht_dereference(ht->tbl, ht);
+	do {
+		last_tbl = rhashtable_last_table(ht, old_tbl);
+	} while (rhashtable_rehash_attach(ht, last_tbl, new_tbl));
+
+	/* ...then publish it as ht->tbl. */
+	rcu_assign_pointer(ht->tbl, new_tbl);
+	mutex_unlock(&ht->mutex);
+
+	tbl = old_tbl;
+	do {
+		struct bucket_table *next_tbl = rcu_dereference_raw(tbl->future_tbl);
+
+		for (i = 0; i < tbl->size; i++) {
+			cond_resched();
+			rhashtable_flush_chain(ht, tbl, i, free_fn, arg);
+		}
+
+		spin_lock(&ht->lock);
+		list_for_each_entry(walker, &tbl->walkers, list)
+			walker->tbl = NULL;
+		/* See rhashtable_rehash_table(): done under ->lock so
+		 * rhashtable_walk_stop() can use rcu_head_after_call_rcu()
+		 * to decide whether to re-link the walker onto this table.
+		 */
+		call_rcu(&tbl->rcu, bucket_table_free_rcu);
+		spin_unlock(&ht->lock);
+
+		tbl = next_tbl;
+	} while (tbl && tbl != new_tbl);
+}
+EXPORT_SYMBOL_GPL(rhashtable_flush_and_free);
+
 struct rhash_lock_head __rcu **__rht_bucket_nested(
 	const struct bucket_table *tbl, unsigned int hash)
 {
-- 
2.55.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.