Home | History | Annotate | Line # | Download | only in libprop
prop_object.c revision 1.41
      1 /*	$NetBSD: prop_object.c,v 1.41 2025/05/13 15:00:16 thorpej Exp $	*/
      2 
      3 /*-
      4  * Copyright (c) 2006, 2007, 2025 The NetBSD Foundation, Inc.
      5  * All rights reserved.
      6  *
      7  * This code is derived from software contributed to The NetBSD Foundation
      8  * by Jason R. Thorpe.
      9  *
     10  * Redistribution and use in source and binary forms, with or without
     11  * modification, are permitted provided that the following conditions
     12  * are met:
     13  * 1. Redistributions of source code must retain the above copyright
     14  *    notice, this list of conditions and the following disclaimer.
     15  * 2. Redistributions in binary form must reproduce the above copyright
     16  *    notice, this list of conditions and the following disclaimer in the
     17  *    documentation and/or other materials provided with the distribution.
     18  *
     19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     29  * POSSIBILITY OF SUCH DAMAGE.
     30  */
     31 
     32 #include "prop_object_impl.h"
     33 #include <prop/prop_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_line --
    103  *	Append the start-of-line character sequence.
    104  */
    105 bool
    106 _prop_object_externalize_start_line(
    107     struct _prop_object_externalize_context *ctx)
    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 	}
    116 	return true;
    117 }
    118 
    119 /*
    120  * _prop_object_externalize_end_line --
    121  *	Append the end-of-line character sequence.
    122  */
    123 bool
    124 _prop_object_externalize_end_line(
    125     struct _prop_object_externalize_context *ctx, const char *trailer)
    126 {
    127 	if (trailer != NULL &&
    128 	    _prop_object_externalize_append_cstring(ctx, trailer) == false) {
    129 		return false;
    130 	}
    131 	return _prop_object_externalize_append_char(ctx, '\n');
    132 }
    133 
    134 /*
    135  * _prop_object_externalize_start_tag --
    136  *	Append an item's start tag to the externalize buffer.
    137  */
    138 bool
    139 _prop_object_externalize_start_tag(
    140     struct _prop_object_externalize_context *ctx,
    141     const struct _prop_object_type_tags *tags,
    142     const char *tagattrs)
    143 {
    144 	bool rv;
    145 
    146 	_PROP_ASSERT(ctx->poec_format == PROP_FORMAT_XML ||
    147 		     ctx->poec_format == PROP_FORMAT_JSON);
    148 
    149 	switch (ctx->poec_format) {
    150 	case PROP_FORMAT_JSON:
    151 		rv = tags->json_open_tag == NULL ||
    152 		     _prop_object_externalize_append_cstring(ctx,
    153 							tags->json_open_tag);
    154 		break;
    155 
    156 	default:		/* XML */
    157 		rv = _prop_object_externalize_append_char(ctx, '<') &&
    158 		     _prop_object_externalize_append_cstring(ctx,
    159 							     tags->xml_tag) &&
    160 		     (tagattrs == NULL ||
    161 		      (_prop_object_externalize_append_char(ctx, ' ') &&
    162 		       _prop_object_externalize_append_cstring(ctx,
    163 							       tagattrs))) &&
    164 		     _prop_object_externalize_append_char(ctx, '>');
    165 		break;
    166 	}
    167 
    168 	return rv;
    169 }
    170 
    171 /*
    172  * _prop_object_externalize_end_tag --
    173  *	Append an item's end tag to the externalize buffer.
    174  */
    175 bool
    176 _prop_object_externalize_end_tag(
    177     struct _prop_object_externalize_context *ctx,
    178     const struct _prop_object_type_tags *tags)
    179 {
    180 	bool rv;
    181 
    182 	_PROP_ASSERT(ctx->poec_format == PROP_FORMAT_XML ||
    183 		     ctx->poec_format == PROP_FORMAT_JSON);
    184 
    185 	switch (ctx->poec_format) {
    186 	case PROP_FORMAT_JSON:
    187 		rv = tags->json_close_tag == NULL ||
    188 		     _prop_object_externalize_append_cstring(ctx,
    189 							tags->json_close_tag);
    190 		break;
    191 
    192 	default:		/* XML */
    193 		rv = _prop_object_externalize_append_char(ctx, '<') &&
    194 		     _prop_object_externalize_append_char(ctx, '/') &&
    195 		     _prop_object_externalize_append_cstring(ctx,
    196 							     tags->xml_tag) &&
    197 		     _prop_object_externalize_append_char(ctx, '>');
    198 		break;
    199 	}
    200 
    201 	return rv;
    202 }
    203 
    204 /*
    205  * _prop_object_externalize_empty_tag --
    206  *	Append an item's empty tag to the externalize buffer.
    207  */
    208 bool
    209 _prop_object_externalize_empty_tag(
    210     struct _prop_object_externalize_context *ctx,
    211     const struct _prop_object_type_tags *tags)
    212 {
    213 	bool rv;
    214 
    215 	_PROP_ASSERT(ctx->poec_format == PROP_FORMAT_XML ||
    216 		     ctx->poec_format == PROP_FORMAT_JSON);
    217 
    218 	switch (ctx->poec_format) {
    219 	case PROP_FORMAT_JSON:
    220 		if (tags->json_open_tag == NULL ||
    221 		    _prop_object_externalize_append_cstring(ctx,
    222 					tags->json_open_tag) == false) {
    223 			return false;
    224 		}
    225 		if (tags->json_empty_sep != NULL &&
    226 		    _prop_object_externalize_append_cstring(ctx,
    227 					tags->json_empty_sep) == false) {
    228 			return false;
    229 		}
    230 		if (tags->json_close_tag != NULL) {
    231 			rv = _prop_object_externalize_append_cstring(ctx,
    232 							tags->json_close_tag);
    233 		} else {
    234 			rv = true;
    235 		}
    236 		break;
    237 
    238 	default:		/* XML */
    239 		rv = _prop_object_externalize_append_char(ctx, '<') &&
    240 		     _prop_object_externalize_append_cstring(ctx,
    241 							     tags->xml_tag) &&
    242 		     _prop_object_externalize_append_char(ctx, '/') &&
    243 		     _prop_object_externalize_append_char(ctx, '>');
    244 		break;
    245 	}
    246 
    247 	return rv;
    248 }
    249 
    250 /*
    251  * _prop_object_externalize_append_cstring --
    252  *	Append a C string to the externalize buffer.
    253  */
    254 bool
    255 _prop_object_externalize_append_cstring(
    256     struct _prop_object_externalize_context *ctx, const char *cp)
    257 {
    258 
    259 	while (*cp != '\0') {
    260 		if (_prop_object_externalize_append_char(ctx,
    261 						(unsigned char) *cp) == false)
    262 			return (false);
    263 		cp++;
    264 	}
    265 
    266 	return (true);
    267 }
    268 
    269 /*
    270  * _prop_object_externalize_append_encoded_cstring --
    271  *	Append an encoded C string to the externalize buffer.
    272  */
    273 static bool
    274 _prop_object_externalize_append_encoded_cstring_xml(
    275     struct _prop_object_externalize_context *ctx, const char *cp)
    276 {
    277 
    278 	while (*cp != '\0') {
    279 		switch (*cp) {
    280 		case '<':
    281 			if (_prop_object_externalize_append_cstring(ctx,
    282 					"&lt;") == false)
    283 				return (false);
    284 			break;
    285 		case '>':
    286 			if (_prop_object_externalize_append_cstring(ctx,
    287 					"&gt;") == false)
    288 				return (false);
    289 			break;
    290 		case '&':
    291 			if (_prop_object_externalize_append_cstring(ctx,
    292 					"&amp;") == false)
    293 				return (false);
    294 			break;
    295 		default:
    296 			if (_prop_object_externalize_append_char(ctx,
    297 					(unsigned char) *cp) == false)
    298 				return (false);
    299 			break;
    300 		}
    301 		cp++;
    302 	}
    303 
    304 	return (true);
    305 }
    306 
    307 static bool
    308 _prop_object_externalize_append_escu(
    309     struct _prop_object_externalize_context *ctx, uint16_t val)
    310 {
    311 	char tmpstr[sizeof("\\uXXXX")];
    312 
    313 	snprintf(tmpstr, sizeof(tmpstr), "\\u%04X", val);
    314 	return _prop_object_externalize_append_cstring(ctx, tmpstr);
    315 }
    316 
    317 static bool
    318 _prop_object_externalize_append_encoded_cstring_json(
    319     struct _prop_object_externalize_context *ctx, const char *cp)
    320 {
    321 	bool esc;
    322 	unsigned char ch;
    323 
    324 	while ((ch = *cp) != '\0') {
    325 		esc = true;
    326 		switch (ch) {
    327 		/*
    328 		 * First, the two explicit exclusions.  They must be
    329 		 * escaped.
    330 		 */
    331 		case '"':	/* U+0022 quotation mark */
    332 			goto emit;
    333 
    334 		case '\\':	/* U+005C reverse solidus */
    335 			goto emit;
    336 
    337 		/*
    338 		 * And some special cases that are explcit in the grammar.
    339 		 */
    340 		case '/':	/* U+002F solidus (XXX this one seems silly) */
    341 			goto emit;
    342 
    343 		case 0x08:	/* U+0008 backspace */
    344 			ch = 'b';
    345 			goto emit;
    346 
    347 		case 0x0c:	/* U+000C form feed */
    348 			ch = 'f';
    349 			goto emit;
    350 
    351 		case 0x0a:	/* U+000A line feed */
    352 			ch = 'n';
    353 			goto emit;
    354 
    355 		case 0x0d:	/* U+000D carriage return */
    356 			ch = 'r';
    357 			goto emit;
    358 
    359 		case 0x09:	/* U+0009 tab */
    360 			ch = 't';
    361 			goto emit;
    362 
    363 		default:
    364 			/*
    365 			 * \u-escape all other single-byte ASCII control
    366 			 * characters, per RFC 8259:
    367 			 *
    368 			 * <quote>
    369 			 * All Unicode characters may be placed within the
    370 			 * quotation marks, except for the characters that
    371 			 * MUST be escaped: quotation mark, reverse solidus,
    372 			 * and the control characters (U+0000 through U+001F).
    373 			 * </quote>
    374 			 */
    375 			if (ch < 0x20) {
    376 				if (_prop_object_externalize_append_escu(ctx,
    377 							ch) == false) {
    378 					return false;
    379 				}
    380 				break;
    381 			}
    382 			/*
    383 			 * We're going to just treat everything else like
    384 			 * UTF-8 (we've been handed a C-string, after all)
    385 			 * and pretend it will all be OK.
    386 			 */
    387 			esc = false;
    388 		emit:
    389 			if ((esc && _prop_object_externalize_append_char(ctx,
    390 							'\\') == false) ||
    391 			    _prop_object_externalize_append_char(ctx,
    392 							ch) == false) {
    393 				return false;
    394 			}
    395 			break;
    396 		}
    397 		cp++;
    398 	}
    399 
    400 	return true;
    401 }
    402 
    403 bool
    404 _prop_object_externalize_append_encoded_cstring(
    405     struct _prop_object_externalize_context *ctx, const char *cp)
    406 {
    407 	_PROP_ASSERT(ctx->poec_format == PROP_FORMAT_XML ||
    408 		     ctx->poec_format == PROP_FORMAT_JSON);
    409 
    410 	switch (ctx->poec_format) {
    411 	case PROP_FORMAT_JSON:
    412 		return _prop_object_externalize_append_encoded_cstring_json(ctx,
    413 									    cp);
    414 	default:
    415 		return _prop_object_externalize_append_encoded_cstring_xml(ctx,
    416 									   cp);
    417 	}
    418 }
    419 
    420 #define	BUF_EXPAND		256
    421 
    422 /*
    423  * _prop_object_externalize_append_char --
    424  *	Append a single character to the externalize buffer.
    425  */
    426 bool
    427 _prop_object_externalize_append_char(
    428     struct _prop_object_externalize_context *ctx, unsigned char c)
    429 {
    430 
    431 	_PROP_ASSERT(ctx->poec_capacity != 0);
    432 	_PROP_ASSERT(ctx->poec_buf != NULL);
    433 	_PROP_ASSERT(ctx->poec_len <= ctx->poec_capacity);
    434 
    435 	if (ctx->poec_len == ctx->poec_capacity) {
    436 		char *cp = _PROP_REALLOC(ctx->poec_buf,
    437 					 ctx->poec_capacity + BUF_EXPAND,
    438 					 M_TEMP);
    439 		if (cp == NULL)
    440 			return (false);
    441 		ctx->poec_capacity = ctx->poec_capacity + BUF_EXPAND;
    442 		ctx->poec_buf = cp;
    443 	}
    444 
    445 	ctx->poec_buf[ctx->poec_len++] = c;
    446 
    447 	return (true);
    448 }
    449 
    450 static const struct _prop_object_type_tags _plist_type_tags = {
    451 	.xml_tag	=	"plist",
    452 };
    453 
    454 /*
    455  * _prop_object_externalize_header --
    456  *	Append the standard XML header to the externalize buffer.
    457  */
    458 bool
    459 _prop_object_externalize_header(struct _prop_object_externalize_context *ctx)
    460 {
    461 	static const char _plist_xml_header[] =
    462 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
    463 "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n";
    464 
    465 	if (ctx->poec_format != PROP_FORMAT_XML) {
    466 		return true;
    467 	}
    468 
    469 	if (_prop_object_externalize_append_cstring(ctx,
    470 						 _plist_xml_header) == false ||
    471 	    _prop_object_externalize_start_tag(ctx,
    472 	    				&_plist_type_tags,
    473 					"version=\"1.0\"") == false ||
    474 	    _prop_object_externalize_append_char(ctx, '\n') == false)
    475 		return (false);
    476 
    477 	return (true);
    478 }
    479 
    480 /*
    481  * _prop_object_externalize_footer --
    482  *	Append the standard XML footer to the externalize buffer.  This
    483  *	also NUL-terminates the buffer.
    484  */
    485 bool
    486 _prop_object_externalize_footer(struct _prop_object_externalize_context *ctx)
    487 {
    488 	if (_prop_object_externalize_end_line(ctx, NULL) == false) {
    489 		return false;
    490 	}
    491 
    492 	if (ctx->poec_format == PROP_FORMAT_XML) {
    493 		if (_prop_object_externalize_end_tag(ctx,
    494 					&_plist_type_tags) == false ||
    495 		    _prop_object_externalize_end_line(ctx, NULL) == false) {
    496 			return false;
    497 		}
    498 	}
    499 
    500 	return _prop_object_externalize_append_char(ctx, '\0');
    501 }
    502 
    503 /*
    504  * _prop_object_externalize_context_alloc --
    505  *	Allocate an externalize context.
    506  */
    507 struct _prop_object_externalize_context *
    508 _prop_object_externalize_context_alloc(prop_format_t fmt)
    509 {
    510 	struct _prop_object_externalize_context *ctx;
    511 
    512 	ctx = _PROP_MALLOC(sizeof(*ctx), M_TEMP);
    513 	if (ctx != NULL) {
    514 		ctx->poec_buf = _PROP_MALLOC(BUF_EXPAND, M_TEMP);
    515 		if (ctx->poec_buf == NULL) {
    516 			_PROP_FREE(ctx, M_TEMP);
    517 			return (NULL);
    518 		}
    519 		ctx->poec_len = 0;
    520 		ctx->poec_capacity = BUF_EXPAND;
    521 		ctx->poec_depth = 0;
    522 		ctx->poec_format = fmt;
    523 	}
    524 	return (ctx);
    525 }
    526 
    527 /*
    528  * _prop_object_externalize_context_free --
    529  *	Free an externalize context.
    530  */
    531 void
    532 _prop_object_externalize_context_free(
    533 		struct _prop_object_externalize_context *ctx)
    534 {
    535 
    536 	/* Buffer is always freed by the caller. */
    537 	_PROP_FREE(ctx, M_TEMP);
    538 }
    539 
    540 /*
    541  * _prop_object_externalize --
    542  *	Externalize an object, returning a NUL-terminated buffer
    543  *	containing the serialized data in either XML or JSON format.
    544  *	The buffer is allocated with the M_TEMP memory type.
    545  */
    546 char *
    547 _prop_object_externalize(struct _prop_object *obj, prop_format_t fmt)
    548 {
    549 	struct _prop_object_externalize_context *ctx;
    550 	char *cp = NULL;
    551 
    552 	if (obj == NULL || obj->po_type->pot_extern == NULL) {
    553 		return NULL;
    554 	}
    555 	if (fmt != PROP_FORMAT_XML && fmt != PROP_FORMAT_JSON) {
    556 		return NULL;
    557 	}
    558 
    559 	ctx = _prop_object_externalize_context_alloc(fmt);
    560 	if (ctx == NULL) {
    561 		return NULL;
    562 	}
    563 
    564 	if (_prop_object_externalize_header(ctx) == false ||
    565 	    obj->po_type->pot_extern(ctx, obj) == false ||
    566 	    _prop_object_externalize_footer(ctx) == false) {
    567 		/* We are responsible for releasing the buffer. */
    568 		_PROP_FREE(ctx->poec_buf, M_TEMP);
    569 		goto bad;
    570 	}
    571 
    572 	cp = ctx->poec_buf;
    573  bad:
    574 	_prop_object_externalize_context_free(ctx);
    575 	return cp;
    576 }
    577 
    578 /*
    579  * _prop_object_internalize_skip_comment --
    580  *	Skip the body and end tag of a comment.
    581  */
    582 static bool
    583 _prop_object_internalize_skip_comment(
    584 				struct _prop_object_internalize_context *ctx)
    585 {
    586 	const char *cp = ctx->poic_cp;
    587 
    588 	while (!_PROP_EOF(*cp)) {
    589 		if (cp[0] == '-' &&
    590 		    cp[1] == '-' &&
    591 		    cp[2] == '>') {
    592 			ctx->poic_cp = cp + 3;
    593 			return (true);
    594 		}
    595 		cp++;
    596 	}
    597 
    598 	return (false);		/* ran out of buffer */
    599 }
    600 
    601 /*
    602  * _prop_object_internalize_find_tag --
    603  *	Find the next tag in an XML stream.  Optionally compare the found
    604  *	tag to an expected tag name.  State of the context is undefined
    605  *	if this routine returns false.  Upon success, the context points
    606  *	to the first octet after the tag.
    607  */
    608 bool
    609 _prop_object_internalize_find_tag(struct _prop_object_internalize_context *ctx,
    610 		      const char *tag, _prop_tag_type_t type)
    611 {
    612 	const char *cp;
    613 	size_t taglen;
    614 
    615 	if (tag != NULL)
    616 		taglen = strlen(tag);
    617 	else
    618 		taglen = 0;
    619 
    620  start_over:
    621 	cp = ctx->poic_cp;
    622 
    623 	/*
    624 	 * Find the start of the tag.
    625 	 */
    626 	cp = _prop_object_internalize_skip_whitespace(cp);
    627 	if (*cp != '<')
    628 		return (false);
    629 
    630 	ctx->poic_tag_start = cp++;
    631 	if (_PROP_EOF(*cp))
    632 		return (false);
    633 
    634 	if (*cp == '!') {
    635 		if (cp[1] != '-' || cp[2] != '-')
    636 			return (false);
    637 		/*
    638 		 * Comment block -- only allowed if we are allowed to
    639 		 * return a start tag.
    640 		 */
    641 		if (type == _PROP_TAG_TYPE_END)
    642 			return (false);
    643 		ctx->poic_cp = cp + 3;
    644 		if (_prop_object_internalize_skip_comment(ctx) == false)
    645 			return (false);
    646 		goto start_over;
    647 	}
    648 
    649 	if (*cp == '/') {
    650 		if (type != _PROP_TAG_TYPE_END &&
    651 		    type != _PROP_TAG_TYPE_EITHER)
    652 			return (false);
    653 		cp++;
    654 		if (_PROP_EOF(*cp))
    655 			return (false);
    656 		ctx->poic_tag_type = _PROP_TAG_TYPE_END;
    657 	} else {
    658 		if (type != _PROP_TAG_TYPE_START &&
    659 		    type != _PROP_TAG_TYPE_EITHER)
    660 			return (false);
    661 		ctx->poic_tag_type = _PROP_TAG_TYPE_START;
    662 	}
    663 
    664 	ctx->poic_tagname = cp;
    665 
    666 	while (!_PROP_ISSPACE(*cp) && *cp != '/' && *cp != '>') {
    667 		if (_PROP_EOF(*cp))
    668 			return (false);
    669 		cp++;
    670 	}
    671 
    672 	ctx->poic_tagname_len = cp - ctx->poic_tagname;
    673 
    674 	/* Make sure this is the tag we're looking for. */
    675 	if (tag != NULL &&
    676 	    (taglen != ctx->poic_tagname_len ||
    677 	     memcmp(tag, ctx->poic_tagname, taglen) != 0))
    678 		return (false);
    679 
    680 	/* Check for empty tag. */
    681 	if (*cp == '/') {
    682 		if (ctx->poic_tag_type != _PROP_TAG_TYPE_START)
    683 			return(false);		/* only valid on start tags */
    684 		ctx->poic_is_empty_element = true;
    685 		cp++;
    686 		if (_PROP_EOF(*cp) || *cp != '>')
    687 			return (false);
    688 	} else
    689 		ctx->poic_is_empty_element = false;
    690 
    691 	/* Easy case of no arguments. */
    692 	if (*cp == '>') {
    693 		ctx->poic_tagattr = NULL;
    694 		ctx->poic_tagattr_len = 0;
    695 		ctx->poic_tagattrval = NULL;
    696 		ctx->poic_tagattrval_len = 0;
    697 		ctx->poic_cp = cp + 1;
    698 		return (true);
    699 	}
    700 
    701 	_PROP_ASSERT(!_PROP_EOF(*cp));
    702 	cp++;
    703 	if (_PROP_EOF(*cp))
    704 		return (false);
    705 
    706 	cp = _prop_object_internalize_skip_whitespace(cp);
    707 	if (_PROP_EOF(*cp))
    708 		return (false);
    709 
    710 	ctx->poic_tagattr = cp;
    711 
    712 	while (!_PROP_ISSPACE(*cp) && *cp != '=') {
    713 		if (_PROP_EOF(*cp))
    714 			return (false);
    715 		cp++;
    716 	}
    717 
    718 	ctx->poic_tagattr_len = cp - ctx->poic_tagattr;
    719 
    720 	cp++;
    721 	if (*cp != '\"')
    722 		return (false);
    723 	cp++;
    724 	if (_PROP_EOF(*cp))
    725 		return (false);
    726 
    727 	ctx->poic_tagattrval = cp;
    728 	while (*cp != '\"') {
    729 		if (_PROP_EOF(*cp))
    730 			return (false);
    731 		cp++;
    732 	}
    733 	ctx->poic_tagattrval_len = cp - ctx->poic_tagattrval;
    734 
    735 	cp++;
    736 	if (*cp != '>')
    737 		return (false);
    738 
    739 	ctx->poic_cp = cp + 1;
    740 	return (true);
    741 }
    742 
    743 /*
    744  * _prop_object_internalize_decode_string --
    745  *	Decode an encoded string.
    746  */
    747 
    748 #define	ADDCHAR(x)							\
    749 	do {								\
    750 		if (target) {						\
    751 			if (tarindex >= targsize) {			\
    752 				return false;				\
    753 			}						\
    754 			target[tarindex] = (x);				\
    755 		}							\
    756 		tarindex++;						\
    757 	} while (/*CONSTCOND*/0)
    758 
    759 static bool
    760 _prop_object_internalize_decode_string_xml(
    761 				struct _prop_object_internalize_context *ctx,
    762 				char *target, size_t targsize, size_t *sizep,
    763 				const char **cpp)
    764 {
    765 	const char *src;
    766 	size_t tarindex;
    767 	char c;
    768 
    769 	tarindex = 0;
    770 	src = ctx->poic_cp;
    771 
    772 	for (;;) {
    773 		if (_PROP_EOF(*src))
    774 			return (false);
    775 		if (*src == '<') {
    776 			break;
    777 		}
    778 
    779 		if ((c = *src) == '&') {
    780 			if (src[1] == 'a' &&
    781 			    src[2] == 'm' &&
    782 			    src[3] == 'p' &&
    783 			    src[4] == ';') {
    784 			    	c = '&';
    785 				src += 5;
    786 			} else if (src[1] == 'l' &&
    787 				   src[2] == 't' &&
    788 				   src[3] == ';') {
    789 				c = '<';
    790 				src += 4;
    791 			} else if (src[1] == 'g' &&
    792 				   src[2] == 't' &&
    793 				   src[3] == ';') {
    794 				c = '>';
    795 				src += 4;
    796 			} else if (src[1] == 'a' &&
    797 				   src[2] == 'p' &&
    798 				   src[3] == 'o' &&
    799 				   src[4] == 's' &&
    800 				   src[5] == ';') {
    801 				c = '\'';
    802 				src += 6;
    803 			} else if (src[1] == 'q' &&
    804 				   src[2] == 'u' &&
    805 				   src[3] == 'o' &&
    806 				   src[4] == 't' &&
    807 				   src[5] == ';') {
    808 				c = '\"';
    809 				src += 6;
    810 			} else
    811 				return (false);
    812 		} else
    813 			src++;
    814 		ADDCHAR(c);
    815 	}
    816 
    817 	_PROP_ASSERT(*src == '<');
    818 	if (sizep != NULL)
    819 		*sizep = tarindex;
    820 	if (cpp != NULL)
    821 		*cpp = src;
    822 
    823 	return (true);
    824 }
    825 
    826 static unsigned int
    827 _prop_object_decode_string_uesc_getu16(const char *src, unsigned int idx,
    828     uint16_t *valp)
    829 {
    830 	unsigned int i;
    831 	uint16_t val;
    832 	unsigned char c;
    833 
    834 	if (src[idx] != '\\' || src[idx + 1] != 'u') {
    835 		return 0;
    836 	}
    837 
    838 	for (val = 0, i = 2; i < 6; i++) {
    839 		val <<= 4;
    840 		c = src[idx + i];
    841 		if (c >= 'A' && c <= 'F') {
    842 			val |= 10 + (c - 'A');
    843 		} else if (c >= 'a' && c <= 'f') {
    844 			val |= 10 + (c - 'a');
    845 		} else if (c >= '0' && c <= '9') {
    846 			val |= c - '0';
    847 		} else {
    848 			return 0;
    849 		}
    850 	}
    851 
    852 	*valp = val;
    853 	return idx + i;
    854 }
    855 
    856 #define	HS_FIRST	0xd800
    857 #define	HS_LAST		0xdbff
    858 #define	HS_SHIFT	10
    859 #define	LS_FIRST	0xdc00
    860 #define	LS_LAST		0xdfff
    861 
    862 #define	HIGH_SURROGAGE_P(x)	\
    863 	((x) >= HS_FIRST && (x) <= HS_LAST)
    864 #define	LOW_SURROGATE_P(x)	\
    865 	((x) >= LS_FIRST && (x) <= LS_LAST)
    866 #define	SURROGATE_P(x)		\
    867 	(HIGH_SURROGAGE_P(x) || LOW_SURROGATE_P(x))
    868 
    869 static int
    870 _prop_object_decode_string_uesc(const char *src, char *c,
    871     unsigned int *cszp)
    872 {
    873 	unsigned int idx = 0;
    874 	uint32_t code;
    875 	uint16_t code16[2] = { 0, 0 };
    876 
    877 	idx = _prop_object_decode_string_uesc_getu16(src, idx, &code16[0]);
    878 	if (idx == 0) {
    879 		return 0;
    880 	}
    881 	if (! SURROGATE_P(code16[0])) {
    882 		/* Simple case: not a surrogate pair */
    883 		code = code16[0];
    884 	} else if (HIGH_SURROGAGE_P(code16[0])) {
    885 		idx = _prop_object_decode_string_uesc_getu16(src, idx,
    886 							     &code16[1]);
    887 		if (idx == 0) {
    888 			return 0;
    889 		}
    890 		/* Next code must be the low surrogate. */
    891 		if (! LOW_SURROGATE_P(code16[1])) {
    892 			return 0;
    893 		}
    894 		code = (((uint32_t)code16[0] - HS_FIRST) << HS_SHIFT) +
    895 		        (          code16[1] - LS_FIRST)              +
    896 		       0x10000;
    897 	} else {
    898 		/* Got the low surrogate first; this is an error. */
    899 		return 0;
    900 	}
    901 
    902 	/*
    903 	 * Ok, we have the code point.  Now convert it to UTF-8.
    904 	 * First we'll just split into nybbles.
    905 	 */
    906 	uint8_t u = (code >> 20) & 0xf;
    907 	uint8_t v = (code >> 16) & 0xf;
    908 	uint8_t w = (code >> 12) & 0xf;
    909 	uint8_t x = (code >>  8) & 0xf;
    910 	uint8_t y = (code >>  4) & 0xf;
    911 	uint8_t z = (code      ) & 0xf;
    912 
    913 	/*
    914 	 * ...and swizzle the nybbles accordingly.
    915 	 *
    916 	 * N.B. we expcitly disallow inserting a NUL into the string
    917 	 * by way of a \uXXXX escape.
    918 	 */
    919 	if (code == 0) {
    920 		/* Not allowed. */
    921 		return 0;
    922 	} else if (/*code >= 0x0000 &&*/ code <= 0x007f) {
    923 		c[0] = (char)code;	/* == (y << 4) | z */
    924 		*cszp = 1;
    925 	} else if (/*code >= 0x0080 &&*/ code <= 0x07ff) {
    926 		c[0] = 0xc0 | (x << 2) | (y >> 2);
    927 		c[1] = 0x80 | ((y & 3) << 4) | z;
    928 		*cszp = 2;
    929 	} else if (/*code >= 0x0800 &&*/ code <= 0xffff) {
    930 		c[0] = 0xe0 | w;
    931 		c[1] = 0x80 | (x << 2) | (y >> 2);
    932 		c[2] = 0x80 | ((y & 3) << 4) | z;
    933 		*cszp = 3;
    934 	} else if (/*code >= 0x010000 &&*/ code <= 0x10ffff) {
    935 		c[0] = 0xf0 | ((u & 1) << 2) | (v >> 2);
    936 		c[1] = 0x80 | ((v & 3) << 4) | w;
    937 		c[2] = 0x80 | (x << 2) | (y >> 2);
    938 		c[3] = 0x80 | ((y & 3) << 4) | z;
    939 		*cszp = 4;
    940 	} else {
    941 		/* Invalid code. */
    942 		return 0;
    943 	}
    944 
    945 	return idx;	/* advance input by this much */
    946 }
    947 
    948 #undef HS_FIRST
    949 #undef HS_LAST
    950 #undef LS_FIRST
    951 #undef LS_LAST
    952 #undef HIGH_SURROGAGE_P
    953 #undef LOW_SURROGATE_P
    954 #undef SURROGATE_P
    955 
    956 static bool
    957 _prop_object_internalize_decode_string_json(
    958 				struct _prop_object_internalize_context *ctx,
    959 				char *target, size_t targsize, size_t *sizep,
    960 				const char **cpp)
    961 {
    962 	const char *src;
    963 	size_t tarindex;
    964 	char c[4];
    965 	unsigned int csz;
    966 
    967 	tarindex = 0;
    968 	src = ctx->poic_cp;
    969 
    970 	for (;;) {
    971 		if (_PROP_EOF(*src)) {
    972 			return false;
    973 		}
    974 		if (*src == '"') {
    975 			break;
    976 		}
    977 
    978 		csz = 1;
    979 		if ((c[0] = *src) == '\\') {
    980 			int advance = 2;
    981 
    982 			switch ((c[0] = src[1])) {
    983 			case '"':		/* quotation mark */
    984 			case '\\':		/* reverse solidus */
    985 			case '/':		/* solidus */
    986 				/* identity mapping */
    987 				break;
    988 
    989 			case 'b':		/* backspace */
    990 				c[0] = 0x08;
    991 				break;
    992 
    993 			case 'f':		/* form feed */
    994 				c[0] = 0x0c;
    995 				break;
    996 
    997 			case 'n':		/* line feed */
    998 				c[0] = 0x0a;
    999 				break;
   1000 
   1001 			case 'r':		/* carriage return */
   1002 				c[0] = 0x0d;
   1003 				break;
   1004 
   1005 			case 't':		/* tab */
   1006 				c[0] = 0x09;
   1007 				break;
   1008 
   1009 			case 'u':
   1010 				advance = _prop_object_decode_string_uesc(
   1011 				    src, c, &csz);
   1012 				if (advance == 0) {
   1013 					return false;
   1014 				}
   1015 				break;
   1016 
   1017 			default:
   1018 				/* invalid escape */
   1019 				return false;
   1020 			}
   1021 			src += advance;
   1022 		} else {
   1023 			src++;
   1024 		}
   1025 		for (unsigned int i = 0; i < csz; i++) {
   1026 			ADDCHAR(c[i]);
   1027 		}
   1028 	}
   1029 
   1030 	_PROP_ASSERT(*src == '"');
   1031 	if (sizep != NULL) {
   1032 		*sizep = tarindex;
   1033 	}
   1034 	if (cpp != NULL) {
   1035 		*cpp = src;
   1036 	}
   1037 
   1038 	return true;
   1039 }
   1040 
   1041 #undef ADDCHAR
   1042 
   1043 bool
   1044 _prop_object_internalize_decode_string(
   1045 				struct _prop_object_internalize_context *ctx,
   1046 				char *target, size_t targsize, size_t *sizep,
   1047 				const char **cpp)
   1048 {
   1049 	_PROP_ASSERT(ctx->poic_format == PROP_FORMAT_XML ||
   1050 		     ctx->poic_format == PROP_FORMAT_JSON);
   1051 
   1052 	switch (ctx->poic_format) {
   1053 	case PROP_FORMAT_JSON:
   1054 		return _prop_object_internalize_decode_string_json(ctx,
   1055 		    target, targsize, sizep, cpp);
   1056 
   1057 	default:		/* XML */
   1058 		return _prop_object_internalize_decode_string_xml(ctx,
   1059 		    target, targsize, sizep, cpp);
   1060 	}
   1061 }
   1062 
   1063 /*
   1064  * _prop_object_internalize_skip_whitespace --
   1065  *	Skip a span of whitespace.
   1066  */
   1067 const char *
   1068 _prop_object_internalize_skip_whitespace(const char *cp)
   1069 {
   1070 	while (_PROP_ISSPACE(*cp)) {
   1071 		cp++;
   1072 	}
   1073 	return cp;
   1074 }
   1075 
   1076 /*
   1077  * _prop_object_internalize_match --
   1078  *	Returns true if the two character streams match.
   1079  */
   1080 bool
   1081 _prop_object_internalize_match(const char *str1, size_t len1,
   1082 			       const char *str2, size_t len2)
   1083 {
   1084 
   1085 	return (len1 == len2 && memcmp(str1, str2, len1) == 0);
   1086 }
   1087 
   1088 #define	INTERNALIZER(t, f)			\
   1089 {	t,	sizeof(t) - 1,		f	}
   1090 
   1091 static const struct _prop_object_internalizer {
   1092 	const char			*poi_tag;
   1093 	size_t				poi_taglen;
   1094 	prop_object_internalizer_t	poi_intern;
   1095 } _prop_object_internalizer_table[] = {
   1096 	INTERNALIZER("array", _prop_array_internalize),
   1097 
   1098 	INTERNALIZER("true", _prop_bool_internalize),
   1099 	INTERNALIZER("false", _prop_bool_internalize),
   1100 
   1101 	INTERNALIZER("data", _prop_data_internalize),
   1102 
   1103 	INTERNALIZER("dict", _prop_dictionary_internalize),
   1104 
   1105 	INTERNALIZER("integer", _prop_number_internalize),
   1106 
   1107 	INTERNALIZER("string", _prop_string_internalize),
   1108 
   1109 	{ 0, 0, NULL }
   1110 };
   1111 
   1112 #undef INTERNALIZER
   1113 
   1114 /*
   1115  * _prop_object_internalize_by_tag --
   1116  *	Determine the object type from the tag in the context and
   1117  *	internalize it.
   1118  */
   1119 static prop_object_t
   1120 _prop_object_internalize_by_tag(struct _prop_object_internalize_context *ctx)
   1121 {
   1122 	const struct _prop_object_internalizer *poi;
   1123 	prop_object_t obj, parent_obj;
   1124 	void *data, *iter;
   1125 	prop_object_internalizer_continue_t iter_func;
   1126 	struct _prop_stack stack;
   1127 
   1128 	_prop_stack_init(&stack);
   1129 
   1130  match_start:
   1131 	for (poi = _prop_object_internalizer_table;
   1132 	     poi->poi_tag != NULL; poi++) {
   1133 		if (_prop_object_internalize_match(ctx->poic_tagname,
   1134 						   ctx->poic_tagname_len,
   1135 						   poi->poi_tag,
   1136 						   poi->poi_taglen))
   1137 			break;
   1138 	}
   1139 	if ((poi == NULL) || (poi->poi_tag == NULL)) {
   1140 		while (_prop_stack_pop(&stack, &obj, &iter, &data, NULL)) {
   1141 			iter_func = (prop_object_internalizer_continue_t)iter;
   1142 			(*iter_func)(&stack, &obj, ctx, data, NULL);
   1143 		}
   1144 
   1145 		return (NULL);
   1146 	}
   1147 
   1148 	obj = NULL;
   1149 	if (!(*poi->poi_intern)(&stack, &obj, ctx))
   1150 		goto match_start;
   1151 
   1152 	parent_obj = obj;
   1153 	while (_prop_stack_pop(&stack, &parent_obj, &iter, &data, NULL)) {
   1154 		iter_func = (prop_object_internalizer_continue_t)iter;
   1155 		if (!(*iter_func)(&stack, &parent_obj, ctx, data, obj))
   1156 			goto match_start;
   1157 		obj = parent_obj;
   1158 	}
   1159 
   1160 	return (parent_obj);
   1161 }
   1162 
   1163 /*
   1164  * _prop_object_internalize_xml --
   1165  *	Internalize a property list from XML data.
   1166  */
   1167 static prop_object_t
   1168 _prop_object_internalize_xml(struct _prop_object_internalize_context *ctx,
   1169     const struct _prop_object_type_tags *initial_tag)
   1170 {
   1171 	prop_object_t obj = NULL;
   1172 
   1173 	/* We start with a <plist> tag. */
   1174 	if (_prop_object_internalize_find_tag(ctx, "plist",
   1175 					      _PROP_TAG_TYPE_START) == false)
   1176 		goto out;
   1177 
   1178 	/* Plist elements cannot be empty. */
   1179 	if (ctx->poic_is_empty_element)
   1180 		goto out;
   1181 
   1182 	/*
   1183 	 * We don't understand any plist attributes, but Apple XML
   1184 	 * property lists often have a "version" attribute.  If we
   1185 	 * see that one, we simply ignore it.
   1186 	 */
   1187 	if (ctx->poic_tagattr != NULL &&
   1188 	    !_PROP_TAGATTR_MATCH(ctx, "version"))
   1189 		goto out;
   1190 
   1191 	/* Next we expect to see opening master_tag. */
   1192 	if (_prop_object_internalize_find_tag(ctx,
   1193 				initial_tag != NULL ? initial_tag->xml_tag
   1194 			      			    : NULL,
   1195 				_PROP_TAG_TYPE_START) == false)
   1196 		goto out;
   1197 
   1198 	obj = _prop_object_internalize_by_tag(ctx);
   1199 	if (obj == NULL)
   1200 		goto out;
   1201 
   1202 	/*
   1203 	 * We've advanced past the closing main tag.
   1204 	 * Now we want </plist>.
   1205 	 */
   1206 	if (_prop_object_internalize_find_tag(ctx, "plist",
   1207 					      _PROP_TAG_TYPE_END) == false) {
   1208 		prop_object_release(obj);
   1209 		obj = NULL;
   1210 	}
   1211 
   1212  out:
   1213 	return (obj);
   1214 }
   1215 
   1216 /*
   1217  * _prop_object_internalize_json --
   1218  *	Internalize a property list from JSON data.
   1219  */
   1220 static prop_object_t
   1221 _prop_object_internalize_json(struct _prop_object_internalize_context *ctx,
   1222     const struct _prop_object_type_tags *initial_tag __unused)
   1223 {
   1224 	prop_object_t obj, parent_obj;
   1225 	void *data, *iter;
   1226 	prop_object_internalizer_continue_t iter_func;
   1227 	struct _prop_stack stack;
   1228 	bool (*intern)(prop_stack_t, prop_object_t *,
   1229 		       struct _prop_object_internalize_context *);
   1230 
   1231 	_prop_stack_init(&stack);
   1232 
   1233  match_start:
   1234 	intern = NULL;
   1235 	ctx->poic_tagname = ctx->poic_tagattr = ctx->poic_tagattrval = NULL;
   1236 	ctx->poic_tagname_len = ctx->poic_tagattr_len =
   1237 	    ctx->poic_tagattrval_len = 0;
   1238 	ctx->poic_is_empty_element = false;
   1239 	ctx->poic_cp = _prop_object_internalize_skip_whitespace(ctx->poic_cp);
   1240 	switch (ctx->poic_cp[0]) {
   1241 	case '{':
   1242 		ctx->poic_cp++;
   1243 		intern = _prop_dictionary_internalize;
   1244 		break;
   1245 
   1246 	case '[':
   1247 		ctx->poic_cp++;
   1248 		intern = _prop_array_internalize;
   1249 		break;
   1250 
   1251 	case '"':
   1252 		ctx->poic_cp++;
   1253 		/* XXX Slightly gross. */
   1254 		if (*ctx->poic_cp == '"') {
   1255 			ctx->poic_cp++;
   1256 			ctx->poic_is_empty_element = true;
   1257 		}
   1258 		intern = _prop_string_internalize;
   1259 		break;
   1260 
   1261 	case 't':
   1262 		if (ctx->poic_cp[1] == 'r' &&
   1263 		    ctx->poic_cp[2] == 'u' &&
   1264 		    ctx->poic_cp[3] == 'e') {
   1265 			/* XXX Slightly gross. */
   1266 			ctx->poic_tagname = ctx->poic_cp;
   1267 			ctx->poic_tagname_len = 4;
   1268 			ctx->poic_is_empty_element = true;
   1269 			intern = _prop_bool_internalize;
   1270 			ctx->poic_cp += 4;
   1271 		}
   1272 		break;
   1273 
   1274 	case 'f':
   1275 		if (ctx->poic_cp[1] == 'a' &&
   1276 		    ctx->poic_cp[2] == 'l' &&
   1277 		    ctx->poic_cp[3] == 's' &&
   1278 		    ctx->poic_cp[4] == 'e') {
   1279 			/* XXX Slightly gross. */
   1280 			ctx->poic_tagname = ctx->poic_cp;
   1281 			ctx->poic_tagname_len = 5;
   1282 			ctx->poic_is_empty_element = true;
   1283 			intern = _prop_bool_internalize;
   1284 			ctx->poic_cp += 5;
   1285 		}
   1286 		break;
   1287 
   1288 	default:
   1289 		if (ctx->poic_cp[0] == '+' ||
   1290 		    ctx->poic_cp[0] == '-' ||
   1291 		    (ctx->poic_cp[0] >= '0' && ctx->poic_cp[0] <= '9')) {
   1292 			intern = _prop_number_internalize;
   1293 		}
   1294 		break;
   1295 	}
   1296 
   1297 	if (intern == NULL) {
   1298 		while (_prop_stack_pop(&stack, &obj, &iter, &data, NULL)) {
   1299 			iter_func = (prop_object_internalizer_continue_t)iter;
   1300 			(*iter_func)(&stack, &obj, ctx, data, NULL);
   1301 		}
   1302 		return NULL;
   1303 	}
   1304 
   1305 	obj = NULL;
   1306 	if ((*intern)(&stack, &obj, ctx) == false) {
   1307 		goto match_start;
   1308 	}
   1309 
   1310 	parent_obj = obj;
   1311 	while (_prop_stack_pop(&stack, &parent_obj, &iter, &data, NULL)) {
   1312 		iter_func = (prop_object_internalizer_continue_t)iter;
   1313 		if ((*iter_func)(&stack, &parent_obj, ctx, data,
   1314 							obj) == false) {
   1315 			goto match_start;
   1316 		}
   1317 		obj = parent_obj;
   1318 	}
   1319 
   1320 	/* Ensure there's no trailing junk. */
   1321 	if (parent_obj != NULL) {
   1322 		ctx->poic_cp =
   1323 		    _prop_object_internalize_skip_whitespace(ctx->poic_cp);
   1324 		if (!_PROP_EOF(*ctx->poic_cp)) {
   1325 			prop_object_release(parent_obj);
   1326 			parent_obj = NULL;
   1327 		}
   1328 	}
   1329 	return parent_obj;
   1330 }
   1331 
   1332 /*
   1333  * _prop_object_internalize --
   1334  *	Internalize a property list from a NUL-terminated data blob.
   1335  */
   1336 prop_object_t
   1337 _prop_object_internalize(const char *data,
   1338     const struct _prop_object_type_tags *initial_tag)
   1339 {
   1340 	struct _prop_object_internalize_context *ctx;
   1341 	prop_object_t obj;
   1342 	prop_format_t fmt;
   1343 
   1344 	/*
   1345 	 * Skip all whitespace until and look at the first
   1346 	 * non-whitespace character to determine the format:
   1347 	 * An XML plist will always have '<' as the first non-ws
   1348 	 * character.  If we encounter something else, we assume
   1349 	 * it is JSON.
   1350 	 */
   1351 	data = _prop_object_internalize_skip_whitespace(data);
   1352 	if (_PROP_EOF(*data)) {
   1353 		return NULL;
   1354 	}
   1355 
   1356 	fmt = *data == '<' ? PROP_FORMAT_XML : PROP_FORMAT_JSON;
   1357 
   1358 	ctx = _prop_object_internalize_context_alloc(data, fmt);
   1359 	if (ctx == NULL) {
   1360 		return NULL;
   1361 	}
   1362 
   1363 	if (fmt == PROP_FORMAT_XML) {
   1364 		obj = _prop_object_internalize_xml(ctx, initial_tag);
   1365 	} else {
   1366 		obj = _prop_object_internalize_json(ctx, initial_tag);
   1367 	}
   1368 
   1369  	_prop_object_internalize_context_free(ctx);
   1370 	return (obj);
   1371 }
   1372 
   1373 _PROP_EXPORT prop_object_t
   1374 prop_object_internalize(const char *data)
   1375 {
   1376 	return _prop_object_internalize(data, NULL);
   1377 }
   1378 
   1379 /*
   1380  * _prop_object_internalize_context_alloc --
   1381  *	Allocate an internalize context.
   1382  */
   1383 struct _prop_object_internalize_context *
   1384 _prop_object_internalize_context_alloc(const char *data, prop_format_t fmt)
   1385 {
   1386 	struct _prop_object_internalize_context *ctx;
   1387 
   1388 	ctx = _PROP_MALLOC(sizeof(*ctx), M_TEMP);
   1389 	if (ctx == NULL)
   1390 		return (NULL);
   1391 
   1392 	ctx->poic_format = fmt;
   1393 	ctx->poic_data = ctx->poic_cp = data;
   1394 
   1395 	/*
   1396 	 * If we're digesting JSON, check for a byte order mark and
   1397 	 * skip it, if present.  We should never see one, but we're
   1398 	 * allowed to detect and ignore it.  (RFC 8259 section 8.1)
   1399 	 */
   1400 	if (fmt == PROP_FORMAT_JSON) {
   1401 		if (((unsigned char)data[0] == 0xff &&
   1402 		     (unsigned char)data[1] == 0xfe) ||
   1403 		    ((unsigned char)data[0] == 0xfe &&
   1404 		     (unsigned char)data[1] == 0xff)) {
   1405 			ctx->poic_cp = data + 2;
   1406 		}
   1407 
   1408 		/* No additional processing work to do for JSON. */
   1409 		return ctx;
   1410 	}
   1411 
   1412 	/*
   1413 	 * Skip any whitespace and XML preamble stuff that we don't
   1414 	 * know about / care about.
   1415 	 */
   1416 	for (;;) {
   1417 		data = _prop_object_internalize_skip_whitespace(data);
   1418 		if (_PROP_EOF(*data) || *data != '<')
   1419 			goto bad;
   1420 
   1421 #define	MATCH(str)	(strncmp(&data[1], str, strlen(str)) == 0)
   1422 
   1423 		/*
   1424 		 * Skip over the XML preamble that Apple XML property
   1425 		 * lists usually include at the top of the file.
   1426 		 */
   1427 		if (MATCH("?xml ") ||
   1428 		    MATCH("!DOCTYPE plist")) {
   1429 			while (*data != '>' && !_PROP_EOF(*data))
   1430 				data++;
   1431 			if (_PROP_EOF(*data))
   1432 				goto bad;
   1433 			data++;	/* advance past the '>' */
   1434 			continue;
   1435 		}
   1436 
   1437 		if (MATCH("<!--")) {
   1438 			ctx->poic_cp = data + 4;
   1439 			if (_prop_object_internalize_skip_comment(ctx) == false)
   1440 				goto bad;
   1441 			data = ctx->poic_cp;
   1442 			continue;
   1443 		}
   1444 
   1445 #undef MATCH
   1446 
   1447 		/*
   1448 		 * We don't think we should skip it, so let's hope we can
   1449 		 * parse it.
   1450 		 */
   1451 		break;
   1452 	}
   1453 
   1454 	ctx->poic_cp = data;
   1455 	return (ctx);
   1456  bad:
   1457 	_PROP_FREE(ctx, M_TEMP);
   1458 	return (NULL);
   1459 }
   1460 
   1461 /*
   1462  * _prop_object_internalize_context_free --
   1463  *	Free an internalize context.
   1464  */
   1465 void
   1466 _prop_object_internalize_context_free(
   1467 		struct _prop_object_internalize_context *ctx)
   1468 {
   1469 
   1470 	_PROP_FREE(ctx, M_TEMP);
   1471 }
   1472 
   1473 #if !defined(_KERNEL) && !defined(_STANDALONE)
   1474 /*
   1475  * _prop_object_externalize_file_dirname --
   1476  *	dirname(3), basically.  We have to roll our own because the
   1477  *	system dirname(3) isn't reentrant.
   1478  */
   1479 static void
   1480 _prop_object_externalize_file_dirname(const char *path, char *result)
   1481 {
   1482 	const char *lastp;
   1483 	size_t len;
   1484 
   1485 	/*
   1486 	 * If `path' is a NULL pointer or points to an empty string,
   1487 	 * return ".".
   1488 	 */
   1489 	if (path == NULL || *path == '\0')
   1490 		goto singledot;
   1491 
   1492 	/* String trailing slashes, if any. */
   1493 	lastp = path + strlen(path) - 1;
   1494 	while (lastp != path && *lastp == '/')
   1495 		lastp--;
   1496 
   1497 	/* Terminate path at the last occurrence of '/'. */
   1498 	do {
   1499 		if (*lastp == '/') {
   1500 			/* Strip trailing slashes, if any. */
   1501 			while (lastp != path && *lastp == '/')
   1502 				lastp--;
   1503 
   1504 			/* ...and copy the result into the result buffer. */
   1505 			len = (lastp - path) + 1 /* last char */;
   1506 			if (len > (PATH_MAX - 1))
   1507 				len = PATH_MAX - 1;
   1508 
   1509 			memcpy(result, path, len);
   1510 			result[len] = '\0';
   1511 			return;
   1512 		}
   1513 	} while (--lastp >= path);
   1514 
   1515  	/* No /'s found, return ".". */
   1516  singledot:
   1517 	strcpy(result, ".");
   1518 }
   1519 
   1520 /*
   1521  * _prop_object_externalize_write_file --
   1522  *	Write an externalized dictionary to the specified file.
   1523  *	The file is written atomically from the caller's perspective,
   1524  *	and the mode set to 0666 modified by the caller's umask.
   1525  */
   1526 static bool
   1527 _prop_object_externalize_write_file(const char *fname, const char *data,
   1528     size_t len)
   1529 {
   1530 	char tname_store[PATH_MAX];
   1531 	char *tname = NULL;
   1532 	int fd = -1;
   1533 	int save_errno;
   1534 	mode_t myumask;
   1535 	bool rv = false;
   1536 
   1537 	if (len > SSIZE_MAX) {
   1538 		errno = EFBIG;
   1539 		return false;
   1540 	}
   1541 
   1542 	/*
   1543 	 * Get the directory name where the file is to be written
   1544 	 * and create the temporary file.
   1545 	 */
   1546 	_prop_object_externalize_file_dirname(fname, tname_store);
   1547 #define PLISTTMP "/.plistXXXXXX"
   1548 	if (strlen(tname_store) + strlen(PLISTTMP) >= sizeof(tname_store)) {
   1549 		errno = ENAMETOOLONG;
   1550 		return false;
   1551 	}
   1552 	strcat(tname_store, PLISTTMP);
   1553 #undef PLISTTMP
   1554 
   1555 	if ((fd = mkstemp(tname_store)) == -1) {
   1556 		return (false);
   1557 	}
   1558 	tname = tname_store;
   1559 
   1560 	if (write(fd, data, len) != (ssize_t)len) {
   1561 		goto bad;
   1562 	}
   1563 
   1564 	if (fsync(fd) == -1) {
   1565 		goto bad;
   1566 	}
   1567 
   1568 	myumask = umask(0);
   1569 	(void)umask(myumask);
   1570 	if (fchmod(fd, 0666 & ~myumask) == -1) {
   1571 		goto bad;
   1572 	}
   1573 
   1574 	if (rename(tname, fname) == -1) {
   1575 		goto bad;
   1576 	}
   1577 	tname = NULL;
   1578 
   1579 	rv = true;
   1580 
   1581  bad:
   1582 	save_errno = errno;
   1583 	if (fd != -1) {
   1584 		(void) close(fd);
   1585 	}
   1586 	if (tname != NULL) {
   1587 		(void) unlink(tname);
   1588 	}
   1589 	errno = save_errno;
   1590 	return rv;
   1591 }
   1592 
   1593 /*
   1594  * _prop_object_externalize_to_file --
   1595  *	Externalize an object to the specified file.
   1596  */
   1597 bool
   1598 _prop_object_externalize_to_file(struct _prop_object *obj, const char *fname,
   1599     prop_format_t fmt)
   1600 {
   1601 	char *data = _prop_object_externalize(obj, fmt);
   1602 	if (data == NULL) {
   1603 		return false;
   1604 	}
   1605 	bool rv = _prop_object_externalize_write_file(fname, data,
   1606 	    strlen(data));
   1607 	int save_errno = errno;
   1608 	_PROP_FREE(data, M_TEMP);
   1609 	errno = save_errno;
   1610 
   1611 	return rv;
   1612 }
   1613 
   1614 struct _prop_object_internalize_mapped_file {
   1615 	char *	poimf_xml;
   1616 	size_t	poimf_mapsize;
   1617 };
   1618 
   1619 /*
   1620  * _prop_object_internalize_map_file --
   1621  *	Map a file for the purpose of internalizing it.
   1622  */
   1623 static struct _prop_object_internalize_mapped_file *
   1624 _prop_object_internalize_map_file(const char *fname)
   1625 {
   1626 	struct stat sb;
   1627 	struct _prop_object_internalize_mapped_file *mf;
   1628 	size_t pgsize = (size_t)sysconf(_SC_PAGESIZE);
   1629 	size_t pgmask = pgsize - 1;
   1630 	bool need_guard = false;
   1631 	int fd;
   1632 
   1633 	mf = _PROP_MALLOC(sizeof(*mf), M_TEMP);
   1634 	if (mf == NULL)
   1635 		return (NULL);
   1636 
   1637 	fd = open(fname, O_RDONLY, 0400);
   1638 	if (fd == -1) {
   1639 		_PROP_FREE(mf, M_TEMP);
   1640 		return (NULL);
   1641 	}
   1642 
   1643 	if (fstat(fd, &sb) == -1) {
   1644 		(void) close(fd);
   1645 		_PROP_FREE(mf, M_TEMP);
   1646 		return (NULL);
   1647 	}
   1648 	mf->poimf_mapsize = ((size_t)sb.st_size + pgmask) & ~pgmask;
   1649 	if (mf->poimf_mapsize < (size_t)sb.st_size) {
   1650 		(void) close(fd);
   1651 		_PROP_FREE(mf, M_TEMP);
   1652 		return (NULL);
   1653 	}
   1654 
   1655 	/*
   1656 	 * If the file length is an integral number of pages, then we
   1657 	 * need to map a guard page at the end in order to provide the
   1658 	 * necessary NUL-termination of the buffer.
   1659 	 */
   1660 	if ((sb.st_size & pgmask) == 0)
   1661 		need_guard = true;
   1662 
   1663 	mf->poimf_xml = mmap(NULL, need_guard ? mf->poimf_mapsize + pgsize
   1664 			    		      : mf->poimf_mapsize,
   1665 			    PROT_READ, MAP_FILE|MAP_SHARED, fd, (off_t)0);
   1666 	(void) close(fd);
   1667 	if (mf->poimf_xml == MAP_FAILED) {
   1668 		_PROP_FREE(mf, M_TEMP);
   1669 		return (NULL);
   1670 	}
   1671 #ifdef POSIX_MADV_SEQUENTIAL
   1672 	(void) posix_madvise(mf->poimf_xml, mf->poimf_mapsize,
   1673 	    POSIX_MADV_SEQUENTIAL);
   1674 #endif
   1675 
   1676 	if (need_guard) {
   1677 		if (mmap(mf->poimf_xml + mf->poimf_mapsize,
   1678 			 pgsize, PROT_READ,
   1679 			 MAP_ANON|MAP_PRIVATE|MAP_FIXED, -1,
   1680 			 (off_t)0) == MAP_FAILED) {
   1681 			(void) munmap(mf->poimf_xml, mf->poimf_mapsize);
   1682 			_PROP_FREE(mf, M_TEMP);
   1683 			return (NULL);
   1684 		}
   1685 		mf->poimf_mapsize += pgsize;
   1686 	}
   1687 
   1688 	return (mf);
   1689 }
   1690 
   1691 /*
   1692  * _prop_object_internalize_unmap_file --
   1693  *	Unmap a file previously mapped for internalizing.
   1694  */
   1695 static void
   1696 _prop_object_internalize_unmap_file(
   1697     struct _prop_object_internalize_mapped_file *mf)
   1698 {
   1699 
   1700 #ifdef POSIX_MADV_DONTNEED
   1701 	(void) posix_madvise(mf->poimf_xml, mf->poimf_mapsize,
   1702 	    POSIX_MADV_DONTNEED);
   1703 #endif
   1704 	(void) munmap(mf->poimf_xml, mf->poimf_mapsize);
   1705 	_PROP_FREE(mf, M_TEMP);
   1706 }
   1707 
   1708 /*
   1709  * _prop_object_internalize_from_file --
   1710  *	Internalize a property list from a file.
   1711  */
   1712 prop_object_t
   1713 _prop_object_internalize_from_file(const char *fname,
   1714     const struct _prop_object_type_tags *initial_tag)
   1715 {
   1716 	struct _prop_object_internalize_mapped_file *mf;
   1717 	prop_object_t obj;
   1718 
   1719 	mf = _prop_object_internalize_map_file(fname);
   1720 	if (mf == NULL) {
   1721 		return NULL;
   1722 	}
   1723 	obj = _prop_object_internalize(mf->poimf_xml, initial_tag);
   1724 	_prop_object_internalize_unmap_file(mf);
   1725 
   1726 	return obj;
   1727 }
   1728 
   1729 _PROP_EXPORT prop_object_t
   1730 prop_object_internalize_from_file(const char *fname)
   1731 {
   1732 	return _prop_object_internalize_from_file(fname, NULL);
   1733 }
   1734 #endif /* !_KERNEL && !_STANDALONE */
   1735 
   1736 static prop_format_t	_prop_format_default = PROP_FORMAT_XML;
   1737 
   1738 /*
   1739  * prop_object_externalize --
   1740  *	Externalize an object in the default format.
   1741  */
   1742 _PROP_EXPORT char *
   1743 prop_object_externalize(prop_object_t po)
   1744 {
   1745 	return _prop_object_externalize((struct _prop_object *)po,
   1746 	    _prop_format_default);
   1747 }
   1748 
   1749 /*
   1750  * prop_object_externalize_with_format --
   1751  *	Externalize an object in the specified format.
   1752  */
   1753 _PROP_EXPORT char *
   1754 prop_object_externalize_with_format(prop_object_t po, prop_format_t fmt)
   1755 {
   1756 	return _prop_object_externalize((struct _prop_object *)po, fmt);
   1757 }
   1758 
   1759 #if !defined(_KERNEL) && !defined(_STANDALONE)
   1760 /*
   1761  * prop_object_externalize_to_file --
   1762  *	Externalize an object to the specifed file in the default format.
   1763  */
   1764 _PROP_EXPORT bool
   1765 prop_object_externalize_to_file(prop_object_t po, const char *fname)
   1766 {
   1767 	return _prop_object_externalize_to_file((struct _prop_object *)po,
   1768 	    fname, _prop_format_default);
   1769 }
   1770 
   1771 /*
   1772  * prop_object_externalize_to_file_with_format --
   1773  *	Externalize an object to the specifed file in the specified format.
   1774  */
   1775 _PROP_EXPORT bool
   1776 prop_object_externalize_to_file_with_format(prop_object_t po,
   1777     const char *fname, prop_format_t fmt)
   1778 {
   1779 	return _prop_object_externalize_to_file((struct _prop_object *)po,
   1780 	    fname, fmt);
   1781 }
   1782 #endif /* !_KERNEL && !_STANDALONE */
   1783 
   1784 /*
   1785  * prop_object_retain --
   1786  *	Increment the reference count on an object.
   1787  */
   1788 _PROP_EXPORT void
   1789 prop_object_retain(prop_object_t obj)
   1790 {
   1791 	struct _prop_object *po = obj;
   1792 	uint32_t ncnt __unused;
   1793 
   1794 	_PROP_ATOMIC_INC32_NV(&po->po_refcnt, ncnt);
   1795 	_PROP_ASSERT(ncnt != 0);
   1796 }
   1797 
   1798 /*
   1799  * prop_object_release_emergency
   1800  *	A direct free with prop_object_release failed.
   1801  *	Walk down the tree until a leaf is found and
   1802  *	free that. Do not recurse to avoid stack overflows.
   1803  *
   1804  *	This is a slow edge condition, but necessary to
   1805  *	guarantee that an object can always be freed.
   1806  */
   1807 static void
   1808 prop_object_release_emergency(prop_object_t obj)
   1809 {
   1810 	struct _prop_object *po;
   1811 	void (*unlock)(void);
   1812 	prop_object_t parent = NULL;
   1813 	uint32_t ocnt;
   1814 
   1815 	for (;;) {
   1816 		po = obj;
   1817 		_PROP_ASSERT(obj);
   1818 
   1819 		if (po->po_type->pot_lock != NULL)
   1820 		po->po_type->pot_lock();
   1821 
   1822 		/* Save pointerto unlock function */
   1823 		unlock = po->po_type->pot_unlock;
   1824 
   1825 		/* Dance a bit to make sure we always get the non-racy ocnt */
   1826 		_PROP_ATOMIC_DEC32_NV(&po->po_refcnt, ocnt);
   1827 		ocnt++;
   1828 		_PROP_ASSERT(ocnt != 0);
   1829 
   1830 		if (ocnt != 1) {
   1831 			if (unlock != NULL)
   1832 				unlock();
   1833 			break;
   1834 		}
   1835 
   1836 		_PROP_ASSERT(po->po_type);
   1837 		if ((po->po_type->pot_free)(NULL, &obj) ==
   1838 		    _PROP_OBJECT_FREE_DONE) {
   1839 			if (unlock != NULL)
   1840 				unlock();
   1841 			break;
   1842 		}
   1843 
   1844 		if (unlock != NULL)
   1845 			unlock();
   1846 
   1847 		parent = po;
   1848 		_PROP_ATOMIC_INC32(&po->po_refcnt);
   1849 	}
   1850 	_PROP_ASSERT(parent);
   1851 	/* One object was just freed. */
   1852 	po = parent;
   1853 	(*po->po_type->pot_emergency_free)(parent);
   1854 }
   1855 
   1856 /*
   1857  * prop_object_release --
   1858  *	Decrement the reference count on an object.
   1859  *
   1860  *	Free the object if we are releasing the final
   1861  *	reference.
   1862  */
   1863 _PROP_EXPORT void
   1864 prop_object_release(prop_object_t obj)
   1865 {
   1866 	struct _prop_object *po;
   1867 	struct _prop_stack stack;
   1868 	void (*unlock)(void);
   1869 	int ret;
   1870 	uint32_t ocnt;
   1871 
   1872 	_prop_stack_init(&stack);
   1873 
   1874 	do {
   1875 		do {
   1876 			po = obj;
   1877 			_PROP_ASSERT(obj);
   1878 
   1879 			if (po->po_type->pot_lock != NULL)
   1880 				po->po_type->pot_lock();
   1881 
   1882 			/* Save pointer to object unlock function */
   1883 			unlock = po->po_type->pot_unlock;
   1884 
   1885 			_PROP_ATOMIC_DEC32_NV(&po->po_refcnt, ocnt);
   1886 			ocnt++;
   1887 			_PROP_ASSERT(ocnt != 0);
   1888 
   1889 			if (ocnt != 1) {
   1890 				ret = 0;
   1891 				if (unlock != NULL)
   1892 					unlock();
   1893 				break;
   1894 			}
   1895 
   1896 			ret = (po->po_type->pot_free)(&stack, &obj);
   1897 
   1898 			if (unlock != NULL)
   1899 				unlock();
   1900 
   1901 			if (ret == _PROP_OBJECT_FREE_DONE)
   1902 				break;
   1903 
   1904 			_PROP_ATOMIC_INC32(&po->po_refcnt);
   1905 		} while (ret == _PROP_OBJECT_FREE_RECURSE);
   1906 		if (ret == _PROP_OBJECT_FREE_FAILED)
   1907 			prop_object_release_emergency(obj);
   1908 	} while (_prop_stack_pop(&stack, &obj, NULL, NULL, NULL));
   1909 }
   1910 
   1911 /*
   1912  * prop_object_type --
   1913  *	Return the type of an object.
   1914  */
   1915 _PROP_EXPORT prop_type_t
   1916 prop_object_type(prop_object_t obj)
   1917 {
   1918 	struct _prop_object *po = obj;
   1919 
   1920 	if (obj == NULL)
   1921 		return (PROP_TYPE_UNKNOWN);
   1922 
   1923 	return (po->po_type->pot_type);
   1924 }
   1925 
   1926 /*
   1927  * prop_object_equals --
   1928  *	Returns true if thw two objects are equivalent.
   1929  */
   1930 _PROP_EXPORT bool
   1931 prop_object_equals(prop_object_t obj1, prop_object_t obj2)
   1932 {
   1933 	return (prop_object_equals_with_error(obj1, obj2, NULL));
   1934 }
   1935 
   1936 _PROP_EXPORT bool
   1937 prop_object_equals_with_error(prop_object_t obj1, prop_object_t obj2,
   1938     bool *error_flag)
   1939 {
   1940 	struct _prop_object *po1;
   1941 	struct _prop_object *po2;
   1942 	void *stored_pointer1, *stored_pointer2;
   1943 	prop_object_t next_obj1, next_obj2;
   1944 	struct _prop_stack stack;
   1945 	_prop_object_equals_rv_t ret;
   1946 
   1947 	_prop_stack_init(&stack);
   1948 	if (error_flag)
   1949 		*error_flag = false;
   1950 
   1951  start_subtree:
   1952 	stored_pointer1 = NULL;
   1953 	stored_pointer2 = NULL;
   1954 	po1 = obj1;
   1955 	po2 = obj2;
   1956 
   1957 	if (po1->po_type != po2->po_type)
   1958 		return (false);
   1959 
   1960  continue_subtree:
   1961 	ret = (*po1->po_type->pot_equals)(obj1, obj2,
   1962 					  &stored_pointer1, &stored_pointer2,
   1963 					  &next_obj1, &next_obj2);
   1964 	if (ret == _PROP_OBJECT_EQUALS_FALSE)
   1965 		goto finish;
   1966 	if (ret == _PROP_OBJECT_EQUALS_TRUE) {
   1967 		if (!_prop_stack_pop(&stack, &obj1, &obj2,
   1968 				     &stored_pointer1, &stored_pointer2))
   1969 			return true;
   1970 		po1 = obj1;
   1971 		po2 = obj2;
   1972 		goto continue_subtree;
   1973 	}
   1974 	_PROP_ASSERT(ret == _PROP_OBJECT_EQUALS_RECURSE);
   1975 
   1976 	if (!_prop_stack_push(&stack, obj1, obj2,
   1977 			      stored_pointer1, stored_pointer2)) {
   1978 		if (error_flag)
   1979 			*error_flag = true;
   1980 		goto finish;
   1981 	}
   1982 	obj1 = next_obj1;
   1983 	obj2 = next_obj2;
   1984 	goto start_subtree;
   1985 
   1986 finish:
   1987 	while (_prop_stack_pop(&stack, &obj1, &obj2, NULL, NULL)) {
   1988 		po1 = obj1;
   1989 		(*po1->po_type->pot_equals_finish)(obj1, obj2);
   1990 	}
   1991 	return (false);
   1992 }
   1993 
   1994 /*
   1995  * prop_object_iterator_next --
   1996  *	Return the next item during an iteration.
   1997  */
   1998 _PROP_EXPORT prop_object_t
   1999 prop_object_iterator_next(prop_object_iterator_t pi)
   2000 {
   2001 
   2002 	return ((*pi->pi_next_object)(pi));
   2003 }
   2004 
   2005 /*
   2006  * prop_object_iterator_reset --
   2007  *	Reset the iterator to the first object so as to restart
   2008  *	iteration.
   2009  */
   2010 _PROP_EXPORT void
   2011 prop_object_iterator_reset(prop_object_iterator_t pi)
   2012 {
   2013 
   2014 	(*pi->pi_reset)(pi);
   2015 }
   2016 
   2017 /*
   2018  * prop_object_iterator_release --
   2019  *	Release the object iterator.
   2020  */
   2021 _PROP_EXPORT void
   2022 prop_object_iterator_release(prop_object_iterator_t pi)
   2023 {
   2024 
   2025 	prop_object_release(pi->pi_obj);
   2026 	_PROP_FREE(pi, M_TEMP);
   2027 }
   2028