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