Home | History | Annotate | Line # | Download | only in libprop
      1 /*	$NetBSD: prop_array.c,v 1.27 2025/05/14 03:25:45 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_array.h>
     34 
     35 #if !defined(_KERNEL) && !defined(_STANDALONE)
     36 #include <errno.h>
     37 #endif
     38 
     39 struct _prop_array {
     40 	struct _prop_object	pa_obj;
     41 	_PROP_RWLOCK_DECL(pa_rwlock)
     42 	prop_object_t *		pa_array;
     43 	unsigned int		pa_capacity;
     44 	unsigned int		pa_count;
     45 	int			pa_flags;
     46 
     47 	uint32_t		pa_version;
     48 };
     49 
     50 #define PA_F_IMMUTABLE		0x01	/* array is immutable */
     51 
     52 _PROP_POOL_INIT(_prop_array_pool, sizeof(struct _prop_array), "proparay")
     53 _PROP_MALLOC_DEFINE(M_PROP_ARRAY, "prop array",
     54 		    "property array container object")
     55 
     56 static const struct _prop_object_type_tags _prop_array_type_tags = {
     57 	.xml_tag		=	"array",
     58 	.json_open_tag		=	"[",
     59 	.json_close_tag		=	"]",
     60 	.json_empty_sep		=	" ",
     61 };
     62 
     63 struct _prop_array_iterator {
     64 	struct _prop_object_iterator pai_base;
     65 	unsigned int		pai_index;
     66 };
     67 
     68 static _prop_object_free_rv_t
     69 		_prop_array_free(prop_stack_t, prop_object_t *);
     70 static void	_prop_array_emergency_free(prop_object_t);
     71 static bool	_prop_array_externalize(
     72 				struct _prop_object_externalize_context *,
     73 				void *);
     74 static _prop_object_equals_rv_t
     75 		_prop_array_equals(prop_object_t, prop_object_t,
     76 				   void **, void **,
     77 				   prop_object_t *, prop_object_t *);
     78 static void	_prop_array_equals_finish(prop_object_t, prop_object_t);
     79 static struct _prop_array_iterator *
     80 		_prop_array_iterator_locked(prop_array_t);
     81 static prop_object_t
     82 		_prop_array_iterator_next_object_locked(void *);
     83 static void	_prop_array_iterator_reset_locked(void *);
     84 
     85 static const struct _prop_object_type _prop_object_type_array = {
     86 	.pot_type		=	PROP_TYPE_ARRAY,
     87 	.pot_free		=	_prop_array_free,
     88 	.pot_emergency_free	=	_prop_array_emergency_free,
     89 	.pot_extern		=	_prop_array_externalize,
     90 	.pot_equals		=	_prop_array_equals,
     91 	.pot_equals_finish	=	_prop_array_equals_finish,
     92 };
     93 
     94 #define prop_object_is_array(x)		\
     95 	((x) != NULL && (x)->pa_obj.po_type == &_prop_object_type_array)
     96 
     97 #define prop_array_is_immutable(x) (((x)->pa_flags & PA_F_IMMUTABLE) != 0)
     98 
     99 #define EXPAND_STEP		16
    100 
    101 static _prop_object_free_rv_t
    102 _prop_array_free(prop_stack_t stack, prop_object_t *obj)
    103 {
    104 	prop_array_t pa = *obj;
    105 	prop_object_t po;
    106 
    107 	_PROP_ASSERT(pa->pa_count <= pa->pa_capacity);
    108 	_PROP_ASSERT((pa->pa_capacity == 0 && pa->pa_array == NULL) ||
    109 		     (pa->pa_capacity != 0 && pa->pa_array != NULL));
    110 
    111 	/* The easy case is an empty array, just free and return. */
    112 	if (pa->pa_count == 0) {
    113 		if (pa->pa_array != NULL)
    114 			_PROP_FREE(pa->pa_array, M_PROP_ARRAY);
    115 
    116 		_PROP_RWLOCK_DESTROY(pa->pa_rwlock);
    117 
    118 		_PROP_POOL_PUT(_prop_array_pool, pa);
    119 
    120 		return (_PROP_OBJECT_FREE_DONE);
    121 	}
    122 
    123 	po = pa->pa_array[pa->pa_count - 1];
    124 	_PROP_ASSERT(po != NULL);
    125 
    126 	if (stack == NULL) {
    127 		/*
    128 		 * If we are in emergency release mode,
    129 		 * just let caller recurse down.
    130 		 */
    131 		*obj = po;
    132 		return (_PROP_OBJECT_FREE_FAILED);
    133 	}
    134 
    135 	/* Otherwise, try to push the current object on the stack. */
    136 	if (!_prop_stack_push(stack, pa, NULL, NULL, NULL)) {
    137 		/* Push failed, entering emergency release mode. */
    138 		return (_PROP_OBJECT_FREE_FAILED);
    139 	}
    140 	/* Object pushed on stack, caller will release it. */
    141 	--pa->pa_count;
    142 	*obj = po;
    143 	return (_PROP_OBJECT_FREE_RECURSE);
    144 }
    145 
    146 static void
    147 _prop_array_emergency_free(prop_object_t obj)
    148 {
    149 	prop_array_t pa = obj;
    150 
    151 	_PROP_ASSERT(pa->pa_count != 0);
    152 	--pa->pa_count;
    153 }
    154 
    155 static bool
    156 _prop_array_externalize(struct _prop_object_externalize_context *ctx,
    157 			void *v)
    158 {
    159 	prop_array_t pa = v;
    160 	struct _prop_object *po;
    161 	struct _prop_array_iterator *pai;
    162 	bool rv = false;
    163 	const char * const sep =
    164 	    ctx->poec_format == PROP_FORMAT_JSON ? "," : NULL;
    165 
    166 	_PROP_ASSERT(ctx->poec_format == PROP_FORMAT_XML ||
    167 		     ctx->poec_format == PROP_FORMAT_JSON);
    168 
    169 	_PROP_RWLOCK_RDLOCK(pa->pa_rwlock);
    170 
    171 	if (pa->pa_count == 0) {
    172 		_PROP_RWLOCK_UNLOCK(pa->pa_rwlock);
    173 		return (_prop_extern_append_empty_tag(ctx,
    174 		    &_prop_array_type_tags));
    175 	}
    176 
    177 	if (_prop_extern_append_start_tag(ctx,
    178 				&_prop_array_type_tags, NULL) == false ||
    179 	    _prop_extern_end_line(ctx, NULL) == false)
    180 		goto out;
    181 
    182 	pai = _prop_array_iterator_locked(pa);
    183 	if (pai == NULL)
    184 		goto out;
    185 
    186 	ctx->poec_depth++;
    187 	_PROP_ASSERT(ctx->poec_depth != 0);
    188 
    189 	while ((po = _prop_array_iterator_next_object_locked(pai)) != NULL) {
    190 		if (_prop_extern_start_line(ctx) == false ||
    191 		    (*po->po_type->pot_extern)(ctx, po) == false ||
    192 		    _prop_extern_end_line(ctx,
    193 				pai->pai_index < pa->pa_count ?
    194 							sep : NULL) == false) {
    195 			prop_object_iterator_release(&pai->pai_base);
    196 			goto out;
    197 		}
    198 	}
    199 
    200 	prop_object_iterator_release(&pai->pai_base);
    201 
    202 	ctx->poec_depth--;
    203 	if (_prop_extern_start_line(ctx) == false ||
    204 	    _prop_extern_append_end_tag(ctx,
    205 					&_prop_array_type_tags) == false) {
    206 		goto out;
    207 	}
    208 
    209 	rv = true;
    210 
    211  out:
    212 	_PROP_RWLOCK_UNLOCK(pa->pa_rwlock);
    213 	return (rv);
    214 }
    215 
    216 /* ARGSUSED */
    217 static _prop_object_equals_rv_t
    218 _prop_array_equals(prop_object_t v1, prop_object_t v2,
    219     void **stored_pointer1, void **stored_pointer2,
    220     prop_object_t *next_obj1, prop_object_t *next_obj2)
    221 {
    222 	prop_array_t array1 = v1;
    223 	prop_array_t array2 = v2;
    224 	uintptr_t idx;
    225 	_prop_object_equals_rv_t rv = _PROP_OBJECT_EQUALS_FALSE;
    226 
    227 	if (array1 == array2)
    228 		return (_PROP_OBJECT_EQUALS_TRUE);
    229 
    230 	_PROP_ASSERT(*stored_pointer1 == *stored_pointer2);
    231 	idx = (uintptr_t)*stored_pointer1;
    232 
    233 	/* For the first iteration, lock the objects. */
    234 	if (idx == 0) {
    235 		if ((uintptr_t)array1 < (uintptr_t)array2) {
    236 			_PROP_RWLOCK_RDLOCK(array1->pa_rwlock);
    237 			_PROP_RWLOCK_RDLOCK(array2->pa_rwlock);
    238 		} else {
    239 			_PROP_RWLOCK_RDLOCK(array2->pa_rwlock);
    240 			_PROP_RWLOCK_RDLOCK(array1->pa_rwlock);
    241 		}
    242 	}
    243 
    244 	if (array1->pa_count != array2->pa_count)
    245 		goto out;
    246 	if (idx == array1->pa_count) {
    247 		rv = _PROP_OBJECT_EQUALS_TRUE;
    248 		goto out;
    249 	}
    250 	_PROP_ASSERT(idx < array1->pa_count);
    251 
    252 	*stored_pointer1 = (void *)(idx + 1);
    253 	*stored_pointer2 = (void *)(idx + 1);
    254 
    255 	*next_obj1 = array1->pa_array[idx];
    256 	*next_obj2 = array2->pa_array[idx];
    257 
    258 	return (_PROP_OBJECT_EQUALS_RECURSE);
    259 
    260  out:
    261 	_PROP_RWLOCK_UNLOCK(array1->pa_rwlock);
    262 	_PROP_RWLOCK_UNLOCK(array2->pa_rwlock);
    263 	return (rv);
    264 }
    265 
    266 static void
    267 _prop_array_equals_finish(prop_object_t v1, prop_object_t v2)
    268 {
    269 	_PROP_RWLOCK_UNLOCK(((prop_array_t)v1)->pa_rwlock);
    270 	_PROP_RWLOCK_UNLOCK(((prop_array_t)v2)->pa_rwlock);
    271 }
    272 
    273 static prop_array_t
    274 _prop_array_alloc(unsigned int capacity)
    275 {
    276 	prop_array_t pa;
    277 	prop_object_t *array;
    278 
    279 	if (capacity != 0) {
    280 		array = _PROP_CALLOC(capacity * sizeof(prop_object_t),
    281 				     M_PROP_ARRAY);
    282 		if (array == NULL)
    283 			return (NULL);
    284 	} else
    285 		array = NULL;
    286 
    287 	pa = _PROP_POOL_GET(_prop_array_pool);
    288 	if (pa != NULL) {
    289 		_prop_object_init(&pa->pa_obj, &_prop_object_type_array);
    290 		pa->pa_obj.po_type = &_prop_object_type_array;
    291 
    292 		_PROP_RWLOCK_INIT(pa->pa_rwlock);
    293 		pa->pa_array = array;
    294 		pa->pa_capacity = capacity;
    295 		pa->pa_count = 0;
    296 		pa->pa_flags = 0;
    297 
    298 		pa->pa_version = 0;
    299 	} else if (array != NULL)
    300 		_PROP_FREE(array, M_PROP_ARRAY);
    301 
    302 	return (pa);
    303 }
    304 
    305 static bool
    306 _prop_array_expand(prop_array_t pa, unsigned int capacity)
    307 {
    308 	prop_object_t *array, *oarray;
    309 
    310 	/*
    311 	 * Array must be WRITE-LOCKED.
    312 	 */
    313 
    314 	oarray = pa->pa_array;
    315 
    316 	array = _PROP_CALLOC(capacity * sizeof(*array), M_PROP_ARRAY);
    317 	if (array == NULL)
    318 		return (false);
    319 	if (oarray != NULL)
    320 		memcpy(array, oarray, pa->pa_capacity * sizeof(*array));
    321 	pa->pa_array = array;
    322 	pa->pa_capacity = capacity;
    323 
    324 	if (oarray != NULL)
    325 		_PROP_FREE(oarray, M_PROP_ARRAY);
    326 
    327 	return (true);
    328 }
    329 
    330 static prop_object_t
    331 _prop_array_iterator_next_object_locked(void *v)
    332 {
    333 	struct _prop_array_iterator *pai = v;
    334 	prop_array_t pa = pai->pai_base.pi_obj;
    335 	prop_object_t po = NULL;
    336 
    337 	_PROP_ASSERT(prop_object_is_array(pa));
    338 
    339 	if (pa->pa_version != pai->pai_base.pi_version)
    340 		goto out;	/* array changed during iteration */
    341 
    342 	_PROP_ASSERT(pai->pai_index <= pa->pa_count);
    343 
    344 	if (pai->pai_index == pa->pa_count)
    345 		goto out;	/* we've iterated all objects */
    346 
    347 	po = pa->pa_array[pai->pai_index];
    348 	pai->pai_index++;
    349 
    350  out:
    351 	return (po);
    352 }
    353 
    354 static prop_object_t
    355 _prop_array_iterator_next_object(void *v)
    356 {
    357 	struct _prop_array_iterator *pai = v;
    358 	prop_array_t pa _PROP_ARG_UNUSED = pai->pai_base.pi_obj;
    359 	prop_object_t po;
    360 
    361 	_PROP_ASSERT(prop_object_is_array(pa));
    362 
    363 	_PROP_RWLOCK_RDLOCK(pa->pa_rwlock);
    364 	po = _prop_array_iterator_next_object_locked(pai);
    365 	_PROP_RWLOCK_UNLOCK(pa->pa_rwlock);
    366 	return (po);
    367 }
    368 
    369 static void
    370 _prop_array_iterator_reset_locked(void *v)
    371 {
    372 	struct _prop_array_iterator *pai = v;
    373 	prop_array_t pa = pai->pai_base.pi_obj;
    374 
    375 	_PROP_ASSERT(prop_object_is_array(pa));
    376 
    377 	pai->pai_index = 0;
    378 	pai->pai_base.pi_version = pa->pa_version;
    379 }
    380 
    381 static void
    382 _prop_array_iterator_reset(void *v)
    383 {
    384 	struct _prop_array_iterator *pai = v;
    385 	prop_array_t pa _PROP_ARG_UNUSED = pai->pai_base.pi_obj;
    386 
    387 	_PROP_ASSERT(prop_object_is_array(pa));
    388 
    389 	_PROP_RWLOCK_RDLOCK(pa->pa_rwlock);
    390 	_prop_array_iterator_reset_locked(pai);
    391 	_PROP_RWLOCK_UNLOCK(pa->pa_rwlock);
    392 }
    393 
    394 /*
    395  * prop_array_create --
    396  *	Create an empty array.
    397  */
    398 _PROP_EXPORT prop_array_t
    399 prop_array_create(void)
    400 {
    401 
    402 	return (_prop_array_alloc(0));
    403 }
    404 
    405 /*
    406  * prop_array_create_with_capacity --
    407  *	Create an array with the capacity to store N objects.
    408  */
    409 _PROP_EXPORT prop_array_t
    410 prop_array_create_with_capacity(unsigned int capacity)
    411 {
    412 
    413 	return (_prop_array_alloc(capacity));
    414 }
    415 
    416 /*
    417  * prop_array_copy --
    418  *	Copy an array.	The new array has an initial capacity equal to
    419  *	the number of objects stored in the original array.  The new
    420  *	array contains references to the original array's objects, not
    421  *	copies of those objects (i.e. a shallow copy).
    422  */
    423 _PROP_EXPORT prop_array_t
    424 prop_array_copy(prop_array_t opa)
    425 {
    426 	prop_array_t pa;
    427 	prop_object_t po;
    428 	unsigned int idx;
    429 
    430 	if (! prop_object_is_array(opa))
    431 		return (NULL);
    432 
    433 	_PROP_RWLOCK_RDLOCK(opa->pa_rwlock);
    434 
    435 	pa = _prop_array_alloc(opa->pa_count);
    436 	if (pa != NULL) {
    437 		for (idx = 0; idx < opa->pa_count; idx++) {
    438 			po = opa->pa_array[idx];
    439 			prop_object_retain(po);
    440 			pa->pa_array[idx] = po;
    441 		}
    442 		pa->pa_count = opa->pa_count;
    443 		pa->pa_flags = opa->pa_flags;
    444 	}
    445 	_PROP_RWLOCK_UNLOCK(opa->pa_rwlock);
    446 	return (pa);
    447 }
    448 
    449 /*
    450  * prop_array_copy_mutable --
    451  *	Like prop_array_copy(), but the resulting array is mutable.
    452  */
    453 _PROP_EXPORT prop_array_t
    454 prop_array_copy_mutable(prop_array_t opa)
    455 {
    456 	prop_array_t pa;
    457 
    458 	pa = prop_array_copy(opa);
    459 	if (pa != NULL)
    460 		pa->pa_flags &= ~PA_F_IMMUTABLE;
    461 
    462 	return (pa);
    463 }
    464 
    465 /*
    466  * prop_array_capacity --
    467  *	Return the capacity of the array.
    468  */
    469 _PROP_EXPORT unsigned int
    470 prop_array_capacity(prop_array_t pa)
    471 {
    472 	unsigned int rv;
    473 
    474 	if (! prop_object_is_array(pa))
    475 		return (0);
    476 
    477 	_PROP_RWLOCK_RDLOCK(pa->pa_rwlock);
    478 	rv = pa->pa_capacity;
    479 	_PROP_RWLOCK_UNLOCK(pa->pa_rwlock);
    480 
    481 	return (rv);
    482 }
    483 
    484 /*
    485  * prop_array_count --
    486  *	Return the number of objects stored in the array.
    487  */
    488 _PROP_EXPORT unsigned int
    489 prop_array_count(prop_array_t pa)
    490 {
    491 	unsigned int rv;
    492 
    493 	if (! prop_object_is_array(pa))
    494 		return (0);
    495 
    496 	_PROP_RWLOCK_RDLOCK(pa->pa_rwlock);
    497 	rv = pa->pa_count;
    498 	_PROP_RWLOCK_UNLOCK(pa->pa_rwlock);
    499 
    500 	return (rv);
    501 }
    502 
    503 /*
    504  * prop_array_ensure_capacity --
    505  *	Ensure that the array has the capacity to store the specified
    506  *	total number of objects (including the objects already stored
    507  *	in the array).
    508  */
    509 _PROP_EXPORT bool
    510 prop_array_ensure_capacity(prop_array_t pa, unsigned int capacity)
    511 {
    512 	bool rv;
    513 
    514 	if (! prop_object_is_array(pa))
    515 		return (false);
    516 
    517 	_PROP_RWLOCK_WRLOCK(pa->pa_rwlock);
    518 	if (capacity > pa->pa_capacity)
    519 		rv = _prop_array_expand(pa, capacity);
    520 	else
    521 		rv = true;
    522 	_PROP_RWLOCK_UNLOCK(pa->pa_rwlock);
    523 
    524 	return (rv);
    525 }
    526 
    527 static struct _prop_array_iterator *
    528 _prop_array_iterator_locked(prop_array_t pa)
    529 {
    530 	struct _prop_array_iterator *pai;
    531 
    532 	if (! prop_object_is_array(pa))
    533 		return (NULL);
    534 
    535 	pai = _PROP_CALLOC(sizeof(*pai), M_TEMP);
    536 	if (pai == NULL)
    537 		return (NULL);
    538 	pai->pai_base.pi_next_object = _prop_array_iterator_next_object;
    539 	pai->pai_base.pi_reset = _prop_array_iterator_reset;
    540 	prop_object_retain(pa);
    541 	pai->pai_base.pi_obj = pa;
    542 	_prop_array_iterator_reset_locked(pai);
    543 
    544 	return pai;
    545 }
    546 
    547 /*
    548  * prop_array_iterator --
    549  *	Return an iterator for the array.  The array is retained by
    550  *	the iterator.
    551  */
    552 _PROP_EXPORT prop_object_iterator_t
    553 prop_array_iterator(prop_array_t pa)
    554 {
    555 	struct _prop_array_iterator *pai;
    556 
    557 	_PROP_RWLOCK_RDLOCK(pa->pa_rwlock);
    558 	pai = _prop_array_iterator_locked(pa);
    559 	_PROP_RWLOCK_UNLOCK(pa->pa_rwlock);
    560 	return &pai->pai_base;
    561 }
    562 
    563 /*
    564  * prop_array_make_immutable --
    565  *	Make the array immutable.
    566  */
    567 _PROP_EXPORT void
    568 prop_array_make_immutable(prop_array_t pa)
    569 {
    570 
    571 	_PROP_RWLOCK_WRLOCK(pa->pa_rwlock);
    572 	if (prop_array_is_immutable(pa) == false)
    573 		pa->pa_flags |= PA_F_IMMUTABLE;
    574 	_PROP_RWLOCK_UNLOCK(pa->pa_rwlock);
    575 }
    576 
    577 /*
    578  * prop_array_mutable --
    579  *	Returns true if the array is mutable.
    580  */
    581 _PROP_EXPORT bool
    582 prop_array_mutable(prop_array_t pa)
    583 {
    584 	bool rv;
    585 
    586 	_PROP_RWLOCK_RDLOCK(pa->pa_rwlock);
    587 	rv = prop_array_is_immutable(pa) == false;
    588 	_PROP_RWLOCK_UNLOCK(pa->pa_rwlock);
    589 
    590 	return (rv);
    591 }
    592 
    593 /*
    594  * prop_array_get --
    595  *	Return the object stored at the specified array index.
    596  */
    597 _PROP_EXPORT prop_object_t
    598 prop_array_get(prop_array_t pa, unsigned int idx)
    599 {
    600 	prop_object_t po = NULL;
    601 
    602 	if (! prop_object_is_array(pa))
    603 		return (NULL);
    604 
    605 	_PROP_RWLOCK_RDLOCK(pa->pa_rwlock);
    606 	if (idx >= pa->pa_count)
    607 		goto out;
    608 	po = pa->pa_array[idx];
    609 	_PROP_ASSERT(po != NULL);
    610  out:
    611 	_PROP_RWLOCK_UNLOCK(pa->pa_rwlock);
    612 	return (po);
    613 }
    614 
    615 static bool
    616 _prop_array_add(prop_array_t pa, prop_object_t po)
    617 {
    618 
    619 	/*
    620 	 * Array must be WRITE-LOCKED.
    621 	 */
    622 
    623 	_PROP_ASSERT(pa->pa_count <= pa->pa_capacity);
    624 
    625 	if (prop_array_is_immutable(pa) ||
    626 	    (pa->pa_count == pa->pa_capacity &&
    627 	    _prop_array_expand(pa, pa->pa_capacity + EXPAND_STEP) == false))
    628 		return (false);
    629 
    630 	prop_object_retain(po);
    631 	pa->pa_array[pa->pa_count++] = po;
    632 	pa->pa_version++;
    633 
    634 	return (true);
    635 }
    636 
    637 /*
    638  * prop_array_set --
    639  *	Store a reference to an object at the specified array index.
    640  *	This method is not allowed to create holes in the array; the
    641  *	caller must either be setting the object just beyond the existing
    642  *	count or replacing an already existing object reference.
    643  */
    644 _PROP_EXPORT bool
    645 prop_array_set(prop_array_t pa, unsigned int idx, prop_object_t po)
    646 {
    647 	prop_object_t opo;
    648 	bool rv = false;
    649 
    650 	if (! prop_object_is_array(pa))
    651 		return (false);
    652 
    653 	_PROP_RWLOCK_WRLOCK(pa->pa_rwlock);
    654 
    655 	if (prop_array_is_immutable(pa))
    656 		goto out;
    657 
    658 	if (idx == pa->pa_count) {
    659 		rv = _prop_array_add(pa, po);
    660 		goto out;
    661 	}
    662 
    663 	_PROP_ASSERT(idx < pa->pa_count);
    664 
    665 	opo = pa->pa_array[idx];
    666 	_PROP_ASSERT(opo != NULL);
    667 
    668 	prop_object_retain(po);
    669 	pa->pa_array[idx] = po;
    670 	pa->pa_version++;
    671 
    672 	prop_object_release(opo);
    673 
    674 	rv = true;
    675 
    676  out:
    677 	_PROP_RWLOCK_UNLOCK(pa->pa_rwlock);
    678 	return (rv);
    679 }
    680 
    681 /*
    682  * prop_array_add --
    683  *	Add a reference to an object to the specified array, appending
    684  *	to the end and growing the array's capacity, if necessary.
    685  */
    686 _PROP_EXPORT bool
    687 prop_array_add(prop_array_t pa, prop_object_t po)
    688 {
    689 	bool rv;
    690 
    691 	if (! prop_object_is_array(pa))
    692 		return (false);
    693 
    694 	_PROP_RWLOCK_WRLOCK(pa->pa_rwlock);
    695 	rv = _prop_array_add(pa, po);
    696 	_PROP_RWLOCK_UNLOCK(pa->pa_rwlock);
    697 
    698 	return (rv);
    699 }
    700 
    701 /*
    702  * prop_array_remove --
    703  *	Remove the reference to an object from an array at the specified
    704  *	index.	The array will be compacted following the removal.
    705  */
    706 _PROP_EXPORT void
    707 prop_array_remove(prop_array_t pa, unsigned int idx)
    708 {
    709 	prop_object_t po;
    710 
    711 	if (! prop_object_is_array(pa))
    712 		return;
    713 
    714 	_PROP_RWLOCK_WRLOCK(pa->pa_rwlock);
    715 
    716 	_PROP_ASSERT(idx < pa->pa_count);
    717 
    718 	/* XXX Should this be a _PROP_ASSERT()? */
    719 	if (prop_array_is_immutable(pa)) {
    720 		_PROP_RWLOCK_UNLOCK(pa->pa_rwlock);
    721 		return;
    722 	}
    723 
    724 	po = pa->pa_array[idx];
    725 	_PROP_ASSERT(po != NULL);
    726 
    727 	for (++idx; idx < pa->pa_count; idx++)
    728 		pa->pa_array[idx - 1] = pa->pa_array[idx];
    729 	pa->pa_count--;
    730 	pa->pa_version++;
    731 
    732 	_PROP_RWLOCK_UNLOCK(pa->pa_rwlock);
    733 
    734 	prop_object_release(po);
    735 }
    736 
    737 /*
    738  * prop_array_equals --
    739  *	Return true if the two arrays are equivalent.  Note we do a
    740  *	by-value comparison of the objects in the array.
    741  */
    742 _PROP_EXPORT bool
    743 prop_array_equals(prop_array_t array1, prop_array_t array2)
    744 {
    745 	if (!prop_object_is_array(array1) || !prop_object_is_array(array2))
    746 		return (false);
    747 
    748 	return (prop_object_equals(array1, array2));
    749 }
    750 
    751 /*
    752  * prop_array_externalize --
    753  *	Externalize an array in XML format.
    754  */
    755 _PROP_EXPORT char *
    756 prop_array_externalize(prop_array_t pa)
    757 {
    758 	return _prop_object_externalize(&pa->pa_obj, PROP_FORMAT_XML);
    759 }
    760 
    761 /*
    762  * _prop_array_internalize --
    763  *	Parse an <array>...</array> and return the object created from the
    764  *	external representation.
    765  */
    766 static bool _prop_array_internalize_body(prop_stack_t, prop_object_t *,
    767     struct _prop_object_internalize_context *);
    768 
    769 bool
    770 _prop_array_internalize(prop_stack_t stack, prop_object_t *obj,
    771     struct _prop_object_internalize_context *ctx)
    772 {
    773 	/* We don't currently understand any attributes. */
    774 	if (ctx->poic_tagattr != NULL)
    775 		return (true);
    776 
    777 	*obj = prop_array_create();
    778 	/*
    779 	 * We are done if the create failed or no child elements exist.
    780 	 */
    781 	if (*obj == NULL || ctx->poic_is_empty_element)
    782 		return (true);
    783 
    784 	/*
    785 	 * Opening tag is found, now continue to the first element.
    786 	 */
    787 	return (_prop_array_internalize_body(stack, obj, ctx));
    788 }
    789 
    790 static bool
    791 _prop_array_internalize_continue(prop_stack_t stack,
    792     prop_object_t *obj,
    793     struct _prop_object_internalize_context *ctx,
    794     void *data, prop_object_t child)
    795 {
    796 	prop_array_t array;
    797 
    798 	_PROP_ASSERT(data == NULL);
    799 
    800 	if (child == NULL)
    801 		goto bad; /* Element could not be parsed. */
    802 
    803 	array = *obj;
    804 
    805 	if (prop_array_add(array, child) == false) {
    806 		prop_object_release(child);
    807 		goto bad;
    808 	}
    809 	prop_object_release(child);
    810 
    811 	/*
    812 	 * Current element is processed and added, look for next.
    813 	 * For JSON, we'll skip the comma separator, if present.
    814 	 *
    815 	 * By doing this here, we correctly error out if a separator
    816 	 * is found other than after an element, but this does mean
    817 	 * that we do allow a trailing comma after the final element
    818 	 * which isn't allowed in the JSON spec, but seems pretty
    819 	 * harmless (and there are other JSON parsers that also allow
    820 	 * it).
    821 	 *
    822 	 * Conversely, we don't want to *require* the separator if the
    823 	 * spec doesn't require it, and we don't know what's next in
    824 	 * the buffer, so we basically treat the separator as completely
    825 	 * optional.  Since there does not appear to be any ambiguity,
    826 	 * this also seems pretty harmless.
    827 	 *
    828 	 * (FWIW, RFC 8259 section 9 seems to specifically allow this.)
    829 	 */
    830 	if (ctx->poic_format == PROP_FORMAT_JSON) {
    831 		ctx->poic_cp = _prop_intern_skip_whitespace(ctx->poic_cp);
    832 		if (*ctx->poic_cp == ',') {
    833 			ctx->poic_cp++;
    834 		}
    835 	}
    836 	return (_prop_array_internalize_body(stack, obj, ctx));
    837 
    838  bad:
    839 	prop_object_release(*obj);
    840 	*obj = NULL;
    841 	return (true);
    842 }
    843 
    844 static bool
    845 _prop_array_internalize_body(prop_stack_t stack, prop_object_t *obj,
    846     struct _prop_object_internalize_context *ctx)
    847 {
    848 	prop_array_t array = *obj;
    849 
    850 	_PROP_ASSERT(array != NULL);
    851 
    852 	if (ctx->poic_format == PROP_FORMAT_JSON) {
    853 		ctx->poic_cp = _prop_intern_skip_whitespace(ctx->poic_cp);
    854 
    855 		/* Check to see if this is the end of the array. */
    856 		if (*ctx->poic_cp == ']') {
    857 			/* It is, so don't iterate any further. */
    858 			ctx->poic_cp++;
    859 			return true;
    860 		}
    861 	} else {
    862 		/* Fetch the next tag. */
    863 		if (_prop_xml_intern_find_tag(ctx, NULL,
    864 					_PROP_TAG_TYPE_EITHER) == false)
    865 			goto bad;
    866 
    867 		/* Check to see if this is the end of the array. */
    868 		if (_PROP_TAG_MATCH(ctx, "array") &&
    869 		    ctx->poic_tag_type == _PROP_TAG_TYPE_END) {
    870 			/* It is, so don't iterate any further. */
    871 			return (true);
    872 		}
    873 	}
    874 
    875 	if (_prop_stack_push(stack, array,
    876 			     _prop_array_internalize_continue, NULL, NULL))
    877 		return (false);
    878 
    879  bad:
    880 	prop_object_release(array);
    881 	*obj = NULL;
    882 	return (true);
    883 }
    884 
    885 /*
    886  * prop_array_internalize --
    887  *	Create an array by parsing the external representation.
    888  */
    889 _PROP_EXPORT prop_array_t
    890 prop_array_internalize(const char *data)
    891 {
    892 	return _prop_object_internalize(data, &_prop_array_type_tags);
    893 }
    894 
    895 #if !defined(_KERNEL) && !defined(_STANDALONE)
    896 /*
    897  * prop_array_externalize_to_file --
    898  *	Externalize an array to the specified file.
    899  */
    900 _PROP_EXPORT bool
    901 prop_array_externalize_to_file(prop_array_t array, const char *fname)
    902 {
    903 	return _prop_object_externalize_to_file(&array->pa_obj, fname,
    904 	    PROP_FORMAT_XML);
    905 }
    906 
    907 /*
    908  * prop_array_internalize_from_file --
    909  *	Internalize an array from a file.
    910  */
    911 _PROP_EXPORT prop_array_t
    912 prop_array_internalize_from_file(const char *fname)
    913 {
    914 	return _prop_object_internalize_from_file(fname,
    915 	    &_prop_array_type_tags);
    916 }
    917 #endif /* _KERNEL && !_STANDALONE */
    918