view src/lib-storage/index/mbox/mbox-storage.c @ 160:ff05b320482c HEAD

Bigger changes.. full_virtual_size was removed from index record and MessagePart caching is now forced. Also added per-message flags, including binary flags which can be used to check if CRs need to be inserted into message data. Added mbox-rewrite support which can be used to write out mbox file with updated flags. This still has the problem of being able to read changed custom flags, that'll require another bigger change. There's also several other mostly mbox related fixes.
author Timo Sirainen <tss@iki.fi>
date Fri, 06 Sep 2002 16:43:58 +0300
parents 899ea73710fc
children 73bf05a1d862
line wrap: on
line source

/* Copyright (C) 2002 Timo Sirainen */

#include "lib.h"
#include "unlink-directory.h"
#include "subscription-file/subscription-file.h"
#include "mbox-index.h"
#include "mbox-storage.h"

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

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

extern MailStorage mbox_storage;
static Mailbox mbox_mailbox;

static int mbox_autodetect(const char *data)
{
	const char *path;
	struct stat st;

        path = t_strconcat(data, "/.imap", NULL);
	if (stat(path, &st) == 0 && S_ISDIR(st.st_mode) &&
	    access(path, R_OK|W_OK|X_OK) == 0)
		return TRUE;

	path = t_strconcat(data, "/inbox", NULL);
	if (stat(path, &st) == 0 && !S_ISDIR(st.st_mode) &&
	    access(path, R_OK|W_OK) == 0)
		return TRUE;

	path = t_strconcat(data, "/mbox", NULL);
	if (stat(path, &st) == 0 && !S_ISDIR(st.st_mode) &&
	    access(path, R_OK|W_OK) == 0)
		return TRUE;

	return FALSE;
}

static MailStorage *mbox_create(const char *data, const char *user)
{
	MailStorage *storage;
	const char *home, *path;

	if (data == NULL || *data == '\0') {
		/* we'll need to figure out the mail location ourself.
		   it's root dir if we've already chroot()ed, otherwise
		   either $HOME/mail or $HOME/Mail */
		if (mbox_autodetect(""))
			data = "/";
		else {
			home = getenv("HOME");
			if (home != NULL) {
				path = t_strconcat(home, "/mail", NULL);
				if (access(path, R_OK|W_OK|X_OK) == 0)
					data = path;
				else {
					path = t_strconcat(home, "/Mail", NULL);
					if (access(path, R_OK|W_OK|X_OK) == 0)
						data = path;
				}
			}
		}
	}

	if (data == NULL)
		return NULL;

	storage = i_new(MailStorage, 1);
	memcpy(storage, &mbox_storage, sizeof(MailStorage));

	storage->dir = i_strdup(data);
	storage->user = i_strdup(user);
	return storage;
}

static void mbox_free(MailStorage *storage)
{
	i_free(storage->dir);
	i_free(storage->user);
	i_free(storage);
}

static int mbox_is_valid_name(MailStorage *storage, const char *name)
{
	const char *p;
	int newdir;

	if (name[0] == '\0' || name[0] == storage->hierarchy_sep)
		return FALSE;

	/* make sure there's no "../" or "..\" stuff */
	newdir = TRUE;
	for (p = name; *p != '\0'; p++) {
		if (newdir && p[0] == '.' && p[1] == '.' &&
		    (p[2] == '/' || p[2] == '\\'))
			return FALSE;
		newdir = p[0] == '/' || p[0] == '\\';
	}

	return TRUE;
}

static const char *mbox_get_index_dir(const char *mbox_path)
{
	const char *p, *rootpath;

	p = strrchr(mbox_path, '/');
	if (p == NULL)
		return t_strconcat(".imap/", mbox_path);
	else {
		rootpath = t_strdup_until(mbox_path, p);
		return t_strconcat(rootpath, "/.imap/", p+1, NULL);
	}
}

static int create_mbox_index_dirs(const char *mbox_path, int verify)
{
	const char *index_dir, *imap_dir;

	index_dir = mbox_get_index_dir(mbox_path);
	imap_dir = t_strdup_until(index_dir, strstr(index_dir, ".imap/") + 5);

	if (mkdir(imap_dir, CREATE_MODE) == -1 && errno != EEXIST)
		return FALSE;
	if (mkdir(index_dir, CREATE_MODE) == -1 && (errno != EEXIST || !verify))
		return FALSE;

	return TRUE;
}

