prop_string.c revision 1.23 1 /* $NetBSD: prop_string.c,v 1.23 2025/05/13 15:00:16 thorpej Exp $ */
2
3 /*-
4 * Copyright (c) 2006, 2020, 2025 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 _PROP_MALLOC_DEFINE(M_PROP_STRING, "prop string",
60 "property string container object")
61
62 static const struct _prop_object_type_tags _prop_string_type_tags = {
63 .xml_tag = "string",
64 .json_open_tag = "\"",
65 .json_close_tag = "\"",
66 };
67
68 static _prop_object_free_rv_t
69 _prop_string_free(prop_stack_t, prop_object_t *);
70 static bool _prop_string_externalize(
71 struct _prop_object_externalize_context *,
72 void *);
73 static _prop_object_equals_rv_t
74 _prop_string_equals(prop_object_t, prop_object_t,
75 void **, void **,
76 prop_object_t *, prop_object_t *);
77
78 static const struct _prop_object_type _prop_object_type_string = {
79 .pot_type = PROP_TYPE_STRING,
80 .pot_free = _prop_string_free,
81 .pot_extern = _prop_string_externalize,
82 .pot_equals = _prop_string_equals,
83 };
84
85 #define prop_object_is_string(x) \
86 ((x) != NULL && (x)->ps_obj.po_type == &_prop_object_type_string)
87 #define prop_string_contents(x) ((x)->ps_immutable ? (x)->ps_immutable : "")
88
89 /*
90 * In order to reduce memory usage, all immutable string objects are
91 * de-duplicated.
92 */
93
94 static int
95 /*ARGSUSED*/
96 _prop_string_rb_compare_nodes(void *ctx _PROP_ARG_UNUSED,
97 const void *n1, const void *n2)
98 {
99 const struct _prop_string * const ps1 = n1;
100 const struct _prop_string * const ps2 = n2;
101
102 _PROP_ASSERT(ps1->ps_immutable != NULL);
103 _PROP_ASSERT(ps2->ps_immutable != NULL);
104
105 return strcmp(ps1->ps_immutable, ps2->ps_immutable);
106 }
107
108 static int
109 /*ARGSUSED*/
110 _prop_string_rb_compare_key(void *ctx _PROP_ARG_UNUSED,
111 const void *n, const void *v)
112 {
113 const struct _prop_string * const ps = n;
114 const char * const cp = v;
115
116 _PROP_ASSERT(ps->ps_immutable != NULL);
117
118 return strcmp(ps->ps_immutable, cp);
119 }
120
121 static const rb_tree_ops_t _prop_string_rb_tree_ops = {
122 .rbto_compare_nodes = _prop_string_rb_compare_nodes,
123 .rbto_compare_key = _prop_string_rb_compare_key,
124 .rbto_node_offset = offsetof(struct _prop_string, ps_link),
125 .rbto_context = NULL
126 };
127
128 static struct rb_tree _prop_string_tree;
129
130 _PROP_ONCE_DECL(_prop_string_init_once)
131 _PROP_MUTEX_DECL_STATIC(_prop_string_tree_mutex)
132
133 static int
134 _prop_string_init(void)
135 {
136
137 _PROP_MUTEX_INIT(_prop_string_tree_mutex);
138 rb_tree_init(&_prop_string_tree,
139 &_prop_string_rb_tree_ops);
140
141 return 0;
142 }
143
144 /* ARGSUSED */
145 static _prop_object_free_rv_t
146 _prop_string_free(prop_stack_t stack, prop_object_t *obj)
147 {
148 prop_string_t ps = *obj;
149
150 if ((ps->ps_flags & PS_F_MUTABLE) == 0) {
151 _PROP_MUTEX_LOCK(_prop_string_tree_mutex);
152 /*
153 * Double-check the retain count now that we've
154 * acquired the tree lock; holding this lock prevents
155 * new retains from coming in by finding it in the
156 * tree.
157 */
158 if (_PROP_ATOMIC_LOAD(&ps->ps_obj.po_refcnt) == 0)
159 rb_tree_remove_node(&_prop_string_tree, ps);
160 else
161 ps = NULL;
162 _PROP_MUTEX_UNLOCK(_prop_string_tree_mutex);
163
164 if (ps == NULL)
165 return (_PROP_OBJECT_FREE_DONE);
166 }
167
168 if ((ps->ps_flags & PS_F_NOCOPY) == 0 && ps->ps_mutable != NULL)
169 _PROP_FREE(ps->ps_mutable, M_PROP_STRING);
170 _PROP_POOL_PUT(_prop_string_pool, ps);
171
172 return (_PROP_OBJECT_FREE_DONE);
173 }
174
175 bool
176 _prop_string_externalize_internal(struct _prop_object_externalize_context *ctx,
177 const struct _prop_object_type_tags *tags,
178 const char *str)
179 {
180 if (_prop_object_externalize_start_tag(ctx, tags, NULL) == false ||
181 _prop_object_externalize_append_encoded_cstring(ctx,
182 str) == false ||
183 _prop_object_externalize_end_tag(ctx, tags) == false) {
184 return false;
185 }
186
187 return true;
188 }
189
190 static bool
191 _prop_string_externalize(struct _prop_object_externalize_context *ctx,
192 void *v)
193 {
194 prop_string_t ps = v;
195
196 if (ps->ps_size == 0) {
197 return _prop_object_externalize_empty_tag(ctx,
198 &_prop_string_type_tags);
199 }
200
201 return _prop_string_externalize_internal(ctx, &_prop_string_type_tags,
202 ps->ps_immutable);
203 }
204
205 /* ARGSUSED */
206 static _prop_object_equals_rv_t
207 _prop_string_equals(prop_object_t v1, prop_object_t v2,
208 void **stored_pointer1, void **stored_pointer2,
209 prop_object_t *next_obj1, prop_object_t *next_obj2)
210 {
211 prop_string_t str1 = v1;
212 prop_string_t str2 = v2;
213
214 if (str1 == str2)
215 return (_PROP_OBJECT_EQUALS_TRUE);
216 if (str1->ps_size != str2->ps_size)
217 return (_PROP_OBJECT_EQUALS_FALSE);
218 if (strcmp(prop_string_contents(str1), prop_string_contents(str2)))
219 return (_PROP_OBJECT_EQUALS_FALSE);
220 else
221 return (_PROP_OBJECT_EQUALS_TRUE);
222 }
223
224 static prop_string_t
225 _prop_string_alloc(int const flags)
226 {
227 prop_string_t ps;
228
229 ps = _PROP_POOL_GET(_prop_string_pool);
230 if (ps != NULL) {
231 _prop_object_init(&ps->ps_obj, &_prop_object_type_string);
232
233 ps->ps_mutable = NULL;
234 ps->ps_size = 0;
235 ps->ps_flags = flags;
236 }
237
238 return (ps);
239 }
240
241 static prop_string_t
242 _prop_string_instantiate(int const flags, const char * const str,
243 size_t const len)
244 {
245 prop_string_t ps;
246
247 _PROP_ONCE_RUN(_prop_string_init_once, _prop_string_init);
248
249 ps = _prop_string_alloc(flags);
250 if (ps != NULL) {
251 ps->ps_immutable = str;
252 ps->ps_size = len;
253
254 if ((flags & PS_F_MUTABLE) == 0) {
255 prop_string_t ops;
256
257 _PROP_MUTEX_LOCK(_prop_string_tree_mutex);
258 ops = rb_tree_insert_node(&_prop_string_tree, ps);
259 if (ops != ps) {
260 /*
261 * Equivalent string object already exist;
262 * free the new one and return a reference
263 * to the existing object.
264 */
265 prop_object_retain(ops);
266 _PROP_MUTEX_UNLOCK(_prop_string_tree_mutex);
267 if ((flags & PS_F_NOCOPY) == 0) {
268 _PROP_FREE(ps->ps_mutable,
269 M_PROP_STRING);
270 }
271 _PROP_POOL_PUT(_prop_string_pool, ps);
272 ps = ops;
273 } else {
274 _PROP_MUTEX_UNLOCK(_prop_string_tree_mutex);
275 }
276 }
277 } else if ((flags & PS_F_NOCOPY) == 0) {
278 _PROP_FREE(_PROP_UNCONST(str), M_PROP_STRING);
279 }
280
281 return (ps);
282 }
283
284 _PROP_DEPRECATED(prop_string_create,
285 "this program uses prop_string_create(); all functions "
286 "supporting mutable prop_strings are deprecated.")
287 _PROP_EXPORT prop_string_t
288 prop_string_create(void)
289 {
290
291 return (_prop_string_alloc(PS_F_MUTABLE));
292 }
293
294 _PROP_DEPRECATED(prop_string_create_cstring,
295 "this program uses prop_string_create_cstring(); all functions "
296 "supporting mutable prop_strings are deprecated.")
297 _PROP_EXPORT prop_string_t
298 prop_string_create_cstring(const char *str)
299 {
300 prop_string_t ps;
301 char *cp;
302 size_t len;
303
304 _PROP_ASSERT(str != NULL);
305
306 ps = _prop_string_alloc(PS_F_MUTABLE);
307 if (ps != NULL) {
308 len = strlen(str);
309 cp = _PROP_MALLOC(len + 1, M_PROP_STRING);
310 if (cp == NULL) {
311 prop_object_release(ps);
312 return (NULL);
313 }
314 strcpy(cp, str);
315 ps->ps_mutable = cp;
316 ps->ps_size = len;
317 }
318 return (ps);
319 }
320
321 _PROP_DEPRECATED(prop_string_create_cstring_nocopy,
322 "this program uses prop_string_create_cstring_nocopy(), "
323 "which is deprecated; use prop_string_create_nocopy() instead.")
324 _PROP_EXPORT prop_string_t
325 prop_string_create_cstring_nocopy(const char *str)
326 {
327 return prop_string_create_nocopy(str);
328 }
329
330 /*
331 * prop_string_create_format --
332 * Create a string object using the provided format string.
333 */
334 _PROP_EXPORT prop_string_t __printflike(1, 2)
335 prop_string_create_format(const char *fmt, ...)
336 {
337 char *str = NULL;
338 int len;
339 size_t nlen;
340 va_list ap;
341
342 _PROP_ASSERT(fmt != NULL);
343
344 va_start(ap, fmt);
345 len = vsnprintf(NULL, 0, fmt, ap);
346 va_end(ap);
347
348 if (len < 0)
349 return (NULL);
350 nlen = len + 1;
351
352 str = _PROP_MALLOC(nlen, M_PROP_STRING);
353 if (str == NULL)
354 return (NULL);
355
356 va_start(ap, fmt);
357 vsnprintf(str, nlen, fmt, ap);
358 va_end(ap);
359
360 return _prop_string_instantiate(0, str, (size_t)len);
361 }
362
363 /*
364 * prop_string_create_copy --
365 * Create a string object by coping the provided constant string.
366 */
367 _PROP_EXPORT prop_string_t
368 prop_string_create_copy(const char *str)
369 {
370 return prop_string_create_format("%s", str);
371 }
372
373 /*
374 * prop_string_create_nocopy --
375 * Create a string object using the provided external constant
376 * string.
377 */
378 _PROP_EXPORT prop_string_t
379 prop_string_create_nocopy(const char *str)
380 {
381
382 _PROP_ASSERT(str != NULL);
383
384 return _prop_string_instantiate(PS_F_NOCOPY, str, strlen(str));
385 }
386
387 /*
388 * prop_string_copy --
389 * Copy a string. This reduces to a retain in the common case.
390 * Deprecated mutable string objects must be copied.
391 */
392 _PROP_EXPORT prop_string_t
393 prop_string_copy(prop_string_t ops)
394 {
395 char *cp;
396
397 if (! prop_object_is_string(ops))
398 return (NULL);
399
400 if ((ops->ps_flags & PS_F_MUTABLE) == 0) {
401 prop_object_retain(ops);
402 return (ops);
403 }
404
405 cp = _PROP_MALLOC(ops->ps_size + 1, M_PROP_STRING);
406 if (cp == NULL)
407 return NULL;
408
409 strcpy(cp, prop_string_contents(ops));
410
411 return _prop_string_instantiate(PS_F_MUTABLE, cp, ops->ps_size);
412 }
413
414 _PROP_DEPRECATED(prop_string_copy_mutable,
415 "this program uses prop_string_copy_mutable(); all functions "
416 "supporting mutable prop_strings are deprecated.")
417 _PROP_EXPORT prop_string_t
418 prop_string_copy_mutable(prop_string_t ops)
419 {
420 char *cp;
421
422 if (! prop_object_is_string(ops))
423 return (NULL);
424
425 cp = _PROP_MALLOC(ops->ps_size + 1, M_PROP_STRING);
426 if (cp == NULL)
427 return NULL;
428
429 strcpy(cp, prop_string_contents(ops));
430
431 return _prop_string_instantiate(PS_F_MUTABLE, cp, ops->ps_size);
432 }
433
434 /*
435 * prop_string_size --
436 * Return the size of the string, not including the terminating NUL.
437 */
438 _PROP_EXPORT size_t
439 prop_string_size(prop_string_t ps)
440 {
441
442 if (! prop_object_is_string(ps))
443 return (0);
444
445 return (ps->ps_size);
446 }
447
448 /*
449 * prop_string_value --
450 * Returns a pointer to the string object's value. This pointer
451 * remains valid only as long as the string object.
452 */
453 _PROP_EXPORT const char *
454 prop_string_value(prop_string_t ps)
455 {
456
457 if (! prop_object_is_string(ps))
458 return (NULL);
459
460 if ((ps->ps_flags & PS_F_MUTABLE) == 0)
461 return (ps->ps_immutable);
462
463 return (prop_string_contents(ps));
464 }
465
466 /*
467 * prop_string_copy_value --
468 * Copy the string object's value into the supplied buffer.
469 */
470 _PROP_EXPORT bool
471 prop_string_copy_value(prop_string_t ps, void *buf, size_t buflen)
472 {
473
474 if (! prop_object_is_string(ps))
475 return (false);
476
477 if (buf == NULL || buflen < ps->ps_size + 1)
478 return (false);
479
480 strcpy(buf, prop_string_contents(ps));
481
482 return (true);
483 }
484
485 _PROP_DEPRECATED(prop_string_mutable,
486 "this program uses prop_string_mutable(); all functions "
487 "supporting mutable prop_strings are deprecated.")
488 _PROP_EXPORT bool
489 prop_string_mutable(prop_string_t ps)
490 {
491
492 if (! prop_object_is_string(ps))
493 return (false);
494
495 return ((ps->ps_flags & PS_F_MUTABLE) != 0);
496 }
497
498 _PROP_DEPRECATED(prop_string_cstring,
499 "this program uses prop_string_cstring(), "
500 "which is deprecated; use prop_string_copy_value() instead.")
501 _PROP_EXPORT char *
502 prop_string_cstring(prop_string_t ps)
503 {
504 char *cp;
505
506 if (! prop_object_is_string(ps))
507 return (NULL);
508
509 cp = _PROP_MALLOC(ps->ps_size + 1, M_TEMP);
510 if (cp != NULL)
511 strcpy(cp, prop_string_contents(ps));
512
513 return (cp);
514 }
515
516 _PROP_DEPRECATED(prop_string_cstring_nocopy,
517 "this program uses prop_string_cstring_nocopy(), "
518 "which is deprecated; use prop_string_value() instead.")
519 _PROP_EXPORT const char *
520 prop_string_cstring_nocopy(prop_string_t ps)
521 {
522
523 if (! prop_object_is_string(ps))
524 return (NULL);
525
526 return (prop_string_contents(ps));
527 }
528
529 _PROP_DEPRECATED(prop_string_append,
530 "this program uses prop_string_append(); all functions "
531 "supporting mutable prop_strings are deprecated.")
532 _PROP_EXPORT bool
533 prop_string_append(prop_string_t dst, prop_string_t src)
534 {
535 char *ocp, *cp;
536 size_t len;
537
538 if (! (prop_object_is_string(dst) &&
539 prop_object_is_string(src)))
540 return (false);
541
542 if ((dst->ps_flags & PS_F_MUTABLE) == 0)
543 return (false);
544
545 len = dst->ps_size + src->ps_size;
546 cp = _PROP_MALLOC(len + 1, M_PROP_STRING);
547 if (cp == NULL)
548 return (false);
549 snprintf(cp, len + 1, "%s%s", prop_string_contents(dst),
550 prop_string_contents(src));
551 ocp = dst->ps_mutable;
552 dst->ps_mutable = cp;
553 dst->ps_size = len;
554 if (ocp != NULL)
555 _PROP_FREE(ocp, M_PROP_STRING);
556
557 return (true);
558 }
559
560 _PROP_DEPRECATED(prop_string_append_cstring,
561 "this program uses prop_string_append_cstring(); all functions "
562 "supporting mutable prop_strings are deprecated.")
563 _PROP_EXPORT bool
564 prop_string_append_cstring(prop_string_t dst, const char *src)
565 {
566 char *ocp, *cp;
567 size_t len;
568
569 if (! prop_object_is_string(dst))
570 return (false);
571
572 _PROP_ASSERT(src != NULL);
573
574 if ((dst->ps_flags & PS_F_MUTABLE) == 0)
575 return (false);
576
577 len = dst->ps_size + strlen(src);
578 cp = _PROP_MALLOC(len + 1, M_PROP_STRING);
579 if (cp == NULL)
580 return (false);
581 snprintf(cp, len + 1, "%s%s", prop_string_contents(dst), src);
582 ocp = dst->ps_mutable;
583 dst->ps_mutable = cp;
584 dst->ps_size = len;
585 if (ocp != NULL)
586 _PROP_FREE(ocp, M_PROP_STRING);
587
588 return (true);
589 }
590
591 /*
592 * prop_string_equals --
593 * Return true if two strings are equivalent.
594 */
595 _PROP_EXPORT bool
596 prop_string_equals(prop_string_t str1, prop_string_t str2)
597 {
598 if (!prop_object_is_string(str1) || !prop_object_is_string(str2))
599 return (false);
600
601 return prop_object_equals(str1, str2);
602 }
603
604 /*
605 * prop_string_equals_string --
606 * Return true if the string object is equivalent to the specified
607 * C string.
608 */
609 _PROP_EXPORT bool
610 prop_string_equals_string(prop_string_t ps, const char *cp)
611 {
612
613 if (! prop_object_is_string(ps))
614 return (false);
615
616 return (strcmp(prop_string_contents(ps), cp) == 0);
617 }
618
619 _PROP_DEPRECATED(prop_string_equals_cstring,
620 "this program uses prop_string_equals_cstring(), "
621 "which is deprecated; prop_string_equals_string() instead.")
622 _PROP_EXPORT bool
623 prop_string_equals_cstring(prop_string_t ps, const char *cp)
624 {
625 return prop_string_equals_string(ps, cp);
626 }
627
628 /*
629 * prop_string_compare --
630 * Compare two string objects, using strcmp() semantics.
631 */
632 _PROP_EXPORT int
633 prop_string_compare(prop_string_t ps1, prop_string_t ps2)
634 {
635 if (!prop_object_is_string(ps1) || !prop_object_is_string(ps2))
636 return (-666); /* arbitrary */
637
638 return (strcmp(prop_string_contents(ps1),
639 prop_string_contents(ps2)));
640 }
641
642 /*
643 * prop_string_compare_string --
644 * Compare a string object to the specified C string, using
645 * strcmp() semantics.
646 */
647 _PROP_EXPORT int
648 prop_string_compare_string(prop_string_t ps, const char *cp)
649 {
650 if (!prop_object_is_string(ps))
651 return (-666); /* arbitrary */
652
653 return (strcmp(prop_string_contents(ps), cp));
654 }
655
656 /*
657 * _prop_string_internalize --
658 * Parse a <string>...</string> and return the object created from the
659 * external representation.
660 */
661 /* ARGSUSED */
662 bool
663 _prop_string_internalize(prop_stack_t stack, prop_object_t *obj,
664 struct _prop_object_internalize_context *ctx)
665 {
666 char *str;
667 size_t len, alen;
668
669 /*
670 * N.B. for empty JSON strings, the layer above us has made it
671 * look like XML.
672 */
673 if (ctx->poic_is_empty_element) {
674 *obj = prop_string_create();
675 return (true);
676 }
677
678 /* No attributes recognized here. */
679 if (ctx->poic_tagattr != NULL)
680 return (true);
681
682 /* Compute the length of the result. */
683 if (_prop_object_internalize_decode_string(ctx, NULL, 0, &len,
684 NULL) == false)
685 return (true);
686
687 str = _PROP_MALLOC(len + 1, M_PROP_STRING);
688 if (str == NULL)
689 return (true);
690
691 if (_prop_object_internalize_decode_string(ctx, str, len, &alen,
692 &ctx->poic_cp) == false ||
693 alen != len) {
694 _PROP_FREE(str, M_PROP_STRING);
695 return (true);
696 }
697 str[len] = '\0';
698
699 if (ctx->poic_format == PROP_FORMAT_JSON) {
700 if (*ctx->poic_cp != '"') {
701 _PROP_FREE(str, M_PROP_STRING);
702 return (true);
703 }
704 ctx->poic_cp++;
705 } else {
706 if (_prop_object_internalize_find_tag(ctx, "string",
707 _PROP_TAG_TYPE_END) == false) {
708 _PROP_FREE(str, M_PROP_STRING);
709 return (true);
710 }
711 }
712
713 *obj = _prop_string_instantiate(0, str, len);
714 return (true);
715 }
716