pfix-srsd: add a -I option
[apps/pfixtools.git] / common / trie.c
1 /******************************************************************************/
2 /*          pfixtools: a collection of postfix related tools                  */
3 /*          ~~~~~~~~~                                                         */
4 /*  ________________________________________________________________________  */
5 /*                                                                            */
6 /*  Redistribution and use in source and binary forms, with or without        */
7 /*  modification, are permitted provided that the following conditions        */
8 /*  are met:                                                                  */
9 /*                                                                            */
10 /*  1. Redistributions of source code must retain the above copyright         */
11 /*     notice, this list of conditions and the following disclaimer.          */
12 /*  2. Redistributions in binary form must reproduce the above copyright      */
13 /*     notice, this list of conditions and the following disclaimer in the    */
14 /*     documentation and/or other materials provided with the distribution.   */
15 /*  3. The names of its contributors may not be used to endorse or promote    */
16 /*     products derived from this software without specific prior written     */
17 /*     permission.                                                            */
18 /*                                                                            */
19 /*  THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS   */
20 /*  OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED         */
21 /*  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE    */
22 /*  DISCLAIMED.  IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY         */
23 /*  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL        */
24 /*  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS   */
25 /*  OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)     */
26 /*  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,       */
27 /*  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN  */
28 /*  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE           */
29 /*  POSSIBILITY OF SUCH DAMAGE.                                               */
30 /*                                                                            */
31 /*   Copyright (c) 2006-2008 the Authors                                      */
32 /*   see AUTHORS and source files for details                                 */
33 /******************************************************************************/
34
35 /*
36  * Copyright © 2008 Florent Bruneau
37  */
38
39 #include "array.h"
40 #include "str.h"
41 #include "trie.h"
42
43 typedef struct trie_entry_t trie_entry_t;
44
45 struct trie_entry_t {
46     uint32_t c_offset;
47     uint32_t children_offset;
48
49     uint16_t c_len;
50     uint16_t children_len;
51 };
52 #define TRIE_ENTRY_INIT { 0, 0, 0, 0 }
53 ARRAY(trie_entry_t)
54
55 #define str(trie, entry)  array_ptr((trie)->c, (entry)->c_offset)
56 #define key(trie, id) array_ptr((trie)->keys, array_elt((trie)->keys_offset, (id)))
57
58 struct trie_t {
59     A(trie_entry_t) entries;
60     A(char)         c;
61     A(char)         keys;
62     A(int)          keys_offset;
63
64     bool locked;
65 };
66
67 trie_t *trie_new(void)
68 {
69     return p_new(trie_t, 1);
70 }
71
72 static inline void trie_cleanup_build_data(trie_t *trie)
73 {
74     array_wipe(trie->keys);
75     array_wipe(trie->keys_offset);
76 }
77
78 void trie_delete(trie_t **trie)
79 {
80     if (*trie) {
81         trie_cleanup_build_data(*trie);
82         trie_unlock(*trie);
83         array_wipe((*trie)->entries);
84         array_wipe((*trie)->c);
85         p_delete(trie);
86     }
87 }
88
89 /** Check that the given entry is a prefix for the given key.
90  */
91 static inline bool trie_entry_c_match(const trie_t *trie,
92                                       const trie_entry_t *entry,
93                                       const char *key)
94 {
95     const char *c = str(trie, entry);
96     int i = 0;
97     for (i = 0 ; i < entry->c_len ; ++i) {
98         if (key[i] != c[i]) {
99             return false;
100         }
101     }
102     return true;
103 }
104
105 static inline bool trie_entry_match(const trie_t *trie,
106                                     const trie_entry_t *entry, const char *key)
107 {
108     return !!(strcmp(str(trie, entry), key) == 0);
109 }
110
111 static inline bool trie_entry_prefix(const trie_t *trie,
112                                      const trie_entry_t *entry, const char *key)
113 {
114     int len = entry->c_len;
115     if (len > 0 && str(trie, entry)[len -1] == '\0') {
116         --len;
117     }
118     return !!(strncmp(str(trie, entry), key, len) == 0);
119 }
120
121 static inline bool trie_entry_is_leaf(const trie_entry_t *entry)
122 {
123     return entry->children_len == 0;
124 }
125
126 /** Lookup for a child of entry matching the given entry at the given pos.
127  * Only the first character of the children is taken into account in the
128  * lookup. The current entry is assumed to match the key.
129  */
130 static inline const trie_entry_t* trie_entry_child(const trie_t *trie,
131                                                    const trie_entry_t* entry,
132                                                    const char *key)
133 {
134     uint32_t start = entry->children_offset;
135     uint32_t end   = start + entry->children_len;
136     const char c = *key;
137
138     while (start < end) {
139         uint32_t mid = (start + end) >> 1;
140         const trie_entry_t* child = array_ptr(trie->entries, mid);
141         const char c2 = str(trie, child)[0];
142
143         if (c2 == c) {
144             return child;
145         }
146         if (c < c2) {
147             end = mid;
148         } else {
149             start = mid + 1;
150         }
151     }
152     return NULL;
153 }
154
155 static inline uint32_t trie_entry_new(trie_t *trie)
156 {
157     const trie_entry_t e = TRIE_ENTRY_INIT;
158     array_add(trie->entries, e);
159     return trie->entries.len - 1;
160 }
161
162 static inline uint32_t trie_add_leaf(trie_t *trie, const char *key)
163 {
164     trie_entry_t *entry;
165     int len = m_strlen(key) + 1;
166     int id  = trie_entry_new(trie);
167     entry = array_ptr(trie->entries, id);
168     entry->c_offset = trie->c.len;
169     entry->c_len    = len;
170 #ifdef CHECK_INTEGRITY
171     for (int i = 0 ; i < len - 1 ; ++i) {
172         if (key[i] == '\0') {
173             printf("Found a '\\0' in the string of the leaf\n");
174             abort();
175         }
176     }
177     if (key[len - 1] != '\0') {
178       printf("Key does not end with a '\\0'");
179       abort();
180     }
181 #endif
182     array_append(trie->c, key, len);
183     return trie->entries.len - 1;
184 }
185
186 static inline void trie_entry_insert_child(trie_t *trie, uint32_t id, uint32_t pchild)
187 {
188     trie_entry_t *entry = array_ptr(trie->entries, id);
189     if (entry->children_len == 0) {
190         entry->children_offset = pchild;
191         entry->children_len    = 1;
192     } else {
193         if (entry->children_offset + entry->children_len != pchild) {
194             printf("Inserting child %d while offset is %d[%d]\n",
195                    pchild, entry->children_offset, entry->children_len);
196             abort();
197         }
198         ++entry->children_len;
199     }
200 }
201
202 static inline void trie_entry_split(trie_t *trie, uint32_t id, uint16_t pos)
203 {
204     trie_entry_t *child;
205     trie_entry_t *entry;
206     child    = array_ptr(trie->entries, trie_entry_new(trie));
207     entry    = array_ptr(trie->entries, id);
208     if (pos == 0) {
209         child->c_offset = entry->c_offset;
210         child->c_len    = entry->c_len;
211         entry->c_offset = 0;
212         entry->c_len    = 0;
213     } else {
214         assert(pos <= entry->c_len);
215         child->c_offset = entry->c_offset + pos;
216         child->c_len    = entry->c_len - pos;
217         entry->c_len    = pos;
218     }
219     child->children_offset = entry->children_offset;
220     child->children_len    = entry->children_len;
221     entry->children_offset = trie->entries.len - 1;
222     entry->children_len    = 1;
223 }
224
225 void trie_insert(trie_t *trie, const char* key)
226 {
227     assert(trie->entries.len == 0 && "Trie already compiled");
228
229     int len = m_strlen(key) + 1;
230     array_add(trie->keys_offset, trie->keys.len);
231     array_append(trie->keys, key, len);
232 }
233
234
235 static inline void trie_compile_aux(trie_t *trie, uint32_t id,
236                                     uint32_t first_key, uint32_t last_key,
237                                     int offset, int initial_diff)
238 {
239     uint32_t forks[256];
240     uint32_t fork_pos = 0;
241     char current = '\0';
242
243 #ifdef CHECK_INTEGRITY
244     assert(strcmp(key(trie, first_key) + offset, str(trie, entry)) == 0);
245 #endif
246
247     for (int off_diff = initial_diff ; fork_pos == 0 ; ++off_diff, ++offset) {
248         current = key(trie, first_key)[offset];
249         for (uint32_t i = first_key + 1 ; i < last_key ; ++i) {
250             const char *ckey = key(trie, i) + offset;
251             const char c = *ckey;
252             if (c != current) {
253                 array_ensure_capacity_delta(trie->entries, 2);
254                 if (fork_pos == 0) {
255                     trie_entry_split(trie, id, off_diff);
256                 }
257                 trie_entry_insert_child(trie, id, trie_add_leaf(trie, ckey));
258                 forks[fork_pos++] = i;
259                 current = c;
260             }
261         }
262         if (fork_pos == 0 && current == '\0') {
263             return;
264         }
265     }
266     forks[fork_pos] = last_key;
267
268     const uint8_t children_len = array_elt(trie->entries, id).children_len;
269     for (uint16_t i = 0 ; i < children_len ; ++i) {
270         int child = array_elt(trie->entries, id).children_offset + i;
271         if (forks[i] - 1 > first_key) {
272             trie_compile_aux(trie, child, first_key, forks[i], offset, 1);
273         }
274         first_key = forks[i];
275     }
276 }
277
278 void trie_compile(trie_t *trie, bool memlock)
279 {
280     assert(trie->entries.len == 0 && "Trie already compiled");
281     assert(trie->keys.len != 0 && "Trying to compile an empty trie");
282     {
283 #       define QSORT_TYPE int
284 #       define QSORT_BASE trie->keys_offset.data
285 #       define QSORT_NELT trie->keys_offset.len
286 #       define QSORT_LT(a,b) strcmp(trie->keys.data + *a, trie->keys.data + *b) < 0
287 #       include "qsort.c"
288     }
289
290     array_ensure_capacity(trie->entries, trie->keys_offset.len);
291     trie_compile_aux(trie, trie_add_leaf(trie, key(trie, 0)),
292                      0, trie->keys_offset.len, 0, 0);
293     trie_cleanup_build_data(trie);
294     array_adjust(trie->entries);
295     array_adjust(trie->c);
296     if (memlock) {
297         trie_lock(trie);
298     }
299 }
300
301 bool trie_lookup(const trie_t *trie, const char *key)
302 {
303     assert(trie->keys.len == 0L && "Can't lookup: trie not compiled");
304     if (trie->entries.len == 0) {
305         return false;
306     } else {
307         const trie_entry_t *current = array_ptr(trie->entries, 0);
308         while (true) {
309             if (trie_entry_is_leaf(current)) {
310                 return trie_entry_match(trie, current, key);
311             } else if (trie_entry_c_match(trie, current, key)) {
312                 key += current->c_len;
313                 current = trie_entry_child(trie, current, key);
314                 if (current == NULL) {
315                     return false;
316                 }
317             } else {
318                 return false;
319             }
320         }
321     }
322 }
323
324 bool trie_prefix(const trie_t *trie, const char *key)
325 {
326     assert(trie->keys.len == 0L && "Can't lookup: trie not compiled");
327     if (trie->entries.len == 0) {
328         return false;
329     } else {
330         const trie_entry_t *current = array_ptr(trie->entries, 0);
331         while (true) {
332             if (trie_entry_is_leaf(current)) {
333                 return trie_entry_prefix(trie, current, key);
334             } else if (trie_entry_c_match(trie, current, key)) {
335                 key += current->c_len;
336                 current = trie_entry_child(trie, current, key);
337                 if (current == NULL) {
338                     return false;
339                 }
340             } else {
341                 return false;
342             }
343         }
344     }
345 }
346
347 void trie_lock(trie_t *trie)
348 {
349     if (trie->locked) {
350         return;
351     }
352     if (!array_lock(trie->entries)) {
353         UNIXERR("mlock");
354     }
355     if (!array_lock(trie->c)) {
356         UNIXERR("mlock");
357     }
358     if (mlock(trie, sizeof(trie_t)) != 0) {
359         UNIXERR("mlock");
360         return;
361     }
362     trie->locked = true;
363 }
364
365 void trie_unlock(trie_t *trie)
366 {
367     if (!trie->locked) {
368         return;
369     }
370     array_unlock(trie->entries);
371     array_unlock(trie->c);
372     munlock(trie, sizeof(trie_t));
373     trie->locked = false;
374 }
375
376 /* Debug {{{1
377  */
378
379 static inline void trie_entry_inspect(const trie_t *trie, bool show_content,
380                                       const trie_entry_t *entry, int level)
381 {
382     static int max_depth = 0;
383     static int leaves    = 0;
384     static int depth_sum = 0;
385
386     if (entry == array_ptr(trie->entries, 0)) {
387       max_depth = 0;
388       leaves    = 0;
389       depth_sum = 0;
390     }
391     if (trie_entry_is_leaf(entry)) {
392         if (level > max_depth) {
393             max_depth = level;
394         }
395         ++leaves;
396         depth_sum += level;
397     }
398     if (show_content) {
399         for (int i = 0 ; i < level ; ++i) {
400             fputs("  ", stdout);
401         }
402         if (entry->c_len == 0) {
403             fputs("(0)", stdout);
404         } else {
405             const char *c = str(trie, entry);
406             printf("(%d) ", entry->c_len);
407             for (int i = 0 ; i < entry->c_len ; ++i) {
408                 if (c[i]) {
409                     printf("%c ", c[i]);
410                 } else {
411                     fputs("\\0 ", stdout);
412                 }
413             }
414         }
415         fputs("\n", stdout);
416     }
417     for (uint32_t i = 0 ; i < entry->children_len ; ++i) {
418         trie_entry_inspect(trie, show_content,
419                            array_ptr(trie->entries, entry->children_offset + i),
420                            level + 1);
421     }
422     if (level == 0) {
423         printf("Average char per node: %d\n", trie->c.len / trie->entries.len);
424         printf("Number of nodes: %d\n", trie->entries.len);
425         printf("Number of leaves: %d\n", leaves);
426         printf("Max depth: %d\n", max_depth);
427         printf("Average leaf depth: %d\n", depth_sum / leaves);
428         printf("Memory used: %zd\n", (trie->entries.size * sizeof(trie_entry_t))
429                                   + (trie->c.size) + sizeof(trie_t));
430     }
431 }
432
433 void trie_inspect(const trie_t *trie, bool show_content)
434 {
435     trie_entry_inspect(trie, show_content, trie->entries.data, 0);
436 }