view src/lib-storage/index/dbox/dbox-storage.c @ 5448:beabd433cdae HEAD

Moved delete/rename operations to mailbox_list API. Fixed mbox/maildir to work with either fs/maildir++ directory layout. They can be changed by appending :LAYOUT=fs|maildir++ to mail_location.
author Timo Sirainen <tss@iki.fi>
date Thu, 29 Mar 2007 10:59:11 +0300
parents 840d177c468f
children 7a6db5ec047d
line wrap: on
line source

/* Copyright (C) 2005-2007 Timo Sirainen */

#include "lib.h"
#include "array.h"
#include "ioloop.h"
#include "mkdir-parents.h"
#include "unlink-directory.h"
#include "index-mail.h"
#include "mail-copy.h"
#include "dbox-uidlist.h"
#include "dbox-sync.h"
#include "dbox-file.h"
#include "dbox-storage.h"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>

#define CREATE_MODE 0770 /* umask() should limit it more */

/* How often to touch the uidlist lock file when using KEEP_LOCKED flag */
#define DBOX_LOCK_TOUCH_MSECS (10*1000)

#define DBOX_LIST_CONTEXT(obj) \
	*((void **)array_idx_modifiable(&(obj)->module_contexts, \
					dbox_mailbox_list_module_id))

const struct dotlock_settings default_uidlist_dotlock_set = {
	MEMBER(temp_prefix) NULL,
	MEMBER(lock_suffix) NULL,

	MEMBER(timeout) 120,
	MEMBER(stale_timeout) 60,

	MEMBER(callback) NULL,
	MEMBER(context) NULL,

	MEMBER(use_excl_lock) FALSE
};

const struct dotlock_settings default_file_dotlock_set = {
	MEMBER(temp_prefix) NULL,
	MEMBER(lock_suffix) NULL,

	MEMBER(timeout) 120,
	MEMBER(stale_timeout) 60,

	MEMBER(callback) NULL,
	MEMBER(context) NULL,

	MEMBER(use_excl_lock) FALSE
};

static const struct dotlock_settings default_new_file_dotlock_set = {
	MEMBER(temp_prefix) NULL,
	MEMBER(lock_suffix) NULL,

	MEMBER(timeout) 60,
	MEMBER(stale_timeout) 30,

	MEMBER(callback) NULL,
	MEMBER(context) NULL,

	MEMBER(use_excl_lock) FALSE
};

extern struct mail_storage dbox_storage;
extern struct mailbox dbox_mailbox;

static unsigned int dbox_mailbox_list_module_id = 0;

static int
dbox_list_delete_mailbox(struct mailbox_list *list, const char *name);
static int dbox_list_iter_is_mailbox(struct mailbox_list_iterate_context *ctx,
				     const char *dir, const char *fname,
				     enum mailbox_list_file_type type,
				     enum mailbox_info_flags *flags);

static bool
dbox_storage_is_valid_existing_name(struct mailbox_list *list, const char *name)
{
	struct dbox_storage *storage = DBOX_LIST_CONTEXT(list);
	const char *p;

	if (!storage->list_super.is_valid_existing_name(list, name))
		return FALSE;

	/* Don't allow the mailbox name to end in dbox-Mails */
	p = strrchr(name, '/');
	if (p != NULL)
		name = p + 1;
	return strcmp(name, DBOX_MAILDIR_NAME) != 0;
}

static bool
dbox_storage_is_valid_create_name(struct mailbox_list *list, const char *name)
{
	struct dbox_storage *storage = DBOX_LIST_CONTEXT(list);
	const char *const *tmp;
	bool ret = TRUE;

	if (!storage->list_super.is_valid_create_name(list, name))
		return FALSE;

	/* Don't allow creating mailboxes under dbox-Mails */
	t_push();
	for (tmp = t_strsplit(name, "/"); *tmp != NULL; tmp++) {
		if (strcmp(*tmp, DBOX_MAILDIR_NAME) == 0) {
			ret = FALSE;
			break;
		}
	}
	t_pop();
	return ret;
}

