summary refs log tree commit diff
path: root/src/libnm-glib-aux/nm-ref-string.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libnm-glib-aux/nm-ref-string.c')
-rw-r--r--src/libnm-glib-aux/nm-ref-string.c190
1 files changed, 190 insertions, 0 deletions
diff --git a/src/libnm-glib-aux/nm-ref-string.c b/src/libnm-glib-aux/nm-ref-string.c
new file mode 100644
index 00000000..93e97c79
--- /dev/null
+++ b/src/libnm-glib-aux/nm-ref-string.c
@@ -0,0 +1,190 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "libnm-glib-aux/nm-default-glib-i18n-lib.h"
+
+#include "nm-ref-string.h"
+
+/*****************************************************************************/
+
+G_LOCK_DEFINE_STATIC(gl_lock);
+static GHashTable *gl_hash;
+
+/*****************************************************************************/
+
+static void
+_ref_string_get(const NMRefString *rstr, const char **out_str, gsize *out_len)
+{
+    if (rstr->len == G_MAXSIZE) {
+        *out_len = rstr->_priv_lookup.l_len;
+        *out_str = rstr->_priv_lookup.l_str;
+    } else {
+        *out_len = rstr->len;
+        *out_str = rstr->str;
+    }
+}
+
+static guint
+_ref_string_hash(gconstpointer ptr)
+{
+    const char *cstr;
+    gsize       len;
+
+    _ref_string_get(ptr, &cstr, &len);
+    return nm_hash_mem(1463435489u, cstr, len);
+}
+
+static gboolean
+_ref_string_equal(gconstpointer ptr_a, gconstpointer ptr_b)
+{
+    const char *cstr_a;
+    const char *cstr_b;
+    gsize       len_a;
+    gsize       len_b;
+
+    _ref_string_get(ptr_a, &cstr_a, &len_a);
+    _ref_string_get(ptr_b, &cstr_b, &len_b);
+
+    /* memcmp() accepts "n=0" argument, but it's not clear whether in that case
+     * all pointers must still be valid. The input pointer might be provided by
+     * the user via nm_ref_string_new_len(), and for len=0 we want to allow
+     * also invalid pointers. Hence, this extra "len_a==0" check. */
+    return len_a == len_b && (len_a == 0 || (memcmp(cstr_a, cstr_b, len_a) == 0));
+}
+
+/*****************************************************************************/
+
+void
+_nm_assert_nm_ref_string(NMRefString *rstr)
+{
+    int r;
+
+    nm_assert(rstr);
+
+    if (NM_MORE_ASSERTS > 0) {
+        r = g_atomic_int_get(&rstr->_ref_count);
+        nm_assert(r > 0);
+        nm_assert(r < G_MAXINT);
+    }
+
+    nm_assert(rstr->str[rstr->len] == '\0');
+
+    if (NM_MORE_ASSERTS > 10) {
+        G_LOCK(gl_lock);
+        r = g_atomic_int_get(&rstr->_ref_count);
+        nm_assert(r > 0);
+        nm_assert(r < G_MAXINT);
+
+        nm_assert(rstr == g_hash_table_lookup(gl_hash, rstr));
+        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
+ *   acceptable 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 referred 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)
+{
+    NMRefString *rstr;
+
+    /* @len cannot be close to G_MAXSIZE. For one, that would mean our call
+     * to malloc() below overflows. Also, we use G_MAXSIZE as special length
+     * to indicate using _priv_lookup. */
+    nm_assert(len < G_MAXSIZE - G_STRUCT_OFFSET(NMRefString, str) - 1u);
+
+    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);
+        rstr    = NULL;
+    } else {
+        NMRefString rr_lookup = {
+            .len = G_MAXSIZE,
+            ._priv_lookup =
+                {
+                    .l_len = len,
+                    .l_str = cstr,
+                },
+        };
+
+        rstr = g_hash_table_lookup(gl_hash, &rr_lookup);
+    }
+
+    if (rstr) {
+        nm_assert(({
+            int r = g_atomic_int_get(&rstr->_ref_count);
+
+            (r >= 0 && r < G_MAXINT);
+        }));
+        g_atomic_int_inc(&rstr->_ref_count);
+    } else {
+        rstr = g_malloc((G_STRUCT_OFFSET(NMRefString, str) + 1u) + len);
+        if (len > 0)
+            memcpy((char *) rstr->str, cstr, len);
+        ((char *) rstr->str)[len] = '\0';
+        *((gsize *) &rstr->len)   = len;
+        rstr->_ref_count          = 1;
+
+        if (!g_hash_table_add(gl_hash, rstr))
+            nm_assert_not_reached();
+    }
+
+    G_UNLOCK(gl_lock);
+
+    return rstr;
+}
+
+void
+_nm_ref_string_unref_slow_path(NMRefString *rstr)
+{
+    G_LOCK(gl_lock);
+
+    nm_assert(g_hash_table_lookup(gl_hash, rstr) == rstr);
+
+    if (G_LIKELY(g_atomic_int_dec_and_test(&rstr->_ref_count))) {
+        if (!g_hash_table_remove(gl_hash, rstr))
+            nm_assert_not_reached();
+    }
+
+    G_UNLOCK(gl_lock);
+}
+
+/*****************************************************************************/