prop_object.c revision 1.1 1 /* $NetBSD: prop_object.c,v 1.1 2006/04/27 20:11:27 thorpej Exp $ */
2
3 /*-
4 * Copyright (c) 2006 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Jason R. Thorpe.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 * 3. All advertising materials mentioning features or use of this software
19 * must display the following acknowledgement:
20 * This product includes software developed by the NetBSD
21 * Foundation, Inc. and its contributors.
22 * 4. Neither the name of The NetBSD Foundation nor the names of its
23 * contributors may be used to endorse or promote products derived
24 * from this software without specific prior written permission.
25 *
26 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
27 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
28 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
30 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36 * POSSIBILITY OF SUCH DAMAGE.
37 */
38
39 #include <prop/prop_object.h>
40 #include "prop_object_impl.h"
41
42 #ifdef _STANDALONE
43 void *
44 _prop_standalone_calloc(size_t size)
45 {
46 void *rv;
47
48 rv = alloc(size);
49 if (rv != NULL)
50 memset(rv, 0, size);
51
52 return (rv);
53 }
54 #endif /* _STANDALONE */
55
56 /*
57 * _prop_object_init --
58 * Initialize an object. Called when sub-classes create
59 * an instance.
60 */
61 void
62 _prop_object_init(struct _prop_object *po)
63 {
64
65 po->po_refcnt = 1;
66 }
67
68 /*
69 * _prop_object_fini --
70 * Finalize an object. Called when sub-classes destroy
71 * an instance.
72 */
73 void
74 _prop_object_fini(struct _prop_object *po)
75 {
76 /* Nothing to do, currently. */
77 }
78
79 /*
80 * _prop_object_externalize_start_tag --
81 * Append an XML-style start tag to the externalize buffer.
82 */
83 boolean_t
84 _prop_object_externalize_start_tag(
85 struct _prop_object_externalize_context *ctx, const char *tag)
86 {
87 unsigned int i;
88
89 for (i = 0; i < ctx->poec_depth; i++) {
90 if (_prop_object_externalize_append_char(ctx, '\t') == FALSE)
91 return (FALSE);
92 }
93 if (_prop_object_externalize_append_char(ctx, '<') == FALSE ||
94 _prop_object_externalize_append_cstring(ctx, tag) == FALSE ||
95 _prop_object_externalize_append_char(ctx, '>') == FALSE)
96 return (FALSE);
97
98 return (TRUE);
99 }
100
101 /*
102 * _prop_object_externalize_end_tag --
103 * Append an XML-style end tag to the externalize buffer.
104 */
105 boolean_t
106 _prop_object_externalize_end_tag(
107 struct _prop_object_externalize_context *ctx, const char *tag)
108 {
109
110 if (_prop_object_externalize_append_char(ctx, '<') == FALSE ||
111 _prop_object_externalize_append_char(ctx, '/') == FALSE ||
112 _prop_object_externalize_append_cstring(ctx, tag) == FALSE ||
113 _prop_object_externalize_append_char(ctx, '>') == FALSE ||
114 _prop_object_externalize_append_char(ctx, '\n') == FALSE)
115 return (FALSE);
116
117 return (TRUE);
118 }
119
120 /*
121 * _prop_object_externalize_empty_tag --
122 * Append an XML-style empty tag to the externalize buffer.
123 */
124 boolean_t
125 _prop_object_externalize_empty_tag(
126 struct _prop_object_externalize_context *ctx, const char *tag)
127 {
128 unsigned int i;
129
130 for (i = 0; i < ctx->poec_depth; i++) {
131 if (_prop_object_externalize_append_char(ctx, '\t') == FALSE)
132 return (FALSE);
133 }
134
135 if (_prop_object_externalize_append_char(ctx, '<') == FALSE ||
136 _prop_object_externalize_append_cstring(ctx, tag) == FALSE ||
137 _prop_object_externalize_append_char(ctx, '/') == FALSE ||
138 _prop_object_externalize_append_char(ctx, '>') == FALSE ||
139 _prop_object_externalize_append_char(ctx, '\n') == FALSE)
140 return (FALSE);
141
142 return (TRUE);
143 }
144
145 /*
146 * _prop_object_externalize_append_cstring --
147 * Append a C string to the externalize buffer.
148 */
149 boolean_t
150 _prop_object_externalize_append_cstring(
151 struct _prop_object_externalize_context *ctx, const char *cp)
152 {
153
154 while (*cp != '\0') {
155 if (_prop_object_externalize_append_char(ctx,
156 (unsigned char) *cp) == FALSE)
157 return (FALSE);
158 cp++;
159 }
160
161 return (TRUE);
162 }
163
164 /*
165 * _prop_object_externalize_append_encoded_cstring --
166 * Append an encoded C string to the externalize buffer.
167 */
168 boolean_t
169 _prop_object_externalize_append_encoded_cstring(
170 struct _prop_object_externalize_context *ctx, const char *cp)
171 {
172
173 while (*cp != '\0') {
174 switch (*cp) {
175 case '<':
176 if (_prop_object_externalize_append_cstring(ctx,
177 "<") == FALSE)
178 return (FALSE);
179 break;
180 case '>':
181 if (_prop_object_externalize_append_cstring(ctx,
182 ">") == FALSE)
183 return (FALSE);
184 break;
185 case '&':
186 if (_prop_object_externalize_append_cstring(ctx,
187 "&") == FALSE)
188 return (FALSE);
189 break;
190 default:
191 if (_prop_object_externalize_append_char(ctx,
192 (unsigned char) *cp) == FALSE)
193 return (FALSE);
194 break;
195 }
196 cp++;
197 }
198
199 return (TRUE);
200 }
201
202 #define BUF_EXPAND 256
203
204 /*
205 * _prop_object_externalize_append_char --
206 * Append a single character to the externalize buffer.
207 */
208 boolean_t
209 _prop_object_externalize_append_char(
210 struct _prop_object_externalize_context *ctx, unsigned char c)
211 {
212
213 _PROP_ASSERT(ctx->poec_capacity != 0);
214 _PROP_ASSERT(ctx->poec_buf != NULL);
215 _PROP_ASSERT(ctx->poec_len <= ctx->poec_capacity);
216
217 if (ctx->poec_len == ctx->poec_capacity) {
218 char *ocp = ctx->poec_buf;
219 char *cp = _PROP_MALLOC(ctx->poec_capacity + BUF_EXPAND,
220 M_TEMP);
221 if (cp == NULL)
222 return (FALSE);
223 memcpy(cp, ocp, ctx->poec_capacity);
224 ctx->poec_capacity = ctx->poec_capacity + BUF_EXPAND;
225 _PROP_FREE(ocp, M_TEMP);
226 ctx->poec_buf = cp;
227 }
228
229 ctx->poec_buf[ctx->poec_len++] = c;
230
231 return (TRUE);
232 }
233
234 /*
235 * _prop_object_externalize_context_alloc --
236 * Allocate an externalize context.
237 */
238 struct _prop_object_externalize_context *
239 _prop_object_externalize_context_alloc(void)
240 {
241 struct _prop_object_externalize_context *ctx;
242
243 ctx = _PROP_MALLOC(sizeof(*ctx), M_TEMP);
244 if (ctx != NULL) {
245 ctx->poec_buf = _PROP_MALLOC(BUF_EXPAND, M_TEMP);
246 if (ctx->poec_buf == NULL) {
247 _PROP_FREE(ctx, M_TEMP);
248 return (NULL);
249 }
250 ctx->poec_len = 0;
251 ctx->poec_capacity = BUF_EXPAND;
252 ctx->poec_depth = 0;
253 }
254 return (ctx);
255 }
256
257 /*
258 * _prop_object_externalize_context_free --
259 * Free an externalize context.
260 */
261 void
262 _prop_object_externalize_context_free(
263 struct _prop_object_externalize_context *ctx)
264 {
265
266 /* Buffer is always freed by the caller. */
267 _PROP_FREE(ctx, M_TEMP);
268 }
269
270 /*
271 * _prop_object_internalize_skip_comment --
272 * Skip the body and end tag of a comment.
273 */
274 static boolean_t
275 _prop_object_internalize_skip_comment(
276 struct _prop_object_internalize_context *ctx)
277 {
278 const char *cp = ctx->poic_cp;
279
280 while (!_PROP_EOF(*cp)) {
281 if (cp[0] == '-' &&
282 cp[1] == '-' &&
283 cp[2] == '>') {
284 ctx->poic_cp = cp + 3;
285 return (TRUE);
286 }
287 cp++;
288 }
289
290 return (FALSE); /* ran out of buffer */
291 }
292
293 /*
294 * _prop_object_internalize_find_tag --
295 * Find the next tag in an XML stream. Optionally compare the found
296 * tag to an expected tag name. State of the context is undefined
297 * if this routine returns FALSE. Upon success, the context points
298 * to the first octet after the tag.
299 */
300 boolean_t
301 _prop_object_internalize_find_tag(struct _prop_object_internalize_context *ctx,
302 const char *tag, _prop_tag_type_t type)
303 {
304 const char *cp;
305 size_t taglen;
306
307 if (tag != NULL)
308 taglen = strlen(tag);
309 else
310 taglen = 0;
311
312 start_over:
313 cp = ctx->poic_cp;
314
315 /*
316 * Find the start of the tag.
317 */
318 while (_PROP_ISSPACE(*cp))
319 cp++;
320 if (_PROP_EOF(*cp))
321 return (FALSE);
322
323 if (*cp != '<')
324 return (FALSE);
325
326 ctx->poic_tag_start = cp++;
327 if (_PROP_EOF(*cp))
328 return (FALSE);
329
330 if (*cp == '!') {
331 if (cp[1] != '-' || cp[2] != '-')
332 return (FALSE);
333 /*
334 * Comment block -- only allowed if we are allowed to
335 * return a start tag.
336 */
337 if (type == _PROP_TAG_TYPE_END)
338 return (FALSE);
339 ctx->poic_cp = cp + 3;
340 if (_prop_object_internalize_skip_comment(ctx) == FALSE)
341 return (FALSE);
342 goto start_over;
343 }
344
345 if (*cp == '/') {
346 if (type != _PROP_TAG_TYPE_END &&
347 type != _PROP_TAG_TYPE_EITHER)
348 return (FALSE);
349 cp++;
350 if (_PROP_EOF(*cp))
351 return (FALSE);
352 ctx->poic_tag_type = _PROP_TAG_TYPE_END;
353 } else {
354 if (type != _PROP_TAG_TYPE_START &&
355 type != _PROP_TAG_TYPE_EITHER)
356 return (FALSE);
357 ctx->poic_tag_type = _PROP_TAG_TYPE_START;
358 }
359
360 ctx->poic_tagname = cp;
361
362 while (!_PROP_ISSPACE(*cp) && *cp != '/' && *cp != '>')
363 cp++;
364 if (_PROP_EOF(*cp))
365 return (FALSE);
366
367 ctx->poic_tagname_len = cp - ctx->poic_tagname;
368
369 /* Make sure this is the tag we're looking for. */
370 if (tag != NULL &&
371 (taglen != ctx->poic_tagname_len ||
372 memcmp(tag, ctx->poic_tagname, taglen) != 0))
373 return (FALSE);
374
375 /* Check for empty tag. */
376 if (*cp == '/') {
377 if (ctx->poic_tag_type != _PROP_TAG_TYPE_START)
378 return(FALSE); /* only valid on start tags */
379 ctx->poic_is_empty_element = TRUE;
380 cp++;
381 if (_PROP_EOF(*cp) || *cp != '>')
382 return (FALSE);
383 } else
384 ctx->poic_is_empty_element = FALSE;
385
386 /* Easy case of no arguments. */
387 if (*cp == '>') {
388 ctx->poic_tagattr = NULL;
389 ctx->poic_tagattr_len = 0;
390 ctx->poic_tagattrval = NULL;
391 ctx->poic_tagattrval_len = 0;
392 ctx->poic_cp = cp + 1;
393 return (TRUE);
394 }
395
396 _PROP_ASSERT(!_PROP_EOF(*cp));
397 cp++;
398 if (_PROP_EOF(*cp))
399 return (FALSE);
400
401 while (_PROP_ISSPACE(*cp))
402 cp++;
403 if (_PROP_EOF(*cp))
404 return (FALSE);
405
406 ctx->poic_tagattr = cp;
407
408 while (!_PROP_ISSPACE(*cp) && *cp != '=')
409 cp++;
410 if (_PROP_EOF(*cp))
411 return (FALSE);
412
413 ctx->poic_tagattr_len = cp - ctx->poic_tagattr;
414
415 cp++;
416 if (*cp != '\"')
417 return (FALSE);
418 cp++;
419 if (_PROP_EOF(*cp))
420 return (FALSE);
421
422 ctx->poic_tagattrval = cp;
423 while (*cp != '\"')
424 cp++;
425 if (_PROP_EOF(*cp))
426 return (FALSE);
427 ctx->poic_tagattrval_len = cp - ctx->poic_tagattrval;
428
429 cp++;
430 if (*cp != '>')
431 return (FALSE);
432
433 ctx->poic_cp = cp + 1;
434 return (TRUE);
435 }
436
437 /*
438 * _prop_object_internalize_decode_string --
439 * Decode an encoded string.
440 */
441 boolean_t
442 _prop_object_internalize_decode_string(
443 struct _prop_object_internalize_context *ctx,
444 char *target, size_t targsize, size_t *sizep,
445 const char **cpp)
446 {
447 const char *src;
448 size_t tarindex;
449 char c;
450
451 tarindex = 0;
452 src = ctx->poic_cp;
453
454 for (;;) {
455 if (_PROP_EOF(*src))
456 return (FALSE);
457 if (*src == '<') {
458 break;
459 }
460
461 if ((c = *src) == '&') {
462 if (src[1] == 'a' &&
463 src[2] == 'm' &&
464 src[3] == 'p' &&
465 src[4] == ';') {
466 c = '&';
467 src += 5;
468 } else if (src[1] == 'l' &&
469 src[2] == 't' &&
470 src[3] == ';') {
471 c = '<';
472 src += 4;
473 } else if (src[1] == 'g' &&
474 src[2] == 't' &&
475 src[3] == ';') {
476 c = '>';
477 src += 4;
478 } else if (src[1] == 'a' &&
479 src[2] == 'p' &&
480 src[3] == 'o' &&
481 src[4] == 's' &&
482 src[5] == ';') {
483 c = '\'';
484 src += 6;
485 } else if (src[1] == 'q' &&
486 src[2] == 'u' &&
487 src[3] == 'o' &&
488 src[4] == 't' &&
489 src[5] == ';') {
490 c = '\"';
491 src += 6;
492 } else
493 return (FALSE);
494 } else
495 src++;
496 if (target) {
497 if (tarindex >= targsize)
498 return (FALSE);
499 target[tarindex] = c;
500 }
501 tarindex++;
502 }
503
504 _PROP_ASSERT(*src == '<');
505 if (sizep != NULL)
506 *sizep = tarindex;
507 if (cpp != NULL)
508 *cpp = src;
509
510 return (TRUE);
511 }
512
513 /*
514 * _prop_object_internalize_match --
515 * Returns true if the two character streams match.
516 */
517 boolean_t
518 _prop_object_internalize_match(const char *str1, size_t len1,
519 const char *str2, size_t len2)
520 {
521
522 return (len1 == len2 && memcmp(str1, str2, len1) == 0);
523 }
524
525 #define INTERNALIZER(t, f) \
526 { t, sizeof(t) - 1, f }
527
528 static const struct _prop_object_internalizer {
529 const char *poi_tag;
530 size_t poi_taglen;
531 prop_object_t (*poi_intern)(
532 struct _prop_object_internalize_context *);
533 } _prop_object_internalizer_table[] = {
534 INTERNALIZER("array", _prop_array_internalize),
535
536 INTERNALIZER("true", _prop_bool_internalize),
537 INTERNALIZER("false", _prop_bool_internalize),
538
539 INTERNALIZER("data", _prop_data_internalize),
540
541 INTERNALIZER("dict", _prop_dictionary_internalize),
542
543 INTERNALIZER("integer", _prop_number_internalize),
544
545 INTERNALIZER("string", _prop_string_internalize),
546
547 { 0 }
548 };
549
550 #undef INTERNALIZER
551
552 /*
553 * _prop_object_internalize_by_tag --
554 * Determine the object type from the tag in the context and
555 * internalize it.
556 */
557 prop_object_t
558 _prop_object_internalize_by_tag(struct _prop_object_internalize_context *ctx)
559 {
560 const struct _prop_object_internalizer *poi;
561
562 for (poi = _prop_object_internalizer_table;
563 poi->poi_tag != NULL; poi++) {
564 if (_prop_object_internalize_match(ctx->poic_tagname,
565 ctx->poic_tagname_len,
566 poi->poi_tag,
567 poi->poi_taglen))
568 return ((*poi->poi_intern)(ctx));
569 }
570
571 return (NULL);
572 }
573
574 /*
575 * _prop_object_internalize_context_alloc --
576 * Allocate an internalize context.
577 */
578 struct _prop_object_internalize_context *
579 _prop_object_internalize_context_alloc(const char *xml)
580 {
581 struct _prop_object_internalize_context *ctx;
582
583 ctx = _PROP_MALLOC(sizeof(struct _prop_object_internalize_context),
584 M_TEMP);
585 if (ctx == NULL)
586 return (NULL);
587
588 ctx->poic_xml = ctx->poic_cp = xml;
589
590 /*
591 * Skip any whitespace and XML preamble stuff that we don't
592 * know about / care about.
593 */
594 for (;;) {
595 while (_PROP_ISSPACE(*xml))
596 xml++;
597 if (_PROP_EOF(*xml) || *xml != '<')
598 goto bad;
599
600 #define MATCH(str) (memcmp(&xml[1], str, sizeof(str) - 1) == 0)
601
602 /*
603 * Skip over the XML preamble that Apple XML property
604 * lists usually include at the top of the file.
605 */
606 if (MATCH("?xml ") ||
607 MATCH("!DOCTYPE plist")) {
608 while (*xml != '>' && !_PROP_EOF(*xml))
609 xml++;
610 if (_PROP_EOF(*xml))
611 goto bad;
612 xml++; /* advance past the '>' */
613 continue;
614 }
615
616 if (MATCH("<!--")) {
617 ctx->poic_cp = xml + 4;
618 if (_prop_object_internalize_skip_comment(ctx) == FALSE)
619 goto bad;
620 xml = ctx->poic_cp;
621 continue;
622 }
623
624 #undef MATCH
625
626 /*
627 * We don't think we should skip it, so let's hope we can
628 * parse it.
629 */
630 break;
631 }
632
633 ctx->poic_cp = xml;
634 return (ctx);
635 bad:
636 _PROP_FREE(ctx, M_TEMP);
637 return (NULL);
638 }
639
640 /*
641 * _prop_object_internalize_context_free --
642 * Free an internalize context.
643 */
644 void
645 _prop_object_internalize_context_free(
646 struct _prop_object_internalize_context *ctx)
647 {
648
649 _PROP_FREE(ctx, M_TEMP);
650 }
651
652 /*
653 * Retain / release serialization --
654 *
655 * Eventually we would like to use atomic operations. But until we have
656 * an MI API for them that is common to userland and the kernel, we will
657 * use a lock instead.
658 *
659 * We use a single global mutex for all serialization. In the kernel, because
660 * we are still under a biglock, this will basically never contend (properties
661 * cannot be manipulated at interrupt level). In userland, this will cost
662 * nothing for single-threaded programs. For multi-threaded programs, there
663 * could be contention, but it probably won't cost that much unless the program
664 * makes heavy use of property lists.
665 */
666 #if defined(_KERNEL)
667 #include <sys/lock.h>
668 static struct simplelock _prop_refcnt_mutex = SIMPLELOCK_INITIALIZER;
669
670 #define _PROP_REFCNT_LOCK() simple_lock(&_prop_refcnt_mutex)
671 #define _PROP_REFCNT_UNLOCK() simple_unlock(&_prop_refcnt_mutex)
672 #elif defined(_STANDALONE)
673 /*
674 * No locking necessary for standalone environments.
675 */
676 #define _PROP_REFCNT_LOCK() /* nothing */
677 #define _PROP_REFCNT_UNLOCK() /* nothing */
678 #elif defined(__NetBSD__) && defined(_LIBPROP)
679 /*
680 * Use the same mechanism as libc; we get pthread mutexes for threaded
681 * programs and do-nothing stubs for non-threaded programs.
682 */
683 #include "reentrant.h"
684 static mutex_t _prop_refcnt_mutex = MUTEX_INITIALIZER;
685
686 #define _PROP_REFCNT_LOCK() mutex_lock(&_prop_refcnt_mutex)
687 #define _PROP_REFCNT_UNLOCK() mutex_unlock(&_prop_refcnt_mutex)
688 #elif defined(HAVE_NBTOOL_CONFIG_H)
689 /* None of NetBSD's build tools are multi-threaded. */
690 #define _PROP_REFCNT_LOCK() /* nothing */
691 #define _PROP_REFCNT_UNLOCK() /* nothing */
692 #else
693 /* Use pthread mutexes everywhere else. */
694 #include <pthread.h>
695 static pthread_mutex_t _prop_refcnt_mutex = PTHREAD_MUTEX_INITIALIZER;
696
697 #define _PROP_REFCNT_LOCK() pthread_mutex_lock(&_prop_refcnt_mutex)
698 #define _PROP_REFCNT_UNLOCK() pthread_mutex_unlock(&_prop_refcnt_mutex)
699 #endif
700
701 /*
702 * prop_object_retain --
703 * Increment the reference count on an object.
704 */
705 void
706 prop_object_retain(prop_object_t obj)
707 {
708 struct _prop_object *po = obj;
709 uint32_t ocnt;
710
711 _PROP_REFCNT_LOCK();
712 ocnt = po->po_refcnt++;
713 _PROP_REFCNT_UNLOCK();
714
715 _PROP_ASSERT(ocnt != 0xffffffffU);
716 }
717
718 /*
719 * prop_object_release --
720 * Decrement the reference count on an object.
721 *
722 * Free the object if we are releasing the final
723 * reference.
724 */
725 void
726 prop_object_release(prop_object_t obj)
727 {
728 struct _prop_object *po = obj;
729 uint32_t ocnt;
730
731 _PROP_REFCNT_LOCK();
732 ocnt = po->po_refcnt--;
733 _PROP_REFCNT_UNLOCK();
734
735 _PROP_ASSERT(ocnt != 0);
736 if (ocnt == 1)
737 (*po->po_free)(po);
738 }
739
740 /*
741 * prop_object_type --
742 * Return the type of an object.
743 */
744 prop_type_t
745 prop_object_type(prop_object_t obj)
746 {
747 struct _prop_object *po = obj;
748
749 return (po->po_type);
750 }
751
752 /*
753 * prop_object_iterator_next --
754 * Return the next item during an iteration.
755 */
756 prop_object_t
757 prop_object_iterator_next(prop_object_iterator_t pi)
758 {
759
760 return ((*pi->pi_next_object)(pi));
761 }
762
763 /*
764 * prop_object_iterator_reset --
765 * Reset the iterator to the first object so as to restart
766 * iteration.
767 */
768 void
769 prop_object_iterator_reset(prop_object_iterator_t pi)
770 {
771
772 (*pi->pi_reset)(pi);
773 }
774
775 /*
776 * prop_object_iterator_release --
777 * Release the object iterator.
778 */
779 void
780 prop_object_iterator_release(prop_object_iterator_t pi)
781 {
782
783 prop_object_release(pi->pi_obj);
784 _PROP_FREE(pi, M_TEMP);
785 }
786