static int
dbox_get_list_settings(struct mailbox_list_settings *list_set,
		       const char *data, enum mail_storage_flags flags)
{
	bool debug = (flags & MAIL_STORAGE_FLAG_DEBUG) != 0;
	const char *p;
	size_t len;

	memset(list_set, 0, sizeof(*list_set));
	list_set->subscription_fname = DBOX_SUBSCRIPTION_FILE_NAME;
	list_set->maildir_name = DBOX_MAILDIR_NAME;

	if (data == NULL || *data == '\0') {
		/* we won't do any guessing for this format. */
		if (debug)
			i_info("dbox: mailbox location not given");
		return -1;
	}

	/* <root dir> [:INDEX=<dir>] */
	if (debug)
		i_info("dbox: data=%s", data);
	p = strchr(data, ':');
	if (p == NULL)
		list_set->root_dir = data;
	else {
		list_set->root_dir = t_strdup_until(data, p);

		do {
			p++;
			if (strncmp(p, "INDEX=", 6) == 0)
				list_set->index_dir = t_strcut(p+6, ':');
			p = strchr(p, ':');
		} while (p != NULL);
	}

	/* strip trailing '/' */
	len = strlen(list_set->root_dir);
	if (list_set->root_dir[len-1] == '/')
		list_set->root_dir = t_strndup(list_set->root_dir, len-1);

	if (list_set->index_dir != NULL &&
	    strcmp(list_set->index_dir, "MEMORY") == 0)
		list_set->index_dir = "";
	return 0;
}

static struct mail_storage *
dbox_create(const char *data, const char *user,
	    enum mail_storage_flags flags,
	    enum file_lock_method lock_method)
{
	struct dbox_storage *storage;
	struct index_storage *istorage;
	struct mailbox_list_settings list_set;
	struct mailbox_list *list;
	const char *error;
	struct stat st;
	pool_t pool;

	if (dbox_get_list_settings(&list_set, data, flags) < 0)
		return NULL;
	list_set.mail_storage_flags = &flags;
	list_set.lock_method = &lock_method;

	if ((flags & MAIL_STORAGE_FLAG_NO_AUTOCREATE) != 0) {
		if (stat(list_set.root_dir, &st) < 0) {
			if (errno != ENOENT) {
				i_error("stat(%s) failed: %m",
					list_set.root_dir);
			}
			return NULL;
		}
	}

	if (mkdir_parents(list_set.root_dir, CREATE_MODE) < 0 &&
	    errno != EEXIST) {
		i_error("mkdir_parents(%s) failed: %m", list_set.root_dir);
		return NULL;
	}

	pool = pool_alloconly_create("storage", 512+256);
	storage = p_new(pool, struct dbox_storage, 1);

	if (mailbox_list_init("fs", &list_set,
			      mail_storage_get_list_flags(flags),
			      &list, &error) < 0) {
		i_error("dbox fs: %s", error);
		pool_unref(pool);
		return NULL;
	}
	storage->list_super = list->v;
	list->v.is_valid_existing_name = dbox_storage_is_valid_existing_name;
	list->v.is_valid_create_name = dbox_storage_is_valid_create_name;
	list->v.iter_is_mailbox = dbox_list_iter_is_mailbox;
	list->v.delete_mailbox = dbox_list_delete_mailbox;

	array_idx_set(&list->module_contexts,
		      dbox_mailbox_list_module_id, &storage);

	storage->uidlist_dotlock_set = default_uidlist_dotlock_set;
	storage->file_dotlock_set = default_file_dotlock_set;
	storage->new_file_dotlock_set = default_new_file_dotlock_set;
	if ((flags & MAIL_STORAGE_FLAG_DOTLOCK_USE_EXCL) != 0) {
		storage->uidlist_dotlock_set.use_excl_lock = TRUE;
		storage->file_dotlock_set.use_excl_lock = TRUE;
		storage->new_file_dotlock_set.use_excl_lock = TRUE;
	}

	istorage = INDEX_STORAGE(storage);
	istorage->storage = dbox_storage;
	istorage->storage.pool = pool;

	istorage->user = p_strdup(pool, user);
	index_storage_init(istorage, list, flags, lock_method);

	return STORAGE(storage);
}

static void dbox_free(struct mail_storage *_storage)
{
	struct index_storage *storage = (struct index_storage *) _storage;

	index_storage_deinit(storage);
	pool_unref(storage->storage.pool);
}

static bool dbox_autodetect(const char *data, enum mail_storage_flags flags)
{
	bool debug = (flags & MAIL_STORAGE_FLAG_DEBUG) != 0;
	struct stat st;
	const char *path;

	data = t_strcut(data, ':');

	path = t_strconcat(data, "/INBOX/"DBOX_MAILDIR_NAME, NULL);
	if (stat(path, &st) < 0) {
		if (debug)
			i_info("dbox autodetect: stat(%s) failed: %m", path);
		return FALSE;
	}

	if (!S_ISDIR(st.st_mode)) {
		if (debug)
			i_info("dbox autodetect: %s not a directory", path);
		return FALSE;
	}
	return TRUE;
}

