# HG changeset patch # User Timo Sirainen # Date 1194796007 -7200 # Node ID 186b164a9579f2724091194210801cd46028cbae # Parent 47c746a769ba2e65f701d984fd403628c4d3d42d Delay creating directories until we really need them. diff -r 47c746a769ba -r 186b164a9579 src/lib-storage/index/cydir/cydir-storage.c --- a/src/lib-storage/index/cydir/cydir-storage.c Sun Nov 11 17:43:56 2007 +0200 +++ b/src/lib-storage/index/cydir/cydir-storage.c Sun Nov 11 17:46:47 2007 +0200 @@ -150,9 +150,6 @@ path = mailbox_list_get_path(_storage->list, name, MAILBOX_LIST_PATH_TYPE_MAILBOX); - if (create_cydir(_storage, path) < 0) - return NULL; - index = index_storage_alloc(_storage, name, flags, CYDIR_INDEX_PREFIX); mail_index_set_fsync_types(index, MAIL_INDEX_SYNC_TYPE_APPEND | MAIL_INDEX_SYNC_TYPE_EXPUNGE); @@ -186,14 +183,17 @@ return NULL; } - if (strcmp(name, "INBOX") == 0) - return cydir_open(storage, "INBOX", flags); - path = mailbox_list_get_path(_storage->list, name, MAILBOX_LIST_PATH_TYPE_MAILBOX); - if (stat(path, &st) == 0) { + if (stat(path, &st) == 0) return cydir_open(storage, name, flags); - } else if (errno == ENOENT) { + else if (errno == ENOENT) { + if (strcmp(name, "INBOX") == 0) { + /* INBOX always exists, create it */ + if (create_cydir(_storage, path) < 0) + return NULL; + return cydir_open(storage, "INBOX", flags); + } mail_storage_set_error(_storage, MAIL_ERROR_NOTFOUND, T_MAIL_ERR_MAILBOX_NOT_FOUND(name)); return NULL; diff -r 47c746a769ba -r 186b164a9579 src/lib-storage/index/dbox/dbox-storage.c --- a/src/lib-storage/index/dbox/dbox-storage.c Sun Nov 11 17:43:56 2007 +0200 +++ b/src/lib-storage/index/dbox/dbox-storage.c Sun Nov 11 17:46:47 2007 +0200 @@ -169,8 +169,6 @@ path = mailbox_list_get_path(_storage->list, name, MAILBOX_LIST_PATH_TYPE_MAILBOX); - if (create_dbox(_storage, path) < 0) - return NULL; index = index_storage_alloc(_storage, name, flags, DBOX_INDEX_PREFIX); mail_index_set_fsync_types(index, MAIL_INDEX_SYNC_TYPE_APPEND | @@ -241,14 +239,18 @@ return NULL; } - if (strcmp(name, "INBOX") == 0) - return dbox_open(storage, "INBOX", flags); - path = mailbox_list_get_path(_storage->list, name, MAILBOX_LIST_PATH_TYPE_MAILBOX); - if (stat(path, &st) == 0) { + if (stat(path, &st) == 0) return dbox_open(storage, name, flags); - } else if (errno == ENOENT) { + else if (errno == ENOENT) { + if (strcmp(name, "INBOX") == 0) { + /* INBOX always exists, create it */ + if (create_dbox(_storage, path) < 0) + return NULL; + return dbox_open(storage, name, flags); + } + mail_storage_set_error(_storage, MAIL_ERROR_NOTFOUND, T_MAIL_ERR_MAILBOX_NOT_FOUND(name)); return NULL; diff -r 47c746a769ba -r 186b164a9579 src/lib-storage/index/index-storage.c --- a/src/lib-storage/index/index-storage.c Sun Nov 11 17:43:56 2007 +0200 +++ b/src/lib-storage/index/index-storage.c Sun Nov 11 17:46:47 2007 +0200 @@ -406,6 +406,7 @@ ibox->box.name = p_strdup(ibox->box.pool, name); if (ibox->box.file_create_mode == 0) { ibox->box.file_create_mode = 0600; + ibox->box.dir_create_mode = 0700; ibox->box.file_create_gid = (gid_t)-1; } diff -r 47c746a769ba -r 186b164a9579 src/lib-storage/index/maildir/maildir-keywords.c --- a/src/lib-storage/index/maildir/maildir-keywords.c Sun Nov 11 17:43:56 2007 +0200 +++ b/src/lib-storage/index/maildir/maildir-keywords.c Sun Nov 11 17:46:47 2007 +0200 @@ -342,26 +342,36 @@ struct dotlock *dotlock; const char *lock_path; mode_t old_mask; - int fd; + int i, fd; mk->synced = FALSE; if (!mk->changed || mk->mbox == NULL) return 0; - /* we could just create the temp file directly, but doing it this - ways avoids potential problems with overwriting contents in - malicious symlinks */ lock_path = t_strconcat(mk->path, ".lock", NULL); (void)unlink(lock_path); - old_mask = umask(0777 & ~mk->mbox->ibox.box.file_create_mode); - fd = file_dotlock_open(&mk->dotlock_settings, mk->path, - DOTLOCK_CREATE_FLAG_NONBLOCK, &dotlock); - umask(old_mask); - if (fd == -1) { - mail_storage_set_critical(mk->storage, - "file_dotlock_open(%s) failed: %m", mk->path); - return -1; + + for (i = 0;; i++) { + /* we could just create the temp file directly, but doing it + this ways avoids potential problems with overwriting + contents in malicious symlinks */ + old_mask = umask(0777 & ~mk->mbox->ibox.box.file_create_mode); + fd = file_dotlock_open(&mk->dotlock_settings, mk->path, + DOTLOCK_CREATE_FLAG_NONBLOCK, &dotlock); + umask(old_mask); + if (fd != -1) + break; + + if (errno != ENOENT || i == MAILDIR_DELETE_RETRY_COUNT) { + mail_storage_set_critical(mk->storage, + "file_dotlock_open(%s) failed: %m", mk->path); + return -1; + } + /* the control dir doesn't exist. create it unless the whole + mailbox was just deleted. */ + if (maildir_set_deleted(mk->mbox)) + return -1; } if (maildir_keywords_write_fd(mk, lock_path, fd) < 0) { diff -r 47c746a769ba -r 186b164a9579 src/lib-storage/index/maildir/maildir-storage.c --- a/src/lib-storage/index/maildir/maildir-storage.c Sun Nov 11 17:43:56 2007 +0200 +++ b/src/lib-storage/index/maildir/maildir-storage.c Sun Nov 11 17:46:47 2007 +0200 @@ -41,7 +41,6 @@ &mailbox_list_module_register); static const char *maildir_subdirs[] = { "cur", "new", "tmp" }; -static int verify_inbox(struct mail_storage *storage); static int maildir_list_delete_mailbox(struct mailbox_list *list, const char *name); static int @@ -249,9 +248,6 @@ p_strconcat(_storage->pool, "tmp/", storage->temp_prefix, NULL); } - - if ((_storage->ns->flags & NAMESPACE_FLAG_INBOX) != 0) - (void)verify_inbox(_storage); return 0; } @@ -312,69 +308,54 @@ return -1; } -/* create or fix maildir, ignore if it already exists */ -static int create_maildir(struct mail_storage *storage, - const char *dir, bool verify) +static int maildir_check_tmp(struct mail_storage *storage, const char *dir) { const char *path; struct stat st; - if (mkdir_verify(storage, t_strconcat(dir, "/cur", NULL), verify) < 0) - return -1; - if (mkdir_verify(storage, t_strconcat(dir, "/new", NULL), verify) < 0) - return -1; - /* 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 (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 - MAILDIR_TMP_SCAN_SECS) { - /* time to scan */ - (void)maildir_tmp_cleanup(storage, path); - } - } else if (errno == ENOENT) { - if (mkdir_verify(storage, path, verify) < 0) - return -1; - } else { + if (stat(path, &st) < 0) { + if (errno == ENOENT) + return 0; mail_storage_set_critical(storage, "stat(%s) failed: %m", path); return -1; } - return 0; + 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 - MAILDIR_TMP_SCAN_SECS) { + /* time to scan */ + (void)maildir_tmp_cleanup(storage, path); + } + return 1; } -static int create_control_dir(struct mail_storage *storage, const char *name) +/* create or fix maildir, ignore if it already exists */ +static int create_maildir(struct mail_storage *storage, + const char *dir, bool verify) { - const char *control_dir, *root_dir; + int ret; - control_dir = mailbox_list_get_path(storage->list, name, - MAILBOX_LIST_PATH_TYPE_CONTROL); - root_dir = mailbox_list_get_path(storage->list, name, - MAILBOX_LIST_PATH_TYPE_MAILBOX); - if (strcmp(control_dir, root_dir) == 0) - return 0; - - if (mkdir_parents(control_dir, CREATE_MODE) < 0 && errno != EEXIST) { - mail_storage_set_critical(storage, - "mkdir(%s) failed: %m", control_dir); + ret = maildir_check_tmp(storage, dir); + if (ret > 0) { + if (!verify) { + mail_storage_set_error(storage, MAIL_ERROR_NOTPOSSIBLE, + "Mailbox already exists"); + return -1; + } + return 1; + } + if (ret < 0) return -1; - } - return 0; -} - -static int verify_inbox(struct mail_storage *storage) -{ - const char *path; - - path = mailbox_list_get_path(storage->list, "INBOX", - MAILBOX_LIST_PATH_TYPE_MAILBOX); - if (create_maildir(storage, path, TRUE) < 0) + /* doesn't exist, create */ + if (mkdir_verify(storage, t_strconcat(dir, "/cur", NULL), verify) < 0) return -1; - if (create_control_dir(storage, "INBOX") < 0) + if (mkdir_verify(storage, t_strconcat(dir, "/new", NULL), verify) < 0) + return -1; + if (mkdir_verify(storage, t_strconcat(dir, "/tmp", NULL), verify) < 0) return -1; return 0; } @@ -384,6 +365,15 @@ (void)maildir_uidlist_lock_touch(mbox->uidlist); } +static mode_t get_dir_mode(mode_t mode) +{ + /* add the execute bit if either read or write bit is set */ + if ((mode & 0600) != 0) mode |= 0100; + if ((mode & 0060) != 0) mode |= 0010; + if ((mode & 0006) != 0) mode |= 0001; + return mode; +} + static struct mailbox * maildir_open(struct maildir_storage *storage, const char *name, enum mailbox_open_flags flags) @@ -409,7 +399,6 @@ mbox->storage = storage; mbox->path = p_strdup(pool, path); - mbox->control_dir = p_strdup(pool, control_dir); index = index_storage_alloc(&storage->storage, name, flags, MAILDIR_INDEX_PREFIX); @@ -425,6 +414,8 @@ mail_index_set_permissions(index, st.st_mode & 0666, st.st_gid); mbox->ibox.box.file_create_mode = st.st_mode & 0666; + mbox->ibox.box.dir_create_mode = + get_dir_mode(st.st_mode & 0666); mbox->ibox.box.file_create_gid = st.st_gid; mbox->ibox.box.private_flags_mask = MAIL_SEEN; } @@ -464,6 +455,7 @@ struct maildir_storage *storage = (struct maildir_storage *)_storage; const char *path; struct stat st; + int ret; if (input != NULL) { mail_storage_set_critical(_storage, @@ -471,19 +463,31 @@ return NULL; } + path = mailbox_list_get_path(_storage->list, name, + MAILBOX_LIST_PATH_TYPE_MAILBOX); + if (strcmp(name, "INBOX") == 0 && (_storage->ns->flags & NAMESPACE_FLAG_INBOX) != 0) { - if (verify_inbox(_storage) < 0) + /* INBOX always exists */ + if (create_maildir(_storage, path, TRUE) < 0) return NULL; return maildir_open(storage, "INBOX", flags); } - path = mailbox_list_get_path(_storage->list, name, - MAILBOX_LIST_PATH_TYPE_MAILBOX); + /* begin by checking if tmp/ directory exists and if it should be + cleaned up. */ + ret = maildir_check_tmp(_storage, path); + if (ret > 0) { + /* exists */ + return maildir_open(storage, name, flags); + } + if (ret < 0) + return NULL; + + /* tmp/ directory doesn't exist. does the maildir? */ if (stat(path, &st) == 0) { - /* exists - make sure the required directories are also there */ - if (create_maildir(_storage, path, TRUE) < 0 || - create_control_dir(_storage, name) < 0) + /* yes, we'll need to create the missing dirs */ + if (create_maildir(_storage, path, TRUE) < 0) return NULL; return maildir_open(storage, name, flags); diff -r 47c746a769ba -r 186b164a9579 src/lib-storage/index/maildir/maildir-storage.h --- a/src/lib-storage/index/maildir/maildir-storage.h Sun Nov 11 17:43:56 2007 +0200 +++ b/src/lib-storage/index/maildir/maildir-storage.h Sun Nov 11 17:46:47 2007 +0200 @@ -42,6 +42,11 @@ #define MAILDIR_SAVE_FLAG_HARDLINK 0x10000000 #define MAILDIR_SAVE_FLAG_DELETED 0x20000000 +/* If an operation fails with ENOENT, we'll check if the mailbox is deleted + or if some directory is just missing. If it's missing, we'll create the + directories and try again this many times before failing. */ +#define MAILDIR_DELETE_RETRY_COUNT 3 + #include "index-storage.h" #include "mailbox-list-private.h" @@ -76,7 +81,7 @@ struct index_mailbox ibox; struct maildir_storage *storage; - const char *path, *control_dir; + const char *path; struct timeout *keep_lock_to; /* maildir sync: */ @@ -116,6 +121,7 @@ #endif void maildir_tmp_cleanup(struct mail_storage *storage, const char *dir); +bool maildir_set_deleted(struct maildir_mailbox *mbox); void maildir_transaction_class_init(void); void maildir_transaction_class_deinit(void); diff -r 47c746a769ba -r 186b164a9579 src/lib-storage/index/maildir/maildir-sync.c --- a/src/lib-storage/index/maildir/maildir-sync.c Sun Nov 11 17:43:56 2007 +0200 +++ b/src/lib-storage/index/maildir/maildir-sync.c Sun Nov 11 17:46:47 2007 +0200 @@ -338,12 +338,17 @@ static int maildir_stat(struct maildir_mailbox *mbox, const char *path, struct stat *st_r) { - if (nfs_safe_stat(path, st_r) == 0) - return 0; - if (errno == ENOENT) { - /* if mailbox gets deleted under us, don't log an error */ - mailbox_set_deleted(&mbox->ibox.box); - return -1; + int i; + + for (i = 0;; i++) { + if (nfs_safe_stat(path, st_r) == 0) + return 0; + if (errno != ENOENT || i == MAILDIR_DELETE_RETRY_COUNT) + break; + + if (maildir_set_deleted(mbox)) + return -1; + /* try again */ } mail_storage_set_critical(mbox->ibox.box.storage, @@ -366,15 +371,20 @@ bool move_new, check_touch, dir_changed = FALSE; path = new_dir ? ctx->new_dir : ctx->cur_dir; - dirp = opendir(path); - if (dirp == NULL) { - if (errno == ENOENT) { - mailbox_set_deleted(&ctx->mbox->ibox.box); + for (i = 0;; i++) { + dirp = opendir(path); + if (dirp != NULL) + break; + + if (errno != ENOENT || i == MAILDIR_DELETE_RETRY_COUNT) { + mail_storage_set_critical(storage, + "opendir(%s) failed: %m", path); return -1; } - mail_storage_set_critical(storage, - "opendir(%s) failed: %m", path); - return -1; + + if (maildir_set_deleted(ctx->mbox)) + return -1; + /* try again */ } #ifdef HAVE_DIRFD diff -r 47c746a769ba -r 186b164a9579 src/lib-storage/index/maildir/maildir-uidlist.c --- a/src/lib-storage/index/maildir/maildir-uidlist.c Sun Nov 11 17:43:56 2007 +0200 +++ b/src/lib-storage/index/maildir/maildir-uidlist.c Sun Nov 11 17:46:47 2007 +0200 @@ -127,7 +127,9 @@ struct mailbox *box = &uidlist->ibox->box; const char *control_dir, *path; mode_t old_mask; - int ret; + const enum dotlock_create_flags dotlock_flags = + nonblock ? DOTLOCK_CREATE_FLAG_NONBLOCK : 0; + int i, ret; if (uidlist->lock_count > 0) { uidlist->lock_count++; @@ -136,22 +138,32 @@ control_dir = mailbox_list_get_path(box->storage->list, box->name, MAILBOX_LIST_PATH_TYPE_CONTROL); + path = t_strconcat(control_dir, "/" MAILDIR_UIDLIST_NAME, NULL); - path = t_strconcat(control_dir, "/" MAILDIR_UIDLIST_NAME, NULL); - old_mask = umask(0777 & ~box->file_create_mode); - ret = file_dotlock_create(&uidlist->dotlock_settings, path, - nonblock ? DOTLOCK_CREATE_FLAG_NONBLOCK : 0, - &uidlist->dotlock); - umask(old_mask); - if (ret <= 0) { + for (i = 0;; i++) { + old_mask = umask(0777 & ~box->file_create_mode); + ret = file_dotlock_create(&uidlist->dotlock_settings, path, + dotlock_flags, &uidlist->dotlock); + umask(old_mask); + if (ret > 0) + break; + + /* failure */ if (ret == 0) { mail_storage_set_error(box->storage, MAIL_ERROR_TEMP, MAIL_ERRSTR_LOCK_TIMEOUT); return 0; } - mail_storage_set_critical(box->storage, - "file_dotlock_open(%s) failed: %m", path); - return -1; + if (errno != ENOENT || i == MAILDIR_DELETE_RETRY_COUNT || + uidlist->mbox == NULL) { + mail_storage_set_critical(box->storage, + "file_dotlock_create(%s) failed: %m", path); + return -1; + } + /* the control dir doesn't exist. create it unless the whole + mailbox was just deleted. */ + if (maildir_set_deleted(uidlist->mbox)) + return -1; } uidlist->lock_count++; @@ -898,21 +910,30 @@ struct stat st; mode_t old_mask; uoff_t file_size; - int fd, ret; + int i, fd, ret; control_dir = mailbox_list_get_path(box->storage->list, box->name, MAILBOX_LIST_PATH_TYPE_CONTROL); temp_path = t_strconcat(control_dir, "/" MAILDIR_UIDLIST_NAME ".tmp", NULL); - old_mask = umask(0777 & ~box->file_create_mode); - fd = open(temp_path, O_RDWR | O_CREAT | O_TRUNC, 0777); - umask(old_mask); + for (i = 0;; i++) { + old_mask = umask(0777 & ~box->file_create_mode); + fd = open(temp_path, O_RDWR | O_CREAT | O_TRUNC, 0777); + umask(old_mask); + if (fd != -1) + break; - if (fd == -1) { - mail_storage_set_critical(box->storage, - "open(%s, O_CREAT) failed: %m", temp_path); - return -1; + if (errno != ENOENT || i == MAILDIR_DELETE_RETRY_COUNT || + uidlist->mbox == NULL) { + mail_storage_set_critical(box->storage, + "open(%s, O_CREAT) failed: %m", temp_path); + return -1; + } + /* the control dir doesn't exist. create it unless the whole + mailbox was just deleted. */ + if (maildir_set_deleted(uidlist->mbox)) + return -1; } if (box->file_create_gid != (gid_t)-1) { diff -r 47c746a769ba -r 186b164a9579 src/lib-storage/index/maildir/maildir-util.c --- a/src/lib-storage/index/maildir/maildir-util.c Sun Nov 11 17:43:56 2007 +0200 +++ b/src/lib-storage/index/maildir/maildir-util.c Sun Nov 11 17:46:47 2007 +0200 @@ -3,6 +3,7 @@ #include "lib.h" #include "ioloop.h" #include "str.h" +#include "mkdir-parents.h" #include "maildir-storage.h" #include "maildir-uidlist.h" #include "maildir-sync.h" @@ -152,3 +153,66 @@ "closedir(%s) failed: %m", dir); } } + +static int maildir_create_subdirs(struct maildir_mailbox *mbox) +{ + static const char *subdirs[] = { "cur", "new", "tmp" }; + const char *dirs[N_ELEMENTS(subdirs) + 2]; + struct mailbox *box = &mbox->ibox.box; + struct stat st; + const char *path; + unsigned int i; + + t_push(); + /* @UNSAFE: get a list of directories we want to create */ + for (i = 0; i < N_ELEMENTS(subdirs); i++) + dirs[i] = t_strconcat(mbox->path, "/", subdirs[i], NULL); + dirs[i++] = mail_storage_get_mailbox_control_dir(box->storage, + box->name); + dirs[i++] = mail_storage_get_mailbox_index_dir(box->storage, + box->name); + i_assert(i == N_ELEMENTS(dirs)); + + for (i = 0; i < N_ELEMENTS(dirs); i++) { + path = dirs[i]; + if (path == NULL || stat(path, &st) == 0) + continue; + if (errno != ENOENT) { + mail_storage_set_critical(box->storage, + "stat(%s) failed: %m", path); + break; + } + if (mkdir_parents(path, box->dir_create_mode) < 0 && + errno != EEXIST) { + if (errno == ENOENT) { + /* mailbox was being deleted just now */ + mailbox_set_deleted(box); + break; + } + mail_storage_set_critical(box->storage, + "mkdir(%s) failed: %m", path); + break; + } + } + t_pop(); + return i == N_ELEMENTS(dirs) ? 0 : -1; +} + +bool maildir_set_deleted(struct maildir_mailbox *mbox) +{ + struct mailbox *box = &mbox->ibox.box; + struct stat st; + + if (stat(mbox->path, &st) < 0) { + if (errno == ENOENT) + mailbox_set_deleted(box); + else { + mail_storage_set_critical(box->storage, + "stat(%s) failed: %m", mbox->path); + } + return TRUE; + } + /* maildir itself exists. create all of its subdirectories in case + they got lost. */ + return maildir_create_subdirs(mbox) < 0 ? TRUE : FALSE; +} diff -r 47c746a769ba -r 186b164a9579 src/lib-storage/mail-storage-private.h --- a/src/lib-storage/mail-storage-private.h Sun Nov 11 17:43:56 2007 +0200 +++ b/src/lib-storage/mail-storage-private.h Sun Nov 11 17:46:47 2007 +0200 @@ -181,8 +181,8 @@ /* User's private flags if this is a shared mailbox */ enum mail_flags private_flags_mask; - /* mode and GID to use for newly created files */ - mode_t file_create_mode; + /* mode and GID to use for newly created files/dirs */ + mode_t file_create_mode, dir_create_mode; gid_t file_create_gid; /* Mailbox notification settings: */ diff -r 47c746a769ba -r 186b164a9579 src/lib-storage/mailbox-list.c --- a/src/lib-storage/mailbox-list.c Sun Nov 11 17:43:56 2007 +0200 +++ b/src/lib-storage/mailbox-list.c Sun Nov 11 17:46:47 2007 +0200 @@ -4,7 +4,6 @@ #include "array.h" #include "ioloop.h" #include "home-expand.h" -#include "mkdir-parents.h" #include "unlink-directory.h" #include "imap-match.h" #include "mailbox-tree.h" @@ -182,18 +181,6 @@ strcmp(set->control_dir, set->root_dir) == 0 ? NULL : p_strdup(list->pool, set->control_dir); - if (list->set.index_dir != NULL && - *list->set.index_dir != '\0') { - if (mkdir_parents(list->set.index_dir, 0700) < 0 && - errno != EEXIST) - i_error("mkdir(%s) failed: %m", list->set.index_dir); - } - if (list->set.control_dir != NULL) { - if (mkdir_parents(list->set.control_dir, 0700) < 0 && - errno != EEXIST) - i_error("mkdir(%s) failed: %m", list->set.control_dir); - } - list->set.inbox_path = p_strdup(list->pool, set->inbox_path); list->set.subscription_fname = p_strdup(list->pool, set->subscription_fname);