static void verify_inbox(MailStorage *storage)
{
	char path[1024];
	int fd;

	i_snprintf(path, sizeof(path), "%s/inbox", storage->dir);

	/* make sure inbox file itself exists */
	fd = open(path, O_RDWR | O_CREAT | O_EXCL);
	if (fd != -1)
		(void)close(fd);

	/* make sure the index directories exist */
	(void)create_mbox_index_dirs(path, TRUE);
}

static Mailbox *mbox_open(MailStorage *storage, const char *name, int readonly)
{
	IndexMailbox *ibox;
	const char *path, *index_dir;

	/* name = "foo/bar"
	   mbox_path = "/mail/foo/bar"
	   index_dir = "/mail/foo/.imap/bar" */
	path = t_strconcat(storage->dir, "/", name, NULL);
	index_dir = mbox_get_index_dir(path);

	ibox = index_storage_init(storage, &mbox_mailbox,
				  mbox_index_alloc(index_dir, path),
				  name, readonly);
	if (ibox != NULL)
		ibox->expunge_locked = mbox_expunge_locked;
	return (Mailbox *) ibox;
}

static Mailbox *mbox_open_mailbox(MailStorage *storage, const char *name,
				  int readonly)
{
	struct stat st;
	char path[1024];

	mail_storage_clear_error(storage);

	/* INBOX is always case-insensitive */
	if (strcasecmp(name, "INBOX") == 0) {
		/* make sure inbox exists */
		verify_inbox(storage);
		return mbox_open(storage, "inbox", readonly);
	}

	if (!mbox_is_valid_name(storage, name)) {
		mail_storage_set_error(storage, "Invalid mailbox name");
		return FALSE;
	}

	i_snprintf(path, sizeof(path), "%s/%s", storage->dir, name);
	if (stat(path, &st) == 0) {
		/* exists - make sure the required directories are also there */
		(void)create_mbox_index_dirs(path, TRUE);

		return mbox_open(storage, name, readonly);
	} else if (errno == ENOENT) {
		mail_storage_set_error(storage, "Mailbox doesn't exist: %s",
				       name);
		return NULL;
	} else {
		mail_storage_set_critical(storage, "Can't open mailbox %s: %m",
					  name);
		return NULL;
	}
}

static int mbox_create_mailbox(MailStorage *storage, const char *name)
{
	struct stat st;
	char path[1024];
	int fd;

	mail_storage_clear_error(storage);

	if (strcasecmp(name, "INBOX") == 0)
		name = "inbox";

	if (!mbox_is_valid_name(storage, name)) {
		mail_storage_set_error(storage, "Invalid mailbox name");
		return FALSE;
	}

	/* make sure it doesn't exist already */
	i_snprintf(path, sizeof(path), "%s/%s", storage->dir, name);
	if (stat(path, &st) == 0) {
		mail_storage_set_error(storage, "Mailbox already exists");
		return FALSE;
	}

	if (errno != EEXIST) {
		mail_storage_set_critical(storage, "stat() failed for mbox "
					  "file %s: %m", path);
		return FALSE;
	}

	/* create the mailbox file */
	fd = open(path, O_RDWR | O_CREAT | O_EXCL);
	if (fd != -1) {
		(void)close(fd);
		return TRUE;
	} else if (errno == EEXIST) {
		/* mailbox was just created between stat() and open() call.. */
		mail_storage_set_error(storage, "Mailbox already exists");
		return FALSE;
	} else {
		mail_storage_set_critical(storage, "Can't create mailbox "
					  "%s: %m", name);
		return FALSE;
	}
}

static int mbox_delete_mailbox(MailStorage *storage, const char *name)
{
	const char *index_dir;
	char path[1024];

	mail_storage_clear_error(storage);

	if (strcasecmp(name, "INBOX") == 0) {
		mail_storage_set_error(storage, "INBOX can't be deleted.");
		return FALSE;
	}

	if (!mbox_is_valid_name(storage, name)) {
		mail_storage_set_error(storage, "Invalid mailbox name");
		return FALSE;
	}

	/* first unlink the mbox file */
	i_snprintf(path, sizeof(path), "%s/%s", storage->dir, name);
	if (unlink(path) == -1) {
		if (errno == ENOENT) {
			mail_storage_set_error(storage,
					       "Mailbox doesn't exist: %s",
					       name);
		} else {
			mail_storage_set_critical(storage, "Can't delete mbox "
						  "file %s: %m", path);
		}
		return FALSE;
	}

	/* next delete the index directory */
	index_dir = mbox_get_index_dir(path);
	if (!unlink_directory(index_dir)) {
		mail_storage_set_critical(storage, "unlink_directory(%s) "
					  "failed: %m", index_dir);
		return FALSE;
	}
	return TRUE;
}

