/** * mbox_mbox_open - Implements MxOps::mbox_open() */ static int mbox_mbox_open(struct Mailbox *m) { if (init_mailbox(m) != 0) return -1; struct MboxAccountData *adata = mbox_adata_get(m); if (!adata) return -1; adata->fp = fopen(m->path, "r+"); if (!adata->fp) { mutt_perror(m->path); return -1; } mutt_sig_block(); if (mbox_lock_mailbox(m, false, true) == -1) { mutt_sig_unblock(); return -1; } int rc; if (m->magic == MUTT_MBOX) rc = mbox_parse_mailbox(m); else if (m->magic == MUTT_MMDF) rc = mmdf_parse_mailbox(m); else rc = -1; mutt_file_touch_atime(fileno(adata->fp)); mbox_unlock_mailbox(m); mutt_sig_unblock(); return rc; }
/** * execute_command - Run a system command * @param m Mailbox to work with * @param command Command string to execute * @param progress Message to show the user * @retval 1 Success * @retval 0 Failure * * Run the supplied command, taking care of all the NeoMutt requirements, * such as locking files and blocking signals. */ static int execute_command(struct Mailbox *m, const char *command, const char *progress) { int rc = 1; char sys_cmd[STR_COMMAND]; if (!m || !command || !progress) return 0; if (!m->quiet) { mutt_message(progress, m->realpath); } mutt_sig_block(); endwin(); fflush(stdout); expand_command_str(m, command, sys_cmd, sizeof(sys_cmd)); if (mutt_system(sys_cmd) != 0) { rc = 0; mutt_any_key_to_continue(NULL); mutt_error(_("Error running \"%s\""), sys_cmd); } mutt_sig_unblock(); return rc; }
/** * mbox_mbox_close - Implements MxOps::mbox_close() */ static int mbox_mbox_close(struct Mailbox *m) { if (!m) return -1; struct MboxAccountData *adata = mbox_adata_get(m); if (!adata) return -1; if (!adata->fp) return 0; if (adata->append) { mutt_file_unlock(fileno(adata->fp)); mutt_sig_unblock(); } mutt_file_fclose(&adata->fp); /* fix up the times so mailbox won't get confused */ if (m->peekonly && (m->path[0] != '\0') && (mutt_file_timespec_compare(&m->mtime, &adata->atime) > 0)) { #ifdef HAVE_UTIMENSAT struct timespec ts[2]; ts[0] = adata->atime; ts[1] = m->mtime; utimensat(0, m->path, ts, 0); #else struct utimbuf ut; ut.actime = adata->atime.tv_sec; ut.modtime = m->mtime.tv_sec; utime(m->path, &ut); #endif } return 0; }
/** * mbox_mbox_check - Implements MxOps::mbox_check() * @param[in] m Mailbox * @param[out] index_hint Keep track of current index selection * @retval #MUTT_REOPENED Mailbox has been reopened * @retval #MUTT_NEW_MAIL New mail has arrived * @retval #MUTT_LOCKED Couldn't lock the file * @retval 0 No change * @retval -1 Error */ static int mbox_mbox_check(struct Mailbox *m, int *index_hint) { if (!m) return -1; struct MboxAccountData *adata = mbox_adata_get(m); if (!adata) return -1; if (!adata->fp) { if (mbox_mbox_open(m) < 0) return -1; mutt_mailbox_changed(m, MBN_INVALID); } struct stat st; bool unlock = false; bool modified = false; if (stat(m->path, &st) == 0) { if ((mutt_file_stat_timespec_compare(&st, MUTT_STAT_MTIME, &m->mtime) == 0) && (st.st_size == m->size)) { return 0; } if (st.st_size == m->size) { /* the file was touched, but it is still the same length, so just exit */ mutt_file_get_stat_timespec(&m->mtime, &st, MUTT_STAT_MTIME); return 0; } if (st.st_size > m->size) { /* lock the file if it isn't already */ if (!adata->locked) { mutt_sig_block(); if (mbox_lock_mailbox(m, false, false) == -1) { mutt_sig_unblock(); /* 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 MUTT_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. */ char buf[1024]; if (fseeko(adata->fp, m->size, SEEK_SET) != 0) mutt_debug(LL_DEBUG1, "#1 fseek() failed\n"); if (fgets(buf, sizeof(buf), adata->fp)) { if (((m->magic == MUTT_MBOX) && mutt_str_startswith(buf, "From ", CASE_MATCH)) || ((m->magic == MUTT_MMDF) && (mutt_str_strcmp(buf, MMDF_SEP) == 0))) { if (fseeko(adata->fp, m->size, SEEK_SET) != 0) mutt_debug(LL_DEBUG1, "#2 fseek() failed\n"); int old_msg_count = m->msg_count; if (m->magic == MUTT_MBOX) mbox_parse_mailbox(m); else mmdf_parse_mailbox(m); if (m->msg_count > old_msg_count) mutt_mailbox_changed(m, MBN_INVALID); /* 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(m); mutt_sig_unblock(); } return MUTT_NEW_MAIL; /* signal that new mail arrived */ } else modified = true; } else { mutt_debug(LL_DEBUG1, "fgets returned NULL\n"); modified = true; } } else modified = true; } if (modified) { if (reopen_mailbox(m, index_hint) != -1) { mutt_mailbox_changed(m, MBN_INVALID); if (unlock) { mbox_unlock_mailbox(m); mutt_sig_unblock(); } return MUTT_REOPENED; } } /* fatal error */ mbox_unlock_mailbox(m); mx_fastclose_mailbox(m); mutt_sig_unblock(); mutt_error(_("Mailbox was corrupted")); return -1; }
/** * mbox_mbox_sync - Implements MxOps::mbox_sync() */ static int mbox_mbox_sync(struct Mailbox *m, int *index_hint) { if (!m) return -1; struct MboxAccountData *adata = mbox_adata_get(m); if (!adata) return -1; char tempfile[PATH_MAX]; char buf[32]; int i, j; enum SortType 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 */ LOFF_T offset; /* location in mailbox to write changed messages */ struct stat statbuf; struct MUpdate *new_offset = NULL; struct MUpdate *old_offset = NULL; FILE *fp = NULL; struct Progress progress; char msgbuf[PATH_MAX + 64]; /* sort message by their position in the mailbox on disk */ if (C_Sort != SORT_ORDER) { save_sort = C_Sort; C_Sort = SORT_ORDER; mutt_mailbox_changed(m, MBN_RESORT); C_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. */ adata->fp = freopen(m->path, "r+", adata->fp); if (!adata->fp) { mx_fastclose_mailbox(m); mutt_error(_("Fatal error! Could not reopen mailbox!")); return -1; } mutt_sig_block(); if (mbox_lock_mailbox(m, true, true) == -1) { mutt_sig_unblock(); mutt_error(_("Unable to lock mailbox")); goto bail; } /* Check to make sure that the file hasn't changed on disk */ i = mbox_mbox_check(m, index_hint); if ((i == MUTT_NEW_MAIL) || (i == MUTT_REOPENED)) { /* new mail arrived, or mailbox reopened */ 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, sizeof(tempfile)); int fd = open(tempfile, O_WRONLY | O_EXCL | O_CREAT, 0600); if ((fd == -1) || !(fp = fdopen(fd, "w"))) { if (fd != -1) { close(fd); unlink(tempfile); } mutt_error(_("Could not create temporary file")); 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 < m->msg_count) && !m->emails[i]->deleted && !m->emails[i]->changed && !m->emails[i]->attach_del; i++) { } if (i == m->msg_count) { /* this means ctx->changed or m->msg_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 neomutt. */ mutt_error( _("sync: mbox modified, but no modified messages (report this bug)")); mutt_debug(LL_DEBUG1, "no modified messages\n"); unlink(tempfile); goto bail; } /* save the index of the first changed/deleted message */ first = i; /* where to start overwriting */ offset = m->emails[i]->offset; /* the offset stored in the header does not include the MMDF_SEP, so make * sure we seek to the correct location */ if (m->magic == MUTT_MMDF) offset -= (sizeof(MMDF_SEP) - 1); /* allocate space for the new offsets */ new_offset = mutt_mem_calloc(m->msg_count - first, sizeof(struct MUpdate)); old_offset = mutt_mem_calloc(m->msg_count - first, sizeof(struct MUpdate)); if (!m->quiet) { snprintf(msgbuf, sizeof(msgbuf), _("Writing %s..."), m->path); mutt_progress_init(&progress, msgbuf, MUTT_PROGRESS_MSG, C_WriteInc, m->msg_count); } for (i = first, j = 0; i < m->msg_count; i++) { if (!m->quiet) mutt_progress_update(&progress, i, (int) (ftello(adata->fp) / (m->size / 100 + 1))); /* back up some information which is needed to restore offsets when * something fails. */ old_offset[i - first].valid = true; old_offset[i - first].hdr = m->emails[i]->offset; old_offset[i - first].body = m->emails[i]->content->offset; old_offset[i - first].lines = m->emails[i]->lines; old_offset[i - first].length = m->emails[i]->content->length; if (!m->emails[i]->deleted) { j++; if (m->magic == MUTT_MMDF) { if (fputs(MMDF_SEP, fp) == EOF) { mutt_perror(tempfile); 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 */ new_offset[i - first].hdr = ftello(fp) + offset; if (mutt_copy_message_ctx(fp, m, m->emails[i], MUTT_CM_UPDATE, CH_FROM | CH_UPDATE | CH_UPDATE_LEN) != 0) { mutt_perror(tempfile); 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. */ new_offset[i - first].body = ftello(fp) - m->emails[i]->content->length + offset; mutt_body_free(&m->emails[i]->content->parts); switch (m->magic) { case MUTT_MMDF: if (fputs(MMDF_SEP, fp) == EOF) { mutt_perror(tempfile); unlink(tempfile); goto bail; } break; default: if (fputs("\n", fp) == EOF) { mutt_perror(tempfile); unlink(tempfile); goto bail; } } } } if (fclose(fp) != 0) { fp = NULL; mutt_debug(LL_DEBUG1, "mutt_file_fclose (&) returned non-zero\n"); unlink(tempfile); mutt_perror(tempfile); goto bail; } fp = NULL; /* Save the state of this folder. */ if (stat(m->path, &statbuf) == -1) { mutt_perror(m->path); unlink(tempfile); goto bail; } fp = fopen(tempfile, "r"); if (!fp) { mutt_sig_unblock(); mx_fastclose_mailbox(m); mutt_debug(LL_DEBUG1, "unable to reopen temp copy of mailbox!\n"); mutt_perror(tempfile); FREE(&new_offset); FREE(&old_offset); return -1; } if ((fseeko(adata->fp, offset, SEEK_SET) != 0) || /* seek the append location */ /* do a sanity check to make sure the mailbox looks ok */ !fgets(buf, sizeof(buf), adata->fp) || ((m->magic == MUTT_MBOX) && !mutt_str_startswith(buf, "From ", CASE_MATCH)) || ((m->magic == MUTT_MMDF) && (mutt_str_strcmp(MMDF_SEP, buf) != 0))) { mutt_debug(LL_DEBUG1, "message not in expected position\n"); mutt_debug(LL_DEBUG1, "\tLINE: %s\n", buf); i = -1; } else { if (fseeko(adata->fp, offset, SEEK_SET) != 0) /* return to proper offset */ { i = -1; mutt_debug(LL_DEBUG1, "fseek() failed\n"); } else { /* copy the temp mailbox back into place starting at the first * change/deleted message */ if (!m->quiet) mutt_message(_("Committing changes...")); i = mutt_file_copy_stream(fp, adata->fp); if (ferror(adata->fp)) i = -1; } if (i == 0) { m->size = ftello(adata->fp); /* update the mailbox->size of the mailbox */ if ((m->size < 0) || (ftruncate(fileno(adata->fp), m->size) != 0)) { i = -1; mutt_debug(LL_DEBUG1, "ftruncate() failed\n"); } } } mutt_file_fclose(&fp); fp = NULL; mbox_unlock_mailbox(m); if ((mutt_file_fclose(&adata->fp) != 0) || (i == -1)) { /* error occurred while writing the mailbox back, so keep the temp copy around */ char savefile[PATH_MAX]; snprintf(savefile, sizeof(savefile), "%s/neomutt.%s-%s-%u", NONULL(C_Tmpdir), NONULL(Username), NONULL(ShortHostname), (unsigned int) getpid()); rename(tempfile, savefile); mutt_sig_unblock(); mx_fastclose_mailbox(m); mutt_pretty_mailbox(savefile, sizeof(savefile)); mutt_error(_("Write failed! Saved partial mailbox to %s"), savefile); FREE(&new_offset); FREE(&old_offset); return -1; } /* Restore the previous access/modification times */ mbox_reset_atime(m, &statbuf); /* reopen the mailbox in read-only mode */ adata->fp = fopen(m->path, "r"); if (!adata->fp) { unlink(tempfile); mutt_sig_unblock(); mx_fastclose_mailbox(m); mutt_error(_("Fatal error! Could not reopen mailbox!")); FREE(&new_offset); FREE(&old_offset); return -1; } /* update the offsets of the rewritten messages */ for (i = first, j = first; i < m->msg_count; i++) { if (!m->emails[i]->deleted) { m->emails[i]->offset = new_offset[i - first].hdr; m->emails[i]->content->hdr_offset = new_offset[i - first].hdr; m->emails[i]->content->offset = new_offset[i - first].body; m->emails[i]->index = j++; } } FREE(&new_offset); FREE(&old_offset); unlink(tempfile); /* remove partial copy of the mailbox */ mutt_sig_unblock(); if (C_CheckMboxSize) { struct Mailbox *tmp = mutt_find_mailbox(m->path); if (tmp && !tmp->has_new) mutt_update_mailbox(tmp); } return 0; /* signal success */ bail: /* Come here in case of disaster */ mutt_file_fclose(&fp); /* restore offsets, as far as they are valid */ if ((first >= 0) && old_offset) { for (i = first; (i < m->msg_count) && old_offset[i - first].valid; i++) { m->emails[i]->offset = old_offset[i - first].hdr; m->emails[i]->content->hdr_offset = old_offset[i - first].hdr; m->emails[i]->content->offset = old_offset[i - first].body; m->emails[i]->lines = old_offset[i - first].lines; m->emails[i]->content->length = old_offset[i - first].length; } } /* this is ok to call even if we haven't locked anything */ mbox_unlock_mailbox(m); mutt_sig_unblock(); FREE(&new_offset); FREE(&old_offset); adata->fp = freopen(m->path, "r", adata->fp); if (!adata->fp) { mutt_error(_("Could not reopen mailbox")); mx_fastclose_mailbox(m); 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_mailbox_changed(m, MBN_RESORT); } return rc; }