Netdev List
 help / color / mirror / Atom feed
From: Chuck Lever <cel@kernel.org>
To: Donald Hunter <donald.hunter@gmail.com>,
	 Jakub Kicinski <kuba@kernel.org>,
	"David S. Miller" <davem@davemloft.net>,
	 Paolo Abeni <pabeni@redhat.com>, Simon Horman <horms@kernel.org>,
	 John Fastabend <john.fastabend@gmail.com>,
	 Sabrina Dubroca <sd@queasysnail.net>,
	Keith Busch <kbusch@kernel.org>,  Jens Axboe <axboe@kernel.dk>,
	Christoph Hellwig <hch@lst.de>,  Sagi Grimberg <sagi@grimberg.me>,
	Chaitanya Kulkarni <kch@nvidia.com>,
	 Chuck Lever <cel@kernel.org>, Jeff Layton <jlayton@kernel.org>,
	 NeilBrown <neil@brown.name>,
	Olga Kornievskaia <okorniev@redhat.com>,
	 Dai Ngo <Dai.Ngo@oracle.com>, Tom Talpey <tom@talpey.com>,
	 Eric Dumazet <edumazet@kernel.org>
Cc: kernel-tls-handshake@lists.linux.dev, netdev@vger.kernel.org,
	 linux-nvme@lists.infradead.org, linux-nfs@vger.kernel.org
Subject: [PATCH net-next v3 02/10] lib: Add a "stringset" data structure
Date: Fri, 13 Nov 2026 14:22:00 -0500	[thread overview]
Message-ID: <20261113-tls-session-tags-v3-2-91271964cdb0@kernel.org> (raw)
In-Reply-To: <20261113-tls-session-tags-v3-0-91271964cdb0@kernel.org>

Access control mechanisms sometimes need to match metadata tags
between a session and a resource. A stringset provides efficient
membership testing and set intersection operations for this purpose.

Set intersection, not membership, is what motivates a purpose-built
structure. xarray and rbtree answer a membership query in O(log M),
so intersecting by probing costs O(N log M). rhashtable probes in
O(1) expected, but pays a hash per element and gives up the ordering
a merge relies on. Two sorted arrays instead merge in a single
O(N + M) pass.

The implementation therefore uses a sorted array of string pointers.
Beyond the intersection cost, a sorted array needs no per-element
node allocation. An rbtree does, so it carries more memory overhead
for the small sets typical of resource tagging.

The API follows a build-then-query pattern: callers initialize,
allocate capacity, add strings, then finalize before querying. Once
finalized, the stringset is suitable for concurrent read access.

Build the library only when a consumer selects CONFIG_STRINGSET.
Consumers of the new APIs arrive in subsequent patches.

Signed-off-by: Chuck Lever <cel@kernel.org>
---
 Documentation/core-api/index.rst     |   1 +
 Documentation/core-api/stringset.rst | 243 +++++++++++++++++++++++++++++++++++
 MAINTAINERS                          |   7 +
 include/linux/stringset.h            | 191 +++++++++++++++++++++++++++
 lib/Kconfig                          |  12 ++
 lib/Makefile                         |   1 +
 lib/stringset.c                      | 190 +++++++++++++++++++++++++++
 7 files changed, 645 insertions(+)

diff --git a/Documentation/core-api/index.rst b/Documentation/core-api/index.rst
index 92f91c6a0d79..929bf11828c3 100644
--- a/Documentation/core-api/index.rst
+++ b/Documentation/core-api/index.rst
@@ -45,6 +45,7 @@ Library functionality that is used throughout the kernel.
    idr
    circular-buffers
    rbtree
+   stringset
    generic-radix-tree
    packing
    this_cpu_ops
