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