static int create_dbox(struct mail_storage *storage, const char *path)
{
	const char *error;

	if (mkdir_parents(path, CREATE_MODE) < 0 && errno != EEXIST) {
		if (mail_storage_errno2str(&error)) {
			mail_storage_set_error(storage, "%s", error);
			return -1;
		}

		mail_storage_set_critical(storage, "mkdir(%s) failed: %m",
					  path);
		return -1;
	}
	return 0;
}

static int create_index_dir(struct mail_storage *storage, const char *name)
{
	const char *root_dir, *index_dir;

	root_dir = mailbox_list_get_path(storage->list, name,
					 MAILBOX_LIST_PATH_TYPE_MAILBOX);
	index_dir = mailbox_list_get_path(storage->list, name,
					  MAILBOX_LIST_PATH_TYPE_INDEX);
	if (strcmp(index_dir, root_dir) == 0)
		return 0;

	if (mkdir_parents(index_dir, CREATE_MODE) < 0 && errno != EEXIST) {
		mail_storage_set_critical(storage, "mkdir(%s) failed: %m",
					  index_dir);
		return -1;
	}

	return 0;
}

static bool dbox_is_recent(struct index_mailbox *ibox __attr_unused__,
			   uint32_t uid __attr_unused__)
{
	return FALSE;
}

static void dbox_lock_touch_timeout(struct dbox_mailbox *mbox)
{
	(void)dbox_uidlist_lock_touch(mbox->uidlist);
}

static struct mailbox *
dbox_open(struct dbox_storage *storage, const char *name,
	  enum mailbox_open_flags flags)
{
	struct index_storage *istorage = INDEX_STORAGE(storage);
	struct mail_storage *_storage = STORAGE(storage);
	struct dbox_mailbox *mbox;
	struct mail_index *index;
	const char *path, *index_dir, *value;
	pool_t pool;

	path = mailbox_list_get_path(_storage->list, name,
				     MAILBOX_LIST_PATH_TYPE_MAILBOX);
	index_dir = mailbox_list_get_path(_storage->list, name,
					  MAILBOX_LIST_PATH_TYPE_INDEX);

	if (create_dbox(_storage, path) < 0)
		return NULL;
	if (create_index_dir(_storage, name) < 0)
		return NULL;

	index = index_storage_alloc(index_dir, path, DBOX_INDEX_PREFIX);

	pool = pool_alloconly_create("dbox mailbox", 1024+512);
	mbox = p_new(pool, struct dbox_mailbox, 1);
	mbox->ibox.box = dbox_mailbox;
	mbox->ibox.box.pool = pool;
	mbox->ibox.storage = istorage;
	mbox->ibox.mail_vfuncs = &dbox_mail_vfuncs;
	mbox->ibox.is_recent = dbox_is_recent;
	mbox->ibox.index = index;

	value = getenv("DBOX_ROTATE_SIZE");
	if (value != NULL)
		mbox->rotate_size = (uoff_t)strtoul(value, NULL, 10) * 1024;
	else
		mbox->rotate_size = DBOX_DEFAULT_ROTATE_SIZE;
	value = getenv("DBOX_ROTATE_MIN_SIZE");
	if (value != NULL)
		mbox->rotate_min_size = (uoff_t)strtoul(value, NULL, 10) * 1024;
	else
		mbox->rotate_min_size = DBOX_DEFAULT_ROTATE_MIN_SIZE;
	value = getenv("DBOX_ROTATE_DAYS");
	if (value != NULL)
		mbox->rotate_days = (unsigned int)strtoul(value, NULL, 10);
	else
		mbox->rotate_days = DBOX_DEFAULT_ROTATE_DAYS;

	mbox->storage = storage;
	mbox->path = p_strdup(pool, path);
	mbox->dbox_file_ext_idx =
		mail_index_ext_register(index, "dbox-seq", 0,
					sizeof(uint32_t), sizeof(uint32_t));
	mbox->dbox_offset_ext_idx =
		mail_index_ext_register(index, "dbox-off", 0,
					sizeof(uint64_t), sizeof(uint64_t));

