simplify hcache yet a bit more.
[apps/madmutt.git] / lib-mx / hcache.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 2004 Thomas Glanzmann <sithglan@stud.uni-erlangen.de>
4  * Copyright (C) 2004 Tobias Werth <sitowert@stud.uni-erlangen.de>
5  * Copyright (C) 2004 Brian Fundakowski Feldman <green@FreeBSD.org>
6  *
7  * This file is part of mutt-ng, see http://www.muttng.org/.
8  * It's licensed under the GNU General Public License,
9  * please see the file GPL in the top level source directory.
10  */
11
12 #include <lib-lib/lib-lib.h>
13
14 #ifdef USE_HCACHE
15
16 #if defined(HAVE_QDBM)
17 #include <depot.h>
18 #include <cabin.h>
19 #include <villa.h>
20 #elif defined(HAVE_GDBM)
21 #include <gdbm.h>
22 #endif
23
24 #include <imap/message.h>
25
26 #include "charset.h"
27 #include "mutt.h"
28 #include "hcache.h"
29
30 struct hcache_t {
31 #if defined(HAVE_QDBM)
32     VILLA *db;
33 #elif defined(HAVE_GDBM)
34     GDBM_FILE db;
35 #endif
36     char *folder;
37     unsigned int crc;
38 };
39
40 typedef union {
41   struct timeval timeval;
42   unsigned long uid_validity;
43 } validate;
44
45 #define UPPER4K(i)  ((i & ~(4096 - 1)) + 4096)
46
47 static unsigned char *lazy_malloc(ssize_t siz)
48 {
49     return p_new(unsigned char, UPPER4K(siz));
50 }
51
52 static void lazy_realloc(unsigned char **p, ssize_t siz)
53 {
54     p_realloc(p, UPPER4K(siz));
55 }
56
57 static unsigned char *dump_int (unsigned int i, unsigned char *d, int *off)
58 {
59   lazy_realloc (&d, *off + sizeof (int));
60   memcpy (d + *off, &i, sizeof (int));
61   (*off) += sizeof (int);
62
63   return d;
64 }
65
66 static void restore_int (unsigned int *i, const unsigned char *d, int *off)
67 {
68   memcpy (i, d + *off, sizeof (int));
69   (*off) += sizeof (int);
70 }
71
72 static unsigned char *dump_char (char *c, unsigned char *d, int *off)
73 {
74   unsigned int size;
75
76   if (c == NULL) {
77     size = 0;
78     d = dump_int (size, d, off);
79     return d;
80   }
81
82   size = m_strlen(c) + 1;
83   d = dump_int (size, d, off);
84   lazy_realloc (&d, *off + size);
85   memcpy (d + *off, c, size);
86   *off += size;
87
88   return d;
89 }
90
91 static void restore_char (char **c, const unsigned char *d, int *off)
92 {
93   unsigned int size;
94
95   restore_int (&size, d, off);
96
97   if (size == 0) {
98     *c = NULL;
99     return;
100   }
101
102   *c = p_dup(d + *off, size);
103   *off += size;
104 }
105
106 static unsigned char *dump_address (address_t * a, unsigned char *d, int *off)
107 {
108   unsigned int counter = 0;
109   unsigned int start_off = *off;
110
111   d = dump_int (0xdeadbeef, d, off);
112
113   while (a) {
114     d = dump_char (a->personal, d, off);
115     d = dump_char (a->mailbox, d, off);
116     d = dump_int (a->group, d, off);
117     a = a->next;
118     counter++;
119   }
120
121   memcpy (d + start_off, &counter, sizeof (int));
122
123   return d;
124 }
125
126 static void restore_address (address_t ** a, const unsigned char *d, int *off)
127 {
128   unsigned int counter;
129
130   restore_int (&counter, d, off);
131
132   while (counter) {
133     *a = p_new(address_t, 1);
134     restore_char (&(*a)->personal, d, off);
135     restore_char (&(*a)->mailbox, d, off);
136     restore_int ((unsigned int *) &(*a)->group, d, off);
137     a = &(*a)->next;
138     counter--;
139   }
140
141   *a = NULL;
142 }
143
144 static unsigned char *dump_list (string_list_t * l, unsigned char *d, int *off)
145 {
146   unsigned int counter = 0;
147   unsigned int start_off = *off;
148
149   d = dump_int (0xdeadbeef, d, off);
150
151   while (l) {
152     d = dump_char (l->data, d, off);
153     l = l->next;
154     counter++;
155   }
156
157   memcpy (d + start_off, &counter, sizeof (int));
158
159   return d;
160 }
161
162 static void restore_list (string_list_t ** l, const unsigned char *d, int *off)
163 {
164   unsigned int counter;
165
166   restore_int (&counter, d, off);
167
168   while (counter) {
169     *l = p_new(string_list_t, 1);
170     restore_char (&(*l)->data, d, off);
171     l = &(*l)->next;
172     counter--;
173   }
174
175   *l = NULL;
176 }
177
178 static unsigned char *dump_parameter (parameter_t * p, unsigned char *d,
179                                       int *off)
180 {
181   unsigned int counter = 0;
182   unsigned int start_off = *off;
183
184   d = dump_int (0xdeadbeef, d, off);
185
186   while (p) {
187     d = dump_char (p->attribute, d, off);
188     d = dump_char (p->value, d, off);
189     p = p->next;
190     counter++;
191   }
192
193   memcpy (d + start_off, &counter, sizeof (int));
194
195   return d;
196 }
197
198 static void
199 restore_parameter (parameter_t ** p, const unsigned char *d, int *off)
200 {
201   unsigned int counter;
202
203   restore_int (&counter, d, off);
204
205   while (counter) {
206     *p = parameter_new();
207     restore_char (&(*p)->attribute, d, off);
208     restore_char (&(*p)->value, d, off);
209     p = &(*p)->next;
210     counter--;
211   }
212 }
213
214 static unsigned char *dump_body (BODY * c, unsigned char *d, int *off)
215 {
216   lazy_realloc (&d, *off + sizeof (BODY));
217   memcpy (d + *off, c, sizeof (BODY));
218   *off += sizeof (BODY);
219
220   d = dump_char (c->xtype, d, off);
221   d = dump_char (c->subtype, d, off);
222
223   d = dump_parameter (c->parameter, d, off);
224
225   d = dump_char (c->description, d, off);
226   d = dump_char (c->form_name, d, off);
227   d = dump_char (c->filename, d, off);
228   d = dump_char (c->d_filename, d, off);
229
230   return d;
231 }
232
233 static void restore_body (BODY * c, const unsigned char *d, int *off)
234 {
235   memcpy (c, d + *off, sizeof (BODY));
236   *off += sizeof (BODY);
237
238   restore_char (&c->xtype, d, off);
239   restore_char (&c->subtype, d, off);
240
241   restore_parameter (&c->parameter, d, off);
242
243   restore_char (&c->description, d, off);
244   restore_char (&c->form_name, d, off);
245   restore_char (&c->filename, d, off);
246   restore_char (&c->d_filename, d, off);
247 }
248
249 static unsigned char *dump_envelope (ENVELOPE * e, unsigned char *d, int *off)
250 {
251   d = dump_address (e->return_path, d, off);
252   d = dump_address (e->from, d, off);
253   d = dump_address (e->to, d, off);
254   d = dump_address (e->cc, d, off);
255   d = dump_address (e->bcc, d, off);
256   d = dump_address (e->sender, d, off);
257   d = dump_address (e->reply_to, d, off);
258   d = dump_address (e->mail_followup_to, d, off);
259
260   d = dump_char (e->subject, d, off);
261   if (e->real_subj) {
262     d = dump_int (e->real_subj - e->subject, d, off);
263   }
264   else {
265     d = dump_int (-1, d, off);
266   }
267   d = dump_char (e->message_id, d, off);
268   d = dump_char (e->supersedes, d, off);
269   d = dump_char (e->date, d, off);
270   d = dump_char (e->x_label, d, off);
271   d = dump_char (e->list_post, d, off);
272
273 #ifdef USE_NNTP
274   d = dump_char (e->newsgroups, d, off);
275   d = dump_char (e->xref, d, off);
276   d = dump_char (e->followup_to, d, off);
277   d = dump_char (e->x_comment_to, d, off);
278 #endif
279
280   d = dump_list (e->references, d, off);
281   d = dump_list (e->in_reply_to, d, off);
282   d = dump_list (e->userhdrs, d, off);
283
284   return d;
285 }
286
287 static void restore_envelope (ENVELOPE * e, const unsigned char *d, int *off)
288 {
289   int real_subj_off;
290
291   restore_address (&e->return_path, d, off);
292   restore_address (&e->from, d, off);
293   restore_address (&e->to, d, off);
294   restore_address (&e->cc, d, off);
295   restore_address (&e->bcc, d, off);
296   restore_address (&e->sender, d, off);
297   restore_address (&e->reply_to, d, off);
298   restore_address (&e->mail_followup_to, d, off);
299
300   restore_char (&e->subject, d, off);
301   restore_int ((unsigned int *) (&real_subj_off), d, off);
302   if (0 <= real_subj_off) {
303     e->real_subj = e->subject + real_subj_off;
304   }
305   else {
306     e->real_subj = NULL;
307   }
308   restore_char (&e->message_id, d, off);
309   restore_char (&e->supersedes, d, off);
310   restore_char (&e->date, d, off);
311   restore_char (&e->x_label, d, off);
312   restore_char (&e->list_post, d, off);
313
314 #ifdef USE_NNTP
315   restore_char (&e->newsgroups, d, off);
316   restore_char (&e->xref, d, off);
317   restore_char (&e->followup_to, d, off);
318   restore_char (&e->x_comment_to, d, off);
319 #endif
320
321   restore_list (&e->references, d, off);
322   restore_list (&e->in_reply_to, d, off);
323   restore_list (&e->userhdrs, d, off);
324 }
325
326 static unsigned int crc32(unsigned int crc, const void *src, ssize_t len)
327 {
328     int i;
329     const unsigned char *p = src;
330
331     while (len--) {
332         crc ^= *p++;
333         for (i = 0; i < 8; i++)
334             crc = (crc >> 1) ^ ((crc & 1) ? 0xedb88320 : 0);
335     }
336     return crc;
337 }
338
339 static int generate_crc32(void)
340 {
341     static int crc = 0;
342
343     crc = crc32(crc, "madmutt.2007.05.13", m_strlen("madmutt.2007.05.13"));
344 #ifdef HAVE_LANGINFO_CODESET
345     crc = crc32(crc, MCharset.charset, m_strlen(MCharset.charset));
346     crc = crc32(crc, "HAVE_LANGINFO_CODESET",
347                 m_strlen("HAVE_LANGINFO_CODESET"));
348 #endif
349     crc = crc32(crc, "USE_POP",   m_strlen("USE_POP"));
350     crc = crc32(crc, "MIXMASTER", m_strlen("MIXMASTER"));
351     crc = crc32(crc, "USE_IMAP",  m_strlen("USE_IMAP"));
352 #ifdef USE_NNTP
353     crc = crc32(crc, "USE_NNTP",  m_strlen("USE_NNTP"));
354 #endif
355     return crc;
356 }
357
358 static const char *
359 mutt_hcache_per_folder(const char *path, const char *folder)
360 {
361     static char buf[_POSIX_PATH_MAX];
362     struct stat st;
363     int pos;
364
365     if (stat(path, &st) < 0 || !S_ISDIR(st.st_mode)) {
366         return path;
367     }
368
369     pos  = m_strcpy(buf, sizeof(buf), path);
370     pos += m_strputc(buf + pos, sizeof(buf) - pos, '/');
371
372     while (*folder) {
373         if (isalnum((unsigned char)*folder) || *folder == '.') {
374             pos += m_strputc(buf + pos, sizeof(buf) - pos, *folder);
375         } else {
376             pos += m_strputc(buf + pos, sizeof(buf) - pos, '_');
377         }
378         folder++;
379     }
380     pos += m_strcpy(buf + pos, sizeof(buf) - pos, ".hdb");
381     return buf;
382 }
383
384 /* This function transforms a header into a char so that it is useable by
385  * db_store */
386 static void *mutt_hcache_dump (void *_db, HEADER * h, int *off,
387                                unsigned long uid_validity)
388 {
389   struct hcache_t *db = _db;
390   unsigned char *d = NULL;
391
392   *off = 0;
393
394   d = lazy_malloc (sizeof (validate));
395
396   if (uid_validity) {
397     memcpy (d, &uid_validity, sizeof (unsigned long));
398   }
399   else {
400     struct timeval now;
401
402     gettimeofday (&now, NULL);
403     memcpy (d, &now, sizeof (struct timeval));
404   }
405   *off += sizeof (validate);
406
407   d = dump_int (db->crc, d, off);
408
409   lazy_realloc (&d, *off + sizeof (HEADER));
410   memcpy (d + *off, h, sizeof (HEADER));
411   *off += sizeof (HEADER);
412
413   d = dump_envelope (h->env, d, off);
414   d = dump_body (h->content, d, off);
415   d = dump_char (h->maildir_flags, d, off);
416
417   return d;
418 }
419
420 HEADER *mutt_hcache_restore (const unsigned char *d, HEADER ** oh)
421 {
422   int off = 0;
423   HEADER *h = header_new();
424
425   /* skip validate */
426   off += sizeof (validate);
427
428   /* skip crc */
429   off += sizeof (unsigned int);
430
431   memcpy (h, d + off, sizeof (HEADER));
432   off += sizeof (HEADER);
433
434   h->env = envelope_new();
435   restore_envelope (h->env, d, &off);
436
437   h->content = body_new();
438   restore_body (h->content, d, &off);
439
440   restore_char (&h->maildir_flags, d, &off);
441
442   /* this is needed for maildir style mailboxes */
443   if (oh) {
444     h->old = (*oh)->old;
445     h->path = m_strdup((*oh)->path);
446     header_delete(oh);
447   }
448
449   return h;
450 }
451
452 hcache_t *mutt_hcache_open(const char *path, const char *folder)
453 {
454     hcache_t *h = p_new(hcache_t, 1);
455
456     h->folder = m_strdup(folder);
457     h->crc = generate_crc32();
458
459     if (m_strisempty(path)) {
460         p_delete(&h->folder);
461         p_delete(&h);
462         return NULL;
463     }
464
465     path = mutt_hcache_per_folder(path, folder);
466
467     {
468 #if defined(HAVE_QDBM)
469         int flags = VL_OWRITER | VL_OCREAT;
470         if (option(OPTHCACHECOMPRESS))
471             flags |= VL_OZCOMP;
472
473         h->db = vlopen(path, flags, VL_CMPLEX);
474 #elif defined(HAVE_GDBM)
475         int pagesize = atoi(HeaderCachePageSize) ?: 16384;
476
477         h->db = gdbm_open((char *) path, pagesize, GDBM_WRCREAT, 00600, NULL);
478 #endif
479     }
480
481     if (!h->db) {
482         p_delete(&h->folder);
483         p_delete(&h);
484     }
485     return h;
486 }
487
488 void mutt_hcache_close(hcache_t **db)
489 {
490     if (!*db)
491         return;
492
493 #if defined(HAVE_QDBM)
494     vlclose((*db)->db);
495 #elif defined(HAVE_GDBM)
496     gdbm_close((*db)->db);
497 #endif
498
499     p_delete(&(*db)->folder);
500     p_delete(db);
501 }
502
503 void *mutt_hcache_fetch(hcache_t *db, const char *filename,
504                         ssize_t (*keylen)(const char *fn))
505 {
506     char path[_POSIX_PATH_MAX];
507     void *data = NULL;
508
509     if (!db)
510         return NULL;
511
512     snprintf(path, sizeof(path), "%s%s", db->folder, filename);
513
514     {
515 #if defined(HAVE_QDBM)
516         int ksize = strlen(db->folder) + keylen(path + strlen(db->folder));
517         data  = vlget(db->db, path, ksize, NULL);
518 #elif defined(HAVE_GDBM)
519         datum k = { .dptr = path, .dsize = keylen(path) };
520
521         data = gdbm_fetch(db->db, k).dtpr;
522 #endif
523     }
524
525     if (data) {
526         int off = sizeof(validate);
527         unsigned crc = 0;
528
529         restore_int(&crc, data, &off);
530         if (crc != db->crc)
531             p_delete(&data);
532     }
533
534     return data;
535 }
536
537 int mutt_hcache_store(hcache_t *db, const char *filename, HEADER *header,
538                       unsigned long uid_validity,
539                       ssize_t (*keylen)(const char *fn))
540 {
541     char path[_POSIX_PATH_MAX];
542     int ret, dsize;
543     void *data = NULL;
544
545     if (!db)
546         return -1;
547
548     snprintf(path, sizeof(path), "%s%s", db->folder, filename);
549     data = mutt_hcache_dump(db, header, &dsize, uid_validity);
550
551     {
552 #if defined(HAVE_QDBM)
553         int ksize = strlen(db->folder) + keylen(path + strlen(db->folder));
554
555         ret = vlput(db->db, path, ksize, data, dsize, VL_DOVER);
556 #elif defined(HAVE_GDBM)
557         datum k = { .dptr = path, .dsize = keylen(path) };
558         datum v = { .dptr = data, .dsize = dsize };
559
560         ret = gdbm_store (db->db, k, v, GDBM_REPLACE);
561 #endif
562     }
563
564     p_delete(&data);
565     return ret;
566 }
567
568 int mutt_hcache_delete(hcache_t *db, const char *filename,
569                        ssize_t (*keylen)(const char *fn))
570 {
571     char path[_POSIX_PATH_MAX];
572
573     if (!db)
574         return -1;
575
576     snprintf(path, sizeof(path), "%s%s", db->folder, filename);
577
578     {
579 #if defined(HAVE_QDBM)
580         int ksize = strlen(db->folder) + keylen(path + strlen(db->folder));
581         return vlout(db->db, path, ksize);
582 #elif defined(HAVE_GDBM)
583         datum k = { .dptr = path, .dsize = keylen(path) };
584         return gdbm_delete(db->db, k);
585 #endif
586     }
587 }
588
589 #endif /* USE_HCACHE */