style.
[apps/madmutt.git] / lib-mx / 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'' style mailboxes */
11
12 #include <lib-lib/lib-lib.h>
13
14 #include <lib-ui/curses.h>
15 #include <lib-sys/mutt_signal.h>
16
17 #include "mutt.h"
18 #include "mx.h"
19 #include "buffy.h"
20 #include "mbox.h"
21 #include "sort.h"
22 #include "thread.h"
23 #include "copy.h"
24 #include "compress.h"
25
26 /* struct used by mutt_sync_mailbox() to store new offsets */
27 struct m_update_t {
28     short valid;
29     off_t hdr;
30     off_t body;
31     long lines;
32     off_t length;
33 };
34
35 static int mbox_open_new_message(MESSAGE *msg, CONTEXT *dest, HEADER *hdr)
36 {
37     msg->fp = dest->fp;
38     return 0;
39 }
40
41 /* prototypes */
42 static int mbox_reopen_mailbox (CONTEXT*, int*);
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 static 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 /* Note that this function is also called when new mail is appended to the
74  * currently open folder, and NOT just when the mailbox is initially read.
75  *
76  * NOTE: it is assumed that the mailbox being read has been locked before
77  * this routine gets called.  Strange things could happen if it's not!
78  */
79 static int mbox_parse_mailbox (CONTEXT * ctx)
80 {
81   struct stat sb;
82   char buf[HUGE_STRING], return_path[STRING];
83   HEADER *curhdr;
84   time_t t, tz;
85   int count = 0, lines = 0;
86   off_t loc;
87
88   /* Save information about the folder at the time we opened it. */
89   if (stat (ctx->path, &sb) == -1) {
90     mutt_perror (ctx->path);
91     return (-1);
92   }
93
94   ctx->size = sb.st_size;
95   ctx->mtime = sb.st_mtime;
96
97   if (!ctx->readonly)
98     ctx->readonly = access (ctx->path, W_OK) ? 1 : 0;
99
100   /* precompute the local timezone to speed up calculation of the
101      date received */
102   tz = mutt_local_tz (0);
103
104   loc = ftello (ctx->fp);
105   while (fgets (buf, sizeof (buf), ctx->fp) != NULL) {
106     if (is_from (buf, return_path, sizeof (return_path), &t)) {
107       /* Save the Content-Length of the previous message */
108       if (count > 0) {
109 #define PREV ctx->hdrs[ctx->msgcount-1]
110
111         if (PREV->content->length < 0) {
112           PREV->content->length = loc - PREV->content->offset - 1;
113           if (PREV->content->length < 0)
114             PREV->content->length = 0;
115         }
116         if (!PREV->lines)
117           PREV->lines = lines ? lines - 1 : 0;
118       }
119
120       count++;
121
122       if (!ctx->quiet && ReadInc && ((count % ReadInc == 0) || count == 1))
123         mutt_message (_("Reading %s... %d (%d%%)"), ctx->path, count,
124                       (int) (ftello (ctx->fp) / (ctx->size / 100 + 1)));
125
126       if (ctx->msgcount == ctx->hdrmax)
127         mx_alloc_memory (ctx);
128
129       curhdr = ctx->hdrs[ctx->msgcount] = header_new();
130       curhdr->received = t - tz;
131       curhdr->offset = loc;
132       curhdr->index = ctx->msgcount;
133
134       curhdr->env = mutt_read_rfc822_header (ctx->fp, curhdr, 0, 0);
135
136       /* if we know how long this message is, either just skip over the body,
137        * or if we don't know how many lines there are, count them now (this will
138        * save time by not having to search for the next message marker).
139        */
140       if (curhdr->content->length > 0) {
141         off_t tmploc;
142
143         loc = ftello (ctx->fp);
144         tmploc = loc + curhdr->content->length + 1;
145
146         if (0 < tmploc && tmploc < ctx->size) {
147           /*
148            * check to see if the content-length looks valid.  we expect to
149            * to see a valid message separator at this point in the stream
150            */
151           if (fseeko (ctx->fp, tmploc, SEEK_SET) != 0 ||
152               fgets (buf, sizeof (buf), ctx->fp) == NULL ||
153               m_strncmp("From ", buf, 5) != 0) {
154             fseeko (ctx->fp, loc, SEEK_SET); /* nope, return the previous position */
155             curhdr->content->length = -1;
156           }
157         }
158         else if (tmploc != ctx->size) {
159           /* content-length would put us past the end of the file, so it
160            * must be wrong
161            */
162           curhdr->content->length = -1;
163         }
164
165         if (curhdr->content->length != -1) {
166           /* good content-length.  check to see if we know how many lines
167            * are in this message.
168            */
169           if (curhdr->lines == 0) {
170             int cl = curhdr->content->length;
171
172             /* count the number of lines in this message */
173             fseeko (ctx->fp, loc, SEEK_SET);
174             while (cl-- > 0) {
175               if (fgetc (ctx->fp) == '\n')
176                 curhdr->lines++;
177             }
178           }
179
180           /* return to the offset of the next message separator */
181           fseeko(ctx->fp, tmploc, SEEK_SET);
182         }
183       }
184
185       ctx->msgcount++;
186
187       if (!curhdr->env->return_path && return_path[0])
188         curhdr->env->return_path =
189           rfc822_parse_adrlist (curhdr->env->return_path, return_path);
190
191       if (!curhdr->env->from)
192         curhdr->env->from = address_list_dup (curhdr->env->return_path);
193
194       lines = 0;
195     }
196     else
197       lines++;
198
199     loc = ftello (ctx->fp);
200   }
201
202   /*
203    * Only set the content-length of the previous message if we have read more
204    * than one message during _this_ invocation.  If this routine is called
205    * when new mail is received, we need to make sure not to clobber what
206    * previously was the last message since the headers may be sorted.
207    */
208   if (count > 0) {
209     if (PREV->content->length < 0) {
210       PREV->content->length = ftello (ctx->fp) - PREV->content->offset - 1;
211       if (PREV->content->length < 0)
212         PREV->content->length = 0;
213     }
214
215     if (!PREV->lines)
216       PREV->lines = lines ? lines - 1 : 0;
217
218     mx_update_context (ctx, count);
219   }
220
221   return (0);
222 }
223
224 #undef PREV
225
226 /* open a mbox style mailbox */
227 static int mbox_open_mailbox (CONTEXT * ctx)
228 {
229     int rc;
230
231     if (!(ctx->fp = fopen(ctx->path, "r"))) {
232         mutt_perror(ctx->path);
233         return -1;
234     }
235
236     mutt_block_signals();
237     if (mbox_lock_mailbox(ctx, 0, 1) < 0) {
238         mutt_unblock_signals();
239         return -1;
240     }
241
242     rc = ctx->magic == M_MBOX ? mbox_parse_mailbox(ctx) : -1;
243
244     mbox_unlock_mailbox(ctx);
245     mutt_unblock_signals();
246     return rc;
247 }
248
249 /* check to see if the mailbox has changed on disk.
250  *
251  * return values:
252  *      M_REOPENED      mailbox has been reopened
253  *      M_NEW_MAIL      new mail has arrived!
254  *      M_LOCKED        couldn't lock the file
255  *      0               no change
256  *      -1              error
257  */
258 static int _mbox_check_mailbox (CONTEXT * ctx, int *index_hint)
259 {
260   struct stat st;
261   char buffer[LONG_STRING];
262   int unlock = 0;
263   int modified = 0;
264
265   if (stat (ctx->path, &st) == 0) {
266     if (st.st_mtime == ctx->mtime && st.st_size == ctx->size)
267       return (0);
268
269     if (st.st_size == ctx->size) {
270       /* the file was touched, but it is still the same length, so just exit */
271       ctx->mtime = st.st_mtime;
272       return (0);
273     }
274
275     if (st.st_size > ctx->size) {
276       /* lock the file if it isn't already */
277       if (!ctx->locked) {
278         mutt_block_signals ();
279         if (mbox_lock_mailbox (ctx, 0, 0) == -1) {
280           mutt_unblock_signals ();
281           /* we couldn't lock the mailbox, but nothing serious happened:
282            * probably the new mail arrived: no reason to wait till we can
283            * parse it: we'll get it on the next pass
284            */
285           return (M_LOCKED);
286         }
287         unlock = 1;
288       }
289
290       /*
291        * Check to make sure that the only change to the mailbox is that 
292        * message(s) were appended to this file.  My heuristic is that we should
293        * see the message separator at *exactly* what used to be the end of the
294        * folder.
295        */
296       fseeko (ctx->fp, ctx->size, SEEK_SET);
297       if (fgets (buffer, sizeof (buffer), ctx->fp) != NULL) {
298         if (ctx->magic == M_MBOX && m_strncmp("From ", buffer, 5) == 0) {
299           fseeko (ctx->fp, ctx->size, SEEK_SET);
300           mbox_parse_mailbox (ctx);
301
302           /* Only unlock the folder if it was locked inside of this routine.
303            * It may have been locked elsewhere, like in
304            * mutt_checkpoint_mailbox().
305            */
306
307           if (unlock) {
308             mbox_unlock_mailbox (ctx);
309             mutt_unblock_signals ();
310           }
311
312           return (M_NEW_MAIL);  /* signal that new mail arrived */
313         }
314         else
315           modified = 1;
316       } else {
317         modified = 1;
318       }
319     } else {
320       modified = 1;
321     }
322   }
323
324   if (modified) {
325     if (mbox_reopen_mailbox (ctx, index_hint) != -1) {
326       if (unlock) {
327         mbox_unlock_mailbox (ctx);
328         mutt_unblock_signals ();
329       }
330       return (M_REOPENED);
331     }
332   }
333
334   /* fatal error */
335
336   mbox_unlock_mailbox (ctx);
337   mx_fastclose_mailbox (ctx);
338   mutt_unblock_signals ();
339   mutt_error _("Mailbox was corrupted!");
340
341   return (-1);
342 }
343
344 static int mbox_check_mailbox(CONTEXT *ctx, int *index_hint, int lock)
345 {
346     int rc = 0;
347
348     if (lock) {
349         mutt_block_signals();
350         if (mbox_lock_mailbox(ctx, 0, 0) < 0) {
351             mutt_unblock_signals();
352             return M_LOCKED;
353         }
354     }
355
356     rc = _mbox_check_mailbox(ctx, index_hint);
357
358     if (lock) {
359         mutt_unblock_signals ();
360         mbox_unlock_mailbox (ctx);
361     }
362     return rc;
363 }
364
365 /* return values:
366  *      0       success
367  *      -1      failure
368  */
369 static int mbox_sync_mailbox (CONTEXT * ctx, int unused __attribute__ ((unused)), int *index_hint)
370 {
371   char tempfile[_POSIX_PATH_MAX];
372   char buf[32];
373   int i, j, save_sort = SORT_ORDER;
374   int rc = -1;
375   int need_sort = 0;            /* flag to resort mailbox if new mail arrives */
376   int first = -1;               /* first message to be written */
377   off_t offset;                /* location in mailbox to write changed messages */
378   struct stat statbuf;
379   struct utimbuf utimebuf;
380   struct m_update_t *newOffset = NULL;
381   struct m_update_t *oldOffset = NULL;
382   FILE *fp = NULL;
383
384   /* sort message by their position in the mailbox on disk */
385   if (Sort != SORT_ORDER) {
386     save_sort = Sort;
387     Sort = SORT_ORDER;
388     mutt_sort_headers (ctx, 0);
389     Sort = save_sort;
390     need_sort = 1;
391   }
392
393   /* need to open the file for writing in such a way that it does not truncate
394    * the file, so use read-write mode.
395    */
396   if ((ctx->fp = freopen (ctx->path, "r+", ctx->fp)) == NULL) {
397     mx_fastclose_mailbox (ctx);
398     mutt_error _("Fatal error!  Could not reopen mailbox!");
399
400     return (-1);
401   }
402
403   mutt_block_signals ();
404
405   if (mbox_lock_mailbox (ctx, 1, 1) == -1) {
406     mutt_unblock_signals ();
407     mutt_error _("Unable to lock mailbox!");
408
409     goto bail;
410   }
411
412   /* Check to make sure that the file hasn't changed on disk */
413   if ((i = _mbox_check_mailbox (ctx, index_hint)) == M_NEW_MAIL
414       || i == M_REOPENED) {
415     /* new mail arrived, or mailbox reopened */
416     need_sort = i;
417     rc = i;
418     goto bail;
419   }
420   else if (i < 0)
421     /* fatal error */
422     return (-1);
423
424   /* Create a temporary file to write the new version of the mailbox in. */
425   fp = m_tempfile(tempfile, _POSIX_PATH_MAX, NONULL(MCore.tmpdir), NULL);
426   if (fp == NULL) {
427     mutt_error _("Could not create temporary file!");
428     mutt_sleep (5);
429     goto bail;
430   }
431
432   /* find the first deleted/changed message.  we save a lot of time by only
433    * rewriting the mailbox from the point where it has actually changed.
434    */
435   for (i = 0; i < ctx->msgcount && !ctx->hdrs[i]->deleted &&
436        !ctx->hdrs[i]->changed && !ctx->hdrs[i]->attach_del; i++);
437   if (i == ctx->msgcount) {
438     /* this means ctx->changed or ctx->deleted was set, but no
439      * messages were found to be changed or deleted.  This should
440      * never happen, is we presume it is a bug in mutt.
441      */
442     mutt_error
443       _("sync: mbox modified, but no modified messages! (report this bug)");
444     mutt_sleep (5);             /* the mutt_error /will/ get cleared! */
445     unlink (tempfile);
446     goto bail;
447   }
448
449   /* save the index of the first changed/deleted message */
450   first = i;
451   /* where to start overwriting */
452   offset = ctx->hdrs[i]->offset;
453
454   /* allocate space for the new offsets */
455   newOffset = p_new(struct m_update_t, ctx->msgcount - first);
456   oldOffset = p_new(struct m_update_t, ctx->msgcount - first);
457
458   for (i = first, j = 0; i < ctx->msgcount; i++) {
459     /*
460      * back up some information which is needed to restore offsets when
461      * something fails.
462      */
463
464     oldOffset[i - first].valid = 1;
465     oldOffset[i - first].hdr = ctx->hdrs[i]->offset;
466     oldOffset[i - first].body = ctx->hdrs[i]->content->offset;
467     oldOffset[i - first].lines = ctx->hdrs[i]->lines;
468     oldOffset[i - first].length = ctx->hdrs[i]->content->length;
469
470     if (!ctx->hdrs[i]->deleted) {
471       j++;
472       if (!ctx->quiet && WriteInc && ((i % WriteInc) == 0 || j == 1))
473         mutt_message (_("Writing messages... %d (%d%%)"), i,
474                       (int) (ftello (ctx->fp) / (ctx->size / 100 + 1)));
475
476       /* save the new offset for this message.  we add `offset' because the
477        * temporary file only contains saved message which are located after
478        * `offset' in the real mailbox
479        */
480       newOffset[i - first].hdr = ftello (fp) + offset;
481
482       if (mutt_copy_message
483           (fp, ctx, ctx->hdrs[i], M_CM_UPDATE,
484            CH_FROM | CH_UPDATE | CH_UPDATE_LEN) == -1) {
485         mutt_perror (_("Can't create temporary file"));
486         mutt_sleep (5);
487         unlink (tempfile);
488         goto bail;
489       }
490
491       /* Since messages could have been deleted, the offsets stored in memory
492        * will be wrong, so update what we can, which is the offset of this
493        * message, and the offset of the body.  If this is a multipart message,
494        * we just flush the in memory cache so that the message will be reparsed
495        * if the user accesses it later.
496        */
497       newOffset[i - first].body =
498         ftello (fp) - ctx->hdrs[i]->content->length + offset;
499       body_list_wipe(&ctx->hdrs[i]->content->parts);
500
501       if (fputs ("\n", fp) == EOF) {
502         mutt_perror (_("Can't create temporary file"));
503         mutt_sleep (5);
504         unlink (tempfile);
505         goto bail;
506       }
507     }
508   }
509
510   if (m_fclose(&fp) != 0) {
511     unlink (tempfile);
512     mutt_perror (_("Can't create temporary file"));
513     mutt_sleep (5);
514     goto bail;
515   }
516
517   /* Save the state of this folder. */
518   if (stat (ctx->path, &statbuf) == -1) {
519     mutt_perror (ctx->path);
520     mutt_sleep (5);
521     unlink (tempfile);
522     goto bail;
523   }
524
525   if ((fp = fopen (tempfile, "r")) == NULL) {
526     mutt_unblock_signals ();
527     mx_fastclose_mailbox (ctx);
528     mutt_perror (_("Can't create temporary file"));
529     mutt_sleep (5);
530     return (-1);
531   }
532
533   if (fseeko (ctx->fp, offset, SEEK_SET) != 0 /* seek the append location */
534       /* do a sanity check to make sure the mailbox looks ok */
535   ||  fgets (buf, sizeof (buf), ctx->fp) == NULL
536   ||  (ctx->magic == M_MBOX && m_strncmp("From ", buf, 5) != 0))
537   {
538     i = -1;
539   }
540   else {
541     if (fseeko (ctx->fp, offset, SEEK_SET) != 0) {       /* return to proper offset */
542       i = -1;
543     } else {
544       /* copy the temp mailbox back into place starting at the first
545        * change/deleted message
546        */
547       mutt_message _("Committing changes...");
548
549       i = mutt_copy_stream (fp, ctx->fp);
550
551       if (ferror (ctx->fp))
552         i = -1;
553     }
554     if (i == 0) {
555       ctx->size = ftello (ctx->fp);      /* update the size of the mailbox */
556       ftruncate (fileno (ctx->fp), ctx->size);
557     }
558   }
559
560   m_fclose(&fp);
561   mbox_unlock_mailbox (ctx);
562
563   if (m_fclose(&ctx->fp) != 0 || i == -1) {
564     /* error occured while writing the mailbox back, so keep the temp copy
565      * around
566      */
567
568     char savefile[_POSIX_PATH_MAX];
569
570     snprintf(savefile, sizeof (savefile), "%s/mutt.%s-%u",
571              NONULL(MCore.tmpdir), NONULL(MCore.username), (unsigned int)getpid());
572     rename (tempfile, savefile);
573     mutt_unblock_signals ();
574     mx_fastclose_mailbox (ctx);
575     mutt_pretty_mailbox (savefile);
576     mutt_error (_("Write failed!  Saved partial mailbox to %s"), savefile);
577     mutt_sleep (5);
578     return (-1);
579   }
580
581   /* Restore the previous access/modification times */
582   utimebuf.actime = statbuf.st_atime;
583   utimebuf.modtime = statbuf.st_mtime;
584   utime (ctx->path, &utimebuf);
585
586   /* reopen the mailbox in read-only mode */
587   if ((ctx->fp = fopen (ctx->path, "r")) == NULL) {
588     unlink (tempfile);
589     mutt_unblock_signals ();
590     mx_fastclose_mailbox (ctx);
591     mutt_error _("Fatal error!  Could not reopen mailbox!");
592     return (-1);
593   }
594
595   /* update the offsets of the rewritten messages */
596   for (i = first, j = first; i < ctx->msgcount; i++) {
597     if (!ctx->hdrs[i]->deleted) {
598       ctx->hdrs[i]->offset = newOffset[i - first].hdr;
599       ctx->hdrs[i]->content->hdr_offset = newOffset[i - first].hdr;
600       ctx->hdrs[i]->content->offset = newOffset[i - first].body;
601       ctx->hdrs[i]->index = j++;
602     }
603   }
604   p_delete(&newOffset);
605   p_delete(&oldOffset);
606   unlink (tempfile);            /* remove partial copy of the mailbox */
607   mutt_unblock_signals ();
608
609   return (0);                   /* signal success */
610
611 bail:                          /* Come here in case of disaster */
612
613   m_fclose(&fp);
614
615   /* restore offsets, as far as they are valid */
616   if (first >= 0 && oldOffset) {
617     for (i = first; i < ctx->msgcount && oldOffset[i - first].valid; i++) {
618       ctx->hdrs[i]->offset = oldOffset[i - first].hdr;
619       ctx->hdrs[i]->content->hdr_offset = oldOffset[i - first].hdr;
620       ctx->hdrs[i]->content->offset = oldOffset[i - first].body;
621       ctx->hdrs[i]->lines = oldOffset[i - first].lines;
622       ctx->hdrs[i]->content->length = oldOffset[i - first].length;
623     }
624   }
625
626   /* this is ok to call even if we haven't locked anything */
627   mbox_unlock_mailbox (ctx);
628
629   mutt_unblock_signals ();
630   p_delete(&newOffset);
631   p_delete(&oldOffset);
632
633   if ((ctx->fp = freopen (ctx->path, "r", ctx->fp)) == NULL) {
634     mutt_error _("Could not reopen mailbox!");
635
636     mx_fastclose_mailbox (ctx);
637     return (-1);
638   }
639
640   if (need_sort)
641     /* if the mailbox was reopened, the thread tree will be invalid so make
642      * sure to start threading from scratch.  */
643     mutt_sort_headers (ctx, (need_sort == M_REOPENED));
644
645   return rc;
646 }
647
648 /* close a mailbox opened in write-mode */
649 int mbox_close_mailbox (CONTEXT * ctx)
650 {
651   mx_unlock_file (ctx->path, fileno (ctx->fp), 1);
652
653   if (ctx->cinfo)
654     mutt_slow_close_compressed (ctx);
655
656   mutt_unblock_signals ();
657   mx_fastclose_mailbox (ctx);
658   return 0;
659 }
660
661 static int mbox_reopen_mailbox (CONTEXT * ctx, int *index_hint)
662 {
663   int (*cmp_headers) (const HEADER *, const HEADER *) = NULL;
664   HEADER **old_hdrs;
665   int old_msgcount;
666   int msg_mod = 0;
667   int index_hint_set;
668   int i, j;
669   int rc = -1;
670
671   /* silent operations */
672   ctx->quiet = 1;
673
674   mutt_message _("Reopening mailbox...");
675
676   /* our heuristics require the old mailbox to be unsorted */
677   if (Sort != SORT_ORDER) {
678     short old_sort;
679
680     old_sort = Sort;
681     Sort = SORT_ORDER;
682     mutt_sort_headers (ctx, 1);
683     Sort = old_sort;
684   }
685
686   old_hdrs = NULL;
687   old_msgcount = 0;
688
689   /* simulate a close */
690   if (ctx->id_hash)
691     hash_delete (&ctx->id_hash, NULL);
692   if (ctx->subj_hash)
693     hash_delete (&ctx->subj_hash, NULL);
694   mutt_clear_threads (ctx);
695   p_delete(&ctx->v2r);
696   if (ctx->readonly) {
697     for (i = 0; i < ctx->msgcount; i++)
698       header_delete(&(ctx->hdrs[i]));       /* nothing to do! */
699     p_delete(&ctx->hdrs);
700   }
701   else {
702     /* save the old headers */
703     old_msgcount = ctx->msgcount;
704     old_hdrs = ctx->hdrs;
705     ctx->hdrs = NULL;
706   }
707
708   ctx->hdrmax = 0;              /* force allocation of new headers */
709   ctx->msgcount = 0;
710   ctx->vcount = 0;
711   ctx->tagged = 0;
712   ctx->deleted = 0;
713   ctx->new = 0;
714   ctx->unread = 0;
715   ctx->flagged = 0;
716   ctx->changed = 0;
717   ctx->id_hash = NULL;
718   ctx->subj_hash = NULL;
719
720   if (ctx->magic == M_MBOX && !fseeko(ctx->fp, 0, SEEK_SET)) {
721     cmp_headers = mutt_cmp_header;
722     rc = mbox_parse_mailbox (ctx);
723   } else {
724     rc = -1;
725   }
726
727   if (rc == -1) {
728     /* free the old headers */
729     for (j = 0; j < old_msgcount; j++)
730       header_delete(&(old_hdrs[j]));
731     p_delete(&old_hdrs);
732
733     ctx->quiet = 0;
734     return (-1);
735   }
736
737   /* now try to recover the old flags */
738
739   index_hint_set = (index_hint == NULL);
740
741   if (!ctx->readonly) {
742     for (i = 0; i < ctx->msgcount; i++) {
743       int found = 0;
744
745       /* some messages have been deleted, and new  messages have been
746        * appended at the end; the heuristic is that old messages have then
747        * "advanced" towards the beginning of the folder, so we begin the
748        * search at index "i"
749        */
750       for (j = i; j < old_msgcount; j++) {
751         if (old_hdrs[j] == NULL)
752           continue;
753         if (cmp_headers (ctx->hdrs[i], old_hdrs[j])) {
754           found = 1;
755           break;
756         }
757       }
758       if (!found) {
759         for (j = 0; j < i && j < old_msgcount; j++) {
760           if (old_hdrs[j] == NULL)
761             continue;
762           if (cmp_headers (ctx->hdrs[i], old_hdrs[j])) {
763             found = 1;
764             break;
765           }
766         }
767       }
768
769       if (found) {
770         /* this is best done here */
771         if (!index_hint_set && *index_hint == j)
772           *index_hint = i;
773
774         if (old_hdrs[j]->changed) {
775           /* Only update the flags if the old header was changed;
776            * otherwise, the header may have been modified externally,
777            * and we don't want to lose _those_ changes
778            */
779           mutt_set_flag (ctx, ctx->hdrs[i], M_FLAG, old_hdrs[j]->flagged);
780           mutt_set_flag (ctx, ctx->hdrs[i], M_REPLIED, old_hdrs[j]->replied);
781           mutt_set_flag (ctx, ctx->hdrs[i], M_OLD, old_hdrs[j]->old);
782           mutt_set_flag (ctx, ctx->hdrs[i], M_READ, old_hdrs[j]->read);
783         }
784         mutt_set_flag (ctx, ctx->hdrs[i], M_DELETE, old_hdrs[j]->deleted);
785         mutt_set_flag (ctx, ctx->hdrs[i], M_TAG, old_hdrs[j]->tagged);
786
787         /* we don't need this header any more */
788         header_delete(&(old_hdrs[j]));
789       }
790     }
791
792     /* free the remaining old headers */
793     for (j = 0; j < old_msgcount; j++) {
794       if (old_hdrs[j]) {
795         header_delete(&(old_hdrs[j]));
796         msg_mod = 1;
797       }
798     }
799     p_delete(&old_hdrs);
800   }
801
802   ctx->quiet = 0;
803
804   return ((ctx->changed || msg_mod) ? M_REOPENED : M_NEW_MAIL);
805 }
806
807 /*
808  * Returns:
809  * 1 if the mailbox is not empty
810  * 0 if the mailbox is empty
811  * -1 on error
812  */
813 int mbox_check_empty (const char *path)
814 {
815   struct stat st;
816
817   if (stat (path, &st) == -1)
818     return -1;
819
820   return ((st.st_size == 0));
821 }
822
823 int mbox_is_magic (const char* path, struct stat* st)
824 {
825   int magic = -1;
826   FILE* f;
827   char tmp[_POSIX_PATH_MAX];
828
829   if (S_ISDIR(st->st_mode))
830     return (-1);
831
832   if (st->st_size == 0) {
833     return M_MBOX;
834   }
835   else if ((f = fopen (path, "r")) != NULL) {
836     struct utimbuf times;
837
838     fgets (tmp, sizeof (tmp), f);
839     if (m_strncmp("From ", tmp, 5) == 0)
840       magic = M_MBOX;
841     m_fclose(&f);
842
843     /* need to restore the times here, the file was not really accessed,
844      * only the type was accessed.  This is important, because detection
845      * of "new mail" depends on those times set correctly.
846      */
847     times.actime = st->st_atime;
848     times.modtime = st->st_mtime;
849     utime (path, &times);
850   } else {
851     mutt_perror (path);
852     return (-1);         /* fopen failed */
853   }
854
855   if (magic == -1 && mutt_can_read_compressed (path))
856     return (M_COMPRESSED);
857   return (magic);
858 }
859
860 static int mbox_commit_message (MESSAGE* msg, CONTEXT* ctx) {
861   if (fputc ('\n', msg->fp) == EOF)
862     return -1;
863   if ((fflush (msg->fp) == EOF || fsync (fileno (msg->fp)) == -1)) {
864     mutt_perror (_("Can't write message"));
865     return -1;
866   }
867   return 0;
868 }
869
870 mx_t const mbox_mx = {
871     M_MBOX,
872     1,
873     mbox_is_magic,
874     mbox_check_empty,
875     access,
876     mbox_open_mailbox,
877     mbox_open_new_message,
878     NULL,
879     mbox_check_mailbox,
880     NULL,
881     mbox_sync_mailbox,
882     mbox_commit_message,
883 };