X-Git-Url: http://git.madism.org/?p=apps%2Fmadmutt.git;a=blobdiff_plain;f=lib-mx%2Fmbox.c;fp=lib-mx%2Fmbox.c;h=79658fa16dc1c9c5c23e3cb9a0674c09bb8278d9;hp=0000000000000000000000000000000000000000;hb=8476307969a605bea67f6b702b0c1e7a52038bed;hpb=74697a5c38af6a85ce3b0062d781c35814a9f536 diff --git a/lib-mx/mbox.c b/lib-mx/mbox.c new file mode 100644 index 0000000..79658fa --- /dev/null +++ b/lib-mx/mbox.c @@ -0,0 +1,1086 @@ +/* + * Copyright notice from original mutt: + * Copyright (C) 1996-2002 Michael R. Elkins + * + * This file is part of mutt-ng, see http://www.muttng.org/. + * It's licensed under the GNU General Public License, + * please see the file GPL in the top level source directory. + */ + +/* This file contains code to parse ``mbox'' and ``mmdf'' style mailboxes */ + +#include + +#include +#include + +#include "mutt.h" +#include "mx.h" +#include "buffy.h" +#include "mbox.h" +#include "sort.h" +#include "thread.h" +#include "copy.h" +#include "compress.h" + +/* struct used by mutt_sync_mailbox() to store new offsets */ +struct m_update_t { + short valid; + off_t hdr; + off_t body; + long lines; + off_t length; +}; + + +static int mbox_open_new_message (MESSAGE * msg, CONTEXT * dest, HEADER * hdr __attribute__ ((unused))) +{ + msg->fp = dest->fp; + return 0; +} + +/* prototypes */ +static int mbox_reopen_mailbox (CONTEXT*, int*); + +/* parameters: + * ctx - context to lock + * excl - exclusive lock? + * retry - should retry if unable to lock? + */ +int mbox_lock_mailbox (CONTEXT * ctx, int excl, int retry) +{ + int r; + + if ((r = mx_lock_file (ctx->path, fileno (ctx->fp), excl, 1, retry)) == 0) + ctx->locked = 1; + else if (retry && !excl) { + ctx->readonly = 1; + return 0; + } + + return (r); +} + +static void mbox_unlock_mailbox (CONTEXT * ctx) +{ + if (ctx->locked) { + fflush (ctx->fp); + + mx_unlock_file (ctx->path, fileno (ctx->fp), 1); + ctx->locked = 0; + } +} + +static int mmdf_parse_mailbox (CONTEXT * ctx) +{ + char buf[HUGE_STRING]; + char return_path[LONG_STRING]; + int count = 0, oldmsgcount = ctx->msgcount; + int lines; + time_t t, tz; + off_t loc, tmploc; + HEADER *hdr; + struct stat sb; + + if (stat (ctx->path, &sb) == -1) { + mutt_perror (ctx->path); + return (-1); + } + ctx->mtime = sb.st_mtime; + ctx->size = sb.st_size; + + /* precompute the local timezone to speed up calculation of the + received time */ + tz = mutt_local_tz (0); + + buf[sizeof (buf) - 1] = 0; + + for (;;) { + if (fgets (buf, sizeof (buf) - 1, ctx->fp) == NULL) + break; + + if (m_strcmp(buf, MMDF_SEP) == 0) { + loc = ftello (ctx->fp); + + count++; + if (!ctx->quiet && ReadInc && ((count % ReadInc == 0) || count == 1)) + mutt_message (_("Reading %s... %d (%d%%)"), ctx->path, count, + (int) (loc / (ctx->size / 100 + 1))); + + + if (ctx->msgcount == ctx->hdrmax) + mx_alloc_memory (ctx); + ctx->hdrs[ctx->msgcount] = hdr = header_new(); + hdr->offset = loc; + hdr->index = ctx->msgcount; + + if (fgets (buf, sizeof (buf) - 1, ctx->fp) == NULL) { + /* TODO: memory leak??? */ + break; + } + + return_path[0] = 0; + + if (!is_from (buf, return_path, sizeof (return_path), &t)) { + if (fseeko (ctx->fp, loc, SEEK_SET) != 0) { + mutt_error _("Mailbox is corrupt!"); + + return (-1); + } + } + else + hdr->received = t - tz; + + hdr->env = mutt_read_rfc822_header (ctx->fp, hdr, 0, 0); + + loc = ftello (ctx->fp); + + if (hdr->content->length > 0 && hdr->lines > 0) { + tmploc = loc + hdr->content->length; + + if (0 < tmploc && tmploc < ctx->size) { + if (fseeko (ctx->fp, tmploc, SEEK_SET) != 0 || + fgets (buf, sizeof (buf) - 1, ctx->fp) == NULL || + m_strcmp(MMDF_SEP, buf) != 0) { + fseeko (ctx->fp, loc, SEEK_SET); + hdr->content->length = -1; + } + } + else + hdr->content->length = -1; + } + else + hdr->content->length = -1; + + if (hdr->content->length < 0) { + lines = -1; + do { + loc = ftello (ctx->fp); + if (fgets (buf, sizeof (buf) - 1, ctx->fp) == NULL) + break; + lines++; + } while (m_strcmp(buf, MMDF_SEP) != 0); + + hdr->lines = lines; + hdr->content->length = loc - hdr->content->offset; + } + + if (!hdr->env->return_path && return_path[0]) + hdr->env->return_path = + rfc822_parse_adrlist (hdr->env->return_path, return_path); + + if (!hdr->env->from) + hdr->env->from = address_list_dup (hdr->env->return_path); + + ctx->msgcount++; + } + else { + mutt_error _("Mailbox is corrupt!"); + + return (-1); + } + } + + if (ctx->msgcount > oldmsgcount) + mx_update_context (ctx, ctx->msgcount - oldmsgcount); + + return (0); +} + +/* Note that this function is also called when new mail is appended to the + * currently open folder, and NOT just when the mailbox is initially read. + * + * NOTE: it is assumed that the mailbox being read has been locked before + * this routine gets called. Strange things could happen if it's not! + */ +static int mbox_parse_mailbox (CONTEXT * ctx) +{ + struct stat sb; + char buf[HUGE_STRING], return_path[STRING]; + HEADER *curhdr; + time_t t, tz; + int count = 0, lines = 0; + off_t loc; + + /* Save information about the folder at the time we opened it. */ + if (stat (ctx->path, &sb) == -1) { + mutt_perror (ctx->path); + return (-1); + } + + ctx->size = sb.st_size; + ctx->mtime = sb.st_mtime; + + if (!ctx->readonly) + ctx->readonly = access (ctx->path, W_OK) ? 1 : 0; + + /* precompute the local timezone to speed up calculation of the + date received */ + tz = mutt_local_tz (0); + + loc = ftello (ctx->fp); + while (fgets (buf, sizeof (buf), ctx->fp) != NULL) { + if (is_from (buf, return_path, sizeof (return_path), &t)) { + /* Save the Content-Length of the previous message */ + if (count > 0) { +#define PREV ctx->hdrs[ctx->msgcount-1] + + if (PREV->content->length < 0) { + PREV->content->length = loc - PREV->content->offset - 1; + if (PREV->content->length < 0) + PREV->content->length = 0; + } + if (!PREV->lines) + PREV->lines = lines ? lines - 1 : 0; + } + + count++; + + if (!ctx->quiet && ReadInc && ((count % ReadInc == 0) || count == 1)) + mutt_message (_("Reading %s... %d (%d%%)"), ctx->path, count, + (int) (ftello (ctx->fp) / (ctx->size / 100 + 1))); + + if (ctx->msgcount == ctx->hdrmax) + mx_alloc_memory (ctx); + + curhdr = ctx->hdrs[ctx->msgcount] = header_new(); + curhdr->received = t - tz; + curhdr->offset = loc; + curhdr->index = ctx->msgcount; + + curhdr->env = mutt_read_rfc822_header (ctx->fp, curhdr, 0, 0); + + /* if we know how long this message is, either just skip over the body, + * or if we don't know how many lines there are, count them now (this will + * save time by not having to search for the next message marker). + */ + if (curhdr->content->length > 0) { + off_t tmploc; + + loc = ftello (ctx->fp); + tmploc = loc + curhdr->content->length + 1; + + if (0 < tmploc && tmploc < ctx->size) { + /* + * check to see if the content-length looks valid. we expect to + * to see a valid message separator at this point in the stream + */ + if (fseeko (ctx->fp, tmploc, SEEK_SET) != 0 || + fgets (buf, sizeof (buf), ctx->fp) == NULL || + m_strncmp("From ", buf, 5) != 0) { + fseeko (ctx->fp, loc, SEEK_SET); /* nope, return the previous position */ + curhdr->content->length = -1; + } + } + else if (tmploc != ctx->size) { + /* content-length would put us past the end of the file, so it + * must be wrong + */ + curhdr->content->length = -1; + } + + if (curhdr->content->length != -1) { + /* good content-length. check to see if we know how many lines + * are in this message. + */ + if (curhdr->lines == 0) { + int cl = curhdr->content->length; + + /* count the number of lines in this message */ + fseeko (ctx->fp, loc, SEEK_SET); + while (cl-- > 0) { + if (fgetc (ctx->fp) == '\n') + curhdr->lines++; + } + } + + /* return to the offset of the next message separator */ + fseeko(ctx->fp, tmploc, SEEK_SET); + } + } + + ctx->msgcount++; + + if (!curhdr->env->return_path && return_path[0]) + curhdr->env->return_path = + rfc822_parse_adrlist (curhdr->env->return_path, return_path); + + if (!curhdr->env->from) + curhdr->env->from = address_list_dup (curhdr->env->return_path); + + lines = 0; + } + else + lines++; + + loc = ftello (ctx->fp); + } + + /* + * Only set the content-length of the previous message if we have read more + * than one message during _this_ invocation. If this routine is called + * when new mail is received, we need to make sure not to clobber what + * previously was the last message since the headers may be sorted. + */ + if (count > 0) { + if (PREV->content->length < 0) { + PREV->content->length = ftello (ctx->fp) - PREV->content->offset - 1; + if (PREV->content->length < 0) + PREV->content->length = 0; + } + + if (!PREV->lines) + PREV->lines = lines ? lines - 1 : 0; + + mx_update_context (ctx, count); + } + + return (0); +} + +#undef PREV + +/* open a mbox or mmdf style mailbox */ +static int mbox_open_mailbox (CONTEXT * ctx) +{ + int rc; + + if ((ctx->fp = fopen (ctx->path, "r")) == NULL) { + mutt_perror (ctx->path); + return (-1); + } + mutt_block_signals (); + if (mbox_lock_mailbox (ctx, 0, 1) == -1) { + mutt_unblock_signals (); + return (-1); + } + + if (ctx->magic == M_MBOX) + rc = mbox_parse_mailbox (ctx); + else if (ctx->magic == M_MMDF) + rc = mmdf_parse_mailbox (ctx); + else + rc = -1; + + mbox_unlock_mailbox (ctx); + mutt_unblock_signals (); + return (rc); +} + +/* check to see if the mailbox has changed on disk. + * + * return values: + * M_REOPENED mailbox has been reopened + * M_NEW_MAIL new mail has arrived! + * M_LOCKED couldn't lock the file + * 0 no change + * -1 error + */ +static int _mbox_check_mailbox (CONTEXT * ctx, int *index_hint) +{ + struct stat st; + char buffer[LONG_STRING]; + int unlock = 0; + int modified = 0; + + if (stat (ctx->path, &st) == 0) { + if (st.st_mtime == ctx->mtime && st.st_size == ctx->size) + return (0); + + if (st.st_size == ctx->size) { + /* the file was touched, but it is still the same length, so just exit */ + ctx->mtime = st.st_mtime; + return (0); + } + + if (st.st_size > ctx->size) { + /* lock the file if it isn't already */ + if (!ctx->locked) { + mutt_block_signals (); + if (mbox_lock_mailbox (ctx, 0, 0) == -1) { + mutt_unblock_signals (); + /* we couldn't lock the mailbox, but nothing serious happened: + * probably the new mail arrived: no reason to wait till we can + * parse it: we'll get it on the next pass + */ + return (M_LOCKED); + } + unlock = 1; + } + + /* + * Check to make sure that the only change to the mailbox is that + * message(s) were appended to this file. My heuristic is that we should + * see the message separator at *exactly* what used to be the end of the + * folder. + */ + fseeko (ctx->fp, ctx->size, SEEK_SET); + if (fgets (buffer, sizeof (buffer), ctx->fp) != NULL) { + if ((ctx->magic == M_MBOX && m_strncmp("From ", buffer, 5) == 0) + || (ctx->magic == M_MMDF && m_strcmp(MMDF_SEP, buffer) == 0)) { + fseeko (ctx->fp, ctx->size, SEEK_SET); + if (ctx->magic == M_MBOX) + mbox_parse_mailbox (ctx); + else + mmdf_parse_mailbox (ctx); + + /* Only unlock the folder if it was locked inside of this routine. + * It may have been locked elsewhere, like in + * mutt_checkpoint_mailbox(). + */ + + if (unlock) { + mbox_unlock_mailbox (ctx); + mutt_unblock_signals (); + } + + return (M_NEW_MAIL); /* signal that new mail arrived */ + } + else + modified = 1; + } else { + modified = 1; + } + } else { + modified = 1; + } + } + + if (modified) { + if (mbox_reopen_mailbox (ctx, index_hint) != -1) { + if (unlock) { + mbox_unlock_mailbox (ctx); + mutt_unblock_signals (); + } + return (M_REOPENED); + } + } + + /* fatal error */ + + mbox_unlock_mailbox (ctx); + mx_fastclose_mailbox (ctx); + mutt_unblock_signals (); + mutt_error _("Mailbox was corrupted!"); + + return (-1); +} + +static int mbox_check_mailbox (CONTEXT* ctx, int* index_hint, int lock) { + int rc = 0; + + if (lock) { + mutt_block_signals (); + if (mbox_lock_mailbox (ctx, 0, 0) == -1) { + mutt_unblock_signals (); + return M_LOCKED; + } + } + + rc = _mbox_check_mailbox (ctx, index_hint); + + if (lock) { + mutt_unblock_signals (); + mbox_unlock_mailbox (ctx); + } + return rc; +} + +/* return values: + * 0 success + * -1 failure + */ +static int mbox_sync_mailbox (CONTEXT * ctx, int unused __attribute__ ((unused)), int *index_hint) +{ + char tempfile[_POSIX_PATH_MAX]; + char buf[32]; + int i, j, save_sort = SORT_ORDER; + int rc = -1; + int need_sort = 0; /* flag to resort mailbox if new mail arrives */ + int first = -1; /* first message to be written */ + off_t offset; /* location in mailbox to write changed messages */ + struct stat statbuf; + struct utimbuf utimebuf; + struct m_update_t *newOffset = NULL; + struct m_update_t *oldOffset = NULL; + FILE *fp = NULL; + + /* sort message by their position in the mailbox on disk */ + if (Sort != SORT_ORDER) { + save_sort = Sort; + Sort = SORT_ORDER; + mutt_sort_headers (ctx, 0); + Sort = save_sort; + need_sort = 1; + } + + /* need to open the file for writing in such a way that it does not truncate + * the file, so use read-write mode. + */ + if ((ctx->fp = freopen (ctx->path, "r+", ctx->fp)) == NULL) { + mx_fastclose_mailbox (ctx); + mutt_error _("Fatal error! Could not reopen mailbox!"); + + return (-1); + } + + mutt_block_signals (); + + if (mbox_lock_mailbox (ctx, 1, 1) == -1) { + mutt_unblock_signals (); + mutt_error _("Unable to lock mailbox!"); + + goto bail; + } + + /* Check to make sure that the file hasn't changed on disk */ + if ((i = _mbox_check_mailbox (ctx, index_hint)) == M_NEW_MAIL + || i == M_REOPENED) { + /* new mail arrived, or mailbox reopened */ + need_sort = i; + rc = i; + goto bail; + } + else if (i < 0) + /* fatal error */ + return (-1); + + /* Create a temporary file to write the new version of the mailbox in. */ + mutt_mktemp (tempfile); + if ((i = open (tempfile, O_WRONLY | O_EXCL | O_CREAT, 0600)) == -1 || + (fp = fdopen (i, "w")) == NULL) { + if (-1 != i) { + close (i); + unlink (tempfile); + } + mutt_error _("Could not create temporary file!"); + + mutt_sleep (5); + goto bail; + } + + /* find the first deleted/changed message. we save a lot of time by only + * rewriting the mailbox from the point where it has actually changed. + */ + for (i = 0; i < ctx->msgcount && !ctx->hdrs[i]->deleted && + !ctx->hdrs[i]->changed && !ctx->hdrs[i]->attach_del; i++); + if (i == ctx->msgcount) { + /* this means ctx->changed or ctx->deleted was set, but no + * messages were found to be changed or deleted. This should + * never happen, is we presume it is a bug in mutt. + */ + mutt_error + _("sync: mbox modified, but no modified messages! (report this bug)"); + mutt_sleep (5); /* the mutt_error /will/ get cleared! */ + unlink (tempfile); + goto bail; + } + + /* save the index of the first changed/deleted message */ + first = i; + /* where to start overwriting */ + offset = ctx->hdrs[i]->offset; + + /* the offset stored in the header does not include the MMDF_SEP, so make + * sure we seek to the correct location + */ + if (ctx->magic == M_MMDF) + offset -= (sizeof MMDF_SEP - 1); + + /* allocate space for the new offsets */ + newOffset = p_new(struct m_update_t, ctx->msgcount - first); + oldOffset = p_new(struct m_update_t, ctx->msgcount - first); + + for (i = first, j = 0; i < ctx->msgcount; i++) { + /* + * back up some information which is needed to restore offsets when + * something fails. + */ + + oldOffset[i - first].valid = 1; + oldOffset[i - first].hdr = ctx->hdrs[i]->offset; + oldOffset[i - first].body = ctx->hdrs[i]->content->offset; + oldOffset[i - first].lines = ctx->hdrs[i]->lines; + oldOffset[i - first].length = ctx->hdrs[i]->content->length; + + if (!ctx->hdrs[i]->deleted) { + j++; + if (!ctx->quiet && WriteInc && ((i % WriteInc) == 0 || j == 1)) + mutt_message (_("Writing messages... %d (%d%%)"), i, + (int) (ftello (ctx->fp) / (ctx->size / 100 + 1))); + + if (ctx->magic == M_MMDF) { + if (fputs (MMDF_SEP, fp) == EOF) { + mutt_perror (tempfile); + mutt_sleep (5); + unlink (tempfile); + goto bail; + } + + } + + /* save the new offset for this message. we add `offset' because the + * temporary file only contains saved message which are located after + * `offset' in the real mailbox + */ + newOffset[i - first].hdr = ftello (fp) + offset; + + if (mutt_copy_message + (fp, ctx, ctx->hdrs[i], M_CM_UPDATE, + CH_FROM | CH_UPDATE | CH_UPDATE_LEN) == -1) { + mutt_perror (tempfile); + mutt_sleep (5); + unlink (tempfile); + goto bail; + } + + /* Since messages could have been deleted, the offsets stored in memory + * will be wrong, so update what we can, which is the offset of this + * message, and the offset of the body. If this is a multipart message, + * we just flush the in memory cache so that the message will be reparsed + * if the user accesses it later. + */ + newOffset[i - first].body = + ftello (fp) - ctx->hdrs[i]->content->length + offset; + body_list_wipe(&ctx->hdrs[i]->content->parts); + + switch (ctx->magic) { + case M_MMDF: + if (fputs (MMDF_SEP, fp) == EOF) { + mutt_perror (tempfile); + mutt_sleep (5); + unlink (tempfile); + goto bail; + } + break; + default: + if (fputs ("\n", fp) == EOF) { + mutt_perror (tempfile); + mutt_sleep (5); + unlink (tempfile); + goto bail; + } + } + } + } + + if (fclose (fp) != 0) { + fp = NULL; + unlink (tempfile); + mutt_perror (tempfile); + mutt_sleep (5); + goto bail; + } + fp = NULL; + + /* Save the state of this folder. */ + if (stat (ctx->path, &statbuf) == -1) { + mutt_perror (ctx->path); + mutt_sleep (5); + unlink (tempfile); + goto bail; + } + + if ((fp = fopen (tempfile, "r")) == NULL) { + mutt_unblock_signals (); + mx_fastclose_mailbox (ctx); + mutt_perror (tempfile); + mutt_sleep (5); + return (-1); + } + + if (fseeko (ctx->fp, offset, SEEK_SET) != 0 || /* seek the append location */ + /* do a sanity check to make sure the mailbox looks ok */ + fgets (buf, sizeof (buf), ctx->fp) == NULL || + (ctx->magic == M_MBOX && m_strncmp("From ", buf, 5) != 0) || + (ctx->magic == M_MMDF && m_strcmp(MMDF_SEP, buf) != 0)) { + i = -1; + } + else { + if (fseeko (ctx->fp, offset, SEEK_SET) != 0) { /* return to proper offset */ + i = -1; + } else { + /* copy the temp mailbox back into place starting at the first + * change/deleted message + */ + mutt_message _("Committing changes..."); + + i = mutt_copy_stream (fp, ctx->fp); + + if (ferror (ctx->fp)) + i = -1; + } + if (i == 0) { + ctx->size = ftello (ctx->fp); /* update the size of the mailbox */ + ftruncate (fileno (ctx->fp), ctx->size); + } + } + + fclose (fp); + fp = NULL; + mbox_unlock_mailbox (ctx); + + if (fclose (ctx->fp) != 0 || i == -1) { + /* error occured while writing the mailbox back, so keep the temp copy + * around + */ + + char savefile[_POSIX_PATH_MAX]; + + snprintf (savefile, sizeof (savefile), "%s/mutt.%s-%s-%u", + NONULL (Tempdir), NONULL (Username), NONULL (Hostname), + (unsigned int) getpid ()); + rename (tempfile, savefile); + mutt_unblock_signals (); + mx_fastclose_mailbox (ctx); + mutt_pretty_mailbox (savefile); + mutt_error (_("Write failed! Saved partial mailbox to %s"), savefile); + mutt_sleep (5); + return (-1); + } + + /* Restore the previous access/modification times */ + utimebuf.actime = statbuf.st_atime; + utimebuf.modtime = statbuf.st_mtime; + utime (ctx->path, &utimebuf); + + /* reopen the mailbox in read-only mode */ + if ((ctx->fp = fopen (ctx->path, "r")) == NULL) { + unlink (tempfile); + mutt_unblock_signals (); + mx_fastclose_mailbox (ctx); + mutt_error _("Fatal error! Could not reopen mailbox!"); + return (-1); + } + + /* update the offsets of the rewritten messages */ + for (i = first, j = first; i < ctx->msgcount; i++) { + if (!ctx->hdrs[i]->deleted) { + ctx->hdrs[i]->offset = newOffset[i - first].hdr; + ctx->hdrs[i]->content->hdr_offset = newOffset[i - first].hdr; + ctx->hdrs[i]->content->offset = newOffset[i - first].body; + ctx->hdrs[i]->index = j++; + } + } + p_delete(&newOffset); + p_delete(&oldOffset); + unlink (tempfile); /* remove partial copy of the mailbox */ + mutt_unblock_signals (); + + return (0); /* signal success */ + +bail: /* Come here in case of disaster */ + + safe_fclose (&fp); + + /* restore offsets, as far as they are valid */ + if (first >= 0 && oldOffset) { + for (i = first; i < ctx->msgcount && oldOffset[i - first].valid; i++) { + ctx->hdrs[i]->offset = oldOffset[i - first].hdr; + ctx->hdrs[i]->content->hdr_offset = oldOffset[i - first].hdr; + ctx->hdrs[i]->content->offset = oldOffset[i - first].body; + ctx->hdrs[i]->lines = oldOffset[i - first].lines; + ctx->hdrs[i]->content->length = oldOffset[i - first].length; + } + } + + /* this is ok to call even if we haven't locked anything */ + mbox_unlock_mailbox (ctx); + + mutt_unblock_signals (); + p_delete(&newOffset); + p_delete(&oldOffset); + + if ((ctx->fp = freopen (ctx->path, "r", ctx->fp)) == NULL) { + mutt_error _("Could not reopen mailbox!"); + + mx_fastclose_mailbox (ctx); + return (-1); + } + + if (need_sort) + /* if the mailbox was reopened, the thread tree will be invalid so make + * sure to start threading from scratch. */ + mutt_sort_headers (ctx, (need_sort == M_REOPENED)); + + return rc; +} + +/* close a mailbox opened in write-mode */ +int mbox_close_mailbox (CONTEXT * ctx) +{ + mx_unlock_file (ctx->path, fileno (ctx->fp), 1); + + if (ctx->cinfo) + mutt_slow_close_compressed (ctx); + + mutt_unblock_signals (); + mx_fastclose_mailbox (ctx); + return 0; +} + +static int mbox_reopen_mailbox (CONTEXT * ctx, int *index_hint) +{ + int (*cmp_headers) (const HEADER *, const HEADER *) = NULL; + HEADER **old_hdrs; + int old_msgcount; + int msg_mod = 0; + int index_hint_set; + int i, j; + int rc = -1; + + /* silent operations */ + ctx->quiet = 1; + + mutt_message _("Reopening mailbox..."); + + /* our heuristics require the old mailbox to be unsorted */ + if (Sort != SORT_ORDER) { + short old_sort; + + old_sort = Sort; + Sort = SORT_ORDER; + mutt_sort_headers (ctx, 1); + Sort = old_sort; + } + + old_hdrs = NULL; + old_msgcount = 0; + + /* simulate a close */ + if (ctx->id_hash) + hash_destroy (&ctx->id_hash, NULL); + if (ctx->subj_hash) + hash_destroy (&ctx->subj_hash, NULL); + mutt_clear_threads (ctx); + p_delete(&ctx->v2r); + if (ctx->readonly) { + for (i = 0; i < ctx->msgcount; i++) + header_delete(&(ctx->hdrs[i])); /* nothing to do! */ + p_delete(&ctx->hdrs); + } + else { + /* save the old headers */ + old_msgcount = ctx->msgcount; + old_hdrs = ctx->hdrs; + ctx->hdrs = NULL; + } + + ctx->hdrmax = 0; /* force allocation of new headers */ + ctx->msgcount = 0; + ctx->vcount = 0; + ctx->tagged = 0; + ctx->deleted = 0; + ctx->new = 0; + ctx->unread = 0; + ctx->flagged = 0; + ctx->changed = 0; + ctx->id_hash = NULL; + ctx->subj_hash = NULL; + + switch (ctx->magic) { + case M_MBOX: + case M_MMDF: + if (fseeko (ctx->fp, 0, SEEK_SET) != 0) { + rc = -1; + } + else { + cmp_headers = mutt_cmp_header; + if (ctx->magic == M_MBOX) + rc = mbox_parse_mailbox (ctx); + else + rc = mmdf_parse_mailbox (ctx); + } + break; + + default: + rc = -1; + break; + } + + if (rc == -1) { + /* free the old headers */ + for (j = 0; j < old_msgcount; j++) + header_delete(&(old_hdrs[j])); + p_delete(&old_hdrs); + + ctx->quiet = 0; + return (-1); + } + + /* now try to recover the old flags */ + + index_hint_set = (index_hint == NULL); + + if (!ctx->readonly) { + for (i = 0; i < ctx->msgcount; i++) { + int found = 0; + + /* some messages have been deleted, and new messages have been + * appended at the end; the heuristic is that old messages have then + * "advanced" towards the beginning of the folder, so we begin the + * search at index "i" + */ + for (j = i; j < old_msgcount; j++) { + if (old_hdrs[j] == NULL) + continue; + if (cmp_headers (ctx->hdrs[i], old_hdrs[j])) { + found = 1; + break; + } + } + if (!found) { + for (j = 0; j < i && j < old_msgcount; j++) { + if (old_hdrs[j] == NULL) + continue; + if (cmp_headers (ctx->hdrs[i], old_hdrs[j])) { + found = 1; + break; + } + } + } + + if (found) { + /* this is best done here */ + if (!index_hint_set && *index_hint == j) + *index_hint = i; + + if (old_hdrs[j]->changed) { + /* Only update the flags if the old header was changed; + * otherwise, the header may have been modified externally, + * and we don't want to lose _those_ changes + */ + mutt_set_flag (ctx, ctx->hdrs[i], M_FLAG, old_hdrs[j]->flagged); + mutt_set_flag (ctx, ctx->hdrs[i], M_REPLIED, old_hdrs[j]->replied); + mutt_set_flag (ctx, ctx->hdrs[i], M_OLD, old_hdrs[j]->old); + mutt_set_flag (ctx, ctx->hdrs[i], M_READ, old_hdrs[j]->read); + } + mutt_set_flag (ctx, ctx->hdrs[i], M_DELETE, old_hdrs[j]->deleted); + mutt_set_flag (ctx, ctx->hdrs[i], M_TAG, old_hdrs[j]->tagged); + + /* we don't need this header any more */ + header_delete(&(old_hdrs[j])); + } + } + + /* free the remaining old headers */ + for (j = 0; j < old_msgcount; j++) { + if (old_hdrs[j]) { + header_delete(&(old_hdrs[j])); + msg_mod = 1; + } + } + p_delete(&old_hdrs); + } + + ctx->quiet = 0; + + return ((ctx->changed || msg_mod) ? M_REOPENED : M_NEW_MAIL); +} + +/* + * Returns: + * 1 if the mailbox is not empty + * 0 if the mailbox is empty + * -1 on error + */ +int mbox_check_empty (const char *path) +{ + struct stat st; + + if (stat (path, &st) == -1) + return -1; + + return ((st.st_size == 0)); +} + +int mbox_is_magic (const char* path, struct stat* st) { + int magic = -1; + FILE* f; + char tmp[_POSIX_PATH_MAX]; + + if (S_ISDIR(st->st_mode)) + return (-1); + + if (st->st_size == 0) { + /* hard to tell what zero-length files are, so assume the default magic */ + if (DefaultMagic == M_MBOX || DefaultMagic == M_MMDF) + return (DefaultMagic); + else + return (M_MBOX); + } + else if ((f = fopen (path, "r")) != NULL) { + struct utimbuf times; + + fgets (tmp, sizeof (tmp), f); + if (m_strncmp("From ", tmp, 5) == 0) + magic = M_MBOX; + else if (m_strcmp(MMDF_SEP, tmp) == 0) + magic = M_MMDF; + safe_fclose (&f); + + /* need to restore the times here, the file was not really accessed, + * only the type was accessed. This is important, because detection + * of "new mail" depends on those times set correctly. + */ + times.actime = st->st_atime; + times.modtime = st->st_mtime; + utime (path, ×); + } else { + mutt_perror (path); + return (-1); /* fopen failed */ + } + + if (magic == -1 && mutt_can_read_compressed (path)) + return (M_COMPRESSED); + return (magic); +} + +static int commit_message (MESSAGE* msg, CONTEXT* ctx __attribute__ ((unused)), int mbox) { + if ((mbox && fputc ('\n', msg->fp) == EOF) || + (!mbox && fputs (MMDF_SEP, msg->fp) == EOF)) + return (-1); + if ((fflush (msg->fp) == EOF || fsync (fileno (msg->fp)) == -1)) { + mutt_perror (_("Can't write message")); + return (-1); + } + return (0); +} + +static int mbox_commit_message (MESSAGE* msg, CONTEXT* ctx) { + return (commit_message (msg, ctx, 1)); +} + +static int mmdf_commit_message (MESSAGE* msg, CONTEXT* ctx) { + return (commit_message (msg, ctx, 0)); +} + +mx_t const mbox_mx = { + M_MBOX, + 1, + mbox_is_magic, + mbox_check_empty, + access, + mbox_open_mailbox, + mbox_open_new_message, + NULL, + mbox_check_mailbox, + NULL, + mbox_sync_mailbox, + mbox_commit_message, +}; + +mx_t const mmdf_mx = { + M_MMDF, + 1, + mbox_is_magic, + mbox_check_empty, + access, + mbox_open_mailbox, + mbox_open_new_message, + NULL, + mbox_check_mailbox, + NULL, + mbox_sync_mailbox, + mmdf_commit_message, +};