]> Pileus Git - ~andy/fetchmail/blob - uid_db.c
Fix typo repsonsible -> responsible.
[~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 #if 0
209 static inline int record_id_equal(struct uid_db_record const *r0,
210                                   struct uid_db_record const *r1)
211 {
212     return
213         r0->id_len == r1->id_len
214         && memcmp(r0->id, r1->id, r0->id_len) == 0;
215 }
216 #endif
217
218 static struct uid_db_record *append_to_list(struct uid_db_record **recp,
219                                             struct uid_db_record *rec)
220 {
221     /*
222       Append the uid_db_record pointed to by rec to the uid_db_record
223       list accessible as *recp and return rec.
224     */
225     while (*recp) recp = &(*recp)->next;
226     *recp = rec;
227
228     rec->next = NULL;
229     return rec;
230 }
231
232 /***  insert routine */
233 static struct uid_db_record *pat_insert(struct uid_db *db,
234                                         struct uid_db_record *rec)
235 {
236     /*
237       Insert the record pointed to by rec in the (potentially empty)
238       PATRICIA trie pointed to by db->pat_root and return rec.
239     */
240     struct pat_node *np, *closest, *parent, **edge;
241     int me, bit_ndx;
242
243     if (!db->pat_root) {
244         np = get_standalone_node(rec);
245         ptrs(np)[-1] = *ptrs(np) = NULL;
246         ptrs(np)[1] = np;
247
248         db->pat_root = np;
249         return rec;
250     }
251
252     closest = walk_down(db, rec, &edge, &parent);
253
254     if (closest) {
255         bit_ndx = find_first_diff_bit(closest->rec, rec);
256         if (bit_ndx < 0)
257             return append_to_list(&closest->rec->next, rec);
258
259         np = get_pat_node(rec);
260         np->bit_ndx = bit_ndx;
261         np->bit_mask = bit_mask(bit_ndx);
262     } else
263         np = get_standalone_node(rec);
264
265     if (parent->bit_ndx > np->bit_ndx) {
266         closest = walk_up(np->bit_ndx, &parent);
267
268         if (!parent) edge = &db->pat_root;
269         else edge = ptrs(parent)[-1] == closest ?
270                  ptrs(parent) - 1 : ptrs(parent) + 1;
271         *ptrs(closest) = np;
272     }
273
274     *edge = np;
275     *ptrs(np) = parent;
276
277     me = bit_set(np->bit_ndx, rec) ? 1 : -1;
278     ptrs(np)[me] = np;
279     ptrs(np)[-me] = closest;
280
281     return rec;
282 }
283
284 /**  general db insertion */
285 static struct uid_db_record *get_uid_db_record(char const *id, unsigned status)
286 {
287     /*
288       Allocate a uid_db_record structure and set its id pointer to a
289       dynamically allocated copy of id. The status member of the
290       new record is set to status and its message number to zero (invalid).
291       A pointer to it is then returned.
292      */
293     struct uid_db_record *rec;
294     size_t id_len;
295
296     rec = (struct uid_db_record *)xmalloc(sizeof(*rec));
297
298     id_len = strlen(id);
299     rec->id = (char *)memcpy(xmalloc(id_len + 1), id, id_len + 1);
300     rec->id_len = id_len;
301     rec->status = status;
302     rec->num = 0;
303
304     return rec;
305 }
306
307 static void insert_into_records(struct uid_db *db,
308                                 struct uid_db_record *rec)
309 {
310     /*
311       Insert rec into the records array of the uid_db pointed
312       to by db. The array is grown as necessary and the
313       corresponding state variables of the db are updated
314       accordingly. The pos member of rec is set to its position
315       in the array.
316     */
317     unsigned next, want;
318
319     next = db->records_next;
320
321     if (next == db->records_max) {
322         want = db->records_max *= 2;
323         db->records = (struct uid_db_record **)xrealloc(db->records, want * sizeof(rec));
324     }
325
326     rec->pos = next;
327     db->records[next] = rec;
328     db->records_next = next + 1;
329 }
330
331 struct uid_db_record *uid_db_insert(struct uid_db *db,
332                                     char const *id, unsigned status)
333 {
334     /*
335       Create an uid_db_record whose id is id and whose status is
336       status and insert it into the uid_db pointed to by db.
337       Return a pointer to the newly created record.
338     */
339     struct uid_db_record *rec;
340
341     rec = get_uid_db_record(id, status);
342     insert_into_records(db, rec);
343     return pat_insert(db, rec);
344 }
345
346 /**  message number index */
347 void set_uid_db_num(struct uid_db *db, struct uid_db_record *rec,
348                     unsigned num)
349 {
350     /*
351       Set the message number of the record pointed to by rec to num
352       and insert it into the num_ndx of the uid_db pointed to by db
353       at position corresponding with num. The num_ndx lookup array
354       is grown as needed. Message numbers are expected to 'generally'
355       be recorded in ascending order and hence, no provisions are
356       made to deal with the potentially quadratic complexity of
357       inserting a sequence of numbers into an array such that it
358       needs to be grown continuously.
359     */
360     struct num_ndx *num_ndx;
361     unsigned have, want;
362
363     num_ndx = &db->num_ndx;
364
365     if (num_ndx->end_value > num) {
366         have = num_ndx->pos_0_value - num_ndx->end_value + 1;
367         want = num_ndx->pos_0_value - num + 1;
368         num_ndx->end_value = num;
369
370         num_ndx->records = (struct uid_db_record **)xrealloc(num_ndx->records, want * sizeof(rec));
371         do num_ndx->records[--want] = NULL; while (want > have);
372     }
373
374     num_ndx->records[uid_db_num_ofs(num_ndx, num)] = rec;
375 }
376
377 void reset_uid_db_nums(struct uid_db *db)
378 {
379     /*
380       Reset the message numbers of all uid_db_records stored
381       in the uid_db pointed to by db. The corresponding num_ndx
382       lookup array is afterwards freed and the num_ndx end_value
383       adjusted in order to indicate an 'empty' message number
384       index.
385     */
386     struct uid_db_record **rec;
387     struct num_ndx *num_ndx;
388     unsigned ndx;
389
390     num_ndx = &db->num_ndx;
391
392     if (num_ndx->end_value < num_ndx->pos_0_value) {
393         ndx = num_ndx->pos_0_value - num_ndx->end_value;
394         while (ndx) {
395             rec = num_ndx->records + --ndx;
396             if (*rec) (*rec)->num = 0;
397         }
398
399         num_ndx->end_value = num_ndx->pos_0_value + 1;
400
401         free(num_ndx->records);
402         num_ndx->records = NULL;
403     }
404 }
405
406 /**  search routines */
407 struct uid_db_record *find_uid_by_id(struct uid_db *db, char const *id)
408 {
409     /*
410       Search for an uid_db_record whose id is id in the uid_db pointed
411       to by db and return a pointer to it or NULL if no such record was
412       found.
413     */
414     struct pat_node *np;
415     struct uid_db_record *rec;
416     unsigned v = 0, bit_ndx, ofs;
417     size_t len;
418
419     np = db->pat_root;
420     if (np) {
421         len = strlen(id);
422         ofs = -1;
423         do {
424             bit_ndx = np->bit_ndx;
425
426             if (bit_ofs(bit_ndx) != ofs) {
427                 ofs = bit_ofs(bit_ndx);
428                 v = ofs < len ? id[ofs] : 0;
429             }
430
431             np = ptrs(np)[v & np->bit_mask ? 1 : -1];
432         } while (np && np->bit_ndx > bit_ndx);
433
434         if (!np) return NULL;
435
436         rec = np->rec;
437         return rec->id_len == len && memcmp(id, rec->id, len) == 0 ?
438             rec : NULL;
439     }
440
441     return NULL;
442 }
443
444 struct uid_db_record *last_uid_in_db(struct uid_db *db, char const *id)
445 {
446     /*
447       Return a pointer to the 'last' (insert order) uid_db_record
448       contained in the uid_db pointed to by db whose id is id or
449       NULL if no such record exists.
450     */
451     struct uid_db_record *rec;
452
453     rec = find_uid_by_id(db, id);
454     if (!rec) return NULL;
455
456     while (rec->next) rec = rec->next;
457     return rec;
458 }
459
460 /**  destruction */
461 static void free_uid_list(struct uid_db_record *rec)
462 {
463     if (!rec) return;
464
465     /*
466       Free the list of uid_db_records starting with
467       the record pointed to by rec.
468     */
469     if (rec->next) free_uid_list(rec->next);
470
471     xfree(rec->id);
472     xfree(rec);
473 }
474
475 static void free_pat_trie(struct pat_node *np)
476 {
477     /*
478       Free the PATRCIA-trie pointed to by np and all
479       uid_db_records contained in it.
480
481       The algorithm implemented below is:
482
483         1. Load the left pointer of the node pointed to by
484            np into next.
485
486         2. If the result is not NULL,
487                 2a) Set the left pointer to NULL.
488                 2b) Goto 1 if next points to a child of np.
489
490         3. Load the right pointer of the node pointed to by
491            np into next.
492
493         4. If the result is not NULL,
494                 4a) Set the right pointer to NULL.
495                 4b) Goto 1 id next points to a child of np.
496
497         5. Load next with the parent pointer of np.
498
499         6. Free np->rec and np.
500
501         7. Set np to next and goto 1 if it is not null.
502     */
503     struct pat_node *next;
504
505     do {
506         next = ptrs(np)[-1];
507         if (next) {
508             ptrs(np)[-1] = NULL;
509             if (next->bit_ndx > np->bit_ndx) continue;
510         }
511
512         next = ptrs(np)[1];
513         if (next) {
514             ptrs(np)[1] = NULL;
515             if (next->bit_ndx > np->bit_ndx) continue;
516         }
517
518         next = *ptrs(np);
519
520         free_uid_list(np->rec);
521         free(np);
522     } while ((np = next));
523 }
524
525 void free_uid_db(struct uid_db *db)
526 {
527     /*
528       Free all dynamically allocated memory of the uid_db
529       pointed to by db. The structure is not reinitialized.
530     */
531     if (db->pat_root) free_pat_trie(db->pat_root);
532
533     xfree(db->records);
534     xfree(db->num_ndx.records);
535 }
536
537 /**  various public interfaces */
538 void init_uid_db(struct uid_db *db)
539 {
540     /*
541       Initialize the uid_db structure pointed to by db 'properly'
542       such that it represents an empty database. An array of
543       size MIN_RECORDS is allocated and assigned to db->records.
544     */
545     struct num_ndx *num_ndx;
546
547     db->pat_root = NULL;
548
549     db->records = (struct uid_db_record **)xmalloc(MIN_RECORDS * sizeof(*db->records));
550     db->records_max = MIN_RECORDS;
551     db->records_next = 0;
552
553     num_ndx = &db->num_ndx;
554     num_ndx->pos_0_value = num_ndx->end_value = -1;
555     num_ndx->records = NULL;
556 }
557
558 void swap_uid_db_data(struct uid_db *db_0, struct uid_db *db_1)
559 {
560     struct uid_db tmp;
561
562     tmp = *db_0;
563     *db_0 = *db_1;
564     *db_1 = tmp;
565 }
566
567 int traverse_uid_db(struct uid_db *db,
568                      uid_db_traversal_routine *r, void *arg)
569 {
570     /*
571       Traverses the struct uid_db records array in insert order,
572       invoking the subroutine pointed to by r with a pointer to
573       each record and the arg pointer as arguments. If the return
574       value of that is non-zero, traverse_uid_db immediately returns
575       with this value. Otherwise, zero is returned after the last
576       record was visited.
577
578       The uid_db_traversal_routine must not modify the uid_db during
579       traversal.
580     */
581     struct uid_db_record **recs;
582     unsigned ndx, max;
583     int rc;
584
585     rc = 0;
586     ndx = 0;
587     max = db->records_next;
588     recs = db->records;
589     while (ndx < max && (rc = r(recs[ndx], arg)) == 0)
590         ++ndx;
591
592     return rc;
593 }