summaryrefslogtreecommitdiff
path: root/shared/nm-glib-aux/nm-ref-string.c
blob: 6259758c9e2fb34d1cbf6ca6a154ca0c0115feb2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
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);
}

/*****************************************************************************/