1 /*
2 Copyright 2021 Northern.tech AS
3
4 This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the
8 Free Software Foundation; version 3.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
18
19 To the extent this program is licensed as part of the Enterprise
20 versions of CFEngine, the applicable Commercial Open Source License
21 (COSL) may apply to this file if you as a licensee so wish it. See
22 included file COSL.txt.
23 */
24
25 #include <platform.h>
26 #include <map.h>
27 #include <alloc.h>
28 #include <array_map_priv.h>
29 #include <hash_map_priv.h>
30 #include <string_lib.h>
31
32 /*
33 * This associative array implementation uses array with linear search up to
34 * TINY_LIMIT elements, and then converts into full-fledged hash table with open
35 * addressing.
36 *
37 * There is a lot of small hash tables, both iterating and deleting them as a
38 * hashtable takes a lot of time, especially given associative hash tables are
39 * created and destroyed for each scope entered and left.
40 */
41
42 /* FIXME: make configurable */
43 #define DEFAULT_HASHMAP_INIT_SIZE 128
44
45 struct Map_
46 {
47 MapHashFn hash_fn;
48
49 union
50 {
51 ArrayMap *arraymap;
52 HashMap *hashmap;
53 };
54 };
55
IdentityHashFn(const void * ptr,ARG_UNUSED unsigned int seed)56 static unsigned IdentityHashFn(const void *ptr, ARG_UNUSED unsigned int seed)
57 {
58 return (unsigned)(uintptr_t)ptr;
59 }
60
IdentityEqualFn(const void * p1,const void * p2)61 static bool IdentityEqualFn(const void *p1, const void *p2)
62 {
63 return p1 == p2;
64 }
65
NopDestroyFn(ARG_UNUSED void * p1)66 static void NopDestroyFn(ARG_UNUSED void *p1)
67 {
68 }
69
70 /*
71 * hash_fn is used as a flag "this map uses ArrayMap still". We could have a
72 * separate boolean flag for that, but given we have to store hash_fn somewhere
73 * anyway, let's reuse it this way. Saves us 4-8 bytes for each map.
74 */
IsArrayMap(const Map * map)75 static bool IsArrayMap(const Map *map)
76 {
77 assert(map != NULL);
78 return map->hash_fn != NULL;
79 }
80
MapNew(MapHashFn hash_fn,MapKeyEqualFn equal_fn,MapDestroyDataFn destroy_key_fn,MapDestroyDataFn destroy_value_fn)81 Map *MapNew(MapHashFn hash_fn,
82 MapKeyEqualFn equal_fn,
83 MapDestroyDataFn destroy_key_fn,
84 MapDestroyDataFn destroy_value_fn)
85 {
86 if (hash_fn == NULL)
87 {
88 hash_fn = &IdentityHashFn;
89 }
90
91 if (equal_fn == NULL)
92 {
93 equal_fn = &IdentityEqualFn;
94 }
95
96 if (destroy_key_fn == NULL)
97 {
98 destroy_key_fn = &NopDestroyFn;
99 }
100
101 if (destroy_value_fn == NULL)
102 {
103 destroy_value_fn = &NopDestroyFn;
104 }
105
106 Map *map = xcalloc(1, sizeof(Map));
107 map->arraymap = ArrayMapNew(equal_fn, destroy_key_fn, destroy_value_fn);
108 map->hash_fn = hash_fn;
109 return map;
110 }
111
MapSize(const Map * map)112 size_t MapSize(const Map *map)
113 {
114 assert(map != NULL);
115
116 if (IsArrayMap(map))
117 {
118 return map->arraymap->size;
119 }
120 else
121 {
122 MapIterator i = MapIteratorInit((Map*)map);
123 size_t size = 0;
124
125 while (MapIteratorNext(&i))
126 {
127 size++;
128 }
129
130 return size;
131 }
132 }
133
ConvertToHashMap(Map * map)134 static void ConvertToHashMap(Map *map)
135 {
136 assert(map != NULL);
137
138 HashMap *hashmap = HashMapNew(map->hash_fn,
139 map->arraymap->equal_fn,
140 map->arraymap->destroy_key_fn,
141 map->arraymap->destroy_value_fn,
142 DEFAULT_HASHMAP_INIT_SIZE);
143
144 /* We have to use internals of ArrayMap here, as we don't want to
145 destroy the values in ArrayMapDestroy */
146
147 for (int i = 0; i < map->arraymap->size; ++i)
148 {
149 HashMapInsert(hashmap,
150 map->arraymap->values[i].key,
151 map->arraymap->values[i].value);
152 }
153
154 free(map->arraymap->values);
155 free(map->arraymap);
156
157 map->hashmap = hashmap;
158 map->hash_fn = NULL;
159 }
160
MapInsert(Map * map,void * key,void * value)161 bool MapInsert(Map *map, void *key, void *value)
162 {
163 assert(map != NULL);
164 /* MapGet() is not capable of handling NULL values. */
165 assert(key != NULL);
166 assert(value != NULL);
167
168 if (IsArrayMap(map))
169 {
170 int ret = ArrayMapInsert(map->arraymap, key, value);
171 if (ret != 0)
172 {
173 /* Return true if value was replaced, false if key-value was
174 * inserted as new. */
175 return (ret == 1);
176 }
177
178 /* Does not fit in ArrayMap, must convert to HashMap. */
179 ConvertToHashMap(map);
180 }
181
182 return HashMapInsert(map->hashmap, key, value);
183 }
184
185 /*
186 * The best we can get out of C type system. Caller should make sure that if
187 * argument is const, it does not modify the result.
188 */
MapGetRaw(const Map * map,const void * key)189 static MapKeyValue *MapGetRaw(const Map *map, const void *key)
190 {
191 assert(map != NULL);
192
193 if (IsArrayMap(map))
194 {
195 return ArrayMapGet((ArrayMap *)map->arraymap, key);
196 }
197 else
198 {
199 return HashMapGet((HashMap *)map->hashmap, key);
200 }
201 }
202
MapHasKey(const Map * map,const void * key)203 bool MapHasKey(const Map *map, const void *key)
204 {
205 assert(map != NULL);
206 assert(key != NULL);
207 return MapGetRaw(map, key) != NULL;
208 }
209
210 /* NULL return means not found, or value was NULL! TODO fix, it should only
211 * mean one thing. I added the assert() to make sure we never store NULL
212 * values. */
MapGet(Map * map,const void * key)213 void *MapGet(Map *map, const void *key)
214 {
215 assert(map != NULL);
216 MapKeyValue *kv = MapGetRaw(map, key);
217 if (kv != NULL)
218 {
219 assert(kv->value != NULL);
220 return kv->value;
221 }
222
223 return NULL;
224 }
225
MapRemove(Map * map,const void * key)226 bool MapRemove(Map *map, const void *key)
227 {
228 assert(map != NULL);
229
230 if (IsArrayMap(map))
231 {
232 return ArrayMapRemove(map->arraymap, key);
233 }
234 else
235 {
236 return HashMapRemove(map->hashmap, key);
237 }
238 }
239
MapClear(Map * map)240 void MapClear(Map *map)
241 {
242 assert(map != NULL);
243
244 if (IsArrayMap(map))
245 {
246 ArrayMapClear(map->arraymap);
247 }
248 else
249 {
250 HashMapClear(map->hashmap);
251 }
252 }
253
MapSoftDestroy(Map * map)254 void MapSoftDestroy(Map *map)
255 {
256 if (map)
257 {
258 if (IsArrayMap(map))
259 {
260 ArrayMapSoftDestroy(map->arraymap);
261 }
262 else
263 {
264 HashMapSoftDestroy(map->hashmap);
265 }
266 free(map);
267 }
268 }
269
MapDestroy(Map * map)270 void MapDestroy(Map *map)
271 {
272 if (map)
273 {
274 if (IsArrayMap(map))
275 {
276 ArrayMapDestroy(map->arraymap);
277 }
278 else
279 {
280 HashMapDestroy(map->hashmap);
281 }
282 free(map);
283 }
284 }
285
MapContainsSameKeys(const Map * map1,const Map * map2)286 bool MapContainsSameKeys(const Map *map1, const Map *map2)
287 {
288 assert(map1 != NULL);
289 assert(map2 != NULL);
290
291 MapIterator i = MapIteratorInit((Map *)map1);
292 MapKeyValue *item;
293 size_t count = 0;
294 while ((item = MapIteratorNext(&i)))
295 {
296 count++;
297 if (!MapHasKey(map2, item->key))
298 {
299 return false;
300 }
301 }
302 return (count == MapSize(map2));
303 }
304
MapPrintStats(const Map * map,FILE * f)305 void MapPrintStats(const Map *map, FILE *f)
306 {
307 assert(map != NULL);
308
309 fprintf(f, "================ Map statistics ================\n");
310
311 if (IsArrayMap(map))
312 {
313 fprintf(f, "Map is too small, fits in a small array.\n");
314 }
315 else
316 {
317 HashMapPrintStats(map->hashmap, f);
318 }
319
320 fprintf(f, "================================================\n");
321 }
322
323 /******************************************************************************/
324
MapIteratorInit(Map * map)325 MapIterator MapIteratorInit(Map *map)
326 {
327 assert(map != NULL);
328
329 MapIterator i;
330 if (IsArrayMap(map))
331 {
332 i.is_array = true;
333 i.arraymap_iter = ArrayMapIteratorInit(map->arraymap);
334 }
335 else
336 {
337 i.is_array = false;
338 i.hashmap_iter = HashMapIteratorInit(map->hashmap);
339 }
340 return i;
341 }
342
MapIteratorNext(MapIterator * i)343 MapKeyValue *MapIteratorNext(MapIterator *i)
344 {
345 if (i->is_array)
346 {
347 return ArrayMapIteratorNext(&i->arraymap_iter);
348 }
349 else
350 {
351 return HashMapIteratorNext(&i->hashmap_iter);
352 }
353 }
354
355 TYPED_MAP_DEFINE(String, char *, char *,
356 StringHash_untyped,
357 StringEqual_untyped,
358 &free,
359 &free)
360