]> git.proxmox.com Git - mirror_frr.git/blame - lib/id_alloc.c
zebra, lib: fix the ZEBRA_INTERFACE_VRF_UPDATE zapi message
[mirror_frr.git] / lib / id_alloc.c
CommitLineData
a94eca09
MS
1/*
2 * FRR ID Number Allocator
3 * Copyright (C) 2018 Amazon.com, Inc. or its affiliates
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the Free
7 * Software Foundation; either version 2 of the License, or (at your option)
8 * any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 * more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; see the file COPYING; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20#include "id_alloc.h"
21
22#include "log.h"
23#include "lib_errors.h"
24#include "memory.h"
25
26#include <inttypes.h>
27
28DEFINE_MTYPE_STATIC(LIB, IDALLOC_ALLOCATOR, "ID Number Allocator")
29DEFINE_MTYPE_STATIC(LIB, IDALLOC_ALLOCATOR_NAME, "ID Number Allocator Name")
30DEFINE_MTYPE_STATIC(LIB, IDALLOC_DIRECTORY, "ID Number Allocator Directory")
31DEFINE_MTYPE_STATIC(LIB, IDALLOC_SUBDIRECTORY,
32 "ID Number Allocator Subdirectory")
33DEFINE_MTYPE_STATIC(LIB, IDALLOC_PAGE, "ID Number Allocator Page")
34DEFINE_MTYPE_STATIC(LIB, IDALLOC_POOL, "ID Number temporary holding pool entry")
35
36#if UINT_MAX >= UINT32_MAX
37#define FFS32(x) ffs(x)
38#else
39/* ints less than 32 bits? Yikes. */
40#define FFS32(x) ffsl(x)
41#endif
42
43#define DIR_MASK ((1<<IDALLOC_DIR_BITS)-1)
44#define SUBDIR_MASK ((1<<IDALLOC_SUBDIR_BITS)-1)
4a600b08 45#define FRR_ID_PAGE_MASK ((1<<IDALLOC_PAGE_BITS)-1)
a94eca09
MS
46#define WORD_MASK ((1<<IDALLOC_WORD_BITS)-1)
47#define OFFSET_MASK ((1<<IDALLOC_OFFSET_BITS)-1)
48
49#define DIR_SHIFT (IDALLOC_OFFSET_BITS + IDALLOC_WORD_BITS + \
50 IDALLOC_PAGE_BITS + IDALLOC_SUBDIR_BITS)
51#define SUBDIR_SHIFT (IDALLOC_OFFSET_BITS + IDALLOC_WORD_BITS + \
52 IDALLOC_PAGE_BITS)
4a600b08 53#define FRR_ID_PAGE_SHIFT (IDALLOC_OFFSET_BITS + IDALLOC_WORD_BITS)
a94eca09
MS
54#define WORD_SHIFT (IDALLOC_OFFSET_BITS)
55#define OFFSET_SHIFT (0)
56
57#define ID_DIR(id) ((id >> DIR_SHIFT) & DIR_MASK)
58#define ID_SUBDIR(id) ((id >> SUBDIR_SHIFT) & SUBDIR_MASK)
4a600b08 59#define ID_PAGE(id) ((id >> FRR_ID_PAGE_SHIFT) & FRR_ID_PAGE_MASK)
a94eca09
MS
60#define ID_WORD(id) ((id >> WORD_SHIFT) & WORD_MASK)
61#define ID_OFFSET(id) ((id >> OFFSET_SHIFT) & OFFSET_MASK)
62
63/*
64 * Find the page that an ID number belongs to in an allocator.
65 * Optionally create the page if it doesn't exist.
66 */
67static struct id_alloc_page *find_or_create_page(struct id_alloc *alloc,
68 uint32_t id, int create)
69{
70 struct id_alloc_dir *dir = NULL;
71 struct id_alloc_subdir *subdir = NULL;
72 struct id_alloc_page *page = NULL;
73
74 dir = alloc->sublevels[ID_DIR(id)];
75 if (dir == NULL) {
76 if (create) {
77 dir = XCALLOC(MTYPE_IDALLOC_DIRECTORY, sizeof(*dir));
78 alloc->sublevels[ID_DIR(id)] = dir;
79 } else {
80 return NULL;
81 }
82 }
83
84 subdir = dir->sublevels[ID_SUBDIR(id)];
85 if (subdir == NULL) {
86 if (create) {
87 subdir = XCALLOC(MTYPE_IDALLOC_SUBDIRECTORY,
88 sizeof(*subdir));
89 dir->sublevels[ID_SUBDIR(id)] = subdir;
90 } else {
91 return NULL;
92 }
93 }
94
95 page = subdir->sublevels[ID_PAGE(id)];
96 if (page == NULL && create) {
97 page = XCALLOC(MTYPE_IDALLOC_PAGE, sizeof(*page));
98 page->base_value = id;
99 subdir->sublevels[ID_PAGE(id)] = page;
100
4a600b08 101 alloc->capacity += 1 << FRR_ID_PAGE_SHIFT;
a94eca09
MS
102 page->next_has_free = alloc->has_free;
103 alloc->has_free = page;
104 } else if (page != NULL && create) {
105 flog_err(
106 EC_LIB_ID_CONSISTENCY,
107 "ID Allocator %s attempt to re-create page at %" PRIu32,
108 alloc->name, id);
109 }
110
111 return page;
112}
113
114/*
115 * Return an ID number back to the allocator.
116 * While this ID can be re-assigned through idalloc_allocate, the underlying
117 * memory will not be freed. If this is the first free ID in the page, the page
118 * will be added to the allocator's list of pages with free IDs.
119 */
120void idalloc_free(struct id_alloc *alloc, uint32_t id)
121{
122 struct id_alloc_page *page = NULL;
123
124 int word, offset;
125 uint32_t old_word, old_word_mask;
126
127 page = find_or_create_page(alloc, id, 0);
128 if (!page) {
129 flog_err(EC_LIB_ID_CONSISTENCY,
130 "ID Allocator %s cannot free #%" PRIu32
131 ". ID Block does not exist.",
132 alloc->name, id);
133 return;
134 }
135
136 word = ID_WORD(id);
137 offset = ID_OFFSET(id);
138
139 if ((page->allocated_mask[word] & (1 << offset)) == 0) {
140 flog_err(EC_LIB_ID_CONSISTENCY,
141 "ID Allocator %s cannot free #%" PRIu32
142 ". ID was not allocated at the time of free.",
143 alloc->name, id);
144 return;
145 }
146
147 old_word = page->allocated_mask[word];
148 page->allocated_mask[word] &= ~(((uint32_t)1) << offset);
149 alloc->allocated -= 1;
150
151 if (old_word == UINT32_MAX) {
152 /* first bit in this block of 32 to be freed.*/
153
154 old_word_mask = page->full_word_mask;
155 page->full_word_mask &= ~(((uint32_t)1) << word);
156
157 if (old_word_mask == UINT32_MAX) {
158 /* first bit in page freed, add this to the allocator's
159 * list of pages with free space
160 */
161 page->next_has_free = alloc->has_free;
162 alloc->has_free = page;
163 }
164 }
165}
166
167/*
168 * Add a allocation page to the end of the allocator's current range.
169 * Returns null if the allocator has had all possible pages allocated already.
170 */
171static struct id_alloc_page *create_next_page(struct id_alloc *alloc)
172{
173 if (alloc->capacity == 0 && alloc->sublevels[0])
174 return NULL; /* All IDs allocated and the capacity looped. */
175
176 return find_or_create_page(alloc, alloc->capacity, 1);
177}
178
179/*
180 * Marks an ID within an allocator page as in use.
181 * If the ID was the last free ID in the page, the page is removed from the
182 * allocator's list of free IDs. In the typical allocation case, this page is
183 * the first page in the list, and removing the page is fast. If instead an ID
184 * is being reserved by number, this may end up scanning the whole single linked
185 * list of pages in order to remove it.
186 */
187static void reserve_bit(struct id_alloc *alloc, struct id_alloc_page *page,
188 int word, int offset)
189{
190 struct id_alloc_page *itr;
191
192 page->allocated_mask[word] |= ((uint32_t)1) << offset;
193 alloc->allocated += 1;
194
195 if (page->allocated_mask[word] == UINT32_MAX) {
196 page->full_word_mask |= ((uint32_t)1) << word;
197 if (page->full_word_mask == UINT32_MAX) {
198 if (alloc->has_free == page) {
199 /* allocate always pulls from alloc->has_free */
200 alloc->has_free = page->next_has_free;
201 } else {
202 /* reserve could pull from any page with free
203 * bits
204 */
205 itr = alloc->has_free;
206 while (itr) {
207 if (itr->next_has_free == page) {
208 itr->next_has_free =
209 page->next_has_free;
210 return;
211 }
212
213 itr = itr->next_has_free;
214 }
215 }
216 }
217 }
218}
219
220/*
221 * Reserve an ID number from the allocator. Returns IDALLOC_INVALID (0) if the
222 * allocator has no more IDs available.
223 */
224uint32_t idalloc_allocate(struct id_alloc *alloc)
225{
226 struct id_alloc_page *page;
227 int word, offset;
228 uint32_t return_value;
229
230 if (alloc->has_free == NULL)
231 create_next_page(alloc);
232
233 if (alloc->has_free == NULL) {
234 flog_err(EC_LIB_ID_EXHAUST,
235 "ID Allocator %s has run out of IDs.", alloc->name);
236 return IDALLOC_INVALID;
237 }
238
239 page = alloc->has_free;
240 word = FFS32(~(page->full_word_mask)) - 1;
241
242 if (word < 0 || word >= 32) {
243 flog_err(EC_LIB_ID_CONSISTENCY,
244 "ID Allocator %s internal error. Page starting at %d is inconsistent.",
245 alloc->name, page->base_value);
246 return IDALLOC_INVALID;
247 }
248
249 offset = FFS32(~(page->allocated_mask[word])) - 1;
250 if (offset < 0 || offset >= 32) {
251 flog_err(EC_LIB_ID_CONSISTENCY,
252 "ID Allocator %s internal error. Page starting at %d is inconsistent on word %d",
253 alloc->name, page->base_value, word);
254 return IDALLOC_INVALID;
255 }
256 return_value = page->base_value + word * 32 + offset;
257
258 reserve_bit(alloc, page, word, offset);
259
260 return return_value;
261}
262
263/*
264 * Tries to allocate a specific ID from the allocator. Returns IDALLOC_INVALID
265 * when the ID being "reserved" has allready been assigned/reserved. This should
266 * only be done with low numbered IDs, as the allocator needs to reserve bit-map
267 * pages in order
268 */
269uint32_t idalloc_reserve(struct id_alloc *alloc, uint32_t id)
270{
271 struct id_alloc_page *page;
272 int word, offset;
273
274 while (alloc->capacity <= id)
275 create_next_page(alloc);
276
277 word = ID_WORD(id);
278 offset = ID_OFFSET(id);
279 page = find_or_create_page(alloc, id, 0);
280 /* page can't be null because the loop above ensured it was created. */
281
282 if (page->allocated_mask[word] & (((uint32_t)1) << offset)) {
283 flog_err(EC_LIB_ID_CONSISTENCY,
284 "ID Allocator %s could not reserve %" PRIu32
285 " because it is already allocated.",
286 alloc->name, id);
287 return IDALLOC_INVALID;
288 }
289
290 reserve_bit(alloc, page, word, offset);
291 return id;
292}
293
294/*
295 * Set up an empty ID allocator, with IDALLOC_INVALID pre-reserved.
296 */
297struct id_alloc *idalloc_new(const char *name)
298{
299 struct id_alloc *ret;
300
301 ret = XCALLOC(MTYPE_IDALLOC_ALLOCATOR, sizeof(*ret));
302 ret->name = XSTRDUP(MTYPE_IDALLOC_ALLOCATOR_NAME, name);
303
304 idalloc_reserve(ret, IDALLOC_INVALID);
305
306 return ret;
307}
308
309/*
310 * Free a subdir, and all pages below it.
311 */
312static void idalloc_destroy_subdir(struct id_alloc_subdir *subdir)
313{
314 int i;
315
316 for (i = 0; i < IDALLOC_PAGE_COUNT; i++) {
317 if (subdir->sublevels[i])
318 XFREE(MTYPE_IDALLOC_PAGE, subdir->sublevels[i]);
319 else
320 break;
321 }
322 XFREE(MTYPE_IDALLOC_SUBDIRECTORY, subdir);
323}
324
325/*
326 * Free a dir, and all subdirs/pages below it.
327 */
328static void idalloc_destroy_dir(struct id_alloc_dir *dir)
329{
330 int i;
331
332 for (i = 0; i < IDALLOC_SUBDIR_COUNT; i++) {
333 if (dir->sublevels[i])
334 idalloc_destroy_subdir(dir->sublevels[i]);
335 else
336 break;
337 }
338 XFREE(MTYPE_IDALLOC_DIRECTORY, dir);
339}
340
341/*
342 * Free all memory associated with an ID allocator.
343 */
344void idalloc_destroy(struct id_alloc *alloc)
345{
346 int i;
347
348 for (i = 0; i < IDALLOC_DIR_COUNT; i++) {
349 if (alloc->sublevels[i])
350 idalloc_destroy_dir(alloc->sublevels[i]);
351 else
352 break;
353 }
354
355 XFREE(MTYPE_IDALLOC_ALLOCATOR_NAME, alloc->name);
356 XFREE(MTYPE_IDALLOC_ALLOCATOR, alloc);
357}
358
359/*
360 * Give an ID number to temporary holding pool.
361 */
362void idalloc_free_to_pool(struct id_alloc_pool **pool_ptr, uint32_t id)
363{
364 struct id_alloc_pool *new_pool;
365
366 new_pool = XMALLOC(MTYPE_IDALLOC_POOL, sizeof(*new_pool));
367 new_pool->id = id;
368 new_pool->next = *pool_ptr;
369 *pool_ptr = new_pool;
370}
371
372/*
373 * Free all ID numbers held in a holding pool back to the main allocator.
374 */
375void idalloc_drain_pool(struct id_alloc *alloc, struct id_alloc_pool **pool_ptr)
376{
377 struct id_alloc_pool *current, *next;
378
379 while (*pool_ptr) {
380 current = *pool_ptr;
381 next = current->next;
382 idalloc_free(alloc, current->id);
383 XFREE(MTYPE_IDALLOC_POOL, current);
384 *pool_ptr = next;
385 }
386}
387
388/*
389 * Allocate an ID from either a holding pool, or the main allocator. IDs will
390 * only be pulled form the main allocator when the pool is empty.
391 */
392uint32_t idalloc_allocate_prefer_pool(struct id_alloc *alloc,
393 struct id_alloc_pool **pool_ptr)
394{
395 uint32_t ret;
396 struct id_alloc_pool *pool_head = *pool_ptr;
397
398 if (pool_head) {
399 ret = pool_head->id;
400 *pool_ptr = pool_head->next;
401 XFREE(MTYPE_IDALLOC_POOL, pool_head);
402 return ret;
403 } else {
404 return idalloc_allocate(alloc);
405 }
406}