Home | History | Annotate | Line # | Download | only in libprop
prop_object.c revision 1.13
      1 /*	$NetBSD: prop_object.c,v 1.13 2007/07/16 19:20:17 joerg 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 #if !defined(_KERNEL) && !defined(_STANDALONE)
     43 #include <sys/mman.h>
     44 #include <sys/stat.h>
     45 #include <errno.h>
     46 #include <fcntl.h>
     47 #include <limits.h>
     48 #include <unistd.h>
     49 #endif
     50 
     51 #ifdef _STANDALONE
     52 void *
     53 _prop_standalone_calloc(size_t size)
     54 {
     55 	void *rv;
     56 
     57 	rv = alloc(size);
     58 	if (rv != NULL)
     59 		memset(rv, 0, size);
     60 
     61 	return (rv);
     62 }
     63 
     64 void *
     65 _prop_standalone_realloc(void *v, size_t size)
     66 {
     67 	void *rv;
     68 
     69 	rv = alloc(size);
     70 	if (rv != NULL) {
     71 		memcpy(rv, v, size);	/* XXX */
     72 		dealloc(v, 0);		/* XXX */
     73 	}
     74 
     75 	return (rv);
     76 }
     77 #endif /* _STANDALONE */
     78 
     79 /*
     80  * _prop_object_init --
     81  *	Initialize an object.  Called when sub-classes create
     82  *	an instance.
     83  */
     84 void
     85 _prop_object_init(struct _prop_object *po, const struct _prop_object_type *pot)
     86 {
     87 
     88 	po->po_type = pot;
     89 	po->po_refcnt = 1;
     90 }
     91 
     92 /*
     93  * _prop_object_fini --
     94  *	Finalize an object.  Called when sub-classes destroy
     95  *	an instance.
     96  */
     97 /*ARGSUSED*/
     98 void
     99 _prop_object_fini(struct _prop_object *po _PROP_ARG_UNUSED)
    100 {
    101 	/* Nothing to do, currently. */
    102 }
    103 
    104 /*
    105  * _prop_object_externalize_start_tag --
    106  *	Append an XML-style start tag to the externalize buffer.
    107  */
    108 boolean_t
    109 _prop_object_externalize_start_tag(
    110     struct _prop_object_externalize_context *ctx, const char *tag)
    111 {
    112 	unsigned int i;
    113 
    114 	for (i = 0; i < ctx->poec_depth; i++) {
    115 		if (_prop_object_externalize_append_char(ctx, '\t') == FALSE)
    116 			return (FALSE);
    117 	}
    118 	if (_prop_object_externalize_append_char(ctx, '<') == FALSE ||
    119 	    _prop_object_externalize_append_cstring(ctx, tag) == FALSE ||
    120 	    _prop_object_externalize_append_char(ctx, '>') == FALSE)
    121 		return (FALSE);
    122 
    123 	return (TRUE);
    124 }
    125 
    126 /*
    127  * _prop_object_externalize_end_tag --
    128  *	Append an XML-style end tag to the externalize buffer.
    129  */
    130 boolean_t
    131 _prop_object_externalize_end_tag(
    132     struct _prop_object_externalize_context *ctx, const char *tag)
    133 {
    134 
    135 	if (_prop_object_externalize_append_char(ctx, '<') == FALSE ||
    136 	    _prop_object_externalize_append_char(ctx, '/') == FALSE ||
    137 	    _prop_object_externalize_append_cstring(ctx, tag) == FALSE ||
    138 	    _prop_object_externalize_append_char(ctx, '>') == FALSE ||
    139 	    _prop_object_externalize_append_char(ctx, '\n') == FALSE)
    140 		return (FALSE);
    141 
    142 	return (TRUE);
    143 }
    144 
    145 /*
    146  * _prop_object_externalize_empty_tag --
    147  *	Append an XML-style empty tag to the externalize buffer.
    148  */
    149 boolean_t
    150 _prop_object_externalize_empty_tag(
    151     struct _prop_object_externalize_context *ctx, const char *tag)
    152 {
    153 	unsigned int i;
    154 
    155 	for (i = 0; i < ctx->poec_depth; i++) {
    156 		if (_prop_object_externalize_append_char(ctx, '\t') == FALSE)
    157 			return (FALSE);
    158 	}
    159 
    160 	if (_prop_object_externalize_append_char(ctx, '<') == FALSE ||
    161 	    _prop_object_externalize_append_cstring(ctx, tag) == FALSE ||
    162 	    _prop_object_externalize_append_char(ctx, '/') == FALSE ||
    163 	    _prop_object_externalize_append_char(ctx, '>') == FALSE ||
    164 	    _prop_object_externalize_append_char(ctx, '\n') == FALSE)
    165 	    	return (FALSE);
    166 
    167 	return (TRUE);
    168 }
    169 
    170 /*
    171  * _prop_object_externalize_append_cstring --
    172  *	Append a C string to the externalize buffer.
    173  */
    174 boolean_t
    175 _prop_object_externalize_append_cstring(
    176     struct _prop_object_externalize_context *ctx, const char *cp)
    177 {
    178 
    179 	while (*cp != '\0') {
    180 		if (_prop_object_externalize_append_char(ctx,
    181 						(unsigned char) *cp) == FALSE)
    182 			return (FALSE);
    183 		cp++;
    184 	}
    185 
    186 	return (TRUE);
    187 }
    188 
    189 /*
    190  * _prop_object_externalize_append_encoded_cstring --
    191  *	Append an encoded C string to the externalize buffer.
    192  */
    193 boolean_t
    194 _prop_object_externalize_append_encoded_cstring(
    195     struct _prop_object_externalize_context *ctx, const char *cp)
    196 {
    197 
    198 	while (*cp != '\0') {
    199 		switch (*cp) {
    200 		case '<':
    201 			if (_prop_object_externalize_append_cstring(ctx,
    202 					"&lt;") == FALSE)
    203 				return (FALSE);
    204 			break;
    205 		case '>':
    206 			if (_prop_object_externalize_append_cstring(ctx,
    207 					"&gt;") == FALSE)
    208 				return (FALSE);
    209 			break;
    210 		case '&':
    211 			if (_prop_object_externalize_append_cstring(ctx,
    212 					"&amp;") == FALSE)
    213 				return (FALSE);
    214 			break;
    215 		default:
    216 			if (_prop_object_externalize_append_char(ctx,
    217 					(unsigned char) *cp) == FALSE)
    218 				return (FALSE);
    219 			break;
    220 		}
    221 		cp++;
    222 	}
    223 
    224 	return (TRUE);
    225 }
    226 
    227 #define	BUF_EXPAND		256
    228 
    229 /*
    230  * _prop_object_externalize_append_char --
    231  *	Append a single character to the externalize buffer.
    232  */
    233 boolean_t
    234 _prop_object_externalize_append_char(
    235     struct _prop_object_externalize_context *ctx, unsigned char c)
    236 {
    237 
    238 	_PROP_ASSERT(ctx->poec_capacity != 0);
    239 	_PROP_ASSERT(ctx->poec_buf != NULL);
    240 	_PROP_ASSERT(ctx->poec_len <= ctx->poec_capacity);
    241 
    242 	if (ctx->poec_len == ctx->poec_capacity) {
    243 		char *cp = _PROP_REALLOC(ctx->poec_buf,
    244 					 ctx->poec_capacity + BUF_EXPAND,
    245 					 M_TEMP);
    246 		if (cp == NULL)
    247 			return (FALSE);
    248 		ctx->poec_capacity = ctx->poec_capacity + BUF_EXPAND;
    249 		ctx->poec_buf = cp;
    250 	}
    251 
    252 	ctx->poec_buf[ctx->poec_len++] = c;
    253 
    254 	return (TRUE);
    255 }
    256 
    257 /*
    258  * _prop_object_externalize_header --
    259  *	Append the standard XML header to the externalize buffer.
    260  */
    261 boolean_t
    262 _prop_object_externalize_header(struct _prop_object_externalize_context *ctx)
    263 {
    264 	static const char _plist_xml_header[] =
    265 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
    266 "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n";
    267 
    268 	if (_prop_object_externalize_append_cstring(ctx,
    269 						 _plist_xml_header) == FALSE ||
    270 	    _prop_object_externalize_start_tag(ctx,
    271 				       "plist version=\"1.0\"") == FALSE ||
    272 	    _prop_object_externalize_append_char(ctx, '\n') == FALSE)
    273 		return (FALSE);
    274 
    275 	return (TRUE);
    276 }
    277 
    278 /*
    279  * _prop_object_externalize_footer --
    280  *	Append the standard XML footer to the externalize buffer.  This
    281  *	also NUL-terminates the buffer.
    282  */
    283 boolean_t
    284 _prop_object_externalize_footer(struct _prop_object_externalize_context *ctx)
    285 {
    286 
    287 	if (_prop_object_externalize_end_tag(ctx, "plist") == FALSE ||
    288 	    _prop_object_externalize_append_char(ctx, '\0') == FALSE)
    289 		return (FALSE);
    290 
    291 	return (TRUE);
    292 }
    293 
    294 /*
    295  * _prop_object_externalize_context_alloc --
    296  *	Allocate an externalize context.
    297  */
    298 struct _prop_object_externalize_context *
    299 _prop_object_externalize_context_alloc(void)
    300 {
    301 	struct _prop_object_externalize_context *ctx;
    302 
    303 	ctx = _PROP_MALLOC(sizeof(*ctx), M_TEMP);
    304 	if (ctx != NULL) {
    305 		ctx->poec_buf = _PROP_MALLOC(BUF_EXPAND, M_TEMP);
    306 		if (ctx->poec_buf == NULL) {
    307 			_PROP_FREE(ctx, M_TEMP);
    308 			return (NULL);
    309 		}
    310 		ctx->poec_len = 0;
    311 		ctx->poec_capacity = BUF_EXPAND;
    312 		ctx->poec_depth = 0;
    313 	}
    314 	return (ctx);
    315 }
    316 
    317 /*
    318  * _prop_object_externalize_context_free --
    319  *	Free an externalize context.
    320  */
    321 void
    322 _prop_object_externalize_context_free(
    323 		struct _prop_object_externalize_context *ctx)
    324 {
    325 
    326 	/* Buffer is always freed by the caller. */
    327 	_PROP_FREE(ctx, M_TEMP);
    328 }
    329 
    330 /*
    331  * _prop_object_internalize_skip_comment --
    332  *	Skip the body and end tag of a comment.
    333  */
    334 static boolean_t
    335 _prop_object_internalize_skip_comment(
    336 				struct _prop_object_internalize_context *ctx)
    337 {
    338 	const char *cp = ctx->poic_cp;
    339 
    340 	while (!_PROP_EOF(*cp)) {
    341 		if (cp[0] == '-' &&
    342 		    cp[1] == '-' &&
    343 		    cp[2] == '>') {
    344 			ctx->poic_cp = cp + 3;
    345 			return (TRUE);
    346 		}
    347 		cp++;
    348 	}
    349 
    350 	return (FALSE);		/* ran out of buffer */
    351 }
    352 
    353 /*
    354  * _prop_object_internalize_find_tag --
    355  *	Find the next tag in an XML stream.  Optionally compare the found
    356  *	tag to an expected tag name.  State of the context is undefined
    357  *	if this routine returns FALSE.  Upon success, the context points
    358  *	to the first octet after the tag.
    359  */
    360 boolean_t
    361 _prop_object_internalize_find_tag(struct _prop_object_internalize_context *ctx,
    362 		      const char *tag, _prop_tag_type_t type)
    363 {
    364 	const char *cp;
    365 	size_t taglen;
    366 
    367 	if (tag != NULL)
    368 		taglen = strlen(tag);
    369 	else
    370 		taglen = 0;
    371 
    372  start_over:
    373 	cp = ctx->poic_cp;
    374 
    375 	/*
    376 	 * Find the start of the tag.
    377 	 */
    378 	while (_PROP_ISSPACE(*cp))
    379 		cp++;
    380 	if (_PROP_EOF(*cp))
    381 		return (FALSE);
    382 
    383 	if (*cp != '<')
    384 		return (FALSE);
    385 
    386 	ctx->poic_tag_start = cp++;
    387 	if (_PROP_EOF(*cp))
    388 		return (FALSE);
    389 
    390 	if (*cp == '!') {
    391 		if (cp[1] != '-' || cp[2] != '-')
    392 			return (FALSE);
    393 		/*
    394 		 * Comment block -- only allowed if we are allowed to
    395 		 * return a start tag.
    396 		 */
    397 		if (type == _PROP_TAG_TYPE_END)
    398 			return (FALSE);
    399 		ctx->poic_cp = cp + 3;
    400 		if (_prop_object_internalize_skip_comment(ctx) == FALSE)
    401 			return (FALSE);
    402 		goto start_over;
    403 	}
    404 
    405 	if (*cp == '/') {
    406 		if (type != _PROP_TAG_TYPE_END &&
    407 		    type != _PROP_TAG_TYPE_EITHER)
    408 			return (FALSE);
    409 		cp++;
    410 		if (_PROP_EOF(*cp))
    411 			return (FALSE);
    412 		ctx->poic_tag_type = _PROP_TAG_TYPE_END;
    413 	} else {
    414 		if (type != _PROP_TAG_TYPE_START &&
    415 		    type != _PROP_TAG_TYPE_EITHER)
    416 			return (FALSE);
    417 		ctx->poic_tag_type = _PROP_TAG_TYPE_START;
    418 	}
    419 
    420 	ctx->poic_tagname = cp;
    421 
    422 	while (!_PROP_ISSPACE(*cp) && *cp != '/' && *cp != '>')
    423 		cp++;
    424 	if (_PROP_EOF(*cp))
    425 		return (FALSE);
    426 
    427 	ctx->poic_tagname_len = cp - ctx->poic_tagname;
    428 
    429 	/* Make sure this is the tag we're looking for. */
    430 	if (tag != NULL &&
    431 	    (taglen != ctx->poic_tagname_len ||
    432 	     memcmp(tag, ctx->poic_tagname, taglen) != 0))
    433 		return (FALSE);
    434 
    435 	/* Check for empty tag. */
    436 	if (*cp == '/') {
    437 		if (ctx->poic_tag_type != _PROP_TAG_TYPE_START)
    438 			return(FALSE);		/* only valid on start tags */
    439 		ctx->poic_is_empty_element = TRUE;
    440 		cp++;
    441 		if (_PROP_EOF(*cp) || *cp != '>')
    442 			return (FALSE);
    443 	} else
    444 		ctx->poic_is_empty_element = FALSE;
    445 
    446 	/* Easy case of no arguments. */
    447 	if (*cp == '>') {
    448 		ctx->poic_tagattr = NULL;
    449 		ctx->poic_tagattr_len = 0;
    450 		ctx->poic_tagattrval = NULL;
    451 		ctx->poic_tagattrval_len = 0;
    452 		ctx->poic_cp = cp + 1;
    453 		return (TRUE);
    454 	}
    455 
    456 	_PROP_ASSERT(!_PROP_EOF(*cp));
    457 	cp++;
    458 	if (_PROP_EOF(*cp))
    459 		return (FALSE);
    460 
    461 	while (_PROP_ISSPACE(*cp))
    462 		cp++;
    463 	if (_PROP_EOF(*cp))
    464 		return (FALSE);
    465 
    466 	ctx->poic_tagattr = cp;
    467 
    468 	while (!_PROP_ISSPACE(*cp) && *cp != '=')
    469 		cp++;
    470 	if (_PROP_EOF(*cp))
    471 		return (FALSE);
    472 
    473 	ctx->poic_tagattr_len = cp - ctx->poic_tagattr;
    474 
    475 	cp++;
    476 	if (*cp != '\"')
    477 		return (FALSE);
    478 	cp++;
    479 	if (_PROP_EOF(*cp))
    480 		return (FALSE);
    481 
    482 	ctx->poic_tagattrval = cp;
    483 	while (*cp != '\"')
    484 		cp++;
    485 	if (_PROP_EOF(*cp))
    486 		return (FALSE);
    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 boolean_t
    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 boolean_t
    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_t	(*poi_intern)(
    592 				struct _prop_object_internalize_context *);
    593 } _prop_object_internalizer_table[] = {
    594 	INTERNALIZER("array", _prop_array_internalize),
    595 
    596 	INTERNALIZER("true", _prop_bool_internalize),
    597 	INTERNALIZER("false", _prop_bool_internalize),
    598 
    599 	INTERNALIZER("data", _prop_data_internalize),
    600 
    601 	INTERNALIZER("dict", _prop_dictionary_internalize),
    602 
    603 	INTERNALIZER("integer", _prop_number_internalize),
    604 
    605 	INTERNALIZER("string", _prop_string_internalize),
    606 
    607 	{ 0, 0, NULL }
    608 };
    609 
    610 #undef INTERNALIZER
    611 
    612 /*
    613  * _prop_object_internalize_by_tag --
    614  *	Determine the object type from the tag in the context and
    615  *	internalize it.
    616  */
    617 prop_object_t
    618 _prop_object_internalize_by_tag(struct _prop_object_internalize_context *ctx)
    619 {
    620 	const struct _prop_object_internalizer *poi;
    621 
    622 	for (poi = _prop_object_internalizer_table;
    623 	     poi->poi_tag != NULL; poi++) {
    624 		if (_prop_object_internalize_match(ctx->poic_tagname,
    625 						   ctx->poic_tagname_len,
    626 						   poi->poi_tag,
    627 						   poi->poi_taglen))
    628 			return ((*poi->poi_intern)(ctx));
    629 	}
    630 
    631 	return (NULL);
    632 }
    633 
    634 prop_object_t
    635 _prop_generic_internalize(const char *xml, const char *master_tag)
    636 {
    637 	prop_object_t obj = NULL;
    638 	struct _prop_object_internalize_context *ctx;
    639 
    640 	ctx = _prop_object_internalize_context_alloc(xml);
    641 	if (ctx == NULL)
    642 		return (NULL);
    643 
    644 	/* We start with a <plist> tag. */
    645 	if (_prop_object_internalize_find_tag(ctx, "plist",
    646 					      _PROP_TAG_TYPE_START) == FALSE)
    647 		goto out;
    648 
    649 	/* Plist elements cannot be empty. */
    650 	if (ctx->poic_is_empty_element)
    651 		goto out;
    652 
    653 	/*
    654 	 * We don't understand any plist attributes, but Apple XML
    655 	 * property lists often have a "version" attribute.  If we
    656 	 * see that one, we simply ignore it.
    657 	 */
    658 	if (ctx->poic_tagattr != NULL &&
    659 	    !_PROP_TAGATTR_MATCH(ctx, "version"))
    660 		goto out;
    661 
    662 	/* Next we expect to see opening master_tag. */
    663 	if (_prop_object_internalize_find_tag(ctx, master_tag,
    664 					      _PROP_TAG_TYPE_START) == FALSE)
    665 		goto out;
    666 
    667 	obj = _prop_object_internalize_by_tag(ctx);
    668 	if (obj == NULL)
    669 		goto out;
    670 
    671 	/*
    672 	 * We've advanced past the closing master_tag.
    673 	 * Now we want </plist>.
    674 	 */
    675 	if (_prop_object_internalize_find_tag(ctx, "plist",
    676 					      _PROP_TAG_TYPE_END) == FALSE) {
    677 		prop_object_release(obj);
    678 		obj = NULL;
    679 	}
    680 
    681  out:
    682  	_prop_object_internalize_context_free(ctx);
    683 	return (obj);
    684 }
    685 
    686 /*
    687  * _prop_object_internalize_context_alloc --
    688  *	Allocate an internalize context.
    689  */
    690 struct _prop_object_internalize_context *
    691 _prop_object_internalize_context_alloc(const char *xml)
    692 {
    693 	struct _prop_object_internalize_context *ctx;
    694 
    695 	ctx = _PROP_MALLOC(sizeof(struct _prop_object_internalize_context),
    696 			   M_TEMP);
    697 	if (ctx == NULL)
    698 		return (NULL);
    699 
    700 	ctx->poic_xml = ctx->poic_cp = xml;
    701 
    702 	/*
    703 	 * Skip any whitespace and XML preamble stuff that we don't
    704 	 * know about / care about.
    705 	 */
    706 	for (;;) {
    707 		while (_PROP_ISSPACE(*xml))
    708 			xml++;
    709 		if (_PROP_EOF(*xml) || *xml != '<')
    710 			goto bad;
    711 
    712 #define	MATCH(str)	(memcmp(&xml[1], str, sizeof(str) - 1) == 0)
    713 
    714 		/*
    715 		 * Skip over the XML preamble that Apple XML property
    716 		 * lists usually include at the top of the file.
    717 		 */
    718 		if (MATCH("?xml ") ||
    719 		    MATCH("!DOCTYPE plist")) {
    720 			while (*xml != '>' && !_PROP_EOF(*xml))
    721 				xml++;
    722 			if (_PROP_EOF(*xml))
    723 				goto bad;
    724 			xml++;	/* advance past the '>' */
    725 			continue;
    726 		}
    727 
    728 		if (MATCH("<!--")) {
    729 			ctx->poic_cp = xml + 4;
    730 			if (_prop_object_internalize_skip_comment(ctx) == FALSE)
    731 				goto bad;
    732 			xml = ctx->poic_cp;
    733 			continue;
    734 		}
    735 
    736 #undef MATCH
    737 
    738 		/*
    739 		 * We don't think we should skip it, so let's hope we can
    740 		 * parse it.
    741 		 */
    742 		break;
    743 	}
    744 
    745 	ctx->poic_cp = xml;
    746 	return (ctx);
    747  bad:
    748 	_PROP_FREE(ctx, M_TEMP);
    749 	return (NULL);
    750 }
    751 
    752 /*
    753  * _prop_object_internalize_context_free --
    754  *	Free an internalize context.
    755  */
    756 void
    757 _prop_object_internalize_context_free(
    758 		struct _prop_object_internalize_context *ctx)
    759 {
    760 
    761 	_PROP_FREE(ctx, M_TEMP);
    762 }
    763 
    764 #if !defined(_KERNEL) && !defined(_STANDALONE)
    765 /*
    766  * _prop_object_externalize_file_dirname --
    767  *	dirname(3), basically.  We have to roll our own because the
    768  *	system dirname(3) isn't reentrant.
    769  */
    770 static void
    771 _prop_object_externalize_file_dirname(const char *path, char *result)
    772 {
    773 	const char *lastp;
    774 	size_t len;
    775 
    776 	/*
    777 	 * If `path' is a NULL pointer or points to an empty string,
    778 	 * return ".".
    779 	 */
    780 	if (path == NULL || *path == '\0')
    781 		goto singledot;
    782 
    783 	/* String trailing slashes, if any. */
    784 	lastp = path + strlen(path) - 1;
    785 	while (lastp != path && *lastp == '/')
    786 		lastp--;
    787 
    788 	/* Terminate path at the last occurrence of '/'. */
    789 	do {
    790 		if (*lastp == '/') {
    791 			/* Strip trailing slashes, if any. */
    792 			while (lastp != path && *lastp == '/')
    793 				lastp--;
    794 
    795 			/* ...and copy the result into the result buffer. */
    796 			len = (lastp - path) + 1 /* last char */;
    797 			if (len > (PATH_MAX - 1))
    798 				len = PATH_MAX - 1;
    799 
    800 			memcpy(result, path, len);
    801 			result[len] = '\0';
    802 			return;
    803 		}
    804 	} while (--lastp >= path);
    805 
    806  	/* No /'s found, return ".". */
    807  singledot:
    808 	strcpy(result, ".");
    809 }
    810 
    811 /*
    812  * _prop_object_externalize_write_file --
    813  *	Write an externalized dictionary to the specified file.
    814  *	The file is written atomically from the caller's perspective,
    815  *	and the mode set to 0666 modified by the caller's umask.
    816  */
    817 boolean_t
    818 _prop_object_externalize_write_file(const char *fname, const char *xml,
    819     size_t len)
    820 {
    821 	char tname[PATH_MAX];
    822 	int fd;
    823 	int save_errno;
    824 
    825 	if (len > SSIZE_MAX) {
    826 		errno = EFBIG;
    827 		return (FALSE);
    828 	}
    829 
    830 	/*
    831 	 * Get the directory name where the file is to be written
    832 	 * and create the temporary file.
    833 	 *
    834 	 * We don't use mkstemp() because mkstemp() always creates the
    835 	 * file with mode 0600.  We do, however, use mktemp() safely.
    836 	 */
    837  again:
    838 	_prop_object_externalize_file_dirname(fname, tname);
    839 	if (strlcat(tname, "/.plistXXXXXX", sizeof(tname)) >= sizeof(tname)) {
    840 		errno = ENAMETOOLONG;
    841 		return (FALSE);
    842 	}
    843 	if (mktemp(tname) == NULL)
    844 		return (FALSE);
    845 	if ((fd = open(tname, O_CREAT|O_RDWR|O_EXCL, 0666)) == -1) {
    846 		if (errno == EEXIST)
    847 			goto again;
    848 		return (FALSE);
    849 	}
    850 
    851 	if (write(fd, xml, len) != (ssize_t)len)
    852 		goto bad;
    853 
    854 	if (fsync(fd) == -1)
    855 		goto bad;
    856 
    857 	(void) close(fd);
    858 	fd = -1;
    859 
    860 	if (rename(tname, fname) == -1)
    861 		goto bad;
    862 
    863 	return (TRUE);
    864 
    865  bad:
    866 	save_errno = errno;
    867 	if (fd != -1)
    868 		(void) close(fd);
    869 	(void) unlink(tname);
    870 	errno = save_errno;
    871 	return (FALSE);
    872 }
    873 
    874 /*
    875  * _prop_object_internalize_map_file --
    876  *	Map a file for the purpose of internalizing it.
    877  */
    878 struct _prop_object_internalize_mapped_file *
    879 _prop_object_internalize_map_file(const char *fname)
    880 {
    881 	struct stat sb;
    882 	struct _prop_object_internalize_mapped_file *mf;
    883 	size_t pgsize = (size_t)sysconf(_SC_PAGESIZE);
    884 	size_t pgmask = pgsize - 1;
    885 	boolean_t need_guard = FALSE;
    886 	int fd;
    887 
    888 	mf = _PROP_MALLOC(sizeof(*mf), M_TEMP);
    889 	if (mf == NULL)
    890 		return (NULL);
    891 
    892 	fd = open(fname, O_RDONLY, 0400);
    893 	if (fd == -1) {
    894 		_PROP_FREE(mf, M_TEMP);
    895 		return (NULL);
    896 	}
    897 
    898 	if (fstat(fd, &sb) == -1) {
    899 		(void) close(fd);
    900 		_PROP_FREE(mf, M_TEMP);
    901 		return (NULL);
    902 	}
    903 	mf->poimf_mapsize = ((size_t)sb.st_size + pgmask) & ~pgmask;
    904 	if (mf->poimf_mapsize < sb.st_size) {
    905 		(void) close(fd);
    906 		_PROP_FREE(mf, M_TEMP);
    907 		return (NULL);
    908 	}
    909 
    910 	/*
    911 	 * If the file length is an integral number of pages, then we
    912 	 * need to map a guard page at the end in order to provide the
    913 	 * necessary NUL-termination of the buffer.
    914 	 */
    915 	if ((sb.st_size & pgmask) == 0)
    916 		need_guard = TRUE;
    917 
    918 	mf->poimf_xml = mmap(NULL, need_guard ? mf->poimf_mapsize + pgsize
    919 			    		      : mf->poimf_mapsize,
    920 			    PROT_READ, MAP_FILE|MAP_SHARED, fd, (off_t)0);
    921 	(void) close(fd);
    922 	if (mf->poimf_xml == MAP_FAILED) {
    923 		_PROP_FREE(mf, M_TEMP);
    924 		return (NULL);
    925 	}
    926 	(void) madvise(mf->poimf_xml, mf->poimf_mapsize, MADV_SEQUENTIAL);
    927 
    928 	if (need_guard) {
    929 		if (mmap(mf->poimf_xml + mf->poimf_mapsize,
    930 			 pgsize, PROT_READ,
    931 			 MAP_ANON|MAP_PRIVATE|MAP_FIXED, -1,
    932 			 (off_t)0) == MAP_FAILED) {
    933 			(void) munmap(mf->poimf_xml, mf->poimf_mapsize);
    934 			_PROP_FREE(mf, M_TEMP);
    935 			return (NULL);
    936 		}
    937 		mf->poimf_mapsize += pgsize;
    938 	}
    939 
    940 	return (mf);
    941 }
    942 
    943 /*
    944  * _prop_object_internalize_unmap_file --
    945  *	Unmap a file previously mapped for internalizing.
    946  */
    947 void
    948 _prop_object_internalize_unmap_file(
    949     struct _prop_object_internalize_mapped_file *mf)
    950 {
    951 
    952 	(void) madvise(mf->poimf_xml, mf->poimf_mapsize, MADV_DONTNEED);
    953 	(void) munmap(mf->poimf_xml, mf->poimf_mapsize);
    954 	_PROP_FREE(mf, M_TEMP);
    955 }
    956 #endif /* !_KERNEL && !_STANDALONE */
    957 
    958 /*
    959  * Retain / release serialization --
    960  *
    961  * Eventually we would like to use atomic operations.  But until we have
    962  * an MI API for them that is common to userland and the kernel, we will
    963  * use a lock instead.
    964  *
    965  * We use a single global mutex for all serialization.  In the kernel, because
    966  * we are still under a biglock, this will basically never contend (properties
    967  * cannot be manipulated at interrupt level).  In userland, this will cost
    968  * nothing for single-threaded programs.  For multi-threaded programs, there
    969  * could be contention, but it probably won't cost that much unless the program
    970  * makes heavy use of property lists.
    971  */
    972 _PROP_MUTEX_DECL_STATIC(_prop_refcnt_mutex)
    973 #define	_PROP_REFCNT_LOCK()	_PROP_MUTEX_LOCK(_prop_refcnt_mutex)
    974 #define	_PROP_REFCNT_UNLOCK()	_PROP_MUTEX_UNLOCK(_prop_refcnt_mutex)
    975 
    976 /*
    977  * prop_object_retain --
    978  *	Increment the reference count on an object.
    979  */
    980 void
    981 prop_object_retain(prop_object_t obj)
    982 {
    983 	struct _prop_object *po = obj;
    984 	uint32_t ocnt;
    985 
    986 	_PROP_REFCNT_LOCK();
    987 	ocnt = po->po_refcnt++;
    988 	_PROP_REFCNT_UNLOCK();
    989 
    990 	_PROP_ASSERT(ocnt != 0xffffffffU);
    991 }
    992 
    993 /*
    994  * prop_object_release --
    995  *	Decrement the reference count on an object.
    996  *
    997  *	Free the object if we are releasing the final
    998  *	reference.
    999  */
   1000 void
   1001 prop_object_release(prop_object_t obj)
   1002 {
   1003 	struct _prop_object *po = obj;
   1004 	uint32_t ocnt;
   1005 
   1006 	_PROP_REFCNT_LOCK();
   1007 	ocnt = po->po_refcnt--;
   1008 	_PROP_REFCNT_UNLOCK();
   1009 
   1010 	_PROP_ASSERT(ocnt != 0);
   1011 	if (ocnt == 1)
   1012 		(*po->po_type->pot_free)(po);
   1013 }
   1014 
   1015 /*
   1016  * prop_object_type --
   1017  *	Return the type of an object.
   1018  */
   1019 prop_type_t
   1020 prop_object_type(prop_object_t obj)
   1021 {
   1022 	struct _prop_object *po = obj;
   1023 
   1024 	if (obj == NULL)
   1025 		return (PROP_TYPE_UNKNOWN);
   1026 
   1027 	return (po->po_type->pot_type);
   1028 }
   1029 
   1030 /*
   1031  * prop_object_equals --
   1032  *	Returns TRUE if thw two objects are equivalent.
   1033  */
   1034 boolean_t
   1035 prop_object_equals(prop_object_t obj1, prop_object_t obj2)
   1036 {
   1037 	struct _prop_object *po1 = obj1;
   1038 	struct _prop_object *po2 = obj2;
   1039 
   1040 	if (po1->po_type != po2->po_type)
   1041 		return (FALSE);
   1042 
   1043 	return ((*po1->po_type->pot_equals)(obj1, obj2));
   1044 }
   1045 
   1046 /*
   1047  * prop_object_iterator_next --
   1048  *	Return the next item during an iteration.
   1049  */
   1050 prop_object_t
   1051 prop_object_iterator_next(prop_object_iterator_t pi)
   1052 {
   1053 
   1054 	return ((*pi->pi_next_object)(pi));
   1055 }
   1056 
   1057 /*
   1058  * prop_object_iterator_reset --
   1059  *	Reset the iterator to the first object so as to restart
   1060  *	iteration.
   1061  */
   1062 void
   1063 prop_object_iterator_reset(prop_object_iterator_t pi)
   1064 {
   1065 
   1066 	(*pi->pi_reset)(pi);
   1067 }
   1068 
   1069 /*
   1070  * prop_object_iterator_release --
   1071  *	Release the object iterator.
   1072  */
   1073 void
   1074 prop_object_iterator_release(prop_object_iterator_t pi)
   1075 {
   1076 
   1077 	prop_object_release(pi->pi_obj);
   1078 	_PROP_FREE(pi, M_TEMP);
   1079 }
   1080