1/* $NetBSD: prop_object.c,v 1.30 2015/05/12 14:59:35 christos 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
36static 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
49void *
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
61void *
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 */
81void
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*/
95void
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 */
105bool
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 */
127bool
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 */
146bool
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 */
171bool
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 */
190bool
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 */
230bool
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 */
258bool
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 */
280bool
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 */
295struct _prop_object_externalize_context *
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 */
318void
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 */
331static bool
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 */
357bool
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 */
501bool
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 */
577bool
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
588static 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 */
616prop_object_t
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
627match_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
660prop_object_t
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 */
716struct _prop_object_internalize_context *
717_prop_object_internalize_context_alloc(const char *xml)
718{
719 struct _prop_object_internalize_context *ctx;
720
721 ctx = _PROP_MALLOC(sizeof(struct _prop_object_internalize_context),
722 M_TEMP);
723 if (ctx == NULL)
724 return (NULL);
725
726 ctx->poic_xml = ctx->poic_cp = xml;
727
728 /*
729 * Skip any whitespace and XML preamble stuff that we don't
730 * know about / care about.
731 */
732 for (;;) {
733 while (_PROP_ISSPACE(*xml))
734 xml++;
735 if (_PROP_EOF(*xml) || *xml != '<')
736 goto bad;
737
738#define MATCH(str) (memcmp(&xml[1], str, sizeof(str) - 1) == 0)
739
740 /*
741 * Skip over the XML preamble that Apple XML property
742 * lists usually include at the top of the file.
743 */
744 if (MATCH("?xml ") ||
745 MATCH("!DOCTYPE plist")) {
746 while (*xml != '>' && !_PROP_EOF(*xml))
747 xml++;
748 if (_PROP_EOF(*xml))
749 goto bad;
750 xml++; /* advance past the '>' */
751 continue;
752 }
753
754 if (MATCH("<!--")) {
755 ctx->poic_cp = xml + 4;
756 if (_prop_object_internalize_skip_comment(ctx) == false)
757 goto bad;
758 xml = ctx->poic_cp;
759 continue;
760 }
761
762#undef MATCH
763
764 /*
765 * We don't think we should skip it, so let's hope we can
766 * parse it.
767 */
768 break;
769 }
770
771 ctx->poic_cp = xml;
772 return (ctx);
773 bad:
774 _PROP_FREE(ctx, M_TEMP);
775 return (NULL);
776}
777
778/*
779 * _prop_object_internalize_context_free --
780 * Free an internalize context.
781 */
782void
783_prop_object_internalize_context_free(
784 struct _prop_object_internalize_context *ctx)
785{
786
787 _PROP_FREE(ctx, M_TEMP);
788}
789
790#if !defined(_KERNEL) && !defined(_STANDALONE)
791/*
792 * _prop_object_externalize_file_dirname --
793 * dirname(3), basically. We have to roll our own because the
794 * system dirname(3) isn't reentrant.
795 */
796static void
797_prop_object_externalize_file_dirname(const char *path, char *result)
798{
799 const char *lastp;
800 size_t len;
801
802 /*
803 * If `path' is a NULL pointer or points to an empty string,
804 * return ".".
805 */
806 if (path == NULL || *path == '\0')
807 goto singledot;
808
809 /* String trailing slashes, if any. */
810 lastp = path + strlen(path) - 1;
811 while (lastp != path && *lastp == '/')
812 lastp--;
813
814 /* Terminate path at the last occurrence of '/'. */
815 do {
816 if (*lastp == '/') {
817 /* Strip trailing slashes, if any. */
818 while (lastp != path && *lastp == '/')
819 lastp--;
820
821 /* ...and copy the result into the result buffer. */
822 len = (lastp - path) + 1 /* last char */;
823 if (len > (PATH_MAX - 1))
824 len = PATH_MAX - 1;
825
826 memcpy(result, path, len);
827 result[len] = '\0';
828 return;
829 }
830 } while (--lastp >= path);
831
832 /* No /'s found, return ".". */
833 singledot:
834 strcpy(result, ".");
835}
836
837/*
838 * _prop_object_externalize_write_file --
839 * Write an externalized dictionary to the specified file.
840 * The file is written atomically from the caller's perspective,
841 * and the mode set to 0666 modified by the caller's umask.
842 */
843bool
844_prop_object_externalize_write_file(const char *fname, const char *xml,
845 size_t len)
846{
847 char tname[PATH_MAX];
848 int fd;
849 int save_errno;
850 mode_t myumask;
851
852 if (len > SSIZE_MAX) {
853 errno = EFBIG;
854 return (false);
855 }
856
857 /*
858 * Get the directory name where the file is to be written
859 * and create the temporary file.
860 */
861 _prop_object_externalize_file_dirname(fname, tname);
862#define PLISTTMP "/.plistXXXXXX"
863 if (strlen(tname) + strlen(PLISTTMP) >= sizeof(tname)) {
864 errno = ENAMETOOLONG;
865 return (false);
866 }
867 strcat(tname, PLISTTMP);
868#undef PLISTTMP
869
870 if ((fd = mkstemp(tname)) == -1)
871 return (false);
872
873 if (write(fd, xml, len) != (ssize_t)len)
874 goto bad;
875
876 if (fsync(fd) == -1)
877 goto bad;
878
879 myumask = umask(0);
880 (void)umask(myumask);
881 if (fchmod(fd, 0666 & ~myumask) == -1)
882 goto bad;
883
884 (void) close(fd);
885 fd = -1;
886
887 if (rename(tname, fname) == -1)
888 goto bad;
889
890 return (true);
891
892 bad:
893 save_errno = errno;
894 if (fd != -1)
895 (void) close(fd);
896 (void) unlink(tname);
897 errno = save_errno;
898 return (false);
899}
900
901/*
902 * _prop_object_internalize_map_file --
903 * Map a file for the purpose of internalizing it.
904 */
905struct _prop_object_internalize_mapped_file *
906_prop_object_internalize_map_file(const char *fname)
907{
908 struct stat sb;
909 struct _prop_object_internalize_mapped_file *mf;
910 size_t pgsize = (size_t)sysconf(_SC_PAGESIZE);
911 size_t pgmask = pgsize - 1;
912 bool need_guard = false;
913 int fd;
914
915 mf = _PROP_MALLOC(sizeof(*mf), M_TEMP);
916 if (mf == NULL)
917 return (NULL);
918
919 fd = open(fname, O_RDONLY, 0400);
920 if (fd == -1) {
921 _PROP_FREE(mf, M_TEMP);
922 return (NULL);
923 }
924
925 if (fstat(fd, &sb) == -1) {
926 (void) close(fd);
927 _PROP_FREE(mf, M_TEMP);
928 return (NULL);
929 }
930 mf->poimf_mapsize = ((size_t)sb.st_size + pgmask) & ~pgmask;
931 if (mf->poimf_mapsize < (size_t)sb.st_size) {
932 (void) close(fd);
933 _PROP_FREE(mf, M_TEMP);
934 return (NULL);
935 }
936
937 /*
938 * If the file length is an integral number of pages, then we
939 * need to map a guard page at the end in order to provide the
940 * necessary NUL-termination of the buffer.
941 */
942 if ((sb.st_size & pgmask) == 0)
943 need_guard = true;
944
945 mf->poimf_xml = mmap(NULL, need_guard ? mf->poimf_mapsize + pgsize
946 : mf->poimf_mapsize,
947 PROT_READ, MAP_FILE|MAP_SHARED, fd, (off_t)0);
948 (void) close(fd);
949 if (mf->poimf_xml == MAP_FAILED) {
950 _PROP_FREE(mf, M_TEMP);
951 return (NULL);
952 }
953 (void) madvise(mf->poimf_xml, mf->poimf_mapsize, MADV_SEQUENTIAL);
954
955 if (need_guard) {
956 if (mmap(mf->poimf_xml + mf->poimf_mapsize,
957 pgsize, PROT_READ,
958 MAP_ANON|MAP_PRIVATE|MAP_FIXED, -1,
959 (off_t)0) == MAP_FAILED) {
960 (void) munmap(mf->poimf_xml, mf->poimf_mapsize);
961 _PROP_FREE(mf, M_TEMP);
962 return (NULL);
963 }
964 mf->poimf_mapsize += pgsize;
965 }
966
967 return (mf);
968}
969
970/*
971 * _prop_object_internalize_unmap_file --
972 * Unmap a file previously mapped for internalizing.
973 */
974void
975_prop_object_internalize_unmap_file(
976 struct _prop_object_internalize_mapped_file *mf)
977{
978
979 (void) madvise(mf->poimf_xml, mf->poimf_mapsize, MADV_DONTNEED);
980 (void) munmap(mf->poimf_xml, mf->poimf_mapsize);
981 _PROP_FREE(mf, M_TEMP);
982}
983#endif /* !_KERNEL && !_STANDALONE */
984
985/*
986 * prop_object_retain --
987 * Increment the reference count on an object.
988 */
989void
990prop_object_retain(prop_object_t obj)
991{
992 struct _prop_object *po = obj;
993 uint32_t ncnt __unused;
994
995 _PROP_ATOMIC_INC32_NV(&po->po_refcnt, ncnt);
996 _PROP_ASSERT(ncnt != 0);
997}
998
999/*
1000 * prop_object_release_emergency
1001 * A direct free with prop_object_release failed.
1002 * Walk down the tree until a leaf is found and
1003 * free that. Do not recurse to avoid stack overflows.
1004 *
1005 * This is a slow edge condition, but necessary to
1006 * guarantee that an object can always be freed.
1007 */
1008static void
1009prop_object_release_emergency(prop_object_t obj)
1010{
1011 struct _prop_object *po;
1012 void (*unlock)(void);
1013 prop_object_t parent = NULL;
1014 uint32_t ocnt;
1015
1016 for (;;) {
1017 po = obj;
1018 _PROP_ASSERT(obj);
1019
1020 if (po->po_type->pot_lock != NULL)
1021 po->po_type->pot_lock();
1022
1023 /* Save pointerto unlock function */
1024 unlock = po->po_type->pot_unlock;
1025
1026 /* Dance a bit to make sure we always get the non-racy ocnt */
1027 _PROP_ATOMIC_DEC32_NV(&po->po_refcnt, ocnt);
1028 ocnt++;
1029 _PROP_ASSERT(ocnt != 0);
1030
1031 if (ocnt != 1) {
1032 if (unlock != NULL)
1033 unlock();
1034 break;
1035 }
1036
1037 _PROP_ASSERT(po->po_type);
1038 if ((po->po_type->pot_free)(NULL, &obj) ==
1039 _PROP_OBJECT_FREE_DONE) {
1040 if (unlock != NULL)
1041 unlock();
1042 break;
1043 }
1044
1045 if (unlock != NULL)
1046 unlock();
1047
1048 parent = po;
1049 _PROP_ATOMIC_INC32(&po->po_refcnt);
1050 }
1051 _PROP_ASSERT(parent);
1052 /* One object was just freed. */
1053 po = parent;
1054 (*po->po_type->pot_emergency_free)(parent);
1055}
1056
1057/*
1058 * prop_object_release --
1059 * Decrement the reference count on an object.
1060 *
1061 * Free the object if we are releasing the final
1062 * reference.
1063 */
1064void
1065prop_object_release(prop_object_t obj)
1066{
1067 struct _prop_object *po;
1068 struct _prop_stack stack;
1069 void (*unlock)(void);
1070 int ret;
1071 uint32_t ocnt;
1072
1073 _prop_stack_init(&stack);
1074
1075 do {
1076 do {
1077 po = obj;
1078 _PROP_ASSERT(obj);
1079
1080 if (po->po_type->pot_lock != NULL)
1081 po->po_type->pot_lock();
1082
1083 /* Save pointer to object unlock function */
1084 unlock = po->po_type->pot_unlock;
1085
1086 _PROP_ATOMIC_DEC32_NV(&po->po_refcnt, ocnt);
1087 ocnt++;
1088 _PROP_ASSERT(ocnt != 0);
1089
1090 if (ocnt != 1) {
1091 ret = 0;
1092 if (unlock != NULL)
1093 unlock();
1094 break;
1095 }
1096
1097 ret = (po->po_type->pot_free)(&stack, &obj);
1098
1099 if (unlock != NULL)
1100 unlock();
1101
1102 if (ret == _PROP_OBJECT_FREE_DONE)
1103 break;
1104
1105 _PROP_ATOMIC_INC32(&po->po_refcnt);
1106 } while (ret == _PROP_OBJECT_FREE_RECURSE);
1107 if (ret == _PROP_OBJECT_FREE_FAILED)
1108 prop_object_release_emergency(obj);
1109 } while (_prop_stack_pop(&stack, &obj, NULL, NULL, NULL));
1110}
1111
1112/*
1113 * prop_object_type --
1114 * Return the type of an object.
1115 */
1116prop_type_t
1117prop_object_type(prop_object_t obj)
1118{
1119 struct _prop_object *po = obj;
1120
1121 if (obj == NULL)
1122 return (PROP_TYPE_UNKNOWN);
1123
1124 return (po->po_type->pot_type);
1125}
1126
1127/*
1128 * prop_object_equals --
1129 * Returns true if thw two objects are equivalent.
1130 */
1131bool
1132prop_object_equals(prop_object_t obj1, prop_object_t obj2)
1133{
1134 return (prop_object_equals_with_error(obj1, obj2, NULL));
1135}
1136
1137bool
1138prop_object_equals_with_error(prop_object_t obj1, prop_object_t obj2,
1139 bool *error_flag)
1140{
1141 struct _prop_object *po1;
1142 struct _prop_object *po2;
1143 void *stored_pointer1, *stored_pointer2;
1144 prop_object_t next_obj1, next_obj2;
1145 struct _prop_stack stack;
1146 _prop_object_equals_rv_t ret;
1147
1148 _prop_stack_init(&stack);
1149 if (error_flag)
1150 *error_flag = false;
1151
1152 start_subtree:
1153 stored_pointer1 = NULL;
1154 stored_pointer2 = NULL;
1155 po1 = obj1;
1156 po2 = obj2;
1157
1158 if (po1->po_type != po2->po_type)
1159 return (false);
1160
1161 continue_subtree:
1162 ret = (*po1->po_type->pot_equals)(obj1, obj2,
1163 &stored_pointer1, &stored_pointer2,
1164 &next_obj1, &next_obj2);
1165 if (ret == _PROP_OBJECT_EQUALS_FALSE)
1166 goto finish;
1167 if (ret == _PROP_OBJECT_EQUALS_TRUE) {
1168 if (!_prop_stack_pop(&stack, &obj1, &obj2,
1169 &stored_pointer1, &stored_pointer2))
1170 return true;
1171 po1 = obj1;
1172 po2 = obj2;
1173 goto continue_subtree;
1174 }
1175 _PROP_ASSERT(ret == _PROP_OBJECT_EQUALS_RECURSE);
1176
1177 if (!_prop_stack_push(&stack, obj1, obj2,
1178 stored_pointer1, stored_pointer2)) {
1179 if (error_flag)
1180 *error_flag = true;
1181 goto finish;
1182 }
1183 obj1 = next_obj1;
1184 obj2 = next_obj2;
1185 goto start_subtree;
1186
1187finish:
1188 while (_prop_stack_pop(&stack, &obj1, &obj2, NULL, NULL)) {
1189 po1 = obj1;
1190 (*po1->po_type->pot_equals_finish)(obj1, obj2);
1191 }
1192 return (false);
1193}
1194
1195/*
1196 * prop_object_iterator_next --
1197 * Return the next item during an iteration.
1198 */
1199prop_object_t
1200prop_object_iterator_next(prop_object_iterator_t pi)
1201{
1202
1203 return ((*pi->pi_next_object)(pi));
1204}
1205
1206/*
1207 * prop_object_iterator_reset --
1208 * Reset the iterator to the first object so as to restart
1209 * iteration.
1210 */
1211void
1212prop_object_iterator_reset(prop_object_iterator_t pi)
1213{
1214
1215 (*pi->pi_reset)(pi);
1216}
1217
1218/*
1219 * prop_object_iterator_release --
1220 * Release the object iterator.
1221 */
1222void
1223prop_object_iterator_release(prop_object_iterator_t pi)
1224{
1225
1226 prop_object_release(pi->pi_obj);
1227 _PROP_FREE(pi, M_TEMP);
1228}
1229