Blame shared/nm-glib-aux/nm-ref-string.c

Packit Service 87a54e
/* SPDX-License-Identifier: LGPL-2.1-or-later */
Packit 5756e2
Packit 5756e2
#include "nm-default.h"
Packit 5756e2
Packit 5756e2
#include "nm-ref-string.h"
Packit 5756e2
Packit 5756e2
/*****************************************************************************/
Packit 5756e2
Packit 5756e2
typedef struct {
Packit Service a1bd4f
    NMRefString  r;
Packit Service a1bd4f
    volatile int ref_count;
Packit Service a1bd4f
    char         str_data[];
Packit 5756e2
} RefString;
Packit 5756e2
Packit Service a1bd4f
G_LOCK_DEFINE_STATIC(gl_lock);
Packit 5756e2
static GHashTable *gl_hash;
Packit 5756e2
Packit 5756e2
/* the first field of NMRefString is a pointer to the NUL terminated string.
Packit 5756e2
 * This also allows to compare strings with nm_pstr_equal(), although, pointer
Packit 5756e2
 * equality might be better. */
Packit Service a1bd4f
G_STATIC_ASSERT(G_STRUCT_OFFSET(NMRefString, str) == 0);
Packit Service a1bd4f
G_STATIC_ASSERT(G_STRUCT_OFFSET(RefString, r) == 0);
Packit Service a1bd4f
G_STATIC_ASSERT(G_STRUCT_OFFSET(RefString, r.str) == 0);
Packit 5756e2
Packit 5756e2
/*****************************************************************************/
Packit 5756e2
Packit 5756e2
static guint
Packit Service a1bd4f
_ref_string_hash(gconstpointer ptr)
Packit 5756e2
{
Packit Service a1bd4f
    const RefString *a = ptr;
Packit Service a1bd4f
    NMHashState      h;
Packit 5756e2
Packit Service a1bd4f
    nm_hash_init(&h, 1463435489u);
Packit Service a1bd4f
    nm_hash_update(&h, a->r.str, a->r.len);
Packit Service a1bd4f
    return nm_hash_complete(&h);
Packit 5756e2
}
Packit 5756e2
Packit 5756e2
static gboolean
Packit Service a1bd4f
_ref_string_equal(gconstpointer pa, gconstpointer pb)
Packit 5756e2
{
Packit Service a1bd4f
    const RefString *a = pa;
Packit Service a1bd4f
    const RefString *b = pb;
Packit 5756e2
Packit Service a1bd4f
    return a->r.len == b->r.len && memcmp(a->r.str, b->r.str, a->r.len) == 0;
Packit 5756e2
}
Packit 5756e2
Packit 5756e2
/*****************************************************************************/
Packit 5756e2
Packit 5756e2
static void
Packit Service a1bd4f
_ASSERT(const RefString *rstr0)
Packit 5756e2
{
Packit 5756e2
#if NM_MORE_ASSERTS
Packit Service a1bd4f
    int r;
Packit 5756e2
Packit Service a1bd4f
    nm_assert(rstr0);
Packit 5756e2
Packit Service a1bd4f
    G_LOCK(gl_lock);
Packit Service a1bd4f
    r = g_atomic_int_get(&rstr0->ref_count);
Packit 5756e2
Packit Service a1bd4f
    nm_assert(r > 0);
Packit Service a1bd4f
    nm_assert(r < G_MAXINT);
Packit 5756e2
Packit Service a1bd4f
    nm_assert(rstr0 == g_hash_table_lookup(gl_hash, rstr0));
Packit Service a1bd4f
    G_UNLOCK(gl_lock);
Packit 5756e2
#endif
Packit 5756e2
}
Packit 5756e2
Packit 5756e2
/**
Packit 5756e2
 * nm_ref_string_new_len:
Packit 5756e2
 * @cstr: the string to intern. Must contain @len bytes.
Packit 5756e2
 *   If @len is zero, @cstr may be %NULL. Note that it is
Packit 5756e2
 *   acceptable that the string contains a NUL character
Packit 5756e2
 *   within the first @len bytes. That is, the string is
Packit 5756e2
 *   not treated as a NUL terminated string, but as binary.
Packit 5756e2
 *   Also, contrary to strncpy(), this will read all the
Packit 5756e2
 *   first @len bytes. It won't stop at the first NUL.
Packit 5756e2
 * @len: the length of the string (usually there is no NUL character
Packit 5756e2
 *   within the first @len bytes, but that would be acceptable as well
Packit 5756e2
 *   to add binary data).
Packit 5756e2
 *
Packit 5756e2
 * Note that the resulting NMRefString instance will always be NUL terminated
Packit 5756e2
 * (at position @len).
Packit 5756e2
 *
Packit 5756e2
 * Note that NMRefString are always interned/deduplicated. If such a string
Packit 5756e2
 * already exists, the existing instance will be referred and returned.
Packit 5756e2
 *
Packit 5756e2
 *
Packit 5756e2
 * Since all NMRefString are shared and interned, you may use
Packit 5756e2
 * pointer equality to compare them. Note that if a NMRefString contains
Packit 5756e2
 * a NUL character (meaning, if
Packit 5756e2
 *
Packit 5756e2
 *    strlen (nm_ref_string_get_str (str)) != nm_ref_string_get_len (str)
Packit 5756e2
 *
Packit 5756e2
 * ), then pointer in-equality does not mean that the NUL terminated strings
Packit 5756e2
 * are also unequal. In other words, for strings that contain NUL characters,
Packit 5756e2
 *
Packit 5756e2
 *    if (str1 != str2)
Packit 5756e2
 *       assert (!nm_streq0 (nm_ref_string_get_str (str1), nm_ref_string_get_str (str2)));
Packit 5756e2
 *
Packit 5756e2
 * might not hold!
Packit 5756e2
 *
Packit 5756e2
 *
Packit 5756e2
 * NMRefString is thread-safe.
Packit 5756e2
 *
Packit 5756e2
 * Returns: (transfer full): the interned string. This is
Packit 5756e2
 *   never %NULL, but note that %NULL is also a valid NMRefString.
Packit 5756e2
 *   The result must be unrefed with nm_ref_string_unref().
Packit 5756e2
 */
