1 /* $NetBSD: prop_object.c,v 1.3 2006/05/18 16:23:55 thorpej Exp $ */ 2 3 /*- 4 * Copyright (c) 2006 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 * 3. All advertising materials mentioning features or use of this software 19 * must display the following acknowledgement: 20 * This product includes software developed by the NetBSD 21 * Foundation, Inc. and its contributors. 22 * 4. Neither the name of The NetBSD Foundation nor the names of its 23 * contributors may be used to endorse or promote products derived 24 * from this software without specific prior written permission. 25 * 26 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 27 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 28 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 29 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 30 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 31 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 32 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 33 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 34 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 36 * POSSIBILITY OF SUCH DAMAGE. 37 */ 38 39 #include <prop/prop_object.h> 40 #include "prop_object_impl.h" 41 42 #ifdef _STANDALONE 43 void * 44 _prop_standalone_calloc(size_t size) 45 { 46 void *rv; 47 48 rv = alloc(size); 49 if (rv != NULL) 50 memset(rv, 0, size); 51 52 return (rv); 53 } 54 55 void * 56 _prop_standalone_realloc(void *v, size_t size) 57 { 58 void *rv; 59 60 rv = alloc(size); 61 if (rv != NULL) { 62 memcpy(rv, v, size); /* XXX */ 63 dealloc(v, 0); /* XXX */ 64 } 65 66 return (rv); 67 } 68 #endif /* _STANDALONE */ 69 70 /* 71 * _prop_object_init -- 72 * Initialize an object. Called when sub-classes create 73 * an instance. 74 */ 75 void 76 _prop_object_init(struct _prop_object *po, const struct _prop_object_type *pot) 77 { 78 79 po->po_type = pot; 80 po->po_refcnt = 1; 81 } 82 83 /* 84 * _prop_object_fini -- 85 * Finalize an object. Called when sub-classes destroy 86 * an instance. 87 */ 88 void 89 _prop_object_fini(struct _prop_object *po) 90 { 91 /* Nothing to do, currently. */ 92 } 93 94 /* 95 * _prop_object_externalize_start_tag -- 96 * Append an XML-style start tag to the externalize buffer. 97 */ 98 boolean_t 99 _prop_object_externalize_start_tag( 100 struct _prop_object_externalize_context *ctx, const char *tag) 101 { 102 unsigned int i; 103 104 for (i = 0; i < ctx->poec_depth; i++) { 105 if (_prop_object_externalize_append_char(ctx, '\t') == FALSE) 106 return (FALSE); 107 } 108 if (_prop_object_externalize_append_char(ctx, '<') == FALSE || 109 _prop_object_externalize_append_cstring(ctx, tag) == FALSE || 110 _prop_object_externalize_append_char(ctx, '>') == FALSE) 111 return (FALSE); 112 113 return (TRUE); 114 } 115 116 /* 117 * _prop_object_externalize_end_tag -- 118 * Append an XML-style end tag to the externalize buffer. 119 */ 120 boolean_t 121 _prop_object_externalize_end_tag( 122 struct _prop_object_externalize_context *ctx, const char *tag) 123 { 124 125 if (_prop_object_externalize_append_char(ctx, '<') == FALSE || 126 _prop_object_externalize_append_char(ctx, '/') == FALSE || 127 _prop_object_externalize_append_cstring(ctx, tag) == FALSE || 128 _prop_object_externalize_append_char(ctx, '>') == FALSE || 129 _prop_object_externalize_append_char(ctx, '\n') == FALSE) 130 return (FALSE); 131 132 return (TRUE); 133 } 134 135 /* 136 * _prop_object_externalize_empty_tag -- 137 * Append an XML-style empty tag to the externalize buffer. 138 */ 139 boolean_t 140 _prop_object_externalize_empty_tag( 141 struct _prop_object_externalize_context *ctx, const char *tag) 142 { 143 unsigned int i; 144 145 for (i = 0; i < ctx->poec_depth; i++) { 146 if (_prop_object_externalize_append_char(ctx, '\t') == FALSE) 147 return (FALSE); 148 } 149 150 if (_prop_object_externalize_append_char(ctx, '<') == FALSE || 151 _prop_object_externalize_append_cstring(ctx, tag) == FALSE || 152 _prop_object_externalize_append_char(ctx, '/') == FALSE || 153 _prop_object_externalize_append_char(ctx, '>') == FALSE || 154 _prop_object_externalize_append_char(ctx, '\n') == FALSE) 155 return (FALSE); 156 157 return (TRUE); 158 } 159 160 /* 161 * _prop_object_externalize_append_cstring -- 162 * Append a C string to the externalize buffer. 163 */ 164 boolean_t 165 _prop_object_externalize_append_cstring( 166 struct _prop_object_externalize_context *ctx, const char *cp) 167 { 168 169 while (*cp != '\0') { 170 if (_prop_object_externalize_append_char(ctx, 171 (unsigned char) *cp) == FALSE) 172 return (FALSE); 173 cp++; 174 } 175 176 return (TRUE); 177 } 178 179 /* 180 * _prop_object_externalize_append_encoded_cstring -- 181 * Append an encoded C string to the externalize buffer. 182 */ 183 boolean_t 184 _prop_object_externalize_append_encoded_cstring( 185 struct _prop_object_externalize_context *ctx, const char *cp) 186 { 187 188 while (*cp != '\0') { 189 switch (*cp) { 190 case '<': 191 if (_prop_object_externalize_append_cstring(ctx, 192 "<") == FALSE) 193 return (FALSE); 194 break; 195 case '>': 196 if (_prop_object_externalize_append_cstring(ctx, 197 ">") == FALSE) 198 return (FALSE); 199 break; 200 case '&': 201 if (_prop_object_externalize_append_cstring(ctx, 202 "&") == FALSE) 203 return (FALSE); 204 break; 205 default: 206 if (_prop_object_externalize_append_char(ctx, 207 (unsigned char) *cp) == FALSE) 208 return (FALSE); 209 break; 210 } 211 cp++; 212 } 213 214 return (TRUE); 215 } 216 217 #define BUF_EXPAND 256 218 219 /* 220 * _prop_object_externalize_append_char -- 221 * Append a single character to the externalize buffer. 222 */ 223 boolean_t 224 _prop_object_externalize_append_char( 225 struct _prop_object_externalize_context *ctx, unsigned char c) 226 { 227 228 _PROP_ASSERT(ctx->poec_capacity != 0); 229 _PROP_ASSERT(ctx->poec_buf != NULL); 230 _PROP_ASSERT(ctx->poec_len <= ctx->poec_capacity); 231 232 if (ctx->poec_len == ctx->poec_capacity) { 233 char *cp = _PROP_REALLOC(ctx->poec_buf, 234 ctx->poec_capacity + BUF_EXPAND, 235 M_TEMP); 236 if (cp == NULL) 237 return (FALSE); 238 ctx->poec_capacity = ctx->poec_capacity + BUF_EXPAND; 239 ctx->poec_buf = cp; 240 } 241 242 ctx->poec_buf[ctx->poec_len++] = c; 243 244 return (TRUE); 245 } 246 247 /* 248 * _prop_object_externalize_context_alloc -- 249 * Allocate an externalize context. 250 */ 251 struct _prop_object_externalize_context * 252 _prop_object_externalize_context_alloc(void) 253 { 254 struct _prop_object_externalize_context *ctx; 255 256 ctx = _PROP_MALLOC(sizeof(*ctx), M_TEMP); 257 if (ctx != NULL) { 258 ctx->poec_buf = _PROP_MALLOC(BUF_EXPAND, M_TEMP); 259 if (ctx->poec_buf == NULL) { 260 _PROP_FREE(ctx, M_TEMP); 261 return (NULL); 262 } 263 ctx->poec_len = 0; 264 ctx->poec_capacity = BUF_EXPAND; 265 ctx->poec_depth = 0; 266 } 267 return (ctx); 268 } 269 270 /* 271 * _prop_object_externalize_context_free -- 272 * Free an externalize context. 273 */ 274 void 275 _prop_object_externalize_context_free( 276 struct _prop_object_externalize_context *ctx) 277 { 278 279 /* Buffer is always freed by the caller. */ 280 _PROP_FREE(ctx, M_TEMP); 281 } 282 283 /* 284 * _prop_object_internalize_skip_comment -- 285 * Skip the body and end tag of a comment. 286 */ 287 static boolean_t 288 _prop_object_internalize_skip_comment( 289 struct _prop_object_internalize_context *ctx) 290 { 291 const char *cp = ctx->poic_cp; 292 293 while (!_PROP_EOF(*cp)) { 294 if (cp[0] == '-' && 295 cp[1] == '-' && 296 cp[2] == '>') { 297 ctx->poic_cp = cp + 3; 298 return (TRUE); 299 } 300 cp++; 301 } 302 303 return (FALSE); /* ran out of buffer */ 304 } 305 306 /* 307 * _prop_object_internalize_find_tag -- 308 * Find the next tag in an XML stream. Optionally compare the found 309 * tag to an expected tag name. State of the context is undefined 310 * if this routine returns FALSE. Upon success, the context points 311 * to the first octet after the tag. 312 */ 313 boolean_t 314 _prop_object_internalize_find_tag(struct _prop_object_internalize_context *ctx, 315 const char *tag, _prop_tag_type_t type) 316 { 317 const char *cp; 318 size_t taglen; 319 320 if (tag != NULL) 321 taglen = strlen(tag); 322 else 323 taglen = 0; 324 325 start_over: 326 cp = ctx->poic_cp; 327 328 /* 329 * Find the start of the tag. 330 */ 331 while (_PROP_ISSPACE(*cp)) 332 cp++; 333 if (_PROP_EOF(*cp)) 334 return (FALSE); 335 336 if (*cp != '<') 337 return (FALSE); 338 339 ctx->poic_tag_start = cp++; 340 if (_PROP_EOF(*cp)) 341 return (FALSE); 342 343 if (*cp == '!') { 344 if (cp[1] != '-' || cp[2] != '-') 345 return (FALSE); 346 /* 347 * Comment block -- only allowed if we are allowed to 348 * return a start tag. 349 */ 350 if (type == _PROP_TAG_TYPE_END) 351 return (FALSE); 352 ctx->poic_cp = cp + 3; 353 if (_prop_object_internalize_skip_comment(ctx) == FALSE) 354 return (FALSE); 355 goto start_over; 356 } 357 358 if (*cp == '/') { 359 if (type != _PROP_TAG_TYPE_END && 360 type != _PROP_TAG_TYPE_EITHER) 361 return (FALSE); 362 cp++; 363 if (_PROP_EOF(*cp)) 364 return (FALSE); 365 ctx->poic_tag_type = _PROP_TAG_TYPE_END; 366 } else { 367 if (type != _PROP_TAG_TYPE_START && 368 type != _PROP_TAG_TYPE_EITHER) 369 return (FALSE); 370 ctx->poic_tag_type = _PROP_TAG_TYPE_START; 371 } 372 373 ctx->poic_tagname = cp; 374 375 while (!_PROP_ISSPACE(*cp) && *cp != '/' && *cp != '>') 376 cp++; 377 if (_PROP_EOF(*cp)) 378 return (FALSE); 379 380 ctx->poic_tagname_len = cp - ctx->poic_tagname; 381 382 /* Make sure this is the tag we're looking for. */ 383 if (tag != NULL && 384 (taglen != ctx->poic_tagname_len || 385 memcmp(tag, ctx->poic_tagname, taglen) != 0)) 386 return (FALSE); 387 388 /* Check for empty tag. */ 389 if (*cp == '/') { 390 if (ctx->poic_tag_type != _PROP_TAG_TYPE_START) 391 return(FALSE); /* only valid on start tags */ 392 ctx->poic_is_empty_element = TRUE; 393 cp++; 394 if (_PROP_EOF(*cp) || *cp != '>') 395 return (FALSE); 396 } else 397 ctx->poic_is_empty_element = FALSE; 398 399 /* Easy case of no arguments. */ 400 if (*cp == '>') { 401 ctx->poic_tagattr = NULL; 402 ctx->poic_tagattr_len = 0; 403 ctx->poic_tagattrval = NULL; 404 ctx->poic_tagattrval_len = 0; 405 ctx->poic_cp = cp + 1; 406 return (TRUE); 407 } 408 409 _PROP_ASSERT(!_PROP_EOF(*cp)); 410 cp++; 411 if (_PROP_EOF(*cp)) 412 return (FALSE); 413 414 while (_PROP_ISSPACE(*cp)) 415 cp++; 416 if (_PROP_EOF(*cp)) 417 return (FALSE); 418 419 ctx->poic_tagattr = cp; 420 421 while (!_PROP_ISSPACE(*cp) && *cp != '=') 422 cp++; 423 if (_PROP_EOF(*cp)) 424 return (FALSE); 425 426 ctx->poic_tagattr_len = cp - ctx->poic_tagattr; 427 428 cp++; 429 if (*cp != '\"') 430 return (FALSE); 431 cp++; 432 if (_PROP_EOF(*cp)) 433 return (FALSE); 434 435 ctx->poic_tagattrval = cp; 436 while (*cp != '\"') 437 cp++; 438 if (_PROP_EOF(*cp)) 439 return (FALSE); 440 ctx->poic_tagattrval_len = cp - ctx->poic_tagattrval; 441 442 cp++; 443 if (*cp != '>') 444 return (FALSE); 445 446 ctx->poic_cp = cp + 1; 447 return (TRUE); 448 } 449 450 /* 451 * _prop_object_internalize_decode_string -- 452 * Decode an encoded string. 453 */ 454 boolean_t 455 _prop_object_internalize_decode_string( 456 struct _prop_object_internalize_context *ctx, 457 char *target, size_t targsize, size_t *sizep, 458 const char **cpp) 459 { 460 const char *src; 461 size_t tarindex; 462 char c; 463 464 tarindex = 0; 465 src = ctx->poic_cp; 466 467 for (;;) { 468 if (_PROP_EOF(*src)) 469 return (FALSE); 470 if (*src == '<') { 471 break; 472 } 473 474 if ((c = *src) == '&') { 475 if (src[1] == 'a' && 476 src[2] == 'm' && 477 src[3] == 'p' && 478 src[4] == ';') { 479 c = '&'; 480 src += 5; 481 } else if (src[1] == 'l' && 482 src[2] == 't' && 483 src[3] == ';') { 484 c = '<'; 485 src += 4; 486 } else if (src[1] == 'g' && 487 src[2] == 't' && 488 src[3] == ';') { 489 c = '>'; 490 src += 4; 491 } else if (src[1] == 'a' && 492 src[2] == 'p' && 493 src[3] == 'o' && 494 src[4] == 's' && 495 src[5] == ';') { 496 c = '\''; 497 src += 6; 498 } else if (src[1] == 'q' && 499 src[2] == 'u' && 500 src[3] == 'o' && 501 src[4] == 't' && 502 src[5] == ';') { 503 c = '\"'; 504 src += 6; 505 } else 506 return (FALSE); 507 } else 508 src++; 509 if (target) { 510 if (tarindex >= targsize) 511 return (FALSE); 512 target[tarindex] = c; 513 } 514 tarindex++; 515 } 516 517 _PROP_ASSERT(*src == '<'); 518 if (sizep != NULL) 519 *sizep = tarindex; 520 if (cpp != NULL) 521 *cpp = src; 522 523 return (TRUE); 524 } 525 526 /* 527 * _prop_object_internalize_match -- 528 * Returns true if the two character streams match. 529 */ 530 boolean_t 531 _prop_object_internalize_match(const char *str1, size_t len1, 532 const char *str2, size_t len2) 533 { 534 535 return (len1 == len2 && memcmp(str1, str2, len1) == 0); 536 } 537 538 #define INTERNALIZER(t, f) \ 539 { t, sizeof(t) - 1, f } 540 541 static const struct _prop_object_internalizer { 542 const char *poi_tag; 543 size_t poi_taglen; 544 prop_object_t (*poi_intern)( 545 struct _prop_object_internalize_context *); 546 } _prop_object_internalizer_table[] = { 547 INTERNALIZER("array", _prop_array_internalize), 548 549 INTERNALIZER("true", _prop_bool_internalize), 550 INTERNALIZER("false", _prop_bool_internalize), 551 552 INTERNALIZER("data", _prop_data_internalize), 553 554 INTERNALIZER("dict", _prop_dictionary_internalize), 555 556 INTERNALIZER("integer", _prop_number_internalize), 557 558 INTERNALIZER("string", _prop_string_internalize), 559 560 { 0 } 561 }; 562 563 #undef INTERNALIZER 564 565 /* 566 * _prop_object_internalize_by_tag -- 567 * Determine the object type from the tag in the context and 568 * internalize it. 569 */ 570 prop_object_t 571 _prop_object_internalize_by_tag(struct _prop_object_internalize_context *ctx) 572 { 573 const struct _prop_object_internalizer *poi; 574 575 for (poi = _prop_object_internalizer_table; 576 poi->poi_tag != NULL; poi++) { 577 if (_prop_object_internalize_match(ctx->poic_tagname, 578 ctx->poic_tagname_len, 579 poi->poi_tag, 580 poi->poi_taglen)) 581 return ((*poi->poi_intern)(ctx)); 582 } 583 584 return (NULL); 585 } 586 587 /* 588 * _prop_object_internalize_context_alloc -- 589 * Allocate an internalize context. 590 */ 591 struct _prop_object_internalize_context * 592 _prop_object_internalize_context_alloc(const char *xml) 593 { 594 struct _prop_object_internalize_context *ctx; 595 596 ctx = _PROP_MALLOC(sizeof(struct _prop_object_internalize_context), 597 M_TEMP); 598 if (ctx == NULL) 599 return (NULL); 600 601 ctx->poic_xml = ctx->poic_cp = xml; 602 603 /* 604 * Skip any whitespace and XML preamble stuff that we don't 605 * know about / care about. 606 */ 607 for (;;) { 608 while (_PROP_ISSPACE(*xml)) 609 xml++; 610 if (_PROP_EOF(*xml) || *xml != '<') 611 goto bad; 612 613 #define MATCH(str) (memcmp(&xml[1], str, sizeof(str) - 1) == 0) 614 615 /* 616 * Skip over the XML preamble that Apple XML property 617 * lists usually include at the top of the file. 618 */ 619 if (MATCH("?xml ") || 620 MATCH("!DOCTYPE plist")) { 621 while (*xml != '>' && !_PROP_EOF(*xml)) 622 xml++; 623 if (_PROP_EOF(*xml)) 624 goto bad; 625 xml++; /* advance past the '>' */ 626 continue; 627 } 628 629 if (MATCH("<!--")) { 630 ctx->poic_cp = xml + 4; 631 if (_prop_object_internalize_skip_comment(ctx) == FALSE) 632 goto bad; 633 xml = ctx->poic_cp; 634 continue; 635 } 636 637 #undef MATCH 638 639 /* 640 * We don't think we should skip it, so let's hope we can 641 * parse it. 642 */ 643 break; 644 } 645 646 ctx->poic_cp = xml; 647 return (ctx); 648 bad: 649 _PROP_FREE(ctx, M_TEMP); 650 return (NULL); 651 } 652 653 /* 654 * _prop_object_internalize_context_free -- 655 * Free an internalize context. 656 */ 657 void 658 _prop_object_internalize_context_free( 659 struct _prop_object_internalize_context *ctx) 660 { 661 662 _PROP_FREE(ctx, M_TEMP); 663 } 664 665 /* 666 * Retain / release serialization -- 667 * 668 * Eventually we would like to use atomic operations. But until we have 669 * an MI API for them that is common to userland and the kernel, we will 670 * use a lock instead. 671 * 672 * We use a single global mutex for all serialization. In the kernel, because 673 * we are still under a biglock, this will basically never contend (properties 674 * cannot be manipulated at interrupt level). In userland, this will cost 675 * nothing for single-threaded programs. For multi-threaded programs, there 676 * could be contention, but it probably won't cost that much unless the program 677 * makes heavy use of property lists. 678 */ 679 _PROP_MUTEX_DECL(_prop_refcnt_mutex) 680 #define _PROP_REFCNT_LOCK() _PROP_MUTEX_LOCK(_prop_refcnt_mutex) 681 #define _PROP_REFCNT_UNLOCK() _PROP_MUTEX_UNLOCK(_prop_refcnt_mutex) 682 683 /* 684 * prop_object_retain -- 685 * Increment the reference count on an object. 686 */ 687 void 688 prop_object_retain(prop_object_t obj) 689 { 690 struct _prop_object *po = obj; 691 uint32_t ocnt; 692 693 _PROP_REFCNT_LOCK(); 694 ocnt = po->po_refcnt++; 695 _PROP_REFCNT_UNLOCK(); 696 697 _PROP_ASSERT(ocnt != 0xffffffffU); 698 } 699 700 /* 701 * prop_object_release -- 702 * Decrement the reference count on an object. 703 * 704 * Free the object if we are releasing the final 705 * reference. 706 */ 707 void 708 prop_object_release(prop_object_t obj) 709 { 710 struct _prop_object *po = obj; 711 uint32_t ocnt; 712 713 _PROP_REFCNT_LOCK(); 714 ocnt = po->po_refcnt--; 715 _PROP_REFCNT_UNLOCK(); 716 717 _PROP_ASSERT(ocnt != 0); 718 if (ocnt == 1) 719 (*po->po_type->pot_free)(po); 720 } 721 722 /* 723 * prop_object_type -- 724 * Return the type of an object. 725 */ 726 prop_type_t 727 prop_object_type(prop_object_t obj) 728 { 729 struct _prop_object *po = obj; 730 731 return (po->po_type->pot_type); 732 } 733 734 /* 735 * prop_object_equals -- 736 * Returns TRUE if thw two objects are equivalent. 737 */ 738 boolean_t 739 prop_object_equals(prop_object_t obj1, prop_object_t obj2) 740 { 741 struct _prop_object *po1 = obj1; 742 struct _prop_object *po2 = obj2; 743 744 if (po1->po_type != po2->po_type) 745 return (FALSE); 746 747 return ((*po1->po_type->pot_equals)(obj1, obj2)); 748 } 749 750 /* 751 * prop_object_iterator_next -- 752 * Return the next item during an iteration. 753 */ 754 prop_object_t 755 prop_object_iterator_next(prop_object_iterator_t pi) 756 { 757 758 return ((*pi->pi_next_object)(pi)); 759 } 760 761 /* 762 * prop_object_iterator_reset -- 763 * Reset the iterator to the first object so as to restart 764 * iteration. 765 */ 766 void 767 prop_object_iterator_reset(prop_object_iterator_t pi) 768 { 769 770 (*pi->pi_reset)(pi); 771 } 772 773 /* 774 * prop_object_iterator_release -- 775 * Release the object iterator. 776 */ 777 void 778 prop_object_iterator_release(prop_object_iterator_t pi) 779 { 780 781 prop_object_release(pi->pi_obj); 782 _PROP_FREE(pi, M_TEMP); 783 } 784