1 /*
2  * Copyright (C) 2010 Red Hat, Inc.
3  *
4  * Author: Angus Salkeld <asalkeld@redhat.com>
5  *
6  * This file is part of libqb.
7  *
8  * libqb is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU Lesser General Public License as published by
10  * the Free Software Foundation, either version 2.1 of the License, or
11  * (at your option) any later version.
12  *
13  * libqb is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with libqb.  If not, see <http://www.gnu.org/licenses/>.
20  */
21 #include "os_base.h"
22 
23 #include <qb/qbarray.h>
24 #include <qb/qbutil.h>
25 
26 /* The highest ARRAY_INDEX_BITS_BINS bits of the array index are the
27  * number of the bin containing the indicated element, while the
28  * remaining ARRAY_INDEX_BITS_ELEMS_PER_BIN bits give its index inside
29  * the bin.  The full array index is ARRAY_INDEX_BITS_MAX bits long.
30  */
31 
32 #define ARRAY_INDEX_BITS_ELEMS_PER_BIN 4
33 #define ARRAY_INDEX_BITS_BINS \
34 	(QB_ARRAY_MAX_INDEX_BITS - ARRAY_INDEX_BITS_ELEMS_PER_BIN)
35 
36 #define MAX_ELEMENTS_PER_BIN (1 << ARRAY_INDEX_BITS_ELEMS_PER_BIN)
37 #define MAX_BINS (1 << ARRAY_INDEX_BITS_BINS)
38 
39 #define BIN_NUM_GET(_idx_) (_idx_ >> ARRAY_INDEX_BITS_ELEMS_PER_BIN)
40 #define ELEM_NUM_GET(_idx_) (_idx_ & (MAX_ELEMENTS_PER_BIN - 1))
41 
42 struct qb_array {
43 	void **bin;  /* array of void* pointers to element_size big elements */
44 	size_t max_elements;
45 	size_t element_size;
46 	size_t num_bins;
47 	size_t autogrow_elements;
48 	qb_thread_lock_t *grow_lock;
49 	qb_array_new_bin_cb_fn new_bin_cb;
50 };
51 
52 qb_array_t *
qb_array_create(size_t max_elements,size_t element_size)53 qb_array_create(size_t max_elements, size_t element_size)
54 {
55 	return qb_array_create_2(max_elements, element_size, 0);
56 }
57 
58 static int32_t
_grow_bin_array(struct qb_array * a,size_t new_bin_size)59 _grow_bin_array(struct qb_array * a, size_t new_bin_size)
60 {
61 	size_t b;
62 
63 	a->bin = realloc(a->bin, sizeof(void*) * new_bin_size);
64 	if (a->bin == NULL) {
65 		return -ENOMEM;
66 	}
67 	for (b = a->num_bins; b < new_bin_size; b++) {
68 		a->bin[b] = NULL;
69 	}
70 	a->num_bins = new_bin_size;
71 
72 	return 0;
73 }
74 
75 qb_array_t *
qb_array_create_2(size_t max_elements,size_t element_size,size_t autogrow_elements)76 qb_array_create_2(size_t max_elements, size_t element_size,
77 		  size_t autogrow_elements)
78 {
79 	struct qb_array *a = NULL;
80 	size_t b;
81 
82 	if (max_elements > QB_ARRAY_MAX_ELEMENTS) {
83 		errno = EINVAL;
84 		return NULL;
85 	}
86 	if (element_size < 1) {
87 		errno = EINVAL;
88 		return NULL;
89 	}
90 	if (autogrow_elements > MAX_ELEMENTS_PER_BIN) {
91 		errno = EINVAL;
92 		return NULL;
93 	}
94 	a = calloc(1, sizeof(struct qb_array));
95 	if (a == NULL) {
96 		return NULL;
97 	}
98 	a->element_size = element_size;
99 	a->max_elements = max_elements;
100 	b = QB_MIN((max_elements / MAX_ELEMENTS_PER_BIN) + 1, MAX_BINS);
101 	a->autogrow_elements = autogrow_elements;
102 	a->bin = NULL;
103 	if (_grow_bin_array(a, b) < 0) {
104 		free(a);
105 		return NULL;
106 	}
107 	a->grow_lock = qb_thread_lock_create(QB_THREAD_LOCK_SHORT);
108 	return a;
109 }
110 
111 int32_t
qb_array_index(struct qb_array * a,int32_t idx,void ** element_out)112 qb_array_index(struct qb_array * a, int32_t idx, void **element_out)
113 {
114 	size_t b;
115 	int32_t elem;
116 	char *bin;
117 	int32_t rc = 0;
118 
119 	if (a == NULL || element_out == NULL) {
120 		return -EINVAL;
121 	}
122 	if (idx < 0) {
123 		return -ERANGE;
124 	}
125 	(void)qb_thread_lock(a->grow_lock);
126 	if ((uint32_t) idx >= a->max_elements) {
127 		if (a->autogrow_elements == 0) {
128 			(void)qb_thread_unlock(a->grow_lock);
129 			return -ERANGE;
130 		} else {
131 			/* qb_array_grow gets the lock */
132 			(void)qb_thread_unlock(a->grow_lock);
133 			rc = qb_array_grow(a, idx + 1);
134 			if (rc != 0) {
135 				return rc;
136 			}
137 			(void)qb_thread_lock(a->grow_lock);
138 		}
139 	}
140 	b = BIN_NUM_GET((uint32_t) idx);
141 	assert(b < MAX_BINS);
142 
143 	if (b >= a->num_bins || a->bin[b] == NULL) {
144 		int32_t bin_alloced = QB_FALSE;
145 
146 		if (b >= a->num_bins) {
147 			rc = _grow_bin_array(a, b + 1);
148 			if (rc < 0) {
149 				goto unlock_error;
150 			}
151 		}
152 		if (a->bin[b] == NULL) {
153 			a->bin[b] = calloc(MAX_ELEMENTS_PER_BIN, a->element_size);
154 			if (a->bin[b] == NULL) {
155 				rc = -errno;
156 				goto unlock_error;
157 			}
158 			bin_alloced = QB_TRUE;
159 		}
160 		/* new_bin_cb() needs to be called unlocked so can't extend the lock after the if block */
161 		(void)qb_thread_unlock(a->grow_lock);
162 		if (bin_alloced && a->new_bin_cb) {
163 			a->new_bin_cb(a, b);
164 		}
165 	} else {
166 		(void)qb_thread_unlock(a->grow_lock);
167 	}
168 
169 	elem = ELEM_NUM_GET(idx);
170 	assert(elem < MAX_ELEMENTS_PER_BIN);
171 
172 	bin = a->bin[b];
173 	*element_out = (bin + (a->element_size * elem));
174 
175 	return 0;
176 
177 unlock_error:
178 
179 	(void)qb_thread_unlock(a->grow_lock);
180 	return rc;
181 }
182 
183 int32_t
qb_array_new_bin_cb_set(struct qb_array * a,qb_array_new_bin_cb_fn fn)184 qb_array_new_bin_cb_set(struct qb_array * a, qb_array_new_bin_cb_fn fn)
185 {
186 	if (a == NULL) {
187 		return -EINVAL;
188 	}
189 	a->new_bin_cb = fn;
190 	return 0;
191 }
192 
193 size_t
qb_array_num_bins_get(struct qb_array * a)194 qb_array_num_bins_get(struct qb_array * a)
195 {
196 	size_t bins;
197 
198 	if (a == NULL) {
199 		return -EINVAL;
200 	}
201 	(void)qb_thread_lock(a->grow_lock);
202 	bins = a->num_bins;
203 	(void)qb_thread_unlock(a->grow_lock);
204 	return bins;
205 }
206 
207 size_t
qb_array_elems_per_bin_get(struct qb_array * a)208 qb_array_elems_per_bin_get(struct qb_array * a)
209 {
210 	if (a == NULL) {
211 		return -EINVAL;
212 	}
213 	return MAX_ELEMENTS_PER_BIN;
214 }
215 
216 int32_t
qb_array_grow(struct qb_array * a,size_t max_elements)217 qb_array_grow(struct qb_array * a, size_t max_elements)
218 {
219 	size_t b;
220 	int32_t rc = 0;
221 
222 	if (a == NULL || max_elements > QB_ARRAY_MAX_ELEMENTS) {
223 		return -EINVAL;
224 	}
225 
226 	(void)qb_thread_lock(a->grow_lock);
227 	if (max_elements <= a->max_elements) {
228 		(void)qb_thread_unlock(a->grow_lock);
229 		return 0;
230 	}
231 	a->max_elements = max_elements;
232 	b = QB_MIN((max_elements / MAX_ELEMENTS_PER_BIN) + 1, MAX_BINS);
233 	if (b > a->num_bins) {
234 		if (b >= a->num_bins) {
235 			rc = _grow_bin_array(a, b + 1);
236 		}
237 	}
238 	(void)qb_thread_unlock(a->grow_lock);
239 	return rc;
240 }
241 
242 void
qb_array_free(struct qb_array * a)243 qb_array_free(struct qb_array *a)
244 {
245 	size_t i;
246 
247 	/* In theory we should lock before accessing a->num_bins
248 	 * but as we're freeing the whole array here it seems
249 	 * a bit pointless as any other threads would segv when we
250 	 * unlock anyway
251 	 */
252 	for (i = 0; i < a->num_bins; i++) {
253 		free(a->bin[i]);
254 	}
255 	free(a->bin);
256 	(void)qb_thread_lock_destroy(a->grow_lock);
257 	free(a);
258 }
259