Rocco Rutte:
[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 #if HAVE_CONFIG_H
12 # include "config.h"
13 #endif
14
15 #include "mutt.h"
16 #include "mx.h"
17 #include "mbox.h"
18 #include "mh.h"
19 #include "rfc2047.h"
20 #include "sort.h"
21 #include "copy.h"
22 #include "keymap.h"
23 #include "url.h"
24
25 #ifdef USE_COMPRESSED
26 #include "compress.h"
27 #endif
28
29 #ifdef USE_IMAP
30 #include "imap.h"
31 #endif
32
33 #ifdef USE_POP
34 #include "pop.h"
35 #endif
36
37 #ifdef USE_NNTP
38 #include "nntp.h"
39 #endif
40
41 #ifdef BUFFY_SIZE
42 #include "buffy.h"
43 #endif
44
45 #ifdef USE_DOTLOCK
46 #include "dotlock.h"
47 #endif
48
49 #include "mutt_crypt.h"
50
51 #include "lib/mem.h"
52 #include "lib/intl.h"
53 #include "lib/str.h"
54
55 #include <dirent.h>
56 #include <fcntl.h>
57 #include <sys/file.h>
58 #include <sys/stat.h>
59 #include <errno.h>
60 #include <unistd.h>
61 #include <stdlib.h>
62 #include <string.h>
63 #include <ctype.h>
64 #ifndef BUFFY_SIZE
65 #include <utime.h>
66 #endif
67
68
69 #define mutt_is_spool(s)  (safe_strcmp (Spoolfile, s) == 0)
70
71 #ifdef USE_DOTLOCK
72 /* parameters: 
73  * path - file to lock
74  * retry - should retry if unable to lock?
75  */
76
77 #ifdef DL_STANDALONE
78
79 static int invoke_dotlock (const char *path, int dummy, int flags, int retry)
80 {
81   char cmd[LONG_STRING + _POSIX_PATH_MAX];
82   char f[SHORT_STRING + _POSIX_PATH_MAX];
83   char r[SHORT_STRING];
84
85   if (flags & DL_FL_RETRY)
86     snprintf (r, sizeof (r), "-r %d ", retry ? MAXLOCKATTEMPT : 0);
87
88   mutt_quote_filename (f, sizeof (f), path);
89
90   snprintf (cmd, sizeof (cmd),
91             "%s %s%s%s%s%s%s%s",
92             NONULL (MuttDotlock),
93             flags & DL_FL_TRY ? "-t " : "",
94             flags & DL_FL_UNLOCK ? "-u " : "",
95             flags & DL_FL_USEPRIV ? "-p " : "",
96             flags & DL_FL_FORCE ? "-f " : "",
97             flags & DL_FL_UNLINK ? "-d " : "",
98             flags & DL_FL_RETRY ? r : "", f);
99
100   return mutt_system (cmd);
101 }
102
103 #else
104
105 #define invoke_dotlock dotlock_invoke
106
107 #endif
108
109 static int dotlock_file (const char *path, int fd, int retry)
110 {
111   int r;
112   int flags = DL_FL_USEPRIV | DL_FL_RETRY;
113
114   if (retry)
115     retry = 1;
116
117 retry_lock:
118   if ((r = invoke_dotlock (path, fd, flags, retry)) == DL_EX_EXIST) {
119     if (!option (OPTNOCURSES)) {
120       char msg[LONG_STRING];
121
122       snprintf (msg, sizeof (msg),
123                 _("Lock count exceeded, remove lock for %s?"), path);
124       if (retry && mutt_yesorno (msg, M_YES) == M_YES) {
125         flags |= DL_FL_FORCE;
126         retry--;
127         mutt_clear_error ();
128         goto retry_lock;
129       }
130     }
131     else {
132       mutt_error (_("Can't dotlock %s.\n"), path);
133     }
134   }
135   return (r == DL_EX_OK ? 0 : -1);
136 }
137
138 static int undotlock_file (const char *path, int fd)
139 {
140   return (invoke_dotlock (path, fd, DL_FL_USEPRIV | DL_FL_UNLOCK, 0) ==
141           DL_EX_OK ? 0 : -1);
142 }
143
144 #endif /* USE_DOTLOCK */
145
146 /* Args:
147  *      excl            if excl != 0, request an exclusive lock
148  *      dot             if dot != 0, try to dotlock the file
149  *      timeout         should retry locking?
150  */
151 int mx_lock_file (const char *path, int fd, int excl, int dot, int timeout)
152 {
153 #if defined (USE_FCNTL) || defined (USE_FLOCK)
154   int count;
155   int attempt;
156   struct stat prev_sb;
157 #endif
158   int r = 0;
159
160 #ifdef USE_FCNTL
161   struct flock lck;
162
163
164   memset (&lck, 0, sizeof (struct flock));
165   lck.l_type = excl ? F_WRLCK : F_RDLCK;
166   lck.l_whence = SEEK_SET;
167
168   count = 0;
169   attempt = 0;
170   while (fcntl (fd, F_SETLK, &lck) == -1) {
171     struct stat sb;
172
173     dprint (1, (debugfile, "mx_lock_file(): fcntl errno %d.\n", errno));
174     if (errno != EAGAIN && errno != EACCES) {
175       mutt_perror ("fcntl");
176       return (-1);
177     }
178
179     if (fstat (fd, &sb) != 0)
180       sb.st_size = 0;
181
182     if (count == 0)
183       prev_sb = sb;
184
185     /* only unlock file if it is unchanged */
186     if (prev_sb.st_size == sb.st_size
187         && ++count >= (timeout ? MAXLOCKATTEMPT : 0)) {
188       if (timeout)
189         mutt_error _("Timeout exceeded while attempting fcntl lock!");
190
191       return (-1);
192     }
193
194     prev_sb = sb;
195
196     mutt_message (_("Waiting for fcntl lock... %d"), ++attempt);
197     sleep (1);
198   }
199 #endif /* USE_FCNTL */
200
201 #ifdef USE_FLOCK
202   count = 0;
203   attempt = 0;
204   while (flock (fd, (excl ? LOCK_EX : LOCK_SH) | LOCK_NB) == -1) {
205     struct stat sb;
206
207     if (errno != EWOULDBLOCK) {
208       mutt_perror ("flock");
209       r = -1;
210       break;
211     }
212
213     if (fstat (fd, &sb) != 0)
214       sb.st_size = 0;
215
216     if (count == 0)
217       prev_sb = sb;
218
219     /* only unlock file if it is unchanged */
220     if (prev_sb.st_size == sb.st_size
221         && ++count >= (timeout ? MAXLOCKATTEMPT : 0)) {
222       if (timeout)
223         mutt_error _("Timeout exceeded while attempting flock lock!");
224
225       r = -1;
226       break;
227     }
228
229     prev_sb = sb;
230
231     mutt_message (_("Waiting for flock attempt... %d"), ++attempt);
232     sleep (1);
233   }
234 #endif /* USE_FLOCK */
235
236 #ifdef USE_DOTLOCK
237   if (r == 0 && dot)
238     r = dotlock_file (path, fd, timeout);
239 #endif /* USE_DOTLOCK */
240
241   if (r == -1) {
242     /* release any other locks obtained in this routine */
243
244 #ifdef USE_FCNTL
245     lck.l_type = F_UNLCK;
246     fcntl (fd, F_SETLK, &lck);
247 #endif /* USE_FCNTL */
248
249 #ifdef USE_FLOCK
250     flock (fd, LOCK_UN);
251 #endif /* USE_FLOCK */
252
253     return (-1);
254   }
255
256   return 0;
257 }
258
259 int mx_unlock_file (const char *path, int fd, int dot)
260 {
261 #ifdef USE_FCNTL
262   struct flock unlockit = { F_UNLCK, 0, 0, 0 };
263
264   memset (&unlockit, 0, sizeof (struct flock));
265   unlockit.l_type = F_UNLCK;
266   unlockit.l_whence = SEEK_SET;
267   fcntl (fd, F_SETLK, &unlockit);
268 #endif
269
270 #ifdef USE_FLOCK
271   flock (fd, LOCK_UN);
272 #endif
273
274 #ifdef USE_DOTLOCK
275   if (dot)
276     undotlock_file (path, fd);
277 #endif
278
279   return 0;
280 }
281
282 void mx_unlink_empty (const char *path)
283 {
284   int fd;
285
286 #ifndef USE_DOTLOCK
287   struct stat sb;
288 #endif
289
290   if ((fd = open (path, O_RDWR)) == -1)
291     return;
292
293   if (mx_lock_file (path, fd, 1, 0, 1) == -1) {
294     close (fd);
295     return;
296   }
297
298 #ifdef USE_DOTLOCK
299   invoke_dotlock (path, fd, DL_FL_UNLINK, 1);
300 #else
301   if (fstat (fd, &sb) == 0 && sb.st_size == 0)
302     unlink (path);
303 #endif
304
305   mx_unlock_file (path, fd, 0);
306   close (fd);
307 }
308
309 /* try to figure out what type of mailbox ``path'' is
310  *
311  * return values:
312  *      M_*     mailbox type
313  *      0       not a mailbox
314  *      -1      error
315  */
316
317 #ifdef USE_IMAP
318 int mx_is_imap (const char *p)
319 {
320   url_scheme_t scheme;
321
322   if (!p)
323     return 0;
324
325   if (*p == '{')
326     return 1;
327
328   scheme = url_check_scheme (p);
329   if (scheme == U_IMAP || scheme == U_IMAPS)
330     return 1;
331
332   return 0;
333 }
334 #endif
335
336 #ifdef USE_POP
337 int mx_is_pop (const char *p)
338 {
339   url_scheme_t scheme;
340
341   if (!p)
342     return 0;
343
344   scheme = url_check_scheme (p);
345   if (scheme == U_POP || scheme == U_POPS)
346     return 1;
347
348   return 0;
349 }
350 #endif
351
352 #ifdef USE_NNTP
353 int mx_is_nntp (const char *p)
354 {
355   url_scheme_t scheme;
356
357   if (!p)
358     return 0;
359
360   scheme = url_check_scheme (p);
361   if (scheme == U_NNTP || scheme == U_NNTPS)
362     return 1;
363
364   return 0;
365 }
366 #endif
367
368 int mx_get_magic (const char *path)
369 {
370   struct stat st;
371   int magic = 0;
372   char tmp[_POSIX_PATH_MAX];
373   FILE *f;
374
375 #ifdef USE_IMAP
376   if (mx_is_imap (path))
377     return M_IMAP;
378 #endif /* USE_IMAP */
379
380 #ifdef USE_POP
381   if (mx_is_pop (path))
382     return M_POP;
383 #endif /* USE_POP */
384
385 #ifdef USE_NNTP
386   if (mx_is_nntp (path))
387     return M_NNTP;
388 #endif /* USE_NNTP */
389
390   if (stat (path, &st) == -1) {
391     dprint (1,
392             (debugfile, "mx_get_magic(): unable to stat %s: %s (errno %d).\n",
393              path, strerror (errno), errno));
394     return (-1);
395   }
396
397   if (S_ISDIR (st.st_mode)) {
398     /* check for maildir-style mailbox */
399
400     snprintf (tmp, sizeof (tmp), "%s/cur", path);
401     if (stat (tmp, &st) == 0 && S_ISDIR (st.st_mode))
402       return (M_MAILDIR);
403
404     /* check for mh-style mailbox */
405
406     snprintf (tmp, sizeof (tmp), "%s/.mh_sequences", path);
407     if (access (tmp, F_OK) == 0)
408       return (M_MH);
409
410     snprintf (tmp, sizeof (tmp), "%s/.xmhcache", path);
411     if (access (tmp, F_OK) == 0)
412       return (M_MH);
413
414     snprintf (tmp, sizeof (tmp), "%s/.mew_cache", path);
415     if (access (tmp, F_OK) == 0)
416       return (M_MH);
417
418     snprintf (tmp, sizeof (tmp), "%s/.mew-cache", path);
419     if (access (tmp, F_OK) == 0)
420       return (M_MH);
421
422     snprintf (tmp, sizeof (tmp), "%s/.sylpheed_cache", path);
423     if (access (tmp, F_OK) == 0)
424       return (M_MH);
425
426     /* 
427      * ok, this isn't an mh folder, but mh mode can be used to read
428      * Usenet news from the spool. ;-) 
429      */
430
431     snprintf (tmp, sizeof (tmp), "%s/.overview", path);
432     if (access (tmp, F_OK) == 0)
433       return (M_MH);
434
435   }
436   else if (st.st_size == 0) {
437     /* hard to tell what zero-length files are, so assume the default magic */
438     if (DefaultMagic == M_MBOX || DefaultMagic == M_MMDF)
439       return (DefaultMagic);
440     else
441       return (M_MBOX);
442   }
443   else if ((f = fopen (path, "r")) != NULL) {
444 #ifndef BUFFY_SIZE
445     struct utimbuf times;
446 #endif
447
448     fgets (tmp, sizeof (tmp), f);
449     if (safe_strncmp ("From ", tmp, 5) == 0)
450       magic = M_MBOX;
451     else if (safe_strcmp (MMDF_SEP, tmp) == 0)
452       magic = M_MMDF;
453     safe_fclose (&f);
454 #ifndef BUFFY_SIZE
455     /* need to restore the times here, the file was not really accessed,
456      * only the type was accessed.  This is important, because detection
457      * of "new mail" depends on those times set correctly.
458      */
459     times.actime = st.st_atime;
460     times.modtime = st.st_mtime;
461     utime (path, &times);
462 #endif
463   }
464   else {
465     dprint (1,
466             (debugfile,
467              "mx_get_magic(): unable to open file %s for reading.\n", path));
468     mutt_perror (path);
469     return (-1);
470   }
471
472 #ifdef USE_COMPRESSED
473   if (magic == 0 && mutt_can_read_compressed (path))
474     return M_COMPRESSED;
475 #endif
476
477   return (magic);
478 }
479
480 /*
481  * set DefaultMagic to the given value
482  */
483 int mx_set_magic (const char *s)
484 {
485   if (ascii_strcasecmp (s, "mbox") == 0)
486     DefaultMagic = M_MBOX;
487   else if (ascii_strcasecmp (s, "mmdf") == 0)
488     DefaultMagic = M_MMDF;
489   else if (ascii_strcasecmp (s, "mh") == 0)
490     DefaultMagic = M_MH;
491   else if (ascii_strcasecmp (s, "maildir") == 0)
492     DefaultMagic = M_MAILDIR;
493   else
494     return (-1);
495
496   return 0;
497 }
498
499 /* mx_access: Wrapper for access, checks permissions on a given mailbox.
500  *   We may be interested in using ACL-style flags at some point, currently
501  *   we use the normal access() flags. */
502 int mx_access (const char *path, int flags)
503 {
504 #ifdef USE_IMAP
505   if (mx_is_imap (path))
506     return imap_access (path, flags);
507 #endif
508
509   return access (path, flags);
510 }
511
512 static int mx_open_mailbox_append (CONTEXT * ctx, int flags)
513 {
514   struct stat sb;
515
516 #ifdef USE_COMPRESSED
517   /* special case for appending to compressed folders -
518    * even if we can not open them for reading */
519   if (mutt_can_append_compressed (ctx->path))
520     mutt_open_append_compressed (ctx);
521 #endif
522
523   ctx->append = 1;
524
525 #ifdef USE_IMAP
526
527   if (mx_is_imap (ctx->path))
528     return imap_open_mailbox_append (ctx);
529
530 #endif
531
532   if (stat (ctx->path, &sb) == 0) {
533     ctx->magic = mx_get_magic (ctx->path);
534
535     switch (ctx->magic) {
536     case 0:
537       mutt_error (_("%s is not a mailbox."), ctx->path);
538       /* fall through */
539     case -1:
540       return (-1);
541     }
542   }
543   else if (errno == ENOENT) {
544     ctx->magic = DefaultMagic;
545
546     if (ctx->magic == M_MH || ctx->magic == M_MAILDIR) {
547       char tmp[_POSIX_PATH_MAX];
548
549       if (mkdir (ctx->path, S_IRWXU)) {
550         mutt_perror (ctx->path);
551         return (-1);
552       }
553
554       if (ctx->magic == M_MAILDIR) {
555         snprintf (tmp, sizeof (tmp), "%s/cur", ctx->path);
556         if (mkdir (tmp, S_IRWXU)) {
557           mutt_perror (tmp);
558           rmdir (ctx->path);
559           return (-1);
560         }
561
562         snprintf (tmp, sizeof (tmp), "%s/new", ctx->path);
563         if (mkdir (tmp, S_IRWXU)) {
564           mutt_perror (tmp);
565           snprintf (tmp, sizeof (tmp), "%s/cur", ctx->path);
566           rmdir (tmp);
567           rmdir (ctx->path);
568           return (-1);
569         }
570         snprintf (tmp, sizeof (tmp), "%s/tmp", ctx->path);
571         if (mkdir (tmp, S_IRWXU)) {
572           mutt_perror (tmp);
573           snprintf (tmp, sizeof (tmp), "%s/cur", ctx->path);
574           rmdir (tmp);
575           snprintf (tmp, sizeof (tmp), "%s/new", ctx->path);
576           rmdir (tmp);
577           rmdir (ctx->path);
578           return (-1);
579         }
580       }
581       else {
582         int i;
583
584         snprintf (tmp, sizeof (tmp), "%s/.mh_sequences", ctx->path);
585         if ((i = creat (tmp, S_IRWXU)) == -1) {
586           mutt_perror (tmp);
587           rmdir (ctx->path);
588           return (-1);
589         }
590         close (i);
591       }
592     }
593   }
594   else {
595     mutt_perror (ctx->path);
596     return (-1);
597   }
598
599   switch (ctx->magic) {
600   case M_MBOX:
601   case M_MMDF:
602     if ((ctx->fp =
603          safe_fopen (ctx->path, flags & M_NEWFOLDER ? "w" : "a")) == NULL
604         || mbox_lock_mailbox (ctx, 1, 1) != 0) {
605       if (!ctx->fp)
606         mutt_perror (ctx->path);
607       else {
608         mutt_error (_("Couldn't lock %s\n"), ctx->path);
609         safe_fclose (&ctx->fp);
610       }
611       return (-1);
612     }
613     fseek (ctx->fp, 0, 2);
614     break;
615
616   case M_MH:
617   case M_MAILDIR:
618     /* nothing to do */
619     break;
620
621   default:
622     return (-1);
623   }
624
625   return 0;
626 }
627
628 /*
629  * open a mailbox and parse it
630  *
631  * Args:
632  *      flags   M_NOSORT        do not sort mailbox
633  *              M_APPEND        open mailbox for appending
634  *              M_READONLY      open mailbox in read-only mode
635  *              M_QUIET         only print error messages
636  *      ctx     if non-null, context struct to use
637  */
638 CONTEXT *mx_open_mailbox (const char *path, int flags, CONTEXT * pctx)
639 {
640   CONTEXT *ctx = pctx;
641   int rc;
642
643   if (!ctx)
644     ctx = safe_malloc (sizeof (CONTEXT));
645   memset (ctx, 0, sizeof (CONTEXT));
646   ctx->path = safe_strdup (path);
647
648   ctx->msgnotreadyet = -1;
649   ctx->collapsed = 0;
650
651   if (flags & M_QUIET)
652     ctx->quiet = 1;
653   if (flags & M_READONLY)
654     ctx->readonly = 1;
655
656   if (flags & (M_APPEND | M_NEWFOLDER)) {
657     if (mx_open_mailbox_append (ctx, flags) != 0) {
658       mx_fastclose_mailbox (ctx);
659       if (!pctx)
660         FREE (&ctx);
661       return NULL;
662     }
663     return ctx;
664   }
665
666   ctx->magic = mx_get_magic (path);
667
668 #ifdef USE_COMPRESSED
669   if (ctx->magic == M_COMPRESSED)
670     mutt_open_read_compressed (ctx);
671 #endif
672
673   if (ctx->magic == 0)
674     mutt_error (_("%s is not a mailbox."), path);
675
676   if (ctx->magic == -1)
677     mutt_perror (path);
678
679   if (ctx->magic <= 0) {
680     mx_fastclose_mailbox (ctx);
681     if (!pctx)
682       FREE (&ctx);
683     return (NULL);
684   }
685
686   /* if the user has a `push' command in their .muttrc, or in a folder-hook,
687    * it will cause the progress messages not to be displayed because
688    * mutt_refresh() will think we are in the middle of a macro.  so set a
689    * flag to indicate that we should really refresh the screen.
690    */
691   set_option (OPTFORCEREFRESH);
692
693   if (!ctx->quiet)
694     mutt_message (_("Reading %s..."), ctx->path);
695
696   switch (ctx->magic) {
697   case M_MH:
698     rc = mh_read_dir (ctx, NULL);
699     break;
700
701   case M_MAILDIR:
702     rc = maildir_read_dir (ctx);
703     break;
704
705   case M_MMDF:
706   case M_MBOX:
707     rc = mbox_open_mailbox (ctx);
708     break;
709
710 #ifdef USE_IMAP
711   case M_IMAP:
712     rc = imap_open_mailbox (ctx);
713     break;
714 #endif /* USE_IMAP */
715
716 #ifdef USE_POP
717   case M_POP:
718     rc = pop_open_mailbox (ctx);
719     break;
720 #endif /* USE_POP */
721
722 #ifdef USE_NNTP
723   case M_NNTP:
724     rc = nntp_open_mailbox (ctx);
725     break;
726 #endif /* USE_NNTP */
727
728   default:
729     rc = -1;
730     break;
731   }
732
733   if (rc == 0) {
734     if ((flags & M_NOSORT) == 0) {
735       /* avoid unnecessary work since the mailbox is completely unthreaded
736          to begin with */
737       unset_option (OPTSORTSUBTHREADS);
738       unset_option (OPTNEEDRESCORE);
739       mutt_sort_headers (ctx, 1);
740     }
741     if (!ctx->quiet)
742       mutt_clear_error ();
743   }
744   else {
745     mx_fastclose_mailbox (ctx);
746     if (!pctx)
747       FREE (&ctx);
748   }
749
750   unset_option (OPTFORCEREFRESH);
751   return (ctx);
752 }
753
754 /* free up memory associated with the mailbox context */
755 void mx_fastclose_mailbox (CONTEXT * ctx)
756 {
757   int i;
758
759   if (!ctx)
760     return;
761
762 #ifdef USE_IMAP
763   if (ctx->magic == M_IMAP)
764     imap_close_mailbox (ctx);
765 #endif /* USE_IMAP */
766 #ifdef USE_POP
767   if (ctx->magic == M_POP)
768     pop_close_mailbox (ctx);
769 #endif /* USE_POP */
770 #ifdef USE_NNTP
771   if (ctx->magic == M_NNTP)
772     nntp_fastclose_mailbox (ctx);
773 #endif /* USE_NNTP */
774   if (ctx->subj_hash)
775     hash_destroy (&ctx->subj_hash, NULL);
776   if (ctx->id_hash)
777     hash_destroy (&ctx->id_hash, NULL);
778   mutt_clear_threads (ctx);
779   for (i = 0; i < ctx->msgcount; i++)
780     mutt_free_header (&ctx->hdrs[i]);
781   FREE (&ctx->hdrs);
782   FREE (&ctx->v2r);
783 #ifdef USE_COMPRESSED
784   if (ctx->compressinfo)
785     mutt_fast_close_compressed (ctx);
786 #endif
787   FREE (&ctx->path);
788   FREE (&ctx->pattern);
789   if (ctx->limit_pattern)
790     mutt_pattern_free (&ctx->limit_pattern);
791   safe_fclose (&ctx->fp);
792   memset (ctx, 0, sizeof (CONTEXT));
793 }
794
795 /* save changes to disk */
796 static int sync_mailbox (CONTEXT * ctx, int *index_hint)
797 {
798 #ifdef BUFFY_SIZE
799   BUFFY *tmp = NULL;
800 #endif
801   int rc = -1;
802
803   if (!ctx->quiet)
804     mutt_message (_("Writing %s..."), ctx->path);
805
806   switch (ctx->magic) {
807   case M_MBOX:
808   case M_MMDF:
809     rc = mbox_sync_mailbox (ctx, index_hint);
810 #ifdef BUFFY_SIZE
811     tmp = mutt_find_mailbox (ctx->path);
812 #endif
813     break;
814
815   case M_MH:
816   case M_MAILDIR:
817     rc = mh_sync_mailbox (ctx, index_hint);
818     break;
819
820 #ifdef USE_IMAP
821   case M_IMAP:
822     /* extra argument means EXPUNGE */
823     rc = imap_sync_mailbox (ctx, 1, index_hint);
824     break;
825 #endif /* USE_IMAP */
826
827 #ifdef USE_POP
828   case M_POP:
829     rc = pop_sync_mailbox (ctx, index_hint);
830     break;
831 #endif /* USE_POP */
832
833 #ifdef USE_NNTP
834   case M_NNTP:
835     rc = nntp_sync_mailbox (ctx);
836     break;
837 #endif /* USE_NNTP */
838   }
839
840 #if 0
841   if (!ctx->quiet && !ctx->shutup && rc == -1)
842     mutt_error (_("Could not synchronize mailbox %s!"), ctx->path);
843 #endif
844
845 #ifdef BUFFY_SIZE
846   if (tmp && tmp->new == 0)
847     mutt_update_mailbox (tmp);
848 #endif
849
850 #ifdef USE_COMPRESSED
851   if (rc == 0 && ctx->compressinfo)
852     return mutt_sync_compressed (ctx);
853 #endif
854
855   return rc;
856 }
857
858 /* move deleted mails to the trash folder */
859 static int trash_append (CONTEXT * ctx)
860 {
861   CONTEXT *ctx_trash;
862   int i = 0;
863   struct stat st, stc;
864
865   if (!TrashPath || !ctx->deleted ||
866       (ctx->magic == M_MAILDIR && option (OPTMAILDIRTRASH)))
867     return 0;
868
869   for (; i < ctx->msgcount && (!ctx->hdrs[i]->deleted ||
870                                ctx->hdrs[i]->appended); i++);
871   if (i == ctx->msgcount)
872     return 0;                   /* nothing to be done */
873
874   if (mutt_save_confirm (TrashPath, &st) != 0) {
875     mutt_error _("message(s) not deleted");
876
877     return -1;
878   }
879
880   if (lstat (ctx->path, &stc) == 0 && stc.st_ino == st.st_ino
881       && stc.st_dev == st.st_dev && stc.st_rdev == st.st_rdev)
882     return 0;                   /* we are in the trash folder: simple sync */
883
884   if ((ctx_trash = mx_open_mailbox (TrashPath, M_APPEND, NULL)) != NULL) {
885     for (i = 0; i < ctx->msgcount; i++)
886       if (ctx->hdrs[i]->deleted && !ctx->hdrs[i]->appended
887           && !ctx->hdrs[i]->purged
888           && mutt_append_message (ctx_trash, ctx, ctx->hdrs[i], 0, 0) == -1) {
889         mx_close_mailbox (ctx_trash, NULL);
890         return -1;
891       }
892
893     mx_close_mailbox (ctx_trash, NULL);
894   }
895   else {
896     mutt_error _("Can't open trash folder");
897
898     return -1;
899   }
900
901   return 0;
902 }
903
904 /* save changes and close mailbox */
905 int mx_close_mailbox (CONTEXT * ctx, int *index_hint)
906 {
907   int i, move_messages = 0, purge = 1, read_msgs = 0;
908   int check;
909   int isSpool = 0;
910   CONTEXT f;
911   char mbox[_POSIX_PATH_MAX];
912   char buf[SHORT_STRING];
913
914   if (!ctx)
915     return 0;
916
917   ctx->closing = 1;
918
919 #ifdef USE_NNTP
920   if (ctx->magic == M_NNTP) {
921     int ret;
922
923     ret = nntp_close_mailbox (ctx);
924     mx_fastclose_mailbox (ctx);
925     return ret;
926   }
927 #endif
928   if (ctx->readonly || ctx->dontwrite) {
929     /* mailbox is readonly or we don't want to write */
930     mx_fastclose_mailbox (ctx);
931     return 0;
932   }
933
934   if (ctx->append) {
935     /* mailbox was opened in write-mode */
936     if (ctx->magic == M_MBOX || ctx->magic == M_MMDF)
937       mbox_close_mailbox (ctx);
938     else
939       mx_fastclose_mailbox (ctx);
940     return 0;
941   }
942
943   for (i = 0; i < ctx->msgcount; i++) {
944     if (!ctx->hdrs[i]->deleted && ctx->hdrs[i]->read
945         && !(ctx->hdrs[i]->flagged && option (OPTKEEPFLAGGED)))
946       read_msgs++;
947   }
948
949   if (read_msgs && quadoption (OPT_MOVE) != M_NO) {
950     char *p;
951
952     if ((p = mutt_find_hook (M_MBOXHOOK, ctx->path))) {
953       isSpool = 1;
954       strfcpy (mbox, p, sizeof (mbox));
955     }
956     else {
957       strfcpy (mbox, NONULL (Inbox), sizeof (mbox));
958       isSpool = mutt_is_spool (ctx->path) && !mutt_is_spool (mbox);
959     }
960     mutt_expand_path (mbox, sizeof (mbox));
961
962     if (isSpool) {
963       snprintf (buf, sizeof (buf), _("Move read messages to %s?"), mbox);
964       if ((move_messages = query_quadoption (OPT_MOVE, buf)) == -1) {
965         ctx->closing = 0;
966         return (-1);
967       }
968     }
969   }
970
971   /* 
972    * There is no point in asking whether or not to purge if we are
973    * just marking messages as "trash".
974    */
975   if (ctx->deleted && !(ctx->magic == M_MAILDIR && option (OPTMAILDIRTRASH))) {
976     snprintf (buf, sizeof (buf), ctx->deleted == 1
977               ? _("Purge %d deleted message?") :
978               _("Purge %d deleted messages?"), ctx->deleted);
979     if ((purge = query_quadoption (OPT_DELETE, buf)) < 0) {
980       ctx->closing = 0;
981       return (-1);
982     }
983   }
984
985 #ifdef USE_IMAP
986   /* IMAP servers manage the OLD flag themselves */
987   if (ctx->magic != M_IMAP)
988 #endif
989     if (option (OPTMARKOLD)) {
990       for (i = 0; i < ctx->msgcount; i++) {
991         if (!ctx->hdrs[i]->deleted && !ctx->hdrs[i]->old)
992           mutt_set_flag (ctx, ctx->hdrs[i], M_OLD, 1);
993       }
994     }
995
996   if (move_messages) {
997     mutt_message (_("Moving read messages to %s..."), mbox);
998
999 #ifdef USE_IMAP
1000     /* try to use server-side copy first */
1001     i = 1;
1002
1003     if (ctx->magic == M_IMAP && mx_is_imap (mbox)) {
1004       /* tag messages for moving, and clear old tags, if any */
1005       for (i = 0; i < ctx->msgcount; i++)
1006         if (ctx->hdrs[i]->read && !ctx->hdrs[i]->deleted
1007             && !(ctx->hdrs[i]->flagged && option (OPTKEEPFLAGGED)))
1008           ctx->hdrs[i]->tagged = 1;
1009         else
1010           ctx->hdrs[i]->tagged = 0;
1011
1012       i = imap_copy_messages (ctx, NULL, mbox, 1);
1013     }
1014
1015     if (i == 0)                 /* success */
1016       mutt_clear_error ();
1017     else if (i == -1) {         /* horrible error, bail */
1018       ctx->closing = 0;
1019       return -1;
1020     }
1021     else                        /* use regular append-copy mode */
1022 #endif
1023     {
1024       if (mx_open_mailbox (mbox, M_APPEND, &f) == NULL) {
1025         ctx->closing = 0;
1026         return -1;
1027       }
1028
1029       for (i = 0; i < ctx->msgcount; i++) {
1030         if (ctx->hdrs[i]->read && !ctx->hdrs[i]->deleted
1031             && !(ctx->hdrs[i]->flagged && option (OPTKEEPFLAGGED))) {
1032           if (mutt_append_message (&f, ctx, ctx->hdrs[i], 0, CH_UPDATE_LEN) ==
1033               0) {
1034             mutt_set_flag (ctx, ctx->hdrs[i], M_DELETE, 1);
1035             mutt_set_flag (ctx, ctx->hdrs[i], M_APPENDED, 1);
1036           }
1037           else {
1038             mx_close_mailbox (&f, NULL);
1039             ctx->closing = 0;
1040             return -1;
1041           }
1042         }
1043       }
1044
1045       mx_close_mailbox (&f, NULL);
1046     }
1047
1048   }
1049   else if (!ctx->changed && ctx->deleted == 0) {
1050     mutt_message _("Mailbox is unchanged.");
1051
1052     mx_fastclose_mailbox (ctx);
1053     return 0;
1054   }
1055
1056   /* copy mails to the trash before expunging */
1057   if (purge && ctx->deleted)
1058     if (trash_append (ctx) != 0) {
1059       ctx->closing = 0;
1060       return -1;
1061     }
1062
1063 #ifdef USE_IMAP
1064   /* allow IMAP to preserve the deleted flag across sessions */
1065   if (ctx->magic == M_IMAP) {
1066     if ((check = imap_sync_mailbox (ctx, purge, index_hint)) != 0) {
1067       ctx->closing = 0;
1068       return check;
1069     }
1070   }
1071   else
1072 #endif
1073   {
1074     if (!purge) {
1075       for (i = 0; i < ctx->msgcount; i++)
1076         ctx->hdrs[i]->deleted = 0;
1077       ctx->deleted = 0;
1078     }
1079
1080     if (ctx->changed || ctx->deleted) {
1081       if ((check = sync_mailbox (ctx, index_hint)) != 0) {
1082         ctx->closing = 0;
1083         return check;
1084       }
1085     }
1086   }
1087
1088   if (move_messages)
1089     mutt_message (_("%d kept, %d moved, %d deleted."),
1090                   ctx->msgcount - ctx->deleted, read_msgs, ctx->deleted);
1091   else
1092     mutt_message (_("%d kept, %d deleted."),
1093                   ctx->msgcount - ctx->deleted, ctx->deleted);
1094
1095   if (ctx->msgcount == ctx->deleted &&
1096       (ctx->magic == M_MMDF || ctx->magic == M_MBOX) &&
1097       !mutt_is_spool (ctx->path) && !option (OPTSAVEEMPTY))
1098     mx_unlink_empty (ctx->path);
1099
1100 #ifdef USE_COMPRESSED
1101   if (ctx->compressinfo && mutt_slow_close_compressed (ctx))
1102     return (-1);
1103 #endif
1104
1105   mx_fastclose_mailbox (ctx);
1106
1107   return 0;
1108 }
1109
1110
1111 /* update a Context structure's internal tables. */
1112
1113 void mx_update_tables (CONTEXT * ctx, int committing)
1114 {
1115   int i, j;
1116
1117   /* update memory to reflect the new state of the mailbox */
1118   ctx->vcount = 0;
1119   ctx->vsize = 0;
1120   ctx->tagged = 0;
1121   ctx->deleted = 0;
1122   ctx->new = 0;
1123   ctx->unread = 0;
1124   ctx->changed = 0;
1125   ctx->flagged = 0;
1126 #define this_body ctx->hdrs[j]->content
1127   for (i = 0, j = 0; i < ctx->msgcount; i++) {
1128     if ((committing && (!ctx->hdrs[i]->deleted ||
1129                         (ctx->magic == M_MAILDIR
1130                          && option (OPTMAILDIRTRASH)))) || (!committing
1131                                                             && ctx->hdrs[i]->
1132                                                             active)) {
1133       if (i != j) {
1134         ctx->hdrs[j] = ctx->hdrs[i];
1135         ctx->hdrs[i] = NULL;
1136       }
1137       ctx->hdrs[j]->msgno = j;
1138       if (ctx->hdrs[j]->virtual != -1) {
1139         ctx->v2r[ctx->vcount] = j;
1140         ctx->hdrs[j]->virtual = ctx->vcount++;
1141         ctx->vsize += this_body->length + this_body->offset -
1142           this_body->hdr_offset;
1143       }
1144
1145       if (committing)
1146         ctx->hdrs[j]->changed = 0;
1147       else if (ctx->hdrs[j]->changed)
1148         ctx->changed++;
1149
1150       if (!committing
1151           || (ctx->magic == M_MAILDIR && option (OPTMAILDIRTRASH))) {
1152         if (ctx->hdrs[j]->deleted)
1153           ctx->deleted++;
1154       }
1155
1156       if (ctx->hdrs[j]->tagged)
1157         ctx->tagged++;
1158       if (ctx->hdrs[j]->flagged)
1159         ctx->flagged++;
1160       if (!ctx->hdrs[j]->read) {
1161         ctx->unread++;
1162         if (!ctx->hdrs[j]->old)
1163           ctx->new++;
1164       }
1165
1166       j++;
1167     }
1168     else {
1169       if (ctx->magic == M_MH || ctx->magic == M_MAILDIR)
1170         ctx->size -= (ctx->hdrs[i]->content->length +
1171                       ctx->hdrs[i]->content->offset -
1172                       ctx->hdrs[i]->content->hdr_offset);
1173       /* remove message from the hash tables */
1174       if (ctx->subj_hash && ctx->hdrs[i]->env->real_subj)
1175         hash_delete (ctx->subj_hash, ctx->hdrs[i]->env->real_subj,
1176                      ctx->hdrs[i], NULL);
1177       if (ctx->id_hash && ctx->hdrs[i]->env->message_id)
1178         hash_delete (ctx->id_hash, ctx->hdrs[i]->env->message_id,
1179                      ctx->hdrs[i], NULL);
1180       mutt_free_header (&ctx->hdrs[i]);
1181     }
1182   }
1183 #undef this_body
1184   ctx->msgcount = j;
1185 }
1186
1187
1188 /* save changes to mailbox
1189  *
1190  * return values:
1191  *      0               success
1192  *      -1              error
1193  */
1194 int mx_sync_mailbox (CONTEXT * ctx, int *index_hint)
1195 {
1196   int rc, i;
1197   int purge = 1;
1198   int msgcount, deleted;
1199
1200   if (ctx->dontwrite) {
1201     char buf[STRING], tmp[STRING];
1202
1203     if (km_expand_key (buf, sizeof (buf),
1204                        km_find_func (MENU_MAIN, OP_TOGGLE_WRITE)))
1205       snprintf (tmp, sizeof (tmp), _(" Press '%s' to toggle write"), buf);
1206     else
1207       strfcpy (tmp, _("Use 'toggle-write' to re-enable write!"),
1208                sizeof (tmp));
1209
1210     mutt_error (_("Mailbox is marked unwritable. %s"), tmp);
1211     return -1;
1212   }
1213   else if (ctx->readonly) {
1214     mutt_error _("Mailbox is read-only.");
1215
1216     return -1;
1217   }
1218
1219   if (!ctx->changed && !ctx->deleted) {
1220     mutt_message _("Mailbox is unchanged.");
1221
1222     return (0);
1223   }
1224
1225   if (ctx->deleted) {
1226     char buf[SHORT_STRING];
1227
1228     snprintf (buf, sizeof (buf), ctx->deleted == 1
1229               ? _("Purge %d deleted message?") :
1230               _("Purge %d deleted messages?"), ctx->deleted);
1231     if ((purge = query_quadoption (OPT_DELETE, buf)) < 0)
1232       return (-1);
1233     else if (purge == M_NO) {
1234       if (!ctx->changed)
1235         return 0;               /* nothing to do! */
1236 #ifdef USE_IMAP
1237       /* let IMAP servers hold on to D flags */
1238       if (ctx->magic != M_IMAP)
1239 #endif
1240       {
1241         for (i = 0; i < ctx->msgcount; i++)
1242           ctx->hdrs[i]->deleted = 0;
1243         ctx->deleted = 0;
1244       }
1245     }
1246     else if (ctx->last_tag && ctx->last_tag->deleted)
1247       ctx->last_tag = NULL;     /* reset last tagged msg now useless */
1248   }
1249
1250   /* really only for IMAP - imap_sync_mailbox results in a call to
1251    * mx_update_tables, so ctx->deleted is 0 when it comes back */
1252   msgcount = ctx->msgcount;
1253   deleted = ctx->deleted;
1254
1255   if (purge && ctx->deleted) {
1256     if (trash_append (ctx) == -1)
1257       return -1;
1258   }
1259
1260 #ifdef USE_IMAP
1261   if (ctx->magic == M_IMAP)
1262     rc = imap_sync_mailbox (ctx, purge, index_hint);
1263   else
1264 #endif
1265     rc = sync_mailbox (ctx, index_hint);
1266   if (rc == 0) {
1267 #ifdef USE_IMAP
1268     if (ctx->magic == M_IMAP && !purge)
1269       mutt_message (_("Mailbox checkpointed."));
1270
1271     else
1272 #endif
1273       mutt_message (_("%d kept, %d deleted."), msgcount - deleted, deleted);
1274
1275     mutt_sleep (0);
1276
1277     if (ctx->msgcount == ctx->deleted &&
1278         (ctx->magic == M_MBOX || ctx->magic == M_MMDF) &&
1279         !mutt_is_spool (ctx->path) && !option (OPTSAVEEMPTY)) {
1280       unlink (ctx->path);
1281       mx_fastclose_mailbox (ctx);
1282       return 0;
1283     }
1284
1285     /* if we haven't deleted any messages, we don't need to resort */
1286     /* ... except for certain folder formats which need "unsorted" 
1287      * sort order in order to synchronize folders.
1288      * 
1289      * MH and maildir are safe.  mbox-style seems to need re-sorting,
1290      * at least with the new threading code.
1291      */
1292     if (purge || (ctx->magic != M_MAILDIR && ctx->magic != M_MH)) {
1293 #ifdef USE_IMAP
1294       /* IMAP does this automatically after handling EXPUNGE */
1295       if (ctx->magic != M_IMAP)
1296 #endif
1297       {
1298         mx_update_tables (ctx, 1);
1299         mutt_sort_headers (ctx, 1);     /* rethread from scratch */
1300       }
1301     }
1302   }
1303
1304   return (rc);
1305 }
1306
1307 #ifdef USE_IMAP
1308 int imap_open_new_message (MESSAGE * msg, CONTEXT * dest, HEADER * hdr)
1309 {
1310   char tmp[_POSIX_PATH_MAX];
1311
1312   mutt_mktemp (tmp);
1313   if ((msg->fp = safe_fopen (tmp, "w")) == NULL)
1314     return (-1);
1315   msg->path = safe_strdup (tmp);
1316   return 0;
1317 }
1318 #endif
1319
1320 /* args:
1321  *      dest    destintation mailbox
1322  *      hdr     message being copied (required for maildir support, because
1323  *              the filename depends on the message flags)
1324  */
1325 MESSAGE *mx_open_new_message (CONTEXT * dest, HEADER * hdr, int flags)
1326 {
1327   MESSAGE *msg;
1328   int (*func) (MESSAGE *, CONTEXT *, HEADER *);
1329   ADDRESS *p = NULL;
1330
1331   switch (dest->magic) {
1332   case M_MMDF:
1333   case M_MBOX:
1334     func = mbox_open_new_message;
1335     break;
1336   case M_MAILDIR:
1337     func = maildir_open_new_message;
1338     break;
1339   case M_MH:
1340     func = mh_open_new_message;
1341     break;
1342 #ifdef USE_IMAP
1343   case M_IMAP:
1344     func = imap_open_new_message;
1345     break;
1346 #endif
1347   default:
1348     dprint (1,
1349             (debugfile,
1350              "mx_open_new_message(): function unimplemented for mailbox type %d.\n",
1351              dest->magic));
1352     return (NULL);
1353   }
1354
1355   msg = safe_calloc (1, sizeof (MESSAGE));
1356   msg->magic = dest->magic;
1357   msg->write = 1;
1358
1359   if (hdr) {
1360     msg->flags.flagged = hdr->flagged;
1361     msg->flags.replied = hdr->replied;
1362     msg->flags.read = hdr->read;
1363     msg->received = hdr->received;
1364   }
1365
1366   if (msg->received == 0)
1367     time (&msg->received);
1368
1369   if (func (msg, dest, hdr) == 0) {
1370     if (dest->magic == M_MMDF)
1371       fputs (MMDF_SEP, msg->fp);
1372
1373     if ((msg->magic == M_MBOX || msg->magic == M_MMDF) && flags & M_ADD_FROM) {
1374       if (hdr) {
1375         if (hdr->env->return_path)
1376           p = hdr->env->return_path;
1377         else if (hdr->env->sender)
1378           p = hdr->env->sender;
1379         else
1380           p = hdr->env->from;
1381       }
1382
1383       fprintf (msg->fp, "From %s %s", p ? p->mailbox : NONULL (Username),
1384                ctime (&msg->received));
1385     }
1386   }
1387   else
1388     FREE (&msg);
1389
1390   return msg;
1391 }
1392
1393 /* check for new mail */
1394 int mx_check_mailbox (CONTEXT * ctx, int *index_hint, int lock)
1395 {
1396   int rc;
1397
1398 #ifdef USE_COMPRESSED
1399   if (ctx->compressinfo)
1400     return mutt_check_mailbox_compressed (ctx);
1401 #endif
1402
1403   if (ctx) {
1404     if (ctx->locked)
1405       lock = 0;
1406
1407     switch (ctx->magic) {
1408     case M_MBOX:
1409     case M_MMDF:
1410
1411       if (lock) {
1412         mutt_block_signals ();
1413         if (mbox_lock_mailbox (ctx, 0, 0) == -1) {
1414           mutt_unblock_signals ();
1415           return M_LOCKED;
1416         }
1417       }
1418
1419       rc = mbox_check_mailbox (ctx, index_hint);
1420
1421       if (lock) {
1422         mutt_unblock_signals ();
1423         mbox_unlock_mailbox (ctx);
1424       }
1425
1426       return rc;
1427
1428
1429     case M_MH:
1430       return (mh_check_mailbox (ctx, index_hint));
1431     case M_MAILDIR:
1432       return (maildir_check_mailbox (ctx, index_hint));
1433
1434 #ifdef USE_IMAP
1435     case M_IMAP:
1436       return (imap_check_mailbox (ctx, index_hint, 0));
1437 #endif /* USE_IMAP */
1438
1439 #ifdef USE_POP
1440     case M_POP:
1441       return (pop_check_mailbox (ctx, index_hint));
1442 #endif /* USE_POP */
1443
1444 #ifdef USE_NNTP
1445     case M_NNTP:
1446       return (nntp_check_mailbox (ctx));
1447 #endif /* USE_NNTP */
1448     }
1449   }
1450
1451   dprint (1, (debugfile, "mx_check_mailbox: null or invalid context.\n"));
1452   return (-1);
1453 }
1454
1455 /* return a stream pointer for a message */
1456 MESSAGE *mx_open_message (CONTEXT * ctx, int msgno)
1457 {
1458   MESSAGE *msg;
1459
1460   msg = safe_calloc (1, sizeof (MESSAGE));
1461   switch (msg->magic = ctx->magic) {
1462   case M_MBOX:
1463   case M_MMDF:
1464     msg->fp = ctx->fp;
1465     break;
1466
1467   case M_MH:
1468   case M_MAILDIR:
1469     {
1470       HEADER *cur = ctx->hdrs[msgno];
1471       char path[_POSIX_PATH_MAX];
1472
1473       snprintf (path, sizeof (path), "%s/%s", ctx->path, cur->path);
1474
1475       if ((msg->fp = fopen (path, "r")) == NULL && errno == ENOENT &&
1476           ctx->magic == M_MAILDIR)
1477         msg->fp = maildir_open_find_message (ctx->path, cur->path);
1478
1479       if (msg->fp == NULL) {
1480         mutt_perror (path);
1481         dprint (1, (debugfile, "mx_open_message: fopen: %s: %s (errno %d).\n",
1482                     path, strerror (errno), errno));
1483         FREE (&msg);
1484       }
1485     }
1486     break;
1487
1488 #ifdef USE_IMAP
1489   case M_IMAP:
1490     {
1491       if (imap_fetch_message (msg, ctx, msgno) != 0)
1492         FREE (&msg);
1493       break;
1494     }
1495 #endif /* USE_IMAP */
1496
1497 #ifdef USE_POP
1498   case M_POP:
1499     {
1500       if (pop_fetch_message (msg, ctx, msgno) != 0)
1501         FREE (&msg);
1502       break;
1503     }
1504 #endif /* USE_POP */
1505
1506 #ifdef USE_NNTP
1507   case M_NNTP:
1508     {
1509       if (nntp_fetch_message (msg, ctx, msgno) != 0)
1510         FREE (&msg);
1511       break;
1512     }
1513 #endif /* USE_NNTP */
1514
1515   default:
1516     dprint (1,
1517             (debugfile,
1518              "mx_open_message(): function not implemented for mailbox type %d.\n",
1519              ctx->magic));
1520     FREE (&msg);
1521     break;
1522   }
1523   return (msg);
1524 }
1525
1526 /* commit a message to a folder */
1527
1528 int mx_commit_message (MESSAGE * msg, CONTEXT * ctx)
1529 {
1530   int r = 0;
1531
1532   if (!(msg->write && ctx->append)) {
1533     dprint (1,
1534             (debugfile,
1535              "mx_commit_message(): msg->write = %d, ctx->append = %d\n",
1536              msg->write, ctx->append));
1537     return -1;
1538   }
1539
1540   switch (msg->magic) {
1541   case M_MMDF:
1542     {
1543       if (fputs (MMDF_SEP, msg->fp) == EOF)
1544         r = -1;
1545       break;
1546     }
1547
1548   case M_MBOX:
1549     {
1550       if (fputc ('\n', msg->fp) == EOF)
1551         r = -1;
1552       break;
1553     }
1554
1555 #ifdef USE_IMAP
1556   case M_IMAP:
1557     {
1558       if ((r = safe_fclose (&msg->fp)) == 0)
1559         r = imap_append_message (ctx, msg);
1560       break;
1561     }
1562 #endif
1563
1564   case M_MAILDIR:
1565     {
1566       r = maildir_commit_message (ctx, msg, NULL);
1567       break;
1568     }
1569
1570   case M_MH:
1571     {
1572       r = mh_commit_message (ctx, msg, NULL);
1573       break;
1574     }
1575   }
1576
1577   if (r == 0 && (ctx->magic == M_MBOX || ctx->magic == M_MMDF)
1578       && (fflush (msg->fp) == EOF || fsync (fileno (msg->fp)) == -1)) {
1579     mutt_perror _("Can't write message");
1580
1581     r = -1;
1582   }
1583
1584   return r;
1585 }
1586
1587 /* close a pointer to a message */
1588 int mx_close_message (MESSAGE ** msg)
1589 {
1590   int r = 0;
1591
1592   if ((*msg)->magic == M_MH || (*msg)->magic == M_MAILDIR
1593 #ifdef USE_IMAP
1594       || (*msg)->magic == M_IMAP
1595 #endif
1596 #ifdef USE_POP
1597       || (*msg)->magic == M_POP
1598 #endif
1599 #ifdef USE_NNTP
1600       || (*msg)->magic == M_NNTP
1601 #endif
1602     ) {
1603     r = safe_fclose (&(*msg)->fp);
1604   }
1605   else
1606     (*msg)->fp = NULL;
1607
1608   if ((*msg)->path) {
1609     dprint (1, (debugfile, "mx_close_message (): unlinking %s\n",
1610                 (*msg)->path));
1611     unlink ((*msg)->path);
1612     FREE (&(*msg)->path);
1613   }
1614
1615   FREE (msg);
1616   return (r);
1617 }
1618
1619 void mx_alloc_memory (CONTEXT * ctx)
1620 {
1621   int i;
1622   size_t s = MAX (sizeof (HEADER *), sizeof (int));
1623
1624   if ((ctx->hdrmax + 25) * s < ctx->hdrmax * s) {
1625     mutt_error _("Integer overflow -- can't allocate memory.");
1626
1627     sleep (1);
1628     mutt_exit (1);
1629   }
1630
1631   if (ctx->hdrs) {
1632     safe_realloc (&ctx->hdrs, sizeof (HEADER *) * (ctx->hdrmax += 25));
1633     safe_realloc (&ctx->v2r, sizeof (int) * ctx->hdrmax);
1634   }
1635   else {
1636     ctx->hdrs = safe_calloc ((ctx->hdrmax += 25), sizeof (HEADER *));
1637     ctx->v2r = safe_calloc (ctx->hdrmax, sizeof (int));
1638   }
1639   for (i = ctx->msgcount; i < ctx->hdrmax; i++) {
1640     ctx->hdrs[i] = NULL;
1641     ctx->v2r[i] = -1;
1642   }
1643 }
1644
1645 /* this routine is called to update the counts in the context structure for
1646  * the last message header parsed.
1647  */
1648 void mx_update_context (CONTEXT * ctx, int new_messages)
1649 {
1650   HEADER *h;
1651   int msgno;
1652
1653   for (msgno = ctx->msgcount - new_messages; msgno < ctx->msgcount; msgno++) {
1654     h = ctx->hdrs[msgno];
1655
1656     if (WithCrypto) {
1657       /* NOTE: this _must_ be done before the check for mailcap! */
1658       h->security = crypt_query (h->content);
1659     }
1660
1661     if (!ctx->pattern) {
1662       ctx->v2r[ctx->vcount] = msgno;
1663       h->virtual = ctx->vcount++;
1664     }
1665     else
1666       h->virtual = -1;
1667     h->msgno = msgno;
1668
1669     if (h->env->supersedes) {
1670       HEADER *h2;
1671
1672       if (!ctx->id_hash)
1673         ctx->id_hash = mutt_make_id_hash (ctx);
1674
1675       h2 = hash_find (ctx->id_hash, h->env->supersedes);
1676
1677       /* FREE (&h->env->supersedes); should I ? */
1678       if (h2) {
1679         h2->superseded = 1;
1680         if (option (OPTSCORE))
1681           mutt_score_message (ctx, h2, 1);
1682       }
1683     }
1684
1685     /* add this message to the hash tables */
1686     if (ctx->id_hash && h->env->message_id)
1687       hash_insert (ctx->id_hash, h->env->message_id, h, 0);
1688     if (ctx->subj_hash && h->env->real_subj)
1689       hash_insert (ctx->subj_hash, h->env->real_subj, h, 1);
1690
1691     if (option (OPTSCORE))
1692       mutt_score_message (ctx, h, 0);
1693
1694     if (h->changed)
1695       ctx->changed = 1;
1696     if (h->flagged)
1697       ctx->flagged++;
1698     if (h->deleted)
1699       ctx->deleted++;
1700     if (!h->read) {
1701       ctx->unread++;
1702       if (!h->old)
1703         ctx->new++;
1704     }
1705   }
1706 }
1707
1708 /*
1709  * Return:
1710  * 1 if the specified mailbox contains 0 messages.
1711  * 0 if the mailbox contains messages
1712  * -1 on error
1713  */
1714 int mx_check_empty (const char *path)
1715 {
1716   switch (mx_get_magic (path)) {
1717   case M_MBOX:
1718   case M_MMDF:
1719     return mbox_check_empty (path);
1720   case M_MH:
1721     return mh_check_empty (path);
1722   case M_MAILDIR:
1723     return maildir_check_empty (path);
1724   default:
1725     errno = EINVAL;
1726     return -1;
1727   }
1728   /* not reached */
1729 }