Rocco Rutte:
[apps/madmutt.git] / mbox.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1996-2002 Michael R. Elkins <me@mutt.org>
4  *
5  * This file is part of mutt-ng, see http://www.muttng.org/.
6  * It's licensed under the GNU General Public License,
7  * please see the file GPL in the top level source directory.
8  */
9
10 /* This file contains code to parse ``mbox'' and ``mmdf'' style mailboxes */
11
12 #if HAVE_CONFIG_H
13 # include "config.h"
14 #endif
15
16 #include "mutt.h"
17 #include "mx.h"
18 #include "mbox.h"
19 #include "sort.h"
20 #include "copy.h"
21
22 #ifdef USE_COMPRESSED
23 #include "compress.h"
24 #endif
25
26 #include "lib/mem.h"
27 #include "lib/intl.h"
28 #include "lib/str.h"
29
30 #include <sys/stat.h>
31 #include <dirent.h>
32 #include <string.h>
33 #include <utime.h>
34 #include <sys/file.h>
35 #include <errno.h>
36 #include <unistd.h>
37 #include <fcntl.h>
38
39 /* struct used by mutt_sync_mailbox() to store new offsets */
40 struct m_update_t {
41   short valid;
42   long hdr;
43   long body;
44   long lines;
45   long length;
46 };
47
48
49 int mbox_open_new_message (MESSAGE * msg, CONTEXT * dest, HEADER * hdr)
50 {
51   msg->fp = dest->fp;
52   return 0;
53 }
54
55 /* parameters:
56  * ctx - context to lock
57  * excl - exclusive lock?
58  * retry - should retry if unable to lock?
59  */
60 int mbox_lock_mailbox (CONTEXT * ctx, int excl, int retry)
61 {
62   int r;
63
64   if ((r = mx_lock_file (ctx->path, fileno (ctx->fp), excl, 1, retry)) == 0)
65     ctx->locked = 1;
66   else if (retry && !excl) {
67     ctx->readonly = 1;
68     return 0;
69   }
70
71   return (r);
72 }
73
74 void mbox_unlock_mailbox (CONTEXT * ctx)
75 {
76   if (ctx->locked) {
77     fflush (ctx->fp);
78
79     mx_unlock_file (ctx->path, fileno (ctx->fp), 1);
80     ctx->locked = 0;
81   }
82 }
83
84 int mmdf_parse_mailbox (CONTEXT * ctx)
85 {
86   char buf[HUGE_STRING];
87   char return_path[LONG_STRING];
88   int count = 0, oldmsgcount = ctx->msgcount;
89   int lines;
90   time_t t, tz;
91   long loc, tmploc;
92   HEADER *hdr;
93   struct stat sb;
94
95 #ifdef NFS_ATTRIBUTE_HACK
96   struct utimbuf newtime;
97 #endif
98
99   if (stat (ctx->path, &sb) == -1) {
100     mutt_perror (ctx->path);
101     return (-1);
102   }
103   ctx->mtime = sb.st_mtime;
104   ctx->size = sb.st_size;
105
106 #ifdef NFS_ATTRIBUTE_HACK
107   if (sb.st_mtime > sb.st_atime) {
108     newtime.modtime = sb.st_mtime;
109     newtime.actime = time (NULL);
110     utime (ctx->path, &newtime);
111   }
112 #endif
113
114   /* precompute the local timezone to speed up calculation of the
115      received time */
116   tz = mutt_local_tz (0);
117
118   buf[sizeof (buf) - 1] = 0;
119
120   FOREVER {
121     if (fgets (buf, sizeof (buf) - 1, ctx->fp) == NULL)
122       break;
123
124     if (safe_strcmp (buf, MMDF_SEP) == 0) {
125       loc = ftell (ctx->fp);
126
127       count++;
128       if (!ctx->quiet && ReadInc && ((count % ReadInc == 0) || count == 1))
129         mutt_message (_("Reading %s... %d (%d%%)"), ctx->path, count,
130                       (int) (loc / (ctx->size / 100 + 1)));
131
132
133       if (ctx->msgcount == ctx->hdrmax)
134         mx_alloc_memory (ctx);
135       ctx->hdrs[ctx->msgcount] = hdr = mutt_new_header ();
136       hdr->offset = loc;
137       hdr->index = ctx->msgcount;
138
139       if (fgets (buf, sizeof (buf) - 1, ctx->fp) == NULL) {
140         /* TODO: memory leak??? */
141         dprint (1, (debugfile, "mmdf_parse_mailbox: unexpected EOF\n"));
142         break;
143       }
144
145       return_path[0] = 0;
146
147       if (!is_from (buf, return_path, sizeof (return_path), &t)) {
148         if (fseek (ctx->fp, loc, SEEK_SET) != 0) {
149           dprint (1, (debugfile, "mmdf_parse_mailbox: fseek() failed\n"));
150           mutt_error _("Mailbox is corrupt!");
151
152           return (-1);
153         }
154       }
155       else
156         hdr->received = t - tz;
157
158       hdr->env = mutt_read_rfc822_header (ctx->fp, hdr, 0, 0);
159
160       loc = ftell (ctx->fp);
161
162       if (hdr->content->length > 0 && hdr->lines > 0) {
163         tmploc = loc + hdr->content->length;
164
165         if (0 < tmploc && tmploc < ctx->size) {
166           if (fseek (ctx->fp, tmploc, SEEK_SET) != 0 ||
167               fgets (buf, sizeof (buf) - 1, ctx->fp) == NULL ||
168               safe_strcmp (MMDF_SEP, buf) != 0) {
169             if (fseek (ctx->fp, loc, SEEK_SET) != 0)
170               dprint (1, (debugfile, "mmdf_parse_mailbox: fseek() failed\n"));
171             hdr->content->length = -1;
172           }
173         }
174         else
175           hdr->content->length = -1;
176       }
177       else
178         hdr->content->length = -1;
179
180       if (hdr->content->length < 0) {
181         lines = -1;
182         do {
183           loc = ftell (ctx->fp);
184           if (fgets (buf, sizeof (buf) - 1, ctx->fp) == NULL)
185             break;
186           lines++;
187         } while (safe_strcmp (buf, MMDF_SEP) != 0);
188
189         hdr->lines = lines;
190         hdr->content->length = loc - hdr->content->offset;
191       }
192
193       if (!hdr->env->return_path && return_path[0])
194         hdr->env->return_path =
195           rfc822_parse_adrlist (hdr->env->return_path, return_path);
196
197       if (!hdr->env->from)
198         hdr->env->from = rfc822_cpy_adr (hdr->env->return_path);
199
200       ctx->msgcount++;
201     }
202     else {
203       dprint (1, (debugfile, "mmdf_parse_mailbox: corrupt mailbox!\n"));
204       mutt_error _("Mailbox is corrupt!");
205
206       return (-1);
207     }
208   }
209
210   if (ctx->msgcount > oldmsgcount)
211     mx_update_context (ctx, ctx->msgcount - oldmsgcount);
212
213   return (0);
214 }
215
216 /* Note that this function is also called when new mail is appended to the
217  * currently open folder, and NOT just when the mailbox is initially read.
218  *
219  * NOTE: it is assumed that the mailbox being read has been locked before
220  * this routine gets called.  Strange things could happen if it's not!
221  */
222 int mbox_parse_mailbox (CONTEXT * ctx)
223 {
224   struct stat sb;
225   char buf[HUGE_STRING], return_path[STRING];
226   HEADER *curhdr;
227   time_t t, tz;
228   int count = 0, lines = 0;
229   long loc;
230
231 #ifdef NFS_ATTRIBUTE_HACK
232   struct utimbuf newtime;
233 #endif
234
235   /* Save information about the folder at the time we opened it. */
236   if (stat (ctx->path, &sb) == -1) {
237     mutt_perror (ctx->path);
238     return (-1);
239   }
240
241   ctx->size = sb.st_size;
242   ctx->mtime = sb.st_mtime;
243
244 #ifdef NFS_ATTRIBUTE_HACK
245   if (sb.st_mtime > sb.st_atime) {
246     newtime.modtime = sb.st_mtime;
247     newtime.actime = time (NULL);
248     utime (ctx->path, &newtime);
249   }
250 #endif
251
252   if (!ctx->readonly)
253     ctx->readonly = access (ctx->path, W_OK) ? 1 : 0;
254
255   /* precompute the local timezone to speed up calculation of the
256      date received */
257   tz = mutt_local_tz (0);
258
259   loc = ftell (ctx->fp);
260   while (fgets (buf, sizeof (buf), ctx->fp) != NULL) {
261     if (is_from (buf, return_path, sizeof (return_path), &t)) {
262       /* Save the Content-Length of the previous message */
263       if (count > 0) {
264 #define PREV ctx->hdrs[ctx->msgcount-1]
265
266         if (PREV->content->length < 0) {
267           PREV->content->length = loc - PREV->content->offset - 1;
268           if (PREV->content->length < 0)
269             PREV->content->length = 0;
270         }
271         if (!PREV->lines)
272           PREV->lines = lines ? lines - 1 : 0;
273       }
274
275       count++;
276
277       if (!ctx->quiet && ReadInc && ((count % ReadInc == 0) || count == 1))
278         mutt_message (_("Reading %s... %d (%d%%)"), ctx->path, count,
279                       (int) (ftell (ctx->fp) / (ctx->size / 100 + 1)));
280
281       if (ctx->msgcount == ctx->hdrmax)
282         mx_alloc_memory (ctx);
283
284       curhdr = ctx->hdrs[ctx->msgcount] = mutt_new_header ();
285       curhdr->received = t - tz;
286       curhdr->offset = loc;
287       curhdr->index = ctx->msgcount;
288
289       curhdr->env = mutt_read_rfc822_header (ctx->fp, curhdr, 0, 0);
290
291       /* if we know how long this message is, either just skip over the body,
292        * or if we don't know how many lines there are, count them now (this will
293        * save time by not having to search for the next message marker).
294        */
295       if (curhdr->content->length > 0) {
296         long tmploc;
297
298         loc = ftell (ctx->fp);
299         tmploc = loc + curhdr->content->length + 1;
300
301         if (0 < tmploc && tmploc < ctx->size) {
302           /*
303            * check to see if the content-length looks valid.  we expect to
304            * to see a valid message separator at this point in the stream
305            */
306           if (fseek (ctx->fp, tmploc, SEEK_SET) != 0 ||
307               fgets (buf, sizeof (buf), ctx->fp) == NULL ||
308               safe_strncmp ("From ", buf, 5) != 0) {
309             dprint (1,
310                     (debugfile,
311                      "mbox_parse_mailbox: bad content-length in message %d (cl=%ld)\n",
312                      curhdr->index, curhdr->content->length));
313             dprint (1, (debugfile, "\tLINE: %s", buf));
314             if (fseek (ctx->fp, loc, SEEK_SET) != 0) {  /* nope, return the previous position */
315               dprint (1, (debugfile, "mbox_parse_mailbox: fseek() failed\n"));
316             }
317             curhdr->content->length = -1;
318           }
319         }
320         else if (tmploc != ctx->size) {
321           /* content-length would put us past the end of the file, so it
322            * must be wrong
323            */
324           curhdr->content->length = -1;
325         }
326
327         if (curhdr->content->length != -1) {
328           /* good content-length.  check to see if we know how many lines
329            * are in this message.
330            */
331           if (curhdr->lines == 0) {
332             int cl = curhdr->content->length;
333
334             /* count the number of lines in this message */
335             if (fseek (ctx->fp, loc, SEEK_SET) != 0)
336               dprint (1, (debugfile, "mbox_parse_mailbox: fseek() failed\n"));
337             while (cl-- > 0) {
338               if (fgetc (ctx->fp) == '\n')
339                 curhdr->lines++;
340             }
341           }
342
343           /* return to the offset of the next message separator */
344           if (fseek (ctx->fp, tmploc, SEEK_SET) != 0)
345             dprint (1, (debugfile, "mbox_parse_mailbox: fseek() failed\n"));
346         }
347       }
348
349       ctx->msgcount++;
350
351       if (!curhdr->env->return_path && return_path[0])
352         curhdr->env->return_path =
353           rfc822_parse_adrlist (curhdr->env->return_path, return_path);
354
355       if (!curhdr->env->from)
356         curhdr->env->from = rfc822_cpy_adr (curhdr->env->return_path);
357
358       lines = 0;
359     }
360     else
361       lines++;
362
363     loc = ftell (ctx->fp);
364   }
365
366   /*
367    * Only set the content-length of the previous message if we have read more
368    * than one message during _this_ invocation.  If this routine is called
369    * when new mail is received, we need to make sure not to clobber what
370    * previously was the last message since the headers may be sorted.
371    */
372   if (count > 0) {
373     if (PREV->content->length < 0) {
374       PREV->content->length = ftell (ctx->fp) - PREV->content->offset - 1;
375       if (PREV->content->length < 0)
376         PREV->content->length = 0;
377     }
378
379     if (!PREV->lines)
380       PREV->lines = lines ? lines - 1 : 0;
381
382     mx_update_context (ctx, count);
383   }
384
385   return (0);
386 }
387
388 #undef PREV
389
390 /* open a mbox or mmdf style mailbox */
391 int mbox_open_mailbox (CONTEXT * ctx)
392 {
393   int rc;
394
395   if ((ctx->fp = fopen (ctx->path, "r")) == NULL) {
396     mutt_perror (ctx->path);
397     return (-1);
398   }
399   mutt_block_signals ();
400   if (mbox_lock_mailbox (ctx, 0, 1) == -1) {
401     mutt_unblock_signals ();
402     return (-1);
403   }
404
405   if (ctx->magic == M_MBOX)
406     rc = mbox_parse_mailbox (ctx);
407   else if (ctx->magic == M_MMDF)
408     rc = mmdf_parse_mailbox (ctx);
409   else
410     rc = -1;
411
412   mbox_unlock_mailbox (ctx);
413   mutt_unblock_signals ();
414   return (rc);
415 }
416
417 /* return 1 if address lists are strictly identical */
418 static int strict_addrcmp (const ADDRESS * a, const ADDRESS * b)
419 {
420   while (a && b) {
421     if (safe_strcmp (a->mailbox, b->mailbox) ||
422         safe_strcmp (a->personal, b->personal))
423       return (0);
424
425     a = a->next;
426     b = b->next;
427   }
428   if (a || b)
429     return (0);
430
431   return (1);
432 }
433
434 static int strict_cmp_lists (const LIST * a, const LIST * b)
435 {
436   while (a && b) {
437     if (safe_strcmp (a->data, b->data))
438       return (0);
439
440     a = a->next;
441     b = b->next;
442   }
443   if (a || b)
444     return (0);
445
446   return (1);
447 }
448
449 static int strict_cmp_envelopes (const ENVELOPE * e1, const ENVELOPE * e2)
450 {
451   if (e1 && e2) {
452     if (safe_strcmp (e1->message_id, e2->message_id) ||
453         safe_strcmp (e1->subject, e2->subject) ||
454         !strict_cmp_lists (e1->references, e2->references) ||
455         !strict_addrcmp (e1->from, e2->from) ||
456         !strict_addrcmp (e1->sender, e2->sender) ||
457         !strict_addrcmp (e1->reply_to, e2->reply_to) ||
458         !strict_addrcmp (e1->to, e2->to) ||
459         !strict_addrcmp (e1->cc, e2->cc) ||
460         !strict_addrcmp (e1->return_path, e2->return_path))
461       return (0);
462     else
463       return (1);
464   }
465   else {
466     if (e1 == NULL && e2 == NULL)
467       return (1);
468     else
469       return (0);
470   }
471 }
472
473 static int strict_cmp_parameters (const PARAMETER * p1, const PARAMETER * p2)
474 {
475   while (p1 && p2) {
476     if (safe_strcmp (p1->attribute, p2->attribute) ||
477         safe_strcmp (p1->value, p2->value))
478       return (0);
479
480     p1 = p1->next;
481     p2 = p2->next;
482   }
483   if (p1 || p2)
484     return (0);
485
486   return (1);
487 }
488
489 static int strict_cmp_bodies (const BODY * b1, const BODY * b2)
490 {
491   if (b1->type != b2->type ||
492       b1->encoding != b2->encoding ||
493       safe_strcmp (b1->subtype, b2->subtype) ||
494       safe_strcmp (b1->description, b2->description) ||
495       !strict_cmp_parameters (b1->parameter, b2->parameter) ||
496       b1->length != b2->length)
497     return (0);
498   return (1);
499 }
500
501 /* return 1 if headers are strictly identical */
502 int mbox_strict_cmp_headers (const HEADER * h1, const HEADER * h2)
503 {
504   if (h1 && h2) {
505     if (h1->received != h2->received ||
506         h1->date_sent != h2->date_sent ||
507         h1->content->length != h2->content->length ||
508         h1->lines != h2->lines ||
509         h1->zhours != h2->zhours ||
510         h1->zminutes != h2->zminutes ||
511         h1->zoccident != h2->zoccident ||
512         h1->mime != h2->mime ||
513         !strict_cmp_envelopes (h1->env, h2->env) ||
514         !strict_cmp_bodies (h1->content, h2->content))
515       return (0);
516     else
517       return (1);
518   }
519   else {
520     if (h1 == NULL && h2 == NULL)
521       return (1);
522     else
523       return (0);
524   }
525 }
526
527 /* check to see if the mailbox has changed on disk.
528  *
529  * return values:
530  *      M_REOPENED      mailbox has been reopened
531  *      M_NEW_MAIL      new mail has arrived!
532  *      M_LOCKED        couldn't lock the file
533  *      0               no change
534  *      -1              error
535  */
536 int mbox_check_mailbox (CONTEXT * ctx, int *index_hint)
537 {
538   struct stat st;
539   char buffer[LONG_STRING];
540   int unlock = 0;
541   int modified = 0;
542
543   if (stat (ctx->path, &st) == 0) {
544     if (st.st_mtime == ctx->mtime && st.st_size == ctx->size)
545       return (0);
546
547     if (st.st_size == ctx->size) {
548       /* the file was touched, but it is still the same length, so just exit */
549       ctx->mtime = st.st_mtime;
550       return (0);
551     }
552
553     if (st.st_size > ctx->size) {
554       /* lock the file if it isn't already */
555       if (!ctx->locked) {
556         mutt_block_signals ();
557         if (mbox_lock_mailbox (ctx, 0, 0) == -1) {
558           mutt_unblock_signals ();
559           /* we couldn't lock the mailbox, but nothing serious happened:
560            * probably the new mail arrived: no reason to wait till we can
561            * parse it: we'll get it on the next pass
562            */
563           return (M_LOCKED);
564         }
565         unlock = 1;
566       }
567
568       /*
569        * Check to make sure that the only change to the mailbox is that 
570        * message(s) were appended to this file.  My heuristic is that we should
571        * see the message separator at *exactly* what used to be the end of the
572        * folder.
573        */
574       if (fseek (ctx->fp, ctx->size, SEEK_SET) != 0)
575         dprint (1, (debugfile, "mbox_check_mailbox: fseek() failed\n"));
576       if (fgets (buffer, sizeof (buffer), ctx->fp) != NULL) {
577         if ((ctx->magic == M_MBOX && safe_strncmp ("From ", buffer, 5) == 0)
578             || (ctx->magic == M_MMDF && safe_strcmp (MMDF_SEP, buffer) == 0)) {
579           if (fseek (ctx->fp, ctx->size, SEEK_SET) != 0)
580             dprint (1, (debugfile, "mbox_check_mailbox: fseek() failed\n"));
581           if (ctx->magic == M_MBOX)
582             mbox_parse_mailbox (ctx);
583           else
584             mmdf_parse_mailbox (ctx);
585
586           /* Only unlock the folder if it was locked inside of this routine.
587            * It may have been locked elsewhere, like in
588            * mutt_checkpoint_mailbox().
589            */
590
591           if (unlock) {
592             mbox_unlock_mailbox (ctx);
593             mutt_unblock_signals ();
594           }
595
596           return (M_NEW_MAIL);  /* signal that new mail arrived */
597         }
598         else
599           modified = 1;
600       }
601       else {
602         dprint (1, (debugfile, "mbox_check_mailbox: fgets returned NULL.\n"));
603         modified = 1;
604       }
605     }
606     else
607       modified = 1;
608   }
609
610   if (modified) {
611     if (mbox_reopen_mailbox (ctx, index_hint) != -1) {
612       if (unlock) {
613         mbox_unlock_mailbox (ctx);
614         mutt_unblock_signals ();
615       }
616       return (M_REOPENED);
617     }
618   }
619
620   /* fatal error */
621
622   mbox_unlock_mailbox (ctx);
623   mx_fastclose_mailbox (ctx);
624   mutt_unblock_signals ();
625   mutt_error _("Mailbox was corrupted!");
626
627   return (-1);
628 }
629
630 /* return values:
631  *      0       success
632  *      -1      failure
633  */
634 int mbox_sync_mailbox (CONTEXT * ctx, int *index_hint)
635 {
636   char tempfile[_POSIX_PATH_MAX];
637   char buf[32];
638   int i, j, save_sort = SORT_ORDER;
639   int rc = -1;
640   int need_sort = 0;            /* flag to resort mailbox if new mail arrives */
641   int first = -1;               /* first message to be written */
642   long offset;                  /* location in mailbox to write changed messages */
643   struct stat statbuf;
644   struct utimbuf utimebuf;
645   struct m_update_t *newOffset = NULL;
646   struct m_update_t *oldOffset = NULL;
647   FILE *fp = NULL;
648
649   /* sort message by their position in the mailbox on disk */
650   if (Sort != SORT_ORDER) {
651     save_sort = Sort;
652     Sort = SORT_ORDER;
653     mutt_sort_headers (ctx, 0);
654   }
655
656   /* need to open the file for writing in such a way that it does not truncate
657    * the file, so use read-write mode.
658    */
659   if ((ctx->fp = freopen (ctx->path, "r+", ctx->fp)) == NULL) {
660     mx_fastclose_mailbox (ctx);
661     mutt_error _("Fatal error!  Could not reopen mailbox!");
662
663     return (-1);
664   }
665
666   mutt_block_signals ();
667
668   if (mbox_lock_mailbox (ctx, 1, 1) == -1) {
669     mutt_unblock_signals ();
670     mutt_error _("Unable to lock mailbox!");
671
672     goto bail;
673   }
674
675   /* Check to make sure that the file hasn't changed on disk */
676   if ((i = mbox_check_mailbox (ctx, index_hint)) == M_NEW_MAIL
677       || i == M_REOPENED) {
678     /* new mail arrived, or mailbox reopened */
679     need_sort = i;
680     rc = i;
681     goto bail;
682   }
683   else if (i < 0) {
684     /* fatal error */
685     Sort = save_sort;
686     return (-1);
687   }
688
689   /* Create a temporary file to write the new version of the mailbox in. */
690   mutt_mktemp (tempfile);
691   if ((i = open (tempfile, O_WRONLY | O_EXCL | O_CREAT, 0600)) == -1 ||
692       (fp = fdopen (i, "w")) == NULL) {
693     if (-1 != i) {
694       close (i);
695       unlink (tempfile);
696     }
697     mutt_error _("Could not create temporary file!");
698
699     mutt_sleep (5);
700     goto bail;
701   }
702
703   /* find the first deleted/changed message.  we save a lot of time by only
704    * rewriting the mailbox from the point where it has actually changed.
705    */
706   for (i = 0; i < ctx->msgcount && !ctx->hdrs[i]->deleted &&
707        !ctx->hdrs[i]->changed && !ctx->hdrs[i]->attach_del; i++);
708   if (i == ctx->msgcount) {
709     /* this means ctx->changed or ctx->deleted was set, but no
710      * messages were found to be changed or deleted.  This should
711      * never happen, is we presume it is a bug in mutt.
712      */
713     mutt_error
714       _("sync: mbox modified, but no modified messages! (report this bug)");
715     mutt_sleep (5);             /* the mutt_error /will/ get cleared! */
716     dprint (1, (debugfile, "mbox_sync_mailbox(): no modified messages.\n"));
717     unlink (tempfile);
718     goto bail;
719   }
720
721   /* save the index of the first changed/deleted message */
722   first = i;
723   /* where to start overwriting */
724   offset = ctx->hdrs[i]->offset;
725
726   /* the offset stored in the header does not include the MMDF_SEP, so make
727    * sure we seek to the correct location
728    */
729   if (ctx->magic == M_MMDF)
730     offset -= (sizeof MMDF_SEP - 1);
731
732   /* allocate space for the new offsets */
733   newOffset = safe_calloc (ctx->msgcount - first, sizeof (struct m_update_t));
734   oldOffset = safe_calloc (ctx->msgcount - first, sizeof (struct m_update_t));
735
736   for (i = first, j = 0; i < ctx->msgcount; i++) {
737     /*
738      * back up some information which is needed to restore offsets when
739      * something fails.
740      */
741
742     oldOffset[i - first].valid = 1;
743     oldOffset[i - first].hdr = ctx->hdrs[i]->offset;
744     oldOffset[i - first].body = ctx->hdrs[i]->content->offset;
745     oldOffset[i - first].lines = ctx->hdrs[i]->lines;
746     oldOffset[i - first].length = ctx->hdrs[i]->content->length;
747
748     if (!ctx->hdrs[i]->deleted) {
749       j++;
750       if (!ctx->quiet && WriteInc && ((i % WriteInc) == 0 || j == 1))
751         mutt_message (_("Writing messages... %d (%d%%)"), i,
752                       (int) (ftell (ctx->fp) / (ctx->size / 100 + 1)));
753
754       if (ctx->magic == M_MMDF) {
755         if (fputs (MMDF_SEP, fp) == EOF) {
756           mutt_perror (tempfile);
757           mutt_sleep (5);
758           unlink (tempfile);
759           goto bail;
760         }
761
762       }
763
764       /* save the new offset for this message.  we add `offset' because the
765        * temporary file only contains saved message which are located after
766        * `offset' in the real mailbox
767        */
768       newOffset[i - first].hdr = ftell (fp) + offset;
769
770       if (mutt_copy_message
771           (fp, ctx, ctx->hdrs[i], M_CM_UPDATE,
772            CH_FROM | CH_UPDATE | CH_UPDATE_LEN) == -1) {
773         mutt_perror (tempfile);
774         mutt_sleep (5);
775         unlink (tempfile);
776         goto bail;
777       }
778
779       /* Since messages could have been deleted, the offsets stored in memory
780        * will be wrong, so update what we can, which is the offset of this
781        * message, and the offset of the body.  If this is a multipart message,
782        * we just flush the in memory cache so that the message will be reparsed
783        * if the user accesses it later.
784        */
785       newOffset[i - first].body =
786         ftell (fp) - ctx->hdrs[i]->content->length + offset;
787       mutt_free_body (&ctx->hdrs[i]->content->parts);
788
789       switch (ctx->magic) {
790       case M_MMDF:
791         if (fputs (MMDF_SEP, fp) == EOF) {
792           mutt_perror (tempfile);
793           mutt_sleep (5);
794           unlink (tempfile);
795           goto bail;
796         }
797         break;
798       default:
799         if (fputs ("\n", fp) == EOF) {
800           mutt_perror (tempfile);
801           mutt_sleep (5);
802           unlink (tempfile);
803           goto bail;
804         }
805       }
806     }
807   }
808
809   if (fclose (fp) != 0) {
810     fp = NULL;
811     dprint (1,
812             (debugfile, "mbox_sync_mailbox: fclose() returned non-zero.\n"));
813     unlink (tempfile);
814     mutt_perror (tempfile);
815     mutt_sleep (5);
816     goto bail;
817   }
818   fp = NULL;
819
820   /* Save the state of this folder. */
821   if (stat (ctx->path, &statbuf) == -1) {
822     mutt_perror (ctx->path);
823     mutt_sleep (5);
824     unlink (tempfile);
825     goto bail;
826   }
827
828   if ((fp = fopen (tempfile, "r")) == NULL) {
829     mutt_unblock_signals ();
830     mx_fastclose_mailbox (ctx);
831     dprint (1,
832             (debugfile,
833              "mbox_sync_mailbox: unable to reopen temp copy of mailbox!\n"));
834     mutt_perror (tempfile);
835     mutt_sleep (5);
836     return (-1);
837   }
838
839   if (fseek (ctx->fp, offset, SEEK_SET) != 0 || /* seek the append location */
840       /* do a sanity check to make sure the mailbox looks ok */
841       fgets (buf, sizeof (buf), ctx->fp) == NULL ||
842       (ctx->magic == M_MBOX && safe_strncmp ("From ", buf, 5) != 0) ||
843       (ctx->magic == M_MMDF && safe_strcmp (MMDF_SEP, buf) != 0)) {
844     dprint (1,
845             (debugfile,
846              "mbox_sync_mailbox: message not in expected position."));
847     dprint (1, (debugfile, "\tLINE: %s\n", buf));
848     i = -1;
849   }
850   else {
851     if (fseek (ctx->fp, offset, SEEK_SET) != 0) {       /* return to proper offset */
852       i = -1;
853       dprint (1, (debugfile, "mbox_sync_mailbox: fseek() failed\n"));
854     }
855     else {
856       /* copy the temp mailbox back into place starting at the first
857        * change/deleted message
858        */
859       mutt_message _("Committing changes...");
860
861       i = mutt_copy_stream (fp, ctx->fp);
862
863       if (ferror (ctx->fp))
864         i = -1;
865     }
866     if (i == 0) {
867       ctx->size = ftell (ctx->fp);      /* update the size of the mailbox */
868       ftruncate (fileno (ctx->fp), ctx->size);
869     }
870   }
871
872   fclose (fp);
873   fp = NULL;
874   mbox_unlock_mailbox (ctx);
875
876   if (fclose (ctx->fp) != 0 || i == -1) {
877     /* error occured while writing the mailbox back, so keep the temp copy
878      * around
879      */
880
881     char savefile[_POSIX_PATH_MAX];
882
883     snprintf (savefile, sizeof (savefile), "%s/mutt.%s-%s-%u",
884               NONULL (Tempdir), NONULL (Username), NONULL (Hostname),
885               (unsigned int) getpid ());
886     rename (tempfile, savefile);
887     mutt_unblock_signals ();
888     mx_fastclose_mailbox (ctx);
889     mutt_pretty_mailbox (savefile);
890     mutt_error (_("Write failed!  Saved partial mailbox to %s"), savefile);
891     mutt_sleep (5);
892     return (-1);
893   }
894
895   /* Restore the previous access/modification times */
896   utimebuf.actime = statbuf.st_atime;
897   utimebuf.modtime = statbuf.st_mtime;
898   utime (ctx->path, &utimebuf);
899
900   /* reopen the mailbox in read-only mode */
901   if ((ctx->fp = fopen (ctx->path, "r")) == NULL) {
902     unlink (tempfile);
903     mutt_unblock_signals ();
904     mx_fastclose_mailbox (ctx);
905     mutt_error _("Fatal error!  Could not reopen mailbox!");
906
907     Sort = save_sort;
908     return (-1);
909   }
910
911   /* update the offsets of the rewritten messages */
912   for (i = first, j = first; i < ctx->msgcount; i++) {
913     if (!ctx->hdrs[i]->deleted) {
914       ctx->hdrs[i]->offset = newOffset[i - first].hdr;
915       ctx->hdrs[i]->content->hdr_offset = newOffset[i - first].hdr;
916       ctx->hdrs[i]->content->offset = newOffset[i - first].body;
917       ctx->hdrs[i]->index = j++;
918     }
919   }
920   FREE (&newOffset);
921   FREE (&oldOffset);
922   unlink (tempfile);            /* remove partial copy of the mailbox */
923   mutt_unblock_signals ();
924   Sort = save_sort;             /* Restore the default value. */
925
926   return (0);                   /* signal success */
927
928 bail:                          /* Come here in case of disaster */
929
930   safe_fclose (&fp);
931
932   /* restore offsets, as far as they are valid */
933   if (first >= 0 && oldOffset) {
934     for (i = first; i < ctx->msgcount && oldOffset[i - first].valid; i++) {
935       ctx->hdrs[i]->offset = oldOffset[i - first].hdr;
936       ctx->hdrs[i]->content->hdr_offset = oldOffset[i - first].hdr;
937       ctx->hdrs[i]->content->offset = oldOffset[i - first].body;
938       ctx->hdrs[i]->lines = oldOffset[i - first].lines;
939       ctx->hdrs[i]->content->length = oldOffset[i - first].length;
940     }
941   }
942
943   /* this is ok to call even if we haven't locked anything */
944   mbox_unlock_mailbox (ctx);
945
946   mutt_unblock_signals ();
947   FREE (&newOffset);
948   FREE (&oldOffset);
949
950   if ((ctx->fp = freopen (ctx->path, "r", ctx->fp)) == NULL) {
951     mutt_error _("Could not reopen mailbox!");
952
953     mx_fastclose_mailbox (ctx);
954     return (-1);
955   }
956
957   if (need_sort || save_sort != Sort) {
958     Sort = save_sort;
959     /* if the mailbox was reopened, the thread tree will be invalid so make
960      * sure to start threading from scratch.  */
961     mutt_sort_headers (ctx, (need_sort == M_REOPENED));
962   }
963
964   return rc;
965 }
966
967 /* close a mailbox opened in write-mode */
968 int mbox_close_mailbox (CONTEXT * ctx)
969 {
970   mx_unlock_file (ctx->path, fileno (ctx->fp), 1);
971
972 #ifdef USE_COMPRESSED
973   if (ctx->compressinfo)
974     mutt_slow_close_compressed (ctx);
975 #endif
976
977   mutt_unblock_signals ();
978   mx_fastclose_mailbox (ctx);
979   return 0;
980 }
981
982 int mbox_reopen_mailbox (CONTEXT * ctx, int *index_hint)
983 {
984   int (*cmp_headers) (const HEADER *, const HEADER *) = NULL;
985   HEADER **old_hdrs;
986   int old_msgcount;
987   int msg_mod = 0;
988   int index_hint_set;
989   int i, j;
990   int rc = -1;
991
992   /* silent operations */
993   ctx->quiet = 1;
994
995   mutt_message _("Reopening mailbox...");
996
997   /* our heuristics require the old mailbox to be unsorted */
998   if (Sort != SORT_ORDER) {
999     short old_sort;
1000
1001     old_sort = Sort;
1002     Sort = SORT_ORDER;
1003     mutt_sort_headers (ctx, 1);
1004     Sort = old_sort;
1005   }
1006
1007   old_hdrs = NULL;
1008   old_msgcount = 0;
1009
1010   /* simulate a close */
1011   if (ctx->id_hash)
1012     hash_destroy (&ctx->id_hash, NULL);
1013   if (ctx->subj_hash)
1014     hash_destroy (&ctx->subj_hash, NULL);
1015   mutt_clear_threads (ctx);
1016   FREE (&ctx->v2r);
1017   if (ctx->readonly) {
1018     for (i = 0; i < ctx->msgcount; i++)
1019       mutt_free_header (&(ctx->hdrs[i]));       /* nothing to do! */
1020     FREE (&ctx->hdrs);
1021   }
1022   else {
1023     /* save the old headers */
1024     old_msgcount = ctx->msgcount;
1025     old_hdrs = ctx->hdrs;
1026     ctx->hdrs = NULL;
1027   }
1028
1029   ctx->hdrmax = 0;              /* force allocation of new headers */
1030   ctx->msgcount = 0;
1031   ctx->vcount = 0;
1032   ctx->tagged = 0;
1033   ctx->deleted = 0;
1034   ctx->new = 0;
1035   ctx->unread = 0;
1036   ctx->flagged = 0;
1037   ctx->changed = 0;
1038   ctx->id_hash = NULL;
1039   ctx->subj_hash = NULL;
1040
1041   switch (ctx->magic) {
1042   case M_MBOX:
1043   case M_MMDF:
1044     if (fseek (ctx->fp, 0, SEEK_SET) != 0) {
1045       dprint (1, (debugfile, "mbox_reopen_mailbox: fseek() failed\n"));
1046       rc = -1;
1047     }
1048     else {
1049       cmp_headers = mbox_strict_cmp_headers;
1050       if (ctx->magic == M_MBOX)
1051         rc = mbox_parse_mailbox (ctx);
1052       else
1053         rc = mmdf_parse_mailbox (ctx);
1054     }
1055     break;
1056
1057   default:
1058     rc = -1;
1059     break;
1060   }
1061
1062   if (rc == -1) {
1063     /* free the old headers */
1064     for (j = 0; j < old_msgcount; j++)
1065       mutt_free_header (&(old_hdrs[j]));
1066     FREE (&old_hdrs);
1067
1068     ctx->quiet = 0;
1069     return (-1);
1070   }
1071
1072   /* now try to recover the old flags */
1073
1074   index_hint_set = (index_hint == NULL);
1075
1076   if (!ctx->readonly) {
1077     for (i = 0; i < ctx->msgcount; i++) {
1078       int found = 0;
1079
1080       /* some messages have been deleted, and new  messages have been
1081        * appended at the end; the heuristic is that old messages have then
1082        * "advanced" towards the beginning of the folder, so we begin the
1083        * search at index "i"
1084        */
1085       for (j = i; j < old_msgcount; j++) {
1086         if (old_hdrs[j] == NULL)
1087           continue;
1088         if (cmp_headers (ctx->hdrs[i], old_hdrs[j])) {
1089           found = 1;
1090           break;
1091         }
1092       }
1093       if (!found) {
1094         for (j = 0; j < i && j < old_msgcount; j++) {
1095           if (old_hdrs[j] == NULL)
1096             continue;
1097           if (cmp_headers (ctx->hdrs[i], old_hdrs[j])) {
1098             found = 1;
1099             break;
1100           }
1101         }
1102       }
1103
1104       if (found) {
1105         /* this is best done here */
1106         if (!index_hint_set && *index_hint == j)
1107           *index_hint = i;
1108
1109         if (old_hdrs[j]->changed) {
1110           /* Only update the flags if the old header was changed;
1111            * otherwise, the header may have been modified externally,
1112            * and we don't want to lose _those_ changes
1113            */
1114           mutt_set_flag (ctx, ctx->hdrs[i], M_FLAG, old_hdrs[j]->flagged);
1115           mutt_set_flag (ctx, ctx->hdrs[i], M_REPLIED, old_hdrs[j]->replied);
1116           mutt_set_flag (ctx, ctx->hdrs[i], M_OLD, old_hdrs[j]->old);
1117           mutt_set_flag (ctx, ctx->hdrs[i], M_READ, old_hdrs[j]->read);
1118         }
1119         mutt_set_flag (ctx, ctx->hdrs[i], M_DELETE, old_hdrs[j]->deleted);
1120         mutt_set_flag (ctx, ctx->hdrs[i], M_TAG, old_hdrs[j]->tagged);
1121
1122         /* we don't need this header any more */
1123         mutt_free_header (&(old_hdrs[j]));
1124       }
1125     }
1126
1127     /* free the remaining old headers */
1128     for (j = 0; j < old_msgcount; j++) {
1129       if (old_hdrs[j]) {
1130         mutt_free_header (&(old_hdrs[j]));
1131         msg_mod = 1;
1132       }
1133     }
1134     FREE (&old_hdrs);
1135   }
1136
1137   ctx->quiet = 0;
1138
1139   return ((ctx->changed || msg_mod) ? M_REOPENED : M_NEW_MAIL);
1140 }
1141
1142 /*
1143  * Returns:
1144  * 1 if the mailbox is not empty
1145  * 0 if the mailbox is empty
1146  * -1 on error
1147  */
1148 int mbox_check_empty (const char *path)
1149 {
1150   struct stat st;
1151
1152   if (stat (path, &st) == -1)
1153     return -1;
1154
1155   return ((st.st_size == 0));
1156 }
1157
1158 int mbox_is_magic (const char* path, struct stat* st) {
1159   int magic = -1;
1160   FILE* f;
1161   char tmp[_POSIX_PATH_MAX];
1162
1163   if (S_ISDIR(st->st_mode))
1164     return (-1);
1165
1166   if (st->st_size == 0) {
1167     /* hard to tell what zero-length files are, so assume the default magic */
1168     if (DefaultMagic == M_MBOX || DefaultMagic == M_MMDF)
1169       return (DefaultMagic);
1170     else
1171       return (M_MBOX);
1172   }
1173   else if ((f = fopen (path, "r")) != NULL) {
1174 #ifndef BUFFY_SIZE
1175     struct utimbuf times;
1176 #endif
1177     fgets (tmp, sizeof (tmp), f);
1178     if (safe_strncmp ("From ", tmp, 5) == 0)
1179       magic = M_MBOX;
1180     else if (safe_strcmp (MMDF_SEP, tmp) == 0)
1181       magic = M_MMDF;
1182     safe_fclose (&f);
1183 #ifndef BUFFY_SIZE
1184     /* need to restore the times here, the file was not really accessed,
1185      * only the type was accessed.  This is important, because detection
1186      * of "new mail" depends on those times set correctly.
1187      */
1188     times.actime = st->st_atime;
1189     times.modtime = st->st_mtime;
1190     utime (path, &times);
1191 #endif
1192   } else {
1193     mutt_perror (path);
1194     return (-1);         /* fopen failed */
1195   }
1196
1197 #ifdef USE_COMPRESSED
1198   if (magic == -1 && mutt_can_read_compressed (path))
1199     return (M_COMPRESSED);
1200 #endif
1201   return (magic);
1202 }
1203
1204 static mx_t* reg_mx (void) {
1205   mx_t* fmt = safe_calloc (1, sizeof (mx_t));
1206   fmt->local = 1;
1207   fmt->mx_check_empty = mbox_check_empty;
1208   fmt->mx_is_magic = mbox_is_magic;
1209   fmt->mx_access = access;
1210   fmt->mx_open_mailbox = mbox_open_mailbox;
1211   return (fmt);
1212 }
1213
1214 mx_t* mbox_reg_mx (void) {
1215   mx_t* fmt = reg_mx ();
1216   fmt->type = M_MBOX;
1217   return (fmt);
1218 }
1219 mx_t* mmdf_reg_mx (void) {
1220   mx_t* fmt = reg_mx ();
1221   fmt->type = M_MMDF;
1222   return (fmt);
1223 }