prop_object.c revision 1.41 1 /* $NetBSD: prop_object.c,v 1.41 2025/05/13 15:00:16 thorpej Exp $ */
2
3 /*-
4 * Copyright (c) 2006, 2007, 2025 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Jason R. Thorpe.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32 #include "prop_object_impl.h"
33 #include <prop/prop_object.h>
34
35 #ifdef _PROP_NEED_REFCNT_MTX
36 static pthread_mutex_t _prop_refcnt_mtx = PTHREAD_MUTEX_INITIALIZER;
37 #endif /* _PROP_NEED_REFCNT_MTX */
38
39 #if !defined(_KERNEL) && !defined(_STANDALONE)
40 #include <sys/mman.h>
41 #include <sys/stat.h>
42 #include <errno.h>
43 #include <fcntl.h>
44 #include <limits.h>
45 #include <unistd.h>
46 #endif
47
48 #ifdef _STANDALONE
49 void *
50 _prop_standalone_calloc(size_t size)
51 {
52 void *rv;
53
54 rv = alloc(size);
55 if (rv != NULL)
56 memset(rv, 0, size);
57
58 return (rv);
59 }
60
61 void *
62 _prop_standalone_realloc(void *v, size_t size)
63 {
64 void *rv;
65
66 rv = alloc(size);
67 if (rv != NULL) {
68 memcpy(rv, v, size); /* XXX */
69 dealloc(v, 0); /* XXX */
70 }
71
72 return (rv);
73 }
74 #endif /* _STANDALONE */
75
76 /*
77 * _prop_object_init --
78 * Initialize an object. Called when sub-classes create
79 * an instance.
80 */
81 void
82 _prop_object_init(struct _prop_object *po, const struct _prop_object_type *pot)
83 {
84
85 po->po_type = pot;
86 po->po_refcnt = 1;
87 }
88
89 /*
90 * _prop_object_fini --
91 * Finalize an object. Called when sub-classes destroy
92 * an instance.
93 */
94 /*ARGSUSED*/
95 void
96 _prop_object_fini(struct _prop_object *po _PROP_ARG_UNUSED)
97 {
98 /* Nothing to do, currently. */
99 }
100
101 /*
102 * _prop_object_externalize_start_line --
103 * Append the start-of-line character sequence.
104 */
105 bool
106 _prop_object_externalize_start_line(
107 struct _prop_object_externalize_context *ctx)
108 {
109 unsigned int i;
110
111 for (i = 0; i < ctx->poec_depth; i++) {
112 if (_prop_object_externalize_append_char(ctx, '\t') == false) {
113 return false;
114 }
115 }
116 return true;
117 }
118
119 /*
120 * _prop_object_externalize_end_line --
121 * Append the end-of-line character sequence.
122 */
123 bool
124 _prop_object_externalize_end_line(
125 struct _prop_object_externalize_context *ctx, const char *trailer)
126 {
127 if (trailer != NULL &&
128 _prop_object_externalize_append_cstring(ctx, trailer) == false) {
129 return false;
130 }
131 return _prop_object_externalize_append_char(ctx, '\n');
132 }
133
134 /*
135 * _prop_object_externalize_start_tag --
136 * Append an item's start tag to the externalize buffer.
137 */
138 bool
139 _prop_object_externalize_start_tag(
140 struct _prop_object_externalize_context *ctx,
141 const struct _prop_object_type_tags *tags,
142 const char *tagattrs)
143 {
144 bool rv;
145
146 _PROP_ASSERT(ctx->poec_format == PROP_FORMAT_XML ||
147 ctx->poec_format == PROP_FORMAT_JSON);
148
149 switch (ctx->poec_format) {
150 case PROP_FORMAT_JSON:
151 rv = tags->json_open_tag == NULL ||
152 _prop_object_externalize_append_cstring(ctx,
153 tags->json_open_tag);
154 break;
155
156 default: /* XML */
157 rv = _prop_object_externalize_append_char(ctx, '<') &&
158 _prop_object_externalize_append_cstring(ctx,
159 tags->xml_tag) &&
160 (tagattrs == NULL ||
161 (_prop_object_externalize_append_char(ctx, ' ') &&
162 _prop_object_externalize_append_cstring(ctx,
163 tagattrs))) &&
164 _prop_object_externalize_append_char(ctx, '>');
165 break;
166 }
167
168 return rv;
169 }
170
171 /*
172 * _prop_object_externalize_end_tag --
173 * Append an item's end tag to the externalize buffer.
174 */
175 bool
176 _prop_object_externalize_end_tag(
177 struct _prop_object_externalize_context *ctx,
178 const struct _prop_object_type_tags *tags)
179 {
180 bool rv;
181
182 _PROP_ASSERT(ctx->poec_format == PROP_FORMAT_XML ||
183 ctx->poec_format == PROP_FORMAT_JSON);
184
185 switch (ctx->poec_format) {
186 case PROP_FORMAT_JSON:
187 rv = tags->json_close_tag == NULL ||
188 _prop_object_externalize_append_cstring(ctx,
189 tags->json_close_tag);
190 break;
191
192 default: /* XML */
193 rv = _prop_object_externalize_append_char(ctx, '<') &&
194 _prop_object_externalize_append_char(ctx, '/') &&
195 _prop_object_externalize_append_cstring(ctx,
196 tags->xml_tag) &&
197 _prop_object_externalize_append_char(ctx, '>');
198 break;
199 }
200
201 return rv;
202 }
203
204 /*
205 * _prop_object_externalize_empty_tag --
206 * Append an item's empty tag to the externalize buffer.
207 */
208 bool
209 _prop_object_externalize_empty_tag(
210 struct _prop_object_externalize_context *ctx,
211 const struct _prop_object_type_tags *tags)
212 {
213 bool rv;
214
215 _PROP_ASSERT(ctx->poec_format == PROP_FORMAT_XML ||
216 ctx->poec_format == PROP_FORMAT_JSON);
217
218 switch (ctx->poec_format) {
219 case PROP_FORMAT_JSON:
220 if (tags->json_open_tag == NULL ||
221 _prop_object_externalize_append_cstring(ctx,
222 tags->json_open_tag) == false) {
223 return false;
224 }
225 if (tags->json_empty_sep != NULL &&
226 _prop_object_externalize_append_cstring(ctx,
227 tags->json_empty_sep) == false) {
228 return false;
229 }
230 if (tags->json_close_tag != NULL) {
231 rv = _prop_object_externalize_append_cstring(ctx,
232 tags->json_close_tag);
233 } else {
234 rv = true;
235 }
236 break;
237
238 default: /* XML */
239 rv = _prop_object_externalize_append_char(ctx, '<') &&
240 _prop_object_externalize_append_cstring(ctx,
241 tags->xml_tag) &&
242 _prop_object_externalize_append_char(ctx, '/') &&
243 _prop_object_externalize_append_char(ctx, '>');
244 break;
245 }
246
247 return rv;
248 }
249
250 /*
251 * _prop_object_externalize_append_cstring --
252 * Append a C string to the externalize buffer.
253 */
254 bool
255 _prop_object_externalize_append_cstring(
256 struct _prop_object_externalize_context *ctx, const char *cp)
257 {
258
259 while (*cp != '\0') {
260 if (_prop_object_externalize_append_char(ctx,
261 (unsigned char) *cp) == false)
262 return (false);
263 cp++;
264 }
265
266 return (true);
267 }
268
269 /*
270 * _prop_object_externalize_append_encoded_cstring --
271 * Append an encoded C string to the externalize buffer.
272 */
273 static bool
274 _prop_object_externalize_append_encoded_cstring_xml(
275 struct _prop_object_externalize_context *ctx, const char *cp)
276 {
277
278 while (*cp != '\0') {
279 switch (*cp) {
280 case '<':
281 if (_prop_object_externalize_append_cstring(ctx,
282 "<") == false)
283 return (false);
284 break;
285 case '>':
286 if (_prop_object_externalize_append_cstring(ctx,
287 ">") == false)
288 return (false);
289 break;
290 case '&':
291 if (_prop_object_externalize_append_cstring(ctx,
292 "&") == false)
293 return (false);
294 break;
295 default:
296 if (_prop_object_externalize_append_char(ctx,
297 (unsigned char) *cp) == false)
298 return (false);
299 break;
300 }
301 cp++;
302 }
303
304 return (true);
305 }
306
307 static bool
308 _prop_object_externalize_append_escu(
309 struct _prop_object_externalize_context *ctx, uint16_t val)
310 {
311 char tmpstr[sizeof("\\uXXXX")];
312
313 snprintf(tmpstr, sizeof(tmpstr), "\\u%04X", val);
314 return _prop_object_externalize_append_cstring(ctx, tmpstr);
315 }
316
317 static bool
318 _prop_object_externalize_append_encoded_cstring_json(
319 struct _prop_object_externalize_context *ctx, const char *cp)
320 {
321 bool esc;
322 unsigned char ch;
323
324 while ((ch = *cp) != '\0') {
325 esc = true;
326 switch (ch) {
327 /*
328 * First, the two explicit exclusions. They must be
329 * escaped.
330 */
331 case '"': /* U+0022 quotation mark */
332 goto emit;
333
334 case '\\': /* U+005C reverse solidus */
335 goto emit;
336
337 /*
338 * And some special cases that are explcit in the grammar.
339 */
340 case '/': /* U+002F solidus (XXX this one seems silly) */
341 goto emit;
342
343 case 0x08: /* U+0008 backspace */
344 ch = 'b';
345 goto emit;
346
347 case 0x0c: /* U+000C form feed */
348 ch = 'f';
349 goto emit;
350
351 case 0x0a: /* U+000A line feed */
352 ch = 'n';
353 goto emit;
354
355 case 0x0d: /* U+000D carriage return */
356 ch = 'r';
357 goto emit;
358
359 case 0x09: /* U+0009 tab */
360 ch = 't';
361 goto emit;
362
363 default:
364 /*
365 * \u-escape all other single-byte ASCII control
366 * characters, per RFC 8259:
367 *
368 * <quote>
369 * All Unicode characters may be placed within the
370 * quotation marks, except for the characters that
371 * MUST be escaped: quotation mark, reverse solidus,
372 * and the control characters (U+0000 through U+001F).
373 * </quote>
374 */
375 if (ch < 0x20) {
376 if (_prop_object_externalize_append_escu(ctx,
377 ch) == false) {
378 return false;
379 }
380 break;
381 }
382 /*
383 * We're going to just treat everything else like
384 * UTF-8 (we've been handed a C-string, after all)
385 * and pretend it will all be OK.
386 */
387 esc = false;
388 emit:
389 if ((esc && _prop_object_externalize_append_char(ctx,
390 '\\') == false) ||
391 _prop_object_externalize_append_char(ctx,
392 ch) == false) {
393 return false;
394 }
395 break;
396 }
397 cp++;
398 }
399
400 return true;
401 }
402
403 bool
404 _prop_object_externalize_append_encoded_cstring(
405 struct _prop_object_externalize_context *ctx, const char *cp)
406 {
407 _PROP_ASSERT(ctx->poec_format == PROP_FORMAT_XML ||
408 ctx->poec_format == PROP_FORMAT_JSON);
409
410 switch (ctx->poec_format) {
411 case PROP_FORMAT_JSON:
412 return _prop_object_externalize_append_encoded_cstring_json(ctx,
413 cp);
414 default:
415 return _prop_object_externalize_append_encoded_cstring_xml(ctx,
416 cp);
417 }
418 }
419
420 #define BUF_EXPAND 256
421
422 /*
423 * _prop_object_externalize_append_char --
424 * Append a single character to the externalize buffer.
425 */
426 bool
427 _prop_object_externalize_append_char(
428 struct _prop_object_externalize_context *ctx, unsigned char c)
429 {
430
431 _PROP_ASSERT(ctx->poec_capacity != 0);
432 _PROP_ASSERT(ctx->poec_buf != NULL);
433 _PROP_ASSERT(ctx->poec_len <= ctx->poec_capacity);
434
435 if (ctx->poec_len == ctx->poec_capacity) {
436 char *cp = _PROP_REALLOC(ctx->poec_buf,
437 ctx->poec_capacity + BUF_EXPAND,
438 M_TEMP);
439 if (cp == NULL)
440 return (false);
441 ctx->poec_capacity = ctx->poec_capacity + BUF_EXPAND;
442 ctx->poec_buf = cp;
443 }
444
445 ctx->poec_buf[ctx->poec_len++] = c;
446
447 return (true);
448 }
449
450 static const struct _prop_object_type_tags _plist_type_tags = {
451 .xml_tag = "plist",
452 };
453
454 /*
455 * _prop_object_externalize_header --
456 * Append the standard XML header to the externalize buffer.
457 */
458 bool
459 _prop_object_externalize_header(struct _prop_object_externalize_context *ctx)
460 {
461 static const char _plist_xml_header[] =
462 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
463 "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n";
464
465 if (ctx->poec_format != PROP_FORMAT_XML) {
466 return true;
467 }
468
469 if (_prop_object_externalize_append_cstring(ctx,
470 _plist_xml_header) == false ||
471 _prop_object_externalize_start_tag(ctx,
472 &_plist_type_tags,
473 "version=\"1.0\"") == false ||
474 _prop_object_externalize_append_char(ctx, '\n') == false)
475 return (false);
476
477 return (true);
478 }
479
480 /*
481 * _prop_object_externalize_footer --
482 * Append the standard XML footer to the externalize buffer. This
483 * also NUL-terminates the buffer.
484 */
485 bool
486 _prop_object_externalize_footer(struct _prop_object_externalize_context *ctx)
487 {
488 if (_prop_object_externalize_end_line(ctx, NULL) == false) {
489 return false;
490 }
491
492 if (ctx->poec_format == PROP_FORMAT_XML) {
493 if (_prop_object_externalize_end_tag(ctx,
494 &_plist_type_tags) == false ||
495 _prop_object_externalize_end_line(ctx, NULL) == false) {
496 return false;
497 }
498 }
499
500 return _prop_object_externalize_append_char(ctx, '\0');
501 }
502
503 /*
504 * _prop_object_externalize_context_alloc --
505 * Allocate an externalize context.
506 */
507 struct _prop_object_externalize_context *
508 _prop_object_externalize_context_alloc(prop_format_t fmt)
509 {
510 struct _prop_object_externalize_context *ctx;
511
512 ctx = _PROP_MALLOC(sizeof(*ctx), M_TEMP);
513 if (ctx != NULL) {
514 ctx->poec_buf = _PROP_MALLOC(BUF_EXPAND, M_TEMP);
515 if (ctx->poec_buf == NULL) {
516 _PROP_FREE(ctx, M_TEMP);
517 return (NULL);
518 }
519 ctx->poec_len = 0;
520 ctx->poec_capacity = BUF_EXPAND;
521 ctx->poec_depth = 0;
522 ctx->poec_format = fmt;
523 }
524 return (ctx);
525 }
526
527 /*
528 * _prop_object_externalize_context_free --
529 * Free an externalize context.
530 */
531 void
532 _prop_object_externalize_context_free(
533 struct _prop_object_externalize_context *ctx)
534 {
535
536 /* Buffer is always freed by the caller. */
537 _PROP_FREE(ctx, M_TEMP);
538 }
539
540 /*
541 * _prop_object_externalize --
542 * Externalize an object, returning a NUL-terminated buffer
543 * containing the serialized data in either XML or JSON format.
544 * The buffer is allocated with the M_TEMP memory type.
545 */
546 char *
547 _prop_object_externalize(struct _prop_object *obj, prop_format_t fmt)
548 {
549 struct _prop_object_externalize_context *ctx;
550 char *cp = NULL;
551
552 if (obj == NULL || obj->po_type->pot_extern == NULL) {
553 return NULL;
554 }
555 if (fmt != PROP_FORMAT_XML && fmt != PROP_FORMAT_JSON) {
556 return NULL;
557 }
558
559 ctx = _prop_object_externalize_context_alloc(fmt);
560 if (ctx == NULL) {
561 return NULL;
562 }
563
564 if (_prop_object_externalize_header(ctx) == false ||
565 obj->po_type->pot_extern(ctx, obj) == false ||
566 _prop_object_externalize_footer(ctx) == false) {
567 /* We are responsible for releasing the buffer. */
568 _PROP_FREE(ctx->poec_buf, M_TEMP);
569 goto bad;
570 }
571
572 cp = ctx->poec_buf;
573 bad:
574 _prop_object_externalize_context_free(ctx);
575 return cp;
576 }
577
578 /*
579 * _prop_object_internalize_skip_comment --
580 * Skip the body and end tag of a comment.
581 */
582 static bool
583 _prop_object_internalize_skip_comment(
584 struct _prop_object_internalize_context *ctx)
585 {
586 const char *cp = ctx->poic_cp;
587
588 while (!_PROP_EOF(*cp)) {
589 if (cp[0] == '-' &&
590 cp[1] == '-' &&
591 cp[2] == '>') {
592 ctx->poic_cp = cp + 3;
593 return (true);
594 }
595 cp++;
596 }
597
598 return (false); /* ran out of buffer */
599 }
600
601 /*
602 * _prop_object_internalize_find_tag --
603 * Find the next tag in an XML stream. Optionally compare the found
604 * tag to an expected tag name. State of the context is undefined
605 * if this routine returns false. Upon success, the context points
606 * to the first octet after the tag.
607 */
608 bool
609 _prop_object_internalize_find_tag(struct _prop_object_internalize_context *ctx,
610 const char *tag, _prop_tag_type_t type)
611 {
612 const char *cp;
613 size_t taglen;
614
615 if (tag != NULL)
616 taglen = strlen(tag);
617 else
618 taglen = 0;
619
620 start_over:
621 cp = ctx->poic_cp;
622
623 /*
624 * Find the start of the tag.
625 */
626 cp = _prop_object_internalize_skip_whitespace(cp);
627 if (*cp != '<')
628 return (false);
629
630 ctx->poic_tag_start = cp++;
631 if (_PROP_EOF(*cp))
632 return (false);
633
634 if (*cp == '!') {
635 if (cp[1] != '-' || cp[2] != '-')
636 return (false);
637 /*
638 * Comment block -- only allowed if we are allowed to
639 * return a start tag.
640 */
641 if (type == _PROP_TAG_TYPE_END)
642 return (false);
643 ctx->poic_cp = cp + 3;
644 if (_prop_object_internalize_skip_comment(ctx) == false)
645 return (false);
646 goto start_over;
647 }
648
649 if (*cp == '/') {
650 if (type != _PROP_TAG_TYPE_END &&
651 type != _PROP_TAG_TYPE_EITHER)
652 return (false);
653 cp++;
654 if (_PROP_EOF(*cp))
655 return (false);
656 ctx->poic_tag_type = _PROP_TAG_TYPE_END;
657 } else {
658 if (type != _PROP_TAG_TYPE_START &&
659 type != _PROP_TAG_TYPE_EITHER)
660 return (false);
661 ctx->poic_tag_type = _PROP_TAG_TYPE_START;
662 }
663
664 ctx->poic_tagname = cp;
665
666 while (!_PROP_ISSPACE(*cp) && *cp != '/' && *cp != '>') {
667 if (_PROP_EOF(*cp))
668 return (false);
669 cp++;
670 }
671
672 ctx->poic_tagname_len = cp - ctx->poic_tagname;
673
674 /* Make sure this is the tag we're looking for. */
675 if (tag != NULL &&
676 (taglen != ctx->poic_tagname_len ||
677 memcmp(tag, ctx->poic_tagname, taglen) != 0))
678 return (false);
679
680 /* Check for empty tag. */
681 if (*cp == '/') {
682 if (ctx->poic_tag_type != _PROP_TAG_TYPE_START)
683 return(false); /* only valid on start tags */
684 ctx->poic_is_empty_element = true;
685 cp++;
686 if (_PROP_EOF(*cp) || *cp != '>')
687 return (false);
688 } else
689 ctx->poic_is_empty_element = false;
690
691 /* Easy case of no arguments. */
692 if (*cp == '>') {
693 ctx->poic_tagattr = NULL;
694 ctx->poic_tagattr_len = 0;
695 ctx->poic_tagattrval = NULL;
696 ctx->poic_tagattrval_len = 0;
697 ctx->poic_cp = cp + 1;
698 return (true);
699 }
700
701 _PROP_ASSERT(!_PROP_EOF(*cp));
702 cp++;
703 if (_PROP_EOF(*cp))
704 return (false);
705
706 cp = _prop_object_internalize_skip_whitespace(cp);
707 if (_PROP_EOF(*cp))
708 return (false);
709
710 ctx->poic_tagattr = cp;
711
712 while (!_PROP_ISSPACE(*cp) && *cp != '=') {
713 if (_PROP_EOF(*cp))
714 return (false);
715 cp++;
716 }
717
718 ctx->poic_tagattr_len = cp - ctx->poic_tagattr;
719
720 cp++;
721 if (*cp != '\"')
722 return (false);
723 cp++;
724 if (_PROP_EOF(*cp))
725 return (false);
726
727 ctx->poic_tagattrval = cp;
728 while (*cp != '\"') {
729 if (_PROP_EOF(*cp))
730 return (false);
731 cp++;
732 }
733 ctx->poic_tagattrval_len = cp - ctx->poic_tagattrval;
734
735 cp++;
736 if (*cp != '>')
737 return (false);
738
739 ctx->poic_cp = cp + 1;
740 return (true);
741 }
742
743 /*
744 * _prop_object_internalize_decode_string --
745 * Decode an encoded string.
746 */
747
748 #define ADDCHAR(x) \
749 do { \
750 if (target) { \
751 if (tarindex >= targsize) { \
752 return false; \
753 } \
754 target[tarindex] = (x); \
755 } \
756 tarindex++; \
757 } while (/*CONSTCOND*/0)
758
759 static bool
760 _prop_object_internalize_decode_string_xml(
761 struct _prop_object_internalize_context *ctx,
762 char *target, size_t targsize, size_t *sizep,
763 const char **cpp)
764 {
765 const char *src;
766 size_t tarindex;
767 char c;
768
769 tarindex = 0;
770 src = ctx->poic_cp;
771
772 for (;;) {
773 if (_PROP_EOF(*src))
774 return (false);
775 if (*src == '<') {
776 break;
777 }
778
779 if ((c = *src) == '&') {
780 if (src[1] == 'a' &&
781 src[2] == 'm' &&
782 src[3] == 'p' &&
783 src[4] == ';') {
784 c = '&';
785 src += 5;
786 } else if (src[1] == 'l' &&
787 src[2] == 't' &&
788 src[3] == ';') {
789 c = '<';
790 src += 4;
791 } else if (src[1] == 'g' &&
792 src[2] == 't' &&
793 src[3] == ';') {
794 c = '>';
795 src += 4;
796 } else if (src[1] == 'a' &&
797 src[2] == 'p' &&
798 src[3] == 'o' &&
799 src[4] == 's' &&
800 src[5] == ';') {
801 c = '\'';
802 src += 6;
803 } else if (src[1] == 'q' &&
804 src[2] == 'u' &&
805 src[3] == 'o' &&
806 src[4] == 't' &&
807 src[5] == ';') {
808 c = '\"';
809 src += 6;
810 } else
811 return (false);
812 } else
813 src++;
814 ADDCHAR(c);
815 }
816
817 _PROP_ASSERT(*src == '<');
818 if (sizep != NULL)
819 *sizep = tarindex;
820 if (cpp != NULL)
821 *cpp = src;
822
823 return (true);
824 }
825
826 static unsigned int
827 _prop_object_decode_string_uesc_getu16(const char *src, unsigned int idx,
828 uint16_t *valp)
829 {
830 unsigned int i;
831 uint16_t val;
832 unsigned char c;
833
834 if (src[idx] != '\\' || src[idx + 1] != 'u') {
835 return 0;
836 }
837
838 for (val = 0, i = 2; i < 6; i++) {
839 val <<= 4;
840 c = src[idx + i];
841 if (c >= 'A' && c <= 'F') {
842 val |= 10 + (c - 'A');
843 } else if (c >= 'a' && c <= 'f') {
844 val |= 10 + (c - 'a');
845 } else if (c >= '0' && c <= '9') {
846 val |= c - '0';
847 } else {
848 return 0;
849 }
850 }
851
852 *valp = val;
853 return idx + i;
854 }
855
856 #define HS_FIRST 0xd800
857 #define HS_LAST 0xdbff
858 #define HS_SHIFT 10
859 #define LS_FIRST 0xdc00
860 #define LS_LAST 0xdfff
861
862 #define HIGH_SURROGAGE_P(x) \
863 ((x) >= HS_FIRST && (x) <= HS_LAST)
864 #define LOW_SURROGATE_P(x) \
865 ((x) >= LS_FIRST && (x) <= LS_LAST)
866 #define SURROGATE_P(x) \
867 (HIGH_SURROGAGE_P(x) || LOW_SURROGATE_P(x))
868
869 static int
870 _prop_object_decode_string_uesc(const char *src, char *c,
871 unsigned int *cszp)
872 {
873 unsigned int idx = 0;
874 uint32_t code;
875 uint16_t code16[2] = { 0, 0 };
876
877 idx = _prop_object_decode_string_uesc_getu16(src, idx, &code16[0]);
878 if (idx == 0) {
879 return 0;
880 }
881 if (! SURROGATE_P(code16[0])) {
882 /* Simple case: not a surrogate pair */
883 code = code16[0];
884 } else if (HIGH_SURROGAGE_P(code16[0])) {
885 idx = _prop_object_decode_string_uesc_getu16(src, idx,
886 &code16[1]);
887 if (idx == 0) {
888 return 0;
889 }
890 /* Next code must be the low surrogate. */
891 if (! LOW_SURROGATE_P(code16[1])) {
892 return 0;
893 }
894 code = (((uint32_t)code16[0] - HS_FIRST) << HS_SHIFT) +
895 ( code16[1] - LS_FIRST) +
896 0x10000;
897 } else {
898 /* Got the low surrogate first; this is an error. */
899 return 0;
900 }
901
902 /*
903 * Ok, we have the code point. Now convert it to UTF-8.
904 * First we'll just split into nybbles.
905 */
906 uint8_t u = (code >> 20) & 0xf;
907 uint8_t v = (code >> 16) & 0xf;
908 uint8_t w = (code >> 12) & 0xf;
909 uint8_t x = (code >> 8) & 0xf;
910 uint8_t y = (code >> 4) & 0xf;
911 uint8_t z = (code ) & 0xf;
912
913 /*
914 * ...and swizzle the nybbles accordingly.
915 *
916 * N.B. we expcitly disallow inserting a NUL into the string
917 * by way of a \uXXXX escape.
918 */
919 if (code == 0) {
920 /* Not allowed. */
921 return 0;
922 } else if (/*code >= 0x0000 &&*/ code <= 0x007f) {
923 c[0] = (char)code; /* == (y << 4) | z */
924 *cszp = 1;
925 } else if (/*code >= 0x0080 &&*/ code <= 0x07ff) {
926 c[0] = 0xc0 | (x << 2) | (y >> 2);
927 c[1] = 0x80 | ((y & 3) << 4) | z;
928 *cszp = 2;
929 } else if (/*code >= 0x0800 &&*/ code <= 0xffff) {
930 c[0] = 0xe0 | w;
931 c[1] = 0x80 | (x << 2) | (y >> 2);
932 c[2] = 0x80 | ((y & 3) << 4) | z;
933 *cszp = 3;
934 } else if (/*code >= 0x010000 &&*/ code <= 0x10ffff) {
935 c[0] = 0xf0 | ((u & 1) << 2) | (v >> 2);
936 c[1] = 0x80 | ((v & 3) << 4) | w;
937 c[2] = 0x80 | (x << 2) | (y >> 2);
938 c[3] = 0x80 | ((y & 3) << 4) | z;
939 *cszp = 4;
940 } else {
941 /* Invalid code. */
942 return 0;
943 }
944
945 return idx; /* advance input by this much */
946 }
947
948 #undef HS_FIRST
949 #undef HS_LAST
950 #undef LS_FIRST
951 #undef LS_LAST
952 #undef HIGH_SURROGAGE_P
953 #undef LOW_SURROGATE_P
954 #undef SURROGATE_P
955
956 static bool
957 _prop_object_internalize_decode_string_json(
958 struct _prop_object_internalize_context *ctx,
959 char *target, size_t targsize, size_t *sizep,
960 const char **cpp)
961 {
962 const char *src;
963 size_t tarindex;
964 char c[4];
965 unsigned int csz;
966
967 tarindex = 0;
968 src = ctx->poic_cp;
969
970 for (;;) {
971 if (_PROP_EOF(*src)) {
972 return false;
973 }
974 if (*src == '"') {
975 break;
976 }
977
978 csz = 1;
979 if ((c[0] = *src) == '\\') {
980 int advance = 2;
981
982 switch ((c[0] = src[1])) {
983 case '"': /* quotation mark */
984 case '\\': /* reverse solidus */
985 case '/': /* solidus */
986 /* identity mapping */
987 break;
988
989 case 'b': /* backspace */
990 c[0] = 0x08;
991 break;
992
993 case 'f': /* form feed */
994 c[0] = 0x0c;
995 break;
996
997 case 'n': /* line feed */
998 c[0] = 0x0a;
999 break;
1000
1001 case 'r': /* carriage return */
1002 c[0] = 0x0d;
1003 break;
1004
1005 case 't': /* tab */
1006 c[0] = 0x09;
1007 break;
1008
1009 case 'u':
1010 advance = _prop_object_decode_string_uesc(
1011 src, c, &csz);
1012 if (advance == 0) {
1013 return false;
1014 }
1015 break;
1016
1017 default:
1018 /* invalid escape */
1019 return false;
1020 }
1021 src += advance;
1022 } else {
1023 src++;
1024 }
1025 for (unsigned int i = 0; i < csz; i++) {
1026 ADDCHAR(c[i]);
1027 }
1028 }
1029
1030 _PROP_ASSERT(*src == '"');
1031 if (sizep != NULL) {
1032 *sizep = tarindex;
1033 }
1034 if (cpp != NULL) {
1035 *cpp = src;
1036 }
1037
1038 return true;
1039 }
1040
1041 #undef ADDCHAR
1042
1043 bool
1044 _prop_object_internalize_decode_string(
1045 struct _prop_object_internalize_context *ctx,
1046 char *target, size_t targsize, size_t *sizep,
1047 const char **cpp)
1048 {
1049 _PROP_ASSERT(ctx->poic_format == PROP_FORMAT_XML ||
1050 ctx->poic_format == PROP_FORMAT_JSON);
1051
1052 switch (ctx->poic_format) {
1053 case PROP_FORMAT_JSON:
1054 return _prop_object_internalize_decode_string_json(ctx,
1055 target, targsize, sizep, cpp);
1056
1057 default: /* XML */
1058 return _prop_object_internalize_decode_string_xml(ctx,
1059 target, targsize, sizep, cpp);
1060 }
1061 }
1062
1063 /*
1064 * _prop_object_internalize_skip_whitespace --
1065 * Skip a span of whitespace.
1066 */
1067 const char *
1068 _prop_object_internalize_skip_whitespace(const char *cp)
1069 {
1070 while (_PROP_ISSPACE(*cp)) {
1071 cp++;
1072 }
1073 return cp;
1074 }
1075
1076 /*
1077 * _prop_object_internalize_match --
1078 * Returns true if the two character streams match.
1079 */
1080 bool
1081 _prop_object_internalize_match(const char *str1, size_t len1,
1082 const char *str2, size_t len2)
1083 {
1084
1085 return (len1 == len2 && memcmp(str1, str2, len1) == 0);
1086 }
1087
1088 #define INTERNALIZER(t, f) \
1089 { t, sizeof(t) - 1, f }
1090
1091 static const struct _prop_object_internalizer {
1092 const char *poi_tag;
1093 size_t poi_taglen;
1094 prop_object_internalizer_t poi_intern;
1095 } _prop_object_internalizer_table[] = {
1096 INTERNALIZER("array", _prop_array_internalize),
1097
1098 INTERNALIZER("true", _prop_bool_internalize),
1099 INTERNALIZER("false", _prop_bool_internalize),
1100
1101 INTERNALIZER("data", _prop_data_internalize),
1102
1103 INTERNALIZER("dict", _prop_dictionary_internalize),
1104
1105 INTERNALIZER("integer", _prop_number_internalize),
1106
1107 INTERNALIZER("string", _prop_string_internalize),
1108
1109 { 0, 0, NULL }
1110 };
1111
1112 #undef INTERNALIZER
1113
1114 /*
1115 * _prop_object_internalize_by_tag --
1116 * Determine the object type from the tag in the context and
1117 * internalize it.
1118 */
1119 static prop_object_t
1120 _prop_object_internalize_by_tag(struct _prop_object_internalize_context *ctx)
1121 {
1122 const struct _prop_object_internalizer *poi;
1123 prop_object_t obj, parent_obj;
1124 void *data, *iter;
1125 prop_object_internalizer_continue_t iter_func;
1126 struct _prop_stack stack;
1127
1128 _prop_stack_init(&stack);
1129
1130 match_start:
1131 for (poi = _prop_object_internalizer_table;
1132 poi->poi_tag != NULL; poi++) {
1133 if (_prop_object_internalize_match(ctx->poic_tagname,
1134 ctx->poic_tagname_len,
1135 poi->poi_tag,
1136 poi->poi_taglen))
1137 break;
1138 }
1139 if ((poi == NULL) || (poi->poi_tag == NULL)) {
1140 while (_prop_stack_pop(&stack, &obj, &iter, &data, NULL)) {
1141 iter_func = (prop_object_internalizer_continue_t)iter;
1142 (*iter_func)(&stack, &obj, ctx, data, NULL);
1143 }
1144
1145 return (NULL);
1146 }
1147
1148 obj = NULL;
1149 if (!(*poi->poi_intern)(&stack, &obj, ctx))
1150 goto match_start;
1151
1152 parent_obj = obj;
1153 while (_prop_stack_pop(&stack, &parent_obj, &iter, &data, NULL)) {
1154 iter_func = (prop_object_internalizer_continue_t)iter;
1155 if (!(*iter_func)(&stack, &parent_obj, ctx, data, obj))
1156 goto match_start;
1157 obj = parent_obj;
1158 }
1159
1160 return (parent_obj);
1161 }
1162
1163 /*
1164 * _prop_object_internalize_xml --
1165 * Internalize a property list from XML data.
1166 */
1167 static prop_object_t
1168 _prop_object_internalize_xml(struct _prop_object_internalize_context *ctx,
1169 const struct _prop_object_type_tags *initial_tag)
1170 {
1171 prop_object_t obj = NULL;
1172
1173 /* We start with a <plist> tag. */
1174 if (_prop_object_internalize_find_tag(ctx, "plist",
1175 _PROP_TAG_TYPE_START) == false)
1176 goto out;
1177
1178 /* Plist elements cannot be empty. */
1179 if (ctx->poic_is_empty_element)
1180 goto out;
1181
1182 /*
1183 * We don't understand any plist attributes, but Apple XML
1184 * property lists often have a "version" attribute. If we
1185 * see that one, we simply ignore it.
1186 */
1187 if (ctx->poic_tagattr != NULL &&
1188 !_PROP_TAGATTR_MATCH(ctx, "version"))
1189 goto out;
1190
1191 /* Next we expect to see opening master_tag. */
1192 if (_prop_object_internalize_find_tag(ctx,
1193 initial_tag != NULL ? initial_tag->xml_tag
1194 : NULL,
1195 _PROP_TAG_TYPE_START) == false)
1196 goto out;
1197
1198 obj = _prop_object_internalize_by_tag(ctx);
1199 if (obj == NULL)
1200 goto out;
1201
1202 /*
1203 * We've advanced past the closing main tag.
1204 * Now we want </plist>.
1205 */
1206 if (_prop_object_internalize_find_tag(ctx, "plist",
1207 _PROP_TAG_TYPE_END) == false) {
1208 prop_object_release(obj);
1209 obj = NULL;
1210 }
1211
1212 out:
1213 return (obj);
1214 }
1215
1216 /*
1217 * _prop_object_internalize_json --
1218 * Internalize a property list from JSON data.
1219 */
1220 static prop_object_t
1221 _prop_object_internalize_json(struct _prop_object_internalize_context *ctx,
1222 const struct _prop_object_type_tags *initial_tag __unused)
1223 {
1224 prop_object_t obj, parent_obj;
1225 void *data, *iter;
1226 prop_object_internalizer_continue_t iter_func;
1227 struct _prop_stack stack;
1228 bool (*intern)(prop_stack_t, prop_object_t *,
1229 struct _prop_object_internalize_context *);
1230
1231 _prop_stack_init(&stack);
1232
1233 match_start:
1234 intern = NULL;
1235 ctx->poic_tagname = ctx->poic_tagattr = ctx->poic_tagattrval = NULL;
1236 ctx->poic_tagname_len = ctx->poic_tagattr_len =
1237 ctx->poic_tagattrval_len = 0;
1238 ctx->poic_is_empty_element = false;
1239 ctx->poic_cp = _prop_object_internalize_skip_whitespace(ctx->poic_cp);
1240 switch (ctx->poic_cp[0]) {
1241 case '{':
1242 ctx->poic_cp++;
1243 intern = _prop_dictionary_internalize;
1244 break;
1245
1246 case '[':
1247 ctx->poic_cp++;
1248 intern = _prop_array_internalize;
1249 break;
1250
1251 case '"':
1252 ctx->poic_cp++;
1253 /* XXX Slightly gross. */
1254 if (*ctx->poic_cp == '"') {
1255 ctx->poic_cp++;
1256 ctx->poic_is_empty_element = true;
1257 }
1258 intern = _prop_string_internalize;
1259 break;
1260
1261 case 't':
1262 if (ctx->poic_cp[1] == 'r' &&
1263 ctx->poic_cp[2] == 'u' &&
1264 ctx->poic_cp[3] == 'e') {
1265 /* XXX Slightly gross. */
1266 ctx->poic_tagname = ctx->poic_cp;
1267 ctx->poic_tagname_len = 4;
1268 ctx->poic_is_empty_element = true;
1269 intern = _prop_bool_internalize;
1270 ctx->poic_cp += 4;
1271 }
1272 break;
1273
1274 case 'f':
1275 if (ctx->poic_cp[1] == 'a' &&
1276 ctx->poic_cp[2] == 'l' &&
1277 ctx->poic_cp[3] == 's' &&
1278 ctx->poic_cp[4] == 'e') {
1279 /* XXX Slightly gross. */
1280 ctx->poic_tagname = ctx->poic_cp;
1281 ctx->poic_tagname_len = 5;
1282 ctx->poic_is_empty_element = true;
1283 intern = _prop_bool_internalize;
1284 ctx->poic_cp += 5;
1285 }
1286 break;
1287
1288 default:
1289 if (ctx->poic_cp[0] == '+' ||
1290 ctx->poic_cp[0] == '-' ||
1291 (ctx->poic_cp[0] >= '0' && ctx->poic_cp[0] <= '9')) {
1292 intern = _prop_number_internalize;
1293 }
1294 break;
1295 }
1296
1297 if (intern == NULL) {
1298 while (_prop_stack_pop(&stack, &obj, &iter, &data, NULL)) {
1299 iter_func = (prop_object_internalizer_continue_t)iter;
1300 (*iter_func)(&stack, &obj, ctx, data, NULL);
1301 }
1302 return NULL;
1303 }
1304
1305 obj = NULL;
1306 if ((*intern)(&stack, &obj, ctx) == false) {
1307 goto match_start;
1308 }
1309
1310 parent_obj = obj;
1311 while (_prop_stack_pop(&stack, &parent_obj, &iter, &data, NULL)) {
1312 iter_func = (prop_object_internalizer_continue_t)iter;
1313 if ((*iter_func)(&stack, &parent_obj, ctx, data,
1314 obj) == false) {
1315 goto match_start;
1316 }
1317 obj = parent_obj;
1318 }
1319
1320 /* Ensure there's no trailing junk. */
1321 if (parent_obj != NULL) {
1322 ctx->poic_cp =
1323 _prop_object_internalize_skip_whitespace(ctx->poic_cp);
1324 if (!_PROP_EOF(*ctx->poic_cp)) {
1325 prop_object_release(parent_obj);
1326 parent_obj = NULL;
1327 }
1328 }
1329 return parent_obj;
1330 }
1331
1332 /*
1333 * _prop_object_internalize --
1334 * Internalize a property list from a NUL-terminated data blob.
1335 */
1336 prop_object_t
1337 _prop_object_internalize(const char *data,
1338 const struct _prop_object_type_tags *initial_tag)
1339 {
1340 struct _prop_object_internalize_context *ctx;
1341 prop_object_t obj;
1342 prop_format_t fmt;
1343
1344 /*
1345 * Skip all whitespace until and look at the first
1346 * non-whitespace character to determine the format:
1347 * An XML plist will always have '<' as the first non-ws
1348 * character. If we encounter something else, we assume
1349 * it is JSON.
1350 */
1351 data = _prop_object_internalize_skip_whitespace(data);
1352 if (_PROP_EOF(*data)) {
1353 return NULL;
1354 }
1355
1356 fmt = *data == '<' ? PROP_FORMAT_XML : PROP_FORMAT_JSON;
1357
1358 ctx = _prop_object_internalize_context_alloc(data, fmt);
1359 if (ctx == NULL) {
1360 return NULL;
1361 }
1362
1363 if (fmt == PROP_FORMAT_XML) {
1364 obj = _prop_object_internalize_xml(ctx, initial_tag);
1365 } else {
1366 obj = _prop_object_internalize_json(ctx, initial_tag);
1367 }
1368
1369 _prop_object_internalize_context_free(ctx);
1370 return (obj);
1371 }
1372
1373 _PROP_EXPORT prop_object_t
1374 prop_object_internalize(const char *data)
1375 {
1376 return _prop_object_internalize(data, NULL);
1377 }
1378
1379 /*
1380 * _prop_object_internalize_context_alloc --
1381 * Allocate an internalize context.
1382 */
1383 struct _prop_object_internalize_context *
1384 _prop_object_internalize_context_alloc(const char *data, prop_format_t fmt)
1385 {
1386 struct _prop_object_internalize_context *ctx;
1387
1388 ctx = _PROP_MALLOC(sizeof(*ctx), M_TEMP);
1389 if (ctx == NULL)
1390 return (NULL);
1391
1392 ctx->poic_format = fmt;
1393 ctx->poic_data = ctx->poic_cp = data;
1394
1395 /*
1396 * If we're digesting JSON, check for a byte order mark and
1397 * skip it, if present. We should never see one, but we're
1398 * allowed to detect and ignore it. (RFC 8259 section 8.1)
1399 */
1400 if (fmt == PROP_FORMAT_JSON) {
1401 if (((unsigned char)data[0] == 0xff &&
1402 (unsigned char)data[1] == 0xfe) ||
1403 ((unsigned char)data[0] == 0xfe &&
1404 (unsigned char)data[1] == 0xff)) {
1405 ctx->poic_cp = data + 2;
1406 }
1407
1408 /* No additional processing work to do for JSON. */
1409 return ctx;
1410 }
1411
1412 /*
1413 * Skip any whitespace and XML preamble stuff that we don't
1414 * know about / care about.
1415 */
1416 for (;;) {
1417 data = _prop_object_internalize_skip_whitespace(data);
1418 if (_PROP_EOF(*data) || *data != '<')
1419 goto bad;
1420
1421 #define MATCH(str) (strncmp(&data[1], str, strlen(str)) == 0)
1422
1423 /*
1424 * Skip over the XML preamble that Apple XML property
1425 * lists usually include at the top of the file.
1426 */
1427 if (MATCH("?xml ") ||
1428 MATCH("!DOCTYPE plist")) {
1429 while (*data != '>' && !_PROP_EOF(*data))
1430 data++;
1431 if (_PROP_EOF(*data))
1432 goto bad;
1433 data++; /* advance past the '>' */
1434 continue;
1435 }
1436
1437 if (MATCH("<!--")) {
1438 ctx->poic_cp = data + 4;
1439 if (_prop_object_internalize_skip_comment(ctx) == false)
1440 goto bad;
1441 data = ctx->poic_cp;
1442 continue;
1443 }
1444
1445 #undef MATCH
1446
1447 /*
1448 * We don't think we should skip it, so let's hope we can
1449 * parse it.
1450 */
1451 break;
1452 }
1453
1454 ctx->poic_cp = data;
1455 return (ctx);
1456 bad:
1457 _PROP_FREE(ctx, M_TEMP);
1458 return (NULL);
1459 }
1460
1461 /*
1462 * _prop_object_internalize_context_free --
1463 * Free an internalize context.
1464 */
1465 void
1466 _prop_object_internalize_context_free(
1467 struct _prop_object_internalize_context *ctx)
1468 {
1469
1470 _PROP_FREE(ctx, M_TEMP);
1471 }
1472
1473 #if !defined(_KERNEL) && !defined(_STANDALONE)
1474 /*
1475 * _prop_object_externalize_file_dirname --
1476 * dirname(3), basically. We have to roll our own because the
1477 * system dirname(3) isn't reentrant.
1478 */
1479 static void
1480 _prop_object_externalize_file_dirname(const char *path, char *result)
1481 {
1482 const char *lastp;
1483 size_t len;
1484
1485 /*
1486 * If `path' is a NULL pointer or points to an empty string,
1487 * return ".".
1488 */
1489 if (path == NULL || *path == '\0')
1490 goto singledot;
1491
1492 /* String trailing slashes, if any. */
1493 lastp = path + strlen(path) - 1;
1494 while (lastp != path && *lastp == '/')
1495 lastp--;
1496
1497 /* Terminate path at the last occurrence of '/'. */
1498 do {
1499 if (*lastp == '/') {
1500 /* Strip trailing slashes, if any. */
1501 while (lastp != path && *lastp == '/')
1502 lastp--;
1503
1504 /* ...and copy the result into the result buffer. */
1505 len = (lastp - path) + 1 /* last char */;
1506 if (len > (PATH_MAX - 1))
1507 len = PATH_MAX - 1;
1508
1509 memcpy(result, path, len);
1510 result[len] = '\0';
1511 return;
1512 }
1513 } while (--lastp >= path);
1514
1515 /* No /'s found, return ".". */
1516 singledot:
1517 strcpy(result, ".");
1518 }
1519
1520 /*
1521 * _prop_object_externalize_write_file --
1522 * Write an externalized dictionary to the specified file.
1523 * The file is written atomically from the caller's perspective,
1524 * and the mode set to 0666 modified by the caller's umask.
1525 */
1526 static bool
1527 _prop_object_externalize_write_file(const char *fname, const char *data,
1528 size_t len)
1529 {
1530 char tname_store[PATH_MAX];
1531 char *tname = NULL;
1532 int fd = -1;
1533 int save_errno;
1534 mode_t myumask;
1535 bool rv = false;
1536
1537 if (len > SSIZE_MAX) {
1538 errno = EFBIG;
1539 return false;
1540 }
1541
1542 /*
1543 * Get the directory name where the file is to be written
1544 * and create the temporary file.
1545 */
1546 _prop_object_externalize_file_dirname(fname, tname_store);
1547 #define PLISTTMP "/.plistXXXXXX"
1548 if (strlen(tname_store) + strlen(PLISTTMP) >= sizeof(tname_store)) {
1549 errno = ENAMETOOLONG;
1550 return false;
1551 }
1552 strcat(tname_store, PLISTTMP);
1553 #undef PLISTTMP
1554
1555 if ((fd = mkstemp(tname_store)) == -1) {
1556 return (false);
1557 }
1558 tname = tname_store;
1559
1560 if (write(fd, data, len) != (ssize_t)len) {
1561 goto bad;
1562 }
1563
1564 if (fsync(fd) == -1) {
1565 goto bad;
1566 }
1567
1568 myumask = umask(0);
1569 (void)umask(myumask);
1570 if (fchmod(fd, 0666 & ~myumask) == -1) {
1571 goto bad;
1572 }
1573
1574 if (rename(tname, fname) == -1) {
1575 goto bad;
1576 }
1577 tname = NULL;
1578
1579 rv = true;
1580
1581 bad:
1582 save_errno = errno;
1583 if (fd != -1) {
1584 (void) close(fd);
1585 }
1586 if (tname != NULL) {
1587 (void) unlink(tname);
1588 }
1589 errno = save_errno;
1590 return rv;
1591 }
1592
1593 /*
1594 * _prop_object_externalize_to_file --
1595 * Externalize an object to the specified file.
1596 */
1597 bool
1598 _prop_object_externalize_to_file(struct _prop_object *obj, const char *fname,
1599 prop_format_t fmt)
1600 {
1601 char *data = _prop_object_externalize(obj, fmt);
1602 if (data == NULL) {
1603 return false;
1604 }
1605 bool rv = _prop_object_externalize_write_file(fname, data,
1606 strlen(data));
1607 int save_errno = errno;
1608 _PROP_FREE(data, M_TEMP);
1609 errno = save_errno;
1610
1611 return rv;
1612 }
1613
1614 struct _prop_object_internalize_mapped_file {
1615 char * poimf_xml;
1616 size_t poimf_mapsize;
1617 };
1618
1619 /*
1620 * _prop_object_internalize_map_file --
1621 * Map a file for the purpose of internalizing it.
1622 */
1623 static struct _prop_object_internalize_mapped_file *
1624 _prop_object_internalize_map_file(const char *fname)
1625 {
1626 struct stat sb;
1627 struct _prop_object_internalize_mapped_file *mf;
1628 size_t pgsize = (size_t)sysconf(_SC_PAGESIZE);
1629 size_t pgmask = pgsize - 1;
1630 bool need_guard = false;
1631 int fd;
1632
1633 mf = _PROP_MALLOC(sizeof(*mf), M_TEMP);
1634 if (mf == NULL)
1635 return (NULL);
1636
1637 fd = open(fname, O_RDONLY, 0400);
1638 if (fd == -1) {
1639 _PROP_FREE(mf, M_TEMP);
1640 return (NULL);
1641 }
1642
1643 if (fstat(fd, &sb) == -1) {
1644 (void) close(fd);
1645 _PROP_FREE(mf, M_TEMP);
1646 return (NULL);
1647 }
1648 mf->poimf_mapsize = ((size_t)sb.st_size + pgmask) & ~pgmask;
1649 if (mf->poimf_mapsize < (size_t)sb.st_size) {
1650 (void) close(fd);
1651 _PROP_FREE(mf, M_TEMP);
1652 return (NULL);
1653 }
1654
1655 /*
1656 * If the file length is an integral number of pages, then we
1657 * need to map a guard page at the end in order to provide the
1658 * necessary NUL-termination of the buffer.
1659 */
1660 if ((sb.st_size & pgmask) == 0)
1661 need_guard = true;
1662
1663 mf->poimf_xml = mmap(NULL, need_guard ? mf->poimf_mapsize + pgsize
1664 : mf->poimf_mapsize,
1665 PROT_READ, MAP_FILE|MAP_SHARED, fd, (off_t)0);
1666 (void) close(fd);
1667 if (mf->poimf_xml == MAP_FAILED) {
1668 _PROP_FREE(mf, M_TEMP);
1669 return (NULL);
1670 }
1671 #ifdef POSIX_MADV_SEQUENTIAL
1672 (void) posix_madvise(mf->poimf_xml, mf->poimf_mapsize,
1673 POSIX_MADV_SEQUENTIAL);
1674 #endif
1675
1676 if (need_guard) {
1677 if (mmap(mf->poimf_xml + mf->poimf_mapsize,
1678 pgsize, PROT_READ,
1679 MAP_ANON|MAP_PRIVATE|MAP_FIXED, -1,
1680 (off_t)0) == MAP_FAILED) {
1681 (void) munmap(mf->poimf_xml, mf->poimf_mapsize);
1682 _PROP_FREE(mf, M_TEMP);
1683 return (NULL);
1684 }
1685 mf->poimf_mapsize += pgsize;
1686 }
1687
1688 return (mf);
1689 }
1690
1691 /*
1692 * _prop_object_internalize_unmap_file --
1693 * Unmap a file previously mapped for internalizing.
1694 */
1695 static void
1696 _prop_object_internalize_unmap_file(
1697 struct _prop_object_internalize_mapped_file *mf)
1698 {
1699
1700 #ifdef POSIX_MADV_DONTNEED
1701 (void) posix_madvise(mf->poimf_xml, mf->poimf_mapsize,
1702 POSIX_MADV_DONTNEED);
1703 #endif
1704 (void) munmap(mf->poimf_xml, mf->poimf_mapsize);
1705 _PROP_FREE(mf, M_TEMP);
1706 }
1707
1708 /*
1709 * _prop_object_internalize_from_file --
1710 * Internalize a property list from a file.
1711 */
1712 prop_object_t
1713 _prop_object_internalize_from_file(const char *fname,
1714 const struct _prop_object_type_tags *initial_tag)
1715 {
1716 struct _prop_object_internalize_mapped_file *mf;
1717 prop_object_t obj;
1718
1719 mf = _prop_object_internalize_map_file(fname);
1720 if (mf == NULL) {
1721 return NULL;
1722 }
1723 obj = _prop_object_internalize(mf->poimf_xml, initial_tag);
1724 _prop_object_internalize_unmap_file(mf);
1725
1726 return obj;
1727 }
1728
1729 _PROP_EXPORT prop_object_t
1730 prop_object_internalize_from_file(const char *fname)
1731 {
1732 return _prop_object_internalize_from_file(fname, NULL);
1733 }
1734 #endif /* !_KERNEL && !_STANDALONE */
1735
1736 static prop_format_t _prop_format_default = PROP_FORMAT_XML;
1737
1738 /*
1739 * prop_object_externalize --
1740 * Externalize an object in the default format.
1741 */
1742 _PROP_EXPORT char *
1743 prop_object_externalize(prop_object_t po)
1744 {
1745 return _prop_object_externalize((struct _prop_object *)po,
1746 _prop_format_default);
1747 }
1748
1749 /*
1750 * prop_object_externalize_with_format --
1751 * Externalize an object in the specified format.
1752 */
1753 _PROP_EXPORT char *
1754 prop_object_externalize_with_format(prop_object_t po, prop_format_t fmt)
1755 {
1756 return _prop_object_externalize((struct _prop_object *)po, fmt);
1757 }
1758
1759 #if !defined(_KERNEL) && !defined(_STANDALONE)
1760 /*
1761 * prop_object_externalize_to_file --
1762 * Externalize an object to the specifed file in the default format.
1763 */
1764 _PROP_EXPORT bool
1765 prop_object_externalize_to_file(prop_object_t po, const char *fname)
1766 {
1767 return _prop_object_externalize_to_file((struct _prop_object *)po,
1768 fname, _prop_format_default);
1769 }
1770
1771 /*
1772 * prop_object_externalize_to_file_with_format --
1773 * Externalize an object to the specifed file in the specified format.
1774 */
1775 _PROP_EXPORT bool
1776 prop_object_externalize_to_file_with_format(prop_object_t po,
1777 const char *fname, prop_format_t fmt)
1778 {
1779 return _prop_object_externalize_to_file((struct _prop_object *)po,
1780 fname, fmt);
1781 }
1782 #endif /* !_KERNEL && !_STANDALONE */
1783
1784 /*
1785 * prop_object_retain --
1786 * Increment the reference count on an object.
1787 */
1788 _PROP_EXPORT void
1789 prop_object_retain(prop_object_t obj)
1790 {
1791 struct _prop_object *po = obj;
1792 uint32_t ncnt __unused;
1793
1794 _PROP_ATOMIC_INC32_NV(&po->po_refcnt, ncnt);
1795 _PROP_ASSERT(ncnt != 0);
1796 }
1797
1798 /*
1799 * prop_object_release_emergency
1800 * A direct free with prop_object_release failed.
1801 * Walk down the tree until a leaf is found and
1802 * free that. Do not recurse to avoid stack overflows.
1803 *
1804 * This is a slow edge condition, but necessary to
1805 * guarantee that an object can always be freed.
1806 */
1807 static void
1808 prop_object_release_emergency(prop_object_t obj)
1809 {
1810 struct _prop_object *po;
1811 void (*unlock)(void);
1812 prop_object_t parent = NULL;
1813 uint32_t ocnt;
1814
1815 for (;;) {
1816 po = obj;
1817 _PROP_ASSERT(obj);
1818
1819 if (po->po_type->pot_lock != NULL)
1820 po->po_type->pot_lock();
1821
1822 /* Save pointerto unlock function */
1823 unlock = po->po_type->pot_unlock;
1824
1825 /* Dance a bit to make sure we always get the non-racy ocnt */
1826 _PROP_ATOMIC_DEC32_NV(&po->po_refcnt, ocnt);
1827 ocnt++;
1828 _PROP_ASSERT(ocnt != 0);
1829
1830 if (ocnt != 1) {
1831 if (unlock != NULL)
1832 unlock();
1833 break;
1834 }
1835
1836 _PROP_ASSERT(po->po_type);
1837 if ((po->po_type->pot_free)(NULL, &obj) ==
1838 _PROP_OBJECT_FREE_DONE) {
1839 if (unlock != NULL)
1840 unlock();
1841 break;
1842 }
1843
1844 if (unlock != NULL)
1845 unlock();
1846
1847 parent = po;
1848 _PROP_ATOMIC_INC32(&po->po_refcnt);
1849 }
1850 _PROP_ASSERT(parent);
1851 /* One object was just freed. */
1852 po = parent;
1853 (*po->po_type->pot_emergency_free)(parent);
1854 }
1855
1856 /*
1857 * prop_object_release --
1858 * Decrement the reference count on an object.
1859 *
1860 * Free the object if we are releasing the final
1861 * reference.
1862 */
1863 _PROP_EXPORT void
1864 prop_object_release(prop_object_t obj)
1865 {
1866 struct _prop_object *po;
1867 struct _prop_stack stack;
1868 void (*unlock)(void);
1869 int ret;
1870 uint32_t ocnt;
1871
1872 _prop_stack_init(&stack);
1873
1874 do {
1875 do {
1876 po = obj;
1877 _PROP_ASSERT(obj);
1878
1879 if (po->po_type->pot_lock != NULL)
1880 po->po_type->pot_lock();
1881
1882 /* Save pointer to object unlock function */
1883 unlock = po->po_type->pot_unlock;
1884
1885 _PROP_ATOMIC_DEC32_NV(&po->po_refcnt, ocnt);
1886 ocnt++;
1887 _PROP_ASSERT(ocnt != 0);
1888
1889 if (ocnt != 1) {
1890 ret = 0;
1891 if (unlock != NULL)
1892 unlock();
1893 break;
1894 }
1895
1896 ret = (po->po_type->pot_free)(&stack, &obj);
1897
1898 if (unlock != NULL)
1899 unlock();
1900
1901 if (ret == _PROP_OBJECT_FREE_DONE)
1902 break;
1903
1904 _PROP_ATOMIC_INC32(&po->po_refcnt);
1905 } while (ret == _PROP_OBJECT_FREE_RECURSE);
1906 if (ret == _PROP_OBJECT_FREE_FAILED)
1907 prop_object_release_emergency(obj);
1908 } while (_prop_stack_pop(&stack, &obj, NULL, NULL, NULL));
1909 }
1910
1911 /*
1912 * prop_object_type --
1913 * Return the type of an object.
1914 */
1915 _PROP_EXPORT prop_type_t
1916 prop_object_type(prop_object_t obj)
1917 {
1918 struct _prop_object *po = obj;
1919
1920 if (obj == NULL)
1921 return (PROP_TYPE_UNKNOWN);
1922
1923 return (po->po_type->pot_type);
1924 }
1925
1926 /*
1927 * prop_object_equals --
1928 * Returns true if thw two objects are equivalent.
1929 */
1930 _PROP_EXPORT bool
1931 prop_object_equals(prop_object_t obj1, prop_object_t obj2)
1932 {
1933 return (prop_object_equals_with_error(obj1, obj2, NULL));
1934 }
1935
1936 _PROP_EXPORT bool
1937 prop_object_equals_with_error(prop_object_t obj1, prop_object_t obj2,
1938 bool *error_flag)
1939 {
1940 struct _prop_object *po1;
1941 struct _prop_object *po2;
1942 void *stored_pointer1, *stored_pointer2;
1943 prop_object_t next_obj1, next_obj2;
1944 struct _prop_stack stack;
1945 _prop_object_equals_rv_t ret;
1946
1947 _prop_stack_init(&stack);
1948 if (error_flag)
1949 *error_flag = false;
1950
1951 start_subtree:
1952 stored_pointer1 = NULL;
1953 stored_pointer2 = NULL;
1954 po1 = obj1;
1955 po2 = obj2;
1956
1957 if (po1->po_type != po2->po_type)
1958 return (false);
1959
1960 continue_subtree:
1961 ret = (*po1->po_type->pot_equals)(obj1, obj2,
1962 &stored_pointer1, &stored_pointer2,
1963 &next_obj1, &next_obj2);
1964 if (ret == _PROP_OBJECT_EQUALS_FALSE)
1965 goto finish;
1966 if (ret == _PROP_OBJECT_EQUALS_TRUE) {
1967 if (!_prop_stack_pop(&stack, &obj1, &obj2,
1968 &stored_pointer1, &stored_pointer2))
1969 return true;
1970 po1 = obj1;
1971 po2 = obj2;
1972 goto continue_subtree;
1973 }
1974 _PROP_ASSERT(ret == _PROP_OBJECT_EQUALS_RECURSE);
1975
1976 if (!_prop_stack_push(&stack, obj1, obj2,
1977 stored_pointer1, stored_pointer2)) {
1978 if (error_flag)
1979 *error_flag = true;
1980 goto finish;
1981 }
1982 obj1 = next_obj1;
1983 obj2 = next_obj2;
1984 goto start_subtree;
1985
1986 finish:
1987 while (_prop_stack_pop(&stack, &obj1, &obj2, NULL, NULL)) {
1988 po1 = obj1;
1989 (*po1->po_type->pot_equals_finish)(obj1, obj2);
1990 }
1991 return (false);
1992 }
1993
1994 /*
1995 * prop_object_iterator_next --
1996 * Return the next item during an iteration.
1997 */
1998 _PROP_EXPORT prop_object_t
1999 prop_object_iterator_next(prop_object_iterator_t pi)
2000 {
2001
2002 return ((*pi->pi_next_object)(pi));
2003 }
2004
2005 /*
2006 * prop_object_iterator_reset --
2007 * Reset the iterator to the first object so as to restart
2008 * iteration.
2009 */
2010 _PROP_EXPORT void
2011 prop_object_iterator_reset(prop_object_iterator_t pi)
2012 {
2013
2014 (*pi->pi_reset)(pi);
2015 }
2016
2017 /*
2018 * prop_object_iterator_release --
2019 * Release the object iterator.
2020 */
2021 _PROP_EXPORT void
2022 prop_object_iterator_release(prop_object_iterator_t pi)
2023 {
2024
2025 prop_object_release(pi->pi_obj);
2026 _PROP_FREE(pi, M_TEMP);
2027 }
2028