summaryrefslogtreecommitdiff
path: root/src/data-pool.c
blob: 48521b64dec3df796ca876011ee4d6c65d2a4efb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
#include "data-pool.h"
#include "maxminddb.h"

#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>

static bool can_multiply(size_t const, size_t const, size_t const);

// Allocate an MMDB_data_pool_s. It initially has space for size
// MMDB_entry_data_list_s structs.
MMDB_data_pool_s *data_pool_new(size_t const size)
{
    MMDB_data_pool_s *const pool = calloc(1, sizeof(MMDB_data_pool_s));
    if (!pool) {
        return NULL;
    }

    if (size == 0 ||
        !can_multiply(SIZE_MAX, size, sizeof(MMDB_entry_data_list_s))) {
        data_pool_destroy(pool);
        return NULL;
    }
    pool->size = size;
    pool->blocks[0] = calloc(pool->size, sizeof(MMDB_entry_data_list_s));
    if (!pool->blocks[0]) {
        data_pool_destroy(pool);
        return NULL;
    }
    pool->blocks[0]->pool = pool;

    pool->sizes[0] = size;

    pool->block = pool->blocks[0];

    return pool;
}

// Determine if we can multiply m*n. We can do this if the result will be below
// the given max. max will typically be SIZE_MAX.
//
// We want to know if we'll wrap around.
static bool can_multiply(size_t const max, size_t const m, size_t const n)
{
    if (m == 0) {
        return false;
    }

    return n <= max / m;
}

// Clean up the data pool.
void data_pool_destroy(MMDB_data_pool_s *const pool)
{
    if (!pool) {
        return;
    }

    for (size_t i = 0; i <= pool->index; i++) {
        free(pool->blocks[i]);
    }

    free(pool);
}

// Claim a new struct from the pool. Doing this may cause the pool's size to
// grow.
MMDB_entry_data_list_s *data_pool_alloc(MMDB_data_pool_s *const pool)
{
    if (!pool) {
        return NULL;
    }

    if (pool->used < pool->size) {
        MMDB_entry_data_list_s *const element = pool->block + pool->used;
        pool->used++;
        return element;
    }

    // Take it from a new block of memory.

    size_t const new_index = pool->index + 1;
    if (new_index == DATA_POOL_NUM_BLOCKS) {
        // See the comment about not growing this on DATA_POOL_NUM_BLOCKS.
        return NULL;
    }

    if (!can_multiply(SIZE_MAX, pool->size, 2)) {
        return NULL;
    }
    size_t const new_size = pool->size * 2;

    if (!can_multiply(SIZE_MAX, new_size, sizeof(MMDB_entry_data_list_s))) {
        return NULL;
    }
    pool->blocks[new_index] = calloc(new_size, sizeof(MMDB_entry_data_list_s));
    if (!pool->blocks[new_index]) {
        return NULL;
    }

    // We don't need to set this, but it's useful for introspection in tests.
    pool->blocks[new_index]->pool = pool;

    pool->index = new_index;
    pool->block = pool->blocks[pool->index];

    pool->size = new_size;
    pool->sizes[pool->index] = pool->size;

    MMDB_entry_data_list_s *const element = pool->block;
    pool->used = 1;
    return element;
}

// Turn the structs in the array-like pool into a linked list.
//
// Before calling this function, the list isn't linked up.
MMDB_entry_data_list_s *data_pool_to_list(MMDB_data_pool_s *const pool)
{
    if (!pool) {
        return NULL;
    }

    if (pool->index == 0 && pool->used == 0) {
        return NULL;
    }

    for (size_t i = 0; i <= pool->index; i++) {
        MMDB_entry_data_list_s *const block = pool->blocks[i];

        size_t size = pool->sizes[i];
        if (i == pool->index) {
            size = pool->used;
        }

        for (size_t j = 0; j < size - 1; j++) {
            MMDB_entry_data_list_s *const cur = block + j;
            cur->next = block + j + 1;
        }

        if (i < pool->index) {
            MMDB_entry_data_list_s *const last = block + size - 1;
            last->next = pool->blocks[i + 1];
        }
    }

    return pool->blocks[0];
}

#ifdef TEST_DATA_POOL

#include <libtap/tap.h>
#include <maxminddb_test_helper.h>

static void test_can_multiply(void);

int main(void)
{
    plan(NO_PLAN);
    test_can_multiply();
    done_testing();
}

static void test_can_multiply(void)
{
    {
        ok(can_multiply(SIZE_MAX, 1, SIZE_MAX), "1*SIZE_MAX is ok");
    }

    {
        ok(!can_multiply(SIZE_MAX, 2, SIZE_MAX), "2*SIZE_MAX is not ok");
    }

    {
        ok(can_multiply(SIZE_MAX, 10240, sizeof(MMDB_entry_data_list_s)),
           "1024 entry_data_list_s's are okay");
    }
}

#endif