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