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