1 /* $NetBSD: prop_string.c,v 1.18 2023/11/17 21:29:33 thorpej Exp $ */
2
3 /*-
4 * Copyright (c) 2006, 2020 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Jason R. Thorpe.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32 #include "prop_object_impl.h"
33 #include <prop/prop_string.h>
34
35 #include <sys/rbtree.h>
36 #if defined(_KERNEL) || defined(_STANDALONE)
37 #include <sys/stdarg.h>
38 #else
39 #include <stdarg.h>
40 #endif /* _KERNEL || _STANDALONE */
41
42 struct _prop_string {
43 struct _prop_object ps_obj;
44 union {
45 char * psu_mutable;
46 const char * psu_immutable;
47 } ps_un;
48 #define ps_mutable ps_un.psu_mutable
49 #define ps_immutable ps_un.psu_immutable
50 size_t ps_size; /* not including \0 */
51 struct rb_node ps_link;
52 int ps_flags;
53 };
54
55 #define PS_F_NOCOPY 0x01
56 #define PS_F_MUTABLE 0x02
57
58 _PROP_POOL_INIT(_prop_string_pool, sizeof(struct _prop_string), "propstng")
59
60 _PROP_MALLOC_DEFINE(M_PROP_STRING, "prop string",
61 "property string container object")
62
63 static _prop_object_free_rv_t
64 _prop_string_free(prop_stack_t, prop_object_t *);
65 static bool _prop_string_externalize(
66 struct _prop_object_externalize_context *,
67 void *);
68 static _prop_object_equals_rv_t
69 _prop_string_equals(prop_object_t, prop_object_t,
70 void **, void **,
71 prop_object_t *, prop_object_t *);
72
73 static const struct _prop_object_type _prop_object_type_string = {
74 .pot_type = PROP_TYPE_STRING,
75 .pot_free = _prop_string_free,
76 .pot_extern = _prop_string_externalize,
77 .pot_equals = _prop_string_equals,
78 };
79
80 #define prop_object_is_string(x) \
81 ((x) != NULL && (x)->ps_obj.po_type == &_prop_object_type_string)
82 #define prop_string_contents(x) ((x)->ps_immutable ? (x)->ps_immutable : "")
83
84 /*
85 * In order to reduce memory usage, all immutable string objects are
86 * de-duplicated.
87 */
88
89 static int
90 /*ARGSUSED*/
_prop_string_rb_compare_nodes(void * ctx _PROP_ARG_UNUSED,const void * n1,const void * n2)91 _prop_string_rb_compare_nodes(void *ctx _PROP_ARG_UNUSED,
92 const void *n1, const void *n2)
93 {
94 const struct _prop_string * const ps1 = n1;
95 const struct _prop_string * const ps2 = n2;
96
97 _PROP_ASSERT(ps1->ps_immutable != NULL);
98 _PROP_ASSERT(ps2->ps_immutable != NULL);
99
100 return strcmp(ps1->ps_immutable, ps2->ps_immutable);
101 }
102
103 static int
104 /*ARGSUSED*/
_prop_string_rb_compare_key(void * ctx _PROP_ARG_UNUSED,const void * n,const void * v)105 _prop_string_rb_compare_key(void *ctx _PROP_ARG_UNUSED,
106 const void *n, const void *v)
107 {
108 const struct _prop_string * const ps = n;
109 const char * const cp = v;
110
111 _PROP_ASSERT(ps->ps_immutable != NULL);
112
113 return strcmp(ps->ps_immutable, cp);
114 }
115
116 static const rb_tree_ops_t _prop_string_rb_tree_ops = {
117 .rbto_compare_nodes = _prop_string_rb_compare_nodes,
118 .rbto_compare_key = _prop_string_rb_compare_key,
119 .rbto_node_offset = offsetof(struct _prop_string, ps_link),
120 .rbto_context = NULL
121 };
122
123 static struct rb_tree _prop_string_tree;
124
125 _PROP_ONCE_DECL(_prop_string_init_once)
_PROP_MUTEX_DECL_STATIC(_prop_string_tree_mutex)126 _PROP_MUTEX_DECL_STATIC(_prop_string_tree_mutex)
127
128 static int
129 _prop_string_init(void)
130 {
131
132 _PROP_MUTEX_INIT(_prop_string_tree_mutex);
133 rb_tree_init(&_prop_string_tree,
134 &_prop_string_rb_tree_ops);
135
136 return 0;
137 }
138
139 /* ARGSUSED */
140 static _prop_object_free_rv_t
_prop_string_free(prop_stack_t stack,prop_object_t * obj)141 _prop_string_free(prop_stack_t stack, prop_object_t *obj)
142 {
143 prop_string_t ps = *obj;
144
145 if ((ps->ps_flags & PS_F_MUTABLE) == 0) {
146 _PROP_MUTEX_LOCK(_prop_string_tree_mutex);
147 /*
148 * Double-check the retain count now that we've
149 * acquired the tree lock; holding this lock prevents
150 * new retains from coming in by finding it in the
151 * tree.
152 */
153 if (_PROP_ATOMIC_LOAD(&ps->ps_obj.po_refcnt) == 0)
154 rb_tree_remove_node(&_prop_string_tree, ps);
155 else
156 ps = NULL;
157 _PROP_MUTEX_UNLOCK(_prop_string_tree_mutex);
158
159 if (ps == NULL)
160 return (_PROP_OBJECT_FREE_DONE);
161 }
162
163 if ((ps->ps_flags & PS_F_NOCOPY) == 0 && ps->ps_mutable != NULL)
164 _PROP_FREE(ps->ps_mutable, M_PROP_STRING);
165 _PROP_POOL_PUT(_prop_string_pool, ps);
166
167 return (_PROP_OBJECT_FREE_DONE);
168 }
169
170 static bool
_prop_string_externalize(struct _prop_object_externalize_context * ctx,void * v)171 _prop_string_externalize(struct _prop_object_externalize_context *ctx,
172 void *v)
173 {
174 prop_string_t ps = v;
175
176 if (ps->ps_size == 0)
177 return (_prop_object_externalize_empty_tag(ctx, "string"));
178
179 if (_prop_object_externalize_start_tag(ctx, "string") == false ||
180 _prop_object_externalize_append_encoded_cstring(ctx,
181 ps->ps_immutable) == false ||
182 _prop_object_externalize_end_tag(ctx, "string") == false)
183 return (false);
184
185 return (true);
186 }
187
188 /* ARGSUSED */
189 static _prop_object_equals_rv_t
_prop_string_equals(prop_object_t v1,prop_object_t v2,void ** stored_pointer1,void ** stored_pointer2,prop_object_t * next_obj1,prop_object_t * next_obj2)190 _prop_string_equals(prop_object_t v1, prop_object_t v2,
191 void **stored_pointer1, void **stored_pointer2,
192 prop_object_t *next_obj1, prop_object_t *next_obj2)
193 {
194 prop_string_t str1 = v1;
195 prop_string_t str2 = v2;
196
197 if (str1 == str2)
198 return (_PROP_OBJECT_EQUALS_TRUE);
199 if (str1->ps_size != str2->ps_size)
200 return (_PROP_OBJECT_EQUALS_FALSE);
201 if (strcmp(prop_string_contents(str1), prop_string_contents(str2)))
202 return (_PROP_OBJECT_EQUALS_FALSE);
203 else
204 return (_PROP_OBJECT_EQUALS_TRUE);
205 }
206
207 static prop_string_t
_prop_string_alloc(int const flags)208 _prop_string_alloc(int const flags)
209 {
210 prop_string_t ps;
211
212 ps = _PROP_POOL_GET(_prop_string_pool);
213 if (ps != NULL) {
214 _prop_object_init(&ps->ps_obj, &_prop_object_type_string);
215
216 ps->ps_mutable = NULL;
217 ps->ps_size = 0;
218 ps->ps_flags = flags;
219 }
220
221 return (ps);
222 }
223
224 static prop_string_t
_prop_string_instantiate(int const flags,const char * const str,size_t const len)225 _prop_string_instantiate(int const flags, const char * const str,
226 size_t const len)
227 {
228 prop_string_t ps;
229
230 _PROP_ONCE_RUN(_prop_string_init_once, _prop_string_init);
231
232 ps = _prop_string_alloc(flags);
233 if (ps != NULL) {
234 ps->ps_immutable = str;
235 ps->ps_size = len;
236
237 if ((flags & PS_F_MUTABLE) == 0) {
238 prop_string_t ops;
239
240 _PROP_MUTEX_LOCK(_prop_string_tree_mutex);
241 ops = rb_tree_insert_node(&_prop_string_tree, ps);
242 if (ops != ps) {
243 /*
244 * Equivalent string object already exist;
245 * free the new one and return a reference
246 * to the existing object.
247 */
248 prop_object_retain(ops);
249 _PROP_MUTEX_UNLOCK(_prop_string_tree_mutex);
250 if ((flags & PS_F_NOCOPY) == 0) {
251 _PROP_FREE(ps->ps_mutable,
252 M_PROP_STRING);
253 }
254 _PROP_POOL_PUT(_prop_string_pool, ps);
255 ps = ops;
256 } else {
257 _PROP_MUTEX_UNLOCK(_prop_string_tree_mutex);
258 }
259 }
260 } else if ((flags & PS_F_NOCOPY) == 0) {
261 _PROP_FREE(__UNCONST(str), M_PROP_STRING);
262 }
263
264 return (ps);
265 }
266
267 _PROP_DEPRECATED(prop_string_create,
268 "this program uses prop_string_create(); all functions "
269 "supporting mutable prop_strings are deprecated.")
270 prop_string_t
prop_string_create(void)271 prop_string_create(void)
272 {
273
274 return (_prop_string_alloc(PS_F_MUTABLE));
275 }
276
277 _PROP_DEPRECATED(prop_string_create_cstring,
278 "this program uses prop_string_create_cstring(); all functions "
279 "supporting mutable prop_strings are deprecated.")
280 prop_string_t
prop_string_create_cstring(const char * str)281 prop_string_create_cstring(const char *str)
282 {
283 prop_string_t ps;
284 char *cp;
285 size_t len;
286
287 _PROP_ASSERT(str != NULL);
288
289 ps = _prop_string_alloc(PS_F_MUTABLE);
290 if (ps != NULL) {
291 len = strlen(str);
292 cp = _PROP_MALLOC(len + 1, M_PROP_STRING);
293 if (cp == NULL) {
294 prop_object_release(ps);
295 return (NULL);
296 }
297 strcpy(cp, str);
298 ps->ps_mutable = cp;
299 ps->ps_size = len;
300 }
301 return (ps);
302 }
303
304 _PROP_DEPRECATED(prop_string_create_cstring_nocopy,
305 "this program uses prop_string_create_cstring_nocopy(), "
306 "which is deprecated; use prop_string_create_nocopy() instead.")
307 prop_string_t
prop_string_create_cstring_nocopy(const char * str)308 prop_string_create_cstring_nocopy(const char *str)
309 {
310 return prop_string_create_nocopy(str);
311 }
312
313 /*
314 * prop_string_create_format --
315 * Create a string object using the provided format string.
316 */
317 prop_string_t __printflike(1, 2)
prop_string_create_format(const char * fmt,...)318 prop_string_create_format(const char *fmt, ...)
319 {
320 char *str = NULL;
321 int len;
322 size_t nlen;
323 va_list ap;
324
325 _PROP_ASSERT(fmt != NULL);
326
327 va_start(ap, fmt);
328 len = vsnprintf(NULL, 0, fmt, ap);
329 va_end(ap);
330
331 if (len < 0)
332 return (NULL);
333 nlen = len + 1;
334
335 str = _PROP_MALLOC(nlen, M_PROP_STRING);
336 if (str == NULL)
337 return (NULL);
338
339 va_start(ap, fmt);
340 vsnprintf(str, nlen, fmt, ap);
341 va_end(ap);
342
343 return _prop_string_instantiate(0, str, (size_t)len);
344 }
345
346 /*
347 * prop_string_create_copy --
348 * Create a string object by coping the provided constant string.
349 */
350 prop_string_t
prop_string_create_copy(const char * str)351 prop_string_create_copy(const char *str)
352 {
353 return prop_string_create_format("%s", str);
354 }
355
356 /*
357 * prop_string_create_nocopy --
358 * Create a string object using the provided external constant
359 * string.
360 */
361 prop_string_t
prop_string_create_nocopy(const char * str)362 prop_string_create_nocopy(const char *str)
363 {
364
365 _PROP_ASSERT(str != NULL);
366
367 return _prop_string_instantiate(PS_F_NOCOPY, str, strlen(str));
368 }
369
370 /*
371 * prop_string_copy --
372 * Copy a string. This reduces to a retain in the common case.
373 * Deprecated mutable string objects must be copied.
374 */
375 prop_string_t
prop_string_copy(prop_string_t ops)376 prop_string_copy(prop_string_t ops)
377 {
378 char *cp;
379
380 if (! prop_object_is_string(ops))
381 return (NULL);
382
383 if ((ops->ps_flags & PS_F_MUTABLE) == 0) {
384 prop_object_retain(ops);
385 return (ops);
386 }
387
388 cp = _PROP_MALLOC(ops->ps_size + 1, M_PROP_STRING);
389 if (cp == NULL)
390 return NULL;
391
392 strcpy(cp, prop_string_contents(ops));
393
394 return _prop_string_instantiate(PS_F_MUTABLE, cp, ops->ps_size);
395 }
396
397 _PROP_DEPRECATED(prop_string_copy_mutable,
398 "this program uses prop_string_copy_mutable(); all functions "
399 "supporting mutable prop_strings are deprecated.")
400 prop_string_t
prop_string_copy_mutable(prop_string_t ops)401 prop_string_copy_mutable(prop_string_t ops)
402 {
403 char *cp;
404
405 if (! prop_object_is_string(ops))
406 return (NULL);
407
408 cp = _PROP_MALLOC(ops->ps_size + 1, M_PROP_STRING);
409 if (cp == NULL)
410 return NULL;
411
412 strcpy(cp, prop_string_contents(ops));
413
414 return _prop_string_instantiate(PS_F_MUTABLE, cp, ops->ps_size);
415 }
416
417 /*
418 * prop_string_size --
419 * Return the size of the string, not including the terminating NUL.
420 */
421 size_t
prop_string_size(prop_string_t ps)422 prop_string_size(prop_string_t ps)
423 {
424
425 if (! prop_object_is_string(ps))
426 return (0);
427
428 return (ps->ps_size);
429 }
430
431 /*
432 * prop_string_value --
433 * Returns a pointer to the string object's value. This pointer
434 * remains valid only as long as the string object.
435 */
436 const char *
prop_string_value(prop_string_t ps)437 prop_string_value(prop_string_t ps)
438 {
439
440 if (! prop_object_is_string(ps))
441 return (NULL);
442
443 if ((ps->ps_flags & PS_F_MUTABLE) == 0)
444 return (ps->ps_immutable);
445
446 return (prop_string_contents(ps));
447 }
448
449 /*
450 * prop_string_copy_value --
451 * Copy the string object's value into the supplied buffer.
452 */
453 bool
prop_string_copy_value(prop_string_t ps,void * buf,size_t buflen)454 prop_string_copy_value(prop_string_t ps, void *buf, size_t buflen)
455 {
456
457 if (! prop_object_is_string(ps))
458 return (false);
459
460 if (buf == NULL || buflen < ps->ps_size + 1)
461 return (false);
462
463 strcpy(buf, prop_string_contents(ps));
464
465 return (true);
466 }
467
468 _PROP_DEPRECATED(prop_string_mutable,
469 "this program uses prop_string_mutable(); all functions "
470 "supporting mutable prop_strings are deprecated.")
471 bool
prop_string_mutable(prop_string_t ps)472 prop_string_mutable(prop_string_t ps)
473 {
474
475 if (! prop_object_is_string(ps))
476 return (false);
477
478 return ((ps->ps_flags & PS_F_MUTABLE) != 0);
479 }
480
481 _PROP_DEPRECATED(prop_string_cstring,
482 "this program uses prop_string_cstring(), "
483 "which is deprecated; use prop_string_copy_value() instead.")
484 char *
prop_string_cstring(prop_string_t ps)485 prop_string_cstring(prop_string_t ps)
486 {
487 char *cp;
488
489 if (! prop_object_is_string(ps))
490 return (NULL);
491
492 cp = _PROP_MALLOC(ps->ps_size + 1, M_TEMP);
493 if (cp != NULL)
494 strcpy(cp, prop_string_contents(ps));
495
496 return (cp);
497 }
498
499 _PROP_DEPRECATED(prop_string_cstring_nocopy,
500 "this program uses prop_string_cstring_nocopy(), "
501 "which is deprecated; use prop_string_value() instead.")
502 const char *
prop_string_cstring_nocopy(prop_string_t ps)503 prop_string_cstring_nocopy(prop_string_t ps)
504 {
505
506 if (! prop_object_is_string(ps))
507 return (NULL);
508
509 return (prop_string_contents(ps));
510 }
511
512 _PROP_DEPRECATED(prop_string_append,
513 "this program uses prop_string_append(); all functions "
514 "supporting mutable prop_strings are deprecated.")
515 bool
prop_string_append(prop_string_t dst,prop_string_t src)516 prop_string_append(prop_string_t dst, prop_string_t src)
517 {
518 char *ocp, *cp;
519 size_t len;
520
521 if (! (prop_object_is_string(dst) &&
522 prop_object_is_string(src)))
523 return (false);
524
525 if ((dst->ps_flags & PS_F_MUTABLE) == 0)
526 return (false);
527
528 len = dst->ps_size + src->ps_size;
529 cp = _PROP_MALLOC(len + 1, M_PROP_STRING);
530 if (cp == NULL)
531 return (false);
532 snprintf(cp, len + 1, "%s%s", prop_string_contents(dst),
533 prop_string_contents(src));
534 ocp = dst->ps_mutable;
535 dst->ps_mutable = cp;
536 dst->ps_size = len;
537 if (ocp != NULL)
538 _PROP_FREE(ocp, M_PROP_STRING);
539
540 return (true);
541 }
542
543 _PROP_DEPRECATED(prop_string_append_cstring,
544 "this program uses prop_string_append_cstring(); all functions "
545 "supporting mutable prop_strings are deprecated.")
546 bool
prop_string_append_cstring(prop_string_t dst,const char * src)547 prop_string_append_cstring(prop_string_t dst, const char *src)
548 {
549 char *ocp, *cp;
550 size_t len;
551
552 if (! prop_object_is_string(dst))
553 return (false);
554
555 _PROP_ASSERT(src != NULL);
556
557 if ((dst->ps_flags & PS_F_MUTABLE) == 0)
558 return (false);
559
560 len = dst->ps_size + strlen(src);
561 cp = _PROP_MALLOC(len + 1, M_PROP_STRING);
562 if (cp == NULL)
563 return (false);
564 snprintf(cp, len + 1, "%s%s", prop_string_contents(dst), src);
565 ocp = dst->ps_mutable;
566 dst->ps_mutable = cp;
567 dst->ps_size = len;
568 if (ocp != NULL)
569 _PROP_FREE(ocp, M_PROP_STRING);
570
571 return (true);
572 }
573
574 /*
575 * prop_string_equals --
576 * Return true if two strings are equivalent.
577 */
578 bool
prop_string_equals(prop_string_t str1,prop_string_t str2)579 prop_string_equals(prop_string_t str1, prop_string_t str2)
580 {
581 if (!prop_object_is_string(str1) || !prop_object_is_string(str2))
582 return (false);
583
584 return prop_object_equals(str1, str2);
585 }
586
587 /*
588 * prop_string_equals_string --
589 * Return true if the string object is equivalent to the specified
590 * C string.
591 */
592 bool
prop_string_equals_string(prop_string_t ps,const char * cp)593 prop_string_equals_string(prop_string_t ps, const char *cp)
594 {
595
596 if (! prop_object_is_string(ps))
597 return (false);
598
599 return (strcmp(prop_string_contents(ps), cp) == 0);
600 }
601
602 _PROP_DEPRECATED(prop_string_equals_cstring,
603 "this program uses prop_string_equals_cstring(), "
604 "which is deprecated; prop_string_equals_string() instead.")
605 bool
prop_string_equals_cstring(prop_string_t ps,const char * cp)606 prop_string_equals_cstring(prop_string_t ps, const char *cp)
607 {
608 return prop_string_equals_string(ps, cp);
609 }
610
611 /*
612 * prop_string_compare --
613 * Compare two string objects, using strcmp() semantics.
614 */
615 int
prop_string_compare(prop_string_t ps1,prop_string_t ps2)616 prop_string_compare(prop_string_t ps1, prop_string_t ps2)
617 {
618 if (!prop_object_is_string(ps1) || !prop_object_is_string(ps2))
619 return (-666); /* arbitrary */
620
621 return (strcmp(prop_string_contents(ps1),
622 prop_string_contents(ps2)));
623 }
624
625 /*
626 * prop_string_compare_string --
627 * Compare a string object to the specified C string, using
628 * strcmp() semantics.
629 */
630 int
prop_string_compare_string(prop_string_t ps,const char * cp)631 prop_string_compare_string(prop_string_t ps, const char *cp)
632 {
633 if (!prop_object_is_string(ps))
634 return (-666); /* arbitrary */
635
636 return (strcmp(prop_string_contents(ps), cp));
637 }
638
639 /*
640 * _prop_string_internalize --
641 * Parse a <string>...</string> and return the object created from the
642 * external representation.
643 */
644 /* ARGSUSED */
645 bool
_prop_string_internalize(prop_stack_t stack,prop_object_t * obj,struct _prop_object_internalize_context * ctx)646 _prop_string_internalize(prop_stack_t stack, prop_object_t *obj,
647 struct _prop_object_internalize_context *ctx)
648 {
649 char *str;
650 size_t len, alen;
651
652 if (ctx->poic_is_empty_element) {
653 *obj = prop_string_create();
654 return (true);
655 }
656
657 /* No attributes recognized here. */
658 if (ctx->poic_tagattr != NULL)
659 return (true);
660
661 /* Compute the length of the result. */
662 if (_prop_object_internalize_decode_string(ctx, NULL, 0, &len,
663 NULL) == false)
664 return (true);
665
666 str = _PROP_MALLOC(len + 1, M_PROP_STRING);
667 if (str == NULL)
668 return (true);
669
670 if (_prop_object_internalize_decode_string(ctx, str, len, &alen,
671 &ctx->poic_cp) == false ||
672 alen != len) {
673 _PROP_FREE(str, M_PROP_STRING);
674 return (true);
675 }
676 str[len] = '\0';
677
678 if (_prop_object_internalize_find_tag(ctx, "string",
679 _PROP_TAG_TYPE_END) == false) {
680 _PROP_FREE(str, M_PROP_STRING);
681 return (true);
682 }
683
684 *obj = _prop_string_instantiate(0, str, len);
685 return (true);
686 }
687