diff --git a/Documentation/core-api/stringset.rst b/Documentation/core-api/stringset.rst
new file mode 100644
index 000000000000..549199ae1fa2
--- /dev/null
+++ b/Documentation/core-api/stringset.rst
@@ -0,0 +1,243 @@
+.. SPDX-License-Identifier: GPL-2.0+
+
+=========
+Stringset
+=========
+
+Overview
+========
+
+A stringset is a set of strings, intended for resource tagging where
+metadata about a resource is represented simply by a name. The public
+API can be found in ``<linux/stringset.h>``.
+
+
+Initialization and Destruction
+==============================
+
+A stringset must be initialized before use::
+
+    struct stringset set;
+    stringset_init(&set);
+
+``DEFINE_STRINGSET()`` declares a stringset and initializes it in
+one step::
+
+    DEFINE_STRINGSET(set);
+
+Before adding strings, allocate capacity::
+
+    if (!stringset_alloc(&set, num_strings, GFP_KERNEL))
+        return -ENOMEM;
+
+When finished, release all resources::
+
+    stringset_destroy(&set);
+
+This frees all strings and the array. The stringset is reinitialized
+and may be reused by calling ``stringset_alloc()`` again.
+
+
+Adding Strings
+==============
+
+Two functions are provided for adding strings. Both require that
+sufficient capacity has been allocated via ``stringset_alloc()``.
+
+``stringset_add()``
+    Adds a string that is already in kmalloc'd memory. On success,
+    the stringset takes ownership of the string and frees it when
+    the stringset is destroyed, or at ``stringset_finalize()`` if it
+    is a duplicate::
+
+        char *str = kstrdup("example", GFP_KERNEL);
+        if (!str || !stringset_add(&set, str))
+            kfree(str);  /* failed, caller must free */
+
+``stringset_add_dup()``
+    Duplicates the string internally. The caller retains ownership
+    of the original string::
+
+        if (!stringset_add_dup(&set, "example", GFP_KERNEL))
+            return -ENOMEM;
+
+Both functions return true on success, false on failure. Strings must
+not be added after ``stringset_finalize()`` has been called. Pass
+``GFP_ATOMIC`` to ``stringset_add_dup()`` in atomic context.
+
+
+Finalizing
+==========
+
+After adding all strings, the stringset must be finalized before
+querying::
+
+    stringset_finalize(&set);
+
+This sorts the array to enable efficient binary search and removes
+any duplicate strings. Calling ``stringset_is_member()`` or
+``stringset_intersection()`` on a non-finalized stringset warns and
+returns ``false``.
+
+
+Querying Strings
+================
+
+``stringset_is_empty()``
+    Returns true if the stringset contains no strings.
+
+``stringset_count()``
+    Returns the number of strings in the stringset. Before finalization,
+    this is the number of strings added; after finalization, this is
+    the number of unique strings.
+
+``stringset_is_member()``
+    Returns true if a string is present in the stringset. Uses binary
+    search for O(log N) complexity::
+
+        if (stringset_is_member(&set, "example"))
+            pr_info("string found\n");
+
+``stringset_intersection()``
+    Returns true if two stringsets share at least one common string.
+    Uses merge-style comparison for O(N+M) complexity::
+
+        if (stringset_intersection(&set1, &set2))
+            pr_info("sets overlap\n");
+
+
+Iteration
+=========
+
+Use ``stringset_for_each()`` to iterate over all strings::
+
+    unsigned int index;
+    char *str;
+
+    stringset_for_each(&set, index, str)
+        pr_info("string: %s\n", str);
+
+Callers should not depend on the order in which strings are returned.
+Modifying the stringset during iteration produces undefined behavior.
+
+
+Copying
+=======
+
+``stringset_copy()`` duplicates all strings from one stringset to
+another::
+
+    struct stringset copy;
+
+    stringset_init(&copy);
+    if (!stringset_copy(&copy, &original, GFP_KERNEL))
+        pr_err("copy failed\n");
+
+The source stringset must be finalized before copying; copying an
+unfinalized stringset warns and returns failure. The destination
+stringset is initialized and ready for queries after this function
+returns (no separate ``stringset_finalize()`` call is needed). Each
+string is duplicated, so the two stringsets are fully independent after
+copying.
+
+
+Typical Usage Pattern
+=====================
+
+A typical usage pattern for building a stringset::
+
+    struct stringset set;
+
+    stringset_init(&set);
+    if (!stringset_alloc(&set, count, GFP_KERNEL))
+        return -ENOMEM;
+
+    for (i = 0; i < count; i++) {
+        if (!stringset_add_dup(&set, strings[i], GFP_KERNEL)) {
+            stringset_destroy(&set);
+            return -ENOMEM;
+        }
+    }
+    stringset_finalize(&set);
+
+    /* Now safe to query */
+    if (stringset_is_member(&set, "target"))
+        do_something();
+
+    stringset_destroy(&set);
+
+
+Thread Safety
+=============
+
+Stringsets have no internal locking. Callers provide synchronization
+between writers and readers.
+
+The build-and-finalize phase mutates the stringset; the post-finalize
+query phase reads from it. The two phases must be separated by a
+publication boundary, because ``stringset_finalize()`` itself carries
+no memory barrier. A reader that observes the finalized stringset
+before the writer's stores have propagated may see a stale
+``ss_count`` or a partially populated ``ss_strings[]`` array.
+
+Three publication patterns are sufficient:
+
+* Lock release after ``stringset_finalize()``, lock acquire before
+  each query. The matching unlock/lock pair supplies release and
+  acquire ordering.
+
+* ``rcu_assign_pointer()`` of the stringset pointer after
+  ``stringset_finalize()``, paired with ``rcu_dereference()`` inside
+  an RCU read-side critical section on the reader.
+
+* ``smp_store_release()`` of the stringset pointer after
+  ``stringset_finalize()``, paired with ``smp_load_acquire()`` on the
+  reader.
+
+Once published, the stringset must remain immutable until no further
+readers can observe it. ``stringset_destroy()`` is not safe against
+concurrent readers, and ``stringset_finalize()`` must not be called
+more than once. With RCU publication, callers typically defer
+destruction to a grace period (``synchronize_rcu()`` before
+``stringset_destroy()``, or ``stringset_destroy()`` from a
+``call_rcu()`` callback) so that in-flight readers drain before
+the storage is freed.
+
+
+Implementation
+==============
+
+Each stringset is rooted on the following structure::
+
+    struct stringset {
+            char         **ss_strings;
+            unsigned int   ss_count;
+            unsigned int   ss_capacity;
+            bool           ss_finalized;
+    };
+
+The implementation uses a sorted array of string pointers, providing
+O(log N) membership testing and O(N+M) intersection operations.
+
++----------------------------+-------------+
+| Operation                  | Complexity  |
++============================+=============+
+| stringset_add              | O(1)        |
++----------------------------+-------------+
+| stringset_finalize         | O(N log N)  |
++----------------------------+-------------+
+| stringset_is_member        | O(log N)    |
++----------------------------+-------------+
+| stringset_intersection     | O(N + M)    |
++----------------------------+-------------+
+| stringset_copy             | O(N)        |
++----------------------------+-------------+
+
+API Reference
+=============
+
+.. kernel-doc:: include/linux/stringset.h
+   :internal:
+
+.. kernel-doc:: lib/stringset.c
+   :export:
diff --git a/MAINTAINERS b/MAINTAINERS
index c583367c88bc..9b058bb60b0f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -27022,6 +27022,13 @@ S:	Maintained
 F:	Documentation/networking/device_drivers/ethernet/stmicro/
 F:	drivers/net/ethernet/stmicro/stmmac/
 
