]> git.llucax.com Git - software/mutt-debian.git/blob - hcache.c
release 1.5.20-4 w/out sidebar-newmail patch
[software/mutt-debian.git] / hcache.c
1 /*
2  * Copyright (C) 2004 Thomas Glanzmann <sithglan@stud.uni-erlangen.de>
3  * Copyright (C) 2004 Tobias Werth <sitowert@stud.uni-erlangen.de>
4  * Copyright (C) 2004 Brian Fundakowski Feldman <green@FreeBSD.org>
5  *
6  *     This program is free software; you can redistribute it and/or modify
7  *     it under the terms of the GNU General Public License as published by
8  *     the Free Software Foundation; either version 2 of the License, or
9  *     (at your option) any later version.
10  *
11  *     This program is distributed in the hope that it will be useful,
12  *     but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *     GNU General Public License for more details.
15  *
16  *     You should have received a copy of the GNU General Public License
17  *     along with this program; if not, write to the Free Software
18  *     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19  */
20
21 #if HAVE_CONFIG_H
22 #include "config.h"
23 #endif                          /* HAVE_CONFIG_H */
24
25 #if HAVE_QDBM
26 #include <depot.h>
27 #include <cabin.h>
28 #include <villa.h>
29 #elif HAVE_TC
30 #include <tcbdb.h>
31 #elif HAVE_GDBM
32 #include <gdbm.h>
33 #elif HAVE_DB4
34 #include <db.h>
35 #endif
36
37 #include <errno.h>
38 #include <fcntl.h>
39 #if HAVE_SYS_TIME_H
40 #include <sys/time.h>
41 #endif
42 #include "mutt.h"
43 #include "hcache.h"
44 #include "hcversion.h"
45 #include "mx.h"
46 #include "lib.h"
47 #include "md5.h"
48
49 #if HAVE_QDBM
50 static struct header_cache
51 {
52   VILLA *db;
53   char *folder;
54   unsigned int crc;
55 } HEADER_CACHE;
56 #elif HAVE_TC
57 static struct header_cache
58 {
59   TCBDB *db;
60   char *folder;
61   unsigned int crc;
62 } HEADER_CACHE;
63 #elif HAVE_GDBM
64 static struct header_cache
65 {
66   GDBM_FILE db;
67   char *folder;
68   unsigned int crc;
69 } HEADER_CACHE;
70 #elif HAVE_DB4
71 static struct header_cache
72 {
73   DB_ENV *env;
74   DB *db;
75   char *folder;
76   unsigned int crc;
77   int fd;
78   char lockfile[_POSIX_PATH_MAX];
79 } HEADER_CACHE;
80
81 static void mutt_hcache_dbt_init(DBT * dbt, void *data, size_t len);
82 static void mutt_hcache_dbt_empty_init(DBT * dbt);
83 #endif
84
85 typedef union
86 {
87   struct timeval timeval;
88   unsigned long uid_validity;
89 } validate;
90
91 static void *
92 lazy_malloc(size_t siz)
93 {
94   if (0 < siz && siz < 4096)
95     siz = 4096;
96
97   return safe_malloc(siz);
98 }
99
100 static void
101 lazy_realloc(void *ptr, size_t siz)
102 {
103   void **p = (void **) ptr;
104
105   if (p != NULL && 0 < siz && siz < 4096)
106     return;
107
108   safe_realloc(ptr, siz);
109 }
110
111 static unsigned char *
112 dump_int(unsigned int i, unsigned char *d, int *off)
113 {
114   lazy_realloc(&d, *off + sizeof (int));
115   memcpy(d + *off, &i, sizeof (int));
116   (*off) += sizeof (int);
117
118   return d;
119 }
120
121 static void
122 restore_int(unsigned int *i, const unsigned char *d, int *off)
123 {
124   memcpy(i, d + *off, sizeof (int));
125   (*off) += sizeof (int);
126 }
127
128 static inline int is_ascii (const char *p, size_t len) {
129   register const char *s = p;
130   while (s && (unsigned) (s - p) < len) {
131     if ((*s & 0x80) != 0)
132       return 0;
133     s++;
134   }
135   return 1;
136 }
137
138 static unsigned char *
139 dump_char_size(char *c, unsigned char *d, int *off, ssize_t size, int convert)
140 {
141   char *p = c;
142
143   if (c == NULL)
144   {
145     size = 0;
146     d = dump_int(size, d, off);
147     return d;
148   }
149
150   if (convert && !is_ascii (c, size)) {
151     p = mutt_substrdup (c, c + size);
152     if (mutt_convert_string (&p, Charset, "utf-8", 0) == 0) {
153       c = p;
154       size = mutt_strlen (c) + 1;
155     }
156   }
157
158   d = dump_int(size, d, off);
159   lazy_realloc(&d, *off + size);
160   memcpy(d + *off, p, size);
161   *off += size;
162
163   if (p != c)
164     FREE(&p);
165
166   return d;
167 }
168
169 static unsigned char *
170 dump_char(char *c, unsigned char *d, int *off, int convert)
171 {
172   return dump_char_size (c, d, off, mutt_strlen (c) + 1, convert);
173 }
174
175 static void
176 restore_char(char **c, const unsigned char *d, int *off, int convert)
177 {
178   unsigned int size;
179   restore_int(&size, d, off);
180
181   if (size == 0)
182   {
183     *c = NULL;
184     return;
185   }
186
187   *c = safe_malloc(size);
188   memcpy(*c, d + *off, size);
189   if (convert && !is_ascii (*c, size)) {
190     char *tmp = safe_strdup (*c);
191     if (mutt_convert_string (&tmp, "utf-8", Charset, 0) == 0) {
192       mutt_str_replace (c, tmp);
193     } else {
194       FREE(&tmp);
195     }
196   }
197   *off += size;
198 }
199
200 static unsigned char *
201 dump_address(ADDRESS * a, unsigned char *d, int *off, int convert)
202 {
203   unsigned int counter = 0;
204   unsigned int start_off = *off;
205
206   d = dump_int(0xdeadbeef, d, off);
207
208   while (a)
209   {
210 #ifdef EXACT_ADDRESS
211     d = dump_char(a->val, d, off, convert);
212 #endif
213     d = dump_char(a->personal, d, off, convert);
214     d = dump_char(a->mailbox, d, off, 0);
215     d = dump_int(a->group, d, off);
216     a = a->next;
217     counter++;
218   }
219
220   memcpy(d + start_off, &counter, sizeof (int));
221
222   return d;
223 }
224
225 static void
226 restore_address(ADDRESS ** a, const unsigned char *d, int *off, int convert)
227 {
228   unsigned int counter;
229
230   restore_int(&counter, d, off);
231
232   while (counter)
233   {
234     *a = safe_malloc(sizeof (ADDRESS));
235 #ifdef EXACT_ADDRESS
236     restore_char(&(*a)->val, d, off, convert);
237 #endif
238     restore_char(&(*a)->personal, d, off, convert);
239     restore_char(&(*a)->mailbox, d, off, 0);
240     restore_int((unsigned int *) &(*a)->group, d, off);
241     a = &(*a)->next;
242     counter--;
243   }
244
245   *a = NULL;
246 }
247
248 static unsigned char *
249 dump_list(LIST * l, unsigned char *d, int *off, int convert)
250 {
251   unsigned int counter = 0;
252   unsigned int start_off = *off;
253
254   d = dump_int(0xdeadbeef, d, off);
255
256   while (l)
257   {
258     d = dump_char(l->data, d, off, convert);
259     l = l->next;
260     counter++;
261   }
262
263   memcpy(d + start_off, &counter, sizeof (int));
264
265   return d;
266 }
267
268 static void
269 restore_list(LIST ** l, const unsigned char *d, int *off, int convert)
270 {
271   unsigned int counter;
272
273   restore_int(&counter, d, off);
274
275   while (counter)
276   {
277     *l = safe_malloc(sizeof (LIST));
278     restore_char(&(*l)->data, d, off, convert);
279     l = &(*l)->next;
280     counter--;
281   }
282
283   *l = NULL;
284 }
285
286 static unsigned char *
287 dump_buffer(BUFFER * b, unsigned char *d, int *off, int convert)
288 {
289   if (!b)
290   {
291     d = dump_int(0, d, off);
292     return d;
293   }
294   else
295     d = dump_int(1, d, off);
296
297   d = dump_char_size(b->data, d, off, b->dsize + 1, convert);
298   d = dump_int(b->dptr - b->data, d, off);
299   d = dump_int(b->dsize, d, off);
300   d = dump_int(b->destroy, d, off);
301
302   return d;
303 }
304
305 static void
306 restore_buffer(BUFFER ** b, const unsigned char *d, int *off, int convert)
307 {
308   unsigned int used;
309   unsigned int offset;
310   restore_int(&used, d, off);
311   if (!used)
312   {
313     return;
314   }
315
316   *b = safe_malloc(sizeof (BUFFER));
317
318   restore_char(&(*b)->data, d, off, convert);
319   restore_int(&offset, d, off);
320   (*b)->dptr = (*b)->data + offset;
321   restore_int (&used, d, off);
322   (*b)->dsize = used;
323   restore_int (&used, d, off);
324   (*b)->destroy = used;
325 }
326
327 static unsigned char *
328 dump_parameter(PARAMETER * p, unsigned char *d, int *off, int convert)
329 {
330   unsigned int counter = 0;
331   unsigned int start_off = *off;
332
333   d = dump_int(0xdeadbeef, d, off);
334
335   while (p)
336   {
337     d = dump_char(p->attribute, d, off, 0);
338     d = dump_char(p->value, d, off, convert);
339     p = p->next;
340     counter++;
341   }
342
343   memcpy(d + start_off, &counter, sizeof (int));
344
345   return d;
346 }
347
348 static void
349 restore_parameter(PARAMETER ** p, const unsigned char *d, int *off, int convert)
350 {
351   unsigned int counter;
352
353   restore_int(&counter, d, off);
354
355   while (counter)
356   {
357     *p = safe_malloc(sizeof (PARAMETER));
358     restore_char(&(*p)->attribute, d, off, 0);
359     restore_char(&(*p)->value, d, off, convert);
360     p = &(*p)->next;
361     counter--;
362   }
363
364   *p = NULL;
365 }
366
367 static unsigned char *
368 dump_body(BODY * c, unsigned char *d, int *off, int convert)
369 {
370   BODY nb;
371
372   memcpy (&nb, c, sizeof (BODY));
373
374   /* some fields are not safe to cache */
375   nb.content = NULL;
376   nb.charset = NULL;
377   nb.next = NULL;
378   nb.parts = NULL;
379   nb.hdr = NULL;
380   nb.aptr = NULL;
381
382   lazy_realloc(&d, *off + sizeof (BODY));
383   memcpy(d + *off, &nb, sizeof (BODY));
384   *off += sizeof (BODY);
385
386   d = dump_char(nb.xtype, d, off, 0);
387   d = dump_char(nb.subtype, d, off, 0);
388
389   d = dump_parameter(nb.parameter, d, off, convert);
390
391   d = dump_char(nb.description, d, off, convert);
392   d = dump_char(nb.form_name, d, off, convert);
393   d = dump_char(nb.filename, d, off, convert);
394   d = dump_char(nb.d_filename, d, off, convert);
395
396   return d;
397 }
398
399 static void
400 restore_body(BODY * c, const unsigned char *d, int *off, int convert)
401 {
402   memcpy(c, d + *off, sizeof (BODY));
403   *off += sizeof (BODY);
404
405   restore_char(&c->xtype, d, off, 0);
406   restore_char(&c->subtype, d, off, 0);
407
408   restore_parameter(&c->parameter, d, off, convert);
409
410   restore_char(&c->description, d, off, convert);
411   restore_char(&c->form_name, d, off, convert);
412   restore_char(&c->filename, d, off, convert);
413   restore_char(&c->d_filename, d, off, convert);
414 }
415
416 static unsigned char *
417 dump_envelope(ENVELOPE * e, unsigned char *d, int *off, int convert)
418 {
419   d = dump_address(e->return_path, d, off, convert);
420   d = dump_address(e->from, d, off, convert);
421   d = dump_address(e->to, d, off, convert);
422   d = dump_address(e->cc, d, off, convert);
423   d = dump_address(e->bcc, d, off, convert);
424   d = dump_address(e->sender, d, off, convert);
425   d = dump_address(e->reply_to, d, off, convert);
426   d = dump_address(e->mail_followup_to, d, off, convert);
427
428   d = dump_char(e->list_post, d, off, convert);
429   d = dump_char(e->subject, d, off, convert);
430
431   if (e->real_subj)
432     d = dump_int(e->real_subj - e->subject, d, off);
433   else
434     d = dump_int(-1, d, off);
435
436   d = dump_char(e->message_id, d, off, 0);
437   d = dump_char(e->supersedes, d, off, 0);
438   d = dump_char(e->date, d, off, 0);
439   d = dump_char(e->x_label, d, off, convert);
440
441   d = dump_buffer(e->spam, d, off, convert);
442
443   d = dump_list(e->references, d, off, 0);
444   d = dump_list(e->in_reply_to, d, off, 0);
445   d = dump_list(e->userhdrs, d, off, convert);
446
447   return d;
448 }
449
450 static void
451 restore_envelope(ENVELOPE * e, const unsigned char *d, int *off, int convert)
452 {
453   int real_subj_off;
454
455   restore_address(&e->return_path, d, off, convert);
456   restore_address(&e->from, d, off, convert);
457   restore_address(&e->to, d, off, convert);
458   restore_address(&e->cc, d, off, convert);
459   restore_address(&e->bcc, d, off, convert);
460   restore_address(&e->sender, d, off, convert);
461   restore_address(&e->reply_to, d, off, convert);
462   restore_address(&e->mail_followup_to, d, off, convert);
463
464   restore_char(&e->list_post, d, off, convert);
465   restore_char(&e->subject, d, off, convert);
466   restore_int((unsigned int *) (&real_subj_off), d, off);
467
468   if (0 <= real_subj_off)
469     e->real_subj = e->subject + real_subj_off;
470   else
471     e->real_subj = NULL;
472
473   restore_char(&e->message_id, d, off, 0);
474   restore_char(&e->supersedes, d, off, 0);
475   restore_char(&e->date, d, off, 0);
476   restore_char(&e->x_label, d, off, convert);
477
478   restore_buffer(&e->spam, d, off, convert);
479
480   restore_list(&e->references, d, off, 0);
481   restore_list(&e->in_reply_to, d, off, 0);
482   restore_list(&e->userhdrs, d, off, convert);
483 }
484
485 static int
486 crc_matches(const char *d, unsigned int crc)
487 {
488   int off = sizeof (validate);
489   unsigned int mycrc = 0;
490
491   if (!d)
492     return 0;
493
494   restore_int(&mycrc, (unsigned char *) d, &off);
495
496   return (crc == mycrc);
497 }
498
499 /* Append md5sumed folder to path if path is a directory. */
500 static const char *
501 mutt_hcache_per_folder(const char *path, const char *folder,
502                        hcache_namer_t namer)
503 {
504   static char hcpath[_POSIX_PATH_MAX];
505   struct stat sb;
506   unsigned char md5sum[16];
507   char* s;
508   int ret, plen;
509 #ifndef HAVE_ICONV
510   const char *chs = Charset && *Charset ? Charset : 
511                     mutt_get_default_charset ();
512 #endif
513
514   plen = mutt_strlen (path);
515
516   ret = stat(path, &sb);
517   if (ret < 0 && path[plen-1] != '/')
518   {
519 #ifdef HAVE_ICONV
520     return path;
521 #else
522     snprintf (hcpath, _POSIX_PATH_MAX, "%s-%s", path, chs);
523     return hcpath;
524 #endif
525   }
526
527   if (ret >= 0 && !S_ISDIR(sb.st_mode))
528   {
529 #ifdef HAVE_ICONV
530     return path;
531 #else
532     snprintf (hcpath, _POSIX_PATH_MAX, "%s-%s", path, chs);
533     return hcpath;
534 #endif
535   }
536
537   if (namer)
538   {
539     snprintf (hcpath, sizeof (hcpath), "%s%s", path,
540               path[plen-1] == '/' ? "" : "/");
541     if (path[plen-1] != '/')
542       plen++;
543
544     ret = namer (folder, hcpath + plen, sizeof (hcpath) - plen);
545   }
546   else
547   {
548     md5_buffer (folder, strlen (folder), &md5sum);
549
550     ret = snprintf(hcpath, _POSIX_PATH_MAX,
551                    "%s/%02x%02x%02x%02x%02x%02x%02x%02x"
552                    "%02x%02x%02x%02x%02x%02x%02x%02x"
553 #ifndef HAVE_ICONV
554                    "-%s"
555 #endif
556                    ,
557                    path, md5sum[0], md5sum[1], md5sum[2], md5sum[3],
558                    md5sum[4], md5sum[5], md5sum[6], md5sum[7], md5sum[8],
559                    md5sum[9], md5sum[10], md5sum[11], md5sum[12],
560                    md5sum[13], md5sum[14], md5sum[15]
561 #ifndef HAVE_ICONV
562                    ,chs
563 #endif
564                    );
565   }
566   
567   if (ret <= 0)
568     return path;
569
570   if (stat (hcpath, &sb) >= 0)
571     return hcpath;
572
573   s = strchr (hcpath + 1, '/');
574   while (s)
575   {
576     /* create missing path components */
577     *s = '\0';
578     if (stat (hcpath, &sb) < 0 && (errno != ENOENT || mkdir (hcpath, 0777) < 0))
579       return path;
580     *s = '/';
581     s = strchr (s + 1, '/');
582   }
583
584   return hcpath;
585 }
586
587 /* This function transforms a header into a char so that it is useable by
588  * db_store */
589 static void *
590 mutt_hcache_dump(header_cache_t *h, HEADER * header, int *off,
591                  unsigned long uid_validity)
592 {
593   unsigned char *d = NULL;
594   HEADER nh;
595   int convert = !Charset_is_utf8;
596
597   *off = 0;
598   d = lazy_malloc(sizeof (validate));
599
600   if (uid_validity)
601     memcpy(d, &uid_validity, sizeof (unsigned long));
602   else
603   {
604     struct timeval now;
605     gettimeofday(&now, NULL);
606     memcpy(d, &now, sizeof (struct timeval));
607   }
608   *off += sizeof (validate);
609
610   d = dump_int(h->crc, d, off);
611
612   lazy_realloc(&d, *off + sizeof (HEADER));
613   memcpy(&nh, header, sizeof (HEADER));
614
615   /* some fields are not safe to cache */
616   nh.tagged = 0;
617   nh.changed = 0;
618   nh.threaded = 0;
619   nh.recip_valid = 0;
620   nh.searched = 0;
621   nh.matched = 0;
622   nh.collapsed = 0;
623   nh.limited = 0;
624   nh.num_hidden = 0;
625   nh.recipient = 0;
626   nh.attach_valid = 0;
627   nh.path = NULL;
628   nh.tree = NULL;
629   nh.thread = NULL;
630 #ifdef MIXMASTER
631   nh.chain = NULL;
632 #endif
633 #if defined USE_POP || defined USE_IMAP
634   nh.data = NULL;
635 #endif
636
637   memcpy(d + *off, &nh, sizeof (HEADER));
638   *off += sizeof (HEADER);
639
640   d = dump_envelope(nh.env, d, off, convert);
641   d = dump_body(nh.content, d, off, convert);
642   d = dump_char(nh.maildir_flags, d, off, convert);
643
644   return d;
645 }
646
647 HEADER *
648 mutt_hcache_restore(const unsigned char *d, HEADER ** oh)
649 {
650   int off = 0;
651   HEADER *h = mutt_new_header();
652   int convert = !Charset_is_utf8;
653
654   /* skip validate */
655   off += sizeof (validate);
656
657   /* skip crc */
658   off += sizeof (unsigned int);
659
660   memcpy(h, d + off, sizeof (HEADER));
661   off += sizeof (HEADER);
662
663   h->env = mutt_new_envelope();
664   restore_envelope(h->env, d, &off, convert);
665
666   h->content = mutt_new_body();
667   restore_body(h->content, d, &off, convert);
668
669   restore_char(&h->maildir_flags, d, &off, convert);
670
671   /* this is needed for maildir style mailboxes */
672   if (oh)
673   {
674     h->old = (*oh)->old;
675     h->path = safe_strdup((*oh)->path);
676     mutt_free_header(oh);
677   }
678
679   return h;
680 }
681
682 void *
683 mutt_hcache_fetch(header_cache_t *h, const char *filename,
684                   size_t(*keylen) (const char *fn))
685 {
686   void* data;
687
688   data = mutt_hcache_fetch_raw (h, filename, keylen);
689
690   if (!data || !crc_matches(data, h->crc))
691   {
692     FREE(&data);
693     return NULL;
694   }
695   
696   return data;
697 }
698
699 void *
700 mutt_hcache_fetch_raw (header_cache_t *h, const char *filename,
701                        size_t(*keylen) (const char *fn))
702 {
703 #ifndef HAVE_DB4
704   char path[_POSIX_PATH_MAX];
705   int ksize;
706 #endif
707 #ifdef HAVE_QDBM
708   char *data = NULL;
709 #elif HAVE_TC
710   void *data;
711   int sp;
712 #elif HAVE_GDBM
713   datum key;
714   datum data;
715 #elif HAVE_DB4
716   DBT key;
717   DBT data;
718 #endif
719   
720   if (!h)
721     return NULL;
722   
723 #ifdef HAVE_DB4
724   if (filename[0] == '/')
725     filename++;
726
727   mutt_hcache_dbt_init(&key, (void *) filename, keylen(filename));
728   mutt_hcache_dbt_empty_init(&data);
729   data.flags = DB_DBT_MALLOC;
730   
731   h->db->get(h->db, NULL, &key, &data, 0);
732   
733   return data.data;
734 #else
735   strncpy(path, h->folder, sizeof (path));
736   safe_strcat(path, sizeof (path), filename);
737
738   ksize = strlen (h->folder) + keylen (path + strlen (h->folder));  
739 #endif
740 #ifdef HAVE_QDBM
741   data = vlget(h->db, path, ksize, NULL);
742   
743   return data;
744 #elif HAVE_TC
745   data = tcbdbget(h->db, path, ksize, &sp);
746
747   return data;
748 #elif HAVE_GDBM
749   key.dptr = path;
750   key.dsize = ksize;
751   
752   data = gdbm_fetch(h->db, key);
753   
754   return data.dptr;
755 #endif
756 }
757
758 int
759 mutt_hcache_store(header_cache_t *h, const char *filename, HEADER * header,
760                   unsigned long uid_validity,
761                   size_t(*keylen) (const char *fn))
762 {
763   char* data;
764   int dlen;
765   int ret;
766   
767   if (!h)
768     return -1;
769   
770   data = mutt_hcache_dump(h, header, &dlen, uid_validity);
771   ret = mutt_hcache_store_raw (h, filename, data, dlen, keylen);
772   
773   FREE(&data);
774   
775   return ret;
776 }
777
778 int
779 mutt_hcache_store_raw (header_cache_t* h, const char* filename, void* data,
780                        size_t dlen, size_t(*keylen) (const char* fn))
781 {
782 #ifndef HAVE_DB4
783   char path[_POSIX_PATH_MAX];
784   int ksize;
785 #endif
786 #if HAVE_GDBM
787   datum key;
788   datum databuf;
789 #elif HAVE_DB4
790   DBT key;
791   DBT databuf;
792 #endif
793   
794   if (!h)
795     return -1;
796
797 #if HAVE_DB4
798   if (filename[0] == '/')
799     filename++;
800   
801   mutt_hcache_dbt_init(&key, (void *) filename, keylen(filename));
802   
803   mutt_hcache_dbt_empty_init(&databuf);
804   databuf.flags = DB_DBT_USERMEM;
805   databuf.data = data;
806   databuf.size = dlen;
807   databuf.ulen = dlen;
808   
809   return h->db->put(h->db, NULL, &key, &databuf, 0);
810 #else
811   strncpy(path, h->folder, sizeof (path));
812   safe_strcat(path, sizeof (path), filename);
813
814   ksize = strlen(h->folder) + keylen(path + strlen(h->folder));
815 #endif
816 #if HAVE_QDBM
817   return vlput(h->db, path, ksize, data, dlen, VL_DOVER);
818 #elif HAVE_TC
819   return tcbdbput(h->db, path, ksize, data, dlen);
820 #elif HAVE_GDBM
821   key.dptr = path;
822   key.dsize = ksize;
823   
824   databuf.dsize = dlen;
825   databuf.dptr = data;
826   
827   return gdbm_store(h->db, key, databuf, GDBM_REPLACE);
828 #endif
829 }
830
831 static char* get_foldername(const char *folder) {
832   char *p = NULL;
833   struct stat st;
834
835   /* if the folder is local, canonify the path to avoid
836    * to ensure equivalent paths share the hcache */
837   if (stat (folder, &st) == 0)
838   {
839     p = safe_malloc (PATH_MAX+1);
840     if (!realpath (folder, p))
841       mutt_str_replace (&p, folder);
842   } else
843     p = safe_strdup (folder);
844
845   return p;
846 }
847
848 #if HAVE_QDBM
849 static int
850 hcache_open_qdbm (struct header_cache* h, const char* path)
851 {
852   int    flags = VL_OWRITER | VL_OCREAT;
853
854   if (option(OPTHCACHECOMPRESS))
855     flags |= VL_OZCOMP;
856
857   h->db = vlopen (path, flags, VL_CMPLEX);
858   if (h->db)
859     return 0;
860   else
861     return -1;
862 }
863
864 void
865 mutt_hcache_close(header_cache_t *h)
866 {
867   if (!h)
868     return;
869
870   vlclose(h->db);
871   FREE(&h->folder);
872   FREE(&h);
873 }
874
875 int
876 mutt_hcache_delete(header_cache_t *h, const char *filename,
877                    size_t(*keylen) (const char *fn))
878 {
879   char path[_POSIX_PATH_MAX];
880   int ksize;
881
882   if (!h)
883     return -1;
884
885   strncpy(path, h->folder, sizeof (path));
886   safe_strcat(path, sizeof (path), filename);
887
888   ksize = strlen(h->folder) + keylen(path + strlen(h->folder));
889
890   return vlout(h->db, path, ksize);
891 }
892
893 #elif HAVE_TC
894 static int
895 hcache_open_tc (struct header_cache* h, const char* path)
896 {
897   h->db = tcbdbnew();
898   if (option(OPTHCACHECOMPRESS))
899     tcbdbtune(h->db, 0, 0, 0, -1, -1, BDBTDEFLATE);
900   if (tcbdbopen(h->db, path, BDBOWRITER | BDBOCREAT))
901     return 0;
902   else
903   {
904     tcbdbdel(h->db);
905     return -1;
906   }
907 }
908
909 void
910 mutt_hcache_close(header_cache_t *h)
911 {
912   if (!h)
913     return;
914
915   tcbdbclose(h->db);
916   tcbdbdel(h->db);
917   FREE(&h->folder);
918   FREE(&h);
919 }
920
921 int
922 mutt_hcache_delete(header_cache_t *h, const char *filename,
923                    size_t(*keylen) (const char *fn))
924 {
925   char path[_POSIX_PATH_MAX];
926   int ksize;
927
928   if (!h)
929     return -1;
930
931   strncpy(path, h->folder, sizeof (path));
932   safe_strcat(path, sizeof (path), filename);
933
934   ksize = strlen(h->folder) + keylen(path + strlen(h->folder));
935
936   return tcbdbout(h->db, path, ksize);
937 }
938
939 #elif HAVE_GDBM
940 static int
941 hcache_open_gdbm (struct header_cache* h, const char* path)
942 {
943   int pagesize;
944
945   if (mutt_atoi (HeaderCachePageSize, &pagesize) < 0 || pagesize <= 0)
946     pagesize = 16384;
947
948   h->db = gdbm_open((char *) path, pagesize, GDBM_WRCREAT, 00600, NULL);
949   if (h->db)
950     return 0;
951
952   /* if rw failed try ro */
953   h->db = gdbm_open((char *) path, pagesize, GDBM_READER, 00600, NULL);
954   if (h->db)
955     return 0;
956
957   return -1;
958 }
959
960 void
961 mutt_hcache_close(header_cache_t *h)
962 {
963   if (!h)
964     return;
965
966   gdbm_close(h->db);
967   FREE(&h->folder);
968   FREE(&h);
969 }
970
971 int
972 mutt_hcache_delete(header_cache_t *h, const char *filename,
973                    size_t(*keylen) (const char *fn))
974 {
975   datum key;
976   char path[_POSIX_PATH_MAX];
977
978   if (!h)
979     return -1;
980
981   strncpy(path, h->folder, sizeof (path));
982   safe_strcat(path, sizeof (path), filename);
983
984   key.dptr = path;
985   key.dsize = strlen(h->folder) + keylen(path + strlen(h->folder));
986
987   return gdbm_delete(h->db, key);
988 }
989 #elif HAVE_DB4
990
991 static void
992 mutt_hcache_dbt_init(DBT * dbt, void *data, size_t len)
993 {
994   dbt->data = data;
995   dbt->size = dbt->ulen = len;
996   dbt->dlen = dbt->doff = 0;
997   dbt->flags = DB_DBT_USERMEM;
998 }
999
1000 static void
1001 mutt_hcache_dbt_empty_init(DBT * dbt)
1002 {
1003   dbt->data = NULL;
1004   dbt->size = dbt->ulen = dbt->dlen = dbt->doff = 0;
1005   dbt->flags = 0;
1006 }
1007
1008 static int
1009 hcache_open_db4 (struct header_cache* h, const char* path)
1010 {
1011   struct stat sb;
1012   int ret;
1013   u_int32_t createflags = DB_CREATE;
1014   int pagesize;
1015
1016   if (mutt_atoi (HeaderCachePageSize, &pagesize) < 0 || pagesize <= 0)
1017     pagesize = 16384;
1018
1019   snprintf (h->lockfile, _POSIX_PATH_MAX, "%s-lock-hack", path);
1020
1021   h->fd = open (h->lockfile, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
1022   if (h->fd < 0)
1023     return -1;
1024
1025   if (mx_lock_file (h->lockfile, h->fd, 1, 0, 5))
1026     goto fail_close;
1027
1028   ret = db_env_create (&h->env, 0);
1029   if (ret)
1030     goto fail_unlock;
1031
1032   ret = (*h->env->open)(h->env, NULL, DB_INIT_MPOOL | DB_CREATE | DB_PRIVATE,
1033         0600);
1034   if (ret)
1035     goto fail_env;
1036
1037   ret = db_create (&h->db, h->env, 0);
1038   if (ret)
1039     goto fail_env;
1040
1041   if (stat(path, &sb) != 0 && errno == ENOENT)
1042   {
1043     createflags |= DB_EXCL;
1044     h->db->set_pagesize(h->db, pagesize);
1045   }
1046
1047   ret = (*h->db->open)(h->db, NULL, path, h->folder, DB_BTREE, createflags,
1048                        0600);
1049   if (ret)
1050     goto fail_db;
1051
1052   return 0;
1053
1054   fail_db:
1055   h->db->close (h->db, 0);
1056   fail_env:
1057   h->env->close (h->env, 0);
1058   fail_unlock:
1059   mx_unlock_file (h->lockfile, h->fd, 0);
1060   fail_close:
1061   close (h->fd);
1062   unlink (h->lockfile);
1063
1064   return -1;
1065 }
1066
1067 void
1068 mutt_hcache_close(header_cache_t *h)
1069 {
1070   if (!h)
1071     return;
1072
1073   h->db->close (h->db, 0);
1074   h->env->close (h->env, 0);
1075   mx_unlock_file (h->lockfile, h->fd, 0);
1076   close (h->fd);
1077   unlink (h->lockfile);
1078   FREE (&h->folder);
1079   FREE (&h);
1080 }
1081
1082 int
1083 mutt_hcache_delete(header_cache_t *h, const char *filename,
1084                    size_t(*keylen) (const char *fn))
1085 {
1086   DBT key;
1087
1088   if (!h)
1089     return -1;
1090
1091   if (filename[0] == '/')
1092     filename++;
1093
1094   mutt_hcache_dbt_init(&key, (void *) filename, keylen(filename));
1095   return h->db->del(h->db, NULL, &key, 0);
1096 }
1097 #endif
1098
1099 header_cache_t *
1100 mutt_hcache_open(const char *path, const char *folder, hcache_namer_t namer)
1101 {
1102   struct header_cache *h = safe_calloc(1, sizeof (HEADER_CACHE));
1103   int (*hcache_open) (struct header_cache* h, const char* path);
1104   struct stat sb;
1105
1106 #if HAVE_QDBM
1107   hcache_open = hcache_open_qdbm;
1108 #elif HAVE_TC
1109   hcache_open= hcache_open_tc;
1110 #elif HAVE_GDBM
1111   hcache_open = hcache_open_gdbm;
1112 #elif HAVE_DB4
1113   hcache_open = hcache_open_db4;
1114 #endif
1115
1116   h->db = NULL;
1117   h->folder = get_foldername(folder);
1118   h->crc = HCACHEVER;
1119
1120   if (!path || path[0] == '\0')
1121   {
1122     FREE(&h->folder);
1123     FREE(&h);
1124     return NULL;
1125   }
1126
1127   path = mutt_hcache_per_folder(path, h->folder, namer);
1128
1129   if (!hcache_open (h, path))
1130     return h;
1131   else
1132   {
1133     /* remove a possibly incompatible version */
1134     if (!stat (path, &sb) && !unlink (path))
1135     {
1136       if (!hcache_open (h, path))
1137         return h;
1138     }
1139     FREE(&h->folder);
1140     FREE(&h);
1141
1142     return NULL;
1143   }
1144 }
1145
1146 #if HAVE_DB4
1147 const char *mutt_hcache_backend (void)
1148 {
1149   return DB_VERSION_STRING;
1150 }
1151 #elif HAVE_GDBM
1152 const char *mutt_hcache_backend (void)
1153 {
1154   return gdbm_version;
1155 }
1156 #elif HAVE_QDBM
1157 const char *mutt_hcache_backend (void)
1158 {
1159   return "qdbm " _QDBM_VERSION;
1160 }
1161 #elif HAVE_TC
1162 const char *mutt_hcache_backend (void)
1163 {
1164   return "tokyocabinet " _TC_VERSION;
1165 }
1166 #endif