NeoMutt  2025-12-11-980-ge38c27
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
mbox.c
Go to the documentation of this file.
1
25
36
37#include "config.h"
38#include <errno.h>
39#include <fcntl.h>
40#include <inttypes.h>
41#include <stdbool.h>
42#include <stdio.h>
43#include <string.h>
44#include <sys/stat.h>
45#include <sys/types.h>
46#include <time.h>
47#include <unistd.h>
48#include <utime.h>
49#include "mutt/lib.h"
50#include "address/lib.h"
51#include "config/lib.h"
52#include "email/lib.h"
53#include "core/lib.h"
54#include "mutt.h"
55#include "lib.h"
56#include "progress/lib.h"
57#include "globals.h"
58#include "muttlib.h"
59#include "mx.h"
60
64struct MUpdate
65{
66 bool valid;
67 LOFF_T hdr;
68 LOFF_T body;
69 long lines;
70 LOFF_T length;
71};
72
76static void mbox_adata_free(void **ptr)
77{
78 if (!ptr || !*ptr)
79 return;
80
81 struct MboxAccountData *m = *ptr;
82
84 FREE(ptr);
85}
86
91static struct MboxAccountData *mbox_adata_new(void)
92{
93 return MUTT_MEM_CALLOC(1, struct MboxAccountData);
94}
95
102static int init_mailbox(struct Mailbox *m)
103{
104 if (!m || !m->account)
105 return -1;
106 if ((m->type != MUTT_MBOX) && (m->type != MUTT_MMDF))
107 return -1;
108 if (m->account->adata)
109 return 0;
110
113 return 0;
114}
115
121static struct MboxAccountData *mbox_adata_get(struct Mailbox *m)
122{
123 if (init_mailbox(m) == -1)
124 return NULL;
125 return m->account->adata;
126}
127
136static int mbox_lock_mailbox(struct Mailbox *m, bool excl, bool retry)
137{
139 if (!adata)
140 return -1;
141
142 int rc = mutt_file_lock(fileno(adata->fp), excl, retry);
143 if (rc == 0)
144 {
145 adata->locked = true;
146 }
147 else if (retry && !excl)
148 {
149 m->readonly = true;
150 return 0;
151 }
152
153 return rc;
154}
155
160static void mbox_unlock_mailbox(struct Mailbox *m)
161{
163 if (!adata)
164 return;
165
166 if (adata->locked)
167 {
168 fflush(adata->fp);
169
170 mutt_file_unlock(fileno(adata->fp));
171 adata->locked = false;
172 }
173}
174
181{
182 if (!m)
183 return MX_OPEN_ERROR;
184
186 if (!adata)
187 return MX_OPEN_ERROR;
188
189 char buf[8192] = { 0 };
190 char return_path[1024] = { 0 };
191 int count = 0;
192 int lines;
193 time_t t = 0;
194 LOFF_T loc;
195 LOFF_T tmploc;
196 struct Email *e = NULL;
197 struct stat st = { 0 };
198 struct Progress *progress = NULL;
200
201 /* Record mailbox timestamps and size for change detection */
202 if (stat(mailbox_path(m), &st) == -1)
203 {
204 mutt_perror("%s", mailbox_path(m));
205 goto fail;
206 }
209 m->size = st.st_size;
210
211 if (m->verbose)
212 {
213 progress = progress_new(MUTT_PROGRESS_READ, 0);
214 progress_set_message(progress, _("Reading %s..."), mailbox_path(m));
215 }
216
217 /* Read the file line-by-line looking for MMDF separator lines ("\001\001\001\001\n") */
218 while (true)
219 {
220 if (!fgets(buf, sizeof(buf) - 1, adata->fp))
221 break;
222
223 if (SigInt)
224 break;
225
226 if (mutt_str_equal(buf, MMDF_SEP))
227 {
228 loc = ftello(adata->fp);
229 if (loc < 0)
230 goto fail;
231
232 count++;
233 progress_update(progress, count, (int) (loc / (m->size / 100 + 1)));
234
235 /* Allocate a new Email for this message */
237 e = email_new();
238 m->emails[m->msg_count] = e;
239 e->offset = loc;
240 e->index = m->msg_count;
241
242 if (!fgets(buf, sizeof(buf) - 1, adata->fp))
243 {
244 mutt_debug(LL_DEBUG1, "unexpected EOF\n");
245 email_free(&m->emails[m->msg_count]);
246 break;
247 }
248
249 /* Try to parse "From " line for return path and date */
250 return_path[0] = '\0';
251
252 if (!is_from(buf, return_path, sizeof(return_path), &t))
253 {
254 if (!mutt_file_seek(adata->fp, loc, SEEK_SET))
255 {
256 mutt_error(_("Mailbox is corrupt"));
257 goto fail;
258 }
259 }
260 else
261 {
262 e->received = t - mutt_date_local_tz(t);
263 }
264
265 /* Parse RFC 822 headers to populate the envelope */
266 e->env = mutt_rfc822_read_header(adata->fp, e, false, false);
267
268 loc = ftello(adata->fp);
269 if (loc < 0)
270 goto fail;
271
272 /* Try to validate the Content-Length by seeking to the expected end
273 * and verifying an MMDF separator is there */
274 if ((e->body->length > 0) && (e->lines > 0))
275 {
276 tmploc = loc + e->body->length;
277
278 if ((tmploc > 0) && (tmploc < m->size))
279 {
280 if (!mutt_file_seek(adata->fp, tmploc, SEEK_SET) ||
281 !fgets(buf, sizeof(buf) - 1, adata->fp) || !mutt_str_equal(MMDF_SEP, buf))
282 {
283 (void) mutt_file_seek(adata->fp, loc, SEEK_SET);
284 e->body->length = -1;
285 }
286 }
287 else
288 {
289 e->body->length = -1;
290 }
291 }
292 else
293 {
294 e->body->length = -1;
295 }
296
297 /* If Content-Length was missing or invalid, scan for the next separator */
298 if (e->body->length < 0)
299 {
300 lines = -1;
301 do
302 {
303 loc = ftello(adata->fp);
304 if (loc < 0)
305 goto fail;
306 if (!fgets(buf, sizeof(buf) - 1, adata->fp))
307 break;
308 lines++;
309 } while (!mutt_str_equal(buf, MMDF_SEP));
310
311 e->lines = lines;
312 e->body->length = loc - e->body->offset;
313 }
314
315 /* Populate return path and from address if missing from headers */
316 if (TAILQ_EMPTY(&e->env->return_path) && return_path[0])
317 mutt_addrlist_parse(&e->env->return_path, return_path);
318
319 if (TAILQ_EMPTY(&e->env->from))
320 mutt_addrlist_copy(&e->env->from, &e->env->return_path, false);
321
322 m->msg_count++;
323 }
324 else
325 {
326 mutt_debug(LL_DEBUG1, "corrupt mailbox\n");
327 mutt_error(_("Mailbox is corrupt"));
328 goto fail;
329 }
330 }
331
332 if (SigInt)
333 {
334 SigInt = false;
335 rc = MX_OPEN_ABORT; /* action aborted */
336 goto fail;
337 }
338
339 rc = MX_OPEN_OK;
340fail:
341 progress_free(&progress);
342 return rc;
343}
344
357{
358 if (!m)
359 return MX_OPEN_ERROR;
360
362 if (!adata)
363 return MX_OPEN_ERROR;
364
365 struct stat st = { 0 };
366 char buf[8192] = { 0 };
367 char return_path[1024] = { 0 };
368 struct Email *e_cur = NULL;
369 time_t t = 0;
370 int count = 0;
371 int lines = 0;
372 bool has_mbox_sep = false;
373 bool expect_from_line = true;
374 LOFF_T loc;
375 struct Progress *progress = NULL;
377
378 /* Save information about the folder at the time we opened it. */
379 if (stat(mailbox_path(m), &st) == -1)
380 {
381 mutt_perror("%s", mailbox_path(m));
382 goto fail;
383 }
384
385 m->size = st.st_size;
388
389 if (!m->readonly)
390 m->readonly = access(mailbox_path(m), W_OK) ? true : false;
391
392 if (m->verbose)
393 {
394 progress = progress_new(MUTT_PROGRESS_READ, 0);
395 progress_set_message(progress, _("Reading %s..."), mailbox_path(m));
396 }
397
398 loc = ftello(adata->fp);
399 if (loc < 0)
400 {
401 mutt_debug(LL_DEBUG1, "ftello: %s (errno %d)\n", strerror(errno), errno);
402 loc = 0;
403 }
404
405 while ((fgets(buf, sizeof(buf), adata->fp)) && !SigInt)
406 {
407 if (is_from(buf, return_path, sizeof(return_path), &t))
408 {
409 /* Save the Content-Length of the previous message */
410 if (count > 0)
411 {
412 struct Email *e = m->emails[m->msg_count - 1];
413 if (!has_mbox_sep)
414 {
415 mutt_debug(LL_DEBUG1, "mbox_parse_mailbox: missing separator at location: " OFF_T_FMT "\n",
416 loc);
417 }
418 if (e->body->length < 0)
419 {
420 e->body->length = loc - e->body->offset - (has_mbox_sep ? 1 : 0);
421 if (e->body->length < 0)
422 e->body->length = 0;
423 }
424 if (e->lines == 0)
425 e->lines = lines ? lines - 1 : 0;
426 }
427
428 count++;
429 expect_from_line = false;
430
431 progress_update(progress, count, (int) (ftello(adata->fp) / (m->size / 100 + 1)));
432
434
435 m->emails[m->msg_count] = email_new();
436 e_cur = m->emails[m->msg_count];
437 e_cur->received = t - mutt_date_local_tz(t);
438 e_cur->offset = loc;
439 e_cur->index = m->msg_count;
440
441 e_cur->env = mutt_rfc822_read_header(adata->fp, e_cur, false, false);
442
443 /* if we know how long this message is, either just skip over the body,
444 * or if we don't know how many lines there are, count them now (this will
445 * save time by not having to search for the next message marker). */
446 if (e_cur->body->length > 0)
447 {
448 LOFF_T tmploc;
449
450 loc = ftello(adata->fp);
451 if (loc < 0)
452 {
453 mutt_debug(LL_DEBUG1, "ftello: %s (errno %d)\n", strerror(errno), errno);
454 loc = 0;
455 }
456
457 /* The test below avoids a potential integer overflow if the
458 * content-length is huge (thus necessarily invalid). */
459 tmploc = (e_cur->body->length < m->size) ? (loc + e_cur->body->length + 1) : -1;
460
461 if ((tmploc > 0) && (tmploc < m->size))
462 {
463 /* check to see if the content-length looks valid. we expect to
464 * to see a valid message separator at this point in the stream */
465 if (!mutt_file_seek(adata->fp, tmploc, SEEK_SET) ||
466 !fgets(buf, sizeof(buf), adata->fp) || !mutt_str_startswith(buf, "From "))
467 {
468 mutt_debug(LL_DEBUG1, "bad content-length in message %d (cl=" OFF_T_FMT ")\n",
469 e_cur->index, e_cur->body->length);
470 mutt_debug(LL_DEBUG1, " LINE: %s", buf);
471 /* nope, return the previous position */
472 (void) mutt_file_seek(adata->fp, loc, SEEK_SET);
473 e_cur->body->length = -1;
474 }
475 }
476 else if (tmploc != m->size)
477 {
478 /* content-length would put us past the end of the file, so it
479 * must be wrong */
480 e_cur->body->length = -1;
481 }
482
483 if (e_cur->body->length != -1)
484 {
485 /* good content-length. check to see if we know how many lines
486 * are in this message. */
487 if (e_cur->lines == 0)
488 {
489 LOFF_T cl = e_cur->body->length;
490
491 /* count the number of lines in this message */
492 (void) mutt_file_seek(adata->fp, loc, SEEK_SET);
493 while (cl-- > 0)
494 {
495 if (fgetc(adata->fp) == '\n')
496 e_cur->lines++;
497 }
498 }
499
500 /* return to the offset of the next *mbox* separator */
501 (void) mutt_file_seek(adata->fp, tmploc - 1, SEEK_SET);
502 expect_from_line = true;
503 }
504 }
505
506 m->msg_count++;
507
508 if (TAILQ_EMPTY(&e_cur->env->return_path) && return_path[0])
509 {
510 mutt_addrlist_parse(&e_cur->env->return_path, return_path);
511 }
512
513 if (TAILQ_EMPTY(&e_cur->env->from))
514 mutt_addrlist_copy(&e_cur->env->from, &e_cur->env->return_path, false);
515
516 lines = 0;
517 has_mbox_sep = false;
518 }
519 else
520 {
521 lines++;
522 has_mbox_sep = mutt_str_equal(MBOX_SEP, buf);
523 if (expect_from_line && !has_mbox_sep)
524 {
525 mutt_debug(LL_DEBUG1, "mbox_parse_mailbox: missing From_ line at location: " OFF_T_FMT "\n",
526 loc);
527 mutt_error(_("Mailbox is corrupt"));
528 goto fail;
529 }
530 }
531
532 loc = ftello(adata->fp);
533 }
534
535 /* Only set the content-length of the previous message if we have read more
536 * than one message during _this_ invocation. If this routine is called
537 * when new mail is received, we need to make sure not to clobber what
538 * previously was the last message since the headers may be sorted. */
539 if (count > 0)
540 {
541 struct Email *e = m->emails[m->msg_count - 1];
542 if (!has_mbox_sep)
543 {
545 "mbox_parse_mailbox: missing separator at location: " OFF_T_FMT "\n", loc);
546 }
547 if (e->body->length < 0)
548 {
549 e->body->length = ftello(adata->fp) - e->body->offset - (has_mbox_sep ? 1 : 0);
550 if (e->body->length < 0)
551 e->body->length = 0;
552 }
553
554 if (e->lines == 0)
555 e->lines = lines ? lines - 1 : 0;
556 }
557
558 if (SigInt)
559 {
560 SigInt = false;
561 rc = MX_OPEN_ABORT;
562 goto fail; /* action aborted */
563 }
564
565 rc = MX_OPEN_OK;
566fail:
567 progress_free(&progress);
568 return rc;
569}
570
577static int reopen_mailbox(struct Mailbox *m)
578{
579 if (!m)
580 return -1;
581
583 if (!adata)
584 return -1;
585
586 bool (*cmp_headers)(const struct Email *, const struct Email *) = NULL;
587 struct Email **e_old = NULL;
588 int old_msg_count;
589 bool msg_mod = false;
590 int rc = -1;
591
592 /* silent operations */
593 m->verbose = false;
594
595 /* our heuristics require the old mailbox to be unsorted */
596 const enum EmailSortType c_sort = cs_subset_sort(NeoMutt->sub, "sort");
597 if (c_sort != EMAIL_SORT_UNSORTED)
598 {
601 cs_subset_str_native_set(NeoMutt->sub, "sort", c_sort, NULL);
602 }
603
604 e_old = NULL;
605 old_msg_count = 0;
606
607 /* simulate a close */
611 FREE(&m->v2r);
612 if (m->readonly)
613 {
614 for (int i = 0; i < m->msg_count; i++)
615 email_free(&(m->emails[i])); /* nothing to do! */
616 FREE(&m->emails);
617 }
618 else
619 {
620 /* save the old headers */
621 old_msg_count = m->msg_count;
622 e_old = m->emails;
623 m->emails = NULL;
624 }
625
626 m->email_max = 0; /* force allocation of new headers */
627 m->msg_count = 0;
628 m->vcount = 0;
629 m->msg_tagged = 0;
630 m->msg_deleted = 0;
631 m->msg_new = 0;
632 m->msg_unread = 0;
633 m->msg_flagged = 0;
634 m->changed = false;
635 m->id_hash = NULL;
636 m->subj_hash = NULL;
638
639 switch (m->type)
640 {
641 case MUTT_MBOX:
642 case MUTT_MMDF:
643 cmp_headers = email_cmp_strict;
644 mutt_file_fclose(&adata->fp);
645 adata->fp = mutt_file_fopen(mailbox_path(m), "r");
646 if (!adata->fp)
647 rc = -1;
648 else if (m->type == MUTT_MBOX)
649 rc = mbox_parse_mailbox(m);
650 else
651 rc = mmdf_parse_mailbox(m);
652 break;
653
654 default:
655 rc = -1;
656 break;
657 }
658
659 if (rc == -1)
660 {
661 /* free the old headers */
662 for (int i = 0; i < old_msg_count; i++)
663 email_free(&(e_old[i]));
664 FREE(&e_old);
665
666 m->verbose = true;
667 return -1;
668 }
669
670 mutt_file_touch_atime(fileno(adata->fp));
671
672 /* now try to recover the old flags */
673
674 if (!m->readonly)
675 {
676 for (int i = 0; i < m->msg_count; i++)
677 {
678 bool found = false;
679
680 /* some messages have been deleted, and new messages have been
681 * appended at the end; the heuristic is that old messages have then
682 * "advanced" towards the beginning of the folder, so we begin the
683 * search at index "i" */
684 int j = 0;
685 for (j = i; j < old_msg_count; j++)
686 {
687 if (!e_old[j])
688 continue;
689 if (cmp_headers(m->emails[i], e_old[j]))
690 {
691 found = true;
692 break;
693 }
694 }
695 if (!found)
696 {
697 for (j = 0; (j < i) && (j < old_msg_count); j++)
698 {
699 if (!e_old[j])
700 continue;
701 if (cmp_headers(m->emails[i], e_old[j]))
702 {
703 found = true;
704 break;
705 }
706 }
707 }
708
709 if (found)
710 {
711 m->changed = true;
712 if (e_old[j]->changed)
713 {
714 /* Only update the flags if the old header was changed;
715 * otherwise, the header may have been modified externally,
716 * and we don't want to lose _those_ changes */
717 mutt_set_flag(m, m->emails[i], MUTT_FLAG, e_old[j]->flagged, true);
718 mutt_set_flag(m, m->emails[i], MUTT_REPLIED, e_old[j]->replied, true);
719 mutt_set_flag(m, m->emails[i], MUTT_OLD, e_old[j]->old, true);
720 mutt_set_flag(m, m->emails[i], MUTT_READ, e_old[j]->read, true);
721 }
722 mutt_set_flag(m, m->emails[i], MUTT_DELETE, e_old[j]->deleted, true);
723 mutt_set_flag(m, m->emails[i], MUTT_PURGE, e_old[j]->purge, true);
724 mutt_set_flag(m, m->emails[i], MUTT_TAG, e_old[j]->tagged, true);
725
726 /* we don't need this header any more */
727 email_free(&(e_old[j]));
728 }
729 }
730
731 /* free the remaining old emails */
732 for (int j = 0; j < old_msg_count; j++)
733 {
734 if (e_old[j])
735 {
736 email_free(&(e_old[j]));
737 msg_mod = true;
738 }
739 }
740 FREE(&e_old);
741 }
742
744 m->verbose = true;
745
746 return (m->changed || msg_mod) ? MX_STATUS_REOPENED : MX_STATUS_NEW_MAIL;
747}
748
755static bool mbox_has_new(struct Mailbox *m)
756{
757 for (int i = 0; i < m->msg_count; i++)
758 {
759 struct Email *e = m->emails[i];
760 if (!e)
761 break;
762 if (!e->deleted && !e->read && !e->old)
763 return true;
764 }
765 return false;
766}
767
776void mbox_reset_atime(struct Mailbox *m, struct stat *st)
777{
778 struct stat st2 = { 0 };
779 if (!st)
780 {
781 if (stat(mailbox_path(m), &st2) < 0)
782 return;
783 st = &st2;
784 }
785
786 struct utimbuf utimebuf = { 0 };
787 utimebuf.actime = st->st_atime;
788 utimebuf.modtime = st->st_mtime;
789
790 /* When $mbox_check_recent is set, existing new mail is ignored, so do not
791 * reset the atime to mtime-1 to signal new mail. */
792 const bool c_mail_check_recent = cs_subset_bool(NeoMutt->sub, "mail_check_recent");
793 if (!c_mail_check_recent && (utimebuf.actime >= utimebuf.modtime) && mbox_has_new(m))
794 {
795 utimebuf.actime = utimebuf.modtime - 1;
796 }
797
798 utime(mailbox_path(m), &utimebuf);
799}
800
804static bool mbox_ac_owns_path(struct Account *a, const char *path)
805{
806 if ((a->type != MUTT_MBOX) && (a->type != MUTT_MMDF))
807 return false;
808
809 struct Mailbox **mp = ARRAY_FIRST(&a->mailboxes);
810 if (!mp)
811 return false;
812
813 return mutt_str_equal(mailbox_path(*mp), path);
814}
815
819static bool mbox_ac_add(struct Account *a, struct Mailbox *m)
820{
821 return true;
822}
823
831static FILE *mbox_open_readwrite(struct Mailbox *m)
832{
833 FILE *fp = mutt_file_fopen(mailbox_path(m), "r+");
834 if (fp)
835 m->readonly = false;
836 return fp;
837}
838
846static FILE *mbox_open_readonly(struct Mailbox *m)
847{
848 FILE *fp = mutt_file_fopen(mailbox_path(m), "r");
849 if (fp)
850 m->readonly = true;
851 return fp;
852}
853
858{
859 if (init_mailbox(m) != 0)
860 return MX_OPEN_ERROR;
861
863 if (!adata)
864 return MX_OPEN_ERROR;
865
866 adata->fp = m->readonly ? NULL : mbox_open_readwrite(m);
867 if (!adata->fp)
868 {
869 adata->fp = mbox_open_readonly(m);
870 }
871 if (!adata->fp)
872 {
873 mutt_perror("%s", mailbox_path(m));
874 return MX_OPEN_ERROR;
875 }
876
878 if (mbox_lock_mailbox(m, false, true) == -1)
879 {
881 return MX_OPEN_ERROR;
882 }
883
884 m->has_new = true;
886 if (m->type == MUTT_MBOX)
887 rc = mbox_parse_mailbox(m);
888 else if (m->type == MUTT_MMDF)
889 rc = mmdf_parse_mailbox(m);
890 else
891 rc = MX_OPEN_ERROR;
892
893 if (!mbox_has_new(m))
894 m->has_new = false;
895 clearerr(adata->fp); // Clear the EOF flag
896 mutt_file_touch_atime(fileno(adata->fp));
897
900 return rc;
901}
902
906static bool mbox_mbox_open_append(struct Mailbox *m, OpenMailboxFlags flags)
907{
908 if (init_mailbox(m) != 0)
909 return false;
910
912 if (!adata)
913 return false;
914
915 if (!adata->fp)
916 {
917 // create dir recursively
918 char *tmp_path = mutt_path_dirname(mailbox_path(m));
919 if (mutt_file_mkdir(tmp_path, S_IRWXU) == -1)
920 {
921 mutt_perror("%s", mailbox_path(m));
922 FREE(&tmp_path);
923 return false;
924 }
925 FREE(&tmp_path);
926
927 adata->fp = mutt_file_fopen(mailbox_path(m), "a+");
928 if (!adata->fp)
929 {
930 mutt_perror("%s", mailbox_path(m));
931 return false;
932 }
933
934 if (mbox_lock_mailbox(m, true, true) != 0)
935 {
936 mutt_error(_("Couldn't lock %s"), mailbox_path(m));
938 return false;
939 }
940 }
941
942 if (!mutt_file_seek(adata->fp, 0, SEEK_END))
943 {
945 return false;
946 }
947
948 return true;
949}
950
958static enum MxStatus mbox_mbox_check(struct Mailbox *m)
959{
961 if (!adata)
962 return MX_STATUS_ERROR;
963
964 if (!adata->fp)
965 {
966 if (mbox_mbox_open(m) != MX_OPEN_OK)
967 return MX_STATUS_ERROR;
969 }
970 if (!adata->fp)
971 return MX_STATUS_ERROR;
972
973 struct stat st = { 0 };
974 bool unlock = false;
975 bool modified = false;
976
977 if (stat(mailbox_path(m), &st) == 0)
978 {
979 if ((mutt_file_stat_timespec_compare(&st, MUTT_STAT_MTIME, &adata->mtime) == 0) &&
980 (st.st_size == m->size))
981 {
982 return MX_STATUS_OK;
983 }
984
985 if (st.st_size == m->size)
986 {
987 /* the file was touched, but it is still the same length, so just exit */
989 return MX_STATUS_OK;
990 }
991
992 if (st.st_size > m->size)
993 {
994 /* lock the file if it isn't already */
995 if (!adata->locked)
996 {
998 if (mbox_lock_mailbox(m, false, false) == -1)
999 {
1001 /* we couldn't lock the mailbox, but nothing serious happened:
1002 * probably the new mail arrived: no reason to wait till we can
1003 * parse it: we'll get it on the next pass */
1004 return MX_STATUS_LOCKED;
1005 }
1006 unlock = 1;
1007 }
1008
1009 /* Check to make sure that the only change to the mailbox is that
1010 * message(s) were appended to this file. My heuristic is that we should
1011 * see the message separator at *exactly* what used to be the end of the
1012 * folder. */
1013 char buf[1024] = { 0 };
1014 if (!mutt_file_seek(adata->fp, m->size, SEEK_SET))
1015 {
1016 goto error;
1017 }
1018 if (fgets(buf, sizeof(buf), adata->fp))
1019 {
1020 if (((m->type == MUTT_MBOX) && mutt_str_startswith(buf, "From ")) ||
1021 ((m->type == MUTT_MMDF) && mutt_str_equal(buf, MMDF_SEP)))
1022 {
1023 if (!mutt_file_seek(adata->fp, m->size, SEEK_SET))
1024 {
1025 goto error;
1026 }
1027
1028 int old_msg_count = m->msg_count;
1029 if (m->type == MUTT_MBOX)
1031 else
1033
1034 if (m->msg_count > old_msg_count)
1036
1037 /* Only unlock the folder if it was locked inside of this routine.
1038 * It may have been locked elsewhere, like in
1039 * mutt_checkpoint_mailbox(). */
1040 if (unlock)
1041 {
1044 }
1045
1046 return MX_STATUS_NEW_MAIL; /* signal that new mail arrived */
1047 }
1048 else
1049 {
1050 modified = true;
1051 }
1052 }
1053 else
1054 {
1055 mutt_debug(LL_DEBUG1, "fgets returned NULL\n");
1056 modified = true;
1057 }
1058 }
1059 else
1060 {
1061 modified = true;
1062 }
1063 }
1064
1065 if (modified)
1066 {
1067 if (reopen_mailbox(m) != -1)
1068 {
1070 if (unlock)
1071 {
1074 }
1075 return MX_STATUS_REOPENED;
1076 }
1077 }
1078
1079 /* fatal error */
1080
1081error:
1083 mx_fastclose_mailbox(m, false);
1085 mutt_error(_("Mailbox was corrupted"));
1086 return MX_STATUS_ERROR;
1087}
1088
1092static enum MxStatus mbox_mbox_sync(struct Mailbox *m)
1093{
1095 if (!adata)
1096 return MX_STATUS_ERROR;
1097
1098 struct Buffer *tempfile = NULL;
1099 char buf[32] = { 0 };
1100 int j;
1101 bool unlink_tempfile = false;
1102 bool need_sort = false; /* flag to resort mailbox if new mail arrives */
1103 int first = -1; /* first message to be written */
1104 LOFF_T offset; /* location in mailbox to write changed messages */
1105 struct stat st = { 0 };
1106 struct MUpdate *new_offset = NULL;
1107 struct MUpdate *old_offset = NULL;
1108 FILE *fp = NULL;
1109 struct Progress *progress = NULL;
1110 enum MxStatus rc = MX_STATUS_ERROR;
1111
1112 /* sort message by their position in the mailbox on disk */
1113 const enum EmailSortType c_sort = cs_subset_sort(NeoMutt->sub, "sort");
1114 if (c_sort != EMAIL_SORT_UNSORTED)
1115 {
1117 need_sort = true;
1118 }
1119
1120 /* need to open the file for writing in such a way that it does not truncate
1121 * the file, so use read-write mode. */
1122 adata->fp = freopen(mailbox_path(m), "r+", adata->fp);
1123 if (!adata->fp)
1124 {
1125 mx_fastclose_mailbox(m, false);
1126 mutt_error(_("Fatal error! Could not reopen mailbox!"));
1127 goto fatal;
1128 }
1129
1131
1132 if (mbox_lock_mailbox(m, true, true) == -1)
1133 {
1135 mutt_error(_("Unable to lock mailbox"));
1136 goto bail;
1137 }
1138
1139 /* Check to make sure that the file hasn't changed on disk */
1140 enum MxStatus check = mbox_mbox_check(m);
1141 if ((check == MX_STATUS_NEW_MAIL) || (check == MX_STATUS_REOPENED))
1142 {
1143 /* new mail arrived, or mailbox reopened */
1144 rc = check;
1145 goto bail;
1146 }
1147 else if (check < 0)
1148 {
1149 goto fatal;
1150 }
1151
1152 /* Create a temporary file to write the new version of the mailbox in. */
1153 tempfile = buf_pool_get();
1154 buf_mktemp(tempfile);
1155 int fd = open(buf_string(tempfile), O_WRONLY | O_EXCL | O_CREAT, 0600);
1156 if ((fd == -1) || !(fp = fdopen(fd, "w")))
1157 {
1158 if (fd != -1)
1159 {
1160 close(fd);
1161 unlink_tempfile = true;
1162 }
1163 mutt_error(_("Could not create temporary file"));
1164 goto bail;
1165 }
1166 unlink_tempfile = true;
1167
1168 /* find the first deleted/changed message. we save a lot of time by only
1169 * rewriting the mailbox from the point where it has actually changed. */
1170 int i = 0;
1171 for (; (i < m->msg_count) && !m->emails[i]->deleted &&
1172 !m->emails[i]->changed && !m->emails[i]->attach_del;
1173 i++)
1174 {
1175 }
1176 if (i == m->msg_count)
1177 {
1178 /* this means m->changed or m->msg_deleted was set, but no
1179 * messages were found to be changed or deleted. This should
1180 * never happen, is we presume it is a bug in neomutt. */
1181 mutt_error(_("sync: mbox modified, but no modified messages (report this bug)"));
1182 mutt_debug(LL_DEBUG1, "no modified messages\n");
1183 goto bail;
1184 }
1185
1186 /* save the index of the first changed/deleted message */
1187 first = i;
1188 /* where to start overwriting */
1189 offset = m->emails[i]->offset;
1190
1191 /* the offset stored in the header does not include the MMDF_SEP, so make
1192 * sure we seek to the correct location */
1193 if (m->type == MUTT_MMDF)
1194 offset -= (sizeof(MMDF_SEP) - 1);
1195
1196 /* allocate space for the new offsets */
1197 new_offset = MUTT_MEM_CALLOC(m->msg_count - first, struct MUpdate);
1198 old_offset = MUTT_MEM_CALLOC(m->msg_count - first, struct MUpdate);
1199
1200 if (m->verbose)
1201 {
1203 progress_set_message(progress, _("Writing %s..."), mailbox_path(m));
1204 }
1205
1206 for (i = first, j = 0; i < m->msg_count; i++)
1207 {
1208 progress_update(progress, i, i / (m->msg_count / 100 + 1));
1209 /* back up some information which is needed to restore offsets when
1210 * something fails. */
1211
1212 old_offset[i - first].valid = true;
1213 old_offset[i - first].hdr = m->emails[i]->offset;
1214 old_offset[i - first].body = m->emails[i]->body->offset;
1215 old_offset[i - first].lines = m->emails[i]->lines;
1216 old_offset[i - first].length = m->emails[i]->body->length;
1217
1218 if (!m->emails[i]->deleted)
1219 {
1220 j++;
1221
1222 if (m->type == MUTT_MMDF)
1223 {
1224 if (fputs(MMDF_SEP, fp) == EOF)
1225 {
1226 mutt_perror("%s", buf_string(tempfile));
1227 goto bail;
1228 }
1229 }
1230
1231 /* save the new offset for this message. we add 'offset' because the
1232 * temporary file only contains saved message which are located after
1233 * 'offset' in the real mailbox */
1234 new_offset[i - first].hdr = ftello(fp) + offset;
1235
1236 struct Message *msg = mx_msg_open(m, m->emails[i]);
1237 const int rc2 = mutt_copy_message(fp, m->emails[i], msg, MUTT_CM_UPDATE,
1239 mx_msg_close(m, &msg);
1240 if (rc2 != 0)
1241 {
1242 mutt_perror("%s", buf_string(tempfile));
1243 goto bail;
1244 }
1245
1246 /* Since messages could have been deleted, the offsets stored in memory
1247 * will be wrong, so update what we can, which is the offset of this
1248 * message, and the offset of the body. If this is a multipart message,
1249 * we just flush the in memory cache so that the message will be reparsed
1250 * if the user accesses it later. */
1251 new_offset[i - first].body = ftello(fp) - m->emails[i]->body->length + offset;
1252 mutt_body_free(&m->emails[i]->body->parts);
1253
1254 if (m->type == MUTT_MMDF)
1255 {
1256 if (fputs(MMDF_SEP, fp) == EOF)
1257 {
1258 mutt_perror("%s", buf_string(tempfile));
1259 goto bail;
1260 }
1261 }
1262 else
1263 {
1264 if (fputs(MBOX_SEP, fp) == EOF)
1265 {
1266 mutt_perror("%s", buf_string(tempfile));
1267 goto bail;
1268 }
1269 }
1270 }
1271 }
1272
1273 if (mutt_file_fclose(&fp) != 0)
1274 {
1275 mutt_debug(LL_DEBUG1, "mutt_file_fclose (&) returned non-zero\n");
1276 mutt_perror("%s", buf_string(tempfile));
1277 goto bail;
1278 }
1279
1280 /* Save the state of this folder. */
1281 if (stat(mailbox_path(m), &st) == -1)
1282 {
1283 mutt_perror("%s", mailbox_path(m));
1284 goto bail;
1285 }
1286
1287 unlink_tempfile = false;
1288
1289 fp = mutt_file_fopen(buf_string(tempfile), "r");
1290 if (!fp)
1291 {
1293 mx_fastclose_mailbox(m, false);
1294 mutt_debug(LL_DEBUG1, "unable to reopen temp copy of mailbox!\n");
1295 mutt_perror("%s", buf_string(tempfile));
1296 FREE(&new_offset);
1297 FREE(&old_offset);
1298 goto fatal;
1299 }
1300
1301 if (!mutt_file_seek(adata->fp, offset, SEEK_SET) || /* seek the append location */
1302 /* do a sanity check to make sure the mailbox looks ok */
1303 !fgets(buf, sizeof(buf), adata->fp) ||
1304 ((m->type == MUTT_MBOX) && !mutt_str_startswith(buf, "From ")) ||
1305 ((m->type == MUTT_MMDF) && !mutt_str_equal(MMDF_SEP, buf)))
1306 {
1307 mutt_debug(LL_DEBUG1, "message not in expected position\n");
1308 mutt_debug(LL_DEBUG1, " LINE: %s\n", buf);
1309 i = -1;
1310 }
1311 else
1312 {
1313 if (!mutt_file_seek(adata->fp, offset, SEEK_SET)) /* return to proper offset */
1314 {
1315 i = -1;
1316 }
1317 else
1318 {
1319 /* copy the temp mailbox back into place starting at the first
1320 * change/deleted message */
1321 if (m->verbose)
1322 mutt_message(_("Committing changes..."));
1323 i = mutt_file_copy_stream(fp, adata->fp);
1324
1325 if (ferror(adata->fp))
1326 i = -1;
1327 }
1328 if (i >= 0)
1329 {
1330 m->size = ftello(adata->fp); /* update the mailbox->size of the mailbox */
1331 if ((m->size < 0) || (ftruncate(fileno(adata->fp), m->size) != 0))
1332 {
1333 i = -1;
1334 mutt_debug(LL_DEBUG1, "ftruncate() failed\n");
1335 }
1336 }
1337 }
1338
1341
1342 if ((mutt_file_fclose(&adata->fp) != 0) || (i == -1))
1343 {
1344 /* error occurred while writing the mailbox back, so keep the temp copy around */
1345
1346 struct Buffer *savefile = buf_pool_get();
1347
1348 const char *const c_tmp_dir = cs_subset_path(NeoMutt->sub, "tmp_dir");
1349 buf_printf(savefile, "%s/neomutt.%s-%s-%u", NONULL(c_tmp_dir),
1350 NONULL(NeoMutt->username), NONULL(ShortHostname), (unsigned int) getpid());
1351 rename(buf_string(tempfile), buf_string(savefile));
1353 mx_fastclose_mailbox(m, false);
1354 pretty_mailbox(savefile);
1355 mutt_error(_("Write failed! Saved partial mailbox to %s"), buf_string(savefile));
1356 buf_pool_release(&savefile);
1357 FREE(&new_offset);
1358 FREE(&old_offset);
1359 goto fatal;
1360 }
1361
1362 /* Restore the previous access/modification times */
1363 mbox_reset_atime(m, &st);
1364
1365 /* reopen the mailbox in read-only mode */
1366 adata->fp = mbox_open_readwrite(m);
1367 if (!adata->fp)
1368 {
1369 adata->fp = mbox_open_readonly(m);
1370 }
1371 if (!adata->fp)
1372 {
1373 unlink(buf_string(tempfile));
1375 mx_fastclose_mailbox(m, false);
1376 mutt_error(_("Fatal error! Could not reopen mailbox!"));
1377 FREE(&new_offset);
1378 FREE(&old_offset);
1379 goto fatal;
1380 }
1381
1382 /* update the offsets of the rewritten messages */
1383 for (i = first, j = first; i < m->msg_count; i++)
1384 {
1385 if (!m->emails[i]->deleted)
1386 {
1387 m->emails[i]->offset = new_offset[i - first].hdr;
1388 m->emails[i]->body->hdr_offset = new_offset[i - first].hdr;
1389 m->emails[i]->body->offset = new_offset[i - first].body;
1390 m->emails[i]->index = j++;
1391 }
1392 }
1393 FREE(&new_offset);
1394 FREE(&old_offset);
1395 unlink(buf_string(tempfile)); /* remove partial copy of the mailbox */
1396 buf_pool_release(&tempfile);
1398
1399 const bool c_check_mbox_size = cs_subset_bool(NeoMutt->sub, "check_mbox_size");
1400 if (c_check_mbox_size)
1401 {
1402 struct Mailbox *m_tmp = mailbox_find(mailbox_path(m));
1403 if (m_tmp && !m_tmp->has_new)
1404 mailbox_update(m_tmp);
1405 }
1406
1407 progress_free(&progress);
1408 return 0; /* signal success */
1409
1410bail: /* Come here in case of disaster */
1411
1412 mutt_file_fclose(&fp);
1413
1414 if (tempfile && unlink_tempfile)
1415 unlink(buf_string(tempfile));
1416
1417 /* restore offsets, as far as they are valid */
1418 if ((first >= 0) && old_offset)
1419 {
1420 for (i = first; (i < m->msg_count) && old_offset[i - first].valid; i++)
1421 {
1422 m->emails[i]->offset = old_offset[i - first].hdr;
1423 m->emails[i]->body->hdr_offset = old_offset[i - first].hdr;
1424 m->emails[i]->body->offset = old_offset[i - first].body;
1425 m->emails[i]->lines = old_offset[i - first].lines;
1426 m->emails[i]->body->length = old_offset[i - first].length;
1427 }
1428 }
1429
1430 /* this is ok to call even if we haven't locked anything */
1432
1434 FREE(&new_offset);
1435 FREE(&old_offset);
1436
1437 adata->fp = freopen(mailbox_path(m), "r", adata->fp);
1438 if (!adata->fp)
1439 {
1440 mutt_error(_("Could not reopen mailbox"));
1441 mx_fastclose_mailbox(m, false);
1442 goto fatal;
1443 }
1444
1446 if (need_sort)
1447 {
1448 /* if the mailbox was reopened, the thread tree will be invalid so make
1449 * sure to start threading from scratch. */
1451 }
1452
1453fatal:
1454 buf_pool_release(&tempfile);
1455 progress_free(&progress);
1456 return rc;
1457}
1458
1462static enum MxStatus mbox_mbox_close(struct Mailbox *m)
1463{
1465 if (!adata)
1466 return MX_STATUS_ERROR;
1467
1468 if (!adata->fp)
1469 return MX_STATUS_OK;
1470
1471 if (adata->append)
1472 {
1473 mutt_file_unlock(fileno(adata->fp));
1475 }
1476
1477 mutt_file_fclose(&adata->fp);
1478
1479 /* fix up the times so mailbox won't get confused */
1480 if (m->peekonly && !buf_is_empty(&m->pathbuf) &&
1481 (mutt_file_timespec_compare(&adata->mtime, &adata->atime) > 0))
1482 {
1483#ifdef HAVE_UTIMENSAT
1484 struct timespec ts[2] = { { 0 }, { 0 } };
1485 ts[0] = adata->atime;
1486 ts[1] = adata->mtime;
1487 utimensat(AT_FDCWD, mailbox_path(m), ts, 0);
1488#else
1489 struct utimbuf ut = { 0 };
1490 ut.actime = adata->atime.tv_sec;
1491 ut.modtime = adata->mtime.tv_sec;
1492 utime(mailbox_path(m), &ut);
1493#endif
1494 }
1495
1496 return MX_STATUS_OK;
1497}
1498
1502static bool mbox_msg_open(struct Mailbox *m, struct Message *msg, struct Email *e)
1503{
1505 if (!adata)
1506 return false;
1507
1508 msg->fp = mutt_file_fopen(mailbox_path(m), "r");
1509 if (!msg->fp)
1510 return false;
1511
1512 return true;
1513}
1514
1518static bool mbox_msg_open_new(struct Mailbox *m, struct Message *msg, const struct Email *e)
1519{
1521 if (!adata)
1522 return false;
1523
1524 msg->fp = adata->fp;
1525 return true;
1526}
1527
1531static int mbox_msg_commit(struct Mailbox *m, struct Message *msg)
1532{
1533 if (fputs(MBOX_SEP, msg->fp) == EOF)
1534 return -1;
1535
1536 if ((fflush(msg->fp) == EOF) || (fsync(fileno(msg->fp)) == -1))
1537 {
1538 mutt_perror(_("Can't write message"));
1539 return -1;
1540 }
1541
1542 return 0;
1543}
1544
1548static int mbox_msg_close(struct Mailbox *m, struct Message *msg)
1549{
1550 if (msg->write)
1551 msg->fp = NULL;
1552 else
1553 mutt_file_fclose(&msg->fp);
1554
1555 return 0;
1556}
1557
1563static int mbox_msg_padding_size(struct Mailbox *m)
1564{
1565 return 1;
1566}
1567
1571enum MailboxType mbox_path_probe(const char *path, const struct stat *st)
1572{
1573 if (!st)
1574 return MUTT_UNKNOWN;
1575
1576 if (S_ISDIR(st->st_mode))
1577 return MUTT_UNKNOWN;
1578
1579 if (st->st_size == 0)
1580 return MUTT_MBOX;
1581
1582 FILE *fp = mutt_file_fopen(path, "r");
1583 if (!fp)
1584 return MUTT_UNKNOWN;
1585
1586 int ch;
1587 while ((ch = fgetc(fp)) != EOF)
1588 {
1589 /* Some mailbox creation tools erroneously append a blank line to
1590 * a file before appending a mail message. This allows neomutt to
1591 * detect type for and thus open those files. */
1592 if ((ch != '\n') && (ch != '\r'))
1593 {
1594 ungetc(ch, fp);
1595 break;
1596 }
1597 }
1598
1600 char tmp[256] = { 0 };
1601 if (fgets(tmp, sizeof(tmp), fp))
1602 {
1603 if (mutt_str_startswith(tmp, "From "))
1604 type = MUTT_MBOX;
1605 else if (mutt_str_equal(tmp, MMDF_SEP))
1606 type = MUTT_MMDF;
1607 }
1609
1610 const bool c_check_mbox_size = cs_subset_bool(NeoMutt->sub, "check_mbox_size");
1611 if (!c_check_mbox_size)
1612 {
1613 /* need to restore the times here, the file was not really accessed,
1614 * only the type was accessed. This is important, because detection
1615 * of "new mail" depends on those times set correctly. */
1616#ifdef HAVE_UTIMENSAT
1617 struct timespec ts[2] = { { 0 }, { 0 } };
1620 utimensat(AT_FDCWD, path, ts, 0);
1621#else
1622 struct utimbuf times = { 0 };
1623 times.actime = st->st_atime;
1624 times.modtime = st->st_mtime;
1625 utime(path, &times);
1626#endif
1627 }
1628
1629 return type;
1630}
1631
1635static int mbox_path_canon(struct Buffer *path)
1636{
1637 mutt_path_canon(path, NeoMutt->home_dir, false);
1638 return 0;
1639}
1640
1644static int mbox_path_is_empty(struct Buffer *path)
1645{
1646 return mutt_file_check_empty(buf_string(path));
1647}
1648
1652static int mmdf_msg_commit(struct Mailbox *m, struct Message *msg)
1653{
1654 if (fputs(MMDF_SEP, msg->fp) == EOF)
1655 return -1;
1656
1657 if ((fflush(msg->fp) == EOF) || (fsync(fileno(msg->fp)) == -1))
1658 {
1659 mutt_perror(_("Can't write message"));
1660 return -1;
1661 }
1662
1663 return 0;
1664}
1665
1671static int mmdf_msg_padding_size(struct Mailbox *m)
1672{
1673 return 10;
1674}
1675
1679static enum MxStatus mbox_mbox_check_stats(struct Mailbox *m, uint8_t flags)
1680{
1681 struct stat st = { 0 };
1682 if (stat(mailbox_path(m), &st) != 0)
1683 return MX_STATUS_ERROR;
1684
1685 bool new_or_changed;
1686
1687 const bool c_check_mbox_size = cs_subset_bool(NeoMutt->sub, "check_mbox_size");
1688 if (c_check_mbox_size)
1689 {
1690 new_or_changed = (st.st_size > m->size);
1691 }
1692 else
1693 {
1694 new_or_changed =
1696 (m->newly_created &&
1699 }
1700
1701 if (new_or_changed)
1702 {
1703 const bool c_mail_check_recent = cs_subset_bool(NeoMutt->sub, "mail_check_recent");
1704 if (!c_mail_check_recent ||
1706 {
1707 m->has_new = true;
1708 }
1709 }
1710 else if (c_check_mbox_size)
1711 {
1712 /* some other program has deleted mail from the folder */
1713 m->size = (off_t) st.st_size;
1714 }
1715
1716 if (m->newly_created && ((st.st_ctime != st.st_mtime) || (st.st_ctime != st.st_atime)))
1717 m->newly_created = false;
1718
1719 if (flags & MUTT_MAILBOX_CHECK_STATS)
1720 {
1723 &adata->stats_last_checked) > 0)
1724 {
1725 bool old_peek = m->peekonly;
1727 mx_mbox_close(m);
1728 m->peekonly = old_peek;
1729 mutt_time_now(&adata->stats_last_checked);
1730 }
1731 }
1732
1733 if (m->has_new || m->msg_new)
1734 return MX_STATUS_NEW_MAIL;
1735 return MX_STATUS_OK;
1736}
1737
1741const struct MxOps MxMboxOps = {
1742 // clang-format off
1743 .type = MUTT_MBOX,
1744 .name = "mbox",
1745 .is_local = true,
1746 .ac_owns_path = mbox_ac_owns_path,
1747 .ac_add = mbox_ac_add,
1748 .mbox_open = mbox_mbox_open,
1749 .mbox_open_append = mbox_mbox_open_append,
1750 .mbox_check = mbox_mbox_check,
1751 .mbox_check_stats = mbox_mbox_check_stats,
1752 .mbox_sync = mbox_mbox_sync,
1753 .mbox_close = mbox_mbox_close,
1754 .msg_open = mbox_msg_open,
1755 .msg_open_new = mbox_msg_open_new,
1756 .msg_commit = mbox_msg_commit,
1757 .msg_close = mbox_msg_close,
1758 .msg_padding_size = mbox_msg_padding_size,
1759 .msg_save_hcache = NULL,
1760 .tags_edit = NULL,
1761 .tags_commit = NULL,
1762 .path_probe = mbox_path_probe,
1763 .path_canon = mbox_path_canon,
1764 .path_is_empty = mbox_path_is_empty,
1765 // clang-format on
1766};
1767
1771const struct MxOps MxMmdfOps = {
1772 // clang-format off
1773 .type = MUTT_MMDF,
1774 .name = "mmdf",
1775 .is_local = true,
1776 .ac_owns_path = mbox_ac_owns_path,
1777 .ac_add = mbox_ac_add,
1778 .mbox_open = mbox_mbox_open,
1779 .mbox_open_append = mbox_mbox_open_append,
1780 .mbox_check = mbox_mbox_check,
1781 .mbox_check_stats = mbox_mbox_check_stats,
1782 .mbox_sync = mbox_mbox_sync,
1783 .mbox_close = mbox_mbox_close,
1784 .msg_open = mbox_msg_open,
1785 .msg_open_new = mbox_msg_open_new,
1786 .msg_commit = mmdf_msg_commit,
1787 .msg_close = mbox_msg_close,
1788 .msg_padding_size = mmdf_msg_padding_size,
1789 .msg_save_hcache = NULL,
1790 .tags_edit = NULL,
1791 .tags_commit = NULL,
1792 .path_probe = mbox_path_probe,
1793 .path_canon = mbox_path_canon,
1794 .path_is_empty = mbox_path_is_empty,
1795 // clang-format on
1796};
void mutt_addrlist_copy(struct AddressList *dst, const struct AddressList *src, bool prune)
Copy a list of addresses into another list.
Definition address.c:776
int mutt_addrlist_parse(struct AddressList *al, const char *s)
Parse a list of email addresses.
Definition address.c:481
Email Address Handling.
#define ARRAY_FIRST(head)
Convenience method to get the first element.
Definition array.h:136
int buf_printf(struct Buffer *buf, const char *fmt,...)
Format a string overwriting a Buffer.
Definition buffer.c:168
bool buf_is_empty(const struct Buffer *buf)
Is the Buffer empty?
Definition buffer.c:298
static const char * buf_string(const struct Buffer *buf)
Convert a buffer to a const char * "string".
Definition buffer.h:96
const char * cs_subset_path(const struct ConfigSubset *sub, const char *name)
Get a path config item by name.
Definition helpers.c:168
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition helpers.c:47
short cs_subset_sort(const struct ConfigSubset *sub, const char *name)
Get a sort config item by name.
Definition helpers.c:266
Convenience wrapper for the config headers.
int mutt_copy_message(FILE *fp_out, struct Email *e, struct Message *msg, CopyMessageFlags cmflags, CopyHeaderFlags chflags, int wraplen)
Copy a message from a Mailbox.
Definition copy_email.c:920
@ MUTT_CM_UPDATE
Update structs on sync.
Definition copy_email.h:46
@ CH_UPDATE
Update the status and x-status fields?
Definition copy_email.h:66
@ CH_UPDATE_LEN
Update Lines: and Content-Length:
Definition copy_email.h:76
@ CH_FROM
Retain the "From " message separator?
Definition copy_email.h:70
Convenience wrapper for the core headers.
void mailbox_update(struct Mailbox *m)
Get the mailbox's current size.
Definition mailbox.c:214
void mailbox_changed(struct Mailbox *m, enum NotifyMailbox action)
Notify observers of a change to a Mailbox.
Definition mailbox.c:232
struct Mailbox * mailbox_find(const char *path)
Find the mailbox with a given path.
Definition mailbox.c:151
@ NT_MAILBOX_RESORT
Email list needs resorting.
Definition mailbox.h:183
@ NT_MAILBOX_INVALID
Email list was changed.
Definition mailbox.h:182
@ NT_MAILBOX_UPDATE
Update internal tables.
Definition mailbox.h:184
static const char * mailbox_path(const struct Mailbox *m)
Get the Mailbox's path string.
Definition mailbox.h:216
MailboxType
Supported mailbox formats.
Definition mailbox.h:40
@ MUTT_MMDF
'mmdf' Mailbox type
Definition mailbox.h:45
@ MUTT_MBOX
'mbox' Mailbox type
Definition mailbox.h:44
@ MUTT_UNKNOWN
Mailbox wasn't recognised.
Definition mailbox.h:43
void mutt_body_free(struct Body **ptr)
Free a Body.
Definition body.c:58
bool email_cmp_strict(const struct Email *e1, const struct Email *e2)
Strictly compare message emails.
Definition email.c:96
struct Email * email_new(void)
Create a new Email.
Definition email.c:77
void email_free(struct Email **ptr)
Free an Email.
Definition email.c:46
void mutt_make_label_hash(struct Mailbox *m)
Create a Hash Table to store the labels.
Definition header.c:406
Structs that make up an email.
struct Envelope * mutt_rfc822_read_header(FILE *fp, struct Email *e, bool user_hdrs, bool weed)
Parses an RFC822 header.
Definition parse.c:1325
void mutt_sort_unsorted(struct Mailbox *m)
Sort emails by their disk order.
Definition sort.c:452
EmailSortType
Methods for sorting Emails.
Definition sort.h:53
@ EMAIL_SORT_UNSORTED
Sort by the order the messages appear in the mailbox.
Definition sort.h:64
void mutt_file_get_stat_timespec(struct timespec *dest, struct stat *st, enum MuttStatType type)
Read the stat() time into a time value.
Definition file.c:1475
int mutt_file_copy_stream(FILE *fp_in, FILE *fp_out)
Copy the contents of one file into another.
Definition file.c:224
int mutt_file_stat_compare(struct stat *st1, enum MuttStatType st1_type, struct stat *st2, enum MuttStatType st2_type)
Compare two stat infos.
Definition file.c:1537
void mutt_file_touch_atime(int fd)
Set the access time to current time.
Definition file.c:961
int mutt_file_check_empty(const char *path)
Is the mailbox empty.
Definition file.c:1332
int mutt_file_mkdir(const char *path, mode_t mode)
Recursively create directories.
Definition file.c:844
int mutt_file_lock(int fd, bool excl, bool timeout)
(Try to) Lock a file using fcntl()
Definition file.c:1088
int mutt_file_timespec_compare(struct timespec *a, struct timespec *b)
Compare to time values.
Definition file.c:1453
int mutt_file_unlock(int fd)
Unlock a file previously locked by mutt_file_lock()
Definition file.c:1136
bool mutt_file_seek(FILE *fp, LOFF_T offset, int whence)
Wrapper for fseeko with error handling.
Definition file.c:648
int mutt_file_stat_timespec_compare(struct stat *st, enum MuttStatType type, struct timespec *b)
Compare stat info with a time value.
Definition file.c:1515
#define mutt_file_fclose(FP)
Definition file.h:144
#define mutt_file_fopen(PATH, MODE)
Definition file.h:143
@ MUTT_STAT_CTIME
File/dir's ctime - last status change time.
Definition file.h:60
@ MUTT_STAT_ATIME
File/dir's atime - last accessed time.
Definition file.h:58
@ MUTT_STAT_MTIME
File/dir's mtime - last modified time.
Definition file.h:59
void mutt_set_flag(struct Mailbox *m, struct Email *e, enum MessageType flag, bool bf, bool upd_mbox)
Set a flag on an email.
Definition flags.c:54
bool is_from(const char *s, char *path, size_t pathlen, time_t *tp)
Is a string a 'From' header line?
Definition from.c:49
char * ShortHostname
Short version of the hostname.
Definition globals.c:36
Global variables.
static void mbox_adata_free(void **ptr)
Free the private Account data - Implements Account::adata_free() -.
Definition mbox.c:76
#define mutt_error(...)
Definition logging2.h:94
#define mutt_message(...)
Definition logging2.h:93
#define mutt_debug(LEVEL,...)
Definition logging2.h:91
#define mutt_perror(...)
Definition logging2.h:95
static bool mbox_ac_add(struct Account *a, struct Mailbox *m)
Add a Mailbox to an Account - Implements MxOps::ac_add() -.
Definition mbox.c:819
static bool mbox_ac_owns_path(struct Account *a, const char *path)
Check whether an Account owns a Mailbox path - Implements MxOps::ac_owns_path() -.
Definition mbox.c:804
const struct MxOps MxMboxOps
Mbox Mailbox - Implements MxOps -.
Definition mbox.c:1741
const struct MxOps MxMmdfOps
MMDF Mailbox - Implements MxOps -.
Definition mbox.c:1771
static enum MxStatus mbox_mbox_check_stats(struct Mailbox *m, uint8_t flags)
Check the Mailbox statistics - Implements MxOps::mbox_check_stats() -.
Definition mbox.c:1679
static enum MxStatus mbox_mbox_check(struct Mailbox *m)
Check for new mail - Implements MxOps::mbox_check() -.
Definition mbox.c:958
static enum MxStatus mbox_mbox_close(struct Mailbox *m)
Close a Mailbox - Implements MxOps::mbox_close() -.
Definition mbox.c:1462
static bool mbox_mbox_open_append(struct Mailbox *m, OpenMailboxFlags flags)
Open a Mailbox for appending - Implements MxOps::mbox_open_append() -.
Definition mbox.c:906
static enum MxOpenReturns mbox_mbox_open(struct Mailbox *m)
Open a Mailbox - Implements MxOps::mbox_open() -.
Definition mbox.c:857
static enum MxStatus mbox_mbox_sync(struct Mailbox *m)
Save changes to the Mailbox - Implements MxOps::mbox_sync() -.
Definition mbox.c:1092
static int mbox_msg_close(struct Mailbox *m, struct Message *msg)
Close an email - Implements MxOps::msg_close() -.
Definition mbox.c:1548
static int mbox_msg_commit(struct Mailbox *m, struct Message *msg)
Save changes to an email - Implements MxOps::msg_commit() -.
Definition mbox.c:1531
static int mmdf_msg_commit(struct Mailbox *m, struct Message *msg)
Save changes to an email - Implements MxOps::msg_commit() -.
Definition mbox.c:1652
static bool mbox_msg_open_new(struct Mailbox *m, struct Message *msg, const struct Email *e)
Open a new message in a Mailbox - Implements MxOps::msg_open_new() -.
Definition mbox.c:1518
static bool mbox_msg_open(struct Mailbox *m, struct Message *msg, struct Email *e)
Open an email message in a Mailbox - Implements MxOps::msg_open() -.
Definition mbox.c:1502
static int mbox_msg_padding_size(struct Mailbox *m)
Bytes of padding between messages - Implements MxOps::msg_padding_size() -.
Definition mbox.c:1563
static int mmdf_msg_padding_size(struct Mailbox *m)
Bytes of padding between messages - Implements MxOps::msg_padding_size() -.
Definition mbox.c:1671
static int mbox_path_canon(struct Buffer *path)
Canonicalise a Mailbox path - Implements MxOps::path_canon() -.
Definition mbox.c:1635
static int mbox_path_is_empty(struct Buffer *path)
Is the mailbox empty - Implements MxOps::path_is_empty() -.
Definition mbox.c:1644
enum MailboxType mbox_path_probe(const char *path, const struct stat *st)
Is this an mbox Mailbox?
Definition mbox.c:1571
void mutt_hash_free(struct HashTable **ptr)
Free a hash table.
Definition hash.c:460
@ LL_DEBUG1
Log at debug level 1.
Definition logging2.h:45
Mbox local mailbox type.
#define MMDF_SEP
Definition lib.h:63
static enum MxOpenReturns mbox_parse_mailbox(struct Mailbox *m)
Read a mailbox from disk.
Definition mbox.c:356
static struct MboxAccountData * mbox_adata_new(void)
Create a new MboxAccountData struct.
Definition mbox.c:91
static bool mbox_has_new(struct Mailbox *m)
Does the mailbox have new mail.
Definition mbox.c:755
static int mbox_lock_mailbox(struct Mailbox *m, bool excl, bool retry)
Lock a mailbox.
Definition mbox.c:136
static struct MboxAccountData * mbox_adata_get(struct Mailbox *m)
Get the private data associated with a Mailbox.
Definition mbox.c:121
static int init_mailbox(struct Mailbox *m)
Add Mbox data to the Mailbox.
Definition mbox.c:102
static FILE * mbox_open_readwrite(struct Mailbox *m)
Open an mbox read-write.
Definition mbox.c:831
static FILE * mbox_open_readonly(struct Mailbox *m)
Open an mbox read-only.
Definition mbox.c:846
static void mbox_unlock_mailbox(struct Mailbox *m)
Unlock a mailbox.
Definition mbox.c:160
static enum MxOpenReturns mmdf_parse_mailbox(struct Mailbox *m)
Read a mailbox in MMDF format.
Definition mbox.c:180
void mbox_reset_atime(struct Mailbox *m, struct stat *st)
Reset the access time on the mailbox file.
Definition mbox.c:776
static int reopen_mailbox(struct Mailbox *m)
Close and reopen a mailbox.
Definition mbox.c:577
#define FREE(x)
Free memory and set the pointer to NULL.
Definition memory.h:68
#define MUTT_MEM_CALLOC(n, type)
Definition memory.h:52
int mutt_date_local_tz(time_t t)
Calculate the local timezone in seconds east of UTC.
Definition date.c:220
void mutt_time_now(struct timespec *tp)
Set the provided time field to the current time.
Definition date.c:483
Convenience wrapper for the library headers.
#define _(a)
Definition message.h:28
bool mutt_path_canon(struct Buffer *path, const char *homedir, bool is_dir)
Create the canonical version of a path.
Definition path.c:248
char * mutt_path_dirname(const char *path)
Return a path up to, but not including, the final '/'.
Definition path.c:312
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition string.c:666
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition string.c:234
Many unsorted constants and some structs.
@ MUTT_READ
Messages that have been read.
Definition mutt.h:92
@ MUTT_OLD
Old messages.
Definition mutt.h:90
@ MUTT_PURGE
Messages to be purged (bypass trash)
Definition mutt.h:96
@ MUTT_TAG
Tagged messages.
Definition mutt.h:99
@ MUTT_FLAG
Flagged messages.
Definition mutt.h:98
@ MUTT_DELETE
Messages to be deleted.
Definition mutt.h:94
@ MUTT_REPLIED
Messages that have been replied to.
Definition mutt.h:91
void pretty_mailbox(struct Buffer *buf)
Shorten a mailbox path using '~' or '='.
Definition muttlib.c:428
Some miscellaneous functions.
void mx_alloc_memory(struct Mailbox *m, int req_size)
Create storage for the emails.
Definition mx.c:1211
int mx_msg_close(struct Mailbox *m, struct Message **ptr)
Close a message.
Definition mx.c:1185
void mx_fastclose_mailbox(struct Mailbox *m, bool keep_account)
Free up memory associated with the Mailbox.
Definition mx.c:411
bool mx_mbox_open(struct Mailbox *m, OpenMailboxFlags flags)
Open a mailbox and parse it.
Definition mx.c:285
struct Message * mx_msg_open(struct Mailbox *m, struct Email *e)
Return a stream pointer for a message.
Definition mx.c:1139
enum MxStatus mx_mbox_close(struct Mailbox *m)
Save changes and close mailbox.
Definition mx.c:595
API for mailboxes.
#define MBOX_SEP
Definition mx.h:69
uint8_t OpenMailboxFlags
Definition mxapi.h:51
@ MUTT_QUIET
Do not print any messages.
Definition mxapi.h:46
@ MUTT_NOSORT
Do not sort the mailbox after opening it.
Definition mxapi.h:43
@ MUTT_PEEK
Revert atime back after taking a look (if applicable)
Definition mxapi.h:47
MxOpenReturns
Return values for mbox_open()
Definition mxapi.h:83
@ MX_OPEN_ERROR
Open failed with an error.
Definition mxapi.h:85
@ MX_OPEN_ABORT
Open was aborted.
Definition mxapi.h:86
@ MX_OPEN_OK
Open succeeded.
Definition mxapi.h:84
@ MUTT_MAILBOX_CHECK_STATS
Ignore mail_check_stats and calculate statistics (used by <check-stats>)
Definition mxapi.h:60
MxStatus
Return values from mbox_check(), mbox_check_stats(), mbox_sync(), and mbox_close()
Definition mxapi.h:70
@ MX_STATUS_LOCKED
Couldn't lock the Mailbox.
Definition mxapi.h:74
@ MX_STATUS_ERROR
An error occurred.
Definition mxapi.h:71
@ MX_STATUS_OK
No changes.
Definition mxapi.h:72
@ MX_STATUS_REOPENED
Mailbox was reopened.
Definition mxapi.h:75
@ MX_STATUS_NEW_MAIL
New mail received in Mailbox.
Definition mxapi.h:73
struct Buffer * buf_pool_get(void)
Get a Buffer from the pool.
Definition pool.c:91
void buf_pool_release(struct Buffer **ptr)
Return a Buffer to the pool.
Definition pool.c:111
Progress Bar.
@ MUTT_PROGRESS_READ
Progress tracks elements, according to $read_inc
Definition lib.h:84
@ MUTT_PROGRESS_WRITE
Progress tracks elements, according to $write_inc
Definition lib.h:85
struct Progress * progress_new(enum ProgressType type, size_t size)
Create a new Progress Bar.
Definition progress.c:139
void progress_free(struct Progress **ptr)
Free a Progress Bar.
Definition progress.c:110
void progress_set_message(struct Progress *progress, const char *fmt,...) __attribute__((__format__(__printf__
bool progress_update(struct Progress *progress, size_t pos, int percent)
Update the state of the progress bar.
Definition progress.c:80
#define TAILQ_EMPTY(head)
Definition queue.h:778
volatile sig_atomic_t SigInt
true after SIGINT is received
Definition signal.c:68
void mutt_sig_block(void)
Block signals during critical operations.
Definition signal.c:227
void mutt_sig_unblock(void)
Restore previously blocked signals.
Definition signal.c:245
#define NONULL(x)
Definition string2.h:44
A group of associated Mailboxes.
Definition account.h:36
enum MailboxType type
Type of Mailboxes this Account contains.
Definition account.h:37
struct MailboxArray mailboxes
All Mailboxes.
Definition account.h:40
void(* adata_free)(void **ptr)
Definition account.h:53
void * adata
Private data (for Mailbox backends)
Definition account.h:42
struct Body * parts
parts of a multipart or message/rfc822
Definition body.h:73
LOFF_T offset
offset where the actual data begins
Definition body.h:52
LOFF_T length
length (in bytes) of attachment
Definition body.h:53
long hdr_offset
Offset in stream where the headers begin.
Definition body.h:81
String manipulation buffer.
Definition buffer.h:36
The envelope/body of an email.
Definition email.h:39
bool read
Email is read.
Definition email.h:50
bool purge
Skip trash folder when deleting.
Definition email.h:79
struct Envelope * env
Envelope information.
Definition email.h:68
int lines
How many lines in the body of this message?
Definition email.h:62
struct Body * body
List of MIME parts.
Definition email.h:69
bool old
Email is seen, but unread.
Definition email.h:49
bool changed
Email has been edited.
Definition email.h:77
LOFF_T offset
Where in the stream does this message begin?
Definition email.h:71
bool attach_del
Has an attachment marked for deletion.
Definition email.h:99
bool flagged
Marked important?
Definition email.h:47
bool replied
Email has been replied to.
Definition email.h:51
bool deleted
Email is deleted.
Definition email.h:78
int index
The absolute (unsorted) message number.
Definition email.h:110
bool tagged
Email is tagged.
Definition email.h:107
time_t received
Time when the message was placed in the mailbox.
Definition email.h:61
struct AddressList return_path
Return path for the Email.
Definition envelope.h:58
struct AddressList from
Email's 'From' list.
Definition envelope.h:59
Store of new offsets, used by mutt_sync_mailbox()
Definition mbox.c:65
long lines
Number of lines.
Definition mbox.c:69
LOFF_T hdr
Header offset.
Definition mbox.c:67
LOFF_T length
Total length.
Definition mbox.c:70
LOFF_T body
Body offset.
Definition mbox.c:68
bool valid
Is this entry valid?
Definition mbox.c:66
A mailbox.
Definition mailbox.h:81
int vcount
The number of virtual messages.
Definition mailbox.h:101
bool changed
Mailbox has been modified.
Definition mailbox.h:112
bool has_new
Mailbox has new mail.
Definition mailbox.h:87
int * v2r
Mapping from virtual to real msgno.
Definition mailbox.h:100
int msg_new
Number of new messages.
Definition mailbox.h:94
int msg_count
Total number of messages.
Definition mailbox.h:90
int email_max
Size of emails array.
Definition mailbox.h:99
enum MailboxType type
Mailbox type.
Definition mailbox.h:104
bool newly_created
Mbox or mmdf just popped into existence.
Definition mailbox.h:105
struct HashTable * subj_hash
Hash Table: "Subject" -> Email.
Definition mailbox.h:126
struct Email ** emails
Array of Emails.
Definition mailbox.h:98
struct HashTable * id_hash
Hash Table: "Message-ID" -> Email.
Definition mailbox.h:125
struct Buffer pathbuf
Path of the Mailbox.
Definition mailbox.h:82
bool peekonly
Just taking a glance, revert atime.
Definition mailbox.h:116
int msg_deleted
Number of deleted messages.
Definition mailbox.h:95
struct Account * account
Account that owns this Mailbox.
Definition mailbox.h:129
off_t size
Size of the Mailbox.
Definition mailbox.h:86
struct HashTable * label_hash
Hash Table: "X-Label" -> Email.
Definition mailbox.h:127
int msg_flagged
Number of flagged messages.
Definition mailbox.h:92
struct timespec last_visited
Time of last exit from this mailbox.
Definition mailbox.h:106
bool readonly
Don't allow changes to the mailbox.
Definition mailbox.h:118
int msg_tagged
How many messages are tagged?
Definition mailbox.h:96
bool verbose
Display status messages?
Definition mailbox.h:119
int msg_unread
Number of unread messages.
Definition mailbox.h:91
Mbox-specific Account data -.
Definition lib.h:50
FILE * fp
Mailbox file.
Definition lib.h:51
bool locked
is the mailbox locked?
Definition lib.h:56
struct timespec atime
File's last-access time.
Definition lib.h:53
struct timespec mtime
Time Mailbox was last changed.
Definition lib.h:52
A local copy of an email.
Definition message.h:34
FILE * fp
pointer to the message data
Definition message.h:35
bool write
nonzero if message is open for writing
Definition message.h:38
Definition mxapi.h:98
Container for Accounts, Notifications.
Definition neomutt.h:41
char * username
User's login name.
Definition neomutt.h:56
char * home_dir
User's home directory.
Definition neomutt.h:55
struct ConfigSubset * sub
Inherited config items.
Definition neomutt.h:49
int cs_subset_str_native_set(const struct ConfigSubset *sub, const char *name, intptr_t value, struct Buffer *err)
Natively set the value of a string config item.
Definition subset.c:303
#define buf_mktemp(buf)
Definition tmp.h:33