+STRINGSET DATA STRUCTURE
+M:	Chuck Lever <cel@kernel.org>
+S:	Maintained
+F:	Documentation/core-api/stringset.rst
+F:	include/linux/stringset.h
+F:	lib/stringset.c
+
 SUN HAPPY MEAL ETHERNET DRIVER
 M:	Sean Anderson <seanga2@gmail.com>
 S:	Maintained
diff --git a/include/linux/stringset.h b/include/linux/stringset.h
new file mode 100644
index 000000000000..7d6c2b8cea17
--- /dev/null
+++ b/include/linux/stringset.h
@@ -0,0 +1,191 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Stringsets
+ *
+ * Copyright (c) 2025, Oracle and/or its affiliates.
+ * Author: Chuck Lever <chuck.lever@oracle.com>
+ *
+ * A stringset is a set of strings. See
+ * Documentation/core-api/stringset.rst for how to use stringsets.
+ *
+ * Stringsets have no internal locking. Callers synchronize writers
+ * against readers, publish the finalized set with release/acquire
+ * ordering, and drain readers before stringset_destroy(). See the
+ * Thread Safety section of Documentation/core-api/stringset.rst.
+ */
+
+#ifndef _LINUX_STRINGSET_H
+#define _LINUX_STRINGSET_H
+
+#include <linux/bug.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+struct stringset {
+	char		**ss_strings;
+	unsigned int	ss_count;
+	unsigned int	ss_capacity;
+	bool		ss_finalized;
+};
+
+#define STRINGSET_INIT \
+	{ .ss_strings = NULL, .ss_count = 0, .ss_capacity = 0, .ss_finalized = false }
+#define DEFINE_STRINGSET(name) \
+	struct stringset name = STRINGSET_INIT
+
+/**
+ * stringset_for_each - Iterate over items in a stringset
+ * @set: An initialized stringset containing zero or more items
+ * @index: Index counter for the iteration
+ * @str: String retrieved from the stringset
+ */
+#define stringset_for_each(set, index, str) \
+	for ((index) = 0; (index) < (set)->ss_count && \
+	     ((str) = (set)->ss_strings[index], true); (index)++)
+
+/**
+ * stringset_init - Initialize an empty stringset
+ * @set: stringset to be initialized
+ */
+static inline void stringset_init(struct stringset *set)
+{
+	set->ss_strings = NULL;
+	set->ss_count = 0;
+	set->ss_capacity = 0;
+	set->ss_finalized = false;
+}
+
+/**
+ * stringset_alloc - Pre-allocate space for strings
+ * @set: An initialized stringset
+ * @capacity: Number of strings to allocate space for
+ * @gfp: Memory allocation flags
+ *
+ * This function allocates only once per stringset. Calling it on a
+ * stringset that is already allocated returns failure. stringset_destroy()
+ * releases the allocation, after which the stringset may be allocated
+ * again.
+ *
+ * @capacity may be zero. The stringset then represents an empty set, and
+ * stringset_add() rejects further additions. The same state is produced
+ * by DEFINE_STRINGSET() and stringset_init() alone, so callers that know
+ * the set is empty may skip stringset_alloc() entirely. An empty set
+ * still goes through stringset_finalize() before stringset_is_member()
+ * or stringset_intersection() can query it. A zero-capacity call allocates
+ * nothing, so a later stringset_alloc() on the same set still succeeds.
+ *
+ * Return:
+ * * %true: allocation succeeded (or @capacity was zero)
+ * * %false: allocation failed or stringset already allocated
+ */
+static inline __must_check bool
+stringset_alloc(struct stringset *set, unsigned int capacity, gfp_t gfp)
+{
+	if (set->ss_strings)
+		return false;
+	if (capacity == 0)
+		return true;
+
+	set->ss_strings = kcalloc(capacity, sizeof(char *), gfp);
+	if (!set->ss_strings)
+		return false;
+	set->ss_capacity = capacity;
+	return true;
+}
+
+/**
+ * stringset_is_empty - Determine if a stringset contains any strings
+ * @set: An initialized stringset to be checked
+ *
+ * Return:
+ * * %true: if @set is empty
+ * * %false: if @set contains one or more strings
+ */
+static inline bool stringset_is_empty(const struct stringset *set)
+{
+	return set->ss_count == 0;
+}
+
+/**
+ * stringset_count - Return the number of strings in a stringset
+ * @set: An initialized stringset to be checked
+ *
+ * If called before stringset_finalize(), returns the number of strings
+ * added. If called after, returns the number of unique strings.
+ *
+ * Return:
+ *    The number of strings in @set
+ */
+static inline unsigned int stringset_count(const struct stringset *set)
+{
+	return set->ss_count;
+}
+
+/**
+ * stringset_add - Add a string to a stringset
+ * @set: An initialized stringset with available capacity
+ * @str: non-NULL string to be added to @set, in kmalloc'd memory
+ *
+ * On success, @str is now owned by @set and will be freed either
+ * by stringset_finalize() (if a content duplicate) or stringset_destroy().
+ * The stringset must have been allocated with sufficient capacity via
+ * stringset_alloc(). Strings must not be added after stringset_finalize()
+ * has been called. Callers must not hand the same pointer to stringset_add()
+ * more than once: ownership has already transferred, and a second add
+ * produces a double-free at stringset_destroy().
+ *
+ * Return:
+ * * %true: @str is now a member of @set
+ * * %false: @str could not be added (NULL, no capacity, or @set is
+ *   already finalized)
+ */
+static inline __must_check bool
+stringset_add(struct stringset *set, char *str)
+{
+	if (WARN_ON_ONCE(set->ss_finalized))
+		return false;
+	if (!str || set->ss_count >= set->ss_capacity)
+		return false;
+	set->ss_strings[set->ss_count++] = str;
+	return true;
+}
+
+/**
+ * stringset_add_dup - Add a copy of a string to a stringset
+ * @set: An initialized stringset with available capacity
+ * @str: non-NULL string to be copied and added to @set
+ * @gfp: Memory allocation flags
+ *
+ * On success, @str will have been copied into a kmalloc'd
+ * buffer. The caller can release @str immediately. Strings must not
+ * be added after stringset_finalize() has been called.
+ *
+ * Return:
+ * * %true: @str is now a member of @set
+ * * %false: @str could not be added (NULL, no capacity, ENOMEM, or
+ *   @set is already finalized)
+ */
+static inline __must_check bool
+stringset_add_dup(struct stringset *set, const char *str, gfp_t gfp)
+{
+	char *entry;
+
+	if (WARN_ON_ONCE(set->ss_finalized))
+		return false;
+	if (!str || set->ss_count >= set->ss_capacity)
+		return false;
+	entry = kstrdup(str, gfp);
+	if (!entry)
+		return false;
+	set->ss_strings[set->ss_count++] = entry;
+	return true;
+}
+
+/* Implemented in lib/stringset.c */
+void stringset_finalize(struct stringset *set);
+void stringset_destroy(struct stringset *set);
+bool stringset_is_member(const struct stringset *set, const char *str);
+bool stringset_copy(struct stringset *dest, const struct stringset *src, gfp_t gfp);
+bool stringset_intersection(const struct stringset *set1, const struct stringset *set2);
+
+#endif /* _LINUX_STRINGSET_H */
diff --git a/lib/Kconfig b/lib/Kconfig
index 1e42f5167c68..c8ba3f7e13d8 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -363,6 +363,18 @@ config ASSOCIATIVE_ARRAY
 
 	  for more information.
 