Packit 5756e2
NMRefString *
Packit Service a1bd4f
nm_ref_string_new_len(const char *cstr, gsize len)
Packit 5756e2
{
Packit Service a1bd4f
    RefString *rstr0;
Packit Service a1bd4f
Packit Service a1bd4f
    G_LOCK(gl_lock);
Packit Service a1bd4f
Packit Service a1bd4f
    if (G_UNLIKELY(!gl_hash)) {
Packit Service a1bd4f
        gl_hash = g_hash_table_new_full(_ref_string_hash, _ref_string_equal, g_free, NULL);
Packit Service a1bd4f
        rstr0   = NULL;
Packit Service a1bd4f
    } else {
Packit Service a1bd4f
        NMRefString rr_lookup = {
Packit Service a1bd4f
            .len = len,
Packit Service a1bd4f
            .str = cstr,
Packit Service a1bd4f
        };
Packit Service a1bd4f
Packit Service a1bd4f
        rstr0 = g_hash_table_lookup(gl_hash, &rr_lookup);
Packit Service a1bd4f
    }
Packit Service a1bd4f
Packit Service a1bd4f
    if (rstr0) {
Packit Service a1bd4f
        nm_assert(({
Packit Service a1bd4f
            int r = g_atomic_int_get(&rstr0->ref_count);
Packit Service a1bd4f
Packit Service a1bd4f
            (r >= 0 && r < G_MAXINT);
Packit Service a1bd4f
        }));
Packit Service a1bd4f
        g_atomic_int_inc(&rstr0->ref_count);
Packit Service a1bd4f
    } else {
Packit Service a1bd4f
        rstr0                            = g_malloc(sizeof(RefString) + 1 + len);
Packit Service a1bd4f
        rstr0->ref_count                 = 1;
Packit Service a1bd4f
        *((gsize *) &rstr0->r.len)       = len;
Packit Service a1bd4f
        *((const char **) &rstr0->r.str) = rstr0->str_data;
Packit Service a1bd4f
        if (len > 0)
Packit Service a1bd4f
            memcpy(rstr0->str_data, cstr, len);
Packit Service a1bd4f
        rstr0->str_data[len] = '\0';
Packit Service a1bd4f
Packit Service a1bd4f
        if (!g_hash_table_add(gl_hash, rstr0))
Packit Service a1bd4f
            nm_assert_not_reached();
Packit Service a1bd4f
    }
Packit Service a1bd4f
Packit Service a1bd4f
    G_UNLOCK(gl_lock);
Packit Service a1bd4f
Packit Service a1bd4f
    return &rstr0->r;
Packit 5756e2
}
Packit 5756e2
Packit 5756e2
NMRefString *
Packit Service a1bd4f
nm_ref_string_ref(NMRefString *rstr)
Packit 5756e2
{
Packit Service a1bd4f
    RefString *const rstr0 = (RefString *) rstr;
Packit 5756e2
Packit Service a1bd4f
    if (!rstr)
Packit Service a1bd4f
        return NULL;
Packit 5756e2
Packit Service a1bd4f
    _ASSERT(rstr0);
Packit 5756e2
Packit Service a1bd4f
    g_atomic_int_inc(&rstr0->ref_count);
Packit Service a1bd4f
    return &rstr0->r;
Packit 5756e2
}
Packit 5756e2
Packit 5756e2
void
Packit Service a1bd4f
_nm_ref_string_unref_non_null(NMRefString *rstr)
Packit 5756e2
{
Packit Service a1bd4f
    RefString *const rstr0 = (RefString *) rstr;
Packit Service 8f75d2
    int              r;
Packit 5756e2
Packit Service a1bd4f
    _ASSERT(rstr0);
Packit 5756e2
Packit Service 8f75d2
    /* fast-path: first try to decrement the ref-count without bringing it
Packit Service 8f75d2
     * to zero. */
Packit Service 8f75d2
    r = rstr0->ref_count;
Packit Service 8f75d2
    if (G_LIKELY(r > 1 && g_atomic_int_compare_and_exchange(&rstr0->ref_count, r, r - 1)))
Packit Service a1bd4f
        return;
Packit 5756e2
Packit Service 8f75d2
    /* We apparently are about to return the last reference. Take a lock. */
Packit Service 8f75d2
Packit Service a1bd4f
    G_LOCK(gl_lock);
Packit 5756e2
Packit Service 8f75d2
    nm_assert(g_hash_table_lookup(gl_hash, rstr0) == rstr0);
Packit 5756e2
Packit Service 8f75d2
    if (G_LIKELY(g_atomic_int_dec_and_test(&rstr0->ref_count))) {
Packit Service a1bd4f
        if (!g_hash_table_remove(gl_hash, rstr0))
Packit Service a1bd4f
            nm_assert_not_reached();
Packit Service a1bd4f
    }
Packit 5756e2
Packit Service a1bd4f
    G_UNLOCK(gl_lock);
Packit 5756e2
}
Packit 5756e2
Packit 5756e2
/*****************************************************************************/