xref: /netbsd-src/common/lib/libprop/prop_object.c (revision b7ae68fde0d8ef1c03714e8bbb1ee7c6118ea93b)
1 /*	$NetBSD: prop_object.c,v 1.5 2006/08/27 22:31:55 christos Exp $	*/
2 
3 /*-
4  * Copyright (c) 2006 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Jason R. Thorpe.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. All advertising materials mentioning features or use of this software
19  *    must display the following acknowledgement:
20  *      This product includes software developed by the NetBSD
21  *      Foundation, Inc. and its contributors.
22  * 4. Neither the name of The NetBSD Foundation nor the names of its
23  *    contributors may be used to endorse or promote products derived
24  *    from this software without specific prior written permission.
25  *
26  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
27  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
28  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
30  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36  * POSSIBILITY OF SUCH DAMAGE.
37  */
38 
39 #include <prop/prop_object.h>
40 #include "prop_object_impl.h"
41 
42 #if !defined(_KERNEL) && !defined(_STANDALONE)
43 #include <sys/mman.h>
44 #include <sys/stat.h>
45 #include <errno.h>
46 #include <fcntl.h>
47 #include <limits.h>
48 #include <unistd.h>
49 #endif
50 
51 #ifdef _STANDALONE
52 void *
53 _prop_standalone_calloc(size_t size)
54 {
55 	void *rv;
56 
57 	rv = alloc(size);
58 	if (rv != NULL)
59 		memset(rv, 0, size);
60 
61 	return (rv);
62 }
63 
64 void *
65 _prop_standalone_realloc(void *v, size_t size)
66 {
67 	void *rv;
68 
69 	rv = alloc(size);
70 	if (rv != NULL) {
71 		memcpy(rv, v, size);	/* XXX */
72 		dealloc(v, 0);		/* XXX */
73 	}
74 
75 	return (rv);
76 }
77 #endif /* _STANDALONE */
78 
79 /*
80  * _prop_object_init --
81  *	Initialize an object.  Called when sub-classes create
82  *	an instance.
83  */
84 void
85 _prop_object_init(struct _prop_object *po, const struct _prop_object_type *pot)
86 {
87 
88 	po->po_type = pot;
89 	po->po_refcnt = 1;
90 }
91 
92 /*
93  * _prop_object_fini --
94  *	Finalize an object.  Called when sub-classes destroy
95  *	an instance.
96  */
97 /*ARGSUSED*/
98 void
99 _prop_object_fini(struct _prop_object *po)
100 {
101 	/* Nothing to do, currently. */
102 }
103 
104 /*
105  * _prop_object_externalize_start_tag --
106  *	Append an XML-style start tag to the externalize buffer.
107  */
108 boolean_t
109 _prop_object_externalize_start_tag(
110     struct _prop_object_externalize_context *ctx, const char *tag)
111 {
112 	unsigned int i;
113 
114 	for (i = 0; i < ctx->poec_depth; i++) {
115 		if (_prop_object_externalize_append_char(ctx, '\t') == FALSE)
116 			return (FALSE);
117 	}
118 	if (_prop_object_externalize_append_char(ctx, '<') == FALSE ||
119 	    _prop_object_externalize_append_cstring(ctx, tag) == FALSE ||
120 	    _prop_object_externalize_append_char(ctx, '>') == FALSE)
121 		return (FALSE);
122 
123 	return (TRUE);
124 }
125 
126 /*
127  * _prop_object_externalize_end_tag --
128  *	Append an XML-style end tag to the externalize buffer.
129  */
130 boolean_t
131 _prop_object_externalize_end_tag(
132     struct _prop_object_externalize_context *ctx, const char *tag)
133 {
134 
135 	if (_prop_object_externalize_append_char(ctx, '<') == FALSE ||
136 	    _prop_object_externalize_append_char(ctx, '/') == FALSE ||
137 	    _prop_object_externalize_append_cstring(ctx, tag) == FALSE ||
138 	    _prop_object_externalize_append_char(ctx, '>') == FALSE ||
139 	    _prop_object_externalize_append_char(ctx, '\n') == FALSE)
140 		return (FALSE);
141 
142 	return (TRUE);
143 }
144 
145 /*
146  * _prop_object_externalize_empty_tag --
147  *	Append an XML-style empty tag to the externalize buffer.
148  */
149 boolean_t
150 _prop_object_externalize_empty_tag(
151     struct _prop_object_externalize_context *ctx, const char *tag)
152 {
153 	unsigned int i;
154 
155 	for (i = 0; i < ctx->poec_depth; i++) {
156 		if (_prop_object_externalize_append_char(ctx, '\t') == FALSE)
157 			return (FALSE);
158 	}
159 
160 	if (_prop_object_externalize_append_char(ctx, '<') == FALSE ||
161 	    _prop_object_externalize_append_cstring(ctx, tag) == FALSE ||
162 	    _prop_object_externalize_append_char(ctx, '/') == FALSE ||
163 	    _prop_object_externalize_append_char(ctx, '>') == FALSE ||
164 	    _prop_object_externalize_append_char(ctx, '\n') == FALSE)
165 	    	return (FALSE);
166 
167 	return (TRUE);
168 }
169 
170 /*
171  * _prop_object_externalize_append_cstring --
172  *	Append a C string to the externalize buffer.
173  */
174 boolean_t
175 _prop_object_externalize_append_cstring(
176     struct _prop_object_externalize_context *ctx, const char *cp)
177 {
178 
179 	while (*cp != '\0') {
180 		if (_prop_object_externalize_append_char(ctx,
181 						(unsigned char) *cp) == FALSE)
182 			return (FALSE);
183 		cp++;
184 	}
185 
186 	return (TRUE);
187 }
188 
189 /*
190  * _prop_object_externalize_append_encoded_cstring --
191  *	Append an encoded C string to the externalize buffer.
192  */
193 boolean_t
194 _prop_object_externalize_append_encoded_cstring(
195     struct _prop_object_externalize_context *ctx, const char *cp)
196 {
197 
198 	while (*cp != '\0') {
199 		switch (*cp) {
200 		case '<':
201 			if (_prop_object_externalize_append_cstring(ctx,
202 					"&lt;") == FALSE)
203 				return (FALSE);
204 			break;
205 		case '>':
206 			if (_prop_object_externalize_append_cstring(ctx,
207 					"&gt;") == FALSE)
208 				return (FALSE);
209 			break;
210 		case '&':
211 			if (_prop_object_externalize_append_cstring(ctx,
212 					"&amp;") == FALSE)
213 				return (FALSE);
214 			break;
215 		default:
216 			if (_prop_object_externalize_append_char(ctx,
217 					(unsigned char) *cp) == FALSE)
218 				return (FALSE);
219 			break;
220 		}
221 		cp++;
222 	}
223 
224 	return (TRUE);
225 }
226 
227 #define	BUF_EXPAND		256
228 
229 /*
230  * _prop_object_externalize_append_char --
231  *	Append a single character to the externalize buffer.
232  */
233 boolean_t
234 _prop_object_externalize_append_char(
235     struct _prop_object_externalize_context *ctx, unsigned char c)
236 {
237 
238 	_PROP_ASSERT(ctx->poec_capacity != 0);
239 	_PROP_ASSERT(ctx->poec_buf != NULL);
240 	_PROP_ASSERT(ctx->poec_len <= ctx->poec_capacity);
241 
242 	if (ctx->poec_len == ctx->poec_capacity) {
243 		char *cp = _PROP_REALLOC(ctx->poec_buf,
244 					 ctx->poec_capacity + BUF_EXPAND,
245 					 M_TEMP);
246 		if (cp == NULL)
247 			return (FALSE);
248 		ctx->poec_capacity = ctx->poec_capacity + BUF_EXPAND;
249 		ctx->poec_buf = cp;
250 	}
251 
252 	ctx->poec_buf[ctx->poec_len++] = c;
253 
254 	return (TRUE);
255 }
256 
257 /*
258  * _prop_object_externalize_header --
259  *	Append the standard XML header to the externalize buffer.
260  */
261 boolean_t
262 _prop_object_externalize_header(struct _prop_object_externalize_context *ctx)
263 {
264 	static const char _plist_xml_header[] =
265 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
266 "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n";
267 
268 	if (_prop_object_externalize_append_cstring(ctx,
269 						 _plist_xml_header) == FALSE ||
270 	    _prop_object_externalize_start_tag(ctx,
271 				       "plist version=\"1.0\"") == FALSE ||
272 	    _prop_object_externalize_append_char(ctx, '\n') == FALSE)
273 		return (FALSE);
274 
275 	return (TRUE);
276 }
277 
278 /*
279  * _prop_object_externalize_footer --
280  *	Append the standard XML footer to the externalize buffer.  This
281  *	also NUL-terminates the buffer.
282  */
283 boolean_t
284 _prop_object_externalize_footer(struct _prop_object_externalize_context *ctx)
285 {
286 
287 	if (_prop_object_externalize_end_tag(ctx, "plist") == FALSE ||
288 	    _prop_object_externalize_append_char(ctx, '\0') == FALSE)
289 		return (FALSE);
290 
291 	return (TRUE);
292 }
293 
294 /*
295  * _prop_object_externalize_context_alloc --
296  *	Allocate an externalize context.
297  */
298 struct _prop_object_externalize_context *
299 _prop_object_externalize_context_alloc(void)
300 {
301 	struct _prop_object_externalize_context *ctx;
302 
303 	ctx = _PROP_MALLOC(sizeof(*ctx), M_TEMP);
304 	if (ctx != NULL) {
305 		ctx->poec_buf = _PROP_MALLOC(BUF_EXPAND, M_TEMP);
306 		if (ctx->poec_buf == NULL) {
307 			_PROP_FREE(ctx, M_TEMP);
308 			return (NULL);
309 		}
310 		ctx->poec_len = 0;
311 		ctx->poec_capacity = BUF_EXPAND;
312 		ctx->poec_depth = 0;
313 	}
314 	return (ctx);
315 }
316 
317 /*
318  * _prop_object_externalize_context_free --
319  *	Free an externalize context.
320  */
321 void
322 _prop_object_externalize_context_free(
323 		struct _prop_object_externalize_context *ctx)
324 {
325 
326 	/* Buffer is always freed by the caller. */
327 	_PROP_FREE(ctx, M_TEMP);
328 }
329 
330 /*
331  * _prop_object_internalize_skip_comment --
332  *	Skip the body and end tag of a comment.
333  */
334 static boolean_t
335 _prop_object_internalize_skip_comment(
336 				struct _prop_object_internalize_context *ctx)
337 {
338 	const char *cp = ctx->poic_cp;
339 
340 	while (!_PROP_EOF(*cp)) {
341 		if (cp[0] == '-' &&
342 		    cp[1] == '-' &&
343 		    cp[2] == '>') {
344 			ctx->poic_cp = cp + 3;
345 			return (TRUE);
346 		}
347 		cp++;
348 	}
349 
350 	return (FALSE);		/* ran out of buffer */
351 }
352 
353 /*
354  * _prop_object_internalize_find_tag --
355  *	Find the next tag in an XML stream.  Optionally compare the found
356  *	tag to an expected tag name.  State of the context is undefined
357  *	if this routine returns FALSE.  Upon success, the context points
358  *	to the first octet after the tag.
359  */
360 boolean_t
361 _prop_object_internalize_find_tag(struct _prop_object_internalize_context *ctx,
362 		      const char *tag, _prop_tag_type_t type)
363 {
364 	const char *cp;
365 	size_t taglen;
366 
367 	if (tag != NULL)
368 		taglen = strlen(tag);
369 	else
370 		taglen = 0;
371 
372  start_over:
373 	cp = ctx->poic_cp;
374 
375 	/*
376 	 * Find the start of the tag.
377 	 */
378 	while (_PROP_ISSPACE(*cp))
379 		cp++;
380 	if (_PROP_EOF(*cp))
381 		return (FALSE);
382 
383 	if (*cp != '<')
384 		return (FALSE);
385 
386 	ctx->poic_tag_start = cp++;
387 	if (_PROP_EOF(*cp))
388 		return (FALSE);
389 
390 	if (*cp == '!') {
391 		if (cp[1] != '-' || cp[2] != '-')
392 			return (FALSE);
393 		/*
394 		 * Comment block -- only allowed if we are allowed to
395 		 * return a start tag.
396 		 */
397 		if (type == _PROP_TAG_TYPE_END)
398 			return (FALSE);
399 		ctx->poic_cp = cp + 3;
400 		if (_prop_object_internalize_skip_comment(ctx) == FALSE)
401 			return (FALSE);
402 		goto start_over;
403 	}
404 
405 	if (*cp == '/') {
406 		if (type != _PROP_TAG_TYPE_END &&
407 		    type != _PROP_TAG_TYPE_EITHER)
408 			return (FALSE);
409 		cp++;
410 		if (_PROP_EOF(*cp))
411 			return (FALSE);
412 		ctx->poic_tag_type = _PROP_TAG_TYPE_END;
413 	} else {
414 		if (type != _PROP_TAG_TYPE_START &&
415 		    type != _PROP_TAG_TYPE_EITHER)
416 			return (FALSE);
417 		ctx->poic_tag_type = _PROP_TAG_TYPE_START;
418 	}
419 
420 	ctx->poic_tagname = cp;
421 
422 	while (!_PROP_ISSPACE(*cp) && *cp != '/' && *cp != '>')
423 		cp++;
424 	if (_PROP_EOF(*cp))
425 		return (FALSE);
426 
427 	ctx->poic_tagname_len = cp - ctx->poic_tagname;
428 
429 	/* Make sure this is the tag we're looking for. */
430 	if (tag != NULL &&
431 	    (taglen != ctx->poic_tagname_len ||
432 	     memcmp(tag, ctx->poic_tagname, taglen) != 0))
433 		return (FALSE);
434 
435 	/* Check for empty tag. */
436 	if (*cp == '/') {
437 		if (ctx->poic_tag_type != _PROP_TAG_TYPE_START)
438 			return(FALSE);		/* only valid on start tags */
439 		ctx->poic_is_empty_element = TRUE;
440 		cp++;
441 		if (_PROP_EOF(*cp) || *cp != '>')
442 			return (FALSE);
443 	} else
444 		ctx->poic_is_empty_element = FALSE;
445 
446 	/* Easy case of no arguments. */
447 	if (*cp == '>') {
448 		ctx->poic_tagattr = NULL;
449 		ctx->poic_tagattr_len = 0;
450 		ctx->poic_tagattrval = NULL;
451 		ctx->poic_tagattrval_len = 0;
452 		ctx->poic_cp = cp + 1;
453 		return (TRUE);
454 	}
455 
456 	_PROP_ASSERT(!_PROP_EOF(*cp));
457 	cp++;
458 	if (_PROP_EOF(*cp))
459 		return (FALSE);
460 
461 	while (_PROP_ISSPACE(*cp))
462 		cp++;
463 	if (_PROP_EOF(*cp))
464 		return (FALSE);
465 
466 	ctx->poic_tagattr = cp;
467 
468 	while (!_PROP_ISSPACE(*cp) && *cp != '=')
469 		cp++;
470 	if (_PROP_EOF(*cp))
471 		return (FALSE);
472 
473 	ctx->poic_tagattr_len = cp - ctx->poic_tagattr;
474 
475 	cp++;
476 	if (*cp != '\"')
477 		return (FALSE);
478 	cp++;
479 	if (_PROP_EOF(*cp))
480 		return (FALSE);
481 
482 	ctx->poic_tagattrval = cp;
483 	while (*cp != '\"')
484 		cp++;
485 	if (_PROP_EOF(*cp))
486 		return (FALSE);
487 	ctx->poic_tagattrval_len = cp - ctx->poic_tagattrval;
488 
489 	cp++;
490 	if (*cp != '>')
491 		return (FALSE);
492 
493 	ctx->poic_cp = cp + 1;
494 	return (TRUE);
495 }
496 
497 /*
498  * _prop_object_internalize_decode_string --
499  *	Decode an encoded string.
500  */
501 boolean_t
502 _prop_object_internalize_decode_string(
503 				struct _prop_object_internalize_context *ctx,
504 				char *target, size_t targsize, size_t *sizep,
505 				const char **cpp)
506 {
507 	const char *src;
508 	size_t tarindex;
509 	char c;
510 
511 	tarindex = 0;
512 	src = ctx->poic_cp;
513 
514 	for (;;) {
515 		if (_PROP_EOF(*src))
516 			return (FALSE);
517 		if (*src == '<') {
518 			break;
519 		}
520 
521 		if ((c = *src) == '&') {
522 			if (src[1] == 'a' &&
523 			    src[2] == 'm' &&
524 			    src[3] == 'p' &&
525 			    src[4] == ';') {
526 			    	c = '&';
527 				src += 5;
528 			} else if (src[1] == 'l' &&
529 				   src[2] == 't' &&
530 				   src[3] == ';') {
531 				c = '<';
532 				src += 4;
533 			} else if (src[1] == 'g' &&
534 				   src[2] == 't' &&
535 				   src[3] == ';') {
536 				c = '>';
537 				src += 4;
538 			} else if (src[1] == 'a' &&
539 				   src[2] == 'p' &&
540 				   src[3] == 'o' &&
541 				   src[4] == 's' &&
542 				   src[5] == ';') {
543 				c = '\'';
544 				src += 6;
545 			} else if (src[1] == 'q' &&
546 				   src[2] == 'u' &&
547 				   src[3] == 'o' &&
548 				   src[4] == 't' &&
549 				   src[5] == ';') {
550 				c = '\"';
551 				src += 6;
552 			} else
553 				return (FALSE);
554 		} else
555 			src++;
556 		if (target) {
557 			if (tarindex >= targsize)
558 				return (FALSE);
559 			target[tarindex] = c;
560 		}
561 		tarindex++;
562 	}
563 
564 	_PROP_ASSERT(*src == '<');
565 	if (sizep != NULL)
566 		*sizep = tarindex;
567 	if (cpp != NULL)
568 		*cpp = src;
569 
570 	return (TRUE);
571 }
572 
573 /*
574  * _prop_object_internalize_match --
575  *	Returns true if the two character streams match.
576  */
577 boolean_t
578 _prop_object_internalize_match(const char *str1, size_t len1,
579 			       const char *str2, size_t len2)
580 {
581 
582 	return (len1 == len2 && memcmp(str1, str2, len1) == 0);
583 }
584 
585 #define	INTERNALIZER(t, f)			\
586 {	t,	sizeof(t) - 1,		f	}
587 
588 static const struct _prop_object_internalizer {
589 	const char	*poi_tag;
590 	size_t		poi_taglen;
591 	prop_object_t	(*poi_intern)(
592 				struct _prop_object_internalize_context *);
593 } _prop_object_internalizer_table[] = {
594 	INTERNALIZER("array", _prop_array_internalize),
595 
596 	INTERNALIZER("true", _prop_bool_internalize),
597 	INTERNALIZER("false", _prop_bool_internalize),
598 
599 	INTERNALIZER("data", _prop_data_internalize),
600 
601 	INTERNALIZER("dict", _prop_dictionary_internalize),
602 
603 	INTERNALIZER("integer", _prop_number_internalize),
604 
605 	INTERNALIZER("string", _prop_string_internalize),
606 
607 	{ 0, 0, NULL }
608 };
609 
610 #undef INTERNALIZER
611 
612 /*
613  * _prop_object_internalize_by_tag --
614  *	Determine the object type from the tag in the context and
615  *	internalize it.
616  */
617 prop_object_t
618 _prop_object_internalize_by_tag(struct _prop_object_internalize_context *ctx)
619 {
620 	const struct _prop_object_internalizer *poi;
621 
622 	for (poi = _prop_object_internalizer_table;
623 	     poi->poi_tag != NULL; poi++) {
624 		if (_prop_object_internalize_match(ctx->poic_tagname,
625 						   ctx->poic_tagname_len,
626 						   poi->poi_tag,
627 						   poi->poi_taglen))
628 			return ((*poi->poi_intern)(ctx));
629 	}
630 
631 	return (NULL);
632 }
633 
634 /*
635  * _prop_object_internalize_context_alloc --
636  *	Allocate an internalize context.
637  */
638 struct _prop_object_internalize_context *
639 _prop_object_internalize_context_alloc(const char *xml)
640 {
641 	struct _prop_object_internalize_context *ctx;
642 
643 	ctx = _PROP_MALLOC(sizeof(struct _prop_object_internalize_context),
644 			   M_TEMP);
645 	if (ctx == NULL)
646 		return (NULL);
647 
648 	ctx->poic_xml = ctx->poic_cp = xml;
649 
650 	/*
651 	 * Skip any whitespace and XML preamble stuff that we don't
652 	 * know about / care about.
653 	 */
654 	for (;;) {
655 		while (_PROP_ISSPACE(*xml))
656 			xml++;
657 		if (_PROP_EOF(*xml) || *xml != '<')
658 			goto bad;
659 
660 #define	MATCH(str)	(memcmp(&xml[1], str, sizeof(str) - 1) == 0)
661 
662 		/*
663 		 * Skip over the XML preamble that Apple XML property
664 		 * lists usually include at the top of the file.
665 		 */
666 		if (MATCH("?xml ") ||
667 		    MATCH("!DOCTYPE plist")) {
668 			while (*xml != '>' && !_PROP_EOF(*xml))
669 				xml++;
670 			if (_PROP_EOF(*xml))
671 				goto bad;
672 			xml++;	/* advance past the '>' */
673 			continue;
674 		}
675 
676 		if (MATCH("<!--")) {
677 			ctx->poic_cp = xml + 4;
678 			if (_prop_object_internalize_skip_comment(ctx) == FALSE)
679 				goto bad;
680 			xml = ctx->poic_cp;
681 			continue;
682 		}
683 
684 #undef MATCH
685 
686 		/*
687 		 * We don't think we should skip it, so let's hope we can
688 		 * parse it.
689 		 */
690 		break;
691 	}
692 
693 	ctx->poic_cp = xml;
694 	return (ctx);
695  bad:
696 	_PROP_FREE(ctx, M_TEMP);
697 	return (NULL);
698 }
699 
700 /*
701  * _prop_object_internalize_context_free --
702  *	Free an internalize context.
703  */
704 void
705 _prop_object_internalize_context_free(
706 		struct _prop_object_internalize_context *ctx)
707 {
708 
709 	_PROP_FREE(ctx, M_TEMP);
710 }
711 
712 #if !defined(_KERNEL) && !defined(_STANDALONE)
713 /*
714  * _prop_object_externalize_file_dirname --
715  *	dirname(3), basically.  We have to roll our own because the
716  *	system dirname(3) isn't reentrant.
717  */
718 static void
719 _prop_object_externalize_file_dirname(const char *path, char *result)
720 {
721 	const char *lastp;
722 	size_t len;
723 
724 	/*
725 	 * If `path' is a NULL pointer or points to an empty string,
726 	 * return ".".
727 	 */
728 	if (path == NULL || *path == '\0')
729 		goto singledot;
730 
731 	/* String trailing slashes, if any. */
732 	lastp = path + strlen(path) - 1;
733 	while (lastp != path && *lastp == '/')
734 		lastp--;
735 
736 	/* Terminate path at the last occurrence of '/'. */
737 	do {
738 		if (*lastp == '/') {
739 			/* Strip trailing slashes, if any. */
740 			while (lastp != path && *lastp == '/')
741 				lastp--;
742 
743 			/* ...and copy the result into the result buffer. */
744 			len = (lastp - path) + 1 /* last char */;
745 			if (len > (PATH_MAX - 1))
746 				len = PATH_MAX - 1;
747 
748 			memcpy(result, path, len);
749 			result[len] = '\0';
750 			return;
751 		}
752 	} while (--lastp >= path);
753 
754  	/* No /'s found, return ".". */
755  singledot:
756 	strcpy(result, ".");
757 }
758 
759 /*
760  * _prop_object_externalize_write_file --
761  *	Write an externalized dictionary to the specified file.
762  *	The file is written atomically from the caller's perspective,
763  *	and the mode set to 0666 modified by the caller's umask.
764  */
765 boolean_t
766 _prop_object_externalize_write_file(const char *fname, const char *xml,
767     size_t len)
768 {
769 	char tname[PATH_MAX];
770 	int fd;
771 	int save_errno;
772 
773 	if (len > SSIZE_MAX) {
774 		errno = EFBIG;
775 		return (FALSE);
776 	}
777 
778 	/*
779 	 * Get the directory name where the file is to be written
780 	 * and create the temporary file.
781 	 *
782 	 * We don't use mkstemp() because mkstemp() always creates the
783 	 * file with mode 0600.  We do, however, use mktemp() safely.
784 	 */
785  again:
786 	_prop_object_externalize_file_dirname(fname, tname);
787 	if (strlcat(tname, "/.plistXXXXXX", sizeof(tname)) >= sizeof(tname)) {
788 		errno = ENAMETOOLONG;
789 		return (FALSE);
790 	}
791 	if (mktemp(tname) == NULL)
792 		return (FALSE);
793 	if ((fd = open(tname, O_CREAT|O_RDWR|O_EXCL, 0666)) == -1) {
794 		if (errno == EEXIST)
795 			goto again;
796 		return (FALSE);
797 	}
798 
799 	if (write(fd, xml, len) != (ssize_t)len)
800 		goto bad;
801 
802 	if (fsync(fd) == -1)
803 		goto bad;
804 
805 	(void) close(fd);
806 	fd = -1;
807 
808 	if (rename(tname, fname) == -1)
809 		goto bad;
810 
811 	return (TRUE);
812 
813  bad:
814 	save_errno = errno;
815 	if (fd != -1)
816 		(void) close(fd);
817 	(void) unlink(tname);
818 	errno = save_errno;
819 	return (FALSE);
820 }
821 
822 /*
823  * _prop_object_internalize_map_file --
824  *	Map a file for the purpose of internalizing it.
825  */
826 struct _prop_object_internalize_mapped_file *
827 _prop_object_internalize_map_file(const char *fname)
828 {
829 	struct stat sb;
830 	struct _prop_object_internalize_mapped_file *mf;
831 	size_t pgsize = sysconf(_SC_PAGESIZE);
832 	size_t pgmask = pgsize - 1;
833 	boolean_t need_guard = FALSE;
834 	int fd;
835 
836 	mf = _PROP_MALLOC(sizeof(*mf), M_TEMP);
837 	if (mf == NULL)
838 		return (NULL);
839 
840 	fd = open(fname, O_RDONLY, 0400);
841 	if (fd == -1) {
842 		_PROP_FREE(mf, M_TEMP);
843 		return (NULL);
844 	}
845 
846 	if (fstat(fd, &sb) == -1) {
847 		(void) close(fd);
848 		_PROP_FREE(mf, M_TEMP);
849 		return (NULL);
850 	}
851 	mf->poimf_mapsize = ((size_t)sb.st_size + pgmask) & ~pgmask;
852 	if (mf->poimf_mapsize < sb.st_size) {
853 		(void) close(fd);
854 		_PROP_FREE(mf, M_TEMP);
855 		return (NULL);
856 	}
857 
858 	/*
859 	 * If the file length is an integral number of pages, then we
860 	 * need to map a guard page at the end in order to provide the
861 	 * necessary NUL-termination of the buffer.
862 	 */
863 	if ((sb.st_size & pgmask) == 0)
864 		need_guard = TRUE;
865 
866 	mf->poimf_xml = mmap(NULL, need_guard ? mf->poimf_mapsize + pgsize
867 			    		      : mf->poimf_mapsize,
868 			    PROT_READ, MAP_FILE|MAP_SHARED, fd, (off_t)0);
869 	(void) close(fd);
870 	if (mf->poimf_xml == MAP_FAILED) {
871 		_PROP_FREE(mf, M_TEMP);
872 		return (NULL);
873 	}
874 	(void) madvise(mf->poimf_xml, mf->poimf_mapsize, MADV_SEQUENTIAL);
875 
876 	if (need_guard) {
877 		if (mmap(mf->poimf_xml + mf->poimf_mapsize,
878 			 pgsize, PROT_READ,
879 			 MAP_ANON|MAP_PRIVATE|MAP_FIXED, -1,
880 			 (off_t)0) == MAP_FAILED) {
881 			(void) munmap(mf->poimf_xml, mf->poimf_mapsize);
882 			_PROP_FREE(mf, M_TEMP);
883 			return (NULL);
884 		}
885 		mf->poimf_mapsize += pgsize;
886 	}
887 
888 	return (mf);
889 }
890 
891 /*
892  * _prop_object_internalize_unmap_file --
893  *	Unmap a file previously mapped for internalizing.
894  */
895 void
896 _prop_object_internalize_unmap_file(
897     struct _prop_object_internalize_mapped_file *mf)
898 {
899 
900 	(void) madvise(mf->poimf_xml, mf->poimf_mapsize, MADV_DONTNEED);
901 	(void) munmap(mf->poimf_xml, mf->poimf_mapsize);
902 	_PROP_FREE(mf, M_TEMP);
903 }
904 #endif /* !_KERNEL && !_STANDALONE */
905 
906 /*
907  * Retain / release serialization --
908  *
909  * Eventually we would like to use atomic operations.  But until we have
910  * an MI API for them that is common to userland and the kernel, we will
911  * use a lock instead.
912  *
913  * We use a single global mutex for all serialization.  In the kernel, because
914  * we are still under a biglock, this will basically never contend (properties
915  * cannot be manipulated at interrupt level).  In userland, this will cost
916  * nothing for single-threaded programs.  For multi-threaded programs, there
917  * could be contention, but it probably won't cost that much unless the program
918  * makes heavy use of property lists.
919  */
920 _PROP_MUTEX_DECL(_prop_refcnt_mutex)
921 #define	_PROP_REFCNT_LOCK()	_PROP_MUTEX_LOCK(_prop_refcnt_mutex)
922 #define	_PROP_REFCNT_UNLOCK()	_PROP_MUTEX_UNLOCK(_prop_refcnt_mutex)
923 
924 /*
925  * prop_object_retain --
926  *	Increment the reference count on an object.
927  */
928 void
929 prop_object_retain(prop_object_t obj)
930 {
931 	struct _prop_object *po = obj;
932 	uint32_t ocnt;
933 
934 	_PROP_REFCNT_LOCK();
935 	ocnt = po->po_refcnt++;
936 	_PROP_REFCNT_UNLOCK();
937 
938 	_PROP_ASSERT(ocnt != 0xffffffffU);
939 }
940 
941 /*
942  * prop_object_release --
943  *	Decrement the reference count on an object.
944  *
945  *	Free the object if we are releasing the final
946  *	reference.
947  */
948 void
949 prop_object_release(prop_object_t obj)
950 {
951 	struct _prop_object *po = obj;
952 	uint32_t ocnt;
953 
954 	_PROP_REFCNT_LOCK();
955 	ocnt = po->po_refcnt--;
956 	_PROP_REFCNT_UNLOCK();
957 
958 	_PROP_ASSERT(ocnt != 0);
959 	if (ocnt == 1)
960 		(*po->po_type->pot_free)(po);
961 }
962 
963 /*
964  * prop_object_type --
965  *	Return the type of an object.
966  */
967 prop_type_t
968 prop_object_type(prop_object_t obj)
969 {
970 	struct _prop_object *po = obj;
971 
972 	if (obj == NULL)
973 		return (PROP_TYPE_UNKNOWN);
974 
975 	return (po->po_type->pot_type);
976 }
977 
978 /*
979  * prop_object_equals --
980  *	Returns TRUE if thw two objects are equivalent.
981  */
982 boolean_t
983 prop_object_equals(prop_object_t obj1, prop_object_t obj2)
984 {
985 	struct _prop_object *po1 = obj1;
986 	struct _prop_object *po2 = obj2;
987 
988 	if (po1->po_type != po2->po_type)
989 		return (FALSE);
990 
991 	return ((*po1->po_type->pot_equals)(obj1, obj2));
992 }
993 
994 /*
995  * prop_object_iterator_next --
996  *	Return the next item during an iteration.
997  */
998 prop_object_t
999 prop_object_iterator_next(prop_object_iterator_t pi)
1000 {
1001 
1002 	return ((*pi->pi_next_object)(pi));
1003 }
1004 
1005 /*
1006  * prop_object_iterator_reset --
1007  *	Reset the iterator to the first object so as to restart
1008  *	iteration.
1009  */
1010 void
1011 prop_object_iterator_reset(prop_object_iterator_t pi)
1012 {
1013 
1014 	(*pi->pi_reset)(pi);
1015 }
1016 
1017 /*
1018  * prop_object_iterator_release --
1019  *	Release the object iterator.
1020  */
1021 void
1022 prop_object_iterator_release(prop_object_iterator_t pi)
1023 {
1024 
1025 	prop_object_release(pi->pi_obj);
1026 	_PROP_FREE(pi, M_TEMP);
1027 }
1028