+config STRINGSET
+	bool
+	help
+	  A sorted set of strings, built once and then queried for
+	  membership and intersection.
+
+	  See:
+
+		Documentation/core-api/stringset.rst
+
+	  for more information.
+
 config CLOSURES
 	bool
 
diff --git a/lib/Makefile b/lib/Makefile
index a2afe954805c..cbed3e2e7cd3 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -61,6 +61,7 @@ obj-y += bcd.o sort.o parser.o debug_locks.o random32.o \
 	 generic-radix-tree.o bitmap-str.o
 obj-y += string_helpers.o
 obj-y += hexdump.o
+obj-$(CONFIG_STRINGSET) += stringset.o
 obj-$(CONFIG_TEST_HEXDUMP) += test_hexdump.o
 obj-y += kstrtox.o
 obj-$(CONFIG_FIND_BIT_BENCHMARK) += find_bit_benchmark.o
diff --git a/lib/stringset.c b/lib/stringset.c
new file mode 100644
index 000000000000..a7b59836e509
--- /dev/null
+++ b/lib/stringset.c
@@ -0,0 +1,190 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Stringsets - a sorted set of strings
+ *
+ * Copyright (c) 2025, Oracle and/or its affiliates.
+ * Author: Chuck Lever <chuck.lever@oracle.com>
+ */
+
+#include <linux/bug.h>
+#include <linux/export.h>
+#include <linux/sort.h>
+#include <linux/string.h>
+#include <linux/stringset.h>
+
+static int stringset_cmp(const void *a, const void *b)
+{
+	const char * const *sa = a;
+	const char * const *sb = b;
+
+	return strcmp(*sa, *sb);
+}
+
+/**
+ * stringset_finalize - Sort the stringset and remove duplicates
+ * @set: An initialized stringset that has been populated
+ *
+ * This must be called after all strings have been added and before
+ * calling stringset_is_member(), stringset_intersection(), or
+ * stringset_copy(). Duplicate strings are removed and their memory
+ * is freed.
+ */
+void stringset_finalize(struct stringset *set)
+{
+	unsigned int i, j;
+
+	if (WARN_ON_ONCE(set->ss_finalized))
+		return;
+	if (set->ss_count > 1) {
+		sort(set->ss_strings, set->ss_count, sizeof(char *),
+		     stringset_cmp, NULL);
+
+		for (i = 0, j = 1; j < set->ss_count; j++) {
+			if (strcmp(set->ss_strings[i], set->ss_strings[j]) == 0)
+				kfree(set->ss_strings[j]);
+			else
+				set->ss_strings[++i] = set->ss_strings[j];
+		}
+		set->ss_count = i + 1;
+	}
+	set->ss_finalized = true;
+}
+EXPORT_SYMBOL_GPL(stringset_finalize);
+
+/**
+ * stringset_destroy - Release stringset resources
+ * @set: stringset to be destroyed
+ */
+void stringset_destroy(struct stringset *set)
+{
+	unsigned int i;
+
+	for (i = 0; i < set->ss_count; i++)
+		kfree(set->ss_strings[i]);
+	kfree(set->ss_strings);
+	stringset_init(set);
+}
+EXPORT_SYMBOL_GPL(stringset_destroy);
+
+/**
+ * stringset_is_member - Check if a string is already a member of a stringset
+ * @set: An initialized and finalized stringset to be checked
+ * @str: string to search for
+ *
+ * Uses binary search. The stringset must have been finalized first;
+ * a query on an unfinalized stringset warns and returns %false.
+ *
+ * Return:
+ * * %true: if @str is a member of @set
+ * * %false: if @str is not a member of @set, or @set is not finalized
+ */
+bool stringset_is_member(const struct stringset *set, const char *str)
+{
+	unsigned int low = 0, high = set->ss_count;
+
+	if (WARN_ON_ONCE(!set->ss_finalized))
+		return false;
+	/*
+	 * Open-coded rather than bsearch() to call strcmp() directly,
+	 * avoiding an indirect comparator call per search step.
+	 */
+	while (low < high) {
+		unsigned int mid = low + (high - low) / 2;
+		int cmp = strcmp(str, set->ss_strings[mid]);
+
+		if (cmp == 0)
+			return true;
+		if (cmp < 0)
+			high = mid;
+		else
+			low = mid + 1;
+	}
+	return false;
+}
+EXPORT_SYMBOL_GPL(stringset_is_member);
+
+/**
+ * stringset_copy - Duplicate strings to another stringset
+ * @dest: An unallocated, initialized stringset to be filled
+ * @src: An initialized, finalized stringset to be copied from
+ * @gfp: Memory allocation flags
+ *
+ * @dest must be initialized -- via STRINGSET_INIT, DEFINE_STRINGSET,
+ * stringset_init(), or a prior stringset_destroy() -- and must not yet
+ * have storage allocated. An already-allocated @dest is a caller error:
+ * the copy warns, frees @dest's storage, and returns failure.
+ *
+ * @src must be finalized: a copy reproduces @src's sorted, queryable
+ * state, so copying an unfinalized stringset warns and returns failure.
+ *
+ * On success @dest is populated and ready for use; no call to
+ * stringset_finalize() is required. On failure @dest is left as an
+ * empty, finalized stringset, so callers can safely run queries
+ * against it without first checking the return value.
+ *
+ * Return:
+ * * %true: All strings in @src were copied to @dest
+ * * %false: A failure occurred; @dest is empty and finalized
+ */
+bool stringset_copy(struct stringset *dest, const struct stringset *src, gfp_t gfp)
+{
+	unsigned int i;
+
+	if (WARN_ON_ONCE(dest->ss_strings))
+		goto out_fail;
+	if (WARN_ON_ONCE(!src->ss_finalized))
+		goto out_fail;
+	if (src->ss_count && !stringset_alloc(dest, src->ss_count, gfp))
+		goto out_fail;
+	for (i = 0; i < src->ss_count; i++) {
+		char *entry = kstrdup(src->ss_strings[i], gfp);
+
+		if (!entry)
+			goto out_fail;
+		dest->ss_strings[dest->ss_count++] = entry;
+	}
+	/* src is finalized and thus sorted, so dest is too */
+	dest->ss_finalized = true;
+	return true;
+
+out_fail:
+	stringset_destroy(dest);
+	dest->ss_finalized = true;
+	return false;
+}
+EXPORT_SYMBOL_GPL(stringset_copy);
+
+/**
+ * stringset_intersection - Report if there are common strings
+ * @set1: An initialized and finalized stringset
+ * @set2: Another initialized and finalized stringset
+ *
+ * Uses merge-style comparison of two sorted arrays for O(N+M)
+ * complexity. Both stringsets must have been finalized first; a
+ * query on an unfinalized stringset warns and returns %false.
+ *
+ * Return:
+ * * %true: @set1 and @set2 have at least one common string
+ * * %false: @set1 and @set2 have no strings in common, or either
+ *   stringset is not finalized
+ */
+bool stringset_intersection(const struct stringset *set1, const struct stringset *set2)
+{
+	unsigned int i = 0, j = 0;
+
+	if (WARN_ON_ONCE(!set1->ss_finalized) ||
+	    WARN_ON_ONCE(!set2->ss_finalized))
+		return false;
+	while (i < set1->ss_count && j < set2->ss_count) {
+		int cmp = strcmp(set1->ss_strings[i], set2->ss_strings[j]);
+
+		if (cmp == 0)
+			return true;
+		if (cmp < 0)
+			i++;
+		else
+			j++;
+	}
+	return false;
+}
+EXPORT_SYMBOL_GPL(stringset_intersection);

