1 /* $NetBSD: prop_string.c,v 1.17 2022/08/03 21:13:46 riastradh 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 _PROP_POOL_PUT(_prop_string_pool, ps);
251 ps = ops;
252 } else {
253 _PROP_MUTEX_UNLOCK(_prop_string_tree_mutex);
254 }
255 }
256 }
257
258 return (ps);
259 }
260
261 _PROP_DEPRECATED(prop_string_create,
262 "this program uses prop_string_create(); all functions "
263 "supporting mutable prop_strings are deprecated.")
264 prop_string_t
prop_string_create(void)265 prop_string_create(void)
266 {
267
268 return (_prop_string_alloc(PS_F_MUTABLE));
269 }
270
271 _PROP_DEPRECATED(prop_string_create_cstring,
272 "this program uses prop_string_create_cstring(); all functions "
273 "supporting mutable prop_strings are deprecated.")
274 prop_string_t
prop_string_create_cstring(const char * str)275 prop_string_create_cstring(const char *str)
276 {
277 prop_string_t ps;
278 char *cp;
279 size_t len;
280
281 _PROP_ASSERT(str != NULL);
282
283 ps = _prop_string_alloc(PS_F_MUTABLE);
284 if (ps != NULL) {
285 len = strlen(str);
286 cp = _PROP_MALLOC(len + 1, M_PROP_STRING);
287 if (cp == NULL) {
288 prop_object_release(ps);
289 return (NULL);
290 }
291 strcpy(cp, str);
292 ps->ps_mutable = cp;
293 ps->ps_size = len;
294 }
295 return (ps);
296 }
297
298 _PROP_DEPRECATED(prop_string_create_cstring_nocopy,
299 "this program uses prop_string_create_cstring_nocopy(), "
300 "which is deprecated; use prop_string_create_nocopy() instead.")
301 prop_string_t
prop_string_create_cstring_nocopy(const char * str)302 prop_string_create_cstring_nocopy(const char *str)
303 {
304 return prop_string_create_nocopy(str);
305 }
306
307 /*
308 * prop_string_create_format --
309 * Create a string object using the provided format string.
310 */
311 prop_string_t __printflike(1, 2)
prop_string_create_format(const char * fmt,...)312 prop_string_create_format(const char *fmt, ...)
313 {
314 prop_string_t ps;
315 char *str = NULL;
316 int len;
317 size_t nlen;
318 va_list ap;
319
320 _PROP_ASSERT(fmt != NULL);
321
322 va_start(ap, fmt);
323 len = vsnprintf(NULL, 0, fmt, ap);
324 va_end(ap);
325
326 if (len < 0)
327 return (NULL);
328 nlen = len + 1;
329
330 str = _PROP_MALLOC(nlen, M_PROP_STRING);
331 if (str == NULL)
332 return (NULL);
333
334 va_start(ap, fmt);
335 vsnprintf(str, nlen, fmt, ap);
336 va_end(ap);
337
338 ps = _prop_string_instantiate(0, str, (size_t)len);
339 if (ps == NULL)
340 _PROP_FREE(str, M_PROP_STRING);
341
342 return (ps);
343 }
344
345 /*
346 * prop_string_create_copy --
347 * Create a string object by coping the provided constant string.
348 */
349 prop_string_t
prop_string_create_copy(const char * str)350 prop_string_create_copy(const char *str)
351 {
352 return prop_string_create_format("%s", str);
353 }
354
355 /*
356 * prop_string_create_nocopy --
357 * Create a string object using the provided external constant
358 * string.
359 */
360 prop_string_t
prop_string_create_nocopy(const char * str)361 prop_string_create_nocopy(const char *str)
362 {
363
364 _PROP_ASSERT(str != NULL);
365
366 return _prop_string_instantiate(PS_F_NOCOPY, str, strlen(str));
367 }
368
369 /*
370 * prop_string_copy --
371 * Copy a string. This reduces to a retain in the common case.
372 * Deprecated mutable string objects must be copied.
373 */
374 prop_string_t
prop_string_copy(prop_string_t ops)375 prop_string_copy(prop_string_t ops)
376 {
377 prop_string_t ps;
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 ps = _prop_string_instantiate(PS_F_MUTABLE, cp, ops->ps_size);
395 if (ps == NULL)
396 _PROP_FREE(cp, M_PROP_STRING);
397
398 return (ps);
399 }
400
401 _PROP_DEPRECATED(prop_string_copy_mutable,
402 "this program uses prop_string_copy_mutable(); all functions "
403 "supporting mutable prop_strings are deprecated.")
404 prop_string_t
prop_string_copy_mutable(prop_string_t ops)405 prop_string_copy_mutable(prop_string_t ops)
406 {
407 prop_string_t ps;
408 char *cp;
409
410 if (! prop_object_is_string(ops))
411 return (NULL);
412
413 cp = _PROP_MALLOC(ops->ps_size + 1, M_PROP_STRING);
414 if (cp == NULL)
415 return NULL;
416
417 strcpy(cp, prop_string_contents(ops));
418
419 ps = _prop_string_instantiate(PS_F_MUTABLE, cp, ops->ps_size);
420 if (ps == NULL)
421 _PROP_FREE(cp, M_PROP_STRING);
422
423 return (ps);
424 }
425
426 /*
427 * prop_string_size --
428 * Return the size of the string, not including the terminating NUL.
429 */
430 size_t
prop_string_size(prop_string_t ps)431 prop_string_size(prop_string_t ps)
432 {
433
434 if (! prop_object_is_string(ps))
435 return (0);
436
437 return (ps->ps_size);
438 }
439
440 /*
441 * prop_string_value --
442 * Returns a pointer to the string object's value. This pointer
443 * remains valid only as long as the string object.
444 */
445 const char *
prop_string_value(prop_string_t ps)446 prop_string_value(prop_string_t ps)
447 {
448
449 if (! prop_object_is_string(ps))
450 return (NULL);
451
452 if ((ps->ps_flags & PS_F_MUTABLE) == 0)
453 return (ps->ps_immutable);
454
455 return (prop_string_contents(ps));
456 }
457
458 /*
459 * prop_string_copy_value --
460 * Copy the string object's value into the supplied buffer.
461 */
462 bool
prop_string_copy_value(prop_string_t ps,void * buf,size_t buflen)463 prop_string_copy_value(prop_string_t ps, void *buf, size_t buflen)
464 {
465
466 if (! prop_object_is_string(ps))
467 return (false);
468
469 if (buf == NULL || buflen < ps->ps_size + 1)
470 return (false);
471
472 strcpy(buf, prop_string_contents(ps));
473
474 return (true);
475 }
476
477 _PROP_DEPRECATED(prop_string_mutable,
478 "this program uses prop_string_mutable(); all functions "
479 "supporting mutable prop_strings are deprecated.")
480 bool
prop_string_mutable(prop_string_t ps)481 prop_string_mutable(prop_string_t ps)
482 {
483
484 if (! prop_object_is_string(ps))
485 return (false);
486
487 return ((ps->ps_flags & PS_F_MUTABLE) != 0);
488 }
489
490 _PROP_DEPRECATED(prop_string_cstring,
491 "this program uses prop_string_cstring(), "
492 "which is deprecated; use prop_string_copy_value() instead.")
493 char *
prop_string_cstring(prop_string_t ps)494 prop_string_cstring(prop_string_t ps)
495 {
496 char *cp;
497
498 if (! prop_object_is_string(ps))
499 return (NULL);
500
501 cp = _PROP_MALLOC(ps->ps_size + 1, M_TEMP);
502 if (cp != NULL)
503 strcpy(cp, prop_string_contents(ps));
504
505 return (cp);
506 }
507
508 _PROP_DEPRECATED(prop_string_cstring_nocopy,
509 "this program uses prop_string_cstring_nocopy(), "
510 "which is deprecated; use prop_string_value() instead.")
511 const char *
prop_string_cstring_nocopy(prop_string_t ps)512 prop_string_cstring_nocopy(prop_string_t ps)
513 {
514
515 if (! prop_object_is_string(ps))
516 return (NULL);
517
518 return (prop_string_contents(ps));
519 }
520
521 _PROP_DEPRECATED(prop_string_append,
522 "this program uses prop_string_append(); all functions "
523 "supporting mutable prop_strings are deprecated.")
524 bool
prop_string_append(prop_string_t dst,prop_string_t src)525 prop_string_append(prop_string_t dst, prop_string_t src)
526 {
527 char *ocp, *cp;
528 size_t len;
529
530 if (! (prop_object_is_string(dst) &&
531 prop_object_is_string(src)))
532 return (false);
533
534 if ((dst->ps_flags & PS_F_MUTABLE) == 0)
535 return (false);
536
537 len = dst->ps_size + src->ps_size;
538 cp = _PROP_MALLOC(len + 1, M_PROP_STRING);
539 if (cp == NULL)
540 return (false);
541 snprintf(cp, len + 1, "%s%s", prop_string_contents(dst),
542 prop_string_contents(src));
543 ocp = dst->ps_mutable;
544 dst->ps_mutable = cp;
545 dst->ps_size = len;
546 if (ocp != NULL)
547 _PROP_FREE(ocp, M_PROP_STRING);
548
549 return (true);
550 }
551
552 _PROP_DEPRECATED(prop_string_append_cstring,
553 "this program uses prop_string_append_cstring(); all functions "
554 "supporting mutable prop_strings are deprecated.")
555 bool
prop_string_append_cstring(prop_string_t dst,const char * src)556 prop_string_append_cstring(prop_string_t dst, const char *src)
557 {
558 char *ocp, *cp;
559 size_t len;
560
561 if (! prop_object_is_string(dst))
562 return (false);
563
564 _PROP_ASSERT(src != NULL);
565
566 if ((dst->ps_flags & PS_F_MUTABLE) == 0)
567 return (false);
568
569 len = dst->ps_size + strlen(src);
570 cp = _PROP_MALLOC(len + 1, M_PROP_STRING);
571 if (cp == NULL)
572 return (false);
573 snprintf(cp, len + 1, "%s%s", prop_string_contents(dst), src);
574 ocp = dst->ps_mutable;
575 dst->ps_mutable = cp;
576 dst->ps_size = len;
577 if (ocp != NULL)
578 _PROP_FREE(ocp, M_PROP_STRING);
579
580 return (true);
581 }
582
583 /*
584 * prop_string_equals --
585 * Return true if two strings are equivalent.
586 */
587 bool
prop_string_equals(prop_string_t str1,prop_string_t str2)588 prop_string_equals(prop_string_t str1, prop_string_t str2)
589 {
590 if (!prop_object_is_string(str1) || !prop_object_is_string(str2))
591 return (false);
592
593 return prop_object_equals(str1, str2);
594 }
595
596 /*
597 * prop_string_equals_string --
598 * Return true if the string object is equivalent to the specified
599 * C string.
600 */
601 bool
prop_string_equals_string(prop_string_t ps,const char * cp)602 prop_string_equals_string(prop_string_t ps, const char *cp)
603 {
604
605 if (! prop_object_is_string(ps))
606 return (false);
607
608 return (strcmp(prop_string_contents(ps), cp) == 0);
609 }
610
611 _PROP_DEPRECATED(prop_string_equals_cstring,
612 "this program uses prop_string_equals_cstring(), "
613 "which is deprecated; prop_string_equals_string() instead.")
614 bool
prop_string_equals_cstring(prop_string_t ps,const char * cp)615 prop_string_equals_cstring(prop_string_t ps, const char *cp)
616 {
617 return prop_string_equals_string(ps, cp);
618 }
619
620 /*
621 * prop_string_compare --
622 * Compare two string objects, using strcmp() semantics.
623 */
624 int
prop_string_compare(prop_string_t ps1,prop_string_t ps2)625 prop_string_compare(prop_string_t ps1, prop_string_t ps2)
626 {
627 if (!prop_object_is_string(ps1) || !prop_object_is_string(ps2))
628 return (-666); /* arbitrary */
629
630 return (strcmp(prop_string_contents(ps1),
631 prop_string_contents(ps2)));
632 }
633
634 /*
635 * prop_string_compare_string --
636 * Compare a string object to the specified C string, using
637 * strcmp() semantics.
638 */
639 int
prop_string_compare_string(prop_string_t ps,const char * cp)640 prop_string_compare_string(prop_string_t ps, const char *cp)
641 {
642 if (!prop_object_is_string(ps))
643 return (-666); /* arbitrary */
644
645 return (strcmp(prop_string_contents(ps), cp));
646 }
647
648 /*
649 * _prop_string_internalize --
650 * Parse a <string>...</string> and return the object created from the
651 * external representation.
652 */
653 /* ARGSUSED */
654 bool
_prop_string_internalize(prop_stack_t stack,prop_object_t * obj,struct _prop_object_internalize_context * ctx)655 _prop_string_internalize(prop_stack_t stack, prop_object_t *obj,
656 struct _prop_object_internalize_context *ctx)
657 {
658 prop_string_t string;
659 char *str;
660 size_t len, alen;
661
662 if (ctx->poic_is_empty_element) {
663 *obj = prop_string_create();
664 return (true);
665 }
666
667 /* No attributes recognized here. */
668 if (ctx->poic_tagattr != NULL)
669 return (true);
670
671 /* Compute the length of the result. */
672 if (_prop_object_internalize_decode_string(ctx, NULL, 0, &len,
673 NULL) == false)
674 return (true);
675
676 str = _PROP_MALLOC(len + 1, M_PROP_STRING);
677 if (str == NULL)
678 return (true);
679
680 if (_prop_object_internalize_decode_string(ctx, str, len, &alen,
681 &ctx->poic_cp) == false ||
682 alen != len) {
683 _PROP_FREE(str, M_PROP_STRING);
684 return (true);
685 }
686 str[len] = '\0';
687
688 if (_prop_object_internalize_find_tag(ctx, "string",
689 _PROP_TAG_TYPE_END) == false) {
690 _PROP_FREE(str, M_PROP_STRING);
691 return (true);
692 }
693
694 string = _prop_string_instantiate(0, str, len);
695 if (string == NULL)
696 _PROP_FREE(str, M_PROP_STRING);
697
698 *obj = string;
699 return (true);
700 }
701