summaryrefslogtreecommitdiffstats
path: root/src/lib-storage/index/maildir/maildir-storage.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-storage/index/maildir/maildir-storage.c')
-rw-r--r--src/lib-storage/index/maildir/maildir-storage.c795
1 files changed, 795 insertions, 0 deletions
diff --git a/src/lib-storage/index/maildir/maildir-storage.c b/src/lib-storage/index/maildir/maildir-storage.c
new file mode 100644
index 0000000..f6537d8
--- /dev/null
+++ b/src/lib-storage/index/maildir/maildir-storage.c
@@ -0,0 +1,795 @@
+/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "mkdir-parents.h"
+#include "eacces-error.h"
+#include "unlink-old-files.h"
+#include "mailbox-uidvalidity.h"
+#include "mailbox-list-private.h"
+#include "maildir-storage.h"
+#include "maildir-uidlist.h"
+#include "maildir-keywords.h"
+#include "maildir-sync.h"
+#include "index-mail.h"
+
+#include <sys/stat.h>
+
+#define MAILDIR_LIST_CONTEXT(obj) \
+ MODULE_CONTEXT(obj, maildir_mailbox_list_module)
+#define MAILDIR_SUBFOLDER_FILENAME "maildirfolder"
+
+struct maildir_mailbox_list_context {
+ union mailbox_list_module_context module_ctx;
+ const struct maildir_settings *set;
+};
+
+extern struct mail_storage maildir_storage;
+extern struct mailbox maildir_mailbox;
+
+static struct event_category event_category_maildir = {
+ .name = "maildir",
+ .parent = &event_category_storage,
+};
+
+static MODULE_CONTEXT_DEFINE_INIT(maildir_mailbox_list_module,
+ &mailbox_list_module_register);
+static const char *maildir_subdirs[] = { "cur", "new", "tmp" };
+
+static void maildir_mailbox_close(struct mailbox *box);
+
+static struct mail_storage *maildir_storage_alloc(void)
+{
+ struct maildir_storage *storage;
+ pool_t pool;
+
+ pool = pool_alloconly_create("maildir storage", 512+256);
+ storage = p_new(pool, struct maildir_storage, 1);
+ storage->storage = maildir_storage;
+ storage->storage.pool = pool;
+ return &storage->storage;
+}
+
+static int
+maildir_storage_create(struct mail_storage *_storage, struct mail_namespace *ns,
+ const char **error_r ATTR_UNUSED)
+{
+ struct maildir_storage *storage = MAILDIR_STORAGE(_storage);
+ struct mailbox_list *list = ns->list;
+ const char *dir;
+
+ storage->set = mail_namespace_get_driver_settings(ns, _storage);
+
+ storage->temp_prefix = p_strdup(_storage->pool,
+ mailbox_list_get_temp_prefix(list));
+
+ if (list->set.control_dir == NULL && list->set.inbox_path == NULL &&
+ (ns->flags & NAMESPACE_FLAG_INBOX_ANY) != 0) {
+ /* put the temp files into tmp/ directory preferably */
+ storage->temp_prefix = p_strconcat(_storage->pool, "tmp/",
+ storage->temp_prefix, NULL);
+ dir = mailbox_list_get_root_forced(list, MAILBOX_LIST_PATH_TYPE_DIR);
+ } else {
+ /* control dir should also be writable */
+ dir = mailbox_list_get_root_forced(list, MAILBOX_LIST_PATH_TYPE_CONTROL);
+ }
+ _storage->temp_path_prefix = p_strconcat(_storage->pool, dir, "/",
+ storage->temp_prefix, NULL);
+ return 0;
+}
+
+static void maildir_storage_get_list_settings(const struct mail_namespace *ns,
+ struct mailbox_list_settings *set)
+{
+ if (set->layout == NULL)
+ set->layout = MAILBOX_LIST_NAME_MAILDIRPLUSPLUS;
+ if (set->subscription_fname == NULL)
+ set->subscription_fname = MAILDIR_SUBSCRIPTION_FILE_NAME;
+
+ if (set->inbox_path == NULL && *set->maildir_name == '\0' &&
+ (strcmp(set->layout, MAILBOX_LIST_NAME_MAILDIRPLUSPLUS) == 0 ||
+ strcmp(set->layout, MAILBOX_LIST_NAME_FS) == 0) &&
+ (ns->flags & NAMESPACE_FLAG_INBOX_ANY) != 0) {
+ /* Maildir++ INBOX is the Maildir base itself */
+ set->inbox_path = set->root_dir;
+ }
+}
+
+static const char *
+maildir_storage_find_root_dir(const struct mail_namespace *ns)
+{
+ bool debug = ns->mail_set->mail_debug;
+ const char *home, *path;
+
+ /* we'll need to figure out the maildir location ourself.
+ It's ~/Maildir unless we are chrooted. */
+ if (ns->owner != NULL &&
+ mail_user_get_home(ns->owner, &home) > 0) {
+ path = t_strconcat(home, "/Maildir", NULL);
+ if (access(path, R_OK|W_OK|X_OK) == 0) {
+ if (debug)
+ i_debug("maildir: root exists (%s)", path);
+ return path;
+ }
+ if (debug)
+ i_debug("maildir: access(%s, rwx): failed: %m", path);
+ } else {
+ if (debug)
+ i_debug("maildir: Home directory not set");
+ if (access("/cur", R_OK|W_OK|X_OK) == 0) {
+ if (debug)
+ i_debug("maildir: /cur exists, assuming chroot");
+ return "/";
+ }
+ }
+ return NULL;
+}
+
+static bool maildir_storage_autodetect(const struct mail_namespace *ns,
+ struct mailbox_list_settings *set)
+{
+ bool debug = ns->mail_set->mail_debug;
+ struct stat st;
+ const char *path, *root_dir;
+
+ if (set->root_dir != NULL)
+ root_dir = set->root_dir;
+ else {
+ root_dir = maildir_storage_find_root_dir(ns);
+ if (root_dir == NULL) {
+ if (debug)
+ i_debug("maildir: couldn't find root dir");
+ return FALSE;
+ }
+ }
+
+ path = t_strconcat(root_dir, "/cur", NULL);
+ if (stat(path, &st) < 0) {
+ if (debug)
+ i_debug("maildir autodetect: stat(%s) failed: %m", path);
+ return FALSE;
+ }
+
+ if (!S_ISDIR(st.st_mode)) {
+ if (debug)
+ i_debug("maildir autodetect: %s not a directory", path);
+ return FALSE;
+ }
+
+ set->root_dir = root_dir;
+ maildir_storage_get_list_settings(ns, set);
+ return TRUE;
+}
+
+static int
+mkdir_verify(struct mailbox *box, const char *dir, bool verify)
+{
+ const struct mailbox_permissions *perm;
+ struct stat st;
+
+ if (verify) {
+ if (stat(dir, &st) == 0)
+ return 0;
+
+ if (errno != ENOENT) {
+ mailbox_set_critical(box, "stat(%s) failed: %m", dir);
+ return -1;
+ }
+ }
+
+ perm = mailbox_get_permissions(box);
+ if (mkdir_parents_chgrp(dir, perm->dir_create_mode,
+ perm->file_create_gid,
+ perm->file_create_gid_origin) == 0)
+ return 0;
+
+ if (errno == EEXIST) {
+ if (verify)
+ return 0;
+ mail_storage_set_error(box->storage, MAIL_ERROR_EXISTS,
+ "Mailbox already exists");
+ } else if (errno == ENOENT) {
+ mail_storage_set_error(box->storage, MAIL_ERROR_NOTFOUND,
+ "Mailbox was deleted while it was being created");
+ } else if (errno == EACCES) {
+ if (box->list->ns->type == MAIL_NAMESPACE_TYPE_SHARED) {
+ /* shared namespace, don't log permission errors */
+ mail_storage_set_error(box->storage, MAIL_ERROR_PERM,
+ MAIL_ERRSTR_NO_PERMISSION);
+ return -1;
+ }
+ mailbox_set_critical(box, "%s",
+ mail_error_create_eacces_msg("mkdir", dir));
+ } else {
+ mailbox_set_critical(box, "mkdir(%s) failed: %m", dir);
+ }
+ return -1;
+}
+
+static int maildir_check_tmp(struct mail_storage *storage, const char *dir)
+{
+ unsigned int interval = storage->set->mail_temp_scan_interval;
+ const char *path;
+ struct stat st;
+
+ /* if tmp/ directory exists, we need to clean it up once in a while */
+ path = t_strconcat(dir, "/tmp", NULL);
+ if (stat(path, &st) < 0) {
+ if (errno == ENOENT || errno == ENAMETOOLONG)
+ return 0;
+ if (errno == EACCES) {
+ mail_storage_set_critical(storage, "%s",
+ mail_error_eacces_msg("stat", path));
+ return -1;
+ }
+ mail_storage_set_critical(storage, "stat(%s) failed: %m", path);
+ return -1;
+ }
+
+ if (interval == 0) {
+ /* disabled */
+ } else if (st.st_atime > st.st_ctime + MAILDIR_TMP_DELETE_SECS) {
+ /* the directory should be empty. we won't do anything
+ until ctime changes. */
+ } else if (st.st_atime < ioloop_time - (time_t)interval) {
+ /* time to scan */
+ (void)unlink_old_files(path, "",
+ ioloop_time - MAILDIR_TMP_DELETE_SECS);
+ }
+ return 1;
+}
+
+/* create or fix maildir, ignore if it already exists */
+static int create_maildir_subdirs(struct mailbox *box, bool verify)
+{
+ const char *path, *box_path;
+ unsigned int i;
+ enum mail_error error;
+ int ret = 0;
+
+ if (mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_MAILBOX,
+ &box_path) < 0)
+ return -1;
+
+ for (i = 0; i < N_ELEMENTS(maildir_subdirs); i++) {
+ path = t_strconcat(box_path, "/", maildir_subdirs[i], NULL);
+ if (mkdir_verify(box, path, verify) < 0) {
+ error = mailbox_get_last_mail_error(box);
+ if (error != MAIL_ERROR_EXISTS)
+ return -1;
+ /* try to create all of the directories in case one
+ of them doesn't exist */
+ ret = -1;
+ }
+ }
+ return ret;
+}
+
+static void maildir_lock_touch_timeout(struct maildir_mailbox *mbox)
+{
+ (void)maildir_uidlist_lock_touch(mbox->uidlist);
+}
+
+static struct mailbox *
+maildir_mailbox_alloc(struct mail_storage *storage, struct mailbox_list *list,
+ const char *vname, enum mailbox_flags flags)
+{
+ struct maildir_mailbox *mbox;
+ pool_t pool;
+
+ pool = pool_alloconly_create("maildir mailbox", 1024*3);
+ mbox = p_new(pool, struct maildir_mailbox, 1);
+ mbox->box = maildir_mailbox;
+ mbox->box.pool = pool;
+ mbox->box.storage = storage;
+ mbox->box.list = list;
+ mbox->box.mail_vfuncs = &maildir_mail_vfuncs;
+ mbox->maildir_list_index_ext_id = (uint32_t)-1;
+
+ index_storage_mailbox_alloc(&mbox->box, vname, flags, MAIL_INDEX_PREFIX);
+
+ mbox->storage = MAILDIR_STORAGE(storage);
+ return &mbox->box;
+}
+
+static int maildir_mailbox_open_existing(struct mailbox *box)
+{
+ struct maildir_mailbox *mbox = MAILDIR_MAILBOX(box);
+
+ mbox->uidlist = maildir_uidlist_init(mbox);
+ mbox->keywords = maildir_keywords_init(mbox);
+
+ if ((box->flags & MAILBOX_FLAG_KEEP_LOCKED) != 0) {
+ if (maildir_uidlist_lock(mbox->uidlist) <= 0) {
+ maildir_mailbox_close(box);
+ return -1;
+ }
+ mbox->keep_lock_to = timeout_add(MAILDIR_LOCK_TOUCH_SECS * 1000,
+ maildir_lock_touch_timeout,
+ mbox);
+ }
+
+ if (index_storage_mailbox_open(box, FALSE) < 0) {
+ maildir_mailbox_close(box);
+ return -1;
+ }
+
+ mbox->maildir_ext_id =
+ mail_index_ext_register(mbox->box.index, "maildir",
+ sizeof(mbox->maildir_hdr), 0, 0);
+ return 0;
+}
+
+static bool maildir_storage_is_readonly(struct mailbox *box)
+{
+ struct maildir_mailbox *mbox = MAILDIR_MAILBOX(box);
+
+ if (index_storage_is_readonly(box))
+ return TRUE;
+
+ if (maildir_is_backend_readonly(mbox)) {
+ /* return read-only only if there are no private flags
+ (that are stored in index files) */
+ if (mailbox_get_private_flags_mask(box) == 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static int
+maildir_mailbox_exists(struct mailbox *box, bool auto_boxes,
+ enum mailbox_existence *existence_r)
+{
+ if (auto_boxes && mailbox_is_autocreated(box)) {
+ *existence_r = MAILBOX_EXISTENCE_SELECT;
+ return 0;
+ }
+
+ return index_storage_mailbox_exists_full(box, "cur", existence_r);
+}
+
+static bool maildir_has_any_subdir(const char *box_path,
+ const char **error_path)
+{
+ const char *path;
+ unsigned int i;
+ struct stat st;
+
+ for (i = 0; i < N_ELEMENTS(maildir_subdirs); i++) {
+ path = t_strconcat(box_path, "/", maildir_subdirs[i], NULL);
+ if (stat(path, &st) == 0) {
+ /* Return if there is any subdir */
+ return TRUE;
+ } else if (errno == ENOENT || errno == ENAMETOOLONG) {
+ /* Some subdirs may not exist. */
+ } else {
+ *error_path = path;
+ return FALSE;
+ }
+ }
+
+ return FALSE;
+}
+
+static bool maildir_is_selectable(const char *box_path,
+ const char *root_dir,
+ const char **error_path_r)
+{
+ struct stat st;
+
+ *error_path_r = box_path;
+ if (strcmp(box_path, root_dir) == 0)
+ return maildir_has_any_subdir(box_path, error_path_r);
+ else
+ return stat(box_path, &st) == 0;
+}
+
+static int maildir_mailbox_open(struct mailbox *box)
+{
+ const char *box_path = mailbox_get_path(box);
+ const char *root_dir;
+ int ret;
+
+ /* begin by checking if tmp/ directory exists and if it should be
+ cleaned up. */
+ ret = maildir_check_tmp(box->storage, box_path);
+ if (ret > 0) {
+ /* exists */
+ return maildir_mailbox_open_existing(box);
+ }
+ if (ret < 0)
+ return -1;
+
+ /* tmp/ directory doesn't exist. does the maildir? autocreate missing
+ dirs only with Maildir++ and imapdir layouts. */
+ if (strcmp(box->list->name, MAILBOX_LIST_NAME_MAILDIRPLUSPLUS) != 0 &&
+ strcmp(box->list->name, MAILBOX_LIST_NAME_IMAPDIR) != 0) {
+ mail_storage_set_error(box->storage, MAIL_ERROR_NOTFOUND,
+ T_MAIL_ERR_MAILBOX_NOT_FOUND(box->vname));
+ return -1;
+ }
+ root_dir = mailbox_list_get_root_forced(box->list,
+ MAILBOX_LIST_PATH_TYPE_MAILBOX);
+
+ /* This code path is executed only for Maildir++ and imapdir layouts,
+ which don't support \Noselect mailboxes. If the mailbox root
+ directory exists, automatically create any missing cur/new/tmp
+ directories. Otherwise the mailbox would show up as selectable
+ in the mailbox list, but not actually be selectable.
+
+ As a special case we don't do this when the mailbox root directory
+ is the same as the namespace root directory. This especially means
+ that we don't autocreate Maildir INBOX when ~/Maildir directory
+ exists. Instead, we return that mailbox doesn't exist, so the
+ caller goes to the INBOX autocreation code path similarly as with
+ other mailboxes. This is needed e.g. for welcome plugin to work. */
+ const char *error_path;
+ if (maildir_is_selectable(box_path, root_dir, &error_path)) {
+ /* yes, we'll need to create the missing dirs */
+ if (create_maildir_subdirs(box, TRUE) < 0)
+ return -1;
+
+ return maildir_mailbox_open_existing(box);
+ }
+
+ if (errno == ENOENT || errno == ENAMETOOLONG) {
+ mail_storage_set_error(box->storage, MAIL_ERROR_NOTFOUND,
+ T_MAIL_ERR_MAILBOX_NOT_FOUND(box->vname));
+ return -1;
+ } else {
+ mailbox_set_critical(box, "stat(%s) failed: %m", error_path);
+ return -1;
+ }
+}
+
+static int maildir_create_shared(struct mailbox *box)
+{
+ const struct mailbox_permissions *perm = mailbox_get_permissions(box);
+ const char *path;
+ mode_t old_mask;
+ int fd, ret;
+
+ ret = mailbox_get_path_to(box, MAILBOX_LIST_PATH_TYPE_MAILBOX,
+ &path);
+ if (ret < 0)
+ return -1;
+ i_assert(ret > 0);
+
+ old_mask = umask(0);
+ path = t_strconcat(path, "/dovecot-shared", NULL);
+ fd = open(path, O_WRONLY | O_CREAT, perm->file_create_mode);
+ umask(old_mask);
+
+ if (fd == -1) {
+ mailbox_set_critical(box, "open(%s) failed: %m", path);
+ return -1;
+ }
+
+ if (fchown(fd, (uid_t)-1, perm->file_create_gid) < 0) {
+ if (errno == EPERM) {
+ mailbox_set_critical(box, "%s",
+ eperm_error_get_chgrp("fchown", path,
+ perm->file_create_gid,
+ perm->file_create_gid_origin));
+ } else {
+ mailbox_set_critical(box,
+ "fchown(%s) failed: %m", path);
+ }
+ }
+ i_close_fd(&fd);
+ return 0;
+}
+
+static int
+maildir_mailbox_update(struct mailbox *box, const struct mailbox_update *update)
+{
+ struct maildir_mailbox *mbox = MAILDIR_MAILBOX(box);
+ struct maildir_uidlist *uidlist;
+ bool locked = FALSE;
+ int ret = 0;
+
+ if (!box->opened) {
+ if (mailbox_open(box) < 0)
+ return -1;
+ }
+ uidlist = mbox->uidlist;
+
+ if (update->uid_validity != 0 || update->min_next_uid != 0 ||
+ !guid_128_is_empty(update->mailbox_guid)) {
+ if (maildir_uidlist_lock(uidlist) <= 0)
+ return -1;
+
+ locked = TRUE;
+ if (!guid_128_is_empty(update->mailbox_guid))
+ maildir_uidlist_set_mailbox_guid(uidlist, update->mailbox_guid);
+ if (update->uid_validity != 0)
+ maildir_uidlist_set_uid_validity(uidlist, update->uid_validity);
+ if (update->min_next_uid != 0) {
+ maildir_uidlist_set_next_uid(uidlist, update->min_next_uid,
+ FALSE);
+ }
+ ret = maildir_uidlist_update(uidlist);
+ }
+ if (ret == 0)
+ ret = index_storage_mailbox_update(box, update);
+ if (locked)
+ maildir_uidlist_unlock(uidlist);
+ return ret;
+}
+
+static int maildir_create_maildirfolder_file(struct mailbox *box)
+{
+ const struct mailbox_permissions *perm;
+ const char *path;
+ mode_t old_mask;
+ int fd;
+
+ /* Maildir++ spec wants that maildirfolder named file is created for
+ all subfolders. Do this only with Maildir++ layout. */
+ if (strcmp(box->list->name, MAILBOX_LIST_NAME_MAILDIRPLUSPLUS) != 0)
+ return 0;
+ perm = mailbox_get_permissions(box);
+
+ path = t_strconcat(mailbox_get_path(box),
+ "/"MAILDIR_SUBFOLDER_FILENAME, NULL);
+ old_mask = umask(0);
+ fd = open(path, O_CREAT | O_WRONLY, perm->file_create_mode);
+ umask(old_mask);
+ if (fd != -1) {
+ /* ok */
+ } else if (errno == ENOENT) {
+ mail_storage_set_error(box->storage, MAIL_ERROR_NOTFOUND,
+ "Mailbox was deleted while it was being created");
+ return -1;
+ } else {
+ mailbox_set_critical(box, "open(%s, O_CREAT) failed: %m", path);
+ return -1;
+ }
+
+ if (perm->file_create_gid != (gid_t)-1) {
+ if (fchown(fd, (uid_t)-1, perm->file_create_gid) == 0) {
+ /* ok */
+ } else if (errno == EPERM) {
+ mailbox_set_critical(box, "%s",
+ eperm_error_get_chgrp("fchown", path,
+ perm->file_create_gid,
+ perm->file_create_gid_origin));
+ } else {
+ mailbox_set_critical(box, "fchown(%s) failed: %m", path);
+ }
+ }
+ i_close_fd(&fd);
+ return 0;
+}
+
+static int
+maildir_mailbox_create(struct mailbox *box, const struct mailbox_update *update,
+ bool directory)
+{
+ const char *root_dir, *shared_path;
+ /* allow physical location to exist when we rebuild list index, this
+ happens with LAYOUT=INDEX only. */
+ bool verify = box->storage->rebuilding_list_index;
+ struct stat st;
+ int ret;
+
+ if ((ret = index_storage_mailbox_create(box, directory)) <= 0)
+ return ret;
+ ret = 0;
+ /* the maildir is created now. finish the creation as best as we can */
+ if (create_maildir_subdirs(box, verify) < 0)
+ ret = -1;
+ if (maildir_create_maildirfolder_file(box) < 0)
+ ret = -1;
+ /* if dovecot-shared exists in the root dir, copy it to newly
+ created mailboxes */
+ root_dir = mailbox_list_get_root_forced(box->list,
+ MAILBOX_LIST_PATH_TYPE_MAILBOX);
+ shared_path = t_strconcat(root_dir, "/dovecot-shared", NULL);
+ if (stat(shared_path, &st) == 0) {
+ if (maildir_create_shared(box) < 0)
+ ret = -1;
+ }
+ if (ret == 0 && update != NULL) {
+ if (maildir_mailbox_update(box, update) < 0)
+ ret = -1;
+ }
+ return ret;
+}
+
+static int
+maildir_mailbox_get_metadata(struct mailbox *box,
+ enum mailbox_metadata_items items,
+ struct mailbox_metadata *metadata_r)
+{
+ struct maildir_mailbox *mbox = MAILDIR_MAILBOX(box);
+
+ if (index_mailbox_get_metadata(box, items, metadata_r) < 0)
+ return -1;
+
+ if ((items & MAILBOX_METADATA_GUID) != 0) {
+ if (maildir_uidlist_get_mailbox_guid(mbox->uidlist,
+ metadata_r->guid) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+static void maildir_mailbox_close(struct mailbox *box)
+{
+ struct maildir_mailbox *mbox = MAILDIR_MAILBOX(box);
+
+ if (mbox->keep_lock_to != NULL) {
+ maildir_uidlist_unlock(mbox->uidlist);
+ timeout_remove(&mbox->keep_lock_to);
+ }
+
+ if (mbox->flags_view != NULL)
+ mail_index_view_close(&mbox->flags_view);
+ if (mbox->keywords != NULL)
+ maildir_keywords_deinit(&mbox->keywords);
+ if (mbox->uidlist != NULL)
+ maildir_uidlist_deinit(&mbox->uidlist);
+ index_storage_mailbox_close(box);
+}
+
+static void maildir_notify_changes(struct mailbox *box)
+{
+ struct maildir_mailbox *mbox = MAILDIR_MAILBOX(box);
+ const char *box_path = mailbox_get_path(box);
+
+ if (box->notify_callback == NULL)
+ mailbox_watch_remove_all(&mbox->box);
+ else {
+ mailbox_watch_add(&mbox->box,
+ t_strconcat(box_path, "/new", NULL));
+ mailbox_watch_add(&mbox->box,
+ t_strconcat(box_path, "/cur", NULL));
+ }
+}
+
+static bool
+maildir_is_internal_name(struct mailbox_list *list ATTR_UNUSED,
+ const char *name)
+{
+ return strcmp(name, "cur") == 0 ||
+ strcmp(name, "new") == 0 ||
+ strcmp(name, "tmp") == 0;
+}
+
+static void maildir_storage_add_list(struct mail_storage *storage,
+ struct mailbox_list *list)
+{
+ struct maildir_mailbox_list_context *mlist;
+
+ mlist = p_new(list->pool, struct maildir_mailbox_list_context, 1);
+ mlist->module_ctx.super = list->v;
+ mlist->set = mail_namespace_get_driver_settings(list->ns, storage);
+
+ list->v.is_internal_name = maildir_is_internal_name;
+ MODULE_CONTEXT_SET(list, maildir_mailbox_list_module, mlist);
+}
+
+uint32_t maildir_get_uidvalidity_next(struct mailbox_list *list)
+{
+ const char *path;
+
+ path = mailbox_list_get_root_forced(list, MAILBOX_LIST_PATH_TYPE_CONTROL);
+ path = t_strconcat(path, "/"MAILDIR_UIDVALIDITY_FNAME, NULL);
+ return mailbox_uidvalidity_next(list, path);
+}
+
+static enum mail_flags maildir_get_private_flags_mask(struct mailbox *box)
+{
+ struct maildir_mailbox *mbox = MAILDIR_MAILBOX(box);
+ const char *path, *path2;
+ struct stat st;
+
+ if (mbox->private_flags_mask_set)
+ return mbox->_private_flags_mask;
+ mbox->private_flags_mask_set = TRUE;
+
+ path = mailbox_list_get_root_forced(box->list, MAILBOX_LIST_PATH_TYPE_MAILBOX);
+ if (box->list->set.index_pvt_dir != NULL) {
+ /* private index directory is set. we'll definitely have
+ private flags. */
+ mbox->_private_flags_mask = MAIL_SEEN;
+ } else if (!mailbox_list_get_root_path(box->list,
+ MAILBOX_LIST_PATH_TYPE_INDEX,
+ &path2) ||
+ strcmp(path, path2) == 0) {
+ /* no separate index directory. we can't have private flags,
+ so don't even bother checking if dovecot-shared exists */
+ } else {
+ path = t_strconcat(mailbox_get_path(box),
+ "/dovecot-shared", NULL);
+ if (stat(path, &st) == 0)
+ mbox->_private_flags_mask = MAIL_SEEN;
+ }
+ return mbox->_private_flags_mask;
+}
+
+bool maildir_is_backend_readonly(struct maildir_mailbox *mbox)
+{
+ if (!mbox->backend_readonly_set) {
+ const char *box_path = mailbox_get_path(&mbox->box);
+
+ mbox->backend_readonly_set = TRUE;
+ if (access(t_strconcat(box_path, "/cur", NULL), W_OK) < 0 &&
+ errno == EACCES)
+ mbox->backend_readonly = TRUE;
+ }
+ return mbox->backend_readonly;
+}
+
+struct mail_storage maildir_storage = {
+ .name = MAILDIR_STORAGE_NAME,
+ .class_flags = MAIL_STORAGE_CLASS_FLAG_FILE_PER_MSG |
+ MAIL_STORAGE_CLASS_FLAG_HAVE_MAIL_GUIDS |
+ MAIL_STORAGE_CLASS_FLAG_HAVE_MAIL_SAVE_GUIDS |
+ MAIL_STORAGE_CLASS_FLAG_BINARY_DATA,
+ .event_category = &event_category_maildir,
+
+ .v = {
+ maildir_get_setting_parser_info,
+ maildir_storage_alloc,
+ maildir_storage_create,
+ index_storage_destroy,
+ maildir_storage_add_list,
+ maildir_storage_get_list_settings,
+ maildir_storage_autodetect,
+ maildir_mailbox_alloc,
+ NULL,
+ mail_storage_list_index_rebuild,
+ }
+};
+
+struct mailbox maildir_mailbox = {
+ .v = {
+ maildir_storage_is_readonly,
+ index_storage_mailbox_enable,
+ maildir_mailbox_exists,
+ maildir_mailbox_open,
+ maildir_mailbox_close,
+ index_storage_mailbox_free,
+ maildir_mailbox_create,
+ maildir_mailbox_update,
+ index_storage_mailbox_delete,
+ index_storage_mailbox_rename,
+ index_storage_get_status,
+ maildir_mailbox_get_metadata,
+ index_storage_set_subscribed,
+ index_storage_attribute_set,
+ index_storage_attribute_get,
+ index_storage_attribute_iter_init,
+ index_storage_attribute_iter_next,
+ index_storage_attribute_iter_deinit,
+ maildir_list_index_has_changed,
+ maildir_list_index_update_sync,
+ maildir_storage_sync_init,
+ index_mailbox_sync_next,
+ index_mailbox_sync_deinit,
+ NULL,
+ maildir_notify_changes,
+ index_transaction_begin,
+ index_transaction_commit,
+ index_transaction_rollback,
+ maildir_get_private_flags_mask,
+ index_mail_alloc,
+ index_storage_search_init,
+ index_storage_search_deinit,
+ index_storage_search_next_nonblock,
+ index_storage_search_next_update_seq,
+ index_storage_search_next_match_mail,
+ maildir_save_alloc,
+ maildir_save_begin,
+ maildir_save_continue,
+ maildir_save_finish,
+ maildir_save_cancel,
+ maildir_copy,
+ maildir_transaction_save_commit_pre,
+ maildir_transaction_save_commit_post,
+ maildir_transaction_save_rollback,
+ index_storage_is_inconsistent
+ }
+};