-- 
2.56.0


  parent reply	other threads:[~2026-11-13 19:22 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-13 19:21 [PATCH net-next v3 00/10] Deliver TLS session tags to upper-layer consumers (NFSD) Chuck Lever
2026-11-13 19:21 ` [PATCH net-next v3 01/10] handshake: Add tags to "done" downcall Chuck Lever
2026-11-13 19:22 ` Chuck Lever [this message]
2026-11-13 19:22 ` [PATCH net-next v3 03/10] lib: Add KUnit tests for the stringset data structure Chuck Lever
2026-11-13 19:22 ` [PATCH net-next v3 04/10] handshake: Add a session-tags argument to the TLS done callback Chuck Lever
2026-11-13 19:22 ` [PATCH net-next v3 05/10] handshake: Pick up session tags passed during the DONE downcall Chuck Lever
2026-11-13 19:22 ` [PATCH net-next v3 06/10] handshake: Add a kunit test for the completion gate Chuck Lever
2026-11-13 19:22 ` [PATCH net-next v3 07/10] handshake: Advertise the session-tag cap to user space Chuck Lever
2026-11-13 19:22 ` [PATCH net-next v3 08/10] SUNRPC: Copy the TLS session tags when they are available Chuck Lever
2026-11-13 19:22 ` [PATCH net-next v3 09/10] NFSD: Implement export tagging Chuck Lever
2026-11-13 19:22 ` [PATCH net-next v3 10/10] NFSD: Add allow_tags to the netlink export interface Chuck Lever

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261113-tls-session-tags-v3-2-91271964cdb0@kernel.org \
    --to=cel@kernel.org \
    --cc=Dai.Ngo@oracle.com \
    --cc=axboe@kernel.dk \
    --cc=davem@davemloft.net \
    --cc=donald.hunter@gmail.com \
    --cc=edumazet@kernel.org \
    --cc=hch@lst.de \
    --cc=horms@kernel.org \
    --cc=jlayton@kernel.org \
    --cc=john.fastabend@gmail.com \
    --cc=kbusch@kernel.org \
    --cc=kch@nvidia.com \
    --cc=kernel-tls-handshake@lists.linux.dev \
    --cc=kuba@kernel.org \
    --cc=linux-nfs@vger.kernel.org \
    --cc=linux-nvme@lists.infradead.org \
    --cc=neil@brown.name \
    --cc=netdev@vger.kernel.org \
    --cc=okorniev@redhat.com \
    --cc=pabeni@redhat.com \
    --cc=sagi@grimberg.me \
    --cc=sd@queasysnail.net \
    --cc=tom@talpey.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox