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