diff src/lib-storage/index/maildir/maildir-storage.c @ 0:3b1985cbc908 HEAD

Initial revision
author Timo Sirainen <tss@iki.fi>
date Fri, 09 Aug 2002 12:15:38 +0300
parents
children 1b34ec11fff8
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-storage/index/maildir/maildir-storage.c	Fri Aug 09 12:15:38 2002 +0300
@@ -0,0 +1,373 @@
+/* Copyright (C) 2002 Timo Sirainen */
+
+#include "lib.h"
+#include "unlink-directory.h"
+#include "subscription-file/subscription-file.h"
+#include "maildir-index.h"
+#include "maildir-storage.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#define CREATE_MODE 0770 /* umask() should limit it more */
+
+extern MailStorage maildir_storage;
+static Mailbox maildir_mailbox;
+
+static const char *maildirs[] = { "cur", "new", "tmp", NULL  };
+
+static MailStorage *maildir_create(const char *data)
+{
+	MailStorage *storage;
+	const char *home, *path;
+
+	if (data == NULL || *data == '\0') {
+		/* we'll need to figure out the maildir location ourself.
+		   it's either root dir if we've already chroot()ed, or
+		   $HOME/Maildir otherwise */
+		if (access("/cur", R_OK|W_OK|X_OK) == 0)
+			data = "/";
+		else {
+			home = getenv("HOME");
+			if (home != NULL) {
+				path = t_strconcat(home, "/Maildir", 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, &maildir_storage, sizeof(MailStorage));
+
+	storage->dir = i_strdup(data);
+	return storage;
+}
+
+static void maildir_free(MailStorage *storage)
+{
+	i_free(storage->dir);
+	i_free(storage);
+}
+
+static int maildir_autodetect(const char *data)
+{
+	struct stat st;
+
+	return stat(t_strconcat(data, "/cur", NULL), &st) == 0 &&
+		S_ISDIR(st.st_mode);
+}
+
+static int maildir_is_valid_name(MailStorage *storage, const char *name)
+{
+	return name[0] != '\0' && name[0] != storage->hierarchy_sep &&
+		strchr(name, '/') == NULL;
+}
+
+/* create or fix maildir, ignore if it already exists */
+static int create_maildir(const char *dir, int verify)
+{
+	const char **tmp;
+	char path[1024];
+
+	if (mkdir(dir, CREATE_MODE) == -1 && (errno != EEXIST || !verify))
+		return FALSE;
+
+	for (tmp = maildirs; *tmp != NULL; tmp++) {
+		i_snprintf(path, sizeof(path), "%s/%s", dir, *tmp);
+
+		if (mkdir(path, CREATE_MODE) == -1 &&
+		    (errno != EEXIST || !verify))
+			return FALSE;
+	}
+
+	return TRUE;
+}
+
+static int verify_inbox(MailStorage *storage, const char *dir)
+{
+	const char **tmp;
+	char src[1024], dest[1024];
+
+	/* first make sure the cur/ new/ and tmp/ dirs exist in root dir */
+	(void)create_maildir(dir, TRUE);
+
+	/* create the .INBOX directory */
+	i_snprintf(dest, sizeof(dest), "%s/.INBOX", dir);
+	if (mkdir(dest, CREATE_MODE) == -1 && errno != EEXIST) {
+		mail_storage_set_critical(storage, "Can't create directory "
+					  "%s: %m", dest);
+		return FALSE;
+	}
+
+	/* then symlink the cur/ new/ and tmp/ into the .INBOX/ directory */
+	for (tmp = maildirs; *tmp != NULL; tmp++) {
+		i_snprintf(src, sizeof(src), "../%s", *tmp);
+		i_snprintf(dest, sizeof(dest), "%s/.INBOX/%s", dir, *tmp);
+
+		if (symlink(src, dest) == -1 && errno != EEXIST) {
+			mail_storage_set_critical(storage, "symlink(%s, %s) "
+						  "failed: %m", src, dest);
+			return FALSE;
+		}
+	}
+
+	return TRUE;
+}
+
+static Mailbox *maildir_open(MailStorage *storage, const char *name,
+			     int readonly)
+{
+	IndexMailbox *ibox;
+	const char *path;
+
+	path = t_strconcat(storage->dir, "/.", name, NULL);
+
+	ibox = index_storage_init(storage, &maildir_mailbox,
+				  maildir_index_alloc(path), name, readonly);
+	if (ibox != NULL)
+		ibox->expunge_locked = maildir_expunge_locked;
+	return (Mailbox *) ibox;
+}
+
+static Mailbox *maildir_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) {
+		if (!verify_inbox(storage, storage->dir))
+			return NULL;
+		return maildir_open(storage, "INBOX", readonly);
+	}
+
+	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_maildir(path, TRUE);
+
+		return maildir_open(storage, name, readonly);
+	} else if (errno == ENOENT) {
+		mail_storage_set_error(storage, "Mailbox doesn't exist");
+		return NULL;
+	} else {
+		mail_storage_set_critical(storage, "Can't open mailbox %s: %m",
+					  name);
+		return NULL;
+	}
+}
+
+static int maildir_create_mailbox(MailStorage *storage, const char *name)
+{
+	char path[1024];
+
+	mail_storage_clear_error(storage);
+
+	if (strcasecmp(name, "INBOX") == 0)
+		name = "INBOX";
+
+	if (!maildir_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 (create_maildir(path, FALSE))
+		return TRUE;
+	else if (errno == EEXIST) {
+		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 maildir_delete_mailbox(MailStorage *storage, const char *name)
+{
+	struct stat st;
+	char src[1024], dest[1024];
+	int count;
+
+	mail_storage_clear_error(storage);
+
+	if (strcasecmp(name, "INBOX") == 0) {
+		mail_storage_set_error(storage, "INBOX can't be deleted.");
+		return FALSE;
+	}
+
+	/* rename the .maildir into ..maildir which marks it as being
+	   deleted. this way we never see partially deleted maildirs. */
+	i_snprintf(src, sizeof(src), "%s/.%s", storage->dir, name);
+	i_snprintf(dest, sizeof(dest), "%s/..%s", storage->dir, name);
+
+	if (stat(src, &st) != 0 && errno == ENOENT) {
+		mail_storage_set_error(storage, "Mailbox doesn't exist.");
+		return FALSE;
+	}
+
+	count = 0;
+	while (rename(src, dest) == -1 && count < 2) {
+		if (errno != EEXIST) {
+			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)) {
+			mail_storage_set_critical(storage,
+						  "unlink_directory(%s) "
+						  "failed: %m", dest);
+			return FALSE;
+		}
+		count++;
+	}
+
+	if (!unlink_directory(dest)) {
+		mail_storage_set_critical(storage, "unlink_directory(%s) "
+					  "failed: %m", dest);
+		return FALSE;
+	}
+	return TRUE;
+}
+
+static int move_inbox_data(MailStorage *storage, const char *newdir)
+{
+	const char **tmp;
+	char oldpath[1024], newpath[1024];
+
+	/* newpath points to the destination folder directory, which contains
+	   symlinks to real INBOX directories. unlink() the symlinks and
+	   move the real cur/ directory here. */
+	for (tmp = maildirs; *tmp != NULL; tmp++) {
+		i_snprintf(newpath, sizeof(newpath), "%s/%s", newdir, *tmp);
+
+		if (unlink(newpath) == -1 && errno != EEXIST) {
+			mail_storage_set_error(storage, "unlink(%s) failed: "
+					       "%m", newpath);
+			return FALSE;
+		}
+	}
+
+	i_snprintf(oldpath, sizeof(oldpath), "%s/cur", storage->dir);
+	i_snprintf(newpath, sizeof(newpath), "%s/cur", newdir);
+	if (rename(oldpath, newpath) != 0) {
+		mail_storage_set_critical(storage, "rename(%s, %s) failed: %m",
+					  oldpath, newpath);
+		return FALSE;
+	}
+
+	/* create back the cur/ directory for INBOX */
+	(void)mkdir(oldpath, CREATE_MODE);
+	return TRUE;
+}
+
+static int maildir_rename_mailbox(MailStorage *storage, const char *oldname,
+				  const char *newname)
+{
+	char oldpath[1024], newpath[1024];
+
+	mail_storage_clear_error(storage);
+
+	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. Only problem with it is that it's not
+	   atomic operation but that can't be really helped. */
+	i_snprintf(oldpath, sizeof(oldpath), "%s/.%s", storage->dir, oldname);
+	i_snprintf(newpath, sizeof(newpath), "%s/.%s", storage->dir, newname);
+	if (rename(oldpath, newpath) == 0) {
+		if (strcmp(oldname, "INBOX") == 0)
+			return move_inbox_data(storage, newpath);
+		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(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 (!maildir_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;
+	}
+}
+
+MailStorage maildir_storage = {
+	"maildir", /* name */
+
+	'.', /* hierarchy_sep - can't be changed */
+
+	maildir_create,
+	maildir_free,
+	maildir_autodetect,
+	maildir_open_mailbox,
+	maildir_create_mailbox,
+	maildir_delete_mailbox,
+	maildir_rename_mailbox,
+	maildir_find_mailboxes,
+	subsfile_set_subscribed,
+	maildir_find_subscribed,
+	maildir_get_mailbox_name_status,
+	mail_storage_get_last_error
+};
+
+static Mailbox maildir_mailbox = {
+	NULL, /* name */
+	NULL, /* storage */
+
+	index_storage_close,
+	index_storage_get_status,
+	index_storage_sync,
+	index_storage_expunge,
+	index_storage_update_flags,
+	maildir_storage_copy,
+	index_storage_fetch,
+	index_storage_search,
+	maildir_storage_save,
+	mail_storage_is_inconsistency_error
+};