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