summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.am2
-rw-r--r--shared/meson.build1
-rw-r--r--shared/nm-glib-aux/nm-ref-string.c187
-rw-r--r--shared/nm-glib-aux/nm-ref-string.h52
4 files changed, 242 insertions, 0 deletions
diff --git a/Makefile.am b/Makefile.am
index f863b29f34..48e0cd55df 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -373,6 +373,8 @@ shared_nm_glib_aux_libnm_glib_aux_la_SOURCES = \
shared/nm-glib-aux/nm-obj.h \
shared/nm-glib-aux/nm-random-utils.c \
shared/nm-glib-aux/nm-random-utils.h \
+ shared/nm-glib-aux/nm-ref-string.c \
+ shared/nm-glib-aux/nm-ref-string.h \
shared/nm-glib-aux/nm-secret-utils.c \
shared/nm-glib-aux/nm-secret-utils.h \
shared/nm-glib-aux/nm-shared-utils.c \
diff --git a/shared/meson.build b/shared/meson.build
index d542804eb4..aa10f792f8 100644
--- a/shared/meson.build
+++ b/shared/meson.build
@@ -185,6 +185,7 @@ shared_nm_glib_aux = static_library(
'nm-glib-aux/nm-json-aux.c',
'nm-glib-aux/nm-keyfile-aux.c',
'nm-glib-aux/nm-random-utils.c',
+ 'nm-glib-aux/nm-ref-string.c',
'nm-glib-aux/nm-secret-utils.c',
'nm-glib-aux/nm-shared-utils.c',
'nm-glib-aux/nm-time-utils.c'),
diff --git a/shared/nm-glib-aux/nm-ref-string.c b/shared/nm-glib-aux/nm-ref-string.c
new file mode 100644
index 0000000000..6259758c9e
--- /dev/null
+++ b/shared/nm-glib-aux/nm-ref-string.c
@@ -0,0 +1,187 @@
+// SPDX-License-Identifier: LGPL-2.1+
+
+#include "nm-default.h"
+
+#include "nm-ref-string.h"
+
+/*****************************************************************************/
+
+typedef struct {
+ NMRefString r;
+ volatile int ref_count;
+ char str_data[];
+} RefString;
+
+G_LOCK_DEFINE_STATIC (gl_lock);
+static GHashTable *gl_hash;
+
+/* the first field of NMRefString is a pointer to the NUL terminated string.
+ * This also allows to compare strings with nm_pstr_equal(), although, pointer
+ * equality might be better. */
+G_STATIC_ASSERT (G_STRUCT_OFFSET (NMRefString, str) == 0);
+G_STATIC_ASSERT (G_STRUCT_OFFSET (RefString, r) == 0);
+G_STATIC_ASSERT (G_STRUCT_OFFSET (RefString, r.str) == 0);
+
+/*****************************************************************************/
+
+static guint
+_ref_string_hash (gconstpointer ptr)
+{
+ const RefString *a = ptr;
+ NMHashState h;
+
+ nm_hash_init (&h, 1463435489u);
+ nm_hash_update (&h, a->r.str, a->r.len);
+ return nm_hash_complete (&h);
+}
+
+static gboolean
+_ref_string_equal (gconstpointer pa, gconstpointer pb)
+{
+ const RefString *a = pa;
+ const RefString *b = pb;
+
+ return a->r.len == b->r.len
+ && memcmp (a->r.str, b->r.str, a->r.len) == 0;
+}
+
+/*****************************************************************************/
+
+static void
+_ASSERT (const RefString *rstr0)
+{
+ int r;
+
+ nm_assert (rstr0);
+
+ G_LOCK (gl_lock);
+ r = g_atomic_int_get (&rstr0->ref_count);
+
+ nm_assert (r > 0);
+ nm_assert (r < G_MAXINT);
+
+ nm_assert (rstr0 == g_hash_table_lookup (gl_hash, rstr0));
+ G_UNLOCK (gl_lock);
+}
+
+/**
+ * nm_ref_string_new_len:
+ * @cstr: the string to intern. Must contain @len bytes.
+ * If @len is zero, @cstr may be %NULL. Note that it is
+ * accetable that the string contains a NUL character
+ * within the first @len bytes. That is, the string is
+ * not treated as a NUL terminated string, but as binary.
+ * Also, contrary to strncpy(), this will read all the
+ * first @len bytes. It won't stop at the first NUL.
+ * @len: the length of the string (usually there is no NUL character
+ * within the first @len bytes, but that would be acceptable as well
+ * to add binary data).
+ *
+ * Note that the resulting NMRefString instance will always be NUL terminated
+ * (at position @len).
+ *
+ * Note that NMRefString are always interned/deduplicated. If such a string
+ * already exists, the existing instance will be refered and returned.
+ *
+ *
+ * Since all NMRefString are shared and interned, you may use
+ * pointer equality to compare them. Note that if a NMRefString contains
+ * a NUL character (meaning, if
+ *
+ * strlen (nm_ref_string_get_str (str)) != nm_ref_string_get_len (str)
+ *
+ * ), then pointer in-equality does not mean that the NUL terminated strings
+ * are also unequal. In other words, for strings that contain NUL characters,
+ *
+ * if (str1 != str2)
+ * assert (!nm_streq0 (nm_ref_string_get_str (str1), nm_ref_string_get_str (str2)));
+ *
+ * might not hold!
+ *
+ *
+ * NMRefString is thread-safe.
+ *
+ * Returns: (transfer full): the interned string. This is
+ * never %NULL, but note that %NULL is also a valid NMRefString.
+ * The result must be unrefed with nm_ref_string_unref().
+ */
+NMRefString *
+nm_ref_string_new_len (const char *cstr, gsize len)
+{
+ RefString *rstr0;
+
+ G_LOCK (gl_lock);
+
+ if (G_UNLIKELY (!gl_hash)) {
+ gl_hash = g_hash_table_new_full (_ref_string_hash, _ref_string_equal, g_free, NULL);
+ rstr0 = NULL;
+ } else {
+ NMRefString rr_lookup = {
+ .len = len,
+ .str = cstr,
+ };
+
+ rstr0 = g_hash_table_lookup (gl_hash, &rr_lookup);
+ }
+
+ if (rstr0) {
+ nm_assert (({
+ int r = g_atomic_int_get (&rstr0->ref_count);
+
+ (r >= 0 && r < G_MAXINT);
+ }));
+ g_atomic_int_inc (&rstr0->ref_count);
+ } else {
+ rstr0 = g_malloc (sizeof (RefString) + 1 + len);
+ rstr0->ref_count = 1;
+ *((gsize *) rstr0->r.len) = len;
+ *((const char **) rstr0->r.str) = rstr0->str_data;
+ if (len > 0)
+ memcpy (rstr0->str_data, cstr, len);
+ rstr0->str_data[len] = '\0';
+
+ if (!g_hash_table_add (gl_hash, rstr0))
+ nm_assert_not_reached ();
+ }
+
+ G_UNLOCK (gl_lock);
+
+ return &rstr0->r;
+}
+
+NMRefString *
+nm_ref_string_ref (NMRefString *rstr)
+{
+ RefString *const rstr0 = (RefString *) rstr;
+
+ if (!rstr)
+ return NULL;
+
+ _ASSERT (rstr0);
+
+ g_atomic_int_inc (&rstr0->ref_count);
+ return &rstr0->r;
+}
+
+void
+_nm_ref_string_unref_non_null (NMRefString *rstr)
+{
+ RefString *const rstr0 = (RefString *) rstr;
+
+ _ASSERT (rstr0);
+
+ if (G_LIKELY (!g_atomic_int_dec_and_test (&rstr0->ref_count)))
+ return;
+
+ G_LOCK (gl_lock);
+
+ /* in the fast-path above, we already decremented the ref-count to zero.
+ * We need recheck that the ref-count is still zero. */
+
+ if (g_atomic_int_get (&rstr0->ref_count) == 0)
+ g_hash_table_remove (gl_hash, rstr0);
+
+ G_UNLOCK (gl_lock);
+}
+
+/*****************************************************************************/
diff --git a/shared/nm-glib-aux/nm-ref-string.h b/shared/nm-glib-aux/nm-ref-string.h
new file mode 100644
index 0000000000..2a0b072df8
--- /dev/null
+++ b/shared/nm-glib-aux/nm-ref-string.h
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: LGPL-2.1+
+
+#ifndef __NM_REF_STRING_H__
+#define __NM_REF_STRING_H__
+
+/*****************************************************************************/
+
+typedef struct {
+ const char *const str;
+ const gsize len;
+} NMRefString;
+
+/*****************************************************************************/
+
+NMRefString *nm_ref_string_new_len (const char *cstr, gsize len);
+
+static inline NMRefString *
+nm_ref_string_new (const char *cstr)
+{
+ return cstr
+ ? nm_ref_string_new_len (cstr, strlen (cstr))
+ : NULL;
+}
+
+NMRefString *nm_ref_string_ref (NMRefString *rstr);
+void _nm_ref_string_unref_non_null (NMRefString *rstr);
+
+static inline void
+nm_ref_string_unref (NMRefString *rstr)
+{
+ if (rstr)
+ _nm_ref_string_unref_non_null (rstr);
+}
+
+NM_AUTO_DEFINE_FCN_VOID0 (NMRefString *, _nm_auto_ref_string, _nm_ref_string_unref_non_null)
+#define nm_auto_ref_string nm_auto(_nm_auto_ref_string)
+
+/*****************************************************************************/
+
+static inline const char *
+nm_ref_string_get_str (NMRefString *rstr)
+{
+ return rstr ? rstr->str : NULL;
+}
+
+static inline gsize
+nm_ref_string_get_len (NMRefString *rstr)
+{
+ return rstr ? rstr->len : 0u;
+}
+
+#endif /* __NM_REF_STRING_H__ */