Only use fcntl, simplify locking.
[apps/madmutt.git] / lib-mx / mx.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1996-2002 Michael R. Elkins <me@mutt.org>
4  * Copyright (C) 1999-2000 Thomas Roessler <roessler@does-not-exist.org>
5  *
6  * This file is part of mutt-ng, see http://www.muttng.org/.
7  * It's licensed under the GNU General Public License,
8  * please see the file GPL in the top level source directory.
9  */
10
11 #include <lib-lib/lib-lib.h>
12
13 #include <lockfile.h>
14
15 #include <lib-lua/lib-lua.h>
16 #include <lib-sys/unix.h>
17 #include <lib-mime/mime.h>
18 #include <lib-ui/lib-ui.h>
19
20 #include "mutt.h"
21 #include "crypt.h"
22 #include "pattern.h"
23 #include "mx.h"
24 #include "mbox.h"
25 #include "mh.h"
26 #include "sort.h"
27 #include "thread.h"
28 #include "copy.h"
29 #include "keymap.h"
30 #include "compress.h"
31 #include "score.h"
32
33 #include <imap/imap.h>
34 #include "pop.h"
35
36 #define MAXLOCKATTEMPT 5
37
38
39 static mx_t const *mxfmts[] = {
40     &mbox_mx,
41     &mh_mx,
42     &maildir_mx,
43     &imap_mx,
44     &pop_mx,
45     &compress_mx,
46 };
47
48 #define MX_IDX(idx)          (idx >= 0 && idx < countof(mxfmts))
49 #define mutt_is_spool(s)     (m_strcmp(Spoolfile, s) == 0)
50
51 static int dotlock_file(const char *path, int retry)
52 {
53     char lockfile[_POSIX_PATH_MAX];
54     snprintf(lockfile, sizeof(lockfile), "%s.lock", path);
55
56     if (lockfile_create(lockfile, retry ? 1 : 0, 0)) {
57         if (retry)
58             mutt_error (_("Can't dotlock %s.\n"), lockfile);
59     }
60     return 0;
61 }
62
63 static int undotlock_file (const char *path)
64 {
65     char lockfile[_POSIX_PATH_MAX];
66     snprintf(lockfile, sizeof(lockfile), "%s.lock", path);
67     return lockfile_remove(lockfile);
68 }
69
70 /* looks up index of type for path in mxfmts */
71 static int mx_get_idx (const char* path) {
72     int i = 0, t = 0;
73     struct stat st;
74
75     /* first, test all non-local folders to avoid stat() call */
76     for (i = 0; i < countof(mxfmts); i++) {
77         if (!mxfmts[i]->local)
78             t = mxfmts[i]->mx_is_magic(path, NULL);
79         if (t >= 1)
80             return (t-1);
81     }
82     if (stat (path, &st) == 0) {
83         /* if stat() succeeded, keep testing until success and
84          * pass stat() info so that we only need to do it once */
85         for (i = 0; i < countof(mxfmts); i++) {
86             if (mxfmts[i]->local)
87                 t = mxfmts[i]->mx_is_magic(path, &st);
88             if (t >= 1)
89                 return (t-1);
90         }
91     }
92     return (-1);
93 }
94
95 /* Args:
96  *      excl            if excl != 0, request an exclusive lock
97  *      time_out        should retry locking?
98  */
99 int mx_lock_file(const char *path, int fd, int excl, int time_out)
100 {
101     int count = 0, attempt = 0;
102     struct flock lck = {
103         .l_type   = excl ? F_WRLCK : F_RDLCK,
104         .l_whence = SEEK_SET,
105     };
106
107     if (dotlock_file(path, time_out) < 0)
108         return -1;
109
110     while (fcntl(fd, F_SETLK, &lck) == -1) {
111         if (errno != EAGAIN && errno != EACCES) {
112             mutt_perror("fcntl");
113             goto error;
114         }
115
116         if (++count >= (time_out ? MAXLOCKATTEMPT : 0)) {
117             if (time_out)
118                 mutt_error _("Timeout exceeded while attempting fcntl lock!");
119             goto error;
120         }
121         mutt_message(_("Waiting for fcntl lock... %d"), ++attempt);
122         mutt_sleep(1);
123     }
124     return 0;
125
126   error:
127     undotlock_file(path);
128     return -1;
129 }
130
131 int mx_unlock_file(const char *path, int fd)
132 {
133     struct flock unlockit;
134
135     p_clear(&unlockit, 1);
136     unlockit.l_type = F_UNLCK;
137     unlockit.l_whence = SEEK_SET;
138     fcntl(fd, F_SETLK, &unlockit);
139     undotlock_file(path);
140     return 0;
141 }
142
143 /* try to figure out what type of mailbox ``path'' is */
144 int mx_get_magic (const char *path) {
145   int i = 0;
146
147   if (m_strlen(path) == 0)
148     return (-1);
149   if ((i = mx_get_idx (path)) >= 0)
150     return (mxfmts[i]->type);
151   return (-1);
152 }
153
154 int mx_is_local (int m) {
155   if (!MX_IDX(m))
156     return (0);
157   return (mxfmts[m]->local);
158 }
159
160 /*
161  * set DefaultMagic to the given value
162  */
163 int mx_set_magic (const char *s)
164 {
165   if (ascii_strcasecmp (s, "mbox") == 0)
166     DefaultMagic = M_MBOX;
167   else if (ascii_strcasecmp (s, "mh") == 0)
168     DefaultMagic = M_MH;
169   else if (ascii_strcasecmp (s, "maildir") == 0)
170     DefaultMagic = M_MAILDIR;
171   else
172     return (-1);
173
174   return 0;
175 }
176
177 /* mx_access: Wrapper for access, checks permissions on a given mailbox.
178  *   We may be interested in using ACL-style flags at some point, currently
179  *   we use the normal access() flags. */
180 int mx_access (const char *path, int flags)
181 {
182   int i = 0;
183
184   if ((i = mx_get_idx (path)) >= 0 && mxfmts[i]->mx_access)
185     return (mxfmts[i]->mx_access(path,flags));
186   return (0);
187 }
188
189 static int mx_open_mailbox_append (CONTEXT * ctx, int flags)
190 {
191   struct stat sb;
192
193   /* special case for appending to compressed folders -
194    * even if we can not open them for reading */
195   if (mutt_can_append_compressed (ctx->path))
196     mutt_open_append_compressed (ctx);
197
198   ctx->append = 1;
199
200   if (mx_get_magic (ctx->path) == M_IMAP)
201     return imap_open_mailbox_append (ctx);
202
203   if (stat (ctx->path, &sb) == 0) {
204     ctx->magic = mx_get_magic (ctx->path);
205
206     switch (ctx->magic) {
207     case 0:
208       mutt_error (_("%s is not a mailbox."), ctx->path);
209       /* fall through */
210     case -1:
211       return (-1);
212     }
213   }
214   else if (errno == ENOENT) {
215     ctx->magic = DefaultMagic;
216
217     if (ctx->magic == M_MH || ctx->magic == M_MAILDIR) {
218       char tmp[_POSIX_PATH_MAX];
219
220       if (mkdir (ctx->path, S_IRWXU)) {
221         mutt_perror (ctx->path);
222         return (-1);
223       }
224
225       if (ctx->magic == M_MAILDIR) {
226         snprintf (tmp, sizeof (tmp), "%s/cur", ctx->path);
227         if (mkdir (tmp, S_IRWXU)) {
228           mutt_perror (tmp);
229           rmdir (ctx->path);
230           return (-1);
231         }
232
233         snprintf (tmp, sizeof (tmp), "%s/new", ctx->path);
234         if (mkdir (tmp, S_IRWXU)) {
235           mutt_perror (tmp);
236           snprintf (tmp, sizeof (tmp), "%s/cur", ctx->path);
237           rmdir (tmp);
238           rmdir (ctx->path);
239           return (-1);
240         }
241         snprintf (tmp, sizeof (tmp), "%s/tmp", ctx->path);
242         if (mkdir (tmp, S_IRWXU)) {
243           mutt_perror (tmp);
244           snprintf (tmp, sizeof (tmp), "%s/cur", ctx->path);
245           rmdir (tmp);
246           snprintf (tmp, sizeof (tmp), "%s/new", ctx->path);
247           rmdir (tmp);
248           rmdir (ctx->path);
249           return (-1);
250         }
251       }
252       else {
253         int i;
254
255         snprintf (tmp, sizeof (tmp), "%s/.mh_sequences", ctx->path);
256         if ((i = creat (tmp, S_IRWXU)) == -1) {
257           mutt_perror (tmp);
258           rmdir (ctx->path);
259           return (-1);
260         }
261         close (i);
262       }
263     }
264   }
265   else {
266     mutt_perror (ctx->path);
267     return (-1);
268   }
269
270   switch (ctx->magic) {
271   case M_MBOX:
272     if ((ctx->fp =
273          safe_fopen (ctx->path, flags & M_NEWFOLDER ? "w" : "a")) == NULL
274         || mbox_lock_mailbox(ctx, 1, 1) != 0) {
275       if (!ctx->fp)
276         mutt_perror (ctx->path);
277       else {
278         mutt_error (_("Couldn't lock %s\n"), ctx->path);
279         m_fclose(&ctx->fp);
280       }
281       return (-1);
282     }
283     fseeko (ctx->fp, 0, 2);
284     break;
285
286   case M_MH:
287   case M_MAILDIR:
288     /* nothing to do */
289     break;
290
291   default:
292     return (-1);
293   }
294
295   return 0;
296 }
297
298 /*
299  * open a mailbox and parse it
300  *
301  * Args:
302  *      flags   M_NOSORT        do not sort mailbox
303  *              M_APPEND        open mailbox for appending
304  *              M_READONLY      open mailbox in read-only mode
305  *              M_QUIET         only print error messages
306  *      ctx     if non-null, context struct to use
307  */
308 CONTEXT *mx_open_mailbox (const char *path, int flags, CONTEXT * pctx)
309 {
310   CONTEXT *ctx = pctx;
311   int rc;
312
313   if (!ctx)
314     ctx = p_new(CONTEXT, 1);
315   p_clear(ctx, 1);
316   ctx->path = m_strdup(path);
317
318   ctx->msgnotreadyet = -1;
319   ctx->collapsed = 0;
320
321   if (flags & M_QUIET)
322     ctx->quiet = 1;
323   if (flags & M_READONLY)
324     ctx->readonly = 1;
325   if (flags & M_COUNT)
326     ctx->counting = 1;
327
328   if (flags & (M_APPEND | M_NEWFOLDER)) {
329     if (mx_open_mailbox_append (ctx, flags) != 0) {
330       mx_fastclose_mailbox (ctx);
331       if (!pctx)
332         p_delete(&ctx);
333       return NULL;
334     }
335     return ctx;
336   }
337
338   if (!MX_IDX(ctx->magic-1))
339     ctx->magic = mx_get_magic (path);
340
341   if (ctx->magic == M_COMPRESSED)
342     mutt_open_read_compressed (ctx);
343
344   if (ctx->magic == 0)
345     mutt_error (_("%s is not a mailbox."), path);
346
347   if (ctx->magic == -1)
348     mutt_perror (path);
349
350   if (ctx->magic <= 0) {
351     mx_fastclose_mailbox (ctx);
352     if (!pctx)
353       p_delete(&ctx);
354     return (NULL);
355   }
356
357   /* if the user has a `push' command in their .muttrc, or in a folder-hook,
358    * it will cause the progress messages not to be displayed because
359    * mutt_refresh() will think we are in the middle of a macro.  so set a
360    * flag to indicate that we should really refresh the screen.
361    */
362   set_option (OPTFORCEREFRESH);
363
364   if (!ctx->quiet)
365     mutt_message (_("Reading %s..."), ctx->path);
366
367   rc = mxfmts[ctx->magic-1]->mx_open_mailbox(ctx);
368
369   if (rc == 0) {
370     if ((flags & M_NOSORT) == 0) {
371       /* avoid unnecessary work since the mailbox is completely unthreaded
372          to begin with */
373       unset_option (OPTSORTSUBTHREADS);
374       unset_option (OPTNEEDRESCORE);
375       mutt_sort_headers (ctx, 1);
376     }
377     if (!ctx->quiet)
378       mutt_clear_error ();
379   }
380   else {
381     mx_fastclose_mailbox (ctx);
382     if (!pctx)
383       p_delete(&ctx);
384   }
385
386   unset_option (OPTFORCEREFRESH);
387   return (ctx);
388 }
389
390 /* free up memory associated with the mailbox context */
391 void mx_fastclose_mailbox (CONTEXT * ctx)
392 {
393   int i;
394
395   if (!ctx)
396     return;
397
398   if (MX_IDX(ctx->magic-1) && mxfmts[ctx->magic-1]->mx_fastclose_mailbox)
399     mxfmts[ctx->magic-1]->mx_fastclose_mailbox(ctx);
400   if (ctx->subj_hash)
401     hash_delete (&ctx->subj_hash, NULL);
402   if (ctx->id_hash)
403     hash_delete (&ctx->id_hash, NULL);
404   mutt_clear_threads (ctx);
405   for (i = 0; i < ctx->msgcount; i++)
406     header_delete(&ctx->hdrs[i]);
407   p_delete(&ctx->hdrs);
408   p_delete(&ctx->v2r);
409
410   if (ctx->cinfo)
411     mutt_fast_close_compressed (ctx);
412
413   p_delete(&ctx->path);
414   p_delete(&ctx->pattern);
415   pattern_list_wipe(&ctx->limit_pattern);
416   m_fclose(&ctx->fp);
417   p_clear(ctx, 1);
418 }
419
420 /* save changes to disk */
421 static int sync_mailbox (CONTEXT * ctx, int *index_hint)
422 {
423   int rc = -1;
424
425   if (!ctx->quiet)
426     mutt_message (_("Writing %s..."), ctx->path);
427
428   if (MX_IDX(ctx->magic-1))
429     /* the 1 is only of interest for IMAP and means EXPUNGE */
430     rc = mxfmts[ctx->magic-1]->mx_sync_mailbox(ctx,1,index_hint);
431
432   if (rc == 0 && ctx->cinfo)
433     return mutt_sync_compressed (ctx);
434
435   return rc;
436 }
437
438 /* move deleted mails to the trash folder */
439 static int trash_append (CONTEXT * ctx)
440 {
441   CONTEXT *ctx_trash;
442   int i = 0;
443   struct stat st, stc;
444
445   if (!TrashPath || !ctx->deleted ||
446       (ctx->magic == M_MAILDIR && option (OPTMAILDIRTRASH)))
447     return 0;
448
449   for (; i < ctx->msgcount && (!ctx->hdrs[i]->deleted ||
450                                ctx->hdrs[i]->appended); i++);
451   if (i == ctx->msgcount)
452     return 0;                   /* nothing to be done */
453
454   if (mutt_save_confirm (TrashPath, &st) != 0) {
455     mutt_error _("message(s) not deleted");
456
457     return -1;
458   }
459
460   if (lstat (ctx->path, &stc) == 0 && stc.st_ino == st.st_ino
461       && stc.st_dev == st.st_dev && stc.st_rdev == st.st_rdev)
462     return 0;                   /* we are in the trash folder: simple sync */
463
464   if ((ctx_trash = mx_open_mailbox (TrashPath, M_APPEND, NULL)) != NULL) {
465     for (i = 0; i < ctx->msgcount; i++)
466       if (ctx->hdrs[i]->deleted && !ctx->hdrs[i]->appended
467           && !ctx->hdrs[i]->purged
468           && mutt_append_message (ctx_trash, ctx, ctx->hdrs[i], 0, 0) == -1) {
469         mx_close_mailbox (ctx_trash, NULL);
470         return -1;
471       }
472
473     mx_close_mailbox (ctx_trash, NULL);
474   }
475   else {
476     mutt_error _("Can't open trash folder");
477
478     return -1;
479   }
480
481   return 0;
482 }
483
484 /* save changes and close mailbox */
485 static int _mx_close_mailbox (CONTEXT * ctx, int *index_hint)
486 {
487   int i, move_messages = 0, purge = 1, read_msgs = 0;
488   int check;
489   int isSpool = 0;
490   CONTEXT f;
491   char mbox[_POSIX_PATH_MAX];
492   char buf[STRING];
493
494   if (!ctx)
495     return 0;
496
497   ctx->closing = 1;
498
499   if (ctx->readonly || ctx->dontwrite) {
500     /* mailbox is readonly or we don't want to write */
501     mx_fastclose_mailbox (ctx);
502     return 0;
503   }
504
505   if (ctx->append) {
506     /* mailbox was opened in write-mode */
507     if (ctx->magic == M_MBOX)
508       mbox_close_mailbox (ctx);
509     else
510       mx_fastclose_mailbox (ctx);
511     return 0;
512   }
513
514   for (i = 0; i < ctx->msgcount; i++) {
515     if (!ctx->hdrs[i]->deleted && ctx->hdrs[i]->read
516         && !(ctx->hdrs[i]->flagged && option (OPTKEEPFLAGGED)))
517       read_msgs++;
518   }
519
520   if (read_msgs && quadoption (OPT_MOVE) != M_NO) {
521     char *p;
522
523     if ((p = mutt_find_hook (M_MBOXHOOK, ctx->path))) {
524       isSpool = 1;
525       m_strcpy(mbox, sizeof(mbox), p);
526     }
527     else {
528       m_strcpy(mbox, sizeof(mbox), NONULL(Inbox));
529       isSpool = mutt_is_spool (ctx->path) && !mutt_is_spool (mbox);
530     }
531     mutt_expand_path (mbox, sizeof (mbox));
532
533     if (isSpool) {
534       snprintf (buf, sizeof (buf), _("Move read messages to %s?"), mbox);
535       if ((move_messages = query_quadoption (OPT_MOVE, buf)) == -1) {
536         ctx->closing = 0;
537         return (-1);
538       }
539     }
540   }
541
542   /* 
543    * There is no point in asking whether or not to purge if we are
544    * just marking messages as "trash".
545    */
546   if (ctx->deleted && !(ctx->magic == M_MAILDIR && option (OPTMAILDIRTRASH))) {
547     snprintf (buf, sizeof (buf), ctx->deleted == 1
548               ? _("Purge %d deleted message?") :
549               _("Purge %d deleted messages?"), ctx->deleted);
550     if ((purge = query_quadoption (OPT_DELETE, buf)) < 0) {
551       ctx->closing = 0;
552       return (-1);
553     }
554   }
555
556   /* IMAP servers manage the OLD flag themselves */
557   if (ctx->magic != M_IMAP)
558     if (option (OPTMARKOLD)) {
559       for (i = 0; i < ctx->msgcount; i++) {
560         if (!ctx->hdrs[i]->deleted && !ctx->hdrs[i]->old)
561           mutt_set_flag (ctx, ctx->hdrs[i], M_OLD, 1);
562       }
563     }
564
565   if (move_messages) {
566     mutt_message (_("Moving read messages to %s..."), mbox);
567
568     /* try to use server-side copy first */
569     i = 1;
570
571     if (ctx->magic == M_IMAP && imap_is_magic (mbox, NULL) == M_IMAP) {
572       /* tag messages for moving, and clear old tags, if any */
573       for (i = 0; i < ctx->msgcount; i++)
574         if (ctx->hdrs[i]->read && !ctx->hdrs[i]->deleted
575             && !(ctx->hdrs[i]->flagged && option (OPTKEEPFLAGGED)))
576           ctx->hdrs[i]->tagged = 1;
577         else
578           ctx->hdrs[i]->tagged = 0;
579
580       i = imap_copy_messages (ctx, NULL, mbox, 1);
581     }
582
583     if (i == 0)                 /* success */
584       mutt_clear_error ();
585     else if (i == -1) {         /* horrible error, bail */
586       ctx->closing = 0;
587       return -1;
588     }
589     else                        /* use regular append-copy mode */
590     {
591       if (mx_open_mailbox (mbox, M_APPEND, &f) == NULL) {
592         ctx->closing = 0;
593         return -1;
594       }
595
596       for (i = 0; i < ctx->msgcount; i++) {
597         if (ctx->hdrs[i]->read && !ctx->hdrs[i]->deleted
598             && !(ctx->hdrs[i]->flagged && option (OPTKEEPFLAGGED))) {
599           if (mutt_append_message (&f, ctx, ctx->hdrs[i], 0, CH_UPDATE_LEN) ==
600               0) {
601             mutt_set_flag (ctx, ctx->hdrs[i], M_DELETE, 1);
602             mutt_set_flag (ctx, ctx->hdrs[i], M_APPENDED, 1);
603           }
604           else {
605             mx_close_mailbox (&f, NULL);
606             ctx->closing = 0;
607             return -1;
608           }
609         }
610       }
611
612       mx_close_mailbox (&f, NULL);
613     }
614
615   }
616   else if (!ctx->changed && ctx->deleted == 0) {
617     mutt_message _("Mailbox is unchanged.");
618
619     mx_fastclose_mailbox (ctx);
620     return 0;
621   }
622
623   /* copy mails to the trash before expunging */
624   if (purge && ctx->deleted)
625     if (trash_append (ctx) != 0) {
626       ctx->closing = 0;
627       return -1;
628     }
629
630   /* allow IMAP to preserve the deleted flag across sessions */
631   if (ctx->magic == M_IMAP) {
632     if ((check = imap_sync_mailbox (ctx, purge, index_hint)) != 0) {
633       ctx->closing = 0;
634       return check;
635     }
636   }
637   else
638   {
639     if (!purge) {
640       for (i = 0; i < ctx->msgcount; i++)
641         ctx->hdrs[i]->deleted = 0;
642       ctx->deleted = 0;
643     }
644
645     if (ctx->changed || ctx->deleted) {
646       if ((check = sync_mailbox (ctx, index_hint)) != 0) {
647         ctx->closing = 0;
648         return check;
649       }
650     }
651   }
652
653   if (move_messages)
654     mutt_message (_("%d kept, %d moved, %d deleted."),
655                   ctx->msgcount - ctx->deleted, read_msgs, ctx->deleted);
656   else
657     mutt_message (_("%d kept, %d deleted."),
658                   ctx->msgcount - ctx->deleted, ctx->deleted);
659
660   if (ctx->cinfo && mutt_slow_close_compressed (ctx))
661     return (-1);
662
663   mx_fastclose_mailbox (ctx);
664
665   return 0;
666 }
667
668 int mx_close_mailbox (CONTEXT * ctx, int *index_hint) {
669   int ret = 0;
670   if (!ctx)
671     return (0);
672   ret = _mx_close_mailbox (ctx, index_hint);
673   sidebar_set_buffystats (ctx);
674   return (ret);
675 }
676
677 /* update a Context structure's internal tables. */
678
679 void mx_update_tables (CONTEXT * ctx, int committing)
680 {
681   int i, j;
682
683   /* update memory to reflect the new state of the mailbox */
684   ctx->vcount = 0;
685   ctx->vsize = 0;
686   ctx->tagged = 0;
687   ctx->deleted = 0;
688   ctx->new = 0;
689   ctx->unread = 0;
690   ctx->changed = 0;
691   ctx->flagged = 0;
692 #define this_body ctx->hdrs[j]->content
693   for (i = 0, j = 0; i < ctx->msgcount; i++) {
694     if ((committing && (!ctx->hdrs[i]->deleted ||
695                         (ctx->magic == M_MAILDIR
696                          && option (OPTMAILDIRTRASH)))) || (!committing
697                                                             && ctx->hdrs[i]->
698                                                             active)) {
699       if (i != j) {
700         ctx->hdrs[j] = ctx->hdrs[i];
701         ctx->hdrs[i] = NULL;
702       }
703       ctx->hdrs[j]->msgno = j;
704       if (ctx->hdrs[j]->virtual != -1) {
705         ctx->v2r[ctx->vcount] = j;
706         ctx->hdrs[j]->virtual = ctx->vcount++;
707         ctx->vsize += this_body->length + this_body->offset -
708           this_body->hdr_offset;
709       }
710
711       if (committing)
712         ctx->hdrs[j]->changed = 0;
713       else if (ctx->hdrs[j]->changed)
714         ctx->changed++;
715
716       if (!committing
717           || (ctx->magic == M_MAILDIR && option (OPTMAILDIRTRASH))) {
718         if (ctx->hdrs[j]->deleted)
719           ctx->deleted++;
720       }
721
722       if (ctx->hdrs[j]->tagged)
723         ctx->tagged++;
724       if (ctx->hdrs[j]->flagged)
725         ctx->flagged++;
726       if (!ctx->hdrs[j]->read) {
727         ctx->unread++;
728         if (!ctx->hdrs[j]->old)
729           ctx->new++;
730       }
731
732       j++;
733     }
734     else {
735       if (ctx->magic == M_MH || ctx->magic == M_MAILDIR)
736         ctx->size -= (ctx->hdrs[i]->content->length +
737                       ctx->hdrs[i]->content->offset -
738                       ctx->hdrs[i]->content->hdr_offset);
739       /* remove message from the hash tables */
740       if (ctx->subj_hash && ctx->hdrs[i]->env->real_subj)
741         hash_remove (ctx->subj_hash, ctx->hdrs[i]->env->real_subj,
742                      ctx->hdrs[i], NULL);
743       if (ctx->id_hash && ctx->hdrs[i]->env->message_id)
744         hash_remove (ctx->id_hash, ctx->hdrs[i]->env->message_id,
745                      ctx->hdrs[i], NULL);
746       header_delete(&ctx->hdrs[i]);
747     }
748   }
749 #undef this_body
750   ctx->msgcount = j;
751
752   /* update sidebar count */
753   sidebar_set_buffystats (ctx);
754 }
755
756
757 /* save changes to mailbox
758  *
759  * return values:
760  *      0               success
761  *      -1              error
762  */
763 static int _mx_sync_mailbox (CONTEXT * ctx, int *index_hint)
764 {
765   int rc, i;
766   int purge = 1;
767   int msgcount, deleted;
768
769   if (ctx->dontwrite) {
770     char buf[STRING], tmp[STRING];
771
772     if (km_expand_key (buf, sizeof (buf),
773                        km_find_func (MENU_MAIN, OP_TOGGLE_WRITE)))
774       snprintf (tmp, sizeof (tmp), _(" Press '%s' to toggle write"), buf);
775     else
776       m_strcpy(tmp, sizeof(tmp), _("Use 'toggle-write' to re-enable write!"));
777
778     mutt_error (_("Mailbox is marked unwritable. %s"), tmp);
779     return -1;
780   }
781   else if (ctx->readonly) {
782     mutt_error _("Mailbox is read-only.");
783
784     return -1;
785   }
786
787   if (!ctx->changed && !ctx->deleted) {
788     mutt_message _("Mailbox is unchanged.");
789
790     return (0);
791   }
792
793   if (ctx->deleted) {
794     char buf[STRING];
795
796     snprintf (buf, sizeof (buf), ctx->deleted == 1
797               ? _("Purge %d deleted message?") :
798               _("Purge %d deleted messages?"), ctx->deleted);
799     if ((purge = query_quadoption (OPT_DELETE, buf)) < 0)
800       return (-1);
801     else if (purge == M_NO) {
802       if (!ctx->changed)
803         return 0;               /* nothing to do! */
804       /* let IMAP servers hold on to D flags */
805       if (ctx->magic != M_IMAP)
806       {
807         for (i = 0; i < ctx->msgcount; i++)
808           ctx->hdrs[i]->deleted = 0;
809         ctx->deleted = 0;
810       }
811     }
812     else if (ctx->last_tag && ctx->last_tag->deleted)
813       ctx->last_tag = NULL;     /* reset last tagged msg now useless */
814   }
815
816   /* really only for IMAP - imap_sync_mailbox results in a call to
817    * mx_update_tables, so ctx->deleted is 0 when it comes back */
818   msgcount = ctx->msgcount;
819   deleted = ctx->deleted;
820
821   if (purge && ctx->deleted) {
822     if (trash_append (ctx) == -1)
823       return -1;
824   }
825
826   if (ctx->magic == M_IMAP)
827     rc = imap_sync_mailbox (ctx, purge, index_hint);
828   else
829     rc = sync_mailbox (ctx, index_hint);
830   if (rc == 0) {
831     if (ctx->magic == M_IMAP && !purge)
832       mutt_message (_("Mailbox checkpointed."));
833     else
834       mutt_message (_("%d kept, %d deleted."), msgcount - deleted, deleted);
835
836     mutt_sleep (0);
837
838     /* if we haven't deleted any messages, we don't need to resort */
839     /* ... except for certain folder formats which need "unsorted" 
840      * sort order in order to synchronize folders.
841      * 
842      * MH and maildir are safe.  mbox-style seems to need re-sorting,
843      * at least with the new threading code.
844      */
845     if (purge || (ctx->magic != M_MAILDIR && ctx->magic != M_MH)) {
846       /* IMAP does this automatically after handling EXPUNGE */
847       if (ctx->magic != M_IMAP)
848       {
849         mx_update_tables (ctx, 1);
850         mutt_sort_headers (ctx, 1);     /* rethread from scratch */
851       }
852     }
853   }
854
855   return (rc);
856 }
857
858 int mx_sync_mailbox (CONTEXT* ctx, int* index_hint) {
859   int ret = _mx_sync_mailbox (ctx, index_hint);
860   sidebar_set_buffystats (ctx);
861   return (ret);
862 }
863
864 /* args:
865  *      dest    destintation mailbox
866  *      hdr     message being copied (required for maildir support, because
867  *              the filename depends on the message flags)
868  */
869 MESSAGE *mx_open_new_message (CONTEXT * dest, HEADER * hdr, int flags)
870 {
871   MESSAGE *msg;
872   address_t *p = NULL;
873
874   if (!MX_IDX(dest->magic-1)) {
875     return (NULL);
876   }
877
878   msg = p_new(MESSAGE, 1);
879   msg->magic = dest->magic;
880   msg->write = 1;
881
882   if (hdr) {
883     msg->flags.flagged = hdr->flagged;
884     msg->flags.replied = hdr->replied;
885     msg->flags.read = hdr->read;
886     msg->received = hdr->received;
887   }
888
889   if (msg->received == 0)
890     time (&msg->received);
891
892   if (mxfmts[dest->magic-1]->mx_open_new_message(msg, dest, hdr) == 0) {
893     if (msg->magic == M_MBOX && flags & M_ADD_FROM) {
894       if (hdr) {
895         if (hdr->env->return_path)
896           p = hdr->env->return_path;
897         else if (hdr->env->sender)
898           p = hdr->env->sender;
899         else
900           p = hdr->env->from;
901       }
902
903       fprintf (msg->fp, "From %s %s", p ? p->mailbox : NONULL(mod_core.username),
904                ctime (&msg->received));
905     }
906   }
907   else
908     p_delete(&msg);
909
910   return msg;
911 }
912
913 /* check for new mail */
914 int mx_check_mailbox (CONTEXT * ctx, int *index_hint, int lock) {
915   if (ctx->cinfo)
916     return mutt_check_mailbox_compressed (ctx);
917
918   if (ctx) {
919     if (ctx->locked)
920       lock = 0;
921     if (MX_IDX(ctx->magic-1) && mxfmts[ctx->magic-1]->mx_check_mailbox)
922       return (mxfmts[ctx->magic-1]->mx_check_mailbox(ctx, index_hint, lock));
923   }
924
925   return (-1);
926 }
927
928 /* return a stream pointer for a message */
929 MESSAGE *mx_open_message (CONTEXT * ctx, int msgno)
930 {
931   MESSAGE *msg;
932
933   msg = p_new(MESSAGE, 1);
934   switch (msg->magic = ctx->magic) {
935   case M_MBOX:
936     msg->fp = ctx->fp;
937     break;
938
939   case M_MH:
940   case M_MAILDIR:
941     {
942       HEADER *cur = ctx->hdrs[msgno];
943       char path[_POSIX_PATH_MAX];
944
945       snprintf (path, sizeof (path), "%s/%s", ctx->path, cur->path);
946
947       if ((msg->fp = fopen (path, "r")) == NULL && errno == ENOENT &&
948           ctx->magic == M_MAILDIR)
949         msg->fp = maildir_open_find_message (ctx->path, cur->path);
950
951       if (msg->fp == NULL) {
952         mutt_perror (path);
953         p_delete(&msg);
954       }
955     }
956     break;
957
958   case M_IMAP:
959     {
960       if (imap_fetch_message (msg, ctx, msgno) != 0)
961         p_delete(&msg);
962       break;
963     }
964
965   case M_POP:
966     {
967       if (pop_fetch_message (msg, ctx, msgno) != 0)
968         p_delete(&msg);
969       break;
970     }
971
972   default:
973     p_delete(&msg);
974     break;
975   }
976   return (msg);
977 }
978
979 /* commit a message to a folder */
980
981 int mx_commit_message (MESSAGE * msg, CONTEXT * ctx) {
982   if (!(msg->write && ctx->append)) {
983     return -1;
984   }
985   if (!ctx || !MX_IDX(ctx->magic-1) || !mxfmts[ctx->magic-1]->mx_commit_message)
986     return (-1);
987   return (mxfmts[ctx->magic-1]->mx_commit_message (msg, ctx));
988 }
989
990 /* close a pointer to a message */
991 int mx_close_message (MESSAGE ** msg)
992 {
993   int r = 0;
994
995   if ((*msg)->magic == M_MH || (*msg)->magic == M_MAILDIR
996   ||  (*msg)->magic == M_IMAP || (*msg)->magic == M_POP)
997   {
998     r = m_fclose(&(*msg)->fp);
999   }
1000   else
1001     (*msg)->fp = NULL;
1002
1003   if ((*msg)->path) {
1004     unlink ((*msg)->path);
1005     p_delete(&(*msg)->path);
1006   }
1007
1008   p_delete(msg);
1009   return (r);
1010 }
1011
1012 void mx_alloc_memory (CONTEXT * ctx)
1013 {
1014     ctx->hdrmax += 32;
1015
1016     p_realloc(&ctx->hdrs, ctx->hdrmax);
1017     p_realloc(&ctx->v2r, ctx->hdrmax);
1018     p_clear(ctx->hdrs + ctx->msgcount, ctx->hdrmax - ctx->msgcount);
1019     p_clear(ctx->v2r + ctx->msgcount, ctx->hdrmax - ctx->msgcount);
1020 }
1021
1022 /* this routine is called to update the counts in the context structure for
1023  * the last message header parsed.
1024  */
1025 void mx_update_context (CONTEXT * ctx, int new_messages)
1026 {
1027   HEADER *h;
1028   int msgno;
1029
1030   for (msgno = ctx->msgcount - new_messages; msgno < ctx->msgcount; msgno++) {
1031     h = ctx->hdrs[msgno];
1032
1033     /* NOTE: this _must_ be done before the check for mailcap! */
1034     h->security = crypt_query (h->content);
1035
1036     if (!ctx->pattern) {
1037       ctx->v2r[ctx->vcount] = msgno;
1038       h->virtual = ctx->vcount++;
1039     }
1040     else
1041       h->virtual = -1;
1042     h->msgno = msgno;
1043
1044     if (h->env->supersedes) {
1045       HEADER *h2;
1046
1047       if (!ctx->id_hash)
1048         ctx->id_hash = mutt_make_id_hash (ctx);
1049
1050       h2 = hash_find (ctx->id_hash, h->env->supersedes);
1051
1052       /* p_delete(&h->env->supersedes); should I ? */
1053       if (h2) {
1054         h2->superseded = 1;
1055         if (!ctx->counting && mod_score.enable)
1056           mutt_score_message (ctx, h2, 1);
1057       }
1058     }
1059
1060     /* add this message to the hash tables */
1061     if (ctx->id_hash && h->env->message_id)
1062       hash_insert (ctx->id_hash, h->env->message_id, h);
1063     if (!ctx->counting) {
1064       if (ctx->subj_hash && h->env->real_subj)
1065         hash_insert (ctx->subj_hash, h->env->real_subj, h);
1066
1067       if (mod_score.enable)
1068         mutt_score_message (ctx, h, 0);
1069     }
1070
1071     if (h->changed)
1072       ctx->changed = 1;
1073     if (h->flagged)
1074       ctx->flagged++;
1075     if (h->deleted)
1076       ctx->deleted++;
1077     if (!h->read) {
1078       ctx->unread++;
1079       if (!h->old)
1080         ctx->new++;
1081     }
1082   }
1083   /* update sidebar count */
1084   sidebar_set_buffystats (ctx);
1085 }
1086
1087 /*
1088  * Return:
1089  * 1 if the specified mailbox contains 0 messages.
1090  * 0 if the mailbox contains messages
1091  * -1 on error
1092  */
1093 int mx_check_empty (const char *path)
1094 {
1095   int i = 0;
1096   if ((i = mx_get_idx (path)) >= 0 && mxfmts[i]->mx_check_empty)
1097     return (mxfmts[i]->mx_check_empty(path));
1098   errno = EINVAL;
1099   return (-1);
1100 }
1101
1102 int mx_acl_check(CONTEXT *ctx, int flag)
1103 {
1104     if (!mxfmts[ctx->magic-1]->mx_acl_check)
1105         return 1;
1106     return mxfmts[ctx->magic-1]->mx_acl_check(ctx,flag);
1107 }
1108
1109 void mutt_parse_mime_message (CONTEXT * ctx, HEADER * cur)
1110 {
1111   MESSAGE *msg;
1112   int flags = 0;
1113
1114   do {
1115     if (cur->content->type != TYPEMESSAGE
1116         && cur->content->type != TYPEMULTIPART)
1117       break;                     /* nothing to do */
1118
1119     if (cur->content->parts)
1120       break;                     /* The message was parsed earlier. */
1121
1122     if ((msg = mx_open_message (ctx, cur->msgno))) {
1123       mutt_parse_part (msg->fp, cur->content);
1124
1125       cur->security = crypt_query (cur->content);
1126
1127       mx_close_message (&msg);
1128     }
1129   } while (0);
1130   mutt_count_body_parts (cur, flags | M_PARTS_RECOUNT);
1131 }