From: Richard Russon Date: Tue, 11 Sep 2018 16:49:34 +0000 (+0100) Subject: reorg maildir functions X-Git-Tag: 2019-10-25~648^2~16 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=1f632cac5823cefc39e14f9d3999035167d6e7a2;p=neomutt reorg maildir functions --- diff --git a/maildir/mh.c b/maildir/mh.c index 85920e92b..781a93c74 100644 --- a/maildir/mh.c +++ b/maildir/mh.c @@ -349,88 +349,6 @@ static bool mh_valid_message(const char *s) return true; } -/** - * mh_mailbox - Check for new mail for a mh mailbox - * @param mailbox Mailbox to check - * @param check_stats Also count total, new, and flagged messages - * @retval true if the mailbox has new mail - */ -bool mh_mailbox(struct Mailbox *mailbox, bool check_stats) -{ - struct MhSequences mhs = { 0 }; - bool check_new = true; - bool rc = false; - DIR *dirp = NULL; - struct dirent *de = NULL; - - /* when $mail_check_recent is set and the .mh_sequences file hasn't changed - * since the last mailbox visit, there is no "new mail" */ - if (MailCheckRecent && mh_sequences_changed(mailbox) <= 0) - { - rc = false; - check_new = false; - } - - if (!(check_new || check_stats)) - return rc; - - if (mh_read_sequences(&mhs, mailbox->path) < 0) - return false; - - if (check_stats) - { - mailbox->msg_count = 0; - mailbox->msg_unread = 0; - mailbox->msg_flagged = 0; - } - - for (int i = mhs.max; i > 0; i--) - { - if (check_stats && (mhs_check(&mhs, i) & MH_SEQ_FLAGGED)) - mailbox->msg_flagged++; - if (mhs_check(&mhs, i) & MH_SEQ_UNSEEN) - { - if (check_stats) - mailbox->msg_unread++; - if (check_new) - { - /* if the first unseen message we encounter was in the mailbox during the - last visit, don't notify about it */ - if (!MailCheckRecent || mh_already_notified(mailbox, i) == 0) - { - mailbox->has_new = true; - rc = true; - } - /* Because we are traversing from high to low, we can stop - * checking for new mail after the first unseen message. - * Whether it resulted in "new mail" or not. */ - check_new = false; - if (!check_stats) - break; - } - } - } - mhs_free_sequences(&mhs); - - if (check_stats) - { - dirp = opendir(mailbox->path); - if (dirp) - { - while ((de = readdir(dirp))) - { - if (*de->d_name == '.') - continue; - if (mh_valid_message(de->d_name)) - mailbox->msg_count++; - } - closedir(dirp); - } - } - - return rc; -} - /** * mh_mkstemp - Create a temporary file * @param[in] mailbox Mailbox to create the file in @@ -783,65 +701,6 @@ static void maildir_free_maildir(struct Maildir **md) } } -/** - * maildir_parse_flags - Parse Maildir file flags - * @param h Header of email - * @param path Path to email file - */ -void maildir_parse_flags(struct Header *h, const char *path) -{ - char *q = NULL; - - h->flagged = false; - h->read = false; - h->replied = false; - - char *p = strrchr(path, ':'); - if (p && (mutt_str_strncmp(p + 1, "2,", 2) == 0)) - { - p += 3; - - mutt_str_replace(&h->maildir_flags, p); - q = h->maildir_flags; - - while (*p) - { - switch (*p) - { - case 'F': - h->flagged = true; - break; - - case 'R': /* replied */ - h->replied = true; - break; - - case 'S': /* seen */ - h->read = true; - break; - - case 'T': /* trashed */ - if (!h->flagged || !FlagSafe) - { - h->trash = true; - h->deleted = true; - } - break; - - default: - *q++ = *p; - break; - } - p++; - } - } - - if (q == h->maildir_flags) - FREE(&h->maildir_flags); - else if (q) - *q = '\0'; -} - /** * maildir_update_mtime - Update our record of the Maildir modification time * @param mailbox Mailbox @@ -872,72 +731,6 @@ static void maildir_update_mtime(struct Mailbox *mailbox) mutt_get_stat_timespec(&mailbox->mtime, &st, MUTT_STAT_MTIME); } -/** - * maildir_parse_stream - Parse a Maildir message - * @param magic Mailbox type, e.g. #MUTT_MAILDIR - * @param f Mesage file handle - * @param fname Message filename - * @param is_old true, if the email is old (read) - * @param h Email Header to populate (OPTIONAL) - * @retval ptr Populated email Header - * - * Actually parse a maildir message. This may also be used to fill - * out a fake header structure generated by lazy maildir parsing. - */ -struct Header *maildir_parse_stream(enum MailboxType magic, FILE *f, - const char *fname, bool is_old, struct Header *h) -{ - struct stat st; - - if (!h) - h = mutt_header_new(); - h->env = mutt_rfc822_read_header(f, h, false, false); - - fstat(fileno(f), &st); - - if (!h->received) - h->received = h->date_sent; - - /* always update the length since we have fresh information available. */ - h->content->length = st.st_size - h->content->offset; - - h->index = -1; - - if (magic == MUTT_MAILDIR) - { - /* maildir stores its flags in the filename, so ignore the - * flags in the header of the message - */ - - h->old = is_old; - maildir_parse_flags(h, fname); - } - return h; -} - -/** - * maildir_parse_message - Actually parse a maildir message - * @param magic Mailbox type, e.g. #MUTT_MAILDIR - * @param fname Message filename - * @param is_old true, if the email is old (read) - * @param h Email Header to populate (OPTIONAL) - * @retval ptr Populated email Header - * - * This may also be used to fill out a fake header structure generated by lazy - * maildir parsing. - */ -struct Header *maildir_parse_message(enum MailboxType magic, const char *fname, - bool is_old, struct Header *h) -{ - FILE *f = fopen(fname, "r"); - if (!f) - return NULL; - - h = maildir_parse_stream(magic, f, fname, is_old, h); - mutt_file_fclose(&f); - return h; -} - /** * maildir_parse_dir - Read a Maildir mailbox * @param mailbox Mailbox @@ -1418,17 +1211,6 @@ static void maildir_delayed_parsing(struct Mailbox *mailbox, struct Maildir **md mh_sort_natural(mailbox, md); } -/** - * mh_mbox_close - Implements MxOps::mbox_close() - * @retval 0 Always - */ -static int mh_mbox_close(struct Context *ctx) -{ - FREE(&ctx->mailbox->data); - - return 0; -} - /** * mh_read_dir - Read a MH/maildir style mailbox * @param ctx Mailbox @@ -1511,60 +1293,43 @@ static int maildir_read_dir(struct Context *ctx) } /** - * maildir_mbox_open - Open a Maildir mailbox - * @param ctx Mailbox - * @retval 0 Success - * @retval -1 Failure + * ch_compare - qsort callback to sort characters + * @param a First character to compare + * @param b Second character to compare + * @retval -1 a precedes b + * @retval 0 a and b are identical + * @retval 1 b precedes a */ -static int maildir_mbox_open(struct Context *ctx) +static int ch_compare(const void *a, const void *b) { - return maildir_read_dir(ctx); + return (int) (*((const char *) a) - *((const char *) b)); } /** - * maildir_mbox_open_append - Implements MxOps::mbox_open_append() + * maildir_mh_open_message - Open a Maildir or MH message + * @param mailbox Mailbox + * @param msg Message to open + * @param msgno Index number + * @param is_maildir true, if a Maildir + * @retval 0 Success + * @retval -1 Failure */ -static int maildir_mbox_open_append(struct Context *ctx, int flags) +static int maildir_mh_open_message(struct Mailbox *mailbox, struct Message *msg, + int msgno, int is_maildir) { - if (!(flags & MUTT_APPENDNEW)) - { - return 0; - } + struct Header *cur = mailbox->hdrs[msgno]; + char path[PATH_MAX]; - if (mkdir(ctx->mailbox->path, S_IRWXU)) - { - mutt_perror(ctx->mailbox->path); - return -1; - } + snprintf(path, sizeof(path), "%s/%s", mailbox->path, cur->path); - char tmp[PATH_MAX]; - snprintf(tmp, sizeof(tmp), "%s/cur", ctx->mailbox->path); - if (mkdir(tmp, S_IRWXU)) - { - mutt_perror(tmp); - rmdir(ctx->mailbox->path); - return -1; - } - - snprintf(tmp, sizeof(tmp), "%s/new", ctx->mailbox->path); - if (mkdir(tmp, S_IRWXU)) - { - mutt_perror(tmp); - snprintf(tmp, sizeof(tmp), "%s/cur", ctx->mailbox->path); - rmdir(tmp); - rmdir(ctx->mailbox->path); - return -1; - } + msg->fp = fopen(path, "r"); + if (!msg->fp && (errno == ENOENT) && is_maildir) + msg->fp = maildir_open_find_message(mailbox->path, cur->path, NULL); - snprintf(tmp, sizeof(tmp), "%s/tmp", ctx->mailbox->path); - if (mkdir(tmp, S_IRWXU)) + if (!msg->fp) { - mutt_perror(tmp); - snprintf(tmp, sizeof(tmp), "%s/cur", ctx->mailbox->path); - rmdir(tmp); - snprintf(tmp, sizeof(tmp), "%s/new", ctx->mailbox->path); - rmdir(tmp); - rmdir(ctx->mailbox->path); + mutt_perror(path); + mutt_debug(1, "fopen: %s: %s (errno %d).\n", path, strerror(errno), errno); return -1; } @@ -1572,285 +1337,146 @@ static int maildir_mbox_open_append(struct Context *ctx, int flags) } /** - * mh_mbox_open - Implements MxOps::mbox_open() + * mh_commit_msg - Commit a message to an MH folder + * @param mailbox Mailbox + * @param msg Message to commit + * @param hdr Email Header + * @param updseq If true, update the sequence number + * @retval 0 Success + * @retval -1 Failure */ -static int mh_mbox_open(struct Context *ctx) +static int mh_commit_msg(struct Mailbox *mailbox, struct Message *msg, + struct Header *hdr, bool updseq) { - return mh_read_dir(ctx, NULL); -} + DIR *dirp = NULL; + struct dirent *de = NULL; + char *cp = NULL, *dep = NULL; + unsigned int n, hi = 0; + char path[PATH_MAX]; + char tmp[16]; -/** - * mh_mbox_open_append - Implements MxOps::mbox_open_append() - */ -static int mh_mbox_open_append(struct Context *ctx, int flags) -{ - if (!(flags & MUTT_APPENDNEW)) + if (mutt_file_fsync_close(&msg->fp)) { - return 0; + mutt_perror(_("Could not flush message to disk")); + return -1; } - if (mkdir(ctx->mailbox->path, S_IRWXU)) + dirp = opendir(mailbox->path); + if (!dirp) { - mutt_perror(ctx->mailbox->path); + mutt_perror(mailbox->path); return -1; } - char tmp[PATH_MAX]; - snprintf(tmp, sizeof(tmp), "%s/.mh_sequences", ctx->mailbox->path); - const int i = creat(tmp, S_IRWXU); - if (i == -1) + /* figure out what the next message number is */ + while ((de = readdir(dirp))) { - mutt_perror(tmp); - rmdir(ctx->mailbox->path); - return -1; + dep = de->d_name; + if (*dep == ',') + dep++; + cp = dep; + while (*cp) + { + if (!isdigit((unsigned char) *cp)) + break; + cp++; + } + if (!*cp) + { + n = atoi(dep); + if (n > hi) + hi = n; + } } - close(i); - - return 0; -} - -/** - * mh_msg_open_new - Implements MxOps::msg_open_new() - * - * Open a new (temporary) message in an MH folder. - */ -static int mh_msg_open_new(struct Context *ctx, struct Message *msg, struct Header *hdr) -{ - return mh_mkstemp(ctx->mailbox, &msg->fp, &msg->path); -} - -/** - * ch_compare - qsort callback to sort characters - * @param a First character to compare - * @param b Second character to compare - * @retval -1 a precedes b - * @retval 0 a and b are identical - * @retval 1 b precedes a - */ -static int ch_compare(const void *a, const void *b) -{ - return (int) (*((const char *) a) - *((const char *) b)); -} - -/** - * maildir_flags - Generate the Maildir flags for an email - * @param dest Buffer for the result - * @param destlen Length of buffer - * @param hdr Header of the email - */ -void maildir_flags(char *dest, size_t destlen, struct Header *hdr) -{ - *dest = '\0'; + closedir(dirp); - /* The maildir specification requires that all files in the cur - * subdirectory have the :unique string appended, regardless of whether - * or not there are any flags. If .old is set, we know that this message - * will end up in the cur directory, so we include it in the following - * test even though there is no associated flag. + /* Now try to rename the file to the proper name. + * + * Note: We may have to try multiple times, until we find a free slot. */ - if (hdr && (hdr->flagged || hdr->replied || hdr->read || hdr->deleted || - hdr->old || hdr->maildir_flags)) + while (true) { - char tmp[LONG_STRING]; - snprintf(tmp, sizeof(tmp), "%s%s%s%s%s", hdr->flagged ? "F" : "", - hdr->replied ? "R" : "", hdr->read ? "S" : "", - hdr->deleted ? "T" : "", NONULL(hdr->maildir_flags)); - if (hdr->maildir_flags) - qsort(tmp, strlen(tmp), 1, ch_compare); - snprintf(dest, destlen, ":2,%s", tmp); + hi++; + snprintf(tmp, sizeof(tmp), "%u", hi); + snprintf(path, sizeof(path), "%s/%s", mailbox->path, tmp); + if (mutt_file_safe_rename(msg->path, path) == 0) + { + if (hdr) + mutt_str_replace(&hdr->path, tmp); + mutt_str_replace(&msg->committed_path, path); + FREE(&msg->path); + break; + } + else if (errno != EEXIST) + { + mutt_perror(mailbox->path); + return -1; + } + } + if (updseq) + { + mh_sequences_add_one(mailbox, hi, !msg->flags.read, msg->flags.flagged, + msg->flags.replied); } + return 0; } /** - * maildir_mh_open_message - Open a Maildir or MH message - * @param mailbox Mailbox - * @param msg Message to open - * @param msgno Index number - * @param is_maildir true, if a Maildir + * md_commit_message - Commit a message to a maildir folder + * @param mailbox Mailbox + * @param msg Message to commit + * @param hdr Header of the email * @retval 0 Success * @retval -1 Failure + * + * msg->path contains the file name of a file in tmp/. We take the + * flags from this file's name. + * + * mailbox is the mail folder we commit to. + * + * hdr is a header structure to which we write the message's new + * file name. This is used in the mh and maildir folder synch + * routines. When this routine is invoked from mx_msg_commit(), + * hdr is NULL. + * + * msg->path looks like this: + * + * tmp/{cur,new}.neomutt-HOSTNAME-PID-COUNTER:flags + * + * See also maildir_msg_open_new(). */ -static int maildir_mh_open_message(struct Mailbox *mailbox, struct Message *msg, - int msgno, int is_maildir) +static int md_commit_message(struct Mailbox *mailbox, struct Message *msg, struct Header *hdr) { - struct Header *cur = mailbox->hdrs[msgno]; + char subdir[4]; + char suffix[16]; char path[PATH_MAX]; + char full[PATH_MAX]; + char *s = NULL; - snprintf(path, sizeof(path), "%s/%s", mailbox->path, cur->path); - - msg->fp = fopen(path, "r"); - if (!msg->fp && (errno == ENOENT) && is_maildir) - msg->fp = maildir_open_find_message(mailbox->path, cur->path, NULL); - - if (!msg->fp) + if (mutt_file_fsync_close(&msg->fp)) { - mutt_perror(path); - mutt_debug(1, "fopen: %s: %s (errno %d).\n", path, strerror(errno), errno); + mutt_perror(_("Could not flush message to disk")); return -1; } - return 0; -} + /* extract the subdir */ + s = strrchr(msg->path, '/') + 1; + mutt_str_strfcpy(subdir, s, 4); -/** - * maildir_msg_open - Implements MxOps::msg_open() - */ -static int maildir_msg_open(struct Context *ctx, struct Message *msg, int msgno) -{ - return maildir_mh_open_message(ctx->mailbox, msg, msgno, 1); -} + /* extract the flags */ + s = strchr(s, ':'); + if (s) + mutt_str_strfcpy(suffix, s, sizeof(suffix)); + else + suffix[0] = '\0'; -/** - * mh_msg_open - Implements MxOps::msg_open() - */ -static int mh_msg_open(struct Context *ctx, struct Message *msg, int msgno) -{ - return maildir_mh_open_message(ctx->mailbox, msg, msgno, 0); -} - -/** - * mh_msg_close - Close a message - * @param ctx Mailbox - * @param msg Message to close - * @retval 0 Success - * @retval EOF Error, see errno - * - * @note May also return EOF Failure, see errno - */ -static int mh_msg_close(struct Context *ctx, struct Message *msg) -{ - return mutt_file_fclose(&msg->fp); -} - -/** - * maildir_msg_open_new - Implements MxOps::msg_open_new() - * - * Open a new (temporary) message in a maildir folder. - * - * @note This uses _almost_ the maildir file name format, - * but with a {cur,new} prefix. - */ -static int maildir_msg_open_new(struct Context *ctx, struct Message *msg, struct Header *hdr) -{ - int fd; - char path[PATH_MAX]; - char suffix[16]; - char subdir[16]; - mode_t omask; - - if (hdr) - { - bool deleted = hdr->deleted; - hdr->deleted = false; - - maildir_flags(suffix, sizeof(suffix), hdr); - - hdr->deleted = deleted; - } - else - *suffix = '\0'; - - if (hdr && (hdr->read || hdr->old)) - mutt_str_strfcpy(subdir, "cur", sizeof(subdir)); - else - mutt_str_strfcpy(subdir, "new", sizeof(subdir)); - - omask = umask(mh_umask(ctx->mailbox)); - while (true) - { - snprintf(path, sizeof(path), "%s/tmp/%s.%lld.R%" PRIu64 ".%s%s", - ctx->mailbox->path, subdir, (long long) time(NULL), mutt_rand64(), - NONULL(ShortHostname), suffix); - - mutt_debug(2, "Trying %s.\n", path); - - fd = open(path, O_WRONLY | O_EXCL | O_CREAT, 0666); - if (fd == -1) - { - if (errno != EEXIST) - { - umask(omask); - mutt_perror(path); - return -1; - } - } - else - { - mutt_debug(2, "Success.\n"); - msg->path = mutt_str_strdup(path); - break; - } - } - umask(omask); - - msg->fp = fdopen(fd, "w"); - if (!msg->fp) - { - FREE(&msg->path); - close(fd); - unlink(path); - return -1; - } - - return 0; -} - -/** - * md_commit_message - Commit a message to a maildir folder - * @param mailbox Mailbox - * @param msg Message to commit - * @param hdr Header of the email - * @retval 0 Success - * @retval -1 Failure - * - * msg->path contains the file name of a file in tmp/. We take the - * flags from this file's name. - * - * mailbox is the mail folder we commit to. - * - * hdr is a header structure to which we write the message's new - * file name. This is used in the mh and maildir folder synch - * routines. When this routine is invoked from mx_msg_commit(), - * hdr is NULL. - * - * msg->path looks like this: - * - * tmp/{cur,new}.neomutt-HOSTNAME-PID-COUNTER:flags - * - * See also maildir_msg_open_new(). - */ -static int md_commit_message(struct Mailbox *mailbox, struct Message *msg, struct Header *hdr) -{ - char subdir[4]; - char suffix[16]; - char path[PATH_MAX]; - char full[PATH_MAX]; - char *s = NULL; - - if (mutt_file_fsync_close(&msg->fp)) - { - mutt_perror(_("Could not flush message to disk")); - return -1; - } - - /* extract the subdir */ - s = strrchr(msg->path, '/') + 1; - mutt_str_strfcpy(subdir, s, 4); - - /* extract the flags */ - s = strchr(s, ':'); - if (s) - mutt_str_strfcpy(suffix, s, sizeof(suffix)); - else - suffix[0] = '\0'; - - /* construct a new file name. */ - while (true) - { - snprintf(path, sizeof(path), "%s/%lld.R%" PRIu64 ".%s%s", subdir, - (long long) time(NULL), mutt_rand64(), NONULL(ShortHostname), suffix); - snprintf(full, sizeof(full), "%s/%s", mailbox->path, path); + /* construct a new file name. */ + while (true) + { + snprintf(path, sizeof(path), "%s/%lld.R%" PRIu64 ".%s%s", subdir, + (long long) time(NULL), mutt_rand64(), NONULL(ShortHostname), suffix); + snprintf(full, sizeof(full), "%s/%s", mailbox->path, path); mutt_debug(2, "renaming %s to %s.\n", msg->path, full); @@ -1896,108 +1522,6 @@ static int md_commit_message(struct Mailbox *mailbox, struct Message *msg, struc } } -/** - * maildir_msg_commit - Implements MxOps::msg_commit() - */ -static int maildir_msg_commit(struct Context *ctx, struct Message *msg) -{ - return md_commit_message(ctx->mailbox, msg, NULL); -} - -/** - * mh_commit_msg - Commit a message to an MH folder - * @param mailbox Mailbox - * @param msg Message to commit - * @param hdr Email Header - * @param updseq If true, update the sequence number - * @retval 0 Success - * @retval -1 Failure - */ -static int mh_commit_msg(struct Mailbox *mailbox, struct Message *msg, - struct Header *hdr, bool updseq) -{ - DIR *dirp = NULL; - struct dirent *de = NULL; - char *cp = NULL, *dep = NULL; - unsigned int n, hi = 0; - char path[PATH_MAX]; - char tmp[16]; - - if (mutt_file_fsync_close(&msg->fp)) - { - mutt_perror(_("Could not flush message to disk")); - return -1; - } - - dirp = opendir(mailbox->path); - if (!dirp) - { - mutt_perror(mailbox->path); - return -1; - } - - /* figure out what the next message number is */ - while ((de = readdir(dirp))) - { - dep = de->d_name; - if (*dep == ',') - dep++; - cp = dep; - while (*cp) - { - if (!isdigit((unsigned char) *cp)) - break; - cp++; - } - if (!*cp) - { - n = atoi(dep); - if (n > hi) - hi = n; - } - } - closedir(dirp); - - /* Now try to rename the file to the proper name. - * - * Note: We may have to try multiple times, until we find a free slot. - */ - - while (true) - { - hi++; - snprintf(tmp, sizeof(tmp), "%u", hi); - snprintf(path, sizeof(path), "%s/%s", mailbox->path, tmp); - if (mutt_file_safe_rename(msg->path, path) == 0) - { - if (hdr) - mutt_str_replace(&hdr->path, tmp); - mutt_str_replace(&msg->committed_path, path); - FREE(&msg->path); - break; - } - else if (errno != EEXIST) - { - mutt_perror(mailbox->path); - return -1; - } - } - if (updseq) - { - mh_sequences_add_one(mailbox, hi, !msg->flags.read, msg->flags.flagged, - msg->flags.replied); - } - return 0; -} - -/** - * mh_msg_commit - Implements MxOps::msg_commit() - */ -static int mh_msg_commit(struct Context *ctx, struct Message *msg) -{ - return mh_commit_msg(ctx->mailbox, msg, NULL, true); -} - /** * mh_rewrite_message - Sync a message in an MH folder * @param ctx Mailbox @@ -2148,23 +1672,365 @@ static int maildir_sync_message(struct Context *ctx, int msgno) snprintf(fullpath, sizeof(fullpath), "%s/%s", ctx->mailbox->path, partpath); snprintf(oldpath, sizeof(oldpath), "%s/%s", ctx->mailbox->path, h->path); - if (mutt_str_strcmp(fullpath, oldpath) == 0) - { - /* message hasn't really changed */ - return 0; - } + if (mutt_str_strcmp(fullpath, oldpath) == 0) + { + /* message hasn't really changed */ + return 0; + } + + /* record that the message is possibly marked as trashed on disk */ + h->trash = h->deleted; + + if (rename(oldpath, fullpath) != 0) + { + mutt_perror("rename"); + return -1; + } + mutt_str_replace(&h->path, partpath); + } + return 0; +} + +/** + * maildir_canon_filename - Generate the canonical filename for a Maildir folder + * @param src Buffer containing source filename + * @param buf Buffer for the result + * @param buflen Length of buf buffer + * @retval ptr Buf buffer + */ +static char *maildir_canon_filename(const char *src, char *buf, size_t buflen) +{ + char *t = strrchr(src, '/'); + if (t) + src = t + 1; + + mutt_str_strfcpy(buf, src, buflen); + char *u = strrchr(buf, ':'); + if (u) + *u = '\0'; + + return buf; +} + +/** + * maildir_update_tables - Update the Header tables + * @param ctx Mailbox + * @param index_hint Current email in index + */ +static void maildir_update_tables(struct Context *ctx, int *index_hint) +{ + if (Sort != SORT_ORDER) + { + const short old_sort = Sort; + Sort = SORT_ORDER; + mutt_sort_headers(ctx, true); + Sort = old_sort; + } + + const int old_count = ctx->mailbox->msg_count; + for (int i = 0, j = 0; i < old_count; i++) + { + if (ctx->mailbox->hdrs[i]->active && index_hint && *index_hint == i) + *index_hint = j; + + if (ctx->mailbox->hdrs[i]->active) + ctx->mailbox->hdrs[i]->index = j++; + } + + mx_update_tables(ctx, false); + mutt_clear_threads(ctx); +} + +/** + * md_open_find_message - Find a message in a maildir folder + * @param folder Base folder + * @param unique Unique part of filename + * @param subfolder Subfolder to search, e.g. 'cur' + * @param newname File's new name + * @retval ptr File handle + * + * These functions try to find a message in a maildir folder when it + * has moved under our feet. Note that this code is rather expensive, but + * then again, it's called rarely. + */ +static FILE *md_open_find_message(const char *folder, const char *unique, + const char *subfolder, char **newname) +{ + char dir[PATH_MAX]; + char tunique[PATH_MAX]; + char fname[PATH_MAX]; + + struct dirent *de = NULL; + + FILE *fp = NULL; + int oe = ENOENT; + + snprintf(dir, sizeof(dir), "%s/%s", folder, subfolder); + + DIR *dp = opendir(dir); + if (!dp) + { + errno = ENOENT; + return NULL; + } + + while ((de = readdir(dp))) + { + maildir_canon_filename(de->d_name, tunique, sizeof(tunique)); + + if (mutt_str_strcmp(tunique, unique) == 0) + { + snprintf(fname, sizeof(fname), "%s/%s/%s", folder, subfolder, de->d_name); + fp = fopen(fname, "r"); + oe = errno; + break; + } + } + + closedir(dp); + + if (newname && fp) + *newname = mutt_str_strdup(fname); + + errno = oe; + return fp; +} + +/** + * mh_mailbox - Check for new mail for a mh mailbox + * @param mailbox Mailbox to check + * @param check_stats Also count total, new, and flagged messages + * @retval true if the mailbox has new mail + */ +bool mh_mailbox(struct Mailbox *mailbox, bool check_stats) +{ + struct MhSequences mhs = { 0 }; + bool check_new = true; + bool rc = false; + DIR *dirp = NULL; + struct dirent *de = NULL; + + /* when $mail_check_recent is set and the .mh_sequences file hasn't changed + * since the last mailbox visit, there is no "new mail" */ + if (MailCheckRecent && mh_sequences_changed(mailbox) <= 0) + { + rc = false; + check_new = false; + } + + if (!(check_new || check_stats)) + return rc; + + if (mh_read_sequences(&mhs, mailbox->path) < 0) + return false; + + if (check_stats) + { + mailbox->msg_count = 0; + mailbox->msg_unread = 0; + mailbox->msg_flagged = 0; + } + + for (int i = mhs.max; i > 0; i--) + { + if (check_stats && (mhs_check(&mhs, i) & MH_SEQ_FLAGGED)) + mailbox->msg_flagged++; + if (mhs_check(&mhs, i) & MH_SEQ_UNSEEN) + { + if (check_stats) + mailbox->msg_unread++; + if (check_new) + { + /* if the first unseen message we encounter was in the mailbox during the + last visit, don't notify about it */ + if (!MailCheckRecent || mh_already_notified(mailbox, i) == 0) + { + mailbox->has_new = true; + rc = true; + } + /* Because we are traversing from high to low, we can stop + * checking for new mail after the first unseen message. + * Whether it resulted in "new mail" or not. */ + check_new = false; + if (!check_stats) + break; + } + } + } + mhs_free_sequences(&mhs); + + if (check_stats) + { + dirp = opendir(mailbox->path); + if (dirp) + { + while ((de = readdir(dirp))) + { + if (*de->d_name == '.') + continue; + if (mh_valid_message(de->d_name)) + mailbox->msg_count++; + } + closedir(dirp); + } + } + + return rc; +} + +/** + * maildir_parse_flags - Parse Maildir file flags + * @param h Header of email + * @param path Path to email file + */ +void maildir_parse_flags(struct Header *h, const char *path) +{ + char *q = NULL; + + h->flagged = false; + h->read = false; + h->replied = false; + + char *p = strrchr(path, ':'); + if (p && (mutt_str_strncmp(p + 1, "2,", 2) == 0)) + { + p += 3; + + mutt_str_replace(&h->maildir_flags, p); + q = h->maildir_flags; + + while (*p) + { + switch (*p) + { + case 'F': + h->flagged = true; + break; + + case 'R': /* replied */ + h->replied = true; + break; + + case 'S': /* seen */ + h->read = true; + break; + + case 'T': /* trashed */ + if (!h->flagged || !FlagSafe) + { + h->trash = true; + h->deleted = true; + } + break; + + default: + *q++ = *p; + break; + } + p++; + } + } + + if (q == h->maildir_flags) + FREE(&h->maildir_flags); + else if (q) + *q = '\0'; +} + +/** + * maildir_parse_stream - Parse a Maildir message + * @param magic Mailbox type, e.g. #MUTT_MAILDIR + * @param f Mesage file handle + * @param fname Message filename + * @param is_old true, if the email is old (read) + * @param h Email Header to populate (OPTIONAL) + * @retval ptr Populated email Header + * + * Actually parse a maildir message. This may also be used to fill + * out a fake header structure generated by lazy maildir parsing. + */ +struct Header *maildir_parse_stream(enum MailboxType magic, FILE *f, + const char *fname, bool is_old, struct Header *h) +{ + struct stat st; + + if (!h) + h = mutt_header_new(); + h->env = mutt_rfc822_read_header(f, h, false, false); + + fstat(fileno(f), &st); + + if (!h->received) + h->received = h->date_sent; + + /* always update the length since we have fresh information available. */ + h->content->length = st.st_size - h->content->offset; + + h->index = -1; + + if (magic == MUTT_MAILDIR) + { + /* maildir stores its flags in the filename, so ignore the + * flags in the header of the message + */ + + h->old = is_old; + maildir_parse_flags(h, fname); + } + return h; +} + +/** + * maildir_parse_message - Actually parse a maildir message + * @param magic Mailbox type, e.g. #MUTT_MAILDIR + * @param fname Message filename + * @param is_old true, if the email is old (read) + * @param h Email Header to populate (OPTIONAL) + * @retval ptr Populated email Header + * + * This may also be used to fill out a fake header structure generated by lazy + * maildir parsing. + */ +struct Header *maildir_parse_message(enum MailboxType magic, const char *fname, + bool is_old, struct Header *h) +{ + FILE *f = fopen(fname, "r"); + if (!f) + return NULL; + + h = maildir_parse_stream(magic, f, fname, is_old, h); + mutt_file_fclose(&f); + return h; +} - /* record that the message is possibly marked as trashed on disk */ - h->trash = h->deleted; +/** + * maildir_flags - Generate the Maildir flags for an email + * @param dest Buffer for the result + * @param destlen Length of buffer + * @param hdr Header of the email + */ +void maildir_flags(char *dest, size_t destlen, struct Header *hdr) +{ + *dest = '\0'; - if (rename(oldpath, fullpath) != 0) - { - mutt_perror("rename"); - return -1; - } - mutt_str_replace(&h->path, partpath); + /* The maildir specification requires that all files in the cur + * subdirectory have the :unique string appended, regardless of whether + * or not there are any flags. If .old is set, we know that this message + * will end up in the cur directory, so we include it in the following + * test even though there is no associated flag. + */ + + if (hdr && (hdr->flagged || hdr->replied || hdr->read || hdr->deleted || + hdr->old || hdr->maildir_flags)) + { + char tmp[LONG_STRING]; + snprintf(tmp, sizeof(tmp), "%s%s%s%s%s", hdr->flagged ? "F" : "", + hdr->replied ? "R" : "", hdr->read ? "S" : "", + hdr->deleted ? "T" : "", NONULL(hdr->maildir_flags)); + if (hdr->maildir_flags) + qsort(tmp, strlen(tmp), 1, ch_compare); + snprintf(dest, destlen, ":2,%s", tmp); } - return 0; } /** @@ -2260,53 +2126,222 @@ int mh_sync_mailbox_message(struct Context *ctx, int msgno) } /** - * maildir_canon_filename - Generate the canonical filename for a Maildir folder - * @param src Buffer containing source filename - * @param buf Buffer for the result - * @param buflen Length of buf buffer - * @retval ptr Buf buffer + * maildir_update_flags - Update the mailbox flags + * @param ctx Mailbox + * @param o Old email Header + * @param n New email Header + * @retval true If the flags changed + * @retval false Otherwise */ -static char *maildir_canon_filename(const char *src, char *buf, size_t buflen) +bool maildir_update_flags(struct Context *ctx, struct Header *o, struct Header *n) { - char *t = strrchr(src, '/'); - if (t) - src = t + 1; + /* save the global state here so we can reset it at the + * end of list block if required. + */ + bool context_changed = ctx->mailbox->changed; + bool header_changed; - mutt_str_strfcpy(buf, src, buflen); - char *u = strrchr(buf, ':'); - if (u) - *u = '\0'; + /* user didn't modify this message. alter the flags to match the + * current state on disk. This may not actually do + * anything. mutt_set_flag() will just ignore the call if the status + * bits are already properly set, but it is still faster not to pass + * through it */ + if (o->flagged != n->flagged) + mutt_set_flag(ctx, o, MUTT_FLAG, n->flagged); + if (o->replied != n->replied) + mutt_set_flag(ctx, o, MUTT_REPLIED, n->replied); + if (o->read != n->read) + mutt_set_flag(ctx, o, MUTT_READ, n->read); + if (o->old != n->old) + mutt_set_flag(ctx, o, MUTT_OLD, n->old); - return buf; + /* mutt_set_flag() will set this, but we don't need to + * sync the changes we made because we just updated the + * context to match the current on-disk state of the + * message. + */ + header_changed = o->changed; + o->changed = false; + + /* if the mailbox was not modified before we made these + * changes, unset the changed flag since nothing needs to + * be synchronized. + */ + if (!context_changed) + ctx->mailbox->changed = false; + + return header_changed; } /** - * maildir_update_tables - Update the Header tables - * @param ctx Mailbox - * @param index_hint Current email in index + * maildir_open_find_message - Find a new + * @param folder Maildir path + * @param msg Email path + * @param newname New name, if it has moved + * @retval ptr File handle */ -static void maildir_update_tables(struct Context *ctx, int *index_hint) +FILE *maildir_open_find_message(const char *folder, const char *msg, char **newname) { - if (Sort != SORT_ORDER) + char unique[PATH_MAX]; + + static unsigned int new_hits = 0, cur_hits = 0; /* simple dynamic optimization */ + + maildir_canon_filename(msg, unique, sizeof(unique)); + + FILE *fp = md_open_find_message(folder, unique, new_hits > cur_hits ? "new" : "cur", newname); + if (fp || (errno != ENOENT)) { - const short old_sort = Sort; - Sort = SORT_ORDER; - mutt_sort_headers(ctx, true); - Sort = old_sort; + if (new_hits < UINT_MAX && cur_hits < UINT_MAX) + { + new_hits += (new_hits > cur_hits ? 1 : 0); + cur_hits += (new_hits > cur_hits ? 0 : 1); + } + + return fp; + } + fp = md_open_find_message(folder, unique, new_hits > cur_hits ? "cur" : "new", newname); + if (fp || (errno != ENOENT)) + { + if (new_hits < UINT_MAX && cur_hits < UINT_MAX) + { + new_hits += (new_hits > cur_hits ? 0 : 1); + cur_hits += (new_hits > cur_hits ? 1 : 0); + } + + return fp; + } + + return NULL; +} + +/** + * maildir_check_empty - Is the mailbox empty + * @param path Mailbox to check + * @retval 1 Mailbox is empty + * @retval 0 Mailbox contains mail + * @retval -1 Error + */ +int maildir_check_empty(const char *path) +{ + DIR *dp = NULL; + struct dirent *de = NULL; + int r = 1; /* assume empty until we find a message */ + char realpath[PATH_MAX]; + int iter = 0; + + /* Strategy here is to look for any file not beginning with a period */ + + do + { + /* we do "cur" on the first iteration since it's more likely that we'll + * find old messages without having to scan both subdirs + */ + snprintf(realpath, sizeof(realpath), "%s/%s", path, iter == 0 ? "cur" : "new"); + dp = opendir(realpath); + if (!dp) + return -1; + while ((de = readdir(dp))) + { + if (*de->d_name != '.') + { + r = 0; + break; + } + } + closedir(dp); + iter++; + } while (r && iter < 2); + + return r; +} + +/** + * mh_check_empty - Is mailbox empty + * @param path Mailbox to check + * @retval 1 Mailbox is empty + * @retval 0 Mailbox contains mail + * @retval -1 Error + */ +int mh_check_empty(const char *path) +{ + struct dirent *de = NULL; + int r = 1; /* assume empty until we find a message */ + + DIR *dp = opendir(path); + if (!dp) + return -1; + while ((de = readdir(dp))) + { + if (mh_valid_message(de->d_name)) + { + r = 0; + break; + } + } + closedir(dp); + + return r; +} + +/** + * maildir_mbox_open - Open a Maildir mailbox + * @param ctx Mailbox + * @retval 0 Success + * @retval -1 Failure + */ +static int maildir_mbox_open(struct Context *ctx) +{ + return maildir_read_dir(ctx); +} + +/** + * maildir_mbox_open_append - Implements MxOps::mbox_open_append() + */ +static int maildir_mbox_open_append(struct Context *ctx, int flags) +{ + if (!(flags & MUTT_APPENDNEW)) + { + return 0; + } + + if (mkdir(ctx->mailbox->path, S_IRWXU)) + { + mutt_perror(ctx->mailbox->path); + return -1; + } + + char tmp[PATH_MAX]; + snprintf(tmp, sizeof(tmp), "%s/cur", ctx->mailbox->path); + if (mkdir(tmp, S_IRWXU)) + { + mutt_perror(tmp); + rmdir(ctx->mailbox->path); + return -1; + } + + snprintf(tmp, sizeof(tmp), "%s/new", ctx->mailbox->path); + if (mkdir(tmp, S_IRWXU)) + { + mutt_perror(tmp); + snprintf(tmp, sizeof(tmp), "%s/cur", ctx->mailbox->path); + rmdir(tmp); + rmdir(ctx->mailbox->path); + return -1; } - const int old_count = ctx->mailbox->msg_count; - for (int i = 0, j = 0; i < old_count; i++) + snprintf(tmp, sizeof(tmp), "%s/tmp", ctx->mailbox->path); + if (mkdir(tmp, S_IRWXU)) { - if (ctx->mailbox->hdrs[i]->active && index_hint && *index_hint == i) - *index_hint = j; - - if (ctx->mailbox->hdrs[i]->active) - ctx->mailbox->hdrs[i]->index = j++; + mutt_perror(tmp); + snprintf(tmp, sizeof(tmp), "%s/cur", ctx->mailbox->path); + rmdir(tmp); + snprintf(tmp, sizeof(tmp), "%s/new", ctx->mailbox->path); + rmdir(tmp); + rmdir(ctx->mailbox->path); + return -1; } - mx_update_tables(ctx, false); - mutt_clear_threads(ctx); + return 0; } /** @@ -2389,92 +2424,298 @@ static int maildir_mbox_check(struct Context *ctx, int *index_hint) */ fnames = mutt_hash_create(count, 0); - for (p = md; p; p = p->next) - { - maildir_canon_filename(p->h->path, buf, sizeof(buf)); - p->canon_fname = mutt_str_strdup(buf); - mutt_hash_insert(fnames, p->canon_fname, p); - } + for (p = md; p; p = p->next) + { + maildir_canon_filename(p->h->path, buf, sizeof(buf)); + p->canon_fname = mutt_str_strdup(buf); + mutt_hash_insert(fnames, p->canon_fname, p); + } + + /* check for modifications and adjust flags */ + for (int i = 0; i < ctx->mailbox->msg_count; i++) + { + ctx->mailbox->hdrs[i]->active = false; + maildir_canon_filename(ctx->mailbox->hdrs[i]->path, buf, sizeof(buf)); + p = mutt_hash_find(fnames, buf); + if (p && p->h) + { + /* message already exists, merge flags */ + ctx->mailbox->hdrs[i]->active = true; + + /* check to see if the message has moved to a different + * subdirectory. If so, update the associated filename. + */ + if (mutt_str_strcmp(ctx->mailbox->hdrs[i]->path, p->h->path) != 0) + mutt_str_replace(&ctx->mailbox->hdrs[i]->path, p->h->path); + + /* if the user hasn't modified the flags on this message, update + * the flags we just detected. + */ + if (!ctx->mailbox->hdrs[i]->changed) + if (maildir_update_flags(ctx, ctx->mailbox->hdrs[i], p->h)) + flags_changed = true; + + if (ctx->mailbox->hdrs[i]->deleted == ctx->mailbox->hdrs[i]->trash) + { + if (ctx->mailbox->hdrs[i]->deleted != p->h->deleted) + { + ctx->mailbox->hdrs[i]->deleted = p->h->deleted; + flags_changed = true; + } + } + ctx->mailbox->hdrs[i]->trash = p->h->trash; + + /* this is a duplicate of an existing header, so remove it */ + mutt_header_free(&p->h); + } + /* This message was not in the list of messages we just scanned. + * Check to see if we have enough information to know if the + * message has disappeared out from underneath us. + */ + else if (((changed & 1) && (strncmp(ctx->mailbox->hdrs[i]->path, "new/", 4) == 0)) || + ((changed & 2) && (strncmp(ctx->mailbox->hdrs[i]->path, "cur/", 4) == 0))) + { + /* This message disappeared, so we need to simulate a "reopen" + * event. We know it disappeared because we just scanned the + * subdirectory it used to reside in. + */ + occult = true; + } + else + { + /* This message resides in a subdirectory which was not + * modified, so we assume that it is still present and + * unchanged. + */ + ctx->mailbox->hdrs[i]->active = true; + } + } + + /* destroy the file name hash */ + mutt_hash_destroy(&fnames); + + /* If we didn't just get new mail, update the tables. */ + if (occult) + maildir_update_tables(ctx, index_hint); + + /* do any delayed parsing we need to do. */ + maildir_delayed_parsing(ctx->mailbox, &md, NULL); + + /* Incorporate new messages */ + have_new = maildir_move_to_context(ctx, &md); + + if (occult) + return MUTT_REOPENED; + if (have_new) + return MUTT_NEW_MAIL; + if (flags_changed) + return MUTT_FLAGS; + return 0; +} + +/** + * maildir_msg_open - Implements MxOps::msg_open() + */ +static int maildir_msg_open(struct Context *ctx, struct Message *msg, int msgno) +{ + return maildir_mh_open_message(ctx->mailbox, msg, msgno, 1); +} + +/** + * maildir_msg_open_new - Implements MxOps::msg_open_new() + * + * Open a new (temporary) message in a maildir folder. + * + * @note This uses _almost_ the maildir file name format, + * but with a {cur,new} prefix. + */ +static int maildir_msg_open_new(struct Context *ctx, struct Message *msg, struct Header *hdr) +{ + int fd; + char path[PATH_MAX]; + char suffix[16]; + char subdir[16]; + mode_t omask; + + if (hdr) + { + bool deleted = hdr->deleted; + hdr->deleted = false; + + maildir_flags(suffix, sizeof(suffix), hdr); + + hdr->deleted = deleted; + } + else + *suffix = '\0'; + + if (hdr && (hdr->read || hdr->old)) + mutt_str_strfcpy(subdir, "cur", sizeof(subdir)); + else + mutt_str_strfcpy(subdir, "new", sizeof(subdir)); + + omask = umask(mh_umask(ctx->mailbox)); + while (true) + { + snprintf(path, sizeof(path), "%s/tmp/%s.%lld.R%" PRIu64 ".%s%s", + ctx->mailbox->path, subdir, (long long) time(NULL), mutt_rand64(), + NONULL(ShortHostname), suffix); + + mutt_debug(2, "Trying %s.\n", path); + + fd = open(path, O_WRONLY | O_EXCL | O_CREAT, 0666); + if (fd == -1) + { + if (errno != EEXIST) + { + umask(omask); + mutt_perror(path); + return -1; + } + } + else + { + mutt_debug(2, "Success.\n"); + msg->path = mutt_str_strdup(path); + break; + } + } + umask(omask); + + msg->fp = fdopen(fd, "w"); + if (!msg->fp) + { + FREE(&msg->path); + close(fd); + unlink(path); + return -1; + } + + return 0; +} + +/** + * maildir_msg_commit - Implements MxOps::msg_commit() + */ +static int maildir_msg_commit(struct Context *ctx, struct Message *msg) +{ + return md_commit_message(ctx->mailbox, msg, NULL); +} + +/** + * maildir_path_probe - Is this a Maildir mailbox? - Implements MxOps::path_probe() + */ +int maildir_path_probe(const char *path, const struct stat *st) +{ + if (!path) + return MUTT_UNKNOWN; + + if (!st || !S_ISDIR(st->st_mode)) + return MUTT_UNKNOWN; + + char cur[PATH_MAX]; + snprintf(cur, sizeof(cur), "%s/cur", path); + + struct stat stc; + if ((stat(cur, &stc) == 0) && S_ISDIR(stc.st_mode)) + return MUTT_MAILDIR; + + return MUTT_UNKNOWN; +} + +/** + * maildir_path_canon - Canonicalise a mailbox path - Implements MxOps::path_canon() + */ +int maildir_path_canon(char *buf, size_t buflen, const char *folder) +{ + if (!buf) + return -1; + + if ((buf[0] == '+') || (buf[0] == '=')) + { + if (!folder) + return -1; + + buf[0] = '/'; + mutt_str_inline_replace(buf, buflen, 0, folder); + } + + mutt_path_canon(buf, buflen, HomeDir); + return 0; +} + +/** + * maildir_path_pretty - Implements MxOps::path_pretty() + */ +int maildir_path_pretty(char *buf, size_t buflen, const char *folder) +{ + if (!buf) + return -1; + + if (mutt_path_abbr_folder(buf, buflen, folder)) + return 0; + + if (mutt_path_pretty(buf, buflen, HomeDir)) + return 0; + + return -1; +} - /* check for modifications and adjust flags */ - for (int i = 0; i < ctx->mailbox->msg_count; i++) - { - ctx->mailbox->hdrs[i]->active = false; - maildir_canon_filename(ctx->mailbox->hdrs[i]->path, buf, sizeof(buf)); - p = mutt_hash_find(fnames, buf); - if (p && p->h) - { - /* message already exists, merge flags */ - ctx->mailbox->hdrs[i]->active = true; +/** + * maildir_path_parent - Implements MxOps::path_parent() + */ +int maildir_path_parent(char *buf, size_t buflen) +{ + if (!buf) + return -1; - /* check to see if the message has moved to a different - * subdirectory. If so, update the associated filename. - */ - if (mutt_str_strcmp(ctx->mailbox->hdrs[i]->path, p->h->path) != 0) - mutt_str_replace(&ctx->mailbox->hdrs[i]->path, p->h->path); + if (mutt_path_parent(buf, buflen)) + return 0; - /* if the user hasn't modified the flags on this message, update - * the flags we just detected. - */ - if (!ctx->mailbox->hdrs[i]->changed) - if (maildir_update_flags(ctx, ctx->mailbox->hdrs[i], p->h)) - flags_changed = true; + if (buf[0] == '~') + mutt_path_canon(buf, buflen, HomeDir); - if (ctx->mailbox->hdrs[i]->deleted == ctx->mailbox->hdrs[i]->trash) - { - if (ctx->mailbox->hdrs[i]->deleted != p->h->deleted) - { - ctx->mailbox->hdrs[i]->deleted = p->h->deleted; - flags_changed = true; - } - } - ctx->mailbox->hdrs[i]->trash = p->h->trash; + if (mutt_path_parent(buf, buflen)) + return 0; - /* this is a duplicate of an existing header, so remove it */ - mutt_header_free(&p->h); - } - /* This message was not in the list of messages we just scanned. - * Check to see if we have enough information to know if the - * message has disappeared out from underneath us. - */ - else if (((changed & 1) && (strncmp(ctx->mailbox->hdrs[i]->path, "new/", 4) == 0)) || - ((changed & 2) && (strncmp(ctx->mailbox->hdrs[i]->path, "cur/", 4) == 0))) - { - /* This message disappeared, so we need to simulate a "reopen" - * event. We know it disappeared because we just scanned the - * subdirectory it used to reside in. - */ - occult = true; - } - else - { - /* This message resides in a subdirectory which was not - * modified, so we assume that it is still present and - * unchanged. - */ - ctx->mailbox->hdrs[i]->active = true; - } - } + return -1; +} - /* destroy the file name hash */ - mutt_hash_destroy(&fnames); +/** + * mh_mbox_open - Implements MxOps::mbox_open() + */ +static int mh_mbox_open(struct Context *ctx) +{ + return mh_read_dir(ctx, NULL); +} - /* If we didn't just get new mail, update the tables. */ - if (occult) - maildir_update_tables(ctx, index_hint); +/** + * mh_mbox_open_append - Implements MxOps::mbox_open_append() + */ +static int mh_mbox_open_append(struct Context *ctx, int flags) +{ + if (!(flags & MUTT_APPENDNEW)) + { + return 0; + } - /* do any delayed parsing we need to do. */ - maildir_delayed_parsing(ctx->mailbox, &md, NULL); + if (mkdir(ctx->mailbox->path, S_IRWXU)) + { + mutt_perror(ctx->mailbox->path); + return -1; + } - /* Incorporate new messages */ - have_new = maildir_move_to_context(ctx, &md); + char tmp[PATH_MAX]; + snprintf(tmp, sizeof(tmp), "%s/.mh_sequences", ctx->mailbox->path); + const int i = creat(tmp, S_IRWXU); + if (i == -1) + { + mutt_perror(tmp); + rmdir(ctx->mailbox->path); + return -1; + } + close(i); - if (occult) - return MUTT_REOPENED; - if (have_new) - return MUTT_NEW_MAIL; - if (flags_changed) - return MUTT_FLAGS; return 0; } @@ -2630,300 +2871,117 @@ static int mh_mbox_sync(struct Context *ctx, int *index_hint) else i = maildir_mbox_check(ctx, index_hint); - if (i != 0) - return i; - -#ifdef USE_HCACHE - if (ctx->mailbox->magic == MUTT_MAILDIR || ctx->mailbox->magic == MUTT_MH) - hc = mutt_hcache_open(HeaderCache, ctx->mailbox->path, NULL); -#endif - - if (!ctx->mailbox->quiet) - { - snprintf(msgbuf, sizeof(msgbuf), _("Writing %s..."), ctx->mailbox->path); - mutt_progress_init(&progress, msgbuf, MUTT_PROGRESS_MSG, WriteInc, - ctx->mailbox->msg_count); - } - - for (i = 0; i < ctx->mailbox->msg_count; i++) - { - if (!ctx->mailbox->quiet) - mutt_progress_update(&progress, i, -1); - -#ifdef USE_HCACHE - if (mh_sync_mailbox_message(ctx, i, hc) == -1) - goto err; -#else - if (mh_sync_mailbox_message(ctx, i) == -1) - goto err; -#endif - } - -#ifdef USE_HCACHE - if (ctx->mailbox->magic == MUTT_MAILDIR || ctx->mailbox->magic == MUTT_MH) - mutt_hcache_close(hc); -#endif - - if (ctx->mailbox->magic == MUTT_MH) - mh_update_sequences(ctx->mailbox); - - /* XXX race condition? */ - - maildir_update_mtime(ctx->mailbox); - - /* adjust indices */ - - if (ctx->deleted) - { - for (i = 0, j = 0; i < ctx->mailbox->msg_count; i++) - { - if (!ctx->mailbox->hdrs[i]->deleted || (ctx->mailbox->magic == MUTT_MAILDIR && MaildirTrash)) - ctx->mailbox->hdrs[i]->index = j++; - } - } - - return 0; - -err: -#ifdef USE_HCACHE - if (ctx->mailbox->magic == MUTT_MAILDIR || ctx->mailbox->magic == MUTT_MH) - mutt_hcache_close(hc); -#endif - return -1; -} - -/** - * maildir_update_flags - Update the mailbox flags - * @param ctx Mailbox - * @param o Old email Header - * @param n New email Header - * @retval true If the flags changed - * @retval false Otherwise - */ -bool maildir_update_flags(struct Context *ctx, struct Header *o, struct Header *n) -{ - /* save the global state here so we can reset it at the - * end of list block if required. - */ - bool context_changed = ctx->mailbox->changed; - bool header_changed; - - /* user didn't modify this message. alter the flags to match the - * current state on disk. This may not actually do - * anything. mutt_set_flag() will just ignore the call if the status - * bits are already properly set, but it is still faster not to pass - * through it */ - if (o->flagged != n->flagged) - mutt_set_flag(ctx, o, MUTT_FLAG, n->flagged); - if (o->replied != n->replied) - mutt_set_flag(ctx, o, MUTT_REPLIED, n->replied); - if (o->read != n->read) - mutt_set_flag(ctx, o, MUTT_READ, n->read); - if (o->old != n->old) - mutt_set_flag(ctx, o, MUTT_OLD, n->old); - - /* mutt_set_flag() will set this, but we don't need to - * sync the changes we made because we just updated the - * context to match the current on-disk state of the - * message. - */ - header_changed = o->changed; - o->changed = false; - - /* if the mailbox was not modified before we made these - * changes, unset the changed flag since nothing needs to - * be synchronized. - */ - if (!context_changed) - ctx->mailbox->changed = false; - - return header_changed; -} - -/** - * md_open_find_message - Find a message in a maildir folder - * @param folder Base folder - * @param unique Unique part of filename - * @param subfolder Subfolder to search, e.g. 'cur' - * @param newname File's new name - * @retval ptr File handle - * - * These functions try to find a message in a maildir folder when it - * has moved under our feet. Note that this code is rather expensive, but - * then again, it's called rarely. - */ -static FILE *md_open_find_message(const char *folder, const char *unique, - const char *subfolder, char **newname) -{ - char dir[PATH_MAX]; - char tunique[PATH_MAX]; - char fname[PATH_MAX]; - - struct dirent *de = NULL; - - FILE *fp = NULL; - int oe = ENOENT; + if (i != 0) + return i; - snprintf(dir, sizeof(dir), "%s/%s", folder, subfolder); +#ifdef USE_HCACHE + if (ctx->mailbox->magic == MUTT_MAILDIR || ctx->mailbox->magic == MUTT_MH) + hc = mutt_hcache_open(HeaderCache, ctx->mailbox->path, NULL); +#endif - DIR *dp = opendir(dir); - if (!dp) + if (!ctx->mailbox->quiet) { - errno = ENOENT; - return NULL; + snprintf(msgbuf, sizeof(msgbuf), _("Writing %s..."), ctx->mailbox->path); + mutt_progress_init(&progress, msgbuf, MUTT_PROGRESS_MSG, WriteInc, + ctx->mailbox->msg_count); } - while ((de = readdir(dp))) + for (i = 0; i < ctx->mailbox->msg_count; i++) { - maildir_canon_filename(de->d_name, tunique, sizeof(tunique)); + if (!ctx->mailbox->quiet) + mutt_progress_update(&progress, i, -1); - if (mutt_str_strcmp(tunique, unique) == 0) - { - snprintf(fname, sizeof(fname), "%s/%s/%s", folder, subfolder, de->d_name); - fp = fopen(fname, "r"); - oe = errno; - break; - } +#ifdef USE_HCACHE + if (mh_sync_mailbox_message(ctx, i, hc) == -1) + goto err; +#else + if (mh_sync_mailbox_message(ctx, i) == -1) + goto err; +#endif } - closedir(dp); - - if (newname && fp) - *newname = mutt_str_strdup(fname); +#ifdef USE_HCACHE + if (ctx->mailbox->magic == MUTT_MAILDIR || ctx->mailbox->magic == MUTT_MH) + mutt_hcache_close(hc); +#endif - errno = oe; - return fp; -} + if (ctx->mailbox->magic == MUTT_MH) + mh_update_sequences(ctx->mailbox); -/** - * maildir_open_find_message - Find a new - * @param folder Maildir path - * @param msg Email path - * @param newname New name, if it has moved - * @retval ptr File handle - */ -FILE *maildir_open_find_message(const char *folder, const char *msg, char **newname) -{ - char unique[PATH_MAX]; + /* XXX race condition? */ - static unsigned int new_hits = 0, cur_hits = 0; /* simple dynamic optimization */ + maildir_update_mtime(ctx->mailbox); - maildir_canon_filename(msg, unique, sizeof(unique)); + /* adjust indices */ - FILE *fp = md_open_find_message(folder, unique, new_hits > cur_hits ? "new" : "cur", newname); - if (fp || (errno != ENOENT)) + if (ctx->deleted) { - if (new_hits < UINT_MAX && cur_hits < UINT_MAX) + for (i = 0, j = 0; i < ctx->mailbox->msg_count; i++) { - new_hits += (new_hits > cur_hits ? 1 : 0); - cur_hits += (new_hits > cur_hits ? 0 : 1); + if (!ctx->mailbox->hdrs[i]->deleted || (ctx->mailbox->magic == MUTT_MAILDIR && MaildirTrash)) + ctx->mailbox->hdrs[i]->index = j++; } - - return fp; } - fp = md_open_find_message(folder, unique, new_hits > cur_hits ? "cur" : "new", newname); - if (fp || (errno != ENOENT)) - { - if (new_hits < UINT_MAX && cur_hits < UINT_MAX) - { - new_hits += (new_hits > cur_hits ? 0 : 1); - cur_hits += (new_hits > cur_hits ? 1 : 0); - } - return fp; - } + return 0; - return NULL; +err: +#ifdef USE_HCACHE + if (ctx->mailbox->magic == MUTT_MAILDIR || ctx->mailbox->magic == MUTT_MH) + mutt_hcache_close(hc); +#endif + return -1; } /** - * maildir_check_empty - Is the mailbox empty - * @param path Mailbox to check - * @retval 1 Mailbox is empty - * @retval 0 Mailbox contains mail - * @retval -1 Error + * mh_mbox_close - Implements MxOps::mbox_close() + * @retval 0 Always */ -int maildir_check_empty(const char *path) +static int mh_mbox_close(struct Context *ctx) { - DIR *dp = NULL; - struct dirent *de = NULL; - int r = 1; /* assume empty until we find a message */ - char realpath[PATH_MAX]; - int iter = 0; - - /* Strategy here is to look for any file not beginning with a period */ - - do - { - /* we do "cur" on the first iteration since it's more likely that we'll - * find old messages without having to scan both subdirs - */ - snprintf(realpath, sizeof(realpath), "%s/%s", path, iter == 0 ? "cur" : "new"); - dp = opendir(realpath); - if (!dp) - return -1; - while ((de = readdir(dp))) - { - if (*de->d_name != '.') - { - r = 0; - break; - } - } - closedir(dp); - iter++; - } while (r && iter < 2); + FREE(&ctx->mailbox->data); - return r; + return 0; } /** - * mh_check_empty - Is mailbox empty - * @param path Mailbox to check - * @retval 1 Mailbox is empty - * @retval 0 Mailbox contains mail - * @retval -1 Error + * mh_msg_open - Implements MxOps::msg_open() */ -int mh_check_empty(const char *path) +static int mh_msg_open(struct Context *ctx, struct Message *msg, int msgno) { - struct dirent *de = NULL; - int r = 1; /* assume empty until we find a message */ - - DIR *dp = opendir(path); - if (!dp) - return -1; - while ((de = readdir(dp))) - { - if (mh_valid_message(de->d_name)) - { - r = 0; - break; - } - } - closedir(dp); - - return r; + return maildir_mh_open_message(ctx->mailbox, msg, msgno, 0); } /** - * maildir_path_probe - Is this a Maildir mailbox? - Implements MxOps::path_probe() + * mh_msg_open_new - Implements MxOps::msg_open_new() + * + * Open a new (temporary) message in an MH folder. */ -int maildir_path_probe(const char *path, const struct stat *st) +static int mh_msg_open_new(struct Context *ctx, struct Message *msg, struct Header *hdr) { - if (!path) - return MUTT_UNKNOWN; - - if (!st || !S_ISDIR(st->st_mode)) - return MUTT_UNKNOWN; - - char cur[PATH_MAX]; - snprintf(cur, sizeof(cur), "%s/cur", path); + return mh_mkstemp(ctx->mailbox, &msg->fp, &msg->path); +} - struct stat stc; - if ((stat(cur, &stc) == 0) && S_ISDIR(stc.st_mode)) - return MUTT_MAILDIR; +/** + * mh_msg_commit - Implements MxOps::msg_commit() + */ +static int mh_msg_commit(struct Context *ctx, struct Message *msg) +{ + return mh_commit_msg(ctx->mailbox, msg, NULL, true); +} - return MUTT_UNKNOWN; +/** + * mh_msg_close - Close a message + * @param ctx Mailbox + * @param msg Message to close + * @retval 0 Success + * @retval EOF Error, see errno + * + * @note May also return EOF Failure, see errno + */ +static int mh_msg_close(struct Context *ctx, struct Message *msg) +{ + return mutt_file_fclose(&msg->fp); } /** @@ -2969,64 +3027,6 @@ int mh_path_probe(const char *path, const struct stat *st) return MUTT_UNKNOWN; } -/** - * maildir_path_canon - Canonicalise a mailbox path - Implements MxOps::path_canon() - */ -int maildir_path_canon(char *buf, size_t buflen, const char *folder) -{ - if (!buf) - return -1; - - if ((buf[0] == '+') || (buf[0] == '=')) - { - if (!folder) - return -1; - - buf[0] = '/'; - mutt_str_inline_replace(buf, buflen, 0, folder); - } - - mutt_path_canon(buf, buflen, HomeDir); - return 0; -} - -/** - * maildir_path_pretty - Implements MxOps::path_pretty() - */ -int maildir_path_pretty(char *buf, size_t buflen, const char *folder) -{ - if (!buf) - return -1; - - if (mutt_path_abbr_folder(buf, buflen, folder)) - return 0; - - if (mutt_path_pretty(buf, buflen, HomeDir)) - return 0; - - return -1; -} - -/** - * maildir_path_parent - Implements MxOps::path_parent() - */ -int maildir_path_parent(char *buf, size_t buflen) -{ - if (!buf) - return -1; - - if (mutt_path_parent(buf, buflen)) - return 0; - - if (buf[0] == '~') - mutt_path_canon(buf, buflen, HomeDir); - - if (mutt_path_parent(buf, buflen)) - return 0; - - return -1; -} - // clang-format off /** * struct mx_maildir_ops - Maildir mailbox - Implements ::MxOps