diff options
Diffstat (limited to 'src/lib-storage/index/maildir/maildir-util.c')
-rw-r--r-- | src/lib-storage/index/maildir/maildir-util.c | 323 |
1 files changed, 323 insertions, 0 deletions
diff --git a/src/lib-storage/index/maildir/maildir-util.c b/src/lib-storage/index/maildir/maildir-util.c new file mode 100644 index 0000000..c03546e --- /dev/null +++ b/src/lib-storage/index/maildir/maildir-util.c @@ -0,0 +1,323 @@ +/* Copyright (c) 2004-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "ioloop.h" +#include "str.h" +#include "mkdir-parents.h" +#include "mailbox-list-private.h" +#include "maildir-storage.h" +#include "maildir-uidlist.h" +#include "maildir-keywords.h" +#include "maildir-filename-flags.h" +#include "maildir-sync.h" +#include "mailbox-recent-flags.h" + +#include <stdio.h> +#include <unistd.h> +#include <dirent.h> +#include <fcntl.h> +#include <utime.h> +#include <sys/stat.h> + +#define MAILDIR_RESYNC_RETRY_COUNT 10 + +static const char * +maildir_filename_guess(struct maildir_mailbox *mbox, uint32_t uid, + const char *fname, + enum maildir_uidlist_rec_flag *uidlist_flags, + bool *have_flags_r) + +{ + struct mail_index_view *view = mbox->flags_view; + struct maildir_keywords_sync_ctx *kw_ctx; + enum mail_flags flags; + ARRAY_TYPE(keyword_indexes) keywords; + const char *p; + uint32_t seq; + + if (view == NULL || !mail_index_lookup_seq(view, uid, &seq)) { + *have_flags_r = FALSE; + return fname; + } + + t_array_init(&keywords, 32); + mail_index_lookup_view_flags(view, seq, &flags, &keywords); + if (array_count(&keywords) == 0) { + *have_flags_r = (flags & MAIL_FLAGS_NONRECENT) != 0; + fname = maildir_filename_flags_set(fname, flags); + } else { + *have_flags_r = TRUE; + kw_ctx = maildir_keywords_sync_init_readonly(mbox->keywords, + mbox->box.index); + fname = maildir_filename_flags_kw_set(kw_ctx, fname, + flags, &keywords); + maildir_keywords_sync_deinit(&kw_ctx); + } + + if (*have_flags_r) { + /* don't even bother looking into new/ dir */ + *uidlist_flags &= MAILDIR_UIDLIST_REC_FLAG_NEW_DIR; + } else if ((*uidlist_flags & MAILDIR_UIDLIST_REC_FLAG_MOVED) == 0 && + ((*uidlist_flags & MAILDIR_UIDLIST_REC_FLAG_NEW_DIR) != 0 || + mailbox_recent_flags_have_uid(&mbox->box, uid))) { + /* probably in new/ dir, drop ":2," from fname */ + *uidlist_flags |= MAILDIR_UIDLIST_REC_FLAG_NEW_DIR; + p = strrchr(fname, MAILDIR_INFO_SEP); + if (p != NULL) + fname = t_strdup_until(fname, p); + } + + return fname; +} + +static int maildir_file_do_try(struct maildir_mailbox *mbox, uint32_t uid, + maildir_file_do_func *callback, void *context) +{ + const char *path, *fname; + enum maildir_uidlist_rec_flag flags; + bool have_flags; + int ret; + + ret = maildir_sync_lookup(mbox, uid, &flags, &fname); + if (ret <= 0) + return ret == 0 ? -2 : -1; + + if ((flags & MAILDIR_UIDLIST_REC_FLAG_NONSYNCED) != 0) { + /* let's see if we can guess the filename based on index */ + fname = maildir_filename_guess(mbox, uid, fname, + &flags, &have_flags); + } + /* make a copy, just in case callback refreshes uidlist and + the pointer becomes invalid. */ + fname = t_strdup(fname); + + ret = 0; + if ((flags & MAILDIR_UIDLIST_REC_FLAG_NEW_DIR) != 0) { + /* probably in new/ dir */ + path = t_strconcat(mailbox_get_path(&mbox->box), + "/new/", fname, NULL); + ret = callback(mbox, path, context); + } + if (ret == 0) { + path = t_strconcat(mailbox_get_path(&mbox->box), "/cur/", + fname, NULL); + ret = callback(mbox, path, context); + } + if (ret > 0 && (flags & MAILDIR_UIDLIST_REC_FLAG_NONSYNCED) != 0) { + /* file was found. make sure we remember its latest name. */ + maildir_uidlist_update_fname(mbox->uidlist, fname); + } else if (ret == 0 && + (flags & MAILDIR_UIDLIST_REC_FLAG_NONSYNCED) == 0) { + /* file wasn't found. mark this message nonsynced, so we can + retry the lookup by guessing the flags */ + maildir_uidlist_add_flags(mbox->uidlist, fname, + MAILDIR_UIDLIST_REC_FLAG_NONSYNCED); + } + return ret; +} + +static int do_racecheck(struct maildir_mailbox *mbox, const char *path, + void *context) +{ + const uint32_t *uidp = context; + struct stat st; + int ret; + + ret = lstat(path, &st); + if (ret == 0 && (st.st_mode & S_IFMT) == S_IFLNK) { + /* most likely a symlink pointing to a nonexistent file */ + mailbox_set_critical(&mbox->box, + "Maildir: Symlink destination doesn't exist for UID=%u: %s", *uidp, path); + return -2; + } else if (ret < 0 && errno != ENOENT) { + mailbox_set_critical(&mbox->box, "lstat(%s) failed: %m", path); + return -1; + } else { + /* success or ENOENT, either way we're done */ + mailbox_set_critical(&mbox->box, + "maildir_file_do(%s): Filename keeps changing for UID=%u", path, *uidp); + return -1; + } +} + +#undef maildir_file_do +int maildir_file_do(struct maildir_mailbox *mbox, uint32_t uid, + maildir_file_do_func *callback, void *context) +{ + int i, ret; + + T_BEGIN { + ret = maildir_file_do_try(mbox, uid, callback, context); + } T_END; + if (ret == 0 && mbox->storage->set->maildir_very_dirty_syncs) T_BEGIN { + /* try guessing again with refreshed flags */ + if (maildir_sync_refresh_flags_view(mbox) == 0) + ret = maildir_file_do_try(mbox, uid, callback, context); + } T_END; + for (i = 0; i < MAILDIR_RESYNC_RETRY_COUNT && ret == 0; i++) { + /* file is either renamed or deleted. sync the maildir and + see which one. if file appears to be renamed constantly, + don't try to open it more than 10 times. */ + if (maildir_storage_sync_force(mbox, uid) < 0) + return -1; + + T_BEGIN { + ret = maildir_file_do_try(mbox, uid, callback, context); + } T_END; + } + + if (i == MAILDIR_RESYNC_RETRY_COUNT) T_BEGIN { + ret = maildir_file_do_try(mbox, uid, do_racecheck, &uid); + } T_END; + + return ret == -2 ? 0 : ret; +} + +static int maildir_create_path(struct mailbox *box, const char *path, + enum mailbox_list_path_type type, bool retry) +{ + const struct mailbox_permissions *perm = mailbox_get_permissions(box); + const char *p, *parent; + + if (mkdir_chgrp(path, perm->dir_create_mode, perm->file_create_gid, + perm->file_create_gid_origin) == 0) + return 0; + + switch (errno) { + case EEXIST: + return 0; + case ENOENT: + p = strrchr(path, '/'); + if (type == MAILBOX_LIST_PATH_TYPE_MAILBOX || + p == NULL || !retry) { + /* mailbox was being deleted just now */ + mailbox_set_deleted(box); + return -1; + } + /* create index/control root directory */ + parent = t_strdup_until(path, p); + if (mailbox_list_mkdir_root(box->list, parent, type) < 0) { + mail_storage_copy_list_error(box->storage, box->list); + return -1; + } + /* should work now, try again */ + return maildir_create_path(box, path, type, FALSE); + default: + mailbox_set_critical(box, "mkdir(%s) failed: %m", path); + return -1; + } +} + +static int maildir_create_subdirs(struct mailbox *box) +{ + static const char *subdirs[] = { "cur", "new", "tmp" }; + const char *dirs[N_ELEMENTS(subdirs) + 2]; + enum mailbox_list_path_type types[N_ELEMENTS(subdirs) + 2]; + struct stat st; + const char *path; + unsigned int i, count; + + /* @UNSAFE: get a list of directories we want to create */ + for (i = 0; i < N_ELEMENTS(subdirs); i++) { + types[i] = MAILBOX_LIST_PATH_TYPE_MAILBOX; + dirs[i] = t_strconcat(mailbox_get_path(box), + "/", subdirs[i], NULL); + } + if (mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_CONTROL, &path) > 0) { + types[i] = MAILBOX_LIST_PATH_TYPE_CONTROL; + dirs[i++] = path; + } + if (mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_INDEX, &path) > 0) { + types[i] = MAILBOX_LIST_PATH_TYPE_INDEX; + dirs[i++] = path; + } + count = i; + i_assert(count <= N_ELEMENTS(dirs)); + + for (i = 0; i < count; i++) { + path = dirs[i]; + if (stat(path, &st) == 0) + continue; + if (errno != ENOENT) { + mailbox_set_critical(box, "stat(%s) failed: %m", path); + break; + } + if (maildir_create_path(box, path, types[i], TRUE) < 0) + break; + } + return i == N_ELEMENTS(dirs) ? 0 : -1; +} + +bool maildir_set_deleted(struct mailbox *box) +{ + struct stat st; + int ret; + + if (stat(mailbox_get_path(box), &st) < 0) { + if (errno == ENOENT) + mailbox_set_deleted(box); + else { + mailbox_set_critical(box, + "stat(%s) failed: %m", mailbox_get_path(box)); + } + return FALSE; + } + /* maildir itself exists. create all of its subdirectories in case + they got lost. */ + T_BEGIN { + ret = maildir_create_subdirs(box); + } T_END; + return ret < 0 ? FALSE : TRUE; +} + +int maildir_lose_unexpected_dir(struct mail_storage *storage, const char *path) +{ + const char *dest, *fname, *p; + + /* There's a directory in maildir, get rid of it. + + In some installations this was caused by a messed up configuration + where e.g. mails was initially delivered to new/new/ directory. + Also Dovecot v2.0.0 - v2.0.4 sometimes may have renamed tmp/ + directory under new/ or cur/. */ + if (rmdir(path) == 0) { + mail_storage_set_critical(storage, + "Maildir: rmdir()ed unwanted empty directory: %s", + path); + return 1; + } else if (errno == ENOENT) { + /* someone else rmdired or renamed it */ + return 0; + } else if (errno != ENOTEMPTY) { + mail_storage_set_critical(storage, + "Maildir: Found unwanted directory %s, " + "but rmdir() failed: %m", path); + return -1; + } + + /* It's not safe to delete this directory since it has some files in it, + but it's also not helpful to log this message over and over again. + Get rid of this error by renaming the directory elsewhere */ + p = strrchr(path, '/'); + i_assert(p != NULL); + fname = p + 1; + while (p != path && p[-1] != '/') p--; + i_assert(p != NULL); + + dest = t_strconcat(t_strdup_until(path, p), "extra-", fname, NULL); + if (rename(path, dest) == 0) { + mail_storage_set_critical(storage, + "Maildir: renamed unwanted directory %s to %s", + path, dest); + return 1; + } else if (errno == ENOENT) { + /* someone else renamed it (could have been flag change) */ + return 0; + } else { + mail_storage_set_critical(storage, + "Maildir: Found unwanted directory, " + "but rename(%s, %s) failed: %m", path, dest); + return -1; + } +} |