1 /* $NetBSD: prop_object.c,v 1.28 2012/07/27 09:10:59 pooka 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 cp++; 421 if (_PROP_EOF(*cp)) 422 return (false); 423 424 ctx->poic_tagname_len = cp - ctx->poic_tagname; 425 426 /* Make sure this is the tag we're looking for. */ 427 if (tag != NULL && 428 (taglen != ctx->poic_tagname_len || 429 memcmp(tag, ctx->poic_tagname, taglen) != 0)) 430 return (false); 431 432 /* Check for empty tag. */ 433 if (*cp == '/') { 434 if (ctx->poic_tag_type != _PROP_TAG_TYPE_START) 435 return(false); /* only valid on start tags */ 436 ctx->poic_is_empty_element = true; 437 cp++; 438 if (_PROP_EOF(*cp) || *cp != '>') 439 return (false); 440 } else 441 ctx->poic_is_empty_element = false; 442 443 /* Easy case of no arguments. */ 444 if (*cp == '>') { 445 ctx->poic_tagattr = NULL; 446 ctx->poic_tagattr_len = 0; 447 ctx->poic_tagattrval = NULL; 448 ctx->poic_tagattrval_len = 0; 449 ctx->poic_cp = cp + 1; 450 return (true); 451 } 452 453 _PROP_ASSERT(!_PROP_EOF(*cp)); 454 cp++; 455 if (_PROP_EOF(*cp)) 456 return (false); 457 458 while (_PROP_ISSPACE(*cp)) 459 cp++; 460 if (_PROP_EOF(*cp)) 461 return (false); 462 463 ctx->poic_tagattr = cp; 464 465 while (!_PROP_ISSPACE(*cp) && *cp != '=') 466 cp++; 467 if (_PROP_EOF(*cp)) 468 return (false); 469 470 ctx->poic_tagattr_len = cp - ctx->poic_tagattr; 471 472 cp++; 473 if (*cp != '\"') 474 return (false); 475 cp++; 476 if (_PROP_EOF(*cp)) 477 return (false); 478 479 ctx->poic_tagattrval = cp; 480 while (*cp != '\"') 481 cp++; 482 if (_PROP_EOF(*cp)) 483 return (false); 484 ctx->poic_tagattrval_len = cp - ctx->poic_tagattrval; 485 486 cp++; 487 if (*cp != '>') 488 return (false); 489 490 ctx->poic_cp = cp + 1; 491 return (true); 492 } 493 494 /* 495 * _prop_object_internalize_decode_string -- 496 * Decode an encoded string. 497 */ 498 bool 499 _prop_object_internalize_decode_string( 500 struct _prop_object_internalize_context *ctx, 501 char *target, size_t targsize, size_t *sizep, 502 const char **cpp) 503 { 504 const char *src; 505 size_t tarindex; 506 char c; 507 508 tarindex = 0; 509 src = ctx->poic_cp; 510 511 for (;;) { 512 if (_PROP_EOF(*src)) 513 return (false); 514 if (*src == '<') { 515 break; 516 } 517 518 if ((c = *src) == '&') { 519 if (src[1] == 'a' && 520 src[2] == 'm' && 521 src[3] == 'p' && 522 src[4] == ';') { 523 c = '&'; 524 src += 5; 525 } else if (src[1] == 'l' && 526 src[2] == 't' && 527 src[3] == ';') { 528 c = '<'; 529 src += 4; 530 } else if (src[1] == 'g' && 531 src[2] == 't' && 532 src[3] == ';') { 533 c = '>'; 534 src += 4; 535 } else if (src[1] == 'a' && 536 src[2] == 'p' && 537 src[3] == 'o' && 538 src[4] == 's' && 539 src[5] == ';') { 540 c = '\''; 541 src += 6; 542 } else if (src[1] == 'q' && 543 src[2] == 'u' && 544 src[3] == 'o' && 545 src[4] == 't' && 546 src[5] == ';') { 547 c = '\"'; 548 src += 6; 549 } else 550 return (false); 551 } else 552 src++; 553 if (target) { 554 if (tarindex >= targsize) 555 return (false); 556 target[tarindex] = c; 557 } 558 tarindex++; 559 } 560 561 _PROP_ASSERT(*src == '<'); 562 if (sizep != NULL) 563 *sizep = tarindex; 564 if (cpp != NULL) 565 *cpp = src; 566 567 return (true); 568 } 569 570 /* 571 * _prop_object_internalize_match -- 572 * Returns true if the two character streams match. 573 */ 574 bool 575 _prop_object_internalize_match(const char *str1, size_t len1, 576 const char *str2, size_t len2) 577 { 578 579 return (len1 == len2 && memcmp(str1, str2, len1) == 0); 580 } 581 582 #define INTERNALIZER(t, f) \ 583 { t, sizeof(t) - 1, f } 584 585 static const struct _prop_object_internalizer { 586 const char *poi_tag; 587 size_t poi_taglen; 588 prop_object_internalizer_t poi_intern; 589 } _prop_object_internalizer_table[] = { 590 INTERNALIZER("array", _prop_array_internalize), 591 592 INTERNALIZER("true", _prop_bool_internalize), 593 INTERNALIZER("false", _prop_bool_internalize), 594 595 INTERNALIZER("data", _prop_data_internalize), 596 597 INTERNALIZER("dict", _prop_dictionary_internalize), 598 599 INTERNALIZER("integer", _prop_number_internalize), 600 601 INTERNALIZER("string", _prop_string_internalize), 602 603 { 0, 0, NULL } 604 }; 605 606 #undef INTERNALIZER 607 608 /* 609 * _prop_object_internalize_by_tag -- 610 * Determine the object type from the tag in the context and 611 * internalize it. 612 */ 613 prop_object_t 614 _prop_object_internalize_by_tag(struct _prop_object_internalize_context *ctx) 615 { 616 const struct _prop_object_internalizer *poi; 617 prop_object_t obj, parent_obj; 618 void *data, *iter; 619 prop_object_internalizer_continue_t iter_func; 620 struct _prop_stack stack; 621 622 _prop_stack_init(&stack); 623 624 match_start: 625 for (poi = _prop_object_internalizer_table; 626 poi->poi_tag != NULL; poi++) { 627 if (_prop_object_internalize_match(ctx->poic_tagname, 628 ctx->poic_tagname_len, 629 poi->poi_tag, 630 poi->poi_taglen)) 631 break; 632 } 633 if ((poi == NULL) || (poi->poi_tag == NULL)) { 634 while (_prop_stack_pop(&stack, &obj, &iter, &data, NULL)) { 635 iter_func = (prop_object_internalizer_continue_t)iter; 636 (*iter_func)(&stack, &obj, ctx, data, NULL); 637 } 638 639 return (NULL); 640 } 641 642 obj = NULL; 643 if (!(*poi->poi_intern)(&stack, &obj, ctx)) 644 goto match_start; 645 646 parent_obj = obj; 647 while (_prop_stack_pop(&stack, &parent_obj, &iter, &data, NULL)) { 648 iter_func = (prop_object_internalizer_continue_t)iter; 649 if (!(*iter_func)(&stack, &parent_obj, ctx, data, obj)) 650 goto match_start; 651 obj = parent_obj; 652 } 653 654 return (parent_obj); 655 } 656 657 prop_object_t 658 _prop_generic_internalize(const char *xml, const char *master_tag) 659 { 660 prop_object_t obj = NULL; 661 struct _prop_object_internalize_context *ctx; 662 663 ctx = _prop_object_internalize_context_alloc(xml); 664 if (ctx == NULL) 665 return (NULL); 666 667 /* We start with a <plist> tag. */ 668 if (_prop_object_internalize_find_tag(ctx, "plist", 669 _PROP_TAG_TYPE_START) == false) 670 goto out; 671 672 /* Plist elements cannot be empty. */ 673 if (ctx->poic_is_empty_element) 674 goto out; 675 676 /* 677 * We don't understand any plist attributes, but Apple XML 678 * property lists often have a "version" attribute. If we 679 * see that one, we simply ignore it. 680 */ 681 if (ctx->poic_tagattr != NULL && 682 !_PROP_TAGATTR_MATCH(ctx, "version")) 683 goto out; 684 685 /* Next we expect to see opening master_tag. */ 686 if (_prop_object_internalize_find_tag(ctx, master_tag, 687 _PROP_TAG_TYPE_START) == false) 688 goto out; 689 690 obj = _prop_object_internalize_by_tag(ctx); 691 if (obj == NULL) 692 goto out; 693 694 /* 695 * We've advanced past the closing master_tag. 696 * Now we want </plist>. 697 */ 698 if (_prop_object_internalize_find_tag(ctx, "plist", 699 _PROP_TAG_TYPE_END) == false) { 700 prop_object_release(obj); 701 obj = NULL; 702 } 703 704 out: 705 _prop_object_internalize_context_free(ctx); 706 return (obj); 707 } 708 709 /* 710 * _prop_object_internalize_context_alloc -- 711 * Allocate an internalize context. 712 */ 713 struct _prop_object_internalize_context * 714 _prop_object_internalize_context_alloc(const char *xml) 715 { 716 struct _prop_object_internalize_context *ctx; 717 718 ctx = _PROP_MALLOC(sizeof(struct _prop_object_internalize_context), 719 M_TEMP); 720 if (ctx == NULL) 721 return (NULL); 722 723 ctx->poic_xml = ctx->poic_cp = xml; 724 725 /* 726 * Skip any whitespace and XML preamble stuff that we don't 727 * know about / care about. 728 */ 729 for (;;) { 730 while (_PROP_ISSPACE(*xml)) 731 xml++; 732 if (_PROP_EOF(*xml) || *xml != '<') 733 goto bad; 734 735 #define MATCH(str) (memcmp(&xml[1], str, sizeof(str) - 1) == 0) 736 737 /* 738 * Skip over the XML preamble that Apple XML property 739 * lists usually include at the top of the file. 740 */ 741 if (MATCH("?xml ") || 742 MATCH("!DOCTYPE plist")) { 743 while (*xml != '>' && !_PROP_EOF(*xml)) 744 xml++; 745 if (_PROP_EOF(*xml)) 746 goto bad; 747 xml++; /* advance past the '>' */ 748 continue; 749 } 750 751 if (MATCH("<!--")) { 752 ctx->poic_cp = xml + 4; 753 if (_prop_object_internalize_skip_comment(ctx) == false) 754 goto bad; 755 xml = ctx->poic_cp; 756 continue; 757 } 758 759 #undef MATCH 760 761 /* 762 * We don't think we should skip it, so let's hope we can 763 * parse it. 764 */ 765 break; 766 } 767 768 ctx->poic_cp = xml; 769 return (ctx); 770 bad: 771 _PROP_FREE(ctx, M_TEMP); 772 return (NULL); 773 } 774 775 /* 776 * _prop_object_internalize_context_free -- 777 * Free an internalize context. 778 */ 779 void 780 _prop_object_internalize_context_free( 781 struct _prop_object_internalize_context *ctx) 782 { 783 784 _PROP_FREE(ctx, M_TEMP); 785 } 786 787 #if !defined(_KERNEL) && !defined(_STANDALONE) 788 /* 789 * _prop_object_externalize_file_dirname -- 790 * dirname(3), basically. We have to roll our own because the 791 * system dirname(3) isn't reentrant. 792 */ 793 static void 794 _prop_object_externalize_file_dirname(const char *path, char *result) 795 { 796 const char *lastp; 797 size_t len; 798 799 /* 800 * If `path' is a NULL pointer or points to an empty string, 801 * return ".". 802 */ 803 if (path == NULL || *path == '\0') 804 goto singledot; 805 806 /* String trailing slashes, if any. */ 807 lastp = path + strlen(path) - 1; 808 while (lastp != path && *lastp == '/') 809 lastp--; 810 811 /* Terminate path at the last occurrence of '/'. */ 812 do { 813 if (*lastp == '/') { 814 /* Strip trailing slashes, if any. */ 815 while (lastp != path && *lastp == '/') 816 lastp--; 817 818 /* ...and copy the result into the result buffer. */ 819 len = (lastp - path) + 1 /* last char */; 820 if (len > (PATH_MAX - 1)) 821 len = PATH_MAX - 1; 822 823 memcpy(result, path, len); 824 result[len] = '\0'; 825 return; 826 } 827 } while (--lastp >= path); 828 829 /* No /'s found, return ".". */ 830 singledot: 831 strcpy(result, "."); 832 } 833 834 /* 835 * _prop_object_externalize_write_file -- 836 * Write an externalized dictionary to the specified file. 837 * The file is written atomically from the caller's perspective, 838 * and the mode set to 0666 modified by the caller's umask. 839 */ 840 bool 841 _prop_object_externalize_write_file(const char *fname, const char *xml, 842 size_t len) 843 { 844 char tname[PATH_MAX]; 845 int fd; 846 int save_errno; 847 mode_t myumask; 848 849 if (len > SSIZE_MAX) { 850 errno = EFBIG; 851 return (false); 852 } 853 854 /* 855 * Get the directory name where the file is to be written 856 * and create the temporary file. 857 */ 858 _prop_object_externalize_file_dirname(fname, tname); 859 #define PLISTTMP "/.plistXXXXXX" 860 if (strlen(tname) + strlen(PLISTTMP) >= sizeof(tname)) { 861 errno = ENAMETOOLONG; 862 return (false); 863 } 864 strcat(tname, PLISTTMP); 865 #undef PLISTTMP 866 867 if ((fd = mkstemp(tname)) == -1) 868 return (false); 869 870 if (write(fd, xml, len) != (ssize_t)len) 871 goto bad; 872 873 if (fsync(fd) == -1) 874 goto bad; 875 876 myumask = umask(0); 877 (void)umask(myumask); 878 if (fchmod(fd, 0666 & ~myumask) == -1) 879 goto bad; 880 881 (void) close(fd); 882 fd = -1; 883 884 if (rename(tname, fname) == -1) 885 goto bad; 886 887 return (true); 888 889 bad: 890 save_errno = errno; 891 if (fd != -1) 892 (void) close(fd); 893 (void) unlink(tname); 894 errno = save_errno; 895 return (false); 896 } 897 898 /* 899 * _prop_object_internalize_map_file -- 900 * Map a file for the purpose of internalizing it. 901 */ 902 struct _prop_object_internalize_mapped_file * 903 _prop_object_internalize_map_file(const char *fname) 904 { 905 struct stat sb; 906 struct _prop_object_internalize_mapped_file *mf; 907 size_t pgsize = (size_t)sysconf(_SC_PAGESIZE); 908 size_t pgmask = pgsize - 1; 909 bool need_guard = false; 910 int fd; 911 912 mf = _PROP_MALLOC(sizeof(*mf), M_TEMP); 913 if (mf == NULL) 914 return (NULL); 915 916 fd = open(fname, O_RDONLY, 0400); 917 if (fd == -1) { 918 _PROP_FREE(mf, M_TEMP); 919 return (NULL); 920 } 921 922 if (fstat(fd, &sb) == -1) { 923 (void) close(fd); 924 _PROP_FREE(mf, M_TEMP); 925 return (NULL); 926 } 927 mf->poimf_mapsize = ((size_t)sb.st_size + pgmask) & ~pgmask; 928 if (mf->poimf_mapsize < (size_t)sb.st_size) { 929 (void) close(fd); 930 _PROP_FREE(mf, M_TEMP); 931 return (NULL); 932 } 933 934 /* 935 * If the file length is an integral number of pages, then we 936 * need to map a guard page at the end in order to provide the 937 * necessary NUL-termination of the buffer. 938 */ 939 if ((sb.st_size & pgmask) == 0) 940 need_guard = true; 941 942 mf->poimf_xml = mmap(NULL, need_guard ? mf->poimf_mapsize + pgsize 943 : mf->poimf_mapsize, 944 PROT_READ, MAP_FILE|MAP_SHARED, fd, (off_t)0); 945 (void) close(fd); 946 if (mf->poimf_xml == MAP_FAILED) { 947 _PROP_FREE(mf, M_TEMP); 948 return (NULL); 949 } 950 (void) madvise(mf->poimf_xml, mf->poimf_mapsize, MADV_SEQUENTIAL); 951 952 if (need_guard) { 953 if (mmap(mf->poimf_xml + mf->poimf_mapsize, 954 pgsize, PROT_READ, 955 MAP_ANON|MAP_PRIVATE|MAP_FIXED, -1, 956 (off_t)0) == MAP_FAILED) { 957 (void) munmap(mf->poimf_xml, mf->poimf_mapsize); 958 _PROP_FREE(mf, M_TEMP); 959 return (NULL); 960 } 961 mf->poimf_mapsize += pgsize; 962 } 963 964 return (mf); 965 } 966 967 /* 968 * _prop_object_internalize_unmap_file -- 969 * Unmap a file previously mapped for internalizing. 970 */ 971 void 972 _prop_object_internalize_unmap_file( 973 struct _prop_object_internalize_mapped_file *mf) 974 { 975 976 (void) madvise(mf->poimf_xml, mf->poimf_mapsize, MADV_DONTNEED); 977 (void) munmap(mf->poimf_xml, mf->poimf_mapsize); 978 _PROP_FREE(mf, M_TEMP); 979 } 980 #endif /* !_KERNEL && !_STANDALONE */ 981 982 /* 983 * prop_object_retain -- 984 * Increment the reference count on an object. 985 */ 986 void 987 prop_object_retain(prop_object_t obj) 988 { 989 struct _prop_object *po = obj; 990 uint32_t ncnt; 991 992 _PROP_ATOMIC_INC32_NV(&po->po_refcnt, ncnt); 993 _PROP_ASSERT(ncnt != 0); 994 } 995 996 /* 997 * prop_object_release_emergency 998 * A direct free with prop_object_release failed. 999 * Walk down the tree until a leaf is found and 1000 * free that. Do not recurse to avoid stack overflows. 1001 * 1002 * This is a slow edge condition, but necessary to 1003 * guarantee that an object can always be freed. 1004 */ 1005 static void 1006 prop_object_release_emergency(prop_object_t obj) 1007 { 1008 struct _prop_object *po; 1009 void (*unlock)(void); 1010 prop_object_t parent = NULL; 1011 uint32_t ocnt; 1012 1013 for (;;) { 1014 po = obj; 1015 _PROP_ASSERT(obj); 1016 1017 if (po->po_type->pot_lock != NULL) 1018 po->po_type->pot_lock(); 1019 1020 /* Save pointerto unlock function */ 1021 unlock = po->po_type->pot_unlock; 1022 1023 /* Dance a bit to make sure we always get the non-racy ocnt */ 1024 _PROP_ATOMIC_DEC32_NV(&po->po_refcnt, ocnt); 1025 ocnt++; 1026 _PROP_ASSERT(ocnt != 0); 1027 1028 if (ocnt != 1) { 1029 if (unlock != NULL) 1030 unlock(); 1031 break; 1032 } 1033 1034 _PROP_ASSERT(po->po_type); 1035 if ((po->po_type->pot_free)(NULL, &obj) == 1036 _PROP_OBJECT_FREE_DONE) { 1037 if (unlock != NULL) 1038 unlock(); 1039 break; 1040 } 1041 1042 if (unlock != NULL) 1043 unlock(); 1044 1045 parent = po; 1046 _PROP_ATOMIC_INC32(&po->po_refcnt); 1047 } 1048 _PROP_ASSERT(parent); 1049 /* One object was just freed. */ 1050 po = parent; 1051 (*po->po_type->pot_emergency_free)(parent); 1052 } 1053 1054 /* 1055 * prop_object_release -- 1056 * Decrement the reference count on an object. 1057 * 1058 * Free the object if we are releasing the final 1059 * reference. 1060 */ 1061 void 1062 prop_object_release(prop_object_t obj) 1063 { 1064 struct _prop_object *po; 1065 struct _prop_stack stack; 1066 void (*unlock)(void); 1067 int ret; 1068 uint32_t ocnt; 1069 1070 _prop_stack_init(&stack); 1071 1072 do { 1073 do { 1074 po = obj; 1075 _PROP_ASSERT(obj); 1076 1077 if (po->po_type->pot_lock != NULL) 1078 po->po_type->pot_lock(); 1079 1080 /* Save pointer to object unlock function */ 1081 unlock = po->po_type->pot_unlock; 1082 1083 _PROP_ATOMIC_DEC32_NV(&po->po_refcnt, ocnt); 1084 ocnt++; 1085 _PROP_ASSERT(ocnt != 0); 1086 1087 if (ocnt != 1) { 1088 ret = 0; 1089 if (unlock != NULL) 1090 unlock(); 1091 break; 1092 } 1093 1094 ret = (po->po_type->pot_free)(&stack, &obj); 1095 1096 if (unlock != NULL) 1097 unlock(); 1098 1099 if (ret == _PROP_OBJECT_FREE_DONE) 1100 break; 1101 1102 _PROP_ATOMIC_INC32(&po->po_refcnt); 1103 } while (ret == _PROP_OBJECT_FREE_RECURSE); 1104 if (ret == _PROP_OBJECT_FREE_FAILED) 1105 prop_object_release_emergency(obj); 1106 } while (_prop_stack_pop(&stack, &obj, NULL, NULL, NULL)); 1107 } 1108 1109 /* 1110 * prop_object_type -- 1111 * Return the type of an object. 1112 */ 1113 prop_type_t 1114 prop_object_type(prop_object_t obj) 1115 { 1116 struct _prop_object *po = obj; 1117 1118 if (obj == NULL) 1119 return (PROP_TYPE_UNKNOWN); 1120 1121 return (po->po_type->pot_type); 1122 } 1123 1124 /* 1125 * prop_object_equals -- 1126 * Returns true if thw two objects are equivalent. 1127 */ 1128 bool 1129 prop_object_equals(prop_object_t obj1, prop_object_t obj2) 1130 { 1131 return (prop_object_equals_with_error(obj1, obj2, NULL)); 1132 } 1133 1134 bool 1135 prop_object_equals_with_error(prop_object_t obj1, prop_object_t obj2, 1136 bool *error_flag) 1137 { 1138 struct _prop_object *po1; 1139 struct _prop_object *po2; 1140 void *stored_pointer1, *stored_pointer2; 1141 prop_object_t next_obj1, next_obj2; 1142 struct _prop_stack stack; 1143 _prop_object_equals_rv_t ret; 1144 1145 _prop_stack_init(&stack); 1146 if (error_flag) 1147 *error_flag = false; 1148 1149 start_subtree: 1150 stored_pointer1 = NULL; 1151 stored_pointer2 = NULL; 1152 po1 = obj1; 1153 po2 = obj2; 1154 1155 if (po1->po_type != po2->po_type) 1156 return (false); 1157 1158 continue_subtree: 1159 ret = (*po1->po_type->pot_equals)(obj1, obj2, 1160 &stored_pointer1, &stored_pointer2, 1161 &next_obj1, &next_obj2); 1162 if (ret == _PROP_OBJECT_EQUALS_FALSE) 1163 goto finish; 1164 if (ret == _PROP_OBJECT_EQUALS_TRUE) { 1165 if (!_prop_stack_pop(&stack, &obj1, &obj2, 1166 &stored_pointer1, &stored_pointer2)) 1167 return true; 1168 po1 = obj1; 1169 po2 = obj2; 1170 goto continue_subtree; 1171 } 1172 _PROP_ASSERT(ret == _PROP_OBJECT_EQUALS_RECURSE); 1173 1174 if (!_prop_stack_push(&stack, obj1, obj2, 1175 stored_pointer1, stored_pointer2)) { 1176 if (error_flag) 1177 *error_flag = true; 1178 goto finish; 1179 } 1180 obj1 = next_obj1; 1181 obj2 = next_obj2; 1182 goto start_subtree; 1183 1184 finish: 1185 while (_prop_stack_pop(&stack, &obj1, &obj2, NULL, NULL)) { 1186 po1 = obj1; 1187 (*po1->po_type->pot_equals_finish)(obj1, obj2); 1188 } 1189 return (false); 1190 } 1191 1192 /* 1193 * prop_object_iterator_next -- 1194 * Return the next item during an iteration. 1195 */ 1196 prop_object_t 1197 prop_object_iterator_next(prop_object_iterator_t pi) 1198 { 1199 1200 return ((*pi->pi_next_object)(pi)); 1201 } 1202 1203 /* 1204 * prop_object_iterator_reset -- 1205 * Reset the iterator to the first object so as to restart 1206 * iteration. 1207 */ 1208 void 1209 prop_object_iterator_reset(prop_object_iterator_t pi) 1210 { 1211 1212 (*pi->pi_reset)(pi); 1213 } 1214 1215 /* 1216 * prop_object_iterator_release -- 1217 * Release the object iterator. 1218 */ 1219 void 1220 prop_object_iterator_release(prop_object_iterator_t pi) 1221 { 1222 1223 prop_object_release(pi->pi_obj); 1224 _PROP_FREE(pi, M_TEMP); 1225 } 1226