1 /* $NetBSD: prop_string.c,v 1.24 2025/05/14 03:25:46 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_extern_append_start_tag(ctx, tags, NULL) == false || 181 _prop_extern_append_encoded_cstring(ctx, str) == false || 182 _prop_extern_append_end_tag(ctx, tags) == false) { 183 return false; 184 } 185 186 return true; 187 } 188 189 static bool 190 _prop_string_externalize(struct _prop_object_externalize_context *ctx, 191 void *v) 192 { 193 prop_string_t ps = v; 194 195 if (ps->ps_size == 0) { 196 return _prop_extern_append_empty_tag(ctx, 197 &_prop_string_type_tags); 198 } 199 200 return _prop_string_externalize_internal(ctx, &_prop_string_type_tags, 201 ps->ps_immutable); 202 } 203 204 /* ARGSUSED */ 205 static _prop_object_equals_rv_t 206 _prop_string_equals(prop_object_t v1, prop_object_t v2, 207 void **stored_pointer1, void **stored_pointer2, 208 prop_object_t *next_obj1, prop_object_t *next_obj2) 209 { 210 prop_string_t str1 = v1; 211 prop_string_t str2 = v2; 212 213 if (str1 == str2) 214 return (_PROP_OBJECT_EQUALS_TRUE); 215 if (str1->ps_size != str2->ps_size) 216 return (_PROP_OBJECT_EQUALS_FALSE); 217 if (strcmp(prop_string_contents(str1), prop_string_contents(str2))) 218 return (_PROP_OBJECT_EQUALS_FALSE); 219 else 220 return (_PROP_OBJECT_EQUALS_TRUE); 221 } 222 223 static prop_string_t 224 _prop_string_alloc(int const flags) 225 { 226 prop_string_t ps; 227 228 ps = _PROP_POOL_GET(_prop_string_pool); 229 if (ps != NULL) { 230 _prop_object_init(&ps->ps_obj, &_prop_object_type_string); 231 232 ps->ps_mutable = NULL; 233 ps->ps_size = 0; 234 ps->ps_flags = flags; 235 } 236 237 return (ps); 238 } 239 240 static prop_string_t 241 _prop_string_instantiate(int const flags, const char * const str, 242 size_t const len) 243 { 244 prop_string_t ps; 245 246 _PROP_ONCE_RUN(_prop_string_init_once, _prop_string_init); 247 248 ps = _prop_string_alloc(flags); 249 if (ps != NULL) { 250 ps->ps_immutable = str; 251 ps->ps_size = len; 252 253 if ((flags & PS_F_MUTABLE) == 0) { 254 prop_string_t ops; 255 256 _PROP_MUTEX_LOCK(_prop_string_tree_mutex); 257 ops = rb_tree_insert_node(&_prop_string_tree, ps); 258 if (ops != ps) { 259 /* 260 * Equivalent string object already exist; 261 * free the new one and return a reference 262 * to the existing object. 263 */ 264 prop_object_retain(ops); 265 _PROP_MUTEX_UNLOCK(_prop_string_tree_mutex); 266 if ((flags & PS_F_NOCOPY) == 0) { 267 _PROP_FREE(ps->ps_mutable, 268 M_PROP_STRING); 269 } 270 _PROP_POOL_PUT(_prop_string_pool, ps); 271 ps = ops; 272 } else { 273 _PROP_MUTEX_UNLOCK(_prop_string_tree_mutex); 274 } 275 } 276 } else if ((flags & PS_F_NOCOPY) == 0) { 277 _PROP_FREE(_PROP_UNCONST(str), M_PROP_STRING); 278 } 279 280 return (ps); 281 } 282 283 _PROP_DEPRECATED(prop_string_create, 284 "this program uses prop_string_create(); all functions " 285 "supporting mutable prop_strings are deprecated.") 286 _PROP_EXPORT prop_string_t 287 prop_string_create(void) 288 { 289 290 return (_prop_string_alloc(PS_F_MUTABLE)); 291 } 292 293 _PROP_DEPRECATED(prop_string_create_cstring, 294 "this program uses prop_string_create_cstring(); all functions " 295 "supporting mutable prop_strings are deprecated.") 296 _PROP_EXPORT prop_string_t 297 prop_string_create_cstring(const char *str) 298 { 299 prop_string_t ps; 300 char *cp; 301 size_t len; 302 303 _PROP_ASSERT(str != NULL); 304 305 ps = _prop_string_alloc(PS_F_MUTABLE); 306 if (ps != NULL) { 307 len = strlen(str); 308 cp = _PROP_MALLOC(len + 1, M_PROP_STRING); 309 if (cp == NULL) { 310 prop_object_release(ps); 311 return (NULL); 312 } 313 strcpy(cp, str); 314 ps->ps_mutable = cp; 315 ps->ps_size = len; 316 } 317 return (ps); 318 } 319 320 _PROP_DEPRECATED(prop_string_create_cstring_nocopy, 321 "this program uses prop_string_create_cstring_nocopy(), " 322 "which is deprecated; use prop_string_create_nocopy() instead.") 323 _PROP_EXPORT prop_string_t 324 prop_string_create_cstring_nocopy(const char *str) 325 { 326 return prop_string_create_nocopy(str); 327 } 328 329 /* 330 * prop_string_create_format -- 331 * Create a string object using the provided format string. 332 */ 333 _PROP_EXPORT prop_string_t __printflike(1, 2) 334 prop_string_create_format(const char *fmt, ...) 335 { 336 char *str = NULL; 337 int len; 338 size_t nlen; 339 va_list ap; 340 341 _PROP_ASSERT(fmt != NULL); 342 343 va_start(ap, fmt); 344 len = vsnprintf(NULL, 0, fmt, ap); 345 va_end(ap); 346 347 if (len < 0) 348 return (NULL); 349 nlen = len + 1; 350 351 str = _PROP_MALLOC(nlen, M_PROP_STRING); 352 if (str == NULL) 353 return (NULL); 354 355 va_start(ap, fmt); 356 vsnprintf(str, nlen, fmt, ap); 357 va_end(ap); 358 359 return _prop_string_instantiate(0, str, (size_t)len); 360 } 361 362 /* 363 * prop_string_create_copy -- 364 * Create a string object by coping the provided constant string. 365 */ 366 _PROP_EXPORT prop_string_t 367 prop_string_create_copy(const char *str) 368 { 369 return prop_string_create_format("%s", str); 370 } 371 372 /* 373 * prop_string_create_nocopy -- 374 * Create a string object using the provided external constant 375 * string. 376 */ 377 _PROP_EXPORT prop_string_t 378 prop_string_create_nocopy(const char *str) 379 { 380 381 _PROP_ASSERT(str != NULL); 382 383 return _prop_string_instantiate(PS_F_NOCOPY, str, strlen(str)); 384 } 385 386 /* 387 * prop_string_copy -- 388 * Copy a string. This reduces to a retain in the common case. 389 * Deprecated mutable string objects must be copied. 390 */ 391 _PROP_EXPORT prop_string_t 392 prop_string_copy(prop_string_t ops) 393 { 394 char *cp; 395 396 if (! prop_object_is_string(ops)) 397 return (NULL); 398 399 if ((ops->ps_flags & PS_F_MUTABLE) == 0) { 400 prop_object_retain(ops); 401 return (ops); 402 } 403 404 cp = _PROP_MALLOC(ops->ps_size + 1, M_PROP_STRING); 405 if (cp == NULL) 406 return NULL; 407 408 strcpy(cp, prop_string_contents(ops)); 409 410 return _prop_string_instantiate(PS_F_MUTABLE, cp, ops->ps_size); 411 } 412 413 _PROP_DEPRECATED(prop_string_copy_mutable, 414 "this program uses prop_string_copy_mutable(); all functions " 415 "supporting mutable prop_strings are deprecated.") 416 _PROP_EXPORT prop_string_t 417 prop_string_copy_mutable(prop_string_t ops) 418 { 419 char *cp; 420 421 if (! prop_object_is_string(ops)) 422 return (NULL); 423 424 cp = _PROP_MALLOC(ops->ps_size + 1, M_PROP_STRING); 425 if (cp == NULL) 426 return NULL; 427 428 strcpy(cp, prop_string_contents(ops)); 429 430 return _prop_string_instantiate(PS_F_MUTABLE, cp, ops->ps_size); 431 } 432 433 /* 434 * prop_string_size -- 435 * Return the size of the string, not including the terminating NUL. 436 */ 437 _PROP_EXPORT size_t 438 prop_string_size(prop_string_t ps) 439 { 440 441 if (! prop_object_is_string(ps)) 442 return (0); 443 444 return (ps->ps_size); 445 } 446 447 /* 448 * prop_string_value -- 449 * Returns a pointer to the string object's value. This pointer 450 * remains valid only as long as the string object. 451 */ 452 _PROP_EXPORT const char * 453 prop_string_value(prop_string_t ps) 454 { 455 456 if (! prop_object_is_string(ps)) 457 return (NULL); 458 459 if ((ps->ps_flags & PS_F_MUTABLE) == 0) 460 return (ps->ps_immutable); 461 462 return (prop_string_contents(ps)); 463 } 464 465 /* 466 * prop_string_copy_value -- 467 * Copy the string object's value into the supplied buffer. 468 */ 469 _PROP_EXPORT bool 470 prop_string_copy_value(prop_string_t ps, void *buf, size_t buflen) 471 { 472 473 if (! prop_object_is_string(ps)) 474 return (false); 475 476 if (buf == NULL || buflen < ps->ps_size + 1) 477 return (false); 478 479 strcpy(buf, prop_string_contents(ps)); 480 481 return (true); 482 } 483 484 _PROP_DEPRECATED(prop_string_mutable, 485 "this program uses prop_string_mutable(); all functions " 486 "supporting mutable prop_strings are deprecated.") 487 _PROP_EXPORT bool 488 prop_string_mutable(prop_string_t ps) 489 { 490 491 if (! prop_object_is_string(ps)) 492 return (false); 493 494 return ((ps->ps_flags & PS_F_MUTABLE) != 0); 495 } 496 497 _PROP_DEPRECATED(prop_string_cstring, 498 "this program uses prop_string_cstring(), " 499 "which is deprecated; use prop_string_copy_value() instead.") 500 _PROP_EXPORT char * 501 prop_string_cstring(prop_string_t ps) 502 { 503 char *cp; 504 505 if (! prop_object_is_string(ps)) 506 return (NULL); 507 508 cp = _PROP_MALLOC(ps->ps_size + 1, M_TEMP); 509 if (cp != NULL) 510 strcpy(cp, prop_string_contents(ps)); 511 512 return (cp); 513 } 514 515 _PROP_DEPRECATED(prop_string_cstring_nocopy, 516 "this program uses prop_string_cstring_nocopy(), " 517 "which is deprecated; use prop_string_value() instead.") 518 _PROP_EXPORT const char * 519 prop_string_cstring_nocopy(prop_string_t ps) 520 { 521 522 if (! prop_object_is_string(ps)) 523 return (NULL); 524 525 return (prop_string_contents(ps)); 526 } 527 528 _PROP_DEPRECATED(prop_string_append, 529 "this program uses prop_string_append(); all functions " 530 "supporting mutable prop_strings are deprecated.") 531 _PROP_EXPORT bool 532 prop_string_append(prop_string_t dst, prop_string_t src) 533 { 534 char *ocp, *cp; 535 size_t len; 536 537 if (! (prop_object_is_string(dst) && 538 prop_object_is_string(src))) 539 return (false); 540 541 if ((dst->ps_flags & PS_F_MUTABLE) == 0) 542 return (false); 543 544 len = dst->ps_size + src->ps_size; 545 cp = _PROP_MALLOC(len + 1, M_PROP_STRING); 546 if (cp == NULL) 547 return (false); 548 snprintf(cp, len + 1, "%s%s", prop_string_contents(dst), 549 prop_string_contents(src)); 550 ocp = dst->ps_mutable; 551 dst->ps_mutable = cp; 552 dst->ps_size = len; 553 if (ocp != NULL) 554 _PROP_FREE(ocp, M_PROP_STRING); 555 556 return (true); 557 } 558 559 _PROP_DEPRECATED(prop_string_append_cstring, 560 "this program uses prop_string_append_cstring(); all functions " 561 "supporting mutable prop_strings are deprecated.") 562 _PROP_EXPORT bool 563 prop_string_append_cstring(prop_string_t dst, const char *src) 564 { 565 char *ocp, *cp; 566 size_t len; 567 568 if (! prop_object_is_string(dst)) 569 return (false); 570 571 _PROP_ASSERT(src != NULL); 572 573 if ((dst->ps_flags & PS_F_MUTABLE) == 0) 574 return (false); 575 576 len = dst->ps_size + strlen(src); 577 cp = _PROP_MALLOC(len + 1, M_PROP_STRING); 578 if (cp == NULL) 579 return (false); 580 snprintf(cp, len + 1, "%s%s", prop_string_contents(dst), src); 581 ocp = dst->ps_mutable; 582 dst->ps_mutable = cp; 583 dst->ps_size = len; 584 if (ocp != NULL) 585 _PROP_FREE(ocp, M_PROP_STRING); 586 587 return (true); 588 } 589 590 /* 591 * prop_string_equals -- 592 * Return true if two strings are equivalent. 593 */ 594 _PROP_EXPORT bool 595 prop_string_equals(prop_string_t str1, prop_string_t str2) 596 { 597 if (!prop_object_is_string(str1) || !prop_object_is_string(str2)) 598 return (false); 599 600 return prop_object_equals(str1, str2); 601 } 602 603 /* 604 * prop_string_equals_string -- 605 * Return true if the string object is equivalent to the specified 606 * C string. 607 */ 608 _PROP_EXPORT bool 609 prop_string_equals_string(prop_string_t ps, const char *cp) 610 { 611 612 if (! prop_object_is_string(ps)) 613 return (false); 614 615 return (strcmp(prop_string_contents(ps), cp) == 0); 616 } 617 618 _PROP_DEPRECATED(prop_string_equals_cstring, 619 "this program uses prop_string_equals_cstring(), " 620 "which is deprecated; prop_string_equals_string() instead.") 621 _PROP_EXPORT bool 622 prop_string_equals_cstring(prop_string_t ps, const char *cp) 623 { 624 return prop_string_equals_string(ps, cp); 625 } 626 627 /* 628 * prop_string_compare -- 629 * Compare two string objects, using strcmp() semantics. 630 */ 631 _PROP_EXPORT int 632 prop_string_compare(prop_string_t ps1, prop_string_t ps2) 633 { 634 if (!prop_object_is_string(ps1) || !prop_object_is_string(ps2)) 635 return (-666); /* arbitrary */ 636 637 return (strcmp(prop_string_contents(ps1), 638 prop_string_contents(ps2))); 639 } 640 641 /* 642 * prop_string_compare_string -- 643 * Compare a string object to the specified C string, using 644 * strcmp() semantics. 645 */ 646 _PROP_EXPORT int 647 prop_string_compare_string(prop_string_t ps, const char *cp) 648 { 649 if (!prop_object_is_string(ps)) 650 return (-666); /* arbitrary */ 651 652 return (strcmp(prop_string_contents(ps), cp)); 653 } 654 655 /* 656 * _prop_string_internalize -- 657 * Parse a <string>...</string> and return the object created from the 658 * external representation. 659 */ 660 /* ARGSUSED */ 661 bool 662 _prop_string_internalize(prop_stack_t stack, prop_object_t *obj, 663 struct _prop_object_internalize_context *ctx) 664 { 665 char *str; 666 size_t len, alen; 667 668 /* 669 * N.B. for empty JSON strings, the layer above us has made it 670 * look like XML. 671 */ 672 if (ctx->poic_is_empty_element) { 673 *obj = prop_string_create(); 674 return (true); 675 } 676 677 /* No attributes recognized here. */ 678 if (ctx->poic_tagattr != NULL) 679 return (true); 680 681 /* Compute the length of the result. */ 682 if (_prop_intern_decode_string(ctx, NULL, 0, &len, NULL) == false) 683 return (true); 684 685 str = _PROP_MALLOC(len + 1, M_PROP_STRING); 686 if (str == NULL) 687 return (true); 688 689 if (_prop_intern_decode_string(ctx, str, len, &alen, 690 &ctx->poic_cp) == false || 691 alen != len) { 692 _PROP_FREE(str, M_PROP_STRING); 693 return (true); 694 } 695 str[len] = '\0'; 696 697 if (ctx->poic_format == PROP_FORMAT_JSON) { 698 if (*ctx->poic_cp != '"') { 699 _PROP_FREE(str, M_PROP_STRING); 700 return (true); 701 } 702 ctx->poic_cp++; 703 } else { 704 if (_prop_xml_intern_find_tag(ctx, "string", 705 _PROP_TAG_TYPE_END) == false) { 706 _PROP_FREE(str, M_PROP_STRING); 707 return (true); 708 } 709 } 710 711 *obj = _prop_string_instantiate(0, str, len); 712 return (true); 713 } 714