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