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

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