I don't care about systems where atimes are broken.
[apps/madmutt.git] / 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 <utime.h>
14
15 #include <lib-sys/unix.h>
16 #include <lib-mime/mime.h>
17 #include <lib-ui/sidebar.h>
18
19 #include "mutt.h"
20 #include "buffy.h"
21 #include "mx.h"
22 #include "mbox.h"
23 #include "mh.h"
24 #include "sort.h"
25 #include "thread.h"
26 #include "copy.h"
27 #include "keymap.h"
28 #include "compress.h"
29
30 #include <imap/imap.h>
31 #include <imap/mx_imap.h>
32
33 #include <pop/pop.h>
34 #include <pop/mx_pop.h>
35
36 #ifdef USE_NNTP
37 #include "nntp/nntp.h"
38 #include "nntp/mx_nntp.h"
39 #endif
40
41 #ifdef USE_DOTLOCK
42 #include "dotlock.h"
43 #endif
44
45 #include <lib-crypt/crypt.h>
46
47 static mx_t const *mxfmts[] = {
48     &mbox_mx,
49     &mmdf_mx,
50     &mh_mx,
51     &maildir_mx,
52     &imap_mx,
53     &pop_mx,
54 #ifdef USE_NNTP
55     &nntp_mx,
56 #endif
57     &compress_mx,
58 };
59
60 #define MX_IDX(idx)          (idx >= 0 && idx < countof(mxfmts))
61 #define mutt_is_spool(s)     (m_strcmp(Spoolfile, s) == 0)
62
63 #ifdef USE_DOTLOCK
64 /* parameters: 
65  * path - file to lock
66  * retry - should retry if unable to lock?
67  */
68
69 static int invoke_dotlock (const char *path, int flags, int retry)
70 {
71   char cmd[LONG_STRING + _POSIX_PATH_MAX];
72   char f[SHORT_STRING + _POSIX_PATH_MAX];
73   char r[SHORT_STRING];
74
75   if (flags & DL_FL_RETRY)
76     snprintf (r, sizeof (r), "-r %d ", retry ? MAXLOCKATTEMPT : 0);
77
78   mutt_quote_filename (f, sizeof (f), path);
79
80   snprintf (cmd, sizeof (cmd),
81             "%s %s%s%s%s%s%s%s",
82             NONULL (MuttDotlock),
83             flags & DL_FL_TRY ? "-t " : "",
84             flags & DL_FL_UNLOCK ? "-u " : "",
85             flags & DL_FL_USEPRIV ? "-p " : "",
86             flags & DL_FL_FORCE ? "-f " : "",
87             flags & DL_FL_UNLINK ? "-d " : "",
88             flags & DL_FL_RETRY ? r : "", f);
89
90   return mutt_system (cmd);
91 }
92
93 static int dotlock_file (const char *path, int retry)
94 {
95   int r;
96   int flags = DL_FL_USEPRIV | DL_FL_RETRY;
97
98   if (retry)
99     retry = 1;
100
101 retry_lock:
102   if ((r = invoke_dotlock (path, flags, retry)) == DL_EX_EXIST) {
103     if (!option (OPTNOCURSES)) {
104       char msg[LONG_STRING];
105
106       snprintf (msg, sizeof (msg),
107                 _("Lock count exceeded, remove lock for %s?"), path);
108       if (retry && mutt_yesorno (msg, M_YES) == M_YES) {
109         flags |= DL_FL_FORCE;
110         retry--;
111         mutt_clear_error ();
112         goto retry_lock;
113       }
114     }
115     else {
116       mutt_error (_("Can't dotlock %s.\n"), path);
117     }
118   }
119   return (r == DL_EX_OK ? 0 : -1);
120 }
121
122 static int undotlock_file (const char *path)
123 {
124   return (invoke_dotlock (path,  DL_FL_USEPRIV | DL_FL_UNLOCK, 0) ==
125           DL_EX_OK ? 0 : -1);
126 }
127
128 #endif /* USE_DOTLOCK */
129
130 /* looks up index of type for path in mxfmts */
131 static int mx_get_idx (const char* path) {
132     int i = 0, t = 0;
133     struct stat st;
134
135     /* first, test all non-local folders to avoid stat() call */
136     for (i = 0; i < countof(mxfmts); i++) {
137         if (!mxfmts[i]->local)
138             t = mxfmts[i]->mx_is_magic(path, NULL);
139         if (t >= 1)
140             return (t-1);
141     }
142     if (stat (path, &st) == 0) {
143         /* if stat() succeeded, keep testing until success and
144          * pass stat() info so that we only need to do it once */
145         for (i = 0; i < countof(mxfmts); i++) {
146             if (mxfmts[i]->local)
147                 t = mxfmts[i]->mx_is_magic(path, &st);
148             if (t >= 1)
149                 return (t-1);
150         }
151     }
152     return (-1);
153 }
154
155 /* Args:
156  *      excl            if excl != 0, request an exclusive lock
157  *      dot             if dot != 0, try to dotlock the file
158  *      time_out        should retry locking?
159  */
160 int mx_lock_file (const char *path, int fd, int excl, int dot, int time_out)
161 {
162 #if defined (USE_FCNTL) || defined (USE_FLOCK)
163   int count;
164   int attempt;
165   struct stat prev_sb;
166 #endif
167   int r = 0;
168
169 #ifdef USE_FCNTL
170   struct flock lck;
171
172
173   p_clear(&lck, 1);
174   lck.l_type = excl ? F_WRLCK : F_RDLCK;
175   lck.l_whence = SEEK_SET;
176
177   count = 0;
178   attempt = 0;
179   prev_sb.st_size = 0;
180   while (fcntl (fd, F_SETLK, &lck) == -1) {
181     struct stat sb;
182
183     if (errno != EAGAIN && errno != EACCES) {
184       mutt_perror ("fcntl");
185       return (-1);
186     }
187
188     if (fstat (fd, &sb) != 0)
189       sb.st_size = 0;
190
191     if (count == 0)
192       prev_sb = sb;
193
194     /* only unlock file if it is unchanged */
195     if (prev_sb.st_size == sb.st_size
196         && ++count >= (time_out ? MAXLOCKATTEMPT : 0)) {
197       if (time_out)
198         mutt_error _("Timeout exceeded while attempting fcntl lock!");
199
200       return (-1);
201     }
202
203     prev_sb = sb;
204
205     mutt_message (_("Waiting for fcntl lock... %d"), ++attempt);
206     sleep (1);
207   }
208 #endif /* USE_FCNTL */
209
210 #ifdef USE_FLOCK
211   count = 0;
212   attempt = 0;
213   while (flock (fd, (excl ? LOCK_EX : LOCK_SH) | LOCK_NB) == -1) {
214     struct stat sb;
215
216     if (errno != EWOULDBLOCK) {
217       mutt_perror ("flock");
218       r = -1;
219       break;
220     }
221
222     if (fstat (fd, &sb) != 0)
223       sb.st_size = 0;
224
225     if (count == 0)
226       prev_sb = sb;
227
228     /* only unlock file if it is unchanged */
229     if (prev_sb.st_size == sb.st_size
230         && ++count >= (time_out ? MAXLOCKATTEMPT : 0)) {
231       if (time_out)
232         mutt_error _("Timeout exceeded while attempting flock lock!");
233
234       r = -1;
235       break;
236     }
237
238     prev_sb = sb;
239
240     mutt_message (_("Waiting for flock attempt... %d"), ++attempt);
241     sleep (1);
242   }
243 #endif /* USE_FLOCK */
244
245 #ifdef USE_DOTLOCK
246   if (r == 0 && dot)
247     r = dotlock_file (path, time_out);
248 #endif /* USE_DOTLOCK */
249
250   if (r == -1) {
251     /* release any other locks obtained in this routine */
252
253 #ifdef USE_FCNTL
254     lck.l_type = F_UNLCK;
255     fcntl (fd, F_SETLK, &lck);
256 #endif /* USE_FCNTL */
257
258 #ifdef USE_FLOCK
259     flock (fd, LOCK_UN);
260 #endif /* USE_FLOCK */
261
262     return (-1);
263   }
264
265   return 0;
266 }
267
268 int mx_unlock_file (const char *path, int fd, int dot)
269 {
270 #ifdef USE_FCNTL
271   struct flock unlockit;
272
273   p_clear(&unlockit, 1);
274   unlockit.l_type = F_UNLCK;
275   unlockit.l_whence = SEEK_SET;
276   fcntl (fd, F_SETLK, &unlockit);
277 #endif
278
279 #ifdef USE_FLOCK
280   flock (fd, LOCK_UN);
281 #endif
282
283 #ifdef USE_DOTLOCK
284   if (dot)
285     undotlock_file (path);
286 #endif
287
288   return 0;
289 }
290
291 static void mx_unlink_empty (const char *path)
292 {
293   int fd;
294
295 #ifndef USE_DOTLOCK
296   struct stat sb;
297 #endif
298
299   if ((fd = open (path, O_RDWR)) == -1)
300     return;
301
302   if (mx_lock_file (path, fd, 1, 0, 1) == -1) {
303     close (fd);
304     return;
305   }
306
307 #ifdef USE_DOTLOCK
308   invoke_dotlock (path, DL_FL_UNLINK, 1);
309 #else
310   if (fstat (fd, &sb) == 0 && sb.st_size == 0)
311     unlink (path);
312 #endif
313
314   mx_unlock_file (path, fd, 0);
315   close (fd);
316 }
317
318 /* try to figure out what type of mailbox ``path'' is */
319 int mx_get_magic (const char *path) {
320   int i = 0;
321
322   if (m_strlen(path) == 0)
323     return (-1);
324   if ((i = mx_get_idx (path)) >= 0)
325     return (mxfmts[i]->type);
326   return (-1);
327 }
328
329 int mx_is_local (int m) {
330   if (!MX_IDX(m))
331     return (0);
332   return (mxfmts[m]->local);
333 }
334
335 /*
336  * set DefaultMagic to the given value
337  */
338 int mx_set_magic (const char *s)
339 {
340   if (ascii_strcasecmp (s, "mbox") == 0)
341     DefaultMagic = M_MBOX;
342   else if (ascii_strcasecmp (s, "mmdf") == 0)
343     DefaultMagic = M_MMDF;
344   else if (ascii_strcasecmp (s, "mh") == 0)
345     DefaultMagic = M_MH;
346   else if (ascii_strcasecmp (s, "maildir") == 0)
347     DefaultMagic = M_MAILDIR;
348   else
349     return (-1);
350
351   return 0;
352 }
353
354 /* mx_access: Wrapper for access, checks permissions on a given mailbox.
355  *   We may be interested in using ACL-style flags at some point, currently
356  *   we use the normal access() flags. */
357 int mx_access (const char *path, int flags)
358 {
359   int i = 0;
360
361   if ((i = mx_get_idx (path)) >= 0 && mxfmts[i]->mx_access)
362     return (mxfmts[i]->mx_access(path,flags));
363   return (0);
364 }
365
366 static int mx_open_mailbox_append (CONTEXT * ctx, int flags)
367 {
368   struct stat sb;
369
370   /* special case for appending to compressed folders -
371    * even if we can not open them for reading */
372   if (mutt_can_append_compressed (ctx->path))
373     mutt_open_append_compressed (ctx);
374
375   ctx->append = 1;
376
377   if (mx_get_magic (ctx->path) == M_IMAP)
378     return imap_open_mailbox_append (ctx);
379
380   if (stat (ctx->path, &sb) == 0) {
381     ctx->magic = mx_get_magic (ctx->path);
382
383     switch (ctx->magic) {
384     case 0:
385       mutt_error (_("%s is not a mailbox."), ctx->path);
386       /* fall through */
387     case -1:
388       return (-1);
389     }
390   }
391   else if (errno == ENOENT) {
392     ctx->magic = DefaultMagic;
393
394     if (ctx->magic == M_MH || ctx->magic == M_MAILDIR) {
395       char tmp[_POSIX_PATH_MAX];
396
397       if (mkdir (ctx->path, S_IRWXU)) {
398         mutt_perror (ctx->path);
399         return (-1);
400       }
401
402       if (ctx->magic == M_MAILDIR) {
403         snprintf (tmp, sizeof (tmp), "%s/cur", ctx->path);
404         if (mkdir (tmp, S_IRWXU)) {
405           mutt_perror (tmp);
406           rmdir (ctx->path);
407           return (-1);
408         }
409
410         snprintf (tmp, sizeof (tmp), "%s/new", ctx->path);
411         if (mkdir (tmp, S_IRWXU)) {
412           mutt_perror (tmp);
413           snprintf (tmp, sizeof (tmp), "%s/cur", ctx->path);
414           rmdir (tmp);
415           rmdir (ctx->path);
416           return (-1);
417         }
418         snprintf (tmp, sizeof (tmp), "%s/tmp", ctx->path);
419         if (mkdir (tmp, S_IRWXU)) {
420           mutt_perror (tmp);
421           snprintf (tmp, sizeof (tmp), "%s/cur", ctx->path);
422           rmdir (tmp);
423           snprintf (tmp, sizeof (tmp), "%s/new", ctx->path);
424           rmdir (tmp);
425           rmdir (ctx->path);
426           return (-1);
427         }
428       }
429       else {
430         int i;
431
432         snprintf (tmp, sizeof (tmp), "%s/.mh_sequences", ctx->path);
433         if ((i = creat (tmp, S_IRWXU)) == -1) {
434           mutt_perror (tmp);
435           rmdir (ctx->path);
436           return (-1);
437         }
438         close (i);
439       }
440     }
441   }
442   else {
443     mutt_perror (ctx->path);
444     return (-1);
445   }
446
447   switch (ctx->magic) {
448   case M_MBOX:
449   case M_MMDF:
450     if ((ctx->fp =
451          safe_fopen (ctx->path, flags & M_NEWFOLDER ? "w" : "a")) == NULL
452         || mbox_lock_mailbox (ctx, 1, 1) != 0) {
453       if (!ctx->fp)
454         mutt_perror (ctx->path);
455       else {
456         mutt_error (_("Couldn't lock %s\n"), ctx->path);
457         safe_fclose (&ctx->fp);
458       }
459       return (-1);
460     }
461     fseeko (ctx->fp, 0, 2);
462     break;
463
464   case M_MH:
465   case M_MAILDIR:
466     /* nothing to do */
467     break;
468
469   default:
470     return (-1);
471   }
472
473   return 0;
474 }
475
476 /*
477  * open a mailbox and parse it
478  *
479  * Args:
480  *      flags   M_NOSORT        do not sort mailbox
481  *              M_APPEND        open mailbox for appending
482  *              M_READONLY      open mailbox in read-only mode
483  *              M_QUIET         only print error messages
484  *      ctx     if non-null, context struct to use
485  */
486 CONTEXT *mx_open_mailbox (const char *path, int flags, CONTEXT * pctx)
487 {
488   CONTEXT *ctx = pctx;
489   int rc;
490
491   if (!ctx)
492     ctx = p_new(CONTEXT, 1);
493   p_clear(ctx, 1);
494   ctx->path = m_strdup(path);
495
496   ctx->msgnotreadyet = -1;
497   ctx->collapsed = 0;
498
499   if (flags & M_QUIET)
500     ctx->quiet = 1;
501   if (flags & M_READONLY)
502     ctx->readonly = 1;
503   if (flags & M_COUNT)
504     ctx->counting = 1;
505
506   if (flags & (M_APPEND | M_NEWFOLDER)) {
507     if (mx_open_mailbox_append (ctx, flags) != 0) {
508       mx_fastclose_mailbox (ctx);
509       if (!pctx)
510         p_delete(&ctx);
511       return NULL;
512     }
513     return ctx;
514   }
515
516   if (!MX_IDX(ctx->magic-1))
517     ctx->magic = mx_get_magic (path);
518
519   if (ctx->magic == M_COMPRESSED)
520     mutt_open_read_compressed (ctx);
521
522   if (ctx->magic == 0)
523     mutt_error (_("%s is not a mailbox."), path);
524
525   if (ctx->magic == -1)
526     mutt_perror (path);
527
528   if (ctx->magic <= 0) {
529     mx_fastclose_mailbox (ctx);
530     if (!pctx)
531       p_delete(&ctx);
532     return (NULL);
533   }
534
535   /* if the user has a `push' command in their .muttrc, or in a folder-hook,
536    * it will cause the progress messages not to be displayed because
537    * mutt_refresh() will think we are in the middle of a macro.  so set a
538    * flag to indicate that we should really refresh the screen.
539    */
540   set_option (OPTFORCEREFRESH);
541
542   if (!ctx->quiet)
543     mutt_message (_("Reading %s..."), ctx->path);
544
545   rc = mxfmts[ctx->magic-1]->mx_open_mailbox(ctx);
546
547   if (rc == 0) {
548     if ((flags & M_NOSORT) == 0) {
549       /* avoid unnecessary work since the mailbox is completely unthreaded
550          to begin with */
551       unset_option (OPTSORTSUBTHREADS);
552       unset_option (OPTNEEDRESCORE);
553       mutt_sort_headers (ctx, 1);
554     }
555     if (!ctx->quiet)
556       mutt_clear_error ();
557   }
558   else {
559     mx_fastclose_mailbox (ctx);
560     if (!pctx)
561       p_delete(&ctx);
562   }
563
564   unset_option (OPTFORCEREFRESH);
565   return (ctx);
566 }
567
568 /* free up memory associated with the mailbox context */
569 void mx_fastclose_mailbox (CONTEXT * ctx)
570 {
571   int i;
572
573   if (!ctx)
574     return;
575
576   if (MX_IDX(ctx->magic-1) && mxfmts[ctx->magic-1]->mx_fastclose_mailbox)
577     mxfmts[ctx->magic-1]->mx_fastclose_mailbox(ctx);
578   if (ctx->subj_hash)
579     hash_destroy (&ctx->subj_hash, NULL);
580   if (ctx->id_hash)
581     hash_destroy (&ctx->id_hash, NULL);
582   mutt_clear_threads (ctx);
583   for (i = 0; i < ctx->msgcount; i++)
584     header_delete(&ctx->hdrs[i]);
585   p_delete(&ctx->hdrs);
586   p_delete(&ctx->v2r);
587
588   if (ctx->compressinfo)
589     mutt_fast_close_compressed (ctx);
590
591   p_delete(&ctx->path);
592   p_delete(&ctx->pattern);
593   if (ctx->limit_pattern)
594     mutt_pattern_free (&ctx->limit_pattern);
595   safe_fclose (&ctx->fp);
596   p_clear(ctx, 1);
597 }
598
599 /* save changes to disk */
600 static int sync_mailbox (CONTEXT * ctx, int *index_hint)
601 {
602   int rc = -1;
603
604   if (!ctx->quiet)
605     mutt_message (_("Writing %s..."), ctx->path);
606
607   if (MX_IDX(ctx->magic-1))
608     /* the 1 is only of interest for IMAP and means EXPUNGE */
609     rc = mxfmts[ctx->magic-1]->mx_sync_mailbox(ctx,1,index_hint);
610
611   if (rc == 0 && ctx->compressinfo)
612     return mutt_sync_compressed (ctx);
613
614   return rc;
615 }
616
617 /* move deleted mails to the trash folder */
618 static int trash_append (CONTEXT * ctx)
619 {
620   CONTEXT *ctx_trash;
621   int i = 0;
622   struct stat st, stc;
623
624   if (!TrashPath || !ctx->deleted ||
625       (ctx->magic == M_MAILDIR && option (OPTMAILDIRTRASH)))
626     return 0;
627
628   for (; i < ctx->msgcount && (!ctx->hdrs[i]->deleted ||
629                                ctx->hdrs[i]->appended); i++);
630   if (i == ctx->msgcount)
631     return 0;                   /* nothing to be done */
632
633   if (mutt_save_confirm (TrashPath, &st) != 0) {
634     mutt_error _("message(s) not deleted");
635
636     return -1;
637   }
638
639   if (lstat (ctx->path, &stc) == 0 && stc.st_ino == st.st_ino
640       && stc.st_dev == st.st_dev && stc.st_rdev == st.st_rdev)
641     return 0;                   /* we are in the trash folder: simple sync */
642
643   if ((ctx_trash = mx_open_mailbox (TrashPath, M_APPEND, NULL)) != NULL) {
644     for (i = 0; i < ctx->msgcount; i++)
645       if (ctx->hdrs[i]->deleted && !ctx->hdrs[i]->appended
646           && !ctx->hdrs[i]->purged
647           && mutt_append_message (ctx_trash, ctx, ctx->hdrs[i], 0, 0) == -1) {
648         mx_close_mailbox (ctx_trash, NULL);
649         return -1;
650       }
651
652     mx_close_mailbox (ctx_trash, NULL);
653   }
654   else {
655     mutt_error _("Can't open trash folder");
656
657     return -1;
658   }
659
660   return 0;
661 }
662
663 /* save changes and close mailbox */
664 static int _mx_close_mailbox (CONTEXT * ctx, int *index_hint)
665 {
666   int i, move_messages = 0, purge = 1, read_msgs = 0;
667   int check;
668   int isSpool = 0;
669   CONTEXT f;
670   char mbox[_POSIX_PATH_MAX];
671   char buf[SHORT_STRING];
672
673   if (!ctx)
674     return 0;
675
676   ctx->closing = 1;
677
678 #ifdef USE_NNTP
679   if (ctx->magic == M_NNTP) {
680     int ret;
681
682     ret = nntp_close_mailbox (ctx);
683     mx_fastclose_mailbox (ctx);
684     return ret;
685   }
686 #endif
687   if (ctx->readonly || ctx->dontwrite) {
688     /* mailbox is readonly or we don't want to write */
689     mx_fastclose_mailbox (ctx);
690     return 0;
691   }
692
693   if (ctx->append) {
694     /* mailbox was opened in write-mode */
695     if (ctx->magic == M_MBOX || ctx->magic == M_MMDF)
696       mbox_close_mailbox (ctx);
697     else
698       mx_fastclose_mailbox (ctx);
699     return 0;
700   }
701
702   for (i = 0; i < ctx->msgcount; i++) {
703     if (!ctx->hdrs[i]->deleted && ctx->hdrs[i]->read
704         && !(ctx->hdrs[i]->flagged && option (OPTKEEPFLAGGED)))
705       read_msgs++;
706   }
707
708   if (read_msgs && quadoption (OPT_MOVE) != M_NO) {
709     char *p;
710
711     if ((p = mutt_find_hook (M_MBOXHOOK, ctx->path))) {
712       isSpool = 1;
713       m_strcpy(mbox, sizeof(mbox), p);
714     }
715     else {
716       m_strcpy(mbox, sizeof(mbox), NONULL(Inbox));
717       isSpool = mutt_is_spool (ctx->path) && !mutt_is_spool (mbox);
718     }
719     mutt_expand_path (mbox, sizeof (mbox));
720
721     if (isSpool) {
722       snprintf (buf, sizeof (buf), _("Move read messages to %s?"), mbox);
723       if ((move_messages = query_quadoption (OPT_MOVE, buf)) == -1) {
724         ctx->closing = 0;
725         return (-1);
726       }
727     }
728   }
729
730   /* 
731    * There is no point in asking whether or not to purge if we are
732    * just marking messages as "trash".
733    */
734   if (ctx->deleted && !(ctx->magic == M_MAILDIR && option (OPTMAILDIRTRASH))) {
735     snprintf (buf, sizeof (buf), ctx->deleted == 1
736               ? _("Purge %d deleted message?") :
737               _("Purge %d deleted messages?"), ctx->deleted);
738     if ((purge = query_quadoption (OPT_DELETE, buf)) < 0) {
739       ctx->closing = 0;
740       return (-1);
741     }
742   }
743
744   /* IMAP servers manage the OLD flag themselves */
745   if (ctx->magic != M_IMAP)
746     if (option (OPTMARKOLD)) {
747       for (i = 0; i < ctx->msgcount; i++) {
748         if (!ctx->hdrs[i]->deleted && !ctx->hdrs[i]->old)
749           mutt_set_flag (ctx, ctx->hdrs[i], M_OLD, 1);
750       }
751     }
752
753   if (move_messages) {
754     mutt_message (_("Moving read messages to %s..."), mbox);
755
756     /* try to use server-side copy first */
757     i = 1;
758
759     if (ctx->magic == M_IMAP && imap_is_magic (mbox, NULL) == M_IMAP) {
760       /* tag messages for moving, and clear old tags, if any */
761       for (i = 0; i < ctx->msgcount; i++)
762         if (ctx->hdrs[i]->read && !ctx->hdrs[i]->deleted
763             && !(ctx->hdrs[i]->flagged && option (OPTKEEPFLAGGED)))
764           ctx->hdrs[i]->tagged = 1;
765         else
766           ctx->hdrs[i]->tagged = 0;
767
768       i = imap_copy_messages (ctx, NULL, mbox, 1);
769     }
770
771     if (i == 0)                 /* success */
772       mutt_clear_error ();
773     else if (i == -1) {         /* horrible error, bail */
774       ctx->closing = 0;
775       return -1;
776     }
777     else                        /* use regular append-copy mode */
778     {
779       if (mx_open_mailbox (mbox, M_APPEND, &f) == NULL) {
780         ctx->closing = 0;
781         return -1;
782       }
783
784       for (i = 0; i < ctx->msgcount; i++) {
785         if (ctx->hdrs[i]->read && !ctx->hdrs[i]->deleted
786             && !(ctx->hdrs[i]->flagged && option (OPTKEEPFLAGGED))) {
787           if (mutt_append_message (&f, ctx, ctx->hdrs[i], 0, CH_UPDATE_LEN) ==
788               0) {
789             mutt_set_flag (ctx, ctx->hdrs[i], M_DELETE, 1);
790             mutt_set_flag (ctx, ctx->hdrs[i], M_APPENDED, 1);
791           }
792           else {
793             mx_close_mailbox (&f, NULL);
794             ctx->closing = 0;
795             return -1;
796           }
797         }
798       }
799
800       mx_close_mailbox (&f, NULL);
801     }
802
803   }
804   else if (!ctx->changed && ctx->deleted == 0) {
805     mutt_message _("Mailbox is unchanged.");
806
807     mx_fastclose_mailbox (ctx);
808     return 0;
809   }
810
811   /* copy mails to the trash before expunging */
812   if (purge && ctx->deleted)
813     if (trash_append (ctx) != 0) {
814       ctx->closing = 0;
815       return -1;
816     }
817
818   /* allow IMAP to preserve the deleted flag across sessions */
819   if (ctx->magic == M_IMAP) {
820     if ((check = imap_sync_mailbox (ctx, purge, index_hint)) != 0) {
821       ctx->closing = 0;
822       return check;
823     }
824   }
825   else
826   {
827     if (!purge) {
828       for (i = 0; i < ctx->msgcount; i++)
829         ctx->hdrs[i]->deleted = 0;
830       ctx->deleted = 0;
831     }
832
833     if (ctx->changed || ctx->deleted) {
834       if ((check = sync_mailbox (ctx, index_hint)) != 0) {
835         ctx->closing = 0;
836         return check;
837       }
838     }
839   }
840
841   if (move_messages)
842     mutt_message (_("%d kept, %d moved, %d deleted."),
843                   ctx->msgcount - ctx->deleted, read_msgs, ctx->deleted);
844   else
845     mutt_message (_("%d kept, %d deleted."),
846                   ctx->msgcount - ctx->deleted, ctx->deleted);
847
848   if (ctx->msgcount == ctx->deleted &&
849       (ctx->magic == M_MMDF || ctx->magic == M_MBOX) &&
850       !mutt_is_spool (ctx->path) && !option (OPTSAVEEMPTY))
851     mx_unlink_empty (ctx->path);
852
853   if (ctx->compressinfo && mutt_slow_close_compressed (ctx))
854     return (-1);
855
856   mx_fastclose_mailbox (ctx);
857
858   return 0;
859 }
860
861 int mx_close_mailbox (CONTEXT * ctx, int *index_hint) {
862   int ret = 0;
863   if (!ctx)
864     return (0);
865   ret = _mx_close_mailbox (ctx, index_hint);
866   sidebar_set_buffystats (ctx);
867   return (ret);
868 }
869
870 /* update a Context structure's internal tables. */
871
872 void mx_update_tables (CONTEXT * ctx, int committing)
873 {
874   int i, j;
875
876   /* update memory to reflect the new state of the mailbox */
877   ctx->vcount = 0;
878   ctx->vsize = 0;
879   ctx->tagged = 0;
880   ctx->deleted = 0;
881   ctx->new = 0;
882   ctx->unread = 0;
883   ctx->changed = 0;
884   ctx->flagged = 0;
885 #define this_body ctx->hdrs[j]->content
886   for (i = 0, j = 0; i < ctx->msgcount; i++) {
887     if ((committing && (!ctx->hdrs[i]->deleted ||
888                         (ctx->magic == M_MAILDIR
889                          && option (OPTMAILDIRTRASH)))) || (!committing
890                                                             && ctx->hdrs[i]->
891                                                             active)) {
892       if (i != j) {
893         ctx->hdrs[j] = ctx->hdrs[i];
894         ctx->hdrs[i] = NULL;
895       }
896       ctx->hdrs[j]->msgno = j;
897       if (ctx->hdrs[j]->virtual != -1) {
898         ctx->v2r[ctx->vcount] = j;
899         ctx->hdrs[j]->virtual = ctx->vcount++;
900         ctx->vsize += this_body->length + this_body->offset -
901           this_body->hdr_offset;
902       }
903
904       if (committing)
905         ctx->hdrs[j]->changed = 0;
906       else if (ctx->hdrs[j]->changed)
907         ctx->changed++;
908
909       if (!committing
910           || (ctx->magic == M_MAILDIR && option (OPTMAILDIRTRASH))) {
911         if (ctx->hdrs[j]->deleted)
912           ctx->deleted++;
913       }
914
915       if (ctx->hdrs[j]->tagged)
916         ctx->tagged++;
917       if (ctx->hdrs[j]->flagged)
918         ctx->flagged++;
919       if (!ctx->hdrs[j]->read) {
920         ctx->unread++;
921         if (!ctx->hdrs[j]->old)
922           ctx->new++;
923       }
924
925       j++;
926     }
927     else {
928       if (ctx->magic == M_MH || ctx->magic == M_MAILDIR)
929         ctx->size -= (ctx->hdrs[i]->content->length +
930                       ctx->hdrs[i]->content->offset -
931                       ctx->hdrs[i]->content->hdr_offset);
932       /* remove message from the hash tables */
933       if (ctx->subj_hash && ctx->hdrs[i]->env->real_subj)
934         hash_delete (ctx->subj_hash, ctx->hdrs[i]->env->real_subj,
935                      ctx->hdrs[i], NULL);
936       if (ctx->id_hash && ctx->hdrs[i]->env->message_id)
937         hash_delete (ctx->id_hash, ctx->hdrs[i]->env->message_id,
938                      ctx->hdrs[i], NULL);
939       header_delete(&ctx->hdrs[i]);
940     }
941   }
942 #undef this_body
943   ctx->msgcount = j;
944
945   /* update sidebar count */
946   sidebar_set_buffystats (ctx);
947 }
948
949
950 /* save changes to mailbox
951  *
952  * return values:
953  *      0               success
954  *      -1              error
955  */
956 static int _mx_sync_mailbox (CONTEXT * ctx, int *index_hint)
957 {
958   int rc, i;
959   int purge = 1;
960   int msgcount, deleted;
961
962   if (ctx->dontwrite) {
963     char buf[STRING], tmp[STRING];
964
965     if (km_expand_key (buf, sizeof (buf),
966                        km_find_func (MENU_MAIN, OP_TOGGLE_WRITE)))
967       snprintf (tmp, sizeof (tmp), _(" Press '%s' to toggle write"), buf);
968     else
969       m_strcpy(tmp, sizeof(tmp), _("Use 'toggle-write' to re-enable write!"));
970
971     mutt_error (_("Mailbox is marked unwritable. %s"), tmp);
972     return -1;
973   }
974   else if (ctx->readonly) {
975     mutt_error _("Mailbox is read-only.");
976
977     return -1;
978   }
979
980   if (!ctx->changed && !ctx->deleted) {
981     mutt_message _("Mailbox is unchanged.");
982
983     return (0);
984   }
985
986   if (ctx->deleted) {
987     char buf[SHORT_STRING];
988
989     snprintf (buf, sizeof (buf), ctx->deleted == 1
990               ? _("Purge %d deleted message?") :
991               _("Purge %d deleted messages?"), ctx->deleted);
992     if ((purge = query_quadoption (OPT_DELETE, buf)) < 0)
993       return (-1);
994     else if (purge == M_NO) {
995       if (!ctx->changed)
996         return 0;               /* nothing to do! */
997       /* let IMAP servers hold on to D flags */
998       if (ctx->magic != M_IMAP)
999       {
1000         for (i = 0; i < ctx->msgcount; i++)
1001           ctx->hdrs[i]->deleted = 0;
1002         ctx->deleted = 0;
1003       }
1004     }
1005     else if (ctx->last_tag && ctx->last_tag->deleted)
1006       ctx->last_tag = NULL;     /* reset last tagged msg now useless */
1007   }
1008
1009   /* really only for IMAP - imap_sync_mailbox results in a call to
1010    * mx_update_tables, so ctx->deleted is 0 when it comes back */
1011   msgcount = ctx->msgcount;
1012   deleted = ctx->deleted;
1013
1014   if (purge && ctx->deleted) {
1015     if (trash_append (ctx) == -1)
1016       return -1;
1017   }
1018
1019   if (ctx->magic == M_IMAP)
1020     rc = imap_sync_mailbox (ctx, purge, index_hint);
1021   else
1022     rc = sync_mailbox (ctx, index_hint);
1023   if (rc == 0) {
1024     if (ctx->magic == M_IMAP && !purge)
1025       mutt_message (_("Mailbox checkpointed."));
1026
1027     else
1028       mutt_message (_("%d kept, %d deleted."), msgcount - deleted, deleted);
1029
1030     mutt_sleep (0);
1031
1032     if (ctx->msgcount == ctx->deleted &&
1033         (ctx->magic == M_MBOX || ctx->magic == M_MMDF) &&
1034         !mutt_is_spool (ctx->path) && !option (OPTSAVEEMPTY)) {
1035       unlink (ctx->path);
1036       mx_fastclose_mailbox (ctx);
1037       return 0;
1038     }
1039
1040     /* if we haven't deleted any messages, we don't need to resort */
1041     /* ... except for certain folder formats which need "unsorted" 
1042      * sort order in order to synchronize folders.
1043      * 
1044      * MH and maildir are safe.  mbox-style seems to need re-sorting,
1045      * at least with the new threading code.
1046      */
1047     if (purge || (ctx->magic != M_MAILDIR && ctx->magic != M_MH)) {
1048       /* IMAP does this automatically after handling EXPUNGE */
1049       if (ctx->magic != M_IMAP)
1050       {
1051         mx_update_tables (ctx, 1);
1052         mutt_sort_headers (ctx, 1);     /* rethread from scratch */
1053       }
1054     }
1055   }
1056
1057   return (rc);
1058 }
1059
1060 int mx_sync_mailbox (CONTEXT* ctx, int* index_hint) {
1061   int ret = _mx_sync_mailbox (ctx, index_hint);
1062   sidebar_set_buffystats (ctx);
1063   return (ret);
1064 }
1065
1066 /* args:
1067  *      dest    destintation mailbox
1068  *      hdr     message being copied (required for maildir support, because
1069  *              the filename depends on the message flags)
1070  */
1071 MESSAGE *mx_open_new_message (CONTEXT * dest, HEADER * hdr, int flags)
1072 {
1073   MESSAGE *msg;
1074   address_t *p = NULL;
1075
1076   if (!MX_IDX(dest->magic-1)) {
1077     return (NULL);
1078   }
1079
1080   msg = p_new(MESSAGE, 1);
1081   msg->magic = dest->magic;
1082   msg->write = 1;
1083
1084   if (hdr) {
1085     msg->flags.flagged = hdr->flagged;
1086     msg->flags.replied = hdr->replied;
1087     msg->flags.read = hdr->read;
1088     msg->received = hdr->received;
1089   }
1090
1091   if (msg->received == 0)
1092     time (&msg->received);
1093
1094   if (mxfmts[dest->magic-1]->mx_open_new_message(msg, dest, hdr) == 0) {
1095     if (dest->magic == M_MMDF)
1096       fputs (MMDF_SEP, msg->fp);
1097
1098     if ((msg->magic == M_MBOX || msg->magic == M_MMDF) && flags & M_ADD_FROM) {
1099       if (hdr) {
1100         if (hdr->env->return_path)
1101           p = hdr->env->return_path;
1102         else if (hdr->env->sender)
1103           p = hdr->env->sender;
1104         else
1105           p = hdr->env->from;
1106       }
1107
1108       fprintf (msg->fp, "From %s %s", p ? p->mailbox : NONULL (Username),
1109                ctime (&msg->received));
1110     }
1111   }
1112   else
1113     p_delete(&msg);
1114
1115   return msg;
1116 }
1117
1118 /* check for new mail */
1119 int mx_check_mailbox (CONTEXT * ctx, int *index_hint, int lock) {
1120   if (ctx->compressinfo)
1121     return mutt_check_mailbox_compressed (ctx);
1122
1123   if (ctx) {
1124     if (ctx->locked)
1125       lock = 0;
1126     if (MX_IDX(ctx->magic-1) && mxfmts[ctx->magic-1]->mx_check_mailbox)
1127       return (mxfmts[ctx->magic-1]->mx_check_mailbox(ctx, index_hint, lock));
1128   }
1129
1130   return (-1);
1131 }
1132
1133 /* return a stream pointer for a message */
1134 MESSAGE *mx_open_message (CONTEXT * ctx, int msgno)
1135 {
1136   MESSAGE *msg;
1137
1138   msg = p_new(MESSAGE, 1);
1139   switch (msg->magic = ctx->magic) {
1140   case M_MBOX:
1141   case M_MMDF:
1142     msg->fp = ctx->fp;
1143     break;
1144
1145   case M_MH:
1146   case M_MAILDIR:
1147     {
1148       HEADER *cur = ctx->hdrs[msgno];
1149       char path[_POSIX_PATH_MAX];
1150
1151       snprintf (path, sizeof (path), "%s/%s", ctx->path, cur->path);
1152
1153       if ((msg->fp = fopen (path, "r")) == NULL && errno == ENOENT &&
1154           ctx->magic == M_MAILDIR)
1155         msg->fp = maildir_open_find_message (ctx->path, cur->path);
1156
1157       if (msg->fp == NULL) {
1158         mutt_perror (path);
1159         p_delete(&msg);
1160       }
1161     }
1162     break;
1163
1164   case M_IMAP:
1165     {
1166       if (imap_fetch_message (msg, ctx, msgno) != 0)
1167         p_delete(&msg);
1168       break;
1169     }
1170
1171   case M_POP:
1172     {
1173       if (pop_fetch_message (msg, ctx, msgno) != 0)
1174         p_delete(&msg);
1175       break;
1176     }
1177
1178 #ifdef USE_NNTP
1179   case M_NNTP:
1180     {
1181       if (nntp_fetch_message (msg, ctx, msgno) != 0)
1182         p_delete(&msg);
1183       break;
1184     }
1185 #endif /* USE_NNTP */
1186
1187   default:
1188     p_delete(&msg);
1189     break;
1190   }
1191   return (msg);
1192 }
1193
1194 /* commit a message to a folder */
1195
1196 int mx_commit_message (MESSAGE * msg, CONTEXT * ctx) {
1197   if (!(msg->write && ctx->append)) {
1198     return -1;
1199   }
1200   if (!ctx || !MX_IDX(ctx->magic-1) || !mxfmts[ctx->magic-1]->mx_commit_message)
1201     return (-1);
1202   return (mxfmts[ctx->magic-1]->mx_commit_message (msg, ctx));
1203 }
1204
1205 /* close a pointer to a message */
1206 int mx_close_message (MESSAGE ** msg)
1207 {
1208   int r = 0;
1209
1210   if ((*msg)->magic == M_MH || (*msg)->magic == M_MAILDIR
1211       || (*msg)->magic == M_IMAP
1212       || (*msg)->magic == M_POP
1213 #ifdef USE_NNTP
1214       || (*msg)->magic == M_NNTP
1215 #endif
1216     ) {
1217     r = safe_fclose (&(*msg)->fp);
1218   }
1219   else
1220     (*msg)->fp = NULL;
1221
1222   if ((*msg)->path) {
1223     unlink ((*msg)->path);
1224     p_delete(&(*msg)->path);
1225   }
1226
1227   p_delete(msg);
1228   return (r);
1229 }
1230
1231 void mx_alloc_memory (CONTEXT * ctx)
1232 {
1233   int i;
1234   size_t s = MAX (sizeof (HEADER *), sizeof (int));
1235
1236   if ((ctx->hdrmax + 25) * s < ctx->hdrmax * s) {
1237     mutt_error _("Integer overflow -- can't allocate memory.");
1238
1239     sleep (1);
1240     mutt_exit (1);
1241   }
1242
1243   if (ctx->hdrs) {
1244     p_realloc(&ctx->hdrs, ctx->hdrmax += 25);
1245     p_realloc(&ctx->v2r, ctx->hdrmax);
1246   }
1247   else {
1248     ctx->hdrs = p_new(HEADER *, (ctx->hdrmax += 25));
1249     ctx->v2r = p_new(int, ctx->hdrmax);
1250   }
1251   for (i = ctx->msgcount; i < ctx->hdrmax; i++) {
1252     ctx->hdrs[i] = NULL;
1253     ctx->v2r[i] = -1;
1254   }
1255 }
1256
1257 /* this routine is called to update the counts in the context structure for
1258  * the last message header parsed.
1259  */
1260 void mx_update_context (CONTEXT * ctx, int new_messages)
1261 {
1262   HEADER *h;
1263   int msgno;
1264
1265   for (msgno = ctx->msgcount - new_messages; msgno < ctx->msgcount; msgno++) {
1266     h = ctx->hdrs[msgno];
1267
1268     /* NOTE: this _must_ be done before the check for mailcap! */
1269     h->security = crypt_query (h->content);
1270
1271     if (!ctx->pattern) {
1272       ctx->v2r[ctx->vcount] = msgno;
1273       h->virtual = ctx->vcount++;
1274     }
1275     else
1276       h->virtual = -1;
1277     h->msgno = msgno;
1278
1279     if (h->env->supersedes) {
1280       HEADER *h2;
1281
1282       if (!ctx->id_hash)
1283         ctx->id_hash = mutt_make_id_hash (ctx);
1284
1285       h2 = hash_find (ctx->id_hash, h->env->supersedes);
1286
1287       /* p_delete(&h->env->supersedes); should I ? */
1288       if (h2) {
1289         h2->superseded = 1;
1290         if (!ctx->counting && option (OPTSCORE))
1291           mutt_score_message (ctx, h2, 1);
1292       }
1293     }
1294
1295     /* add this message to the hash tables */
1296     if (ctx->id_hash && h->env->message_id)
1297       hash_insert (ctx->id_hash, h->env->message_id, h, 0);
1298     if (!ctx->counting) {
1299       if (ctx->subj_hash && h->env->real_subj)
1300         hash_insert (ctx->subj_hash, h->env->real_subj, h, 1);
1301
1302       if (option (OPTSCORE))
1303         mutt_score_message (ctx, h, 0);
1304     }
1305
1306     if (h->changed)
1307       ctx->changed = 1;
1308     if (h->flagged)
1309       ctx->flagged++;
1310     if (h->deleted)
1311       ctx->deleted++;
1312     if (!h->read) {
1313       ctx->unread++;
1314       if (!h->old)
1315         ctx->new++;
1316     }
1317   }
1318   /* update sidebar count */
1319   sidebar_set_buffystats (ctx);
1320 }
1321
1322 /*
1323  * Return:
1324  * 1 if the specified mailbox contains 0 messages.
1325  * 0 if the mailbox contains messages
1326  * -1 on error
1327  */
1328 int mx_check_empty (const char *path)
1329 {
1330   int i = 0;
1331   if ((i = mx_get_idx (path)) >= 0 && mxfmts[i]->mx_check_empty)
1332     return (mxfmts[i]->mx_check_empty(path));
1333   errno = EINVAL;
1334   return (-1);
1335 }
1336
1337 int mx_acl_check (CONTEXT* ctx, int flag) {
1338   if (!ctx || !MX_IDX(ctx->magic-1))
1339     return (0);
1340   /* if no acl_check defined for module, assume permission is granted */
1341   if (!mxfmts[ctx->magic-1]->mx_acl_check)
1342     return (1);
1343   return (mxfmts[ctx->magic-1]->mx_acl_check(ctx,flag));
1344 }
1345
1346 int mx_rebuild_cache (void) {
1347 #ifndef USE_HCACHE
1348   mutt_error (_("Support for header caching was not build in."));
1349   return (1);
1350 #else
1351   int i = 0, magic = 0;
1352   CONTEXT* ctx = NULL;
1353   BUFFY* b = NULL;
1354
1355   if (list_empty(Incoming)) {
1356     mutt_error (_("No mailboxes defined."));
1357     return (1);
1358   }
1359
1360   for (i = 0; i < Incoming->length; i++) {
1361     b = (BUFFY*) Incoming->data[i];
1362     magic = mx_get_magic (b->path);
1363     if (magic != M_MAILDIR && magic != M_MH && magic != M_IMAP)
1364       continue;
1365     sidebar_set_current (b->path);
1366     sidebar_draw ();
1367     if ((ctx = mx_open_mailbox (b->path,
1368                                 M_READONLY | M_NOSORT | M_COUNT,
1369                                 NULL)) != NULL)
1370       mx_close_mailbox (ctx, 0);
1371   }
1372   mutt_clear_error ();
1373
1374   if (Context && Context->path)
1375     sidebar_set_current (Context->path);
1376   sidebar_draw ();
1377
1378   return (0);
1379 #endif
1380 }
1381
1382 void mutt_parse_mime_message (CONTEXT * ctx, HEADER * cur)
1383 {
1384   MESSAGE *msg;
1385   int flags = 0;
1386
1387   do {
1388     if (cur->content->type != TYPEMESSAGE
1389         && cur->content->type != TYPEMULTIPART)
1390       break;                     /* nothing to do */
1391
1392     if (cur->content->parts)
1393       break;                     /* The message was parsed earlier. */
1394
1395     if ((msg = mx_open_message (ctx, cur->msgno))) {
1396       mutt_parse_part (msg->fp, cur->content);
1397
1398       cur->security = crypt_query (cur->content);
1399
1400       mx_close_message (&msg);
1401     }
1402   } while (0);
1403   mutt_count_body_parts (cur, flags | M_PARTS_RECOUNT);
1404 }