]> git.llucax.com Git - software/mutt-debian.git/blob - mbox.c
doc update: clarify what attach_charset does (Closes: 502628)
[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);
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);
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 /* return values:
683  *      0       success
684  *      -1      failure
685  */
686 int mbox_sync_mailbox (CONTEXT *ctx, int *index_hint)
687 {
688   char tempfile[_POSIX_PATH_MAX];
689   char buf[32];
690   int i, j, save_sort = SORT_ORDER;
691   int rc = -1;
692   int need_sort = 0; /* flag to resort mailbox if new mail arrives */
693   int first = -1;       /* first message to be written */
694   LOFF_T offset;        /* location in mailbox to write changed messages */
695   struct stat statbuf;
696   struct utimbuf utimebuf;
697   struct m_update_t *newOffset = NULL;
698   struct m_update_t *oldOffset = NULL;
699   FILE *fp = NULL;
700   progress_t progress;
701   char msgbuf[STRING];
702
703   /* sort message by their position in the mailbox on disk */
704   if (Sort != SORT_ORDER)
705   {
706     save_sort = Sort;
707     Sort = SORT_ORDER;
708     mutt_sort_headers (ctx, 0);
709     Sort = save_sort;
710     need_sort = 1;
711   }
712
713   /* need to open the file for writing in such a way that it does not truncate
714    * the file, so use read-write mode.
715    */
716   if ((ctx->fp = freopen (ctx->path, "r+", ctx->fp)) == NULL)
717   {
718     mx_fastclose_mailbox (ctx);
719     mutt_error _("Fatal error!  Could not reopen mailbox!");
720     return (-1);
721   }
722
723   mutt_block_signals ();
724
725   if (mbox_lock_mailbox (ctx, 1, 1) == -1)
726   {
727     mutt_unblock_signals ();
728     mutt_error _("Unable to lock mailbox!");
729     goto bail;
730   }
731
732   /* Check to make sure that the file hasn't changed on disk */
733   if ((i = mbox_check_mailbox (ctx, index_hint)) == M_NEW_MAIL ||  i == M_REOPENED)
734   {
735     /* new mail arrived, or mailbox reopened */
736     need_sort = i;
737     rc = i;
738     goto bail;
739   }
740   else if (i < 0)
741     /* fatal error */
742     return (-1);
743
744   /* Create a temporary file to write the new version of the mailbox in. */
745   mutt_mktemp (tempfile);
746   if ((i = open (tempfile, O_WRONLY | O_EXCL | O_CREAT, 0600)) == -1 ||
747       (fp = fdopen (i, "w")) == NULL)
748   {
749     if (-1 != i)
750     {
751       close (i);
752       unlink (tempfile);
753     }
754     mutt_error _("Could not create temporary file!");
755     mutt_sleep (5);
756     goto bail;
757   }
758
759   /* find the first deleted/changed message.  we save a lot of time by only
760    * rewriting the mailbox from the point where it has actually changed.
761    */
762   for (i = 0 ; i < ctx->msgcount && !ctx->hdrs[i]->deleted && 
763                !ctx->hdrs[i]->changed && !ctx->hdrs[i]->attach_del; i++)
764     ;
765   if (i == ctx->msgcount)
766   { 
767     /* this means ctx->changed or ctx->deleted was set, but no
768      * messages were found to be changed or deleted.  This should
769      * never happen, is we presume it is a bug in mutt.
770      */
771     mutt_error _("sync: mbox modified, but no modified messages! (report this bug)");
772     mutt_sleep(5); /* the mutt_error /will/ get cleared! */
773     dprint(1, (debugfile, "mbox_sync_mailbox(): no modified messages.\n"));
774     unlink (tempfile);
775     goto bail;
776   }
777
778     /* save the index of the first changed/deleted message */
779   first = i; 
780   /* where to start overwriting */
781   offset = ctx->hdrs[i]->offset; 
782
783   /* the offset stored in the header does not include the MMDF_SEP, so make
784    * sure we seek to the correct location
785    */
786   if (ctx->magic == M_MMDF)
787     offset -= (sizeof MMDF_SEP - 1);
788   
789   /* allocate space for the new offsets */
790   newOffset = safe_calloc (ctx->msgcount - first, sizeof (struct m_update_t));
791   oldOffset = safe_calloc (ctx->msgcount - first, sizeof (struct m_update_t));
792
793   if (!ctx->quiet)
794   {
795     snprintf (msgbuf, sizeof (msgbuf), _("Writing %s..."), ctx->path);
796     mutt_progress_init (&progress, msgbuf, M_PROGRESS_MSG, WriteInc, ctx->msgcount);
797   }
798
799   for (i = first, j = 0; i < ctx->msgcount; i++)
800   {
801     if (!ctx->quiet)
802       mutt_progress_update (&progress, i, (int)(ftello (ctx->fp) / (ctx->size / 100 + 1)));
803     /*
804      * back up some information which is needed to restore offsets when
805      * something fails.
806      */
807     
808     oldOffset[i-first].valid  = 1;
809     oldOffset[i-first].hdr    = ctx->hdrs[i]->offset;
810     oldOffset[i-first].body   = ctx->hdrs[i]->content->offset;
811     oldOffset[i-first].lines  = ctx->hdrs[i]->lines;
812     oldOffset[i-first].length = ctx->hdrs[i]->content->length;
813
814     if (! ctx->hdrs[i]->deleted)
815     {
816       j++;
817
818       if (ctx->magic == M_MMDF)
819       {
820         if (fputs (MMDF_SEP, fp) == EOF)
821         {
822           mutt_perror (tempfile);
823           mutt_sleep (5);
824           unlink (tempfile);
825           goto bail;
826         }
827           
828       }
829
830       /* save the new offset for this message.  we add `offset' because the
831        * temporary file only contains saved message which are located after
832        * `offset' in the real mailbox
833        */
834       newOffset[i - first].hdr = ftello (fp) + offset;
835
836       if (mutt_copy_message (fp, ctx, ctx->hdrs[i], M_CM_UPDATE,
837                              CH_FROM | CH_UPDATE | CH_UPDATE_LEN) != 0)
838       {
839         mutt_perror (tempfile);
840         mutt_sleep (5);
841         unlink (tempfile);
842         goto bail;
843       }
844
845       /* Since messages could have been deleted, the offsets stored in memory
846        * will be wrong, so update what we can, which is the offset of this
847        * message, and the offset of the body.  If this is a multipart message,
848        * we just flush the in memory cache so that the message will be reparsed
849        * if the user accesses it later.
850        */
851       newOffset[i - first].body = ftello (fp) - ctx->hdrs[i]->content->length + offset;
852       mutt_free_body (&ctx->hdrs[i]->content->parts);
853
854       switch(ctx->magic)
855       {
856         case M_MMDF: 
857           if(fputs(MMDF_SEP, fp) == EOF) 
858           {
859             mutt_perror (tempfile);
860             mutt_sleep (5);
861             unlink (tempfile);
862             goto bail; 
863           }
864           break;
865         default:
866           if(fputs("\n", fp) == EOF) 
867           {
868             mutt_perror (tempfile);
869             mutt_sleep (5);
870             unlink (tempfile);
871             goto bail;
872           }
873       }
874     }
875   }
876   
877   if (fclose (fp) != 0)
878   {
879     fp = NULL;
880     dprint(1, (debugfile, "mbox_sync_mailbox: fclose() returned non-zero.\n"));
881     unlink (tempfile);
882     mutt_perror (tempfile);
883     mutt_sleep (5);
884     goto bail;
885   }
886   fp = NULL;
887
888   /* Save the state of this folder. */
889   if (stat (ctx->path, &statbuf) == -1)
890   {
891     mutt_perror (ctx->path);
892     mutt_sleep (5);
893     unlink (tempfile);
894     goto bail;
895   }
896
897   if ((fp = fopen (tempfile, "r")) == NULL)
898   {
899     mutt_unblock_signals ();
900     mx_fastclose_mailbox (ctx);
901     dprint (1, (debugfile, "mbox_sync_mailbox: unable to reopen temp copy of mailbox!\n"));
902     mutt_perror (tempfile);
903     mutt_sleep (5);
904     return (-1);
905   }
906
907   if (fseeko (ctx->fp, offset, SEEK_SET) != 0 ||  /* seek the append location */
908       /* do a sanity check to make sure the mailbox looks ok */
909       fgets (buf, sizeof (buf), ctx->fp) == NULL ||
910       (ctx->magic == M_MBOX && mutt_strncmp ("From ", buf, 5) != 0) ||
911       (ctx->magic == M_MMDF && mutt_strcmp (MMDF_SEP, buf) != 0))
912   {
913     dprint (1, (debugfile, "mbox_sync_mailbox: message not in expected position."));
914     dprint (1, (debugfile, "\tLINE: %s\n", buf));
915     i = -1;
916   }
917   else
918   {
919     if (fseeko (ctx->fp, offset, SEEK_SET) != 0) /* return to proper offset */
920     {
921       i = -1;
922       dprint (1, (debugfile, "mbox_sync_mailbox: fseek() failed\n"));
923     }
924     else
925     {
926       /* copy the temp mailbox back into place starting at the first
927        * change/deleted message
928        */
929       mutt_message _("Committing changes...");
930       i = mutt_copy_stream (fp, ctx->fp);
931
932       if (ferror (ctx->fp))
933         i = -1;
934     }
935     if (i == 0)
936     {
937       ctx->size = ftello (ctx->fp); /* update the size of the mailbox */
938       ftruncate (fileno (ctx->fp), ctx->size);
939     }
940   }
941
942   fclose (fp);
943   fp = NULL;
944   mbox_unlock_mailbox (ctx);
945
946   if (fclose (ctx->fp) != 0 || i == -1)
947   {
948     /* error occured while writing the mailbox back, so keep the temp copy
949      * around
950      */
951     
952     char savefile[_POSIX_PATH_MAX];
953     
954     snprintf (savefile, sizeof (savefile), "%s/mutt.%s-%s-%u",
955               NONULL (Tempdir), NONULL(Username), NONULL(Hostname), (unsigned int)getpid ());
956     rename (tempfile, savefile);
957     mutt_unblock_signals ();
958     mx_fastclose_mailbox (ctx);
959     mutt_pretty_mailbox (savefile, sizeof (savefile));
960     mutt_error (_("Write failed!  Saved partial mailbox to %s"), savefile);
961     mutt_sleep (5);
962     return (-1);
963   }
964
965   /* Restore the previous access/modification times */
966   utimebuf.actime = statbuf.st_atime;
967   utimebuf.modtime = statbuf.st_mtime;
968   utime (ctx->path, &utimebuf);
969
970   /* reopen the mailbox in read-only mode */
971   if ((ctx->fp = fopen (ctx->path, "r")) == NULL)
972   {
973     unlink (tempfile);
974     mutt_unblock_signals ();
975     mx_fastclose_mailbox (ctx);
976     mutt_error _("Fatal error!  Could not reopen mailbox!");
977     return (-1);
978   }
979
980   /* update the offsets of the rewritten messages */
981   for (i = first, j = first; i < ctx->msgcount; i++)
982   {
983     if (!ctx->hdrs[i]->deleted)
984     {
985       ctx->hdrs[i]->offset = newOffset[i - first].hdr;
986       ctx->hdrs[i]->content->hdr_offset = newOffset[i - first].hdr;
987       ctx->hdrs[i]->content->offset = newOffset[i - first].body;
988       ctx->hdrs[i]->index = j++;
989     }
990   }
991   FREE (&newOffset);
992   FREE (&oldOffset);
993   unlink (tempfile); /* remove partial copy of the mailbox */
994   mutt_unblock_signals ();
995
996   return (0); /* signal success */
997
998 bail:  /* Come here in case of disaster */
999
1000   safe_fclose (&fp);
1001
1002   /* restore offsets, as far as they are valid */
1003   if (first >= 0 && oldOffset)
1004   {
1005     for (i = first; i < ctx->msgcount && oldOffset[i-first].valid; i++)
1006     {
1007       ctx->hdrs[i]->offset = oldOffset[i-first].hdr;
1008       ctx->hdrs[i]->content->hdr_offset = oldOffset[i-first].hdr;
1009       ctx->hdrs[i]->content->offset = oldOffset[i-first].body;
1010       ctx->hdrs[i]->lines = oldOffset[i-first].lines;
1011       ctx->hdrs[i]->content->length = oldOffset[i-first].length;
1012     }
1013   }
1014   
1015   /* this is ok to call even if we haven't locked anything */
1016   mbox_unlock_mailbox (ctx);
1017
1018   mutt_unblock_signals ();
1019   FREE (&newOffset);
1020   FREE (&oldOffset);
1021
1022   if ((ctx->fp = freopen (ctx->path, "r", ctx->fp)) == NULL)
1023   {
1024     mutt_error _("Could not reopen mailbox!");
1025     mx_fastclose_mailbox (ctx);
1026     return (-1);
1027   }
1028
1029   if (need_sort)
1030     /* if the mailbox was reopened, the thread tree will be invalid so make
1031      * sure to start threading from scratch.  */
1032     mutt_sort_headers (ctx, (need_sort == M_REOPENED));
1033
1034   return rc;
1035 }
1036
1037 /* close a mailbox opened in write-mode */
1038 int mbox_close_mailbox (CONTEXT *ctx)
1039 {
1040   mx_unlock_file (ctx->path, fileno (ctx->fp), 1);
1041   mutt_unblock_signals ();
1042   mx_fastclose_mailbox (ctx);
1043   return 0;
1044 }
1045
1046 int mutt_reopen_mailbox (CONTEXT *ctx, int *index_hint)
1047 {
1048   int (*cmp_headers) (const HEADER *, const HEADER *) = NULL;
1049   HEADER **old_hdrs;
1050   int old_msgcount;
1051   int msg_mod = 0;
1052   int index_hint_set;
1053   int i, j;
1054   int rc = -1;
1055
1056   /* silent operations */
1057   ctx->quiet = 1;
1058   
1059   mutt_message _("Reopening mailbox...");
1060   
1061   /* our heuristics require the old mailbox to be unsorted */
1062   if (Sort != SORT_ORDER)
1063   {
1064     short old_sort;
1065
1066     old_sort = Sort;
1067     Sort = SORT_ORDER;
1068     mutt_sort_headers (ctx, 1);
1069     Sort = old_sort;
1070   }
1071
1072   old_hdrs = NULL;
1073   old_msgcount = 0;
1074   
1075   /* simulate a close */
1076   if (ctx->id_hash)
1077     hash_destroy (&ctx->id_hash, NULL);
1078   if (ctx->subj_hash)
1079     hash_destroy (&ctx->subj_hash, NULL);
1080   mutt_clear_threads (ctx);
1081   FREE (&ctx->v2r);
1082   if (ctx->readonly)
1083   {
1084     for (i = 0; i < ctx->msgcount; i++)
1085       mutt_free_header (&(ctx->hdrs[i])); /* nothing to do! */
1086     FREE (&ctx->hdrs);
1087   }
1088   else
1089   {
1090       /* save the old headers */
1091     old_msgcount = ctx->msgcount;
1092     old_hdrs = ctx->hdrs;
1093     ctx->hdrs = NULL;
1094   }
1095
1096   ctx->hdrmax = 0;      /* force allocation of new headers */
1097   ctx->msgcount = 0;
1098   ctx->vcount = 0;
1099   ctx->tagged = 0;
1100   ctx->deleted = 0;
1101   ctx->new = 0;
1102   ctx->unread = 0;
1103   ctx->flagged = 0;
1104   ctx->changed = 0;
1105   ctx->id_hash = NULL;
1106   ctx->subj_hash = NULL;
1107
1108   switch (ctx->magic)
1109   {
1110     case M_MBOX:
1111     case M_MMDF:
1112       if (fseek (ctx->fp, 0, SEEK_SET) != 0)
1113       {
1114         dprint (1, (debugfile, "mutt_reopen_mailbox: fseek() failed\n"));
1115         rc = -1;
1116       } 
1117       else 
1118       {
1119         cmp_headers = mbox_strict_cmp_headers;
1120         rc = ((ctx->magic == M_MBOX) ? mbox_parse_mailbox
1121                                      : mmdf_parse_mailbox) (ctx);
1122       }
1123       break;
1124
1125     default:
1126       rc = -1;
1127       break;
1128   }
1129   
1130   if (rc == -1)
1131   {
1132     /* free the old headers */
1133     for (j = 0; j < old_msgcount; j++)
1134       mutt_free_header (&(old_hdrs[j]));
1135     FREE (&old_hdrs);
1136
1137     ctx->quiet = 0;
1138     return (-1);
1139   }
1140
1141   /* now try to recover the old flags */
1142
1143   index_hint_set = (index_hint == NULL);
1144
1145   if (!ctx->readonly)
1146   {
1147     for (i = 0; i < ctx->msgcount; i++)
1148     {
1149       int found = 0;
1150
1151       /* some messages have been deleted, and new  messages have been
1152        * appended at the end; the heuristic is that old messages have then
1153        * "advanced" towards the beginning of the folder, so we begin the
1154        * search at index "i"
1155        */
1156       for (j = i; j < old_msgcount; j++)
1157       {
1158         if (old_hdrs[j] == NULL)
1159           continue;
1160         if (cmp_headers (ctx->hdrs[i], old_hdrs[j]))
1161         {
1162           found = 1;
1163           break;
1164         }
1165       }
1166       if (!found)
1167       {
1168         for (j = 0; j < i && j < old_msgcount; j++)
1169         {
1170           if (old_hdrs[j] == NULL)
1171             continue;
1172           if (cmp_headers (ctx->hdrs[i], old_hdrs[j]))
1173           {
1174             found = 1;
1175             break;
1176           }
1177         }
1178       }
1179
1180       if (found)
1181       {
1182         /* this is best done here */
1183         if (!index_hint_set && *index_hint == j)
1184           *index_hint = i;
1185
1186         if (old_hdrs[j]->changed)
1187         {
1188           /* Only update the flags if the old header was changed;
1189            * otherwise, the header may have been modified externally,
1190            * and we don't want to lose _those_ changes
1191            */
1192           mutt_set_flag (ctx, ctx->hdrs[i], M_FLAG, old_hdrs[j]->flagged);
1193           mutt_set_flag (ctx, ctx->hdrs[i], M_REPLIED, old_hdrs[j]->replied);
1194           mutt_set_flag (ctx, ctx->hdrs[i], M_OLD, old_hdrs[j]->old);
1195           mutt_set_flag (ctx, ctx->hdrs[i], M_READ, old_hdrs[j]->read);
1196         }
1197         mutt_set_flag (ctx, ctx->hdrs[i], M_DELETE, old_hdrs[j]->deleted);
1198         mutt_set_flag (ctx, ctx->hdrs[i], M_TAG, old_hdrs[j]->tagged);
1199
1200         /* we don't need this header any more */
1201         mutt_free_header (&(old_hdrs[j]));
1202       }
1203     }
1204
1205     /* free the remaining old headers */
1206     for (j = 0; j < old_msgcount; j++)
1207     {
1208       if (old_hdrs[j])
1209       {
1210         mutt_free_header (&(old_hdrs[j]));
1211         msg_mod = 1;
1212       }
1213     }
1214     FREE (&old_hdrs);
1215   }
1216
1217   ctx->quiet = 0;
1218
1219   return ((ctx->changed || msg_mod) ? M_REOPENED : M_NEW_MAIL);
1220 }
1221
1222 /*
1223  * Returns:
1224  * 1 if the mailbox is not empty
1225  * 0 if the mailbox is empty
1226  * -1 on error
1227  */
1228 int mbox_check_empty (const char *path)
1229 {
1230   struct stat st;
1231
1232   if (stat (path, &st) == -1)
1233     return -1;
1234
1235   return ((st.st_size == 0));
1236 }