Coverage Report

Created: 2026-07-16 06:42

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