	mbox->uidlist = dbox_uidlist_init(mbox);
	if ((flags & MAILBOX_OPEN_KEEP_LOCKED) != 0) {
		if (dbox_uidlist_lock(mbox->uidlist) < 0) {
			struct mailbox *box = &mbox->ibox.box;

			mailbox_close(&box);
			return NULL;
		}
		mbox->keep_lock_to = timeout_add(DBOX_LOCK_TOUCH_MSECS,
						 dbox_lock_touch_timeout,
						 mbox);
	}

	index_storage_mailbox_init(&mbox->ibox, name, flags, FALSE);
	return &mbox->ibox.box;
}

static struct mailbox *
dbox_mailbox_open(struct mail_storage *_storage, const char *name,
		  struct istream *input, enum mailbox_open_flags flags)
{
	struct dbox_storage *storage = (struct dbox_storage *)_storage;
	const char *path;
	struct stat st;

	mail_storage_clear_error(_storage);

	if (input != NULL) {
		mail_storage_set_critical(_storage,
			"dbox doesn't support streamed mailboxes");
		return NULL;
	}

	if (strcmp(name, "INBOX") == 0)
		return dbox_open(storage, "INBOX", flags);

	if (!mailbox_list_is_valid_existing_name(_storage->list, name)) {
		mail_storage_set_error(_storage, "Invalid mailbox name");
		return NULL;
	}

	path = mailbox_list_get_path(_storage->list, name,
				     MAILBOX_LIST_PATH_TYPE_MAILBOX);
	if (stat(path, &st) == 0) {
		return dbox_open(storage, name, flags);
	} else if (errno == ENOENT) {
		mail_storage_set_error(_storage,
			MAILBOX_LIST_ERR_MAILBOX_NOT_FOUND, name);
		return NULL;
	} else {
		mail_storage_set_critical(_storage, "stat(%s) failed: %m",
					  path);
		return NULL;
	}
}

static int dbox_mailbox_create(struct mail_storage *_storage,
			       const char *name,
			       bool directory __attr_unused__)
{
	const char *path;
	struct stat st;

	mail_storage_clear_error(_storage);

	if (!mailbox_list_is_valid_create_name(_storage->list, name)) {
		mail_storage_set_error(_storage, "Invalid mailbox name");
		return -1;
	}

	path = mailbox_list_get_path(_storage->list, name,
				     MAILBOX_LIST_PATH_TYPE_MAILBOX);
	if (stat(path, &st) == 0) {
		mail_storage_set_error(_storage, "Mailbox already exists");
		return -1;
	}

	return create_dbox(_storage, path);
}

static int
dbox_list_delete_mailbox(struct mailbox_list *list, const char *name)
{
	struct dbox_storage *storage = DBOX_LIST_CONTEXT(list);
	struct stat st;
	const char *path, *mail_path, *error;

	/* make sure the indexes are closed before trying to delete the
	   directory that contains them */
	index_storage_destroy_unrefed();

	/* delete the index and control directories */
	if (storage->list_super.delete_mailbox(list, name) < 0)
		return -1;

	path = mailbox_list_get_path(list, name, MAILBOX_LIST_PATH_TYPE_DIR);
	mail_path = mailbox_list_get_path(list, name,
					  MAILBOX_LIST_PATH_TYPE_MAILBOX);

	if (stat(mail_path, &st) < 0 && ENOTFOUND(errno)) {
		if (stat(path, &st) < 0) {
			/* doesn't exist at all */
			mailbox_list_set_error(list, t_strdup_printf(
				MAILBOX_LIST_ERR_MAILBOX_NOT_FOUND, name));
			return -1;
		}

		/* exists as a \NoSelect mailbox */
		if (rmdir(path) == 0)
			return 0;

		if (errno == ENOTEMPTY) {
			mailbox_list_set_error(list, t_strdup_printf(
				"Directory %s isn't empty, can't delete it.",
				name));
		} else {
			mailbox_list_set_critical(list,
				"rmdir() failed for %s: %m", path);
		}

		return -1;
	}


	if (unlink_directory(mail_path, TRUE) < 0) {
		if (mail_storage_errno2str(&error))
			mailbox_list_set_error(list, error);
		else {
			mailbox_list_set_critical(list,
				"unlink_directory() failed for %s: %m",
				mail_path);
		}
		return -1;
	}
	/* try also removing the root directory. it can fail if the deleted
	   mailbox had submailboxes. do it as long as we can. */
	while (rmdir(path) == 0 || errno == ENOENT) {
		const char *p = strrchr(name, '/');

		if (p == NULL)
			break;

		name = t_strdup_until(name, p);
		path = mailbox_list_get_path(list, name,
					     MAILBOX_LIST_PATH_TYPE_DIR);
	}
	return 0;
}

