xref: /illumos-gate/usr/src/cmd/sgs/common/alist.c (revision 8a2b682e57a046b828f37bcde1776f131ef4629f)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 #include <sgs.h>
27 #include <string.h>
28 #include <stdio.h>
29 #include <sys/debug.h>
30 
31 /*
32  * Alist manipulation.  An Alist is a list of elements formed into an array.
33  * Traversal of the list is an array scan, which because of the locality of
34  * each reference is probably more efficient than a link-list traversal.
35  *
36  * See alist.h for more background information about array lists.
37  */
38 
39 /*
40  * Insert a value into an array at a specified index:
41  *
42  *	alist_insert(): Insert an item into an Alist at the specified index
43  *	alist_insert_by_offset(): Insert an item into an Alist at the
44  *		specified offset relative to the list address.
45  *	aplist_insert() Insert a pointer into an APlist at the specified index
46  *
47  * entry:
48  *	Note: All the arguments for all three routines are listed here.
49  *	The routine to which a given argument applies is given with
50  *	each description.
51  *
52  *	llp [all] - Address of a pointer to an Alist/APlist. The pointer should
53  *		be initialized to NULL before its first use.
54  *	datap [alist_insert / aplist_insert] - Pointer to item data, or
55  *		NULL. If non-null the data referenced is copied into the
56  *		Alist item. Otherwise, the list item is zeroed, and
57  *		further initialization is left to the caller.
58  *	ptr [aplist_insert] - Pointer to be inserted.
59  *	size [alist_insert / alist_insert_by_offset] - Size of an item
60  *		in the array list, in bytes. As with any array, A given
61  *		Alist can support any item size, but every item in that
62  *		list must have the same size.
63  *	init_arritems [all] - Initial allocation size: On the first insertion
64  *		into the array list, room for init_arritems items is allocated.
65  *	idx [alist_insert / aplist_insert] - Index at which to insert the
66  *		new item. This index must lie within the existing list,
67  *		or be the next index following.
68  *	off [alist_insert_by_offset] - Offset at which  to insert the new
69  *		item, based from the start of the Alist. The offset of
70  *		the first item is ALIST_OFF_DATA.
71  *
72  * exit:
73  *	The item is inserted at the specified position. This operation
74  *	can cause memory for the list to be allocated, or reallocated,
75  *	either of which will cause the value of the list pointer
76  *	to change.
77  *
78  *	These routines can only fail if unable to allocate memory,
79  *	in which case NULL is returned.
80  *
81  *	If a pointer list (aplist_insert), then the pointer
82  *	is stored in the requested index. On success, the address
83  *	of the pointer within the list is returned.
84  *
85  *	If the list contains arbitrary data (not aplist_insert): If datap
86  *	is non-NULL, the data it references is copied into the item at
87  *	the index. If datap is NULL, the specified item is zeroed.
88  *	On success, a pointer to the inserted item is returned.
89  *
90  *	The  caller must not retain the returned pointer from this
91  *	routine across calls to the list module. It is only safe to use
92  *	it until the next call to this module for the given list.
93  *
94  */
95 void *
96 alist_insert(Alist **lpp, const void *datap, size_t size,
97     Aliste init_arritems, Aliste idx)
98 {
99 	Alist	*lp = *lpp;
100 	char	*addr;
101 
102 	/* The size and initial array count need to be non-zero */
103 	ASSERT(init_arritems != 0);
104 	ASSERT(size != 0);
105 
106 	if (lp == NULL) {
107 		Aliste bsize;
108 
109 		/*
110 		 * First time here, allocate a new Alist.  Note that the
111 		 * Alist al_desc[] entry is defined for 1 element,
112 		 * but we actually allocate the number we need.
113 		 */
114 		bsize = size * init_arritems;
115 		bsize = S_ROUND(bsize, sizeof (void *));
116 		bsize = ALIST_OFF_DATA + bsize;
117 		if ((lp = malloc((size_t)bsize)) == NULL)
118 			return (NULL);
119 		lp->al_arritems = init_arritems;
120 		lp->al_nitems = 0;
121 		lp->al_next = ALIST_OFF_DATA;
122 		lp->al_size = size;
123 		*lpp = lp;
124 	} else {
125 		/* We must get the same value for size every time */
126 		ASSERT(size == lp->al_size);
127 
128 		if (lp->al_nitems >= lp->al_arritems) {
129 			/*
130 			 * The list is full: Increase the memory allocation
131 			 * by doubling it.
132 			 */
133 			Aliste	bsize;
134 
135 			bsize = lp->al_size * lp->al_arritems * 2;
136 			bsize = S_ROUND(bsize, sizeof (void *));
137 			bsize = ALIST_OFF_DATA + bsize;
138 			if ((lp = realloc((void *)lp, (size_t)bsize)) == 0)
139 				return (NULL);
140 			lp->al_arritems *= 2;
141 			*lpp = lp;
142 		}
143 	}
144 
145 	/*
146 	 * The caller is not supposed to use an index that
147 	 * would introduce a "hole" in the array.
148 	 */
149 	ASSERT(idx <= lp->al_nitems);
150 
151 	addr = (idx * lp->al_size) + (char *)lp->al_data;
152 
153 	/*
154 	 * An appended item is added to the next available array element.
155 	 * An insert at any other spot requires that the data items that
156 	 * exist at the point of insertion be shifted down to open a slot.
157 	 */
158 	if (idx < lp->al_nitems)
159 		(void) memmove(addr + lp->al_size, addr,
160 		    (lp->al_nitems - idx) * lp->al_size);
161 
162 	lp->al_nitems++;
163 	lp->al_next += lp->al_size;
164 	if (datap != NULL)
165 		(void) memcpy(addr, datap, lp->al_size);
166 	else
167 		(void) memset(addr, 0, lp->al_size);
168 	return (addr);
169 }
170 
171 void *
172 alist_insert_by_offset(Alist **lpp, const void *datap, size_t size,
173     Aliste init_arritems, Aliste off)
174 {
175 	Aliste idx;
176 
177 	if (*lpp == NULL) {
178 		ASSERT(off == ALIST_OFF_DATA);
179 		idx = 0;
180 	} else {
181 		idx = (off - ALIST_OFF_DATA) / (*lpp)->al_size;
182 	}
183 
184 	return (alist_insert(lpp, datap, size, init_arritems, idx));
185 }
186 
187 void *
188 aplist_insert(APlist **lpp, const void *ptr, Aliste init_arritems, Aliste idx)
189 {
190 	APlist	*lp = *lpp;
191 
192 	/* The initial array count needs to be non-zero */
193 	ASSERT(init_arritems != 0);
194 
195 	if (lp == NULL) {
196 		Aliste bsize;
197 
198 		/*
199 		 * First time here, allocate a new APlist.  Note that the
200 		 * APlist apl_desc[] entry is defined for 1 element,
201 		 * but we actually allocate the number we need.
202 		 */
203 		bsize = APLIST_OFF_DATA + (sizeof (void *) * init_arritems);
204 		if ((lp = malloc((size_t)bsize)) == NULL)
205 			return (NULL);
206 		lp->apl_arritems = init_arritems;
207 		lp->apl_nitems = 0;
208 		*lpp = lp;
209 	} else if (lp->apl_nitems >= lp->apl_arritems) {
210 		/*
211 		 * The list is full: Increase the memory allocation
212 		 * by doubling it.
213 		 */
214 		Aliste	bsize;
215 
216 		bsize = APLIST_OFF_DATA +
217 		    (2 * sizeof (void *) * lp->apl_arritems);
218 		if ((lp = realloc((void *)lp, (size_t)bsize)) == 0)
219 			return (NULL);
220 		lp->apl_arritems *= 2;
221 		*lpp = lp;
222 	}
223 
224 	/*
225 	 * The caller is not supposed to use an index that
226 	 * would introduce a "hole" in the array.
227 	 */
228 	ASSERT(idx <= lp->apl_nitems);
229 
230 	/*
231 	 * An appended item is added to the next available array element.
232 	 * An insert at any other spot requires that the data items that
233 	 * exist at the point of insertion be shifted down to open a slot.
234 	 */
235 	if (idx < lp->apl_nitems)
236 		(void) memmove((char *)&lp->apl_data[idx + 1],
237 		    (char *)&lp->apl_data[idx],
238 		    (lp->apl_nitems - idx) * sizeof (void *));
239 
240 	lp->apl_nitems++;
241 	lp->apl_data[idx] = (void *)ptr;
242 	return (&lp->apl_data[idx]);
243 }
244 
245 /*
246  * Append a value to a list. These are convenience wrappers on top
247  * of the insert operation. See the description of those routine above
248  * for details.
249  */
250 void *
251 alist_append(Alist **lpp, const void *datap, size_t size,
252     Aliste init_arritems)
253 {
254 	Aliste ndx = ((*lpp) == NULL) ? 0 : (*lpp)->al_nitems;
255 
256 	return (alist_insert(lpp, datap, size, init_arritems, ndx));
257 }
258 
259 void *
260 aplist_append(APlist **lpp, const void *ptr, Aliste init_arritems)
261 {
262 	Aliste ndx = ((*lpp) == NULL) ? 0 : (*lpp)->apl_nitems;
263 
264 	return (aplist_insert(lpp, ptr, init_arritems, ndx));
265 }
266 
267 /*
268  * Delete the item at a specified index/offset, and decrement the variable
269  * containing the index:
270  *
271  *	alist_delete - Delete an item from an Alist at the specified
272  *		index.
273  *	alist_delete_by_offset - Delete an item from an Alist at the
274  *		specified offset from the list pointer.
275  *	aplist_delete - Delete a pointer from an APlist at the specified
276  *		index.
277  *
278  * entry:
279  *	alp - List to delete item from
280  *	idxp - Address of variable containing the index of the
281  *		item to delete.
282  *	offp - Address of variable containing the offset of the
283  *		item to delete.
284  *
285  * exit:
286  *	The item at the position given by (*idxp) or (*offp), depending
287  *	on the routine, is removed from the list. Then, the position
288  *	variable (*idxp or *offp) is decremented by one item. This is done
289  *	to facilitate use of this routine within a TRAVERSE loop.
290  *
291  * note:
292  *	Deleting the last element in an array list is cheap, but
293  *	deleting any other item causes a memory copy to occur to
294  *	move the following items up. If you intend to traverse the
295  *	entire list, deleting every item as you go, it will be cheaper
296  *	to omit the delete within the traverse, and then call
297  *	the reset function reset() afterwards.
298  */
299 void
300 alist_delete(Alist *lp, Aliste *idxp)
301 {
302 	Aliste	idx = *idxp;
303 
304 
305 	/* The list must be allocated and the index in range */
306 	ASSERT(lp != NULL);
307 	ASSERT(idx < lp->al_nitems);
308 
309 	/*
310 	 * If the element to be removed is not the last entry of the array,
311 	 * slide the following elements over the present element.
312 	 */
313 	if (idx < --lp->al_nitems) {
314 		char *addr = (idx * lp->al_size) + (char *)lp->al_data;
315 
316 		(void) memmove(addr, addr + lp->al_size,
317 		    (lp->al_nitems - idx) * lp->al_size);
318 	}
319 	lp->al_next -= lp->al_size;
320 
321 	/* Decrement the callers index variable */
322 	(*idxp)--;
323 }
324 
325 void
326 alist_delete_by_offset(Alist *lp, Aliste *offp)
327 {
328 	Aliste idx;
329 
330 	ASSERT(lp != NULL);
331 	idx = (*offp - ALIST_OFF_DATA) / lp->al_size;
332 
333 	alist_delete(lp, &idx);
334 	*offp -= lp->al_size;
335 }
336 
337 void
338 aplist_delete(APlist *lp, Aliste *idxp)
339 {
340 	Aliste	idx = *idxp;
341 
342 
343 	/* The list must be allocated and the index in range */
344 	ASSERT(lp != NULL);
345 	ASSERT(idx < lp->apl_nitems);
346 
347 	/*
348 	 * If the element to be removed is not the last entry of the array,
349 	 * slide the following elements over the present element.
350 	 */
351 	if (idx < --lp->apl_nitems)
352 		(void) memmove(&lp->apl_data[idx], &lp->apl_data[idx + 1],
353 		    (lp->apl_nitems - idx) * sizeof (void *));
354 
355 	/* Decrement the callers index variable */
356 	(*idxp)--;
357 }
358 
359 /*
360  * Delete the pointer with a specified value from the APlist.
361  *
362  * entry:
363  *	lp - Initialized APlist to delete item from
364  *	ptr - Pointer to be deleted.
365  *
366  * exit:
367  *	The list is searched for an item containing the given pointer,
368  *	and if a match is found, that item is delted and True (1) returned.
369  *	If no match is found, then False (0) is returned.
370  *
371  * note:
372  *	See note for delete operation, above.
373  */
374 int
375 aplist_delete_value(APlist *lp, const void *ptr)
376 {
377 	size_t	idx;
378 
379 	/*
380 	 * If the pointer is found in the list, use aplist_delete to
381 	 * remove it, and we're done.
382 	 */
383 	for (idx = 0; idx < lp->apl_nitems; idx++)
384 		if (ptr == lp->apl_data[idx]) {
385 			aplist_delete(lp, &idx);
386 			return (1);
387 		}
388 
389 	/* If we get here, the item was not in the list */
390 	return (0);
391 }
392 
393 /*
394  * Search the APlist for an element with a given value, and
395  * if not found, optionally append the element to the end of the list.
396  *
397  * entry:
398  *	lpp, ptr - As per aplist_insert().
399  *	init_arritems - As per aplist_insert() if a non-zero value.
400  *		A value of zero is special, and is taken to indicate
401  *		that no insert operation should be performed if
402  *		the item is not found in the list.
403  *
404  * exit
405  *	The given item is compared to every item in the given APlist.
406  *	If it is found, ALE_EXISTS is returned.
407  *
408  *	If it is not found: If init_arr_items is False (0), then
409  *	ALE_NOTFOUND is returned. If init_arr_items is True, then
410  *	the item is appended to the list, and ALE_CREATE returned on success.
411  *
412  *	On failure, which can only occur due to memory allocation failure,
413  *	ALE_ALLOCFAIL is returned.
414  *
415  * note:
416  *	The test operation used by this routine is a linear
417  *	O(N) operation, and is not efficient for more than a
418  *	few items.
419  */
420 aplist_test_t
421 aplist_test(APlist **lpp, const void *ptr, Aliste init_arritems)
422 {
423 	APlist	*lp = *lpp;
424 	size_t	idx;
425 
426 	/* Is the pointer already in the list? */
427 	if (lp != NULL)
428 		for (idx = 0; idx < lp->apl_nitems; idx++)
429 			if (ptr == lp->apl_data[idx])
430 				return (ALE_EXISTS);
431 
432 	/* Is this a no-insert case? If so, report that the item is not found */
433 	if (init_arritems == 0)
434 		return (ALE_NOTFND);
435 
436 	/* Add it to the end of the list */
437 	if (aplist_append(lpp, ptr, init_arritems) == NULL)
438 		return (ALE_ALLOCFAIL);
439 	return (ALE_CREATE);
440 }
441 
442 /*
443  * Reset the given list to its empty state. Any memory allocated by the
444  * list is preserved, ready for reuse, but the list is set to its
445  * empty state, equivalent to having called the delete operation for
446  * every item.
447  *
448  * Note that no cleanup of the discarded items is done. The caller must
449  * take care of any necessary cleanup before calling aplist_reset().
450  */
451 void
452 alist_reset(Alist *lp)
453 {
454 	if (lp != NULL) {
455 		lp->al_nitems = 0;
456 		lp->al_next = ALIST_OFF_DATA;
457 	}
458 }
459 
460 void
461 aplist_reset(APlist *lp)
462 {
463 	if (lp != NULL)
464 		lp->apl_nitems = 0;
465 }
466