/* 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); } /*****************************************************************************/