1 /* wmem_strbuf.c
2 * Wireshark Memory Manager String Buffer
3 * Copyright 2012, Evan Huus <eapache@gmail.com>
4 *
5 * Wireshark - Network traffic analyzer
6 * By Gerald Combs <gerald@wireshark.org>
7 * Copyright 1998 Gerald Combs
8 *
9 * SPDX-License-Identifier: GPL-2.0-or-later
10 */
11
12 #include "config.h"
13
14 #include <string.h>
15 #include <stdio.h>
16 #include <errno.h>
17 #include <glib.h>
18
19 #include "wmem-int.h"
20 #include "wmem_core.h"
21 #include "wmem_strbuf.h"
22
23 #define DEFAULT_MINIMUM_LEN 16
24
25 /* Holds a wmem-allocated string-buffer.
26 * len is the length of the string (not counting the null-terminator) and
27 * should be the same as strlen(str) unless the string contains embedded
28 * nulls.
29 * alloc_len is the length of the raw buffer pointed to by str, regardless of
30 * what string is actually being stored (i.e. the buffer contents)
31 * max_len is the maximum permitted alloc_len (NOT the maximum permitted len,
32 * which must be one shorter than alloc_len to permit null-termination).
33 * When max_len is 0 (the default), no maximum is enforced.
34 */
35 struct _wmem_strbuf_t {
36 wmem_allocator_t *allocator;
37
38 gchar *str;
39
40 gsize len;
41 gsize alloc_len;
42 gsize max_len;
43 };
44
45 /* _ROOM accounts for the null-terminator, _RAW_ROOM does not.
46 * Some functions need one, some functions need the other. */
47 #define WMEM_STRBUF_ROOM(S) ((S)->alloc_len - (S)->len - 1)
48 #define WMEM_STRBUF_RAW_ROOM(S) ((S)->alloc_len - (S)->len)
49
50 wmem_strbuf_t *
wmem_strbuf_sized_new(wmem_allocator_t * allocator,gsize alloc_len,gsize max_len)51 wmem_strbuf_sized_new(wmem_allocator_t *allocator,
52 gsize alloc_len, gsize max_len)
53 {
54 wmem_strbuf_t *strbuf;
55
56 ASSERT((max_len == 0) || (alloc_len <= max_len));
57
58 strbuf = wmem_new(allocator, wmem_strbuf_t);
59
60 strbuf->allocator = allocator;
61 strbuf->len = 0;
62 strbuf->alloc_len = alloc_len ? alloc_len : DEFAULT_MINIMUM_LEN;
63 strbuf->max_len = max_len;
64
65 strbuf->str = (gchar *)wmem_alloc(strbuf->allocator, strbuf->alloc_len);
66 strbuf->str[0] = '\0';
67
68 return strbuf;
69 }
70
71 wmem_strbuf_t *
wmem_strbuf_new(wmem_allocator_t * allocator,const gchar * str)72 wmem_strbuf_new(wmem_allocator_t *allocator, const gchar *str)
73 {
74 wmem_strbuf_t *strbuf;
75 gsize len, alloc_len;
76
77 len = str ? strlen(str) : 0;
78 alloc_len = DEFAULT_MINIMUM_LEN;
79
80 /* +1 for the null-terminator */
81 while (alloc_len < (len + 1)) {
82 alloc_len *= 2;
83 }
84
85 strbuf = wmem_strbuf_sized_new(allocator, alloc_len, 0);
86
87 if (str && len > 0) {
88 (void) g_strlcpy(strbuf->str, str, alloc_len);
89 strbuf->len = len;
90 }
91
92 return strbuf;
93 }
94
95 /* grows the allocated size of the wmem_strbuf_t. If max_len is set, then
96 * not guaranteed to grow by the full amount to_add */
97 static inline void
wmem_strbuf_grow(wmem_strbuf_t * strbuf,const gsize to_add)98 wmem_strbuf_grow(wmem_strbuf_t *strbuf, const gsize to_add)
99 {
100 gsize new_alloc_len, new_len;
101
102 /* short-circuit for efficiency if we have room already; greatly speeds up
103 * repeated calls to wmem_strbuf_append_c and others which grow a little bit
104 * at a time.
105 */
106 if (WMEM_STRBUF_ROOM(strbuf) >= to_add) {
107 return;
108 }
109
110 new_alloc_len = strbuf->alloc_len;
111 new_len = strbuf->len + to_add;
112
113 /* +1 for the null-terminator */
114 while (new_alloc_len < (new_len + 1)) {
115 new_alloc_len *= 2;
116 }
117
118 /* max length only enforced if not 0 */
119 if (strbuf->max_len && new_alloc_len > strbuf->max_len) {
120 new_alloc_len = strbuf->max_len;
121 }
122
123 if (new_alloc_len == strbuf->alloc_len) {
124 return;
125 }
126
127 strbuf->str = (gchar *)wmem_realloc(strbuf->allocator, strbuf->str, new_alloc_len);
128
129 strbuf->alloc_len = new_alloc_len;
130 }
131
132 void
wmem_strbuf_append(wmem_strbuf_t * strbuf,const gchar * str)133 wmem_strbuf_append(wmem_strbuf_t *strbuf, const gchar *str)
134 {
135 gsize append_len;
136
137 if (!str || str[0] == '\0') {
138 return;
139 }
140
141 append_len = strlen(str);
142
143 wmem_strbuf_grow(strbuf, append_len);
144
145 (void) g_strlcpy(&strbuf->str[strbuf->len], str, strbuf->max_len ? WMEM_STRBUF_RAW_ROOM(strbuf) : append_len+1);
146
147 strbuf->len = MIN(strbuf->len + append_len, strbuf->alloc_len - 1);
148 }
149
150 void
wmem_strbuf_append_len(wmem_strbuf_t * strbuf,const gchar * str,gsize append_len)151 wmem_strbuf_append_len(wmem_strbuf_t *strbuf, const gchar *str, gsize append_len)
152 {
153
154 if (!append_len || !str) {
155 return;
156 }
157
158 wmem_strbuf_grow(strbuf, append_len);
159
160 if (strbuf->max_len) {
161 append_len = MIN(append_len, WMEM_STRBUF_ROOM(strbuf));
162 }
163
164 memcpy(&strbuf->str[strbuf->len], str, append_len);
165 strbuf->len += append_len;
166 strbuf->str[strbuf->len] = '\0';
167 }
168
169 static inline
_strbuf_vsnprintf(wmem_strbuf_t * strbuf,const char * format,va_list ap,gboolean reset)170 int _strbuf_vsnprintf(wmem_strbuf_t *strbuf, const char *format, va_list ap, gboolean reset)
171 {
172 int want_len;
173 char *buffer = &strbuf->str[strbuf->len];
174 size_t buffer_size = WMEM_STRBUF_RAW_ROOM(strbuf);
175
176 want_len = vsnprintf(buffer, buffer_size, format, ap);
177 if (want_len < 0) {
178 /* Error. */
179 g_warning("%s: vsnprintf: (%d) %s", G_STRFUNC, want_len, g_strerror(errno));
180 return -1;
181 }
182 if ((size_t)want_len < buffer_size) {
183 /* Success. */
184 strbuf->len += want_len;
185 return 0;
186 }
187
188 /* No space in buffer, output was truncated. */
189 if (reset) {
190 strbuf->str[strbuf->len] = '\0'; /* Reset. */
191 }
192 else {
193 strbuf->len += buffer_size - 1; /* Append. */
194 ASSERT(strbuf->len == strbuf->alloc_len - 1);
195 }
196
197 return want_len; /* Length (not including terminating null) that would be written
198 if there was enough space in buffer. */
199 }
200
201 void
wmem_strbuf_append_vprintf(wmem_strbuf_t * strbuf,const gchar * fmt,va_list ap)202 wmem_strbuf_append_vprintf(wmem_strbuf_t *strbuf, const gchar *fmt, va_list ap)
203 {
204 int want_len;
205 va_list ap2;
206
207 va_copy(ap2, ap);
208 /* Try to write buffer, check if output fits. */
209 want_len = _strbuf_vsnprintf(strbuf, fmt, ap2, TRUE); /* Remove output if truncated. */
210 va_end(ap2);
211 if (want_len <= 0)
212 return;
213
214 /* Resize buffer and try again. This could hit the 'max_len' ceiling. */
215 wmem_strbuf_grow(strbuf, want_len);
216 _strbuf_vsnprintf(strbuf, fmt, ap, FALSE); /* Keep output if truncated. */
217 }
218
219 void
wmem_strbuf_append_printf(wmem_strbuf_t * strbuf,const gchar * format,...)220 wmem_strbuf_append_printf(wmem_strbuf_t *strbuf, const gchar *format, ...)
221 {
222 va_list ap;
223
224 va_start(ap, format);
225 wmem_strbuf_append_vprintf(strbuf, format, ap);
226 va_end(ap);
227 }
228
229 void
wmem_strbuf_append_c(wmem_strbuf_t * strbuf,const gchar c)230 wmem_strbuf_append_c(wmem_strbuf_t *strbuf, const gchar c)
231 {
232 wmem_strbuf_grow(strbuf, 1);
233
234 if (!strbuf->max_len || WMEM_STRBUF_ROOM(strbuf) >= 1) {
235 strbuf->str[strbuf->len] = c;
236 strbuf->len++;
237 strbuf->str[strbuf->len] = '\0';
238 }
239 }
240
241 void
wmem_strbuf_append_unichar(wmem_strbuf_t * strbuf,const gunichar c)242 wmem_strbuf_append_unichar(wmem_strbuf_t *strbuf, const gunichar c)
243 {
244 gchar buf[6];
245 gsize charlen;
246
247 charlen = g_unichar_to_utf8(c, buf);
248
249 wmem_strbuf_grow(strbuf, charlen);
250
251 if (!strbuf->max_len || WMEM_STRBUF_ROOM(strbuf) >= charlen) {
252 memcpy(&strbuf->str[strbuf->len], buf, charlen);
253 strbuf->len += charlen;
254 strbuf->str[strbuf->len] = '\0';
255 }
256 }
257
258 void
wmem_strbuf_truncate(wmem_strbuf_t * strbuf,const gsize len)259 wmem_strbuf_truncate(wmem_strbuf_t *strbuf, const gsize len)
260 {
261 if (len >= strbuf->len) {
262 return;
263 }
264
265 strbuf->str[len] = '\0';
266 strbuf->len = len;
267 }
268
269 const gchar *
wmem_strbuf_get_str(wmem_strbuf_t * strbuf)270 wmem_strbuf_get_str(wmem_strbuf_t *strbuf)
271 {
272 return strbuf->str;
273 }
274
275 gsize
wmem_strbuf_get_len(wmem_strbuf_t * strbuf)276 wmem_strbuf_get_len(wmem_strbuf_t *strbuf)
277 {
278 return strbuf->len;
279 }
280
281 /* Truncates the allocated memory down to the minimal amount, frees the header
282 * structure, and returns a non-const pointer to the raw string. The
283 * wmem_strbuf_t structure cannot be used after this is called.
284 */
285 char *
wmem_strbuf_finalize(wmem_strbuf_t * strbuf)286 wmem_strbuf_finalize(wmem_strbuf_t *strbuf)
287 {
288 char *ret;
289
290 ret = (char *)wmem_realloc(strbuf->allocator, strbuf->str, strbuf->len+1);
291
292 wmem_free(strbuf->allocator, strbuf);
293
294 return ret;
295 }
296
297 void
wmem_strbuf_destroy(wmem_strbuf_t * strbuf)298 wmem_strbuf_destroy(wmem_strbuf_t *strbuf)
299 {
300 wmem_allocator_t *allocator = strbuf->allocator;
301
302 wmem_free(allocator, strbuf->str);
303 wmem_free(allocator, strbuf);
304 }
305
306 /*
307 * Editor modelines - https://www.wireshark.org/tools/modelines.html
308 *
309 * Local variables:
310 * c-basic-offset: 4
311 * tab-width: 8
312 * indent-tabs-mode: nil
313 * End:
314 *
315 * vi: set shiftwidth=4 tabstop=8 expandtab:
316 * :indentSize=4:tabSize=8:noTabs=true:
317 */
318