1 /* $NetBSD: prop_object.c,v 1.35 2022/08/07 23:49:46 riastradh Exp $ */ 2 3 /*- 4 * Copyright (c) 2006, 2007 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_object.h> 34 35 #ifdef _PROP_NEED_REFCNT_MTX 36 static pthread_mutex_t _prop_refcnt_mtx = PTHREAD_MUTEX_INITIALIZER; 37 #endif /* _PROP_NEED_REFCNT_MTX */ 38 39 #if !defined(_KERNEL) && !defined(_STANDALONE) 40 #include <sys/mman.h> 41 #include <sys/stat.h> 42 #include <errno.h> 43 #include <fcntl.h> 44 #include <limits.h> 45 #include <unistd.h> 46 #endif 47 48 #ifdef _STANDALONE 49 void * 50 _prop_standalone_calloc(size_t size) 51 { 52 void *rv; 53 54 rv = alloc(size); 55 if (rv != NULL) 56 memset(rv, 0, size); 57 58 return (rv); 59 } 60 61 void * 62 _prop_standalone_realloc(void *v, size_t size) 63 { 64 void *rv; 65 66 rv = alloc(size); 67 if (rv != NULL) { 68 memcpy(rv, v, size); /* XXX */ 69 dealloc(v, 0); /* XXX */ 70 } 71 72 return (rv); 73 } 74 #endif /* _STANDALONE */ 75 76 /* 77 * _prop_object_init -- 78 * Initialize an object. Called when sub-classes create 79 * an instance. 80 */ 81 void 82 _prop_object_init(struct _prop_object *po, const struct _prop_object_type *pot) 83 { 84 85 po->po_type = pot; 86 po->po_refcnt = 1; 87 } 88 89 /* 90 * _prop_object_fini -- 91 * Finalize an object. Called when sub-classes destroy 92 * an instance. 93 */ 94 /*ARGSUSED*/ 95 void 96 _prop_object_fini(struct _prop_object *po _PROP_ARG_UNUSED) 97 { 98 /* Nothing to do, currently. */ 99 } 100 101 /* 102 * _prop_object_externalize_start_tag -- 103 * Append an XML-style start tag to the externalize buffer. 104 */ 105 bool 106 _prop_object_externalize_start_tag( 107 struct _prop_object_externalize_context *ctx, const char *tag) 108 { 109 unsigned int i; 110 111 for (i = 0; i < ctx->poec_depth; i++) { 112 if (_prop_object_externalize_append_char(ctx, '\t') == false) 113 return (false); 114 } 115 if (_prop_object_externalize_append_char(ctx, '<') == false || 116 _prop_object_externalize_append_cstring(ctx, tag) == false || 117 _prop_object_externalize_append_char(ctx, '>') == false) 118 return (false); 119 120 return (true); 121 } 122 123 /* 124 * _prop_object_externalize_end_tag -- 125 * Append an XML-style end tag to the externalize buffer. 126 */ 127 bool 128 _prop_object_externalize_end_tag( 129 struct _prop_object_externalize_context *ctx, const char *tag) 130 { 131 132 if (_prop_object_externalize_append_char(ctx, '<') == false || 133 _prop_object_externalize_append_char(ctx, '/') == false || 134 _prop_object_externalize_append_cstring(ctx, tag) == false || 135 _prop_object_externalize_append_char(ctx, '>') == false || 136 _prop_object_externalize_append_char(ctx, '\n') == false) 137 return (false); 138 139 return (true); 140 } 141 142 /* 143 * _prop_object_externalize_empty_tag -- 144 * Append an XML-style empty tag to the externalize buffer. 145 */ 146 bool 147 _prop_object_externalize_empty_tag( 148 struct _prop_object_externalize_context *ctx, const char *tag) 149 { 150 unsigned int i; 151 152 for (i = 0; i < ctx->poec_depth; i++) { 153 if (_prop_object_externalize_append_char(ctx, '\t') == false) 154 return (false); 155 } 156 157 if (_prop_object_externalize_append_char(ctx, '<') == false || 158 _prop_object_externalize_append_cstring(ctx, tag) == false || 159 _prop_object_externalize_append_char(ctx, '/') == false || 160 _prop_object_externalize_append_char(ctx, '>') == false || 161 _prop_object_externalize_append_char(ctx, '\n') == false) 162 return (false); 163 164 return (true); 165 } 166 167 /* 168 * _prop_object_externalize_append_cstring -- 169 * Append a C string to the externalize buffer. 170 */ 171 bool 172 _prop_object_externalize_append_cstring( 173 struct _prop_object_externalize_context *ctx, const char *cp) 174 { 175 176 while (*cp != '\0') { 177 if (_prop_object_externalize_append_char(ctx, 178 (unsigned char) *cp) == false) 179 return (false); 180 cp++; 181 } 182 183 return (true); 184 } 185 186 /* 187 * _prop_object_externalize_append_encoded_cstring -- 188 * Append an encoded C string to the externalize buffer. 189 */ 190 bool 191 _prop_object_externalize_append_encoded_cstring( 192 struct _prop_object_externalize_context *ctx, const char *cp) 193 { 194 195 while (*cp != '\0') { 196 switch (*cp) { 197 case '<': 198 if (_prop_object_externalize_append_cstring(ctx, 199 "<") == false) 200 return (false); 201 break; 202 case '>': 203 if (_prop_object_externalize_append_cstring(ctx, 204 ">") == false) 205 return (false); 206 break; 207 case '&': 208 if (_prop_object_externalize_append_cstring(ctx, 209 "&") == false) 210 return (false); 211 break; 212 default: 213 if (_prop_object_externalize_append_char(ctx, 214 (unsigned char) *cp) == false) 215 return (false); 216 break; 217 } 218 cp++; 219 } 220 221 return (true); 222 } 223 224 #define BUF_EXPAND 256 225 226 /* 227 * _prop_object_externalize_append_char -- 228 * Append a single character to the externalize buffer. 229 */ 230 bool 231 _prop_object_externalize_append_char( 232 struct _prop_object_externalize_context *ctx, unsigned char c) 233 { 234 235 _PROP_ASSERT(ctx->poec_capacity != 0); 236 _PROP_ASSERT(ctx->poec_buf != NULL); 237 _PROP_ASSERT(ctx->poec_len <= ctx->poec_capacity); 238 239 if (ctx->poec_len == ctx->poec_capacity) { 240 char *cp = _PROP_REALLOC(ctx->poec_buf, 241 ctx->poec_capacity + BUF_EXPAND, 242 M_TEMP); 243 if (cp == NULL) 244 return (false); 245 ctx->poec_capacity = ctx->poec_capacity + BUF_EXPAND; 246 ctx->poec_buf = cp; 247 } 248 249 ctx->poec_buf[ctx->poec_len++] = c; 250 251 return (true); 252 } 253 254 /* 255 * _prop_object_externalize_header -- 256 * Append the standard XML header to the externalize buffer. 257 */ 258 bool 259 _prop_object_externalize_header(struct _prop_object_externalize_context *ctx) 260 { 261 static const char _plist_xml_header[] = 262 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" 263 "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"; 264 265 if (_prop_object_externalize_append_cstring(ctx, 266 _plist_xml_header) == false || 267 _prop_object_externalize_start_tag(ctx, 268 "plist version=\"1.0\"") == false || 269 _prop_object_externalize_append_char(ctx, '\n') == false) 270 return (false); 271 272 return (true); 273 } 274 275 /* 276 * _prop_object_externalize_footer -- 277 * Append the standard XML footer to the externalize buffer. This 278 * also NUL-terminates the buffer. 279 */ 280 bool 281 _prop_object_externalize_footer(struct _prop_object_externalize_context *ctx) 282 { 283 284 if (_prop_object_externalize_end_tag(ctx, "plist") == false || 285 _prop_object_externalize_append_char(ctx, '\0') == false) 286 return (false); 287 288 return (true); 289 } 290 291 /* 292 * _prop_object_externalize_context_alloc -- 293 * Allocate an externalize context. 294 */ 295 struct _prop_object_externalize_context * 296 _prop_object_externalize_context_alloc(void) 297 { 298 struct _prop_object_externalize_context *ctx; 299 300 ctx = _PROP_MALLOC(sizeof(*ctx), M_TEMP); 301 if (ctx != NULL) { 302 ctx->poec_buf = _PROP_MALLOC(BUF_EXPAND, M_TEMP); 303 if (ctx->poec_buf == NULL) { 304 _PROP_FREE(ctx, M_TEMP); 305 return (NULL); 306 } 307 ctx->poec_len = 0; 308 ctx->poec_capacity = BUF_EXPAND; 309 ctx->poec_depth = 0; 310 } 311 return (ctx); 312 } 313 314 /* 315 * _prop_object_externalize_context_free -- 316 * Free an externalize context. 317 */ 318 void 319 _prop_object_externalize_context_free( 320 struct _prop_object_externalize_context *ctx) 321 { 322 323 /* Buffer is always freed by the caller. */ 324 _PROP_FREE(ctx, M_TEMP); 325 } 326 327 /* 328 * _prop_object_internalize_skip_comment -- 329 * Skip the body and end tag of a comment. 330 */ 331 static bool 332 _prop_object_internalize_skip_comment( 333 struct _prop_object_internalize_context *ctx) 334 { 335 const char *cp = ctx->poic_cp; 336 337 while (!_PROP_EOF(*cp)) { 338 if (cp[0] == '-' && 339 cp[1] == '-' && 340 cp[2] == '>') { 341 ctx->poic_cp = cp + 3; 342 return (true); 343 } 344 cp++; 345 } 346 347 return (false); /* ran out of buffer */ 348 } 349 350 /* 351 * _prop_object_internalize_find_tag -- 352 * Find the next tag in an XML stream. Optionally compare the found 353 * tag to an expected tag name. State of the context is undefined 354 * if this routine returns false. Upon success, the context points 355 * to the first octet after the tag. 356 */ 357 bool 358 _prop_object_internalize_find_tag(struct _prop_object_internalize_context *ctx, 359 const char *tag, _prop_tag_type_t type) 360 { 361 const char *cp; 362 size_t taglen; 363 364 if (tag != NULL) 365 taglen = strlen(tag); 366 else 367 taglen = 0; 368 369 start_over: 370 cp = ctx->poic_cp; 371 372 /* 373 * Find the start of the tag. 374 */ 375 while (_PROP_ISSPACE(*cp)) 376 cp++; 377 if (_PROP_EOF(*cp)) 378 return (false); 379 380 if (*cp != '<') 381 return (false); 382 383 ctx->poic_tag_start = cp++; 384 if (_PROP_EOF(*cp)) 385 return (false); 386 387 if (*cp == '!') { 388 if (cp[1] != '-' || cp[2] != '-') 389 return (false); 390 /* 391 * Comment block -- only allowed if we are allowed to 392 * return a start tag. 393 */ 394 if (type == _PROP_TAG_TYPE_END) 395 return (false); 396 ctx->poic_cp = cp + 3; 397 if (_prop_object_internalize_skip_comment(ctx) == false) 398 return (false); 399 goto start_over; 400 } 401 402 if (*cp == '/') { 403 if (type != _PROP_TAG_TYPE_END && 404 type != _PROP_TAG_TYPE_EITHER) 405 return (false); 406 cp++; 407 if (_PROP_EOF(*cp)) 408 return (false); 409 ctx->poic_tag_type = _PROP_TAG_TYPE_END; 410 } else { 411 if (type != _PROP_TAG_TYPE_START && 412 type != _PROP_TAG_TYPE_EITHER) 413 return (false); 414 ctx->poic_tag_type = _PROP_TAG_TYPE_START; 415 } 416 417 ctx->poic_tagname = cp; 418 419 while (!_PROP_ISSPACE(*cp) && *cp != '/' && *cp != '>') { 420 if (_PROP_EOF(*cp)) 421 return (false); 422 cp++; 423 } 424 425 ctx->poic_tagname_len = cp - ctx->poic_tagname; 426 427 /* Make sure this is the tag we're looking for. */ 428 if (tag != NULL && 429 (taglen != ctx->poic_tagname_len || 430 memcmp(tag, ctx->poic_tagname, taglen) != 0)) 431 return (false); 432 433 /* Check for empty tag. */ 434 if (*cp == '/') { 435 if (ctx->poic_tag_type != _PROP_TAG_TYPE_START) 436 return(false); /* only valid on start tags */ 437 ctx->poic_is_empty_element = true; 438 cp++; 439 if (_PROP_EOF(*cp) || *cp != '>') 440 return (false); 441 } else 442 ctx->poic_is_empty_element = false; 443 444 /* Easy case of no arguments. */ 445 if (*cp == '>') { 446 ctx->poic_tagattr = NULL; 447 ctx->poic_tagattr_len = 0; 448 ctx->poic_tagattrval = NULL; 449 ctx->poic_tagattrval_len = 0; 450 ctx->poic_cp = cp + 1; 451 return (true); 452 } 453 454 _PROP_ASSERT(!_PROP_EOF(*cp)); 455 cp++; 456 if (_PROP_EOF(*cp)) 457 return (false); 458 459 while (_PROP_ISSPACE(*cp)) 460 cp++; 461 if (_PROP_EOF(*cp)) 462 return (false); 463 464 ctx->poic_tagattr = cp; 465 466 while (!_PROP_ISSPACE(*cp) && *cp != '=') { 467 if (_PROP_EOF(*cp)) 468 return (false); 469 cp++; 470 } 471 472 ctx->poic_tagattr_len = cp - ctx->poic_tagattr; 473 474 cp++; 475 if (*cp != '\"') 476 return (false); 477 cp++; 478 if (_PROP_EOF(*cp)) 479 return (false); 480 481 ctx->poic_tagattrval = cp; 482 while (*cp != '\"') { 483 if (_PROP_EOF(*cp)) 484 return (false); 485 cp++; 486 } 487 ctx->poic_tagattrval_len = cp - ctx->poic_tagattrval; 488 489 cp++; 490 if (*cp != '>') 491 return (false); 492 493 ctx->poic_cp = cp + 1; 494 return (true); 495 } 496 497 /* 498 * _prop_object_internalize_decode_string -- 499 * Decode an encoded string. 500 */ 501 bool 502 _prop_object_internalize_decode_string( 503 struct _prop_object_internalize_context *ctx, 504 char *target, size_t targsize, size_t *sizep, 505 const char **cpp) 506 { 507 const char *src; 508 size_t tarindex; 509 char c; 510 511 tarindex = 0; 512 src = ctx->poic_cp; 513 514 for (;;) { 515 if (_PROP_EOF(*src)) 516 return (false); 517 if (*src == '<') { 518 break; 519 } 520 521 if ((c = *src) == '&') { 522 if (src[1] == 'a' && 523 src[2] == 'm' && 524 src[3] == 'p' && 525 src[4] == ';') { 526 c = '&'; 527 src += 5; 528 } else if (src[1] == 'l' && 529 src[2] == 't' && 530 src[3] == ';') { 531 c = '<'; 532 src += 4; 533 } else if (src[1] == 'g' && 534 src[2] == 't' && 535 src[3] == ';') { 536 c = '>'; 537 src += 4; 538 } else if (src[1] == 'a' && 539 src[2] == 'p' && 540 src[3] == 'o' && 541 src[4] == 's' && 542 src[5] == ';') { 543 c = '\''; 544 src += 6; 545 } else if (src[1] == 'q' && 546 src[2] == 'u' && 547 src[3] == 'o' && 548 src[4] == 't' && 549 src[5] == ';') { 550 c = '\"'; 551 src += 6; 552 } else 553 return (false); 554 } else 555 src++; 556 if (target) { 557 if (tarindex >= targsize) 558 return (false); 559 target[tarindex] = c; 560 } 561 tarindex++; 562 } 563 564 _PROP_ASSERT(*src == '<'); 565 if (sizep != NULL) 566 *sizep = tarindex; 567 if (cpp != NULL) 568 *cpp = src; 569 570 return (true); 571 } 572 573 /* 574 * _prop_object_internalize_match -- 575 * Returns true if the two character streams match. 576 */ 577 bool 578 _prop_object_internalize_match(const char *str1, size_t len1, 579 const char *str2, size_t len2) 580 { 581 582 return (len1 == len2 && memcmp(str1, str2, len1) == 0); 583 } 584 585 #define INTERNALIZER(t, f) \ 586 { t, sizeof(t) - 1, f } 587 588 static const struct _prop_object_internalizer { 589 const char *poi_tag; 590 size_t poi_taglen; 591 prop_object_internalizer_t poi_intern; 592 } _prop_object_internalizer_table[] = { 593 INTERNALIZER("array", _prop_array_internalize), 594 595 INTERNALIZER("true", _prop_bool_internalize), 596 INTERNALIZER("false", _prop_bool_internalize), 597 598 INTERNALIZER("data", _prop_data_internalize), 599 600 INTERNALIZER("dict", _prop_dictionary_internalize), 601 602 INTERNALIZER("integer", _prop_number_internalize), 603 604 INTERNALIZER("string", _prop_string_internalize), 605 606 { 0, 0, NULL } 607 }; 608 609 #undef INTERNALIZER 610 611 /* 612 * _prop_object_internalize_by_tag -- 613 * Determine the object type from the tag in the context and 614 * internalize it. 615 */ 616 prop_object_t 617 _prop_object_internalize_by_tag(struct _prop_object_internalize_context *ctx) 618 { 619 const struct _prop_object_internalizer *poi; 620 prop_object_t obj, parent_obj; 621 void *data, *iter; 622 prop_object_internalizer_continue_t iter_func; 623 struct _prop_stack stack; 624 625 _prop_stack_init(&stack); 626 627 match_start: 628 for (poi = _prop_object_internalizer_table; 629 poi->poi_tag != NULL; poi++) { 630 if (_prop_object_internalize_match(ctx->poic_tagname, 631 ctx->poic_tagname_len, 632 poi->poi_tag, 633 poi->poi_taglen)) 634 break; 635 } 636 if ((poi == NULL) || (poi->poi_tag == NULL)) { 637 while (_prop_stack_pop(&stack, &obj, &iter, &data, NULL)) { 638 iter_func = (prop_object_internalizer_continue_t)iter; 639 (*iter_func)(&stack, &obj, ctx, data, NULL); 640 } 641 642 return (NULL); 643 } 644 645 obj = NULL; 646 if (!(*poi->poi_intern)(&stack, &obj, ctx)) 647 goto match_start; 648 649 parent_obj = obj; 650 while (_prop_stack_pop(&stack, &parent_obj, &iter, &data, NULL)) { 651 iter_func = (prop_object_internalizer_continue_t)iter; 652 if (!(*iter_func)(&stack, &parent_obj, ctx, data, obj)) 653 goto match_start; 654 obj = parent_obj; 655 } 656 657 return (parent_obj); 658 } 659 660 prop_object_t 661 _prop_generic_internalize(const char *xml, const char *master_tag) 662 { 663 prop_object_t obj = NULL; 664 struct _prop_object_internalize_context *ctx; 665 666 ctx = _prop_object_internalize_context_alloc(xml); 667 if (ctx == NULL) 668 return (NULL); 669 670 /* We start with a <plist> tag. */ 671 if (_prop_object_internalize_find_tag(ctx, "plist", 672 _PROP_TAG_TYPE_START) == false) 673 goto out; 674 675 /* Plist elements cannot be empty. */ 676 if (ctx->poic_is_empty_element) 677 goto out; 678 679 /* 680 * We don't understand any plist attributes, but Apple XML 681 * property lists often have a "version" attribute. If we 682 * see that one, we simply ignore it. 683 */ 684 if (ctx->poic_tagattr != NULL && 685 !_PROP_TAGATTR_MATCH(ctx, "version")) 686 goto out; 687 688 /* Next we expect to see opening master_tag. */ 689 if (_prop_object_internalize_find_tag(ctx, master_tag, 690 _PROP_TAG_TYPE_START) == false) 691 goto out; 692 693 obj = _prop_object_internalize_by_tag(ctx); 694 if (obj == NULL) 695 goto out; 696 697 /* 698 * We've advanced past the closing master_tag. 699 * Now we want </plist>. 700 */ 701 if (_prop_object_internalize_find_tag(ctx, "plist", 702 _PROP_TAG_TYPE_END) == false) { 703 prop_object_release(obj); 704 obj = NULL; 705 } 706 707 out: 708 _prop_object_internalize_context_free(ctx); 709 return (obj); 710 } 711 712 /* 713 * _prop_object_internalize_context_alloc -- 714 * Allocate an internalize context. 715 */ 716 struct _prop_object_internalize_context * 717 _prop_object_internalize_context_alloc(const char *xml) 718 { 719 struct _prop_object_internalize_context *ctx; 720 721 ctx = _PROP_MALLOC(sizeof(*ctx), M_TEMP); 722 if (ctx == NULL) 723 return (NULL); 724 725 ctx->poic_xml = ctx->poic_cp = xml; 726 727 /* 728 * Skip any whitespace and XML preamble stuff that we don't 729 * know about / care about. 730 */ 731 for (;;) { 732 while (_PROP_ISSPACE(*xml)) 733 xml++; 734 if (_PROP_EOF(*xml) || *xml != '<') 735 goto bad; 736 737 #define MATCH(str) (strncmp(&xml[1], str, strlen(str)) == 0) 738 739 /* 740 * Skip over the XML preamble that Apple XML property 741 * lists usually include at the top of the file. 742 */ 743 if (MATCH("?xml ") || 744 MATCH("!DOCTYPE plist")) { 745 while (*xml != '>' && !_PROP_EOF(*xml)) 746 xml++; 747 if (_PROP_EOF(*xml)) 748 goto bad; 749 xml++; /* advance past the '>' */ 750 continue; 751 } 752 753 if (MATCH("<!--")) { 754 ctx->poic_cp = xml + 4; 755 if (_prop_object_internalize_skip_comment(ctx) == false) 756 goto bad; 757 xml = ctx->poic_cp; 758 continue; 759 } 760 761 #undef MATCH 762 763 /* 764 * We don't think we should skip it, so let's hope we can 765 * parse it. 766 */ 767 break; 768 } 769 770 ctx->poic_cp = xml; 771 return (ctx); 772 bad: 773 _PROP_FREE(ctx, M_TEMP); 774 return (NULL); 775 } 776 777 /* 778 * _prop_object_internalize_context_free -- 779 * Free an internalize context. 780 */ 781 void 782 _prop_object_internalize_context_free( 783 struct _prop_object_internalize_context *ctx) 784 { 785 786 _PROP_FREE(ctx, M_TEMP); 787 } 788 789 #if !defined(_KERNEL) && !defined(_STANDALONE) 790 /* 791 * _prop_object_externalize_file_dirname -- 792 * dirname(3), basically. We have to roll our own because the 793 * system dirname(3) isn't reentrant. 794 */ 795 static void 796 _prop_object_externalize_file_dirname(const char *path, char *result) 797 { 798 const char *lastp; 799 size_t len; 800 801 /* 802 * If `path' is a NULL pointer or points to an empty string, 803 * return ".". 804 */ 805 if (path == NULL || *path == '\0') 806 goto singledot; 807 808 /* String trailing slashes, if any. */ 809 lastp = path + strlen(path) - 1; 810 while (lastp != path && *lastp == '/') 811 lastp--; 812 813 /* Terminate path at the last occurrence of '/'. */ 814 do { 815 if (*lastp == '/') { 816 /* Strip trailing slashes, if any. */ 817 while (lastp != path && *lastp == '/') 818 lastp--; 819 820 /* ...and copy the result into the result buffer. */ 821 len = (lastp - path) + 1 /* last char */; 822 if (len > (PATH_MAX - 1)) 823 len = PATH_MAX - 1; 824 825 memcpy(result, path, len); 826 result[len] = '\0'; 827 return; 828 } 829 } while (--lastp >= path); 830 831 /* No /'s found, return ".". */ 832 singledot: 833 strcpy(result, "."); 834 } 835 836 /* 837 * _prop_object_externalize_write_file -- 838 * Write an externalized dictionary to the specified file. 839 * The file is written atomically from the caller's perspective, 840 * and the mode set to 0666 modified by the caller's umask. 841 */ 842 bool 843 _prop_object_externalize_write_file(const char *fname, const char *xml, 844 size_t len) 845 { 846 char tname[PATH_MAX]; 847 int fd; 848 int save_errno; 849 mode_t myumask; 850 851 if (len > SSIZE_MAX) { 852 errno = EFBIG; 853 return (false); 854 } 855 856 /* 857 * Get the directory name where the file is to be written 858 * and create the temporary file. 859 */ 860 _prop_object_externalize_file_dirname(fname, tname); 861 #define PLISTTMP "/.plistXXXXXX" 862 if (strlen(tname) + strlen(PLISTTMP) >= sizeof(tname)) { 863 errno = ENAMETOOLONG; 864 return (false); 865 } 866 strcat(tname, PLISTTMP); 867 #undef PLISTTMP 868 869 if ((fd = mkstemp(tname)) == -1) 870 return (false); 871 872 if (write(fd, xml, len) != (ssize_t)len) 873 goto bad; 874 875 if (fsync(fd) == -1) 876 goto bad; 877 878 myumask = umask(0); 879 (void)umask(myumask); 880 if (fchmod(fd, 0666 & ~myumask) == -1) 881 goto bad; 882 883 (void) close(fd); 884 fd = -1; 885 886 if (rename(tname, fname) == -1) 887 goto bad; 888 889 return (true); 890 891 bad: 892 save_errno = errno; 893 if (fd != -1) 894 (void) close(fd); 895 (void) unlink(tname); 896 errno = save_errno; 897 return (false); 898 } 899 900 /* 901 * _prop_object_internalize_map_file -- 902 * Map a file for the purpose of internalizing it. 903 */ 904 struct _prop_object_internalize_mapped_file * 905 _prop_object_internalize_map_file(const char *fname) 906 { 907 struct stat sb; 908 struct _prop_object_internalize_mapped_file *mf; 909 size_t pgsize = (size_t)sysconf(_SC_PAGESIZE); 910 size_t pgmask = pgsize - 1; 911 bool need_guard = false; 912 int fd; 913 914 mf = _PROP_MALLOC(sizeof(*mf), M_TEMP); 915 if (mf == NULL) 916 return (NULL); 917 918 fd = open(fname, O_RDONLY, 0400); 919 if (fd == -1) { 920 _PROP_FREE(mf, M_TEMP); 921 return (NULL); 922 } 923 924 if (fstat(fd, &sb) == -1) { 925 (void) close(fd); 926 _PROP_FREE(mf, M_TEMP); 927 return (NULL); 928 } 929 mf->poimf_mapsize = ((size_t)sb.st_size + pgmask) & ~pgmask; 930 if (mf->poimf_mapsize < (size_t)sb.st_size) { 931 (void) close(fd); 932 _PROP_FREE(mf, M_TEMP); 933 return (NULL); 934 } 935 936 /* 937 * If the file length is an integral number of pages, then we 938 * need to map a guard page at the end in order to provide the 939 * necessary NUL-termination of the buffer. 940 */ 941 if ((sb.st_size & pgmask) == 0) 942 need_guard = true; 943 944 mf->poimf_xml = mmap(NULL, need_guard ? mf->poimf_mapsize + pgsize 945 : mf->poimf_mapsize, 946 PROT_READ, MAP_FILE|MAP_SHARED, fd, (off_t)0); 947 (void) close(fd); 948 if (mf->poimf_xml == MAP_FAILED) { 949 _PROP_FREE(mf, M_TEMP); 950 return (NULL); 951 } 952 #ifdef POSIX_MADV_SEQUENTIAL 953 (void) posix_madvise(mf->poimf_xml, mf->poimf_mapsize, 954 POSIX_MADV_SEQUENTIAL); 955 #endif 956 957 if (need_guard) { 958 if (mmap(mf->poimf_xml + mf->poimf_mapsize, 959 pgsize, PROT_READ, 960 MAP_ANON|MAP_PRIVATE|MAP_FIXED, -1, 961 (off_t)0) == MAP_FAILED) { 962 (void) munmap(mf->poimf_xml, mf->poimf_mapsize); 963 _PROP_FREE(mf, M_TEMP); 964 return (NULL); 965 } 966 mf->poimf_mapsize += pgsize; 967 } 968 969 return (mf); 970 } 971 972 /* 973 * _prop_object_internalize_unmap_file -- 974 * Unmap a file previously mapped for internalizing. 975 */ 976 void 977 _prop_object_internalize_unmap_file( 978 struct _prop_object_internalize_mapped_file *mf) 979 { 980 981 #ifdef POSIX_MADV_DONTNEED 982 (void) posix_madvise(mf->poimf_xml, mf->poimf_mapsize, 983 POSIX_MADV_DONTNEED); 984 #endif 985 (void) munmap(mf->poimf_xml, mf->poimf_mapsize); 986 _PROP_FREE(mf, M_TEMP); 987 } 988 #endif /* !_KERNEL && !_STANDALONE */ 989 990 /* 991 * prop_object_retain -- 992 * Increment the reference count on an object. 993 */ 994 void 995 prop_object_retain(prop_object_t obj) 996 { 997 struct _prop_object *po = obj; 998 uint32_t ncnt __unused; 999 1000 _PROP_ATOMIC_INC32_NV(&po->po_refcnt, ncnt); 1001 _PROP_ASSERT(ncnt != 0); 1002 } 1003 1004 /* 1005 * prop_object_release_emergency 1006 * A direct free with prop_object_release failed. 1007 * Walk down the tree until a leaf is found and 1008 * free that. Do not recurse to avoid stack overflows. 1009 * 1010 * This is a slow edge condition, but necessary to 1011 * guarantee that an object can always be freed. 1012 */ 1013 static void 1014 prop_object_release_emergency(prop_object_t obj) 1015 { 1016 struct _prop_object *po; 1017 void (*unlock)(void); 1018 prop_object_t parent = NULL; 1019 uint32_t ocnt; 1020 1021 for (;;) { 1022 po = obj; 1023 _PROP_ASSERT(obj); 1024 1025 if (po->po_type->pot_lock != NULL) 1026 po->po_type->pot_lock(); 1027 1028 /* Save pointerto unlock function */ 1029 unlock = po->po_type->pot_unlock; 1030 1031 /* Dance a bit to make sure we always get the non-racy ocnt */ 1032 _PROP_ATOMIC_DEC32_NV(&po->po_refcnt, ocnt); 1033 ocnt++; 1034 _PROP_ASSERT(ocnt != 0); 1035 1036 if (ocnt != 1) { 1037 if (unlock != NULL) 1038 unlock(); 1039 break; 1040 } 1041 1042 _PROP_ASSERT(po->po_type); 1043 if ((po->po_type->pot_free)(NULL, &obj) == 1044 _PROP_OBJECT_FREE_DONE) { 1045 if (unlock != NULL) 1046 unlock(); 1047 break; 1048 } 1049 1050 if (unlock != NULL) 1051 unlock(); 1052 1053 parent = po; 1054 _PROP_ATOMIC_INC32(&po->po_refcnt); 1055 } 1056 _PROP_ASSERT(parent); 1057 /* One object was just freed. */ 1058 po = parent; 1059 (*po->po_type->pot_emergency_free)(parent); 1060 } 1061 1062 /* 1063 * prop_object_release -- 1064 * Decrement the reference count on an object. 1065 * 1066 * Free the object if we are releasing the final 1067 * reference. 1068 */ 1069 void 1070 prop_object_release(prop_object_t obj) 1071 { 1072 struct _prop_object *po; 1073 struct _prop_stack stack; 1074 void (*unlock)(void); 1075 int ret; 1076 uint32_t ocnt; 1077 1078 _prop_stack_init(&stack); 1079 1080 do { 1081 do { 1082 po = obj; 1083 _PROP_ASSERT(obj); 1084 1085 if (po->po_type->pot_lock != NULL) 1086 po->po_type->pot_lock(); 1087 1088 /* Save pointer to object unlock function */ 1089 unlock = po->po_type->pot_unlock; 1090 1091 _PROP_ATOMIC_DEC32_NV(&po->po_refcnt, ocnt); 1092 ocnt++; 1093 _PROP_ASSERT(ocnt != 0); 1094 1095 if (ocnt != 1) { 1096 ret = 0; 1097 if (unlock != NULL) 1098 unlock(); 1099 break; 1100 } 1101 1102 ret = (po->po_type->pot_free)(&stack, &obj); 1103 1104 if (unlock != NULL) 1105 unlock(); 1106 1107 if (ret == _PROP_OBJECT_FREE_DONE) 1108 break; 1109 1110 _PROP_ATOMIC_INC32(&po->po_refcnt); 1111 } while (ret == _PROP_OBJECT_FREE_RECURSE); 1112 if (ret == _PROP_OBJECT_FREE_FAILED) 1113 prop_object_release_emergency(obj); 1114 } while (_prop_stack_pop(&stack, &obj, NULL, NULL, NULL)); 1115 } 1116 1117 /* 1118 * prop_object_type -- 1119 * Return the type of an object. 1120 */ 1121 prop_type_t 1122 prop_object_type(prop_object_t obj) 1123 { 1124 struct _prop_object *po = obj; 1125 1126 if (obj == NULL) 1127 return (PROP_TYPE_UNKNOWN); 1128 1129 return (po->po_type->pot_type); 1130 } 1131 1132 /* 1133 * prop_object_equals -- 1134 * Returns true if thw two objects are equivalent. 1135 */ 1136 bool 1137 prop_object_equals(prop_object_t obj1, prop_object_t obj2) 1138 { 1139 return (prop_object_equals_with_error(obj1, obj2, NULL)); 1140 } 1141 1142 bool 1143 prop_object_equals_with_error(prop_object_t obj1, prop_object_t obj2, 1144 bool *error_flag) 1145 { 1146 struct _prop_object *po1; 1147 struct _prop_object *po2; 1148 void *stored_pointer1, *stored_pointer2; 1149 prop_object_t next_obj1, next_obj2; 1150 struct _prop_stack stack; 1151 _prop_object_equals_rv_t ret; 1152 1153 _prop_stack_init(&stack); 1154 if (error_flag) 1155 *error_flag = false; 1156 1157 start_subtree: 1158 stored_pointer1 = NULL; 1159 stored_pointer2 = NULL; 1160 po1 = obj1; 1161 po2 = obj2; 1162 1163 if (po1->po_type != po2->po_type) 1164 return (false); 1165 1166 continue_subtree: 1167 ret = (*po1->po_type->pot_equals)(obj1, obj2, 1168 &stored_pointer1, &stored_pointer2, 1169 &next_obj1, &next_obj2); 1170 if (ret == _PROP_OBJECT_EQUALS_FALSE) 1171 goto finish; 1172 if (ret == _PROP_OBJECT_EQUALS_TRUE) { 1173 if (!_prop_stack_pop(&stack, &obj1, &obj2, 1174 &stored_pointer1, &stored_pointer2)) 1175 return true; 1176 po1 = obj1; 1177 po2 = obj2; 1178 goto continue_subtree; 1179 } 1180 _PROP_ASSERT(ret == _PROP_OBJECT_EQUALS_RECURSE); 1181 1182 if (!_prop_stack_push(&stack, obj1, obj2, 1183 stored_pointer1, stored_pointer2)) { 1184 if (error_flag) 1185 *error_flag = true; 1186 goto finish; 1187 } 1188 obj1 = next_obj1; 1189 obj2 = next_obj2; 1190 goto start_subtree; 1191 1192 finish: 1193 while (_prop_stack_pop(&stack, &obj1, &obj2, NULL, NULL)) { 1194 po1 = obj1; 1195 (*po1->po_type->pot_equals_finish)(obj1, obj2); 1196 } 1197 return (false); 1198 } 1199 1200 /* 1201 * prop_object_iterator_next -- 1202 * Return the next item during an iteration. 1203 */ 1204 prop_object_t 1205 prop_object_iterator_next(prop_object_iterator_t pi) 1206 { 1207 1208 return ((*pi->pi_next_object)(pi)); 1209 } 1210 1211 /* 1212 * prop_object_iterator_reset -- 1213 * Reset the iterator to the first object so as to restart 1214 * iteration. 1215 */ 1216 void 1217 prop_object_iterator_reset(prop_object_iterator_t pi) 1218 { 1219 1220 (*pi->pi_reset)(pi); 1221 } 1222 1223 /* 1224 * prop_object_iterator_release -- 1225 * Release the object iterator. 1226 */ 1227 void 1228 prop_object_iterator_release(prop_object_iterator_t pi) 1229 { 1230 1231 prop_object_release(pi->pi_obj); 1232 _PROP_FREE(pi, M_TEMP); 1233 } 1234