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