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