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