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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
|
/* 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);
nm_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;
}
/**
* nmtst_ref_string_find_len:
* @cstr: the string to find.
* @len: length of @cstr.
*
* Returns: (transfer none): %NULL, if the string is currently
* not interned. Otherwise a reference to the interned string.
* Beware: this does not return ownership of the reference,
* it is thus not thread safe. Only use this from unit tests
* when you know that your thread holds a reference to the string
* to keep it alive. Otherwise, this might be a dangling pointer.
*/
NMRefString *
nmtst_ref_string_find_len(const char *cstr, gsize len)
{
NMRefString *rstr = NULL;
/* @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_LIKELY(gl_hash)) {
NMRefString rr_lookup = {
.len = G_MAXSIZE,
._priv_lookup =
{
.l_len = len,
.l_str = cstr,
},
};
rstr = g_hash_table_lookup(gl_hash, &rr_lookup);
}
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);
}
/*****************************************************************************/
|