prop_object.c revision 1.22 1 /* $NetBSD: prop_object.c,v 1.22 2008/08/03 04:00:12 thorpej 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 prop_object_t parent = NULL;
1023 uint32_t ocnt;
1024
1025 for (;;) {
1026 po = obj;
1027 _PROP_ASSERT(obj);
1028
1029 _PROP_REFCNT_LOCK();
1030 ocnt = po->po_refcnt--;
1031 _PROP_REFCNT_UNLOCK();
1032
1033 _PROP_ASSERT(ocnt != 0);
1034 if (ocnt != 1)
1035 break;
1036
1037 _PROP_ASSERT(po->po_type);
1038 if ((po->po_type->pot_free)(NULL, &obj) ==
1039 _PROP_OBJECT_FREE_DONE)
1040 break;
1041
1042 parent = po;
1043 _PROP_REFCNT_LOCK();
1044 ++po->po_refcnt;
1045 _PROP_REFCNT_UNLOCK();
1046 }
1047 _PROP_ASSERT(parent);
1048 /* One object was just freed. */
1049 po = parent;
1050 (*po->po_type->pot_emergency_free)(parent);
1051 }
1052
1053 /*
1054 * prop_object_release --
1055 * Decrement the reference count on an object.
1056 *
1057 * Free the object if we are releasing the final
1058 * reference.
1059 */
1060 void
1061 prop_object_release(prop_object_t obj)
1062 {
1063 struct _prop_object *po;
1064 struct _prop_stack stack;
1065 int ret;
1066 uint32_t ocnt;
1067
1068 _prop_stack_init(&stack);
1069
1070 do {
1071 do {
1072 po = obj;
1073 _PROP_ASSERT(obj);
1074
1075 _PROP_REFCNT_LOCK();
1076 ocnt = po->po_refcnt--;
1077 _PROP_REFCNT_UNLOCK();
1078
1079 _PROP_ASSERT(ocnt != 0);
1080 if (ocnt != 1) {
1081 ret = 0;
1082 break;
1083 }
1084
1085 ret = (po->po_type->pot_free)(&stack, &obj);
1086
1087 if (ret == _PROP_OBJECT_FREE_DONE)
1088 break;
1089
1090 _PROP_REFCNT_LOCK();
1091 ++po->po_refcnt;
1092 _PROP_REFCNT_UNLOCK();
1093 } while (ret == _PROP_OBJECT_FREE_RECURSE);
1094 if (ret == _PROP_OBJECT_FREE_FAILED)
1095 prop_object_release_emergency(obj);
1096 } while (_prop_stack_pop(&stack, &obj, NULL, NULL, NULL));
1097 }
1098
1099 /*
1100 * prop_object_type --
1101 * Return the type of an object.
1102 */
1103 prop_type_t
1104 prop_object_type(prop_object_t obj)
1105 {
1106 struct _prop_object *po = obj;
1107
1108 if (obj == NULL)
1109 return (PROP_TYPE_UNKNOWN);
1110
1111 return (po->po_type->pot_type);
1112 }
1113
1114 /*
1115 * prop_object_equals --
1116 * Returns true if thw two objects are equivalent.
1117 */
1118 bool
1119 prop_object_equals(prop_object_t obj1, prop_object_t obj2)
1120 {
1121 return (prop_object_equals_with_error(obj1, obj2, NULL));
1122 }
1123
1124 bool
1125 prop_object_equals_with_error(prop_object_t obj1, prop_object_t obj2,
1126 bool *error_flag)
1127 {
1128 struct _prop_object *po1;
1129 struct _prop_object *po2;
1130 void *stored_pointer1, *stored_pointer2;
1131 prop_object_t next_obj1, next_obj2;
1132 struct _prop_stack stack;
1133 _prop_object_equals_rv_t ret;
1134
1135 _prop_stack_init(&stack);
1136 if (error_flag)
1137 *error_flag = false;
1138
1139 start_subtree:
1140 stored_pointer1 = NULL;
1141 stored_pointer2 = NULL;
1142 po1 = obj1;
1143 po2 = obj2;
1144
1145 if (po1->po_type != po2->po_type)
1146 return (false);
1147
1148 continue_subtree:
1149 ret = (*po1->po_type->pot_equals)(obj1, obj2,
1150 &stored_pointer1, &stored_pointer2,
1151 &next_obj1, &next_obj2);
1152 if (ret == _PROP_OBJECT_EQUALS_FALSE)
1153 goto finish;
1154 if (ret == _PROP_OBJECT_EQUALS_TRUE) {
1155 if (!_prop_stack_pop(&stack, &obj1, &obj2,
1156 &stored_pointer1, &stored_pointer2))
1157 return true;
1158 goto continue_subtree;
1159 }
1160 _PROP_ASSERT(ret == _PROP_OBJECT_EQUALS_RECURSE);
1161
1162 if (!_prop_stack_push(&stack, obj1, obj2,
1163 stored_pointer1, stored_pointer2)) {
1164 if (error_flag)
1165 *error_flag = true;
1166 goto finish;
1167 }
1168 obj1 = next_obj1;
1169 obj2 = next_obj2;
1170 goto start_subtree;
1171
1172 finish:
1173 while (_prop_stack_pop(&stack, &obj1, &obj2, NULL, NULL)) {
1174 po1 = obj1;
1175 (*po1->po_type->pot_equals_finish)(obj1, obj2);
1176 }
1177 return (false);
1178 }
1179
1180 /*
1181 * prop_object_iterator_next --
1182 * Return the next item during an iteration.
1183 */
1184 prop_object_t
1185 prop_object_iterator_next(prop_object_iterator_t pi)
1186 {
1187
1188 return ((*pi->pi_next_object)(pi));
1189 }
1190
1191 /*
1192 * prop_object_iterator_reset --
1193 * Reset the iterator to the first object so as to restart
1194 * iteration.
1195 */
1196 void
1197 prop_object_iterator_reset(prop_object_iterator_t pi)
1198 {
1199
1200 (*pi->pi_reset)(pi);
1201 }
1202
1203 /*
1204 * prop_object_iterator_release --
1205 * Release the object iterator.
1206 */
1207 void
1208 prop_object_iterator_release(prop_object_iterator_t pi)
1209 {
1210
1211 prop_object_release(pi->pi_obj);
1212 _PROP_FREE(pi, M_TEMP);
1213 }
1214