static int mbox_rename_mailbox(MailStorage *storage, const char *oldname,
			       const char *newname)
{
	const char *old_indexdir, *new_indexdir;
	char oldpath[1024], newpath[1024];

	mail_storage_clear_error(storage);

	if (!mbox_is_valid_name(storage, oldname) ||
	    !mbox_is_valid_name(storage, newname)) {
		mail_storage_set_error(storage, "Invalid mailbox name");
		return FALSE;
	}

	if (strcasecmp(oldname, "INBOX") == 0)
		oldname = "inbox";

	/* NOTE: renaming INBOX works just fine with us, it's simply created
	   the next time it's needed. */
	i_snprintf(oldpath, sizeof(oldpath), "%s/%s", storage->dir, oldname);
	i_snprintf(newpath, sizeof(newpath), "%s/%s", storage->dir, newname);
	if (link(oldpath, newpath) == 0) {
		(void)unlink(oldpath);
		/* ... */
	} else if (errno == EEXIST) {
		mail_storage_set_error(storage,
				       "Target mailbox already exists");
		return FALSE;
	} else {
		mail_storage_set_critical(storage, "link(%s, %s) failed: %m",
					  oldpath, newpath);
		return FALSE;
	}

	/* we need to rename the index directory as well */
	old_indexdir = mbox_get_index_dir(oldpath);
	new_indexdir = mbox_get_index_dir(newpath);
	(void)rename(old_indexdir, new_indexdir);

	return TRUE;
}

static int mbox_get_mailbox_name_status(MailStorage *storage, const char *name,
					MailboxNameStatus *status)
{
	struct stat st;
	char path[1024];

	mail_storage_clear_error(storage);

	if (strcasecmp(name, "INBOX") == 0)
		name = "inbox";

	if (!mbox_is_valid_name(storage, name)) {
		*status = MAILBOX_NAME_INVALID;
		return TRUE;
	}

	i_snprintf(path, sizeof(path), "%s/%s", storage->dir, name);
	if (stat(path, &st) == 0) {
		*status = MAILBOX_NAME_EXISTS;
		return TRUE;
	} else if (errno == ENOENT) {
		*status = MAILBOX_NAME_VALID;
		return TRUE;
	} else {
		mail_storage_set_critical(storage, "mailbox name status: "
					  "stat(%s) failed: %m", path);
		return FALSE;
	}
}

static void mbox_storage_close(Mailbox *box)
{
	IndexMailbox *ibox = (IndexMailbox *) box;

	if (!ibox->index->set_lock(ibox->index, MAIL_LOCK_EXCLUSIVE))
		mail_storage_set_index_error(ibox);
	else {
		/* update flags by rewrite mbox file */
		mbox_index_rewrite(ibox->index,
				   flags_file_list_get(ibox->flagsfile));
		flags_file_list_unref(ibox->flagsfile);

		(void)ibox->index->set_lock(ibox->index, MAIL_LOCK_UNLOCK);
	}

	index_storage_close(box);
}

MailStorage mbox_storage = {
	"mbox", /* name */

	'/', /* hierarchy_sep - can't be changed */

	mbox_create,
	mbox_free,
	mbox_autodetect,
	mbox_open_mailbox,
	mbox_create_mailbox,
	mbox_delete_mailbox,
	mbox_rename_mailbox,
	mbox_find_mailboxes,
	subsfile_set_subscribed,
	mbox_find_subscribed,
	mbox_get_mailbox_name_status,
	mail_storage_get_last_error,

	NULL,
	NULL,
	NULL
};

static Mailbox mbox_mailbox = {
	NULL, /* name */
	NULL, /* storage */

	mbox_storage_close,
	index_storage_get_status,
	index_storage_sync,
	index_storage_expunge,
	index_storage_update_flags,
	index_storage_copy,
	index_storage_fetch,
	index_storage_search,
	mbox_storage_save,
	mail_storage_is_inconsistency_error,

	FALSE,
	FALSE
};