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