]> git.llucax.com Git - software/mutt-debian.git/blob - mbox.c
Imported Upstream version 1.5.18
[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)(ftell (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)(ftell (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, CH_FROM | CH_UPDATE | CH_UPDATE_LEN) == -1)
837       {
838         mutt_perror (tempfile);
839         mutt_sleep (5);
840         unlink (tempfile);
841         goto bail;
842       }
843
844       /* Since messages could have been deleted, the offsets stored in memory
845        * will be wrong, so update what we can, which is the offset of this
846        * message, and the offset of the body.  If this is a multipart message,
847        * we just flush the in memory cache so that the message will be reparsed
848        * if the user accesses it later.
849        */
850       newOffset[i - first].body = ftello (fp) - ctx->hdrs[i]->content->length + offset;
851       mutt_free_body (&ctx->hdrs[i]->content->parts);
852
853       switch(ctx->magic)
854       {
855         case M_MMDF: 
856           if(fputs(MMDF_SEP, fp) == EOF) 
857           {
858             mutt_perror (tempfile);
859             mutt_sleep (5);
860             unlink (tempfile);
861             goto bail; 
862           }
863           break;
864         default:
865           if(fputs("\n", fp) == EOF) 
866           {
867             mutt_perror (tempfile);
868             mutt_sleep (5);
869             unlink (tempfile);
870             goto bail;
871           }
872       }
873     }
874   }
875   
876   if (fclose (fp) != 0)
877   {
878     fp = NULL;
879     dprint(1, (debugfile, "mbox_sync_mailbox: fclose() returned non-zero.\n"));
880     unlink (tempfile);
881     mutt_perror (tempfile);
882     mutt_sleep (5);
883     goto bail;
884   }
885   fp = NULL;
886
887   /* Save the state of this folder. */
888   if (stat (ctx->path, &statbuf) == -1)
889   {
890     mutt_perror (ctx->path);
891     mutt_sleep (5);
892     unlink (tempfile);
893     goto bail;
894   }
895
896   if ((fp = fopen (tempfile, "r")) == NULL)
897   {
898     mutt_unblock_signals ();
899     mx_fastclose_mailbox (ctx);
900     dprint (1, (debugfile, "mbox_sync_mailbox: unable to reopen temp copy of mailbox!\n"));
901     mutt_perror (tempfile);
902     mutt_sleep (5);
903     return (-1);
904   }
905
906   if (fseeko (ctx->fp, offset, SEEK_SET) != 0 ||  /* seek the append location */
907       /* do a sanity check to make sure the mailbox looks ok */
908       fgets (buf, sizeof (buf), ctx->fp) == NULL ||
909       (ctx->magic == M_MBOX && mutt_strncmp ("From ", buf, 5) != 0) ||
910       (ctx->magic == M_MMDF && mutt_strcmp (MMDF_SEP, buf) != 0))
911   {
912     dprint (1, (debugfile, "mbox_sync_mailbox: message not in expected position."));
913     dprint (1, (debugfile, "\tLINE: %s\n", buf));
914     i = -1;
915   }
916   else
917   {
918     if (fseeko (ctx->fp, offset, SEEK_SET) != 0) /* return to proper offset */
919     {
920       i = -1;
921       dprint (1, (debugfile, "mbox_sync_mailbox: fseek() failed\n"));
922     }
923     else
924     {
925       /* copy the temp mailbox back into place starting at the first
926        * change/deleted message
927        */
928       mutt_message _("Committing changes...");
929       i = mutt_copy_stream (fp, ctx->fp);
930
931       if (ferror (ctx->fp))
932         i = -1;
933     }
934     if (i == 0)
935     {
936       ctx->size = ftello (ctx->fp); /* update the size of the mailbox */
937       ftruncate (fileno (ctx->fp), ctx->size);
938     }
939   }
940
941   fclose (fp);
942   fp = NULL;
943   mbox_unlock_mailbox (ctx);
944
945   if (fclose (ctx->fp) != 0 || i == -1)
946   {
947     /* error occured while writing the mailbox back, so keep the temp copy
948      * around
949      */
950     
951     char savefile[_POSIX_PATH_MAX];
952     
953     snprintf (savefile, sizeof (savefile), "%s/mutt.%s-%s-%u",
954               NONULL (Tempdir), NONULL(Username), NONULL(Hostname), (unsigned int)getpid ());
955     rename (tempfile, savefile);
956     mutt_unblock_signals ();
957     mx_fastclose_mailbox (ctx);
958     mutt_pretty_mailbox (savefile);
959     mutt_error (_("Write failed!  Saved partial mailbox to %s"), savefile);
960     mutt_sleep (5);
961     return (-1);
962   }
963
964   /* Restore the previous access/modification times */
965   utimebuf.actime = statbuf.st_atime;
966   utimebuf.modtime = statbuf.st_mtime;
967   utime (ctx->path, &utimebuf);
968
969   /* reopen the mailbox in read-only mode */
970   if ((ctx->fp = fopen (ctx->path, "r")) == NULL)
971   {
972     unlink (tempfile);
973     mutt_unblock_signals ();
974     mx_fastclose_mailbox (ctx);
975     mutt_error _("Fatal error!  Could not reopen mailbox!");
976     return (-1);
977   }
978
979   /* update the offsets of the rewritten messages */
980   for (i = first, j = first; i < ctx->msgcount; i++)
981   {
982     if (!ctx->hdrs[i]->deleted)
983     {
984       ctx->hdrs[i]->offset = newOffset[i - first].hdr;
985       ctx->hdrs[i]->content->hdr_offset = newOffset[i - first].hdr;
986       ctx->hdrs[i]->content->offset = newOffset[i - first].body;
987       ctx->hdrs[i]->index = j++;
988     }
989   }
990   FREE (&newOffset);
991   FREE (&oldOffset);
992   unlink (tempfile); /* remove partial copy of the mailbox */
993   mutt_unblock_signals ();
994
995   return (0); /* signal success */
996
997 bail:  /* Come here in case of disaster */
998
999   safe_fclose (&fp);
1000
1001   /* restore offsets, as far as they are valid */
1002   if (first >= 0 && oldOffset)
1003   {
1004     for (i = first; i < ctx->msgcount && oldOffset[i-first].valid; i++)
1005     {
1006       ctx->hdrs[i]->offset = oldOffset[i-first].hdr;
1007       ctx->hdrs[i]->content->hdr_offset = oldOffset[i-first].hdr;
1008       ctx->hdrs[i]->content->offset = oldOffset[i-first].body;
1009       ctx->hdrs[i]->lines = oldOffset[i-first].lines;
1010       ctx->hdrs[i]->content->length = oldOffset[i-first].length;
1011     }
1012   }
1013   
1014   /* this is ok to call even if we haven't locked anything */
1015   mbox_unlock_mailbox (ctx);
1016
1017   mutt_unblock_signals ();
1018   FREE (&newOffset);
1019   FREE (&oldOffset);
1020
1021   if ((ctx->fp = freopen (ctx->path, "r", ctx->fp)) == NULL)
1022   {
1023     mutt_error _("Could not reopen mailbox!");
1024     mx_fastclose_mailbox (ctx);
1025     return (-1);
1026   }
1027
1028   if (need_sort)
1029     /* if the mailbox was reopened, the thread tree will be invalid so make
1030      * sure to start threading from scratch.  */
1031     mutt_sort_headers (ctx, (need_sort == M_REOPENED));
1032
1033   return rc;
1034 }
1035
1036 /* close a mailbox opened in write-mode */
1037 int mbox_close_mailbox (CONTEXT *ctx)
1038 {
1039   mx_unlock_file (ctx->path, fileno (ctx->fp), 1);
1040   mutt_unblock_signals ();
1041   mx_fastclose_mailbox (ctx);
1042   return 0;
1043 }
1044
1045 int mutt_reopen_mailbox (CONTEXT *ctx, int *index_hint)
1046 {
1047   int (*cmp_headers) (const HEADER *, const HEADER *) = NULL;
1048   HEADER **old_hdrs;
1049   int old_msgcount;
1050   int msg_mod = 0;
1051   int index_hint_set;
1052   int i, j;
1053   int rc = -1;
1054
1055   /* silent operations */
1056   ctx->quiet = 1;
1057   
1058   mutt_message _("Reopening mailbox...");
1059   
1060   /* our heuristics require the old mailbox to be unsorted */
1061   if (Sort != SORT_ORDER)
1062   {
1063     short old_sort;
1064
1065     old_sort = Sort;
1066     Sort = SORT_ORDER;
1067     mutt_sort_headers (ctx, 1);
1068     Sort = old_sort;
1069   }
1070
1071   old_hdrs = NULL;
1072   old_msgcount = 0;
1073   
1074   /* simulate a close */
1075   if (ctx->id_hash)
1076     hash_destroy (&ctx->id_hash, NULL);
1077   if (ctx->subj_hash)
1078     hash_destroy (&ctx->subj_hash, NULL);
1079   mutt_clear_threads (ctx);
1080   FREE (&ctx->v2r);
1081   if (ctx->readonly)
1082   {
1083     for (i = 0; i < ctx->msgcount; i++)
1084       mutt_free_header (&(ctx->hdrs[i])); /* nothing to do! */
1085     FREE (&ctx->hdrs);
1086   }
1087   else
1088   {
1089       /* save the old headers */
1090     old_msgcount = ctx->msgcount;
1091     old_hdrs = ctx->hdrs;
1092     ctx->hdrs = NULL;
1093   }
1094
1095   ctx->hdrmax = 0;      /* force allocation of new headers */
1096   ctx->msgcount = 0;
1097   ctx->vcount = 0;
1098   ctx->tagged = 0;
1099   ctx->deleted = 0;
1100   ctx->new = 0;
1101   ctx->unread = 0;
1102   ctx->flagged = 0;
1103   ctx->changed = 0;
1104   ctx->id_hash = NULL;
1105   ctx->subj_hash = NULL;
1106
1107   switch (ctx->magic)
1108   {
1109     case M_MBOX:
1110     case M_MMDF:
1111       if (fseek (ctx->fp, 0, SEEK_SET) != 0)
1112       {
1113         dprint (1, (debugfile, "mutt_reopen_mailbox: fseek() failed\n"));
1114         rc = -1;
1115       } 
1116       else 
1117       {
1118         cmp_headers = mbox_strict_cmp_headers;
1119         rc = ((ctx->magic == M_MBOX) ? mbox_parse_mailbox
1120                                      : mmdf_parse_mailbox) (ctx);
1121       }
1122       break;
1123
1124     default:
1125       rc = -1;
1126       break;
1127   }
1128   
1129   if (rc == -1)
1130   {
1131     /* free the old headers */
1132     for (j = 0; j < old_msgcount; j++)
1133       mutt_free_header (&(old_hdrs[j]));
1134     FREE (&old_hdrs);
1135
1136     ctx->quiet = 0;
1137     return (-1);
1138   }
1139
1140   /* now try to recover the old flags */
1141
1142   index_hint_set = (index_hint == NULL);
1143
1144   if (!ctx->readonly)
1145   {
1146     for (i = 0; i < ctx->msgcount; i++)
1147     {
1148       int found = 0;
1149
1150       /* some messages have been deleted, and new  messages have been
1151        * appended at the end; the heuristic is that old messages have then
1152        * "advanced" towards the beginning of the folder, so we begin the
1153        * search at index "i"
1154        */
1155       for (j = i; j < old_msgcount; j++)
1156       {
1157         if (old_hdrs[j] == NULL)
1158           continue;
1159         if (cmp_headers (ctx->hdrs[i], old_hdrs[j]))
1160         {
1161           found = 1;
1162           break;
1163         }
1164       }
1165       if (!found)
1166       {
1167         for (j = 0; j < i && j < old_msgcount; j++)
1168         {
1169           if (old_hdrs[j] == NULL)
1170             continue;
1171           if (cmp_headers (ctx->hdrs[i], old_hdrs[j]))
1172           {
1173             found = 1;
1174             break;
1175           }
1176         }
1177       }
1178
1179       if (found)
1180       {
1181         /* this is best done here */
1182         if (!index_hint_set && *index_hint == j)
1183           *index_hint = i;
1184
1185         if (old_hdrs[j]->changed)
1186         {
1187           /* Only update the flags if the old header was changed;
1188            * otherwise, the header may have been modified externally,
1189            * and we don't want to lose _those_ changes
1190            */
1191           mutt_set_flag (ctx, ctx->hdrs[i], M_FLAG, old_hdrs[j]->flagged);
1192           mutt_set_flag (ctx, ctx->hdrs[i], M_REPLIED, old_hdrs[j]->replied);
1193           mutt_set_flag (ctx, ctx->hdrs[i], M_OLD, old_hdrs[j]->old);
1194           mutt_set_flag (ctx, ctx->hdrs[i], M_READ, old_hdrs[j]->read);
1195         }
1196         mutt_set_flag (ctx, ctx->hdrs[i], M_DELETE, old_hdrs[j]->deleted);
1197         mutt_set_flag (ctx, ctx->hdrs[i], M_TAG, old_hdrs[j]->tagged);
1198
1199         /* we don't need this header any more */
1200         mutt_free_header (&(old_hdrs[j]));
1201       }
1202     }
1203
1204     /* free the remaining old headers */
1205     for (j = 0; j < old_msgcount; j++)
1206     {
1207       if (old_hdrs[j])
1208       {
1209         mutt_free_header (&(old_hdrs[j]));
1210         msg_mod = 1;
1211       }
1212     }
1213     FREE (&old_hdrs);
1214   }
1215
1216   ctx->quiet = 0;
1217
1218   return ((ctx->changed || msg_mod) ? M_REOPENED : M_NEW_MAIL);
1219 }
1220
1221 /*
1222  * Returns:
1223  * 1 if the mailbox is not empty
1224  * 0 if the mailbox is empty
1225  * -1 on error
1226  */
1227 int mbox_check_empty (const char *path)
1228 {
1229   struct stat st;
1230
1231   if (stat (path, &st) == -1)
1232     return -1;
1233
1234   return ((st.st_size == 0));
1235 }