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