maildir-storage.c revision 406a92a344f789e6ec763104b03de3f0b8fcfdb7
5f5870385cff47efd2f58e7892f251cf13761528Timo Sirainen/* Copyright (C) 2002 Timo Sirainen */
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen#include "lib.h"
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen#include "home-expand.h"
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen#include "mkdir-parents.h"
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen#include "unlink-directory.h"
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen#include "subscription-file/subscription-file.h"
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen#include "maildir-index.h"
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen#include "maildir-storage.h"
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen#include <stdio.h>
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen#include <stdlib.h>
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen#include <unistd.h>
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen#include <sys/stat.h>
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen#define CREATE_MODE 0770 /* umask() should limit it more */
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainenstruct rename_context {
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen int found;
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen size_t oldnamelen;
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen const char *newname;
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen};
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainenextern struct mail_storage maildir_storage;
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainenextern struct mailbox maildir_mailbox;
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainenstatic const char *maildirs[] = { "cur", "new", "tmp", NULL };
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainenstatic struct mail_storage *maildir_create(const char *data, const char *user)
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen{
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen struct mail_storage *storage;
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen const char *home, *path, *root_dir, *index_dir, *control_dir, *p;
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen root_dir = index_dir = control_dir = NULL;
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen if (data == NULL || *data == '\0') {
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen /* we'll need to figure out the maildir location ourself.
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen it's either root dir if we've already chroot()ed, or
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen $HOME/Maildir otherwise */
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen if (access("/cur", R_OK|W_OK|X_OK) == 0)
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen root_dir = "/";
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen else {
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen home = getenv("HOME");
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen if (home != NULL) {
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen path = t_strconcat(home, "/Maildir", NULL);
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen if (access(path, R_OK|W_OK|X_OK) == 0)
7631f16156aca373004953fe6b01a7f343fb47e0Timo Sirainen root_dir = path;
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen }
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen }
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen } else {
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen /* <Maildir> [:INDEX=<dir>] [:CONTROL=<dir>] */
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen p = strchr(data, ':');
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen if (p == NULL)
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen root_dir = data;
e6440616c02bb1404dc35debf45d9741260c7831Timo Sirainen else {
e6440616c02bb1404dc35debf45d9741260c7831Timo Sirainen root_dir = t_strdup_until(data, p);
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen do {
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen p++;
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen if (strncmp(p, "INDEX=", 6) == 0)
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen index_dir = t_strcut(p+6, ':');
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen else if (strncmp(p, "CONTROL=", 8) == 0)
e6440616c02bb1404dc35debf45d9741260c7831Timo Sirainen control_dir = t_strcut(p+8, ':');
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen p = strchr(p, ':');
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen } while (p != NULL);
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen }
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen }
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen if (root_dir == NULL)
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen return NULL;
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen if (index_dir == NULL)
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen index_dir = root_dir;
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen else if (strcmp(index_dir, "MEMORY") == 0)
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen index_dir = NULL;
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen storage = i_new(struct mail_storage, 1);
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen memcpy(storage, &maildir_storage, sizeof(struct mail_storage));
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen storage->dir = i_strdup(root_dir);
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen storage->index_dir = i_strdup(index_dir);
e6440616c02bb1404dc35debf45d9741260c7831Timo Sirainen storage->control_dir = i_strdup(control_dir);
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen storage->user = i_strdup(user);
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen storage->callbacks = i_new(struct mail_storage_callbacks, 1);
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen return storage;
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen}
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainenstatic void maildir_free(struct mail_storage *storage)
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen{
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen i_free(storage->dir);
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen i_free(storage->index_dir);
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen i_free(storage->control_dir);
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen i_free(storage->user);
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen i_free(storage->error);
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen i_free(storage->callbacks);
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen i_free(storage);
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen}
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainenstatic int maildir_autodetect(const char *data)
e6440616c02bb1404dc35debf45d9741260c7831Timo Sirainen{
e6440616c02bb1404dc35debf45d9741260c7831Timo Sirainen struct stat st;
e6440616c02bb1404dc35debf45d9741260c7831Timo Sirainen
e6440616c02bb1404dc35debf45d9741260c7831Timo Sirainen data = t_strcut(data, ':');
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen return stat(t_strconcat(data, "/cur", NULL), &st) == 0 &&
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen S_ISDIR(st.st_mode);
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen}
c3d9da3955043aef88c17b71f2081e894186aa6bTimo Sirainen
c3d9da3955043aef88c17b71f2081e894186aa6bTimo Sirainenstatic int maildir_is_valid_create_name(struct mail_storage *storage,
c3d9da3955043aef88c17b71f2081e894186aa6bTimo Sirainen const char *name)
c3d9da3955043aef88c17b71f2081e894186aa6bTimo Sirainen{
c3d9da3955043aef88c17b71f2081e894186aa6bTimo Sirainen if (name[0] == '\0' || name[strlen(name)-1] == storage->hierarchy_sep ||
c3d9da3955043aef88c17b71f2081e894186aa6bTimo Sirainen strchr(name, '*') != NULL || strchr(name, '%') != NULL)
c3d9da3955043aef88c17b71f2081e894186aa6bTimo Sirainen return FALSE;
c3d9da3955043aef88c17b71f2081e894186aa6bTimo Sirainen
e6440616c02bb1404dc35debf45d9741260c7831Timo Sirainen if (full_filesystem_access)
e6440616c02bb1404dc35debf45d9741260c7831Timo Sirainen return TRUE;
1f19649986397419d014febd1337c6eb7b530f26Timo Sirainen
1f19649986397419d014febd1337c6eb7b530f26Timo Sirainen return *name != '~' &&
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen strchr(name, '/') == NULL && strchr(name, '\\') == NULL;
de62ce819d59a529530da4b57be1b8d6dad13d6bTimo Sirainen}
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen
1f19649986397419d014febd1337c6eb7b530f26Timo Sirainenstatic int maildir_is_valid_existing_name(const char *name)
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen{
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen if (name[0] == '\0' || name[0] == '.')
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen return FALSE;
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen if (full_filesystem_access)
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen return TRUE;
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen return *name != '~' &&
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen strchr(name, '/') == NULL && strchr(name, '\\') == NULL;
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen}
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen
1f19649986397419d014febd1337c6eb7b530f26Timo Sirainenstatic const char *maildir_get_absolute_path(const char *name)
1f19649986397419d014febd1337c6eb7b530f26Timo Sirainen{
1f19649986397419d014febd1337c6eb7b530f26Timo Sirainen const char *p;
1f19649986397419d014febd1337c6eb7b530f26Timo Sirainen
1f19649986397419d014febd1337c6eb7b530f26Timo Sirainen name = home_expand(name);
b58aafbd21b365117538f73f306d22f75acd91f1Timo Sirainen
1f19649986397419d014febd1337c6eb7b530f26Timo Sirainen /* insert "/" if it's missing */
b58aafbd21b365117538f73f306d22f75acd91f1Timo Sirainen p = strchr(name, '.');
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen if (p == NULL || p[-1] == '/')
1f19649986397419d014febd1337c6eb7b530f26Timo Sirainen return name;
1f19649986397419d014febd1337c6eb7b530f26Timo Sirainen return t_strconcat(t_strdup_until(name, p), "/", p, NULL);
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen}
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainenconst char *maildir_get_path(struct mail_storage *storage, const char *name)
1f19649986397419d014febd1337c6eb7b530f26Timo Sirainen{
7cd7bd65aba6d84f4e4f5066d248437eaa4e5e54Timo Sirainen if (full_filesystem_access && (*name == '/' || *name == '~'))
a8bc64d2ec8babb5109fa23aa3c90383de61cd69Timo Sirainen return maildir_get_absolute_path(name);
1f19649986397419d014febd1337c6eb7b530f26Timo Sirainen
a8bc64d2ec8babb5109fa23aa3c90383de61cd69Timo Sirainen if (strcasecmp(name, "INBOX") == 0)
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen return storage->dir;
1f19649986397419d014febd1337c6eb7b530f26Timo Sirainen
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen return t_strconcat(storage->dir, "/.", name, NULL);
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen}
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen
de62ce819d59a529530da4b57be1b8d6dad13d6bTimo Sirainenstatic const char *maildir_get_index_path(struct mail_storage *storage,
de62ce819d59a529530da4b57be1b8d6dad13d6bTimo Sirainen const char *name)
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen{
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen if (storage->index_dir == NULL)
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen return NULL;
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen if (full_filesystem_access && (*name == '/' || *name == '~'))
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen return maildir_get_absolute_path(name);
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen return t_strconcat(storage->index_dir, "/.", name, NULL);
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen}
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainenstatic const char *maildir_get_control_path(struct mail_storage *storage,
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen const char *name)
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen{
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen if (storage->control_dir == NULL)
b9f564d00b7a115f465ffd6840341c7b8f9bfc8aTimo Sirainen return maildir_get_path(storage, name);
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen if (full_filesystem_access && (*name == '/' || *name == '~'))
1ac7c8e9040e0d0b7e9f849e45b94bfe919595a9Timo Sirainen return maildir_get_absolute_path(name);
return t_strconcat(storage->control_dir, "/.", name, NULL);
}
static int mkdir_verify(struct mail_storage *storage,
const char *dir, int verify)
{
struct stat st;
if (verify) {
if (lstat(dir, &st) == 0)
return TRUE;
if (errno != ENOENT) {
mail_storage_set_critical(storage,
"lstat(%s) failed: %m", dir);
return FALSE;
}
}
if (mkdir(dir, CREATE_MODE) < 0 && (errno != EEXIST || !verify)) {
if (errno != EEXIST && (!verify || errno != ENOENT)) {
mail_storage_set_critical(storage,
"mkdir(%s) failed: %m", dir);
}
return FALSE;
}
return TRUE;
}
/* create or fix maildir, ignore if it already exists */
static int create_maildir(struct mail_storage *storage,
const char *dir, int verify)
{
const char **tmp, *path;
if (!verify && !mkdir_verify(storage, dir, verify))
return FALSE;
for (tmp = maildirs; *tmp != NULL; tmp++) {
path = t_strconcat(dir, "/", *tmp, NULL);
if (!mkdir_verify(storage, path, verify)) {
if (!verify || errno != ENOENT)
return FALSE;
/* small optimization. if we're verifying, we don't
check that the root dir actually exists unless we
fail here. */
if (!mkdir_verify(storage, dir, verify))
return FALSE;
if (!mkdir_verify(storage, path, verify))
return FALSE;
}
}
return TRUE;
}
static int create_index_dir(struct mail_storage *storage, const char *name)
{
const char *dir;
if (storage->index_dir == NULL)
return TRUE;
if (strcmp(storage->index_dir, storage->dir) == 0)
return TRUE;
dir = t_strconcat(storage->index_dir, "/.", name, NULL);
if (mkdir_parents(dir, CREATE_MODE) == -1 && errno != EEXIST) {
mail_storage_set_critical(storage, "mkdir(%s) failed: %m", dir);
return FALSE;
}
return TRUE;
}
static int create_control_dir(struct mail_storage *storage, const char *name)
{
const char *dir;
if (storage->control_dir == NULL)
return TRUE;
dir = t_strconcat(storage->control_dir, "/.", name, NULL);
if (mkdir_parents(dir, CREATE_MODE) < 0 && errno != EEXIST) {
mail_storage_set_critical(storage, "mkdir(%s) failed: %m", dir);
return FALSE;
}
return TRUE;
}
static int verify_inbox(struct mail_storage *storage)
{
const char *inbox;
/* first make sure the cur/ new/ and tmp/ dirs exist in root dir */
if (!create_maildir(storage, storage->dir, TRUE))
return FALSE;
/* create the .INBOX directory */
inbox = t_strconcat(storage->dir, "/.INBOX", NULL);
if (!mkdir_verify(storage, inbox, TRUE))
return FALSE;
/* make sure the index directories exist */
return create_index_dir(storage, "INBOX") &&
create_control_dir(storage, "INBOX");
}
static struct mailbox *
maildir_open(struct mail_storage *storage, const char *name,
int readonly, int fast)
{
struct index_mailbox *ibox;
struct mail_index *index;
const char *path, *index_dir, *control_dir;
path = maildir_get_path(storage, name);
index_dir = maildir_get_index_path(storage, name);
control_dir = maildir_get_control_path(storage, name);
index = index_storage_lookup_ref(index_dir);
if (index == NULL) {
index = maildir_index_alloc(path, index_dir, control_dir);
index_storage_add(index);
}
ibox = index_storage_init(storage, &maildir_mailbox, index, name,
readonly, fast);
if (ibox != NULL)
ibox->expunge_locked = maildir_expunge_locked;
return (struct mailbox *) ibox;
}
static const char *inbox_fix_case(struct mail_storage *storage,
const char *name)
{
if (strncasecmp(name, "INBOX", 5) == 0 &&
(name[5] == '\0' || name[5] == storage->hierarchy_sep)) {
/* use same case with all INBOX folders or we'll get
into trouble */
name = t_strconcat("INBOX", name+5, NULL);
}
return name;
}
static struct mailbox *
maildir_open_mailbox(struct mail_storage *storage,
const char *name, int readonly, int fast)
{
const char *path;
struct stat st;
mail_storage_clear_error(storage);
name = inbox_fix_case(storage, name);
if (strcmp(name, "INBOX") == 0) {
if (!verify_inbox(storage))
return NULL;
return maildir_open(storage, "INBOX", readonly, fast);
}
if (!maildir_is_valid_existing_name(name)) {
mail_storage_set_error(storage, "Invalid mailbox name");
return FALSE;
}
path = maildir_get_path(storage, name);
if (stat(path, &st) == 0) {
/* exists - make sure the required directories are also there */
if (!create_maildir(storage, path, TRUE) ||
!create_index_dir(storage, name) ||
!create_control_dir(storage, name))
return FALSE;
return maildir_open(storage, name, readonly, fast);
} else if (errno == ENOENT) {
mail_storage_set_error(storage, "Mailbox doesn't exist: %s",
name);
return NULL;
} else {
mail_storage_set_critical(storage, "stat(%s) failed: %m", path);
return NULL;
}
}
static int maildir_create_mailbox(struct mail_storage *storage,
const char *name, int only_hierarchy)
{
const char *path;
mail_storage_clear_error(storage);
name = inbox_fix_case(storage, name);
if (!maildir_is_valid_create_name(storage, name)) {
mail_storage_set_error(storage, "Invalid mailbox name");
return FALSE;
}
if (only_hierarchy) {
/* no need to do anything */
return TRUE;
}
path = maildir_get_path(storage, name);
if (!create_maildir(storage, path, FALSE)) {
if (errno == EEXIST) {
mail_storage_set_error(storage,
"Mailbox already exists");
}
return FALSE;
}
return TRUE;
}
static int maildir_delete_mailbox(struct mail_storage *storage,
const char *name)
{
struct stat st;
const char *src, *dest, *index_dir;
int count;
mail_storage_clear_error(storage);
name = inbox_fix_case(storage, name);
if (strcasecmp(name, "INBOX") == 0) {
mail_storage_set_error(storage, "INBOX can't be deleted.");
return FALSE;
}
if (!maildir_is_valid_existing_name(name)) {
mail_storage_set_error(storage, "Invalid mailbox name");
return FALSE;
}
/* rename the .maildir into ..maildir which marks it as being
deleted. delete indexes before the actual maildir. this way we
never see partially deleted mailboxes. */
src = maildir_get_path(storage, name);
dest = maildir_get_path(storage, t_strconcat(".", name, NULL));
if (stat(src, &st) != 0 && errno == ENOENT) {
mail_storage_set_error(storage, "Mailbox doesn't exist: %s",
name);
return FALSE;
}
if (storage->index_dir != NULL && *name != '/' && *name != '~' &&
strcmp(storage->index_dir, storage->dir) != 0) {
index_dir = t_strconcat(storage->index_dir, "/.", name, NULL);
index_storage_destroy_unrefed();
/* it can fail with some NFS implementations if indexes are
opened by another session.. can't really help it. */
if (unlink_directory(index_dir, TRUE) < 0 &&
errno != ENOTEMPTY) {
mail_storage_set_critical(storage,
"unlink_directory(%s) failed: %m", index_dir);
return FALSE;
}
}
count = 0;
while (rename(src, dest) < 0 && count < 2) {
if (errno != EEXIST && errno != ENOTEMPTY) {
mail_storage_set_critical(storage,
"rename(%s, %s) failed: %m", src, dest);
return FALSE;
}
/* ..dir already existed? delete it and try again */
if (unlink_directory(dest, TRUE) < 0) {
mail_storage_set_critical(storage,
"unlink_directory(%s) failed: %m", dest);
return FALSE;
}
count++;
}
if (unlink_directory(dest, TRUE) < 0 && errno != ENOTEMPTY) {
mail_storage_set_critical(storage,
"unlink_directory(%s) failed: %m", dest);
/* it's already renamed to ..dir, which means it's deleted
as far as client is concerned. Report success. */
}
return TRUE;
}
static int rename_indexes(struct mail_storage *storage,
const char *oldname, const char *newname)
{
const char *oldpath, *newpath;
if (storage->index_dir == NULL ||
strcmp(storage->index_dir, storage->dir) == 0)
return TRUE;
/* Rename it's index. */
oldpath = t_strconcat(storage->index_dir, "/.", oldname, NULL);
newpath = t_strconcat(storage->index_dir, "/.", newname, NULL);
if (rename(oldpath, newpath) < 0 && errno != ENOENT) {
mail_storage_set_critical(storage, "rename(%s, %s) failed: %m",
oldpath, newpath);
return FALSE;
}
return TRUE;
}
static int rename_subfolders(struct mail_storage *storage,
const char *oldname, const char *newname)
{
struct mailbox_list_context *ctx;
struct mailbox_list *list;
const char *oldpath, *newpath, *new_listname;
size_t oldnamelen;
int sorted, ret;
ret = 0;
oldnamelen = strlen(oldname);
ctx = storage->list_mailbox_init(storage,
t_strconcat(oldname, ".*", NULL),
MAILBOX_LIST_FAST_FLAGS, &sorted);
while ((list = maildir_list_mailbox_next(ctx)) != NULL) {
i_assert(oldnamelen <= strlen(list->name));
t_push();
new_listname = t_strconcat(newname,
list->name + oldnamelen, NULL);
oldpath = maildir_get_path(storage, list->name);
newpath = maildir_get_path(storage, new_listname);
/* FIXME: it's possible to merge two folders if either one of
them doesn't have existing root folder. We could check this
but I'm not sure if it's worth it. It could be even
considered as a feature.
Anyway, the bug with merging is that if both folders have
identically named subfolder they conflict. Just ignore those
and leave them under the old folder. */
if (rename(oldpath, newpath) == 0 ||
errno == EEXIST || errno == ENOTEMPTY)
ret = 1;
else {
mail_storage_set_critical(storage,
"rename(%s, %s) failed: %m",
oldpath, newpath);
ret = -1;
t_pop();
break;
}
(void)rename_indexes(storage, list->name, new_listname);
t_pop();
}
if (!maildir_list_mailbox_deinit(ctx))
return -1;
return ret;
}
static int maildir_rename_mailbox(struct mail_storage *storage,
const char *oldname, const char *newname)
{
const char *oldpath, *newpath;
int ret, found;
mail_storage_clear_error(storage);
oldname = inbox_fix_case(storage, oldname);
if (!maildir_is_valid_existing_name(oldname) ||
!maildir_is_valid_create_name(storage, newname)) {
mail_storage_set_error(storage, "Invalid mailbox name");
return FALSE;
}
if (strcmp(oldname, "INBOX") == 0) {
mail_storage_set_error(storage,
"Renaming INBOX isn't supported.");
return FALSE;
}
/* NOTE: it's possible to rename a nonexisting folder which has
subfolders. In that case we should ignore the rename() error. */
oldpath = maildir_get_path(storage, oldname);
newpath = maildir_get_path(storage, newname);
ret = rename(oldpath, newpath);
if (ret == 0 || errno == ENOENT) {
(void)rename_indexes(storage, oldname, newname);
found = ret == 0;
ret = rename_subfolders(storage, oldname, newname);
if (ret < 0)
return FALSE;
if (!found && ret == 0) {
mail_storage_set_error(storage,
"Mailbox doesn't exist");
return FALSE;
}
return TRUE;
}
if (errno == EEXIST) {
mail_storage_set_error(storage,
"Target mailbox already exists");
return FALSE;
} else {
mail_storage_set_critical(storage, "rename(%s, %s) failed: %m",
oldpath, newpath);
return FALSE;
}
}
static int maildir_get_mailbox_name_status(struct mail_storage *storage,
const char *name,
enum mailbox_name_status *status)
{
struct stat st;
const char *path;
mail_storage_clear_error(storage);
name = inbox_fix_case(storage, name);
if (!maildir_is_valid_existing_name(name)) {
*status = MAILBOX_NAME_INVALID;
return TRUE;
}
path = maildir_get_path(storage, name);
if (stat(path, &st) == 0) {
*status = MAILBOX_NAME_EXISTS;
return TRUE;
}
if (!maildir_is_valid_create_name(storage, name)) {
*status = MAILBOX_NAME_INVALID;
return TRUE;
}
if (errno == ENOENT) {
*status = MAILBOX_NAME_VALID;
return TRUE;
} else {
mail_storage_set_critical(storage, "stat(%s) failed: %m", path);
return FALSE;
}
}
static int maildir_storage_close(struct mailbox *box)
{
struct index_mailbox *ibox = (struct index_mailbox *) box;
int failed = FALSE;
index_storage_init_lock_notify(ibox);
if (!maildir_try_flush_dirty_flags(ibox->index, TRUE)) {
mail_storage_set_index_error(ibox);
failed = TRUE;
}
ibox->index->set_lock_notify_callback(ibox->index, NULL, NULL);
return index_storage_close(box) && !failed;
}
static void maildir_storage_auto_sync(struct mailbox *box,
enum mailbox_sync_type sync_type,
unsigned int min_newmail_notify_interval)
{
struct index_mailbox *ibox = (struct index_mailbox *) box;
ibox->autosync_type = sync_type;
ibox->min_newmail_notify_interval = min_newmail_notify_interval;
index_mailbox_check_remove_all(ibox);
if (sync_type != MAILBOX_SYNC_NONE) {
index_mailbox_check_add(ibox,
t_strconcat(ibox->index->mailbox_path, "/new", NULL));
index_mailbox_check_add(ibox,
t_strconcat(ibox->index->mailbox_path, "/cur", NULL));
}
}
struct mail_storage maildir_storage = {
"maildir", /* name */
'.', /* hierarchy_sep - can't be changed */
maildir_create,
maildir_free,
maildir_autodetect,
index_storage_set_callbacks,
maildir_open_mailbox,
maildir_create_mailbox,
maildir_delete_mailbox,
maildir_rename_mailbox,
maildir_list_mailbox_init,
maildir_list_mailbox_deinit,
maildir_list_mailbox_next,
subsfile_set_subscribed,
maildir_get_mailbox_name_status,
mail_storage_get_last_error,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL, NULL, NULL,
0
};
struct mailbox maildir_mailbox = {
NULL, /* name */
NULL, /* storage */
maildir_storage_close,
index_storage_get_status,
index_storage_sync,
maildir_storage_auto_sync,
index_storage_expunge,
index_storage_update_flags,
maildir_storage_copy,
index_storage_fetch_init,
index_storage_fetch_deinit,
index_storage_fetch_next,
index_storage_fetch_uid,
index_storage_fetch_seq,
index_storage_search_get_sorting,
index_storage_search_init,
index_storage_search_deinit,
index_storage_search_next,
maildir_storage_save_init,
maildir_storage_save_deinit,
maildir_storage_save_next,
mail_storage_is_inconsistency_error,
FALSE,
FALSE,
FALSE
};