1 | /* $NetBSD: prop_object.c,v 1.30 2015/05/12 14:59:35 christos Exp $ */ |
2 | |
3 | /*- |
4 | * Copyright (c) 2006, 2007 The NetBSD Foundation, Inc. |
5 | * All rights reserved. |
6 | * |
7 | * This code is derived from software contributed to The NetBSD Foundation |
8 | * by Jason R. Thorpe. |
9 | * |
10 | * Redistribution and use in source and binary forms, with or without |
11 | * modification, are permitted provided that the following conditions |
12 | * are met: |
13 | * 1. Redistributions of source code must retain the above copyright |
14 | * notice, this list of conditions and the following disclaimer. |
15 | * 2. Redistributions in binary form must reproduce the above copyright |
16 | * notice, this list of conditions and the following disclaimer in the |
17 | * documentation and/or other materials provided with the distribution. |
18 | * |
19 | * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS |
20 | * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
21 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
22 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS |
23 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
24 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
25 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
26 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
27 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
28 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
29 | * POSSIBILITY OF SUCH DAMAGE. |
30 | */ |
31 | |
32 | #include "prop_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_tag -- |
103 | * Append an XML-style start tag to the externalize buffer. |
104 | */ |
105 | bool |
106 | _prop_object_externalize_start_tag( |
107 | struct _prop_object_externalize_context *ctx, const char *tag) |
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 | if (_prop_object_externalize_append_char(ctx, '<') == false || |
116 | _prop_object_externalize_append_cstring(ctx, tag) == false || |
117 | _prop_object_externalize_append_char(ctx, '>') == false) |
118 | return (false); |
119 | |
120 | return (true); |
121 | } |
122 | |
123 | /* |
124 | * _prop_object_externalize_end_tag -- |
125 | * Append an XML-style end tag to the externalize buffer. |
126 | */ |
127 | bool |
128 | _prop_object_externalize_end_tag( |
129 | struct _prop_object_externalize_context *ctx, const char *tag) |
130 | { |
131 | |
132 | if (_prop_object_externalize_append_char(ctx, '<') == false || |
133 | _prop_object_externalize_append_char(ctx, '/') == false || |
134 | _prop_object_externalize_append_cstring(ctx, tag) == false || |
135 | _prop_object_externalize_append_char(ctx, '>') == false || |
136 | _prop_object_externalize_append_char(ctx, '\n') == false) |
137 | return (false); |
138 | |
139 | return (true); |
140 | } |
141 | |
142 | /* |
143 | * _prop_object_externalize_empty_tag -- |
144 | * Append an XML-style empty tag to the externalize buffer. |
145 | */ |
146 | bool |
147 | _prop_object_externalize_empty_tag( |
148 | struct _prop_object_externalize_context *ctx, const char *tag) |
149 | { |
150 | unsigned int i; |
151 | |
152 | for (i = 0; i < ctx->poec_depth; i++) { |
153 | if (_prop_object_externalize_append_char(ctx, '\t') == false) |
154 | return (false); |
155 | } |
156 | |
157 | if (_prop_object_externalize_append_char(ctx, '<') == false || |
158 | _prop_object_externalize_append_cstring(ctx, tag) == false || |
159 | _prop_object_externalize_append_char(ctx, '/') == false || |
160 | _prop_object_externalize_append_char(ctx, '>') == false || |
161 | _prop_object_externalize_append_char(ctx, '\n') == false) |
162 | return (false); |
163 | |
164 | return (true); |
165 | } |
166 | |
167 | /* |
168 | * _prop_object_externalize_append_cstring -- |
169 | * Append a C string to the externalize buffer. |
170 | */ |
171 | bool |
172 | _prop_object_externalize_append_cstring( |
173 | struct _prop_object_externalize_context *ctx, const char *cp) |
174 | { |
175 | |
176 | while (*cp != '\0') { |
177 | if (_prop_object_externalize_append_char(ctx, |
178 | (unsigned char) *cp) == false) |
179 | return (false); |
180 | cp++; |
181 | } |
182 | |
183 | return (true); |
184 | } |
185 | |
186 | /* |
187 | * _prop_object_externalize_append_encoded_cstring -- |
188 | * Append an encoded C string to the externalize buffer. |
189 | */ |
190 | bool |
191 | _prop_object_externalize_append_encoded_cstring( |
192 | struct _prop_object_externalize_context *ctx, const char *cp) |
193 | { |
194 | |
195 | while (*cp != '\0') { |
196 | switch (*cp) { |
197 | case '<': |
198 | if (_prop_object_externalize_append_cstring(ctx, |
199 | "<" ) == false) |
200 | return (false); |
201 | break; |
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 | default: |
213 | if (_prop_object_externalize_append_char(ctx, |
214 | (unsigned char) *cp) == false) |
215 | return (false); |
216 | break; |
217 | } |
218 | cp++; |
219 | } |
220 | |
221 | return (true); |
222 | } |
223 | |
224 | #define BUF_EXPAND 256 |
225 | |
226 | /* |
227 | * _prop_object_externalize_append_char -- |
228 | * Append a single character to the externalize buffer. |
229 | */ |
230 | bool |
231 | _prop_object_externalize_append_char( |
232 | struct _prop_object_externalize_context *ctx, unsigned char c) |
233 | { |
234 | |
235 | _PROP_ASSERT(ctx->poec_capacity != 0); |
236 | _PROP_ASSERT(ctx->poec_buf != NULL); |
237 | _PROP_ASSERT(ctx->poec_len <= ctx->poec_capacity); |
238 | |
239 | if (ctx->poec_len == ctx->poec_capacity) { |
240 | char *cp = _PROP_REALLOC(ctx->poec_buf, |
241 | ctx->poec_capacity + BUF_EXPAND, |
242 | M_TEMP); |
243 | if (cp == NULL) |
244 | return (false); |
245 | ctx->poec_capacity = ctx->poec_capacity + BUF_EXPAND; |
246 | ctx->poec_buf = cp; |
247 | } |
248 | |
249 | ctx->poec_buf[ctx->poec_len++] = c; |
250 | |
251 | return (true); |
252 | } |
253 | |
254 | /* |
255 | * _prop_object_externalize_header -- |
256 | * Append the standard XML header to the externalize buffer. |
257 | */ |
258 | bool |
259 | (struct _prop_object_externalize_context *ctx) |
260 | { |
261 | static const char [] = |
262 | "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" |
263 | "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n" ; |
264 | |
265 | if (_prop_object_externalize_append_cstring(ctx, |
266 | _plist_xml_header) == false || |
267 | _prop_object_externalize_start_tag(ctx, |
268 | "plist version=\"1.0\"" ) == false || |
269 | _prop_object_externalize_append_char(ctx, '\n') == false) |
270 | return (false); |
271 | |
272 | return (true); |
273 | } |
274 | |
275 | /* |
276 | * _prop_object_externalize_footer -- |
277 | * Append the standard XML footer to the externalize buffer. This |
278 | * also NUL-terminates the buffer. |
279 | */ |
280 | bool |
281 | (struct _prop_object_externalize_context *ctx) |
282 | { |
283 | |
284 | if (_prop_object_externalize_end_tag(ctx, "plist" ) == false || |
285 | _prop_object_externalize_append_char(ctx, '\0') == false) |
286 | return (false); |
287 | |
288 | return (true); |
289 | } |
290 | |
291 | /* |
292 | * _prop_object_externalize_context_alloc -- |
293 | * Allocate an externalize context. |
294 | */ |
295 | struct _prop_object_externalize_context * |
296 | _prop_object_externalize_context_alloc(void) |
297 | { |
298 | struct _prop_object_externalize_context *ctx; |
299 | |
300 | ctx = _PROP_MALLOC(sizeof(*ctx), M_TEMP); |
301 | if (ctx != NULL) { |
302 | ctx->poec_buf = _PROP_MALLOC(BUF_EXPAND, M_TEMP); |
303 | if (ctx->poec_buf == NULL) { |
304 | _PROP_FREE(ctx, M_TEMP); |
305 | return (NULL); |
306 | } |
307 | ctx->poec_len = 0; |
308 | ctx->poec_capacity = BUF_EXPAND; |
309 | ctx->poec_depth = 0; |
310 | } |
311 | return (ctx); |
312 | } |
313 | |
314 | /* |
315 | * _prop_object_externalize_context_free -- |
316 | * Free an externalize context. |
317 | */ |
318 | void |
319 | _prop_object_externalize_context_free( |
320 | struct _prop_object_externalize_context *ctx) |
321 | { |
322 | |
323 | /* Buffer is always freed by the caller. */ |
324 | _PROP_FREE(ctx, M_TEMP); |
325 | } |
326 | |
327 | /* |
328 | * _prop_object_internalize_skip_comment -- |
329 | * Skip the body and end tag of a comment. |
330 | */ |
331 | static bool |
332 | ( |
333 | struct _prop_object_internalize_context *ctx) |
334 | { |
335 | const char *cp = ctx->poic_cp; |
336 | |
337 | while (!_PROP_EOF(*cp)) { |
338 | if (cp[0] == '-' && |
339 | cp[1] == '-' && |
340 | cp[2] == '>') { |
341 | ctx->poic_cp = cp + 3; |
342 | return (true); |
343 | } |
344 | cp++; |
345 | } |
346 | |
347 | return (false); /* ran out of buffer */ |
348 | } |
349 | |
350 | /* |
351 | * _prop_object_internalize_find_tag -- |
352 | * Find the next tag in an XML stream. Optionally compare the found |
353 | * tag to an expected tag name. State of the context is undefined |
354 | * if this routine returns false. Upon success, the context points |
355 | * to the first octet after the tag. |
356 | */ |
357 | bool |
358 | _prop_object_internalize_find_tag(struct _prop_object_internalize_context *ctx, |
359 | const char *tag, _prop_tag_type_t type) |
360 | { |
361 | const char *cp; |
362 | size_t taglen; |
363 | |
364 | if (tag != NULL) |
365 | taglen = strlen(tag); |
366 | else |
367 | taglen = 0; |
368 | |
369 | start_over: |
370 | cp = ctx->poic_cp; |
371 | |
372 | /* |
373 | * Find the start of the tag. |
374 | */ |
375 | while (_PROP_ISSPACE(*cp)) |
376 | cp++; |
377 | if (_PROP_EOF(*cp)) |
378 | return (false); |
379 | |
380 | if (*cp != '<') |
381 | return (false); |
382 | |
383 | ctx->poic_tag_start = cp++; |
384 | if (_PROP_EOF(*cp)) |
385 | return (false); |
386 | |
387 | if (*cp == '!') { |
388 | if (cp[1] != '-' || cp[2] != '-') |
389 | return (false); |
390 | /* |
391 | * Comment block -- only allowed if we are allowed to |
392 | * return a start tag. |
393 | */ |
394 | if (type == _PROP_TAG_TYPE_END) |
395 | return (false); |
396 | ctx->poic_cp = cp + 3; |
397 | if (_prop_object_internalize_skip_comment(ctx) == false) |
398 | return (false); |
399 | goto start_over; |
400 | } |
401 | |
402 | if (*cp == '/') { |
403 | if (type != _PROP_TAG_TYPE_END && |
404 | type != _PROP_TAG_TYPE_EITHER) |
405 | return (false); |
406 | cp++; |
407 | if (_PROP_EOF(*cp)) |
408 | return (false); |
409 | ctx->poic_tag_type = _PROP_TAG_TYPE_END; |
410 | } else { |
411 | if (type != _PROP_TAG_TYPE_START && |
412 | type != _PROP_TAG_TYPE_EITHER) |
413 | return (false); |
414 | ctx->poic_tag_type = _PROP_TAG_TYPE_START; |
415 | } |
416 | |
417 | ctx->poic_tagname = cp; |
418 | |
419 | while (!_PROP_ISSPACE(*cp) && *cp != '/' && *cp != '>') { |
420 | if (_PROP_EOF(*cp)) |
421 | return (false); |
422 | cp++; |
423 | } |
424 | |
425 | ctx->poic_tagname_len = cp - ctx->poic_tagname; |
426 | |
427 | /* Make sure this is the tag we're looking for. */ |
428 | if (tag != NULL && |
429 | (taglen != ctx->poic_tagname_len || |
430 | memcmp(tag, ctx->poic_tagname, taglen) != 0)) |
431 | return (false); |
432 | |
433 | /* Check for empty tag. */ |
434 | if (*cp == '/') { |
435 | if (ctx->poic_tag_type != _PROP_TAG_TYPE_START) |
436 | return(false); /* only valid on start tags */ |
437 | ctx->poic_is_empty_element = true; |
438 | cp++; |
439 | if (_PROP_EOF(*cp) || *cp != '>') |
440 | return (false); |
441 | } else |
442 | ctx->poic_is_empty_element = false; |
443 | |
444 | /* Easy case of no arguments. */ |
445 | if (*cp == '>') { |
446 | ctx->poic_tagattr = NULL; |
447 | ctx->poic_tagattr_len = 0; |
448 | ctx->poic_tagattrval = NULL; |
449 | ctx->poic_tagattrval_len = 0; |
450 | ctx->poic_cp = cp + 1; |
451 | return (true); |
452 | } |
453 | |
454 | _PROP_ASSERT(!_PROP_EOF(*cp)); |
455 | cp++; |
456 | if (_PROP_EOF(*cp)) |
457 | return (false); |
458 | |
459 | while (_PROP_ISSPACE(*cp)) |
460 | cp++; |
461 | if (_PROP_EOF(*cp)) |
462 | return (false); |
463 | |
464 | ctx->poic_tagattr = cp; |
465 | |
466 | while (!_PROP_ISSPACE(*cp) && *cp != '=') { |
467 | if (_PROP_EOF(*cp)) |
468 | return (false); |
469 | cp++; |
470 | } |
471 | |
472 | ctx->poic_tagattr_len = cp - ctx->poic_tagattr; |
473 | |
474 | cp++; |
475 | if (*cp != '\"') |
476 | return (false); |
477 | cp++; |
478 | if (_PROP_EOF(*cp)) |
479 | return (false); |
480 | |
481 | ctx->poic_tagattrval = cp; |
482 | while (*cp != '\"') { |
483 | if (_PROP_EOF(*cp)) |
484 | return (false); |
485 | cp++; |
486 | } |
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) || (poi->poi_tag == NULL)) { |
637 | while (_prop_stack_pop(&stack, &obj, &iter, &data, NULL)) { |
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, &iter, &data, NULL)) { |
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 | mode_t myumask; |
851 | |
852 | if (len > SSIZE_MAX) { |
853 | errno = EFBIG; |
854 | return (false); |
855 | } |
856 | |
857 | /* |
858 | * Get the directory name where the file is to be written |
859 | * and create the temporary file. |
860 | */ |
861 | _prop_object_externalize_file_dirname(fname, tname); |
862 | #define PLISTTMP "/.plistXXXXXX" |
863 | if (strlen(tname) + strlen(PLISTTMP) >= sizeof(tname)) { |
864 | errno = ENAMETOOLONG; |
865 | return (false); |
866 | } |
867 | strcat(tname, PLISTTMP); |
868 | #undef PLISTTMP |
869 | |
870 | if ((fd = mkstemp(tname)) == -1) |
871 | return (false); |
872 | |
873 | if (write(fd, xml, len) != (ssize_t)len) |
874 | goto bad; |
875 | |
876 | if (fsync(fd) == -1) |
877 | goto bad; |
878 | |
879 | myumask = umask(0); |
880 | (void)umask(myumask); |
881 | if (fchmod(fd, 0666 & ~myumask) == -1) |
882 | goto bad; |
883 | |
884 | (void) close(fd); |
885 | fd = -1; |
886 | |
887 | if (rename(tname, fname) == -1) |
888 | goto bad; |
889 | |
890 | return (true); |
891 | |
892 | bad: |
893 | save_errno = errno; |
894 | if (fd != -1) |
895 | (void) close(fd); |
896 | (void) unlink(tname); |
897 | errno = save_errno; |
898 | return (false); |
899 | } |
900 | |
901 | /* |
902 | * _prop_object_internalize_map_file -- |
903 | * Map a file for the purpose of internalizing it. |
904 | */ |
905 | struct _prop_object_internalize_mapped_file * |
906 | _prop_object_internalize_map_file(const char *fname) |
907 | { |
908 | struct stat sb; |
909 | struct _prop_object_internalize_mapped_file *mf; |
910 | size_t pgsize = (size_t)sysconf(_SC_PAGESIZE); |
911 | size_t pgmask = pgsize - 1; |
912 | bool need_guard = false; |
913 | int fd; |
914 | |
915 | mf = _PROP_MALLOC(sizeof(*mf), M_TEMP); |
916 | if (mf == NULL) |
917 | return (NULL); |
918 | |
919 | fd = open(fname, O_RDONLY, 0400); |
920 | if (fd == -1) { |
921 | _PROP_FREE(mf, M_TEMP); |
922 | return (NULL); |
923 | } |
924 | |
925 | if (fstat(fd, &sb) == -1) { |
926 | (void) close(fd); |
927 | _PROP_FREE(mf, M_TEMP); |
928 | return (NULL); |
929 | } |
930 | mf->poimf_mapsize = ((size_t)sb.st_size + pgmask) & ~pgmask; |
931 | if (mf->poimf_mapsize < (size_t)sb.st_size) { |
932 | (void) close(fd); |
933 | _PROP_FREE(mf, M_TEMP); |
934 | return (NULL); |
935 | } |
936 | |
937 | /* |
938 | * If the file length is an integral number of pages, then we |
939 | * need to map a guard page at the end in order to provide the |
940 | * necessary NUL-termination of the buffer. |
941 | */ |
942 | if ((sb.st_size & pgmask) == 0) |
943 | need_guard = true; |
944 | |
945 | mf->poimf_xml = mmap(NULL, need_guard ? mf->poimf_mapsize + pgsize |
946 | : mf->poimf_mapsize, |
947 | PROT_READ, MAP_FILE|MAP_SHARED, fd, (off_t)0); |
948 | (void) close(fd); |
949 | if (mf->poimf_xml == MAP_FAILED) { |
950 | _PROP_FREE(mf, M_TEMP); |
951 | return (NULL); |
952 | } |
953 | (void) madvise(mf->poimf_xml, mf->poimf_mapsize, MADV_SEQUENTIAL); |
954 | |
955 | if (need_guard) { |
956 | if (mmap(mf->poimf_xml + mf->poimf_mapsize, |
957 | pgsize, PROT_READ, |
958 | MAP_ANON|MAP_PRIVATE|MAP_FIXED, -1, |
959 | (off_t)0) == MAP_FAILED) { |
960 | (void) munmap(mf->poimf_xml, mf->poimf_mapsize); |
961 | _PROP_FREE(mf, M_TEMP); |
962 | return (NULL); |
963 | } |
964 | mf->poimf_mapsize += pgsize; |
965 | } |
966 | |
967 | return (mf); |
968 | } |
969 | |
970 | /* |
971 | * _prop_object_internalize_unmap_file -- |
972 | * Unmap a file previously mapped for internalizing. |
973 | */ |
974 | void |
975 | _prop_object_internalize_unmap_file( |
976 | struct _prop_object_internalize_mapped_file *mf) |
977 | { |
978 | |
979 | (void) madvise(mf->poimf_xml, mf->poimf_mapsize, MADV_DONTNEED); |
980 | (void) munmap(mf->poimf_xml, mf->poimf_mapsize); |
981 | _PROP_FREE(mf, M_TEMP); |
982 | } |
983 | #endif /* !_KERNEL && !_STANDALONE */ |
984 | |
985 | /* |
986 | * prop_object_retain -- |
987 | * Increment the reference count on an object. |
988 | */ |
989 | void |
990 | prop_object_retain(prop_object_t obj) |
991 | { |
992 | struct _prop_object *po = obj; |
993 | uint32_t ncnt __unused; |
994 | |
995 | _PROP_ATOMIC_INC32_NV(&po->po_refcnt, ncnt); |
996 | _PROP_ASSERT(ncnt != 0); |
997 | } |
998 | |
999 | /* |
1000 | * prop_object_release_emergency |
1001 | * A direct free with prop_object_release failed. |
1002 | * Walk down the tree until a leaf is found and |
1003 | * free that. Do not recurse to avoid stack overflows. |
1004 | * |
1005 | * This is a slow edge condition, but necessary to |
1006 | * guarantee that an object can always be freed. |
1007 | */ |
1008 | static void |
1009 | prop_object_release_emergency(prop_object_t obj) |
1010 | { |
1011 | struct _prop_object *po; |
1012 | void (*unlock)(void); |
1013 | prop_object_t parent = NULL; |
1014 | uint32_t ocnt; |
1015 | |
1016 | for (;;) { |
1017 | po = obj; |
1018 | _PROP_ASSERT(obj); |
1019 | |
1020 | if (po->po_type->pot_lock != NULL) |
1021 | po->po_type->pot_lock(); |
1022 | |
1023 | /* Save pointerto unlock function */ |
1024 | unlock = po->po_type->pot_unlock; |
1025 | |
1026 | /* Dance a bit to make sure we always get the non-racy ocnt */ |
1027 | _PROP_ATOMIC_DEC32_NV(&po->po_refcnt, ocnt); |
1028 | ocnt++; |
1029 | _PROP_ASSERT(ocnt != 0); |
1030 | |
1031 | if (ocnt != 1) { |
1032 | if (unlock != NULL) |
1033 | unlock(); |
1034 | break; |
1035 | } |
1036 | |
1037 | _PROP_ASSERT(po->po_type); |
1038 | if ((po->po_type->pot_free)(NULL, &obj) == |
1039 | _PROP_OBJECT_FREE_DONE) { |
1040 | if (unlock != NULL) |
1041 | unlock(); |
1042 | break; |
1043 | } |
1044 | |
1045 | if (unlock != NULL) |
1046 | unlock(); |
1047 | |
1048 | parent = po; |
1049 | _PROP_ATOMIC_INC32(&po->po_refcnt); |
1050 | } |
1051 | _PROP_ASSERT(parent); |
1052 | /* One object was just freed. */ |
1053 | po = parent; |
1054 | (*po->po_type->pot_emergency_free)(parent); |
1055 | } |
1056 | |
1057 | /* |
1058 | * prop_object_release -- |
1059 | * Decrement the reference count on an object. |
1060 | * |
1061 | * Free the object if we are releasing the final |
1062 | * reference. |
1063 | */ |
1064 | void |
1065 | prop_object_release(prop_object_t obj) |
1066 | { |
1067 | struct _prop_object *po; |
1068 | struct _prop_stack stack; |
1069 | void (*unlock)(void); |
1070 | int ret; |
1071 | uint32_t ocnt; |
1072 | |
1073 | _prop_stack_init(&stack); |
1074 | |
1075 | do { |
1076 | do { |
1077 | po = obj; |
1078 | _PROP_ASSERT(obj); |
1079 | |
1080 | if (po->po_type->pot_lock != NULL) |
1081 | po->po_type->pot_lock(); |
1082 | |
1083 | /* Save pointer to object unlock function */ |
1084 | unlock = po->po_type->pot_unlock; |
1085 | |
1086 | _PROP_ATOMIC_DEC32_NV(&po->po_refcnt, ocnt); |
1087 | ocnt++; |
1088 | _PROP_ASSERT(ocnt != 0); |
1089 | |
1090 | if (ocnt != 1) { |
1091 | ret = 0; |
1092 | if (unlock != NULL) |
1093 | unlock(); |
1094 | break; |
1095 | } |
1096 | |
1097 | ret = (po->po_type->pot_free)(&stack, &obj); |
1098 | |
1099 | if (unlock != NULL) |
1100 | unlock(); |
1101 | |
1102 | if (ret == _PROP_OBJECT_FREE_DONE) |
1103 | break; |
1104 | |
1105 | _PROP_ATOMIC_INC32(&po->po_refcnt); |
1106 | } while (ret == _PROP_OBJECT_FREE_RECURSE); |
1107 | if (ret == _PROP_OBJECT_FREE_FAILED) |
1108 | prop_object_release_emergency(obj); |
1109 | } while (_prop_stack_pop(&stack, &obj, NULL, NULL, NULL)); |
1110 | } |
1111 | |
1112 | /* |
1113 | * prop_object_type -- |
1114 | * Return the type of an object. |
1115 | */ |
1116 | prop_type_t |
1117 | prop_object_type(prop_object_t obj) |
1118 | { |
1119 | struct _prop_object *po = obj; |
1120 | |
1121 | if (obj == NULL) |
1122 | return (PROP_TYPE_UNKNOWN); |
1123 | |
1124 | return (po->po_type->pot_type); |
1125 | } |
1126 | |
1127 | /* |
1128 | * prop_object_equals -- |
1129 | * Returns true if thw two objects are equivalent. |
1130 | */ |
1131 | bool |
1132 | prop_object_equals(prop_object_t obj1, prop_object_t obj2) |
1133 | { |
1134 | return (prop_object_equals_with_error(obj1, obj2, NULL)); |
1135 | } |
1136 | |
1137 | bool |
1138 | prop_object_equals_with_error(prop_object_t obj1, prop_object_t obj2, |
1139 | bool *error_flag) |
1140 | { |
1141 | struct _prop_object *po1; |
1142 | struct _prop_object *po2; |
1143 | void *stored_pointer1, *stored_pointer2; |
1144 | prop_object_t next_obj1, next_obj2; |
1145 | struct _prop_stack stack; |
1146 | _prop_object_equals_rv_t ret; |
1147 | |
1148 | _prop_stack_init(&stack); |
1149 | if (error_flag) |
1150 | *error_flag = false; |
1151 | |
1152 | start_subtree: |
1153 | stored_pointer1 = NULL; |
1154 | stored_pointer2 = NULL; |
1155 | po1 = obj1; |
1156 | po2 = obj2; |
1157 | |
1158 | if (po1->po_type != po2->po_type) |
1159 | return (false); |
1160 | |
1161 | continue_subtree: |
1162 | ret = (*po1->po_type->pot_equals)(obj1, obj2, |
1163 | &stored_pointer1, &stored_pointer2, |
1164 | &next_obj1, &next_obj2); |
1165 | if (ret == _PROP_OBJECT_EQUALS_FALSE) |
1166 | goto finish; |
1167 | if (ret == _PROP_OBJECT_EQUALS_TRUE) { |
1168 | if (!_prop_stack_pop(&stack, &obj1, &obj2, |
1169 | &stored_pointer1, &stored_pointer2)) |
1170 | return true; |
1171 | po1 = obj1; |
1172 | po2 = obj2; |
1173 | goto continue_subtree; |
1174 | } |
1175 | _PROP_ASSERT(ret == _PROP_OBJECT_EQUALS_RECURSE); |
1176 | |
1177 | if (!_prop_stack_push(&stack, obj1, obj2, |
1178 | stored_pointer1, stored_pointer2)) { |
1179 | if (error_flag) |
1180 | *error_flag = true; |
1181 | goto finish; |
1182 | } |
1183 | obj1 = next_obj1; |
1184 | obj2 = next_obj2; |
1185 | goto start_subtree; |
1186 | |
1187 | finish: |
1188 | while (_prop_stack_pop(&stack, &obj1, &obj2, NULL, NULL)) { |
1189 | po1 = obj1; |
1190 | (*po1->po_type->pot_equals_finish)(obj1, obj2); |
1191 | } |
1192 | return (false); |
1193 | } |
1194 | |
1195 | /* |
1196 | * prop_object_iterator_next -- |
1197 | * Return the next item during an iteration. |
1198 | */ |
1199 | prop_object_t |
1200 | prop_object_iterator_next(prop_object_iterator_t pi) |
1201 | { |
1202 | |
1203 | return ((*pi->pi_next_object)(pi)); |
1204 | } |
1205 | |
1206 | /* |
1207 | * prop_object_iterator_reset -- |
1208 | * Reset the iterator to the first object so as to restart |
1209 | * iteration. |
1210 | */ |
1211 | void |
1212 | prop_object_iterator_reset(prop_object_iterator_t pi) |
1213 | { |
1214 | |
1215 | (*pi->pi_reset)(pi); |
1216 | } |
1217 | |
1218 | /* |
1219 | * prop_object_iterator_release -- |
1220 | * Release the object iterator. |
1221 | */ |
1222 | void |
1223 | prop_object_iterator_release(prop_object_iterator_t pi) |
1224 | { |
1225 | |
1226 | prop_object_release(pi->pi_obj); |
1227 | _PROP_FREE(pi, M_TEMP); |
1228 | } |
1229 | |