prop_object.c revision 1.15 1 /* $NetBSD: prop_object.c,v 1.15 2007/08/16 21:44:07 joerg 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 * 3. All advertising materials mentioning features or use of this software
19 * must display the following acknowledgement:
20 * This product includes software developed by the NetBSD
21 * Foundation, Inc. and its contributors.
22 * 4. Neither the name of The NetBSD Foundation nor the names of its
23 * contributors may be used to endorse or promote products derived
24 * from this software without specific prior written permission.
25 *
26 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
27 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
28 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
30 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36 * POSSIBILITY OF SUCH DAMAGE.
37 */
38
39 #include <prop/prop_object.h>
40 #include "prop_object_impl.h"
41
42 #if !defined(_KERNEL) && !defined(_STANDALONE)
43 #include <sys/mman.h>
44 #include <sys/stat.h>
45 #include <errno.h>
46 #include <fcntl.h>
47 #include <limits.h>
48 #include <unistd.h>
49 #endif
50
51 #ifdef _STANDALONE
52 void *
53 _prop_standalone_calloc(size_t size)
54 {
55 void *rv;
56
57 rv = alloc(size);
58 if (rv != NULL)
59 memset(rv, 0, size);
60
61 return (rv);
62 }
63
64 void *
65 _prop_standalone_realloc(void *v, size_t size)
66 {
67 void *rv;
68
69 rv = alloc(size);
70 if (rv != NULL) {
71 memcpy(rv, v, size); /* XXX */
72 dealloc(v, 0); /* XXX */
73 }
74
75 return (rv);
76 }
77 #endif /* _STANDALONE */
78
79 /*
80 * _prop_object_init --
81 * Initialize an object. Called when sub-classes create
82 * an instance.
83 */
84 void
85 _prop_object_init(struct _prop_object *po, const struct _prop_object_type *pot)
86 {
87
88 po->po_type = pot;
89 po->po_refcnt = 1;
90 }
91
92 /*
93 * _prop_object_fini --
94 * Finalize an object. Called when sub-classes destroy
95 * an instance.
96 */
97 /*ARGSUSED*/
98 void
99 _prop_object_fini(struct _prop_object *po _PROP_ARG_UNUSED)
100 {
101 /* Nothing to do, currently. */
102 }
103
104 /*
105 * _prop_object_externalize_start_tag --
106 * Append an XML-style start tag to the externalize buffer.
107 */
108 bool
109 _prop_object_externalize_start_tag(
110 struct _prop_object_externalize_context *ctx, const char *tag)
111 {
112 unsigned int i;
113
114 for (i = 0; i < ctx->poec_depth; i++) {
115 if (_prop_object_externalize_append_char(ctx, '\t') == false)
116 return (false);
117 }
118 if (_prop_object_externalize_append_char(ctx, '<') == false ||
119 _prop_object_externalize_append_cstring(ctx, tag) == false ||
120 _prop_object_externalize_append_char(ctx, '>') == false)
121 return (false);
122
123 return (true);
124 }
125
126 /*
127 * _prop_object_externalize_end_tag --
128 * Append an XML-style end tag to the externalize buffer.
129 */
130 bool
131 _prop_object_externalize_end_tag(
132 struct _prop_object_externalize_context *ctx, const char *tag)
133 {
134
135 if (_prop_object_externalize_append_char(ctx, '<') == false ||
136 _prop_object_externalize_append_char(ctx, '/') == false ||
137 _prop_object_externalize_append_cstring(ctx, tag) == false ||
138 _prop_object_externalize_append_char(ctx, '>') == false ||
139 _prop_object_externalize_append_char(ctx, '\n') == false)
140 return (false);
141
142 return (true);
143 }
144
145 /*
146 * _prop_object_externalize_empty_tag --
147 * Append an XML-style empty tag to the externalize buffer.
148 */
149 bool
150 _prop_object_externalize_empty_tag(
151 struct _prop_object_externalize_context *ctx, const char *tag)
152 {
153 unsigned int i;
154
155 for (i = 0; i < ctx->poec_depth; i++) {
156 if (_prop_object_externalize_append_char(ctx, '\t') == false)
157 return (false);
158 }
159
160 if (_prop_object_externalize_append_char(ctx, '<') == false ||
161 _prop_object_externalize_append_cstring(ctx, tag) == false ||
162 _prop_object_externalize_append_char(ctx, '/') == false ||
163 _prop_object_externalize_append_char(ctx, '>') == false ||
164 _prop_object_externalize_append_char(ctx, '\n') == false)
165 return (false);
166
167 return (true);
168 }
169
170 /*
171 * _prop_object_externalize_append_cstring --
172 * Append a C string to the externalize buffer.
173 */
174 bool
175 _prop_object_externalize_append_cstring(
176 struct _prop_object_externalize_context *ctx, const char *cp)
177 {
178
179 while (*cp != '\0') {
180 if (_prop_object_externalize_append_char(ctx,
181 (unsigned char) *cp) == false)
182 return (false);
183 cp++;
184 }
185
186 return (true);
187 }
188
189 /*
190 * _prop_object_externalize_append_encoded_cstring --
191 * Append an encoded C string to the externalize buffer.
192 */
193 bool
194 _prop_object_externalize_append_encoded_cstring(
195 struct _prop_object_externalize_context *ctx, const char *cp)
196 {
197
198 while (*cp != '\0') {
199 switch (*cp) {
200 case '<':
201 if (_prop_object_externalize_append_cstring(ctx,
202 "<") == false)
203 return (false);
204 break;
205 case '>':
206 if (_prop_object_externalize_append_cstring(ctx,
207 ">") == false)
208 return (false);
209 break;
210 case '&':
211 if (_prop_object_externalize_append_cstring(ctx,
212 "&") == false)
213 return (false);
214 break;
215 default:
216 if (_prop_object_externalize_append_char(ctx,
217 (unsigned char) *cp) == false)
218 return (false);
219 break;
220 }
221 cp++;
222 }
223
224 return (true);
225 }
226
227 #define BUF_EXPAND 256
228
229 /*
230 * _prop_object_externalize_append_char --
231 * Append a single character to the externalize buffer.
232 */
233 bool
234 _prop_object_externalize_append_char(
235 struct _prop_object_externalize_context *ctx, unsigned char c)
236 {
237
238 _PROP_ASSERT(ctx->poec_capacity != 0);
239 _PROP_ASSERT(ctx->poec_buf != NULL);
240 _PROP_ASSERT(ctx->poec_len <= ctx->poec_capacity);
241
242 if (ctx->poec_len == ctx->poec_capacity) {
243 char *cp = _PROP_REALLOC(ctx->poec_buf,
244 ctx->poec_capacity + BUF_EXPAND,
245 M_TEMP);
246 if (cp == NULL)
247 return (false);
248 ctx->poec_capacity = ctx->poec_capacity + BUF_EXPAND;
249 ctx->poec_buf = cp;
250 }
251
252 ctx->poec_buf[ctx->poec_len++] = c;
253
254 return (true);
255 }
256
257 /*
258 * _prop_object_externalize_header --
259 * Append the standard XML header to the externalize buffer.
260 */
261 bool
262 _prop_object_externalize_header(struct _prop_object_externalize_context *ctx)
263 {
264 static const char _plist_xml_header[] =
265 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
266 "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n";
267
268 if (_prop_object_externalize_append_cstring(ctx,
269 _plist_xml_header) == false ||
270 _prop_object_externalize_start_tag(ctx,
271 "plist version=\"1.0\"") == false ||
272 _prop_object_externalize_append_char(ctx, '\n') == false)
273 return (false);
274
275 return (true);
276 }
277
278 /*
279 * _prop_object_externalize_footer --
280 * Append the standard XML footer to the externalize buffer. This
281 * also NUL-terminates the buffer.
282 */
283 bool
284 _prop_object_externalize_footer(struct _prop_object_externalize_context *ctx)
285 {
286
287 if (_prop_object_externalize_end_tag(ctx, "plist") == false ||
288 _prop_object_externalize_append_char(ctx, '\0') == false)
289 return (false);
290
291 return (true);
292 }
293
294 /*
295 * _prop_object_externalize_context_alloc --
296 * Allocate an externalize context.
297 */
298 struct _prop_object_externalize_context *
299 _prop_object_externalize_context_alloc(void)
300 {
301 struct _prop_object_externalize_context *ctx;
302
303 ctx = _PROP_MALLOC(sizeof(*ctx), M_TEMP);
304 if (ctx != NULL) {
305 ctx->poec_buf = _PROP_MALLOC(BUF_EXPAND, M_TEMP);
306 if (ctx->poec_buf == NULL) {
307 _PROP_FREE(ctx, M_TEMP);
308 return (NULL);
309 }
310 ctx->poec_len = 0;
311 ctx->poec_capacity = BUF_EXPAND;
312 ctx->poec_depth = 0;
313 }
314 return (ctx);
315 }
316
317 /*
318 * _prop_object_externalize_context_free --
319 * Free an externalize context.
320 */
321 void
322 _prop_object_externalize_context_free(
323 struct _prop_object_externalize_context *ctx)
324 {
325
326 /* Buffer is always freed by the caller. */
327 _PROP_FREE(ctx, M_TEMP);
328 }
329
330 /*
331 * _prop_object_internalize_skip_comment --
332 * Skip the body and end tag of a comment.
333 */
334 static bool
335 _prop_object_internalize_skip_comment(
336 struct _prop_object_internalize_context *ctx)
337 {
338 const char *cp = ctx->poic_cp;
339
340 while (!_PROP_EOF(*cp)) {
341 if (cp[0] == '-' &&
342 cp[1] == '-' &&
343 cp[2] == '>') {
344 ctx->poic_cp = cp + 3;
345 return (true);
346 }
347 cp++;
348 }
349
350 return (false); /* ran out of buffer */
351 }
352
353 /*
354 * _prop_object_internalize_find_tag --
355 * Find the next tag in an XML stream. Optionally compare the found
356 * tag to an expected tag name. State of the context is undefined
357 * if this routine returns false. Upon success, the context points
358 * to the first octet after the tag.
359 */
360 bool
361 _prop_object_internalize_find_tag(struct _prop_object_internalize_context *ctx,
362 const char *tag, _prop_tag_type_t type)
363 {
364 const char *cp;
365 size_t taglen;
366
367 if (tag != NULL)
368 taglen = strlen(tag);
369 else
370 taglen = 0;
371
372 start_over:
373 cp = ctx->poic_cp;
374
375 /*
376 * Find the start of the tag.
377 */
378 while (_PROP_ISSPACE(*cp))
379 cp++;
380 if (_PROP_EOF(*cp))
381 return (false);
382
383 if (*cp != '<')
384 return (false);
385
386 ctx->poic_tag_start = cp++;
387 if (_PROP_EOF(*cp))
388 return (false);
389
390 if (*cp == '!') {
391 if (cp[1] != '-' || cp[2] != '-')
392 return (false);
393 /*
394 * Comment block -- only allowed if we are allowed to
395 * return a start tag.
396 */
397 if (type == _PROP_TAG_TYPE_END)
398 return (false);
399 ctx->poic_cp = cp + 3;
400 if (_prop_object_internalize_skip_comment(ctx) == false)
401 return (false);
402 goto start_over;
403 }
404
405 if (*cp == '/') {
406 if (type != _PROP_TAG_TYPE_END &&
407 type != _PROP_TAG_TYPE_EITHER)
408 return (false);
409 cp++;
410 if (_PROP_EOF(*cp))
411 return (false);
412 ctx->poic_tag_type = _PROP_TAG_TYPE_END;
413 } else {
414 if (type != _PROP_TAG_TYPE_START &&
415 type != _PROP_TAG_TYPE_EITHER)
416 return (false);
417 ctx->poic_tag_type = _PROP_TAG_TYPE_START;
418 }
419
420 ctx->poic_tagname = cp;
421
422 while (!_PROP_ISSPACE(*cp) && *cp != '/' && *cp != '>')
423 cp++;
424 if (_PROP_EOF(*cp))
425 return (false);
426
427 ctx->poic_tagname_len = cp - ctx->poic_tagname;
428
429 /* Make sure this is the tag we're looking for. */
430 if (tag != NULL &&
431 (taglen != ctx->poic_tagname_len ||
432 memcmp(tag, ctx->poic_tagname, taglen) != 0))
433 return (false);
434
435 /* Check for empty tag. */
436 if (*cp == '/') {
437 if (ctx->poic_tag_type != _PROP_TAG_TYPE_START)
438 return(false); /* only valid on start tags */
439 ctx->poic_is_empty_element = true;
440 cp++;
441 if (_PROP_EOF(*cp) || *cp != '>')
442 return (false);
443 } else
444 ctx->poic_is_empty_element = false;
445
446 /* Easy case of no arguments. */
447 if (*cp == '>') {
448 ctx->poic_tagattr = NULL;
449 ctx->poic_tagattr_len = 0;
450 ctx->poic_tagattrval = NULL;
451 ctx->poic_tagattrval_len = 0;
452 ctx->poic_cp = cp + 1;
453 return (true);
454 }
455
456 _PROP_ASSERT(!_PROP_EOF(*cp));
457 cp++;
458 if (_PROP_EOF(*cp))
459 return (false);
460
461 while (_PROP_ISSPACE(*cp))
462 cp++;
463 if (_PROP_EOF(*cp))
464 return (false);
465
466 ctx->poic_tagattr = cp;
467
468 while (!_PROP_ISSPACE(*cp) && *cp != '=')
469 cp++;
470 if (_PROP_EOF(*cp))
471 return (false);
472
473 ctx->poic_tagattr_len = cp - ctx->poic_tagattr;
474
475 cp++;
476 if (*cp != '\"')
477 return (false);
478 cp++;
479 if (_PROP_EOF(*cp))
480 return (false);
481
482 ctx->poic_tagattrval = cp;
483 while (*cp != '\"')
484 cp++;
485 if (_PROP_EOF(*cp))
486 return (false);
487 ctx->poic_tagattrval_len = cp - ctx->poic_tagattrval;
488
489 cp++;
490 if (*cp != '>')
491 return (false);
492
493 ctx->poic_cp = cp + 1;
494 return (true);
495 }
496
497 /*
498 * _prop_object_internalize_decode_string --
499 * Decode an encoded string.
500 */
501 bool
502 _prop_object_internalize_decode_string(
503 struct _prop_object_internalize_context *ctx,
504 char *target, size_t targsize, size_t *sizep,
505 const char **cpp)
506 {
507 const char *src;
508 size_t tarindex;
509 char c;
510
511 tarindex = 0;
512 src = ctx->poic_cp;
513
514 for (;;) {
515 if (_PROP_EOF(*src))
516 return (false);
517 if (*src == '<') {
518 break;
519 }
520
521 if ((c = *src) == '&') {
522 if (src[1] == 'a' &&
523 src[2] == 'm' &&
524 src[3] == 'p' &&
525 src[4] == ';') {
526 c = '&';
527 src += 5;
528 } else if (src[1] == 'l' &&
529 src[2] == 't' &&
530 src[3] == ';') {
531 c = '<';
532 src += 4;
533 } else if (src[1] == 'g' &&
534 src[2] == 't' &&
535 src[3] == ';') {
536 c = '>';
537 src += 4;
538 } else if (src[1] == 'a' &&
539 src[2] == 'p' &&
540 src[3] == 'o' &&
541 src[4] == 's' &&
542 src[5] == ';') {
543 c = '\'';
544 src += 6;
545 } else if (src[1] == 'q' &&
546 src[2] == 'u' &&
547 src[3] == 'o' &&
548 src[4] == 't' &&
549 src[5] == ';') {
550 c = '\"';
551 src += 6;
552 } else
553 return (false);
554 } else
555 src++;
556 if (target) {
557 if (tarindex >= targsize)
558 return (false);
559 target[tarindex] = c;
560 }
561 tarindex++;
562 }
563
564 _PROP_ASSERT(*src == '<');
565 if (sizep != NULL)
566 *sizep = tarindex;
567 if (cpp != NULL)
568 *cpp = src;
569
570 return (true);
571 }
572
573 /*
574 * _prop_object_internalize_match --
575 * Returns true if the two character streams match.
576 */
577 bool
578 _prop_object_internalize_match(const char *str1, size_t len1,
579 const char *str2, size_t len2)
580 {
581
582 return (len1 == len2 && memcmp(str1, str2, len1) == 0);
583 }
584
585 #define INTERNALIZER(t, f) \
586 { t, sizeof(t) - 1, f }
587
588 static const struct _prop_object_internalizer {
589 const char *poi_tag;
590 size_t poi_taglen;
591 prop_object_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 */
616 prop_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
627 match_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) {
637 while (_prop_stack_pop(&stack, &obj, &data, &iter)) {
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, &data, &iter)) {
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
660 prop_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 */
716 struct _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 */
782 void
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 */
796 static 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 */
843 bool
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
851 if (len > SSIZE_MAX) {
852 errno = EFBIG;
853 return (false);
854 }
855
856 /*
857 * Get the directory name where the file is to be written
858 * and create the temporary file.
859 *
860 * We don't use mkstemp() because mkstemp() always creates the
861 * file with mode 0600. We do, however, use mktemp() safely.
862 */
863 again:
864 _prop_object_externalize_file_dirname(fname, tname);
865 if (strlcat(tname, "/.plistXXXXXX", sizeof(tname)) >= sizeof(tname)) {
866 errno = ENAMETOOLONG;
867 return (false);
868 }
869 if (mktemp(tname) == NULL)
870 return (false);
871 if ((fd = open(tname, O_CREAT|O_RDWR|O_EXCL, 0666)) == -1) {
872 if (errno == EEXIST)
873 goto again;
874 return (false);
875 }
876
877 if (write(fd, xml, len) != (ssize_t)len)
878 goto bad;
879
880 if (fsync(fd) == -1)
881 goto bad;
882
883 (void) close(fd);
884 fd = -1;
885
886 if (rename(tname, fname) == -1)
887 goto bad;
888
889 return (true);
890
891 bad:
892 save_errno = errno;
893 if (fd != -1)
894 (void) close(fd);
895 (void) unlink(tname);
896 errno = save_errno;
897 return (false);
898 }
899
900 /*
901 * _prop_object_internalize_map_file --
902 * Map a file for the purpose of internalizing it.
903 */
904 struct _prop_object_internalize_mapped_file *
905 _prop_object_internalize_map_file(const char *fname)
906 {
907 struct stat sb;
908 struct _prop_object_internalize_mapped_file *mf;
909 size_t pgsize = (size_t)sysconf(_SC_PAGESIZE);
910 size_t pgmask = pgsize - 1;
911 bool need_guard = false;
912 int fd;
913
914 mf = _PROP_MALLOC(sizeof(*mf), M_TEMP);
915 if (mf == NULL)
916 return (NULL);
917
918 fd = open(fname, O_RDONLY, 0400);
919 if (fd == -1) {
920 _PROP_FREE(mf, M_TEMP);
921 return (NULL);
922 }
923
924 if (fstat(fd, &sb) == -1) {
925 (void) close(fd);
926 _PROP_FREE(mf, M_TEMP);
927 return (NULL);
928 }
929 mf->poimf_mapsize = ((size_t)sb.st_size + pgmask) & ~pgmask;
930 if (mf->poimf_mapsize < sb.st_size) {
931 (void) close(fd);
932 _PROP_FREE(mf, M_TEMP);
933 return (NULL);
934 }
935
936 /*
937 * If the file length is an integral number of pages, then we
938 * need to map a guard page at the end in order to provide the
939 * necessary NUL-termination of the buffer.
940 */
941 if ((sb.st_size & pgmask) == 0)
942 need_guard = true;
943
944 mf->poimf_xml = mmap(NULL, need_guard ? mf->poimf_mapsize + pgsize
945 : mf->poimf_mapsize,
946 PROT_READ, MAP_FILE|MAP_SHARED, fd, (off_t)0);
947 (void) close(fd);
948 if (mf->poimf_xml == MAP_FAILED) {
949 _PROP_FREE(mf, M_TEMP);
950 return (NULL);
951 }
952 (void) madvise(mf->poimf_xml, mf->poimf_mapsize, MADV_SEQUENTIAL);
953
954 if (need_guard) {
955 if (mmap(mf->poimf_xml + mf->poimf_mapsize,
956 pgsize, PROT_READ,
957 MAP_ANON|MAP_PRIVATE|MAP_FIXED, -1,
958 (off_t)0) == MAP_FAILED) {
959 (void) munmap(mf->poimf_xml, mf->poimf_mapsize);
960 _PROP_FREE(mf, M_TEMP);
961 return (NULL);
962 }
963 mf->poimf_mapsize += pgsize;
964 }
965
966 return (mf);
967 }
968
969 /*
970 * _prop_object_internalize_unmap_file --
971 * Unmap a file previously mapped for internalizing.
972 */
973 void
974 _prop_object_internalize_unmap_file(
975 struct _prop_object_internalize_mapped_file *mf)
976 {
977
978 (void) madvise(mf->poimf_xml, mf->poimf_mapsize, MADV_DONTNEED);
979 (void) munmap(mf->poimf_xml, mf->poimf_mapsize);
980 _PROP_FREE(mf, M_TEMP);
981 }
982 #endif /* !_KERNEL && !_STANDALONE */
983
984 /*
985 * Retain / release serialization --
986 *
987 * Eventually we would like to use atomic operations. But until we have
988 * an MI API for them that is common to userland and the kernel, we will
989 * use a lock instead.
990 *
991 * We use a single global mutex for all serialization. In the kernel, because
992 * we are still under a biglock, this will basically never contend (properties
993 * cannot be manipulated at interrupt level). In userland, this will cost
994 * nothing for single-threaded programs. For multi-threaded programs, there
995 * could be contention, but it probably won't cost that much unless the program
996 * makes heavy use of property lists.
997 */
998 _PROP_MUTEX_DECL_STATIC(_prop_refcnt_mutex)
999 #define _PROP_REFCNT_LOCK() _PROP_MUTEX_LOCK(_prop_refcnt_mutex)
1000 #define _PROP_REFCNT_UNLOCK() _PROP_MUTEX_UNLOCK(_prop_refcnt_mutex)
1001
1002 /*
1003 * prop_object_retain --
1004 * Increment the reference count on an object.
1005 */
1006 void
1007 prop_object_retain(prop_object_t obj)
1008 {
1009 struct _prop_object *po = obj;
1010 uint32_t ocnt;
1011
1012 _PROP_REFCNT_LOCK();
1013 ocnt = po->po_refcnt++;
1014 _PROP_REFCNT_UNLOCK();
1015
1016 _PROP_ASSERT(ocnt != 0xffffffffU);
1017 }
1018
1019 /*
1020 * prop_object_release_emergency
1021 * A direct free with prop_object_release failed.
1022 * Walk down the tree until a leaf is found and
1023 * free that. Do not recurse to avoid stack overflows.
1024 *
1025 * This is a slow edge condition, but necessary to
1026 * guaranty that an object can always be freed.
1027 */
1028 static void
1029 prop_object_release_emergency(prop_object_t obj)
1030 {
1031 struct _prop_object *po;
1032 prop_object_t parent = NULL;
1033 uint32_t ocnt;
1034
1035 for (;;) {
1036 po = obj;
1037 _PROP_ASSERT(obj);
1038
1039 _PROP_REFCNT_LOCK();
1040 ocnt = po->po_refcnt--;
1041 _PROP_REFCNT_UNLOCK();
1042
1043 _PROP_ASSERT(ocnt != 0);
1044 if (ocnt != 1)
1045 break;
1046
1047 _PROP_ASSERT(po->po_type);
1048 if ((po->po_type->pot_free)(NULL, &obj) == 0)
1049 break;
1050
1051 parent = po;
1052 _PROP_REFCNT_LOCK();
1053 ++po->po_refcnt;
1054 _PROP_REFCNT_UNLOCK();
1055 }
1056 _PROP_ASSERT(parent);
1057 /* One object was just freed. */
1058 po = parent;
1059 (*po->po_type->pot_emergency_free)(parent);
1060 }
1061
1062 /*
1063 * prop_object_release --
1064 * Decrement the reference count on an object.
1065 *
1066 * Free the object if we are releasing the final
1067 * reference.
1068 */
1069 void
1070 prop_object_release(prop_object_t obj)
1071 {
1072 struct _prop_object *po;
1073 struct _prop_stack stack;
1074 void *dummy1, *dummy2;
1075 int ret;
1076 uint32_t ocnt;
1077
1078 _prop_stack_init(&stack);
1079
1080 do {
1081 do {
1082 po = obj;
1083 _PROP_ASSERT(obj);
1084
1085 _PROP_REFCNT_LOCK();
1086 ocnt = po->po_refcnt--;
1087 _PROP_REFCNT_UNLOCK();
1088
1089 _PROP_ASSERT(ocnt != 0);
1090 if (ocnt != 1) {
1091 ret = 0;
1092 break;
1093 }
1094
1095 ret = (po->po_type->pot_free)(&stack, &obj);
1096
1097 if (ret == _PROP_OBJECT_FREE_DONE)
1098 break;
1099
1100 _PROP_REFCNT_LOCK();
1101 ++po->po_refcnt;
1102 _PROP_REFCNT_UNLOCK();
1103 } while (ret == _PROP_OBJECT_FREE_RECURSE);
1104 if (ret == _PROP_OBJECT_FREE_FAILED)
1105 prop_object_release_emergency(obj);
1106 } while (_prop_stack_pop(&stack, &obj, &dummy1, &dummy2));
1107 }
1108
1109 /*
1110 * prop_object_type --
1111 * Return the type of an object.
1112 */
1113 prop_type_t
1114 prop_object_type(prop_object_t obj)
1115 {
1116 struct _prop_object *po = obj;
1117
1118 if (obj == NULL)
1119 return (PROP_TYPE_UNKNOWN);
1120
1121 return (po->po_type->pot_type);
1122 }
1123
1124 /*
1125 * prop_object_equals --
1126 * Returns true if thw two objects are equivalent.
1127 */
1128 bool
1129 prop_object_equals(prop_object_t obj1, prop_object_t obj2)
1130 {
1131 struct _prop_object *po1 = obj1;
1132 struct _prop_object *po2 = obj2;
1133
1134 if (po1->po_type != po2->po_type)
1135 return (false);
1136
1137 return ((*po1->po_type->pot_equals)(obj1, obj2));
1138 }
1139
1140 /*
1141 * prop_object_iterator_next --
1142 * Return the next item during an iteration.
1143 */
1144 prop_object_t
1145 prop_object_iterator_next(prop_object_iterator_t pi)
1146 {
1147
1148 return ((*pi->pi_next_object)(pi));
1149 }
1150
1151 /*
1152 * prop_object_iterator_reset --
1153 * Reset the iterator to the first object so as to restart
1154 * iteration.
1155 */
1156 void
1157 prop_object_iterator_reset(prop_object_iterator_t pi)
1158 {
1159
1160 (*pi->pi_reset)(pi);
1161 }
1162
1163 /*
1164 * prop_object_iterator_release --
1165 * Release the object iterator.
1166 */
1167 void
1168 prop_object_iterator_release(prop_object_iterator_t pi)
1169 {
1170
1171 prop_object_release(pi->pi_obj);
1172 _PROP_FREE(pi, M_TEMP);
1173 }
1174