static int dbox_storage_close(struct mailbox *box)
{
	struct dbox_mailbox *mbox = (struct dbox_mailbox *)box;

	if (mbox->keep_lock_to != NULL) {
		dbox_uidlist_unlock(mbox->uidlist);
		timeout_remove(&mbox->keep_lock_to);
	}

	dbox_uidlist_deinit(mbox->uidlist);
	if (mbox->file != NULL)
		dbox_file_close(mbox->file);
        index_storage_mailbox_free(box);
	return 0;
}

static void
dbox_notify_changes(struct mailbox *box, unsigned int min_interval,
		    mailbox_notify_callback_t *callback, void *context)
{
	struct dbox_mailbox *mbox = (struct dbox_mailbox *)box;

	mbox->ibox.min_notify_interval = min_interval;
	mbox->ibox.notify_callback = callback;
	mbox->ibox.notify_context = context;

	if (callback == NULL) {
		index_mailbox_check_remove_all(&mbox->ibox);
		return;
	}

	index_mailbox_check_add(&mbox->ibox, mbox->path);
}

static int dbox_list_iter_is_mailbox(struct mailbox_list_iterate_context *ctx,
				     const char *dir, const char *fname,
				     enum mailbox_list_file_type type,
				     enum mailbox_info_flags *flags)
{
	const char *mail_path;
	struct stat st;
	int ret = 1;

	if (strcmp(fname, DBOX_MAILDIR_NAME) == 0) {
		*flags = MAILBOX_NOSELECT;
		return 0;
	}

	/* try to avoid stat() with these checks */
	if (type != MAILBOX_LIST_FILE_TYPE_DIR &&
	    type != MAILBOX_LIST_FILE_TYPE_SYMLINK &&
	    type != MAILBOX_LIST_FILE_TYPE_UNKNOWN &&
	    (ctx->flags & MAILBOX_LIST_ITER_FAST_FLAGS) != 0) {
		/* it's a file */
		*flags |= MAILBOX_NOSELECT | MAILBOX_NOINFERIORS;
		return 0;
	}

	/* need to stat() then */
	t_push();
	mail_path = t_strconcat(dir, "/", fname, "/"DBOX_MAILDIR_NAME, NULL);

	if (stat(mail_path, &st) == 0) {
		if (!S_ISDIR(st.st_mode)) {
			/* non-directory */
			*flags |= MAILBOX_NOSELECT | MAILBOX_NOINFERIORS;
			ret = 0;
		}
	} else {
		/* non-selectable, but may contain subdirs */
		if (errno != ENOTDIR)
			*flags |= MAILBOX_CHILDREN;
		*flags |= MAILBOX_NOSELECT;
	}
	t_pop();

	return ret;
}

static void dbox_class_init(void)
{
	dbox_mailbox_list_module_id = mailbox_list_module_id++;
	dbox_transaction_class_init();
}

static void dbox_class_deinit(void)
{
	dbox_transaction_class_deinit();
}

struct mail_storage dbox_storage = {
	MEMBER(name) DBOX_STORAGE_NAME,
	MEMBER(mailbox_is_file) FALSE,

	{
		dbox_class_init,
		dbox_class_deinit,
		dbox_create,
		dbox_free,
		dbox_autodetect,
		index_storage_set_callbacks,
		dbox_mailbox_open,
		dbox_mailbox_create,
		index_storage_get_last_error
	}
};

struct mailbox dbox_mailbox = {
	MEMBER(name) NULL, 
	MEMBER(storage) NULL, 

	{
		index_storage_is_readonly,
		index_storage_allow_new_keywords,
		dbox_storage_close,
		index_storage_get_status,
		dbox_storage_sync_init,
		index_mailbox_sync_next,
		index_mailbox_sync_deinit,
		dbox_notify_changes,
		index_transaction_begin,
		index_transaction_commit,
		index_transaction_rollback,
		index_keywords_create,
		index_keywords_free,
		index_storage_get_uids,
		index_mail_alloc,
		index_header_lookup_init,
		index_header_lookup_deinit,
		index_storage_search_init,
		index_storage_search_deinit,
		index_storage_search_next_nonblock,
		index_storage_search_next_update_seq,
		dbox_save_init,
		dbox_save_continue,
		dbox_save_finish,
		dbox_save_cancel,
		mail_storage_copy,
		index_storage_is_inconsistent
	}
};