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