]> Pileus Git - ~andy/fetchmail/blob - uid_db.c
#include <strings.h> for ffs() prototype.
[~andy/fetchmail] / uid_db.c
1 /*
2   POP3 UID db
3
4         Copyright (c) 2010 MAD Partners, Ltd. (rweikusat@mssgmbh.com)
5
6         This file is being published in accordance with the GPLv2 terms
7         contained in the COPYING file being part of the fetchmail
8         6.3.17 release, including the OpenSSL exemption.
9 */
10
11 /*  includes */
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <strings.h>  // ffs() lives here
16
17 #include "xmalloc.h"
18 #include "uid_db.h"
19
20 /*  constants */
21 enum {
22     MIN_RECORDS =       16      /* arbitrary */
23 };
24
25 /*  types */
26 struct pat_node {
27     struct pat_node *ptrs_[3];
28
29     /*
30       The bit mask is stored in the nodes (as opposed to the
31       offset, which is (re-)calculated on demand) because
32       calculating the mask is a non-trivial operation (at
33       least on x86).
34      */
35     unsigned bit_ndx, bit_mask;
36
37     struct uid_db_record *rec;
38 };
39
40 /*
41   The idea behind this is that the 'left' pointer of
42   a node is accessible as ptrs(np)[-1] and the right
43   one a ptrs(np)[1]. This implies that no separate codepaths
44   for 'symmetric left- and right-cases' are needed.
45 */
46 #define ptrs(np) ((np)->ptrs_ + 1)
47
48 /*  routines */
49 /**  various helpers */
50 static inline unsigned bit_ofs(unsigned bit_ndx)
51 {
52     return bit_ndx >> 3;
53 }
54
55 static inline unsigned bit_mask(unsigned bit_ndx)
56 {
57     return 1 << (bit_ndx & 7);
58 }
59
60 /**  PATRICIA trie insertion */
61 /***  walkers */
62 static struct pat_node *walk_down(struct uid_db *db, struct uid_db_record *rec,
63                                   struct pat_node ***edgep, struct pat_node **parentp)
64 {
65     /*
66       Find the pat node whose id is 'most similar' to the id
67       stored in rec->id. Return a pointer to this node.
68       'parentp' and 'edgep' are output-only parameters which
69       will point to the parent of returned node and to the edge
70       pointer going from the parent to the returned node after
71       the call has returned.
72
73       This routine is intended for inserts only.
74      */
75     struct pat_node *cur, **edge;
76     unsigned bit_ndx, v = 0, ofs;
77
78     cur = db->pat_root;
79     ofs = -1;
80     do {
81         bit_ndx = cur->bit_ndx;
82
83         if (bit_ofs(bit_ndx) != ofs) {
84             ofs = bit_ofs(bit_ndx);
85             v = ofs < rec->id_len ? rec->id[ofs] : 0;
86         }
87
88         edge = ptrs(cur) + (v & cur->bit_mask ? 1 : -1);
89     } while ((cur = *edge) && cur->bit_ndx > bit_ndx);
90
91     *parentp =
92         (struct pat_node *)
93         ((unsigned char *)edge - (v & bit_mask(bit_ndx) ?
94                                   offsetof(struct pat_node, ptrs_[2])
95                                   : offsetof(struct pat_node, ptrs_[0])));
96     *edgep = edge;
97     return cur;
98 }
99
100 static inline struct pat_node *walk_up(unsigned diff_ndx, struct pat_node **parent)
101 {
102     /*
103       Walk the chain of parent pointers starting with *parent until a node
104       is found whose parent has a bit_ndx smaller than diff_ndx. Return
105       a pointer to this node and update *parent to point to its parent.
106     */
107     struct pat_node *p, *np;
108
109     np = *parent;
110
111     while ((p = *ptrs(np)) && p->bit_ndx > diff_ndx)
112         np = p;
113
114     *parent = p;
115     return np;
116 }
117
118 /***  bit fiddling */
119 static inline unsigned first_set_bit_in_char(unsigned v)
120 {
121     return ffs(v) - 1;
122 }
123
124 static int find_first_diff_bit(struct uid_db_record const *r0,
125                                struct uid_db_record const *r1)
126 {
127     /*
128       Return the bit_ndx of the first differing bit in
129       r0->id and r1->id or -1 if the strings are identical.
130     */
131     struct uid_db_record const *long_id;
132     unsigned ofs, max;
133     unsigned char v;
134
135     max = r0->id_len;
136     if (max > r1->id_len) {
137         max = r1->id_len;
138         long_id = r0;
139     } else
140         long_id = r1;
141
142     ofs = 0;
143     do
144         v = r0->id[ofs] ^ r1->id[ofs];
145     while (!v && ++ofs < max);
146
147     if (!v) {
148         if (r0->id_len == r1->id_len) return -1;
149         v = long_id->id[ofs];
150     }
151
152     return first_set_bit_in_char(v) + ofs * 8;
153 }
154
155 static inline unsigned bit_set(unsigned bit_ndx, struct uid_db_record const *rec)
156 {
157     /*
158       Return non-zero if the bit corresponding with bit_ndx is set
159       in rec->id
160     */
161     unsigned ofs;
162
163     ofs = bit_ofs(bit_ndx);
164     if (ofs >= rec->id_len) return 0;
165     return rec->id[ofs] & bit_mask(bit_ndx);
166 }
167
168 /***  node allocation */
169 static struct pat_node *get_pat_node(struct uid_db_record *rec)
170 {
171     /*
172       Allocate a pat_node, set its rec pointer to rec and the
173       next pointer of rec to NULL. Return pointer to the pat_node.
174     */
175     struct pat_node *np;
176
177     np = (struct pat_node *)xmalloc(sizeof(*np));
178     np->rec = rec;
179     rec->next = NULL;
180     return np;
181 }
182
183 static struct pat_node *get_standalone_node(struct uid_db_record *rec)
184 {
185     /*
186       Return a pat_node suitable for being inserted on the 'left edge'
187       of the trie, ie either linked to a node whose left pointer was zero
188       or being inserted as root node into an empty trie. The bit_ndx of
189       the pat_node is set to the index corresponding with the highest
190       set bit in rec->id.
191
192       NB: This is a bad choice when UIDs share a common prefix because
193       this implies that the root node will cause a bit to be tested which
194       is non-zero in all other nodes, adding a theoretically redundant
195       level to the trie. This is (to the best of my knowledge) un-
196       fortunately unavoidable if nodes with different key lengths need
197       to be supported.
198     */
199     struct pat_node *np;
200
201     np = get_pat_node(rec);
202     np->bit_ndx = first_set_bit_in_char(*rec->id);
203     np->bit_mask = bit_mask(np->bit_ndx);
204     return np;
205 }
206
207 /***  various helpers */
208 static inline int record_id_equal(struct uid_db_record const *r0,
209                                   struct uid_db_record const *r1)
210 {
211     return
212         r0->id_len == r1->id_len
213         && memcmp(r0->id, r1->id, r0->id_len) == 0;
214 }
215
216 static struct uid_db_record *append_to_list(struct uid_db_record **recp,
217                                             struct uid_db_record *rec)
218 {
219     /*
220       Append the uid_db_record pointed to by rec to the uid_db_record
221       list accessible as *recp and return rec.
222     */
223     while (*recp) recp = &(*recp)->next;
224     *recp = rec;
225
226     rec->next = NULL;
227     return rec;
228 }
229
230 /***  insert routine */
231 static struct uid_db_record *pat_insert(struct uid_db *db,
232                                         struct uid_db_record *rec)
233 {
234     /*
235       Insert the record pointed to by rec in the (potentially empty)
236       PATRICIA trie pointed to by db->pat_root and return rec.
237     */
238     struct pat_node *np, *closest, *parent, **edge;
239     int me, bit_ndx;
240
241     if (!db->pat_root) {
242         np = get_standalone_node(rec);
243         ptrs(np)[-1] = *ptrs(np) = NULL;
244         ptrs(np)[1] = np;
245
246         db->pat_root = np;
247         return rec;
248     }
249
250     closest = walk_down(db, rec, &edge, &parent);
251
252     if (closest) {
253         bit_ndx = find_first_diff_bit(closest->rec, rec);
254         if (bit_ndx < 0)
255             return append_to_list(&closest->rec->next, rec);
256
257         np = get_pat_node(rec);
258         np->bit_ndx = bit_ndx;
259         np->bit_mask = bit_mask(bit_ndx);
260     } else
261         np = get_standalone_node(rec);
262
263     if (parent->bit_ndx > np->bit_ndx) {
264         closest = walk_up(np->bit_ndx, &parent);
265
266         if (!parent) edge = &db->pat_root;
267         else edge = ptrs(parent)[-1] == closest ?
268                  ptrs(parent) - 1 : ptrs(parent) + 1;
269         *ptrs(closest) = np;
270     }
271
272     *edge = np;
273     *ptrs(np) = parent;
274
275     me = bit_set(np->bit_ndx, rec) ? 1 : -1;
276     ptrs(np)[me] = np;
277     ptrs(np)[-me] = closest;
278
279     return rec;
280 }
281
282 /**  general db insertion */
283 static struct uid_db_record *get_uid_db_record(char const *id, unsigned status)
284 {
285     /*
286       Allocate a uid_db_record structure and set its id pointer to a
287       dynamically allocated copy of id. The status member of the
288       new record is set to status and its message number to zero (invalid).
289       A pointer to it is then returned.
290      */
291     struct uid_db_record *rec;
292     size_t id_len;
293
294     rec = (struct uid_db_record *)xmalloc(sizeof(*rec));
295
296     id_len = strlen(id);
297     rec->id = (char *)memcpy(xmalloc(id_len + 1), id, id_len + 1);
298     rec->id_len = id_len;
299     rec->status = status;
300     rec->num = 0;
301
302     return rec;
303 }
304
305 static void insert_into_records(struct uid_db *db,
306                                 struct uid_db_record *rec)
307 {
308     /*
309       Insert rec into the records array of the uid_db pointed
310       to by db. The array is grown as necessary and the
311       corresponding state variables of the db are updated
312       accordingly. The pos member of rec is set to its position
313       in the array.
314     */
315     unsigned next, want;
316
317     next = db->records_next;
318
319     if (next == db->records_max) {
320         want = db->records_max *= 2;
321         db->records = (struct uid_db_record **)xrealloc(db->records, want * sizeof(rec));
322     }
323
324     rec->pos = next;
325     db->records[next] = rec;
326     db->records_next = next + 1;
327 }
328
329 struct uid_db_record *uid_db_insert(struct uid_db *db,
330                                     char const *id, unsigned status)
331 {
332     /*
333       Create an uid_db_record whose id is id and whose status is
334       status and insert it into the uid_db pointed to by db.
335       Return a pointer to the newly created record.
336     */
337     struct uid_db_record *rec;
338
339     rec = get_uid_db_record(id, status);
340     insert_into_records(db, rec);
341     return pat_insert(db, rec);
342 }
343
344 /**  message number index */
345 void set_uid_db_num(struct uid_db *db, struct uid_db_record *rec,
346                     unsigned num)
347 {
348     /*
349       Set the message number of the record pointed to by rec to num
350       and insert it into the num_ndx of the uid_db pointed to by db
351       at position corresponding with num. The num_ndx lookup array
352       is grown as needed. Message numbers are expected to 'generally'
353       be recorded in ascending order and hence, no provisions are
354       made to deal with the potentially quadratic complexity of
355       inserting a sequence of numbers into an array such that it
356       needs to be grown continuously.
357     */
358     struct num_ndx *num_ndx;
359     unsigned have, want;
360
361     num_ndx = &db->num_ndx;
362
363     if (num_ndx->end_value > num) {
364         have = num_ndx->pos_0_value - num_ndx->end_value + 1;
365         want = num_ndx->pos_0_value - num + 1;
366         num_ndx->end_value = num;
367
368         num_ndx->records = (struct uid_db_record **)xrealloc(num_ndx->records, want * sizeof(rec));
369         do num_ndx->records[--want] = NULL; while (want > have);
370     }
371
372     num_ndx->records[uid_db_num_ofs(num_ndx, num)] = rec;
373 }
374
375 void reset_uid_db_nums(struct uid_db *db)
376 {
377     /*
378       Reset the message numbers of all uid_db_records stored
379       in the uid_db pointed to by db. The corresponding num_ndx
380       lookup array is afterwards freed and the num_ndx end_value
381       adjusted in order to indicate an 'empty' message number
382       index.
383     */
384     struct uid_db_record **rec;
385     struct num_ndx *num_ndx;
386     unsigned ndx;
387
388     num_ndx = &db->num_ndx;
389
390     if (num_ndx->end_value < num_ndx->pos_0_value) {
391         ndx = num_ndx->pos_0_value - num_ndx->end_value;
392         while (ndx) {
393             rec = num_ndx->records + --ndx;
394             if (*rec) (*rec)->num = 0;
395         }
396
397         num_ndx->end_value = num_ndx->pos_0_value + 1;
398
399         free(num_ndx->records);
400         num_ndx->records = NULL;
401     }
402 }
403
404 /**  search routines */
405 struct uid_db_record *find_uid_by_id(struct uid_db *db, char const *id)
406 {
407     /*
408       Search for an uid_db_record whose id is id in the uid_db pointed
409       to by db and return a pointer to it or NULL if no such record was
410       found.
411     */
412     struct pat_node *np;
413     struct uid_db_record *rec;
414     unsigned v = 0, bit_ndx, ofs;
415     size_t len;
416
417     np = db->pat_root;
418     if (np) {
419         len = strlen(id);
420         ofs = -1;
421         do {
422             bit_ndx = np->bit_ndx;
423
424             if (bit_ofs(bit_ndx) != ofs) {
425                 ofs = bit_ofs(bit_ndx);
426                 v = ofs < len ? id[ofs] : 0;
427             }
428
429             np = ptrs(np)[v & np->bit_mask ? 1 : -1];
430         } while (np && np->bit_ndx > bit_ndx);
431
432         if (!np) return NULL;
433
434         rec = np->rec;
435         return rec->id_len == len && memcmp(id, rec->id, len) == 0 ?
436             rec : NULL;
437     }
438
439     return NULL;
440 }
441
442 struct uid_db_record *last_uid_in_db(struct uid_db *db, char const *id)
443 {
444     /*
445       Return a pointer to the 'last' (insert order) uid_db_record
446       contained in the uid_db pointed to by db whose id is id or
447       NULL if no such record exists.
448     */
449     struct uid_db_record *rec;
450
451     rec = find_uid_by_id(db, id);
452     if (!rec) return NULL;
453
454     while (rec->next) rec = rec->next;
455     return rec;
456 }
457
458 /**  destruction */
459 static void free_uid_list(struct uid_db_record *rec)
460 {
461     /*
462       Free the list of uid_db_records starting with
463       the record pointed to by rec.
464     */
465     if (rec->next) free_uid_list(rec->next);
466
467     xfree(rec->id);
468     xfree(rec);
469 }
470
471 static void free_pat_trie(struct pat_node *np)
472 {
473     /*
474       Free the PATRCIA-trie pointed to by np and all
475       uid_db_records contained in it.
476
477       The algorithm implemented below is:
478
479         1. Load the left pointer of the node pointed to by
480            np into next.
481
482         2. If the result is not NULL,
483                 2a) Set the left pointer to NULL.
484                 2b) Goto 1 if next points to a child of np.
485
486         3. Load the right pointer of the node pointed to by
487            np into next.
488
489         4. If the result is not NULL,
490                 4a) Set the right pointer to NULL.
491                 4b) Goto 1 id next points to a child of np.
492
493         5. Load next with the parent pointer of np.
494
495         6. Free np->rec and np.
496
497         7. Set np to next and goto 1 if it is not null.
498     */
499     struct pat_node *next;
500
501     do {
502         next = ptrs(np)[-1];
503         if (next) {
504             ptrs(np)[-1] = NULL;
505             if (next->bit_ndx > np->bit_ndx) continue;
506         }
507
508         next = ptrs(np)[1];
509         if (next) {
510             ptrs(np)[1] = NULL;
511             if (next->bit_ndx > np->bit_ndx) continue;
512         }
513
514         next = *ptrs(np);
515
516         free_uid_list(np->rec);
517         free(np);
518     } while ((np = next));
519 }
520
521 void free_uid_db(struct uid_db *db)
522 {
523     /*
524       Free all dynamically allocated memory of the uid_db
525       pointed to by db. The structure is not reinitialized.
526     */
527     if (db->pat_root) free_pat_trie(db->pat_root);
528
529     xfree(db->records);
530     xfree(db->num_ndx.records);
531 }
532
533 /**  various public interfaces */
534 void init_uid_db(struct uid_db *db)
535 {
536     /*
537       Initialize the uid_db structure pointed to by db 'properly'
538       such that it represents an empty database. An array of
539       size MIN_RECORDS is allocated and assigned to db->records.
540     */
541     struct num_ndx *num_ndx;
542
543     db->pat_root = NULL;
544
545     db->records = (struct uid_db_record **)xmalloc(MIN_RECORDS * sizeof(*db->records));
546     db->records_max = MIN_RECORDS;
547     db->records_next = 0;
548
549     num_ndx = &db->num_ndx;
550     num_ndx->pos_0_value = num_ndx->end_value = -1;
551     num_ndx->records = NULL;
552 }
553
554 void swap_uid_db_data(struct uid_db *db_0, struct uid_db *db_1)
555 {
556     struct uid_db tmp;
557
558     tmp = *db_0;
559     *db_0 = *db_1;
560     *db_1 = tmp;
561 }
562
563 int traverse_uid_db(struct uid_db *db,
564                      uid_db_traversal_routine *r, void *arg)
565 {
566     /*
567       Traverses the struct uid_db records array in insert order,
568       invoking the subroutine pointed to by r with a pointer to
569       each record and the arg pointer as arguments. If the return
570       value of that is non-zero, traverse_uid_db immediately returns
571       with this value. Otherwise, zero is returned after the last
572       record was visited.
573
574       The uid_db_traversal_routine must not modify the uid_db during
575       traversal.
576     */
577     struct uid_db_record **recs;
578     unsigned ndx, max;
579     int rc;
580
581     rc = 0;
582     ndx = 0;
583     max = db->records_next;
584     recs = db->records;
585     while (ndx < max && (rc = r(recs[ndx], arg)) == 0)
586         ++ndx;
587
588     return rc;
589 }