dbox-storage.c revision e59faf65ce864fe95dc00f5d52b8323cdbd0608a
/* Copyright (c) 2007-2010 Dovecot authors, see the included COPYING file */
#include "lib.h"
#include "ioloop.h"
#include "randgen.h"
#include "hex-binary.h"
#include "mkdir-parents.h"
#include "unlink-directory.h"
#include "unlink-old-files.h"
#include "mailbox-uidvalidity.h"
#include "mailbox-list-private.h"
#include "index-storage.h"
#include "dbox-storage.h"
#include <stdio.h>
void dbox_storage_get_list_settings(const struct mail_namespace *ns ATTR_UNUSED,
struct mailbox_list_settings *set)
{
if (set->layout == NULL)
set->layout = MAILBOX_LIST_NAME_FS;
if (set->subscription_fname == NULL)
set->subscription_fname = DBOX_SUBSCRIPTION_FILE_NAME;
if (set->maildir_name == NULL)
set->maildir_name = DBOX_MAILDIR_NAME;
if (set->mailbox_dir_name == NULL)
set->mailbox_dir_name = DBOX_MAILBOX_DIR_NAME;
}
uint32_t dbox_get_uidvalidity_next(struct mailbox_list *list)
{
const char *path;
path = mailbox_list_get_path(list, NULL,
MAILBOX_LIST_PATH_TYPE_CONTROL);
path = t_strconcat(path, "/"DBOX_UIDVALIDITY_FILE_NAME, NULL);
return mailbox_uidvalidity_next(list, path);
}
void dbox_notify_changes(struct mailbox *box)
{
struct index_mailbox *ibox = (struct index_mailbox *)box;
const char *dir, *path;
if (box->notify_callback == NULL)
index_mailbox_check_remove_all(ibox);
else {
dir = mailbox_list_get_path(box->list, box->name,
MAILBOX_LIST_PATH_TYPE_INDEX);
path = t_strdup_printf("%s/"DBOX_INDEX_PREFIX".log", dir);
index_mailbox_check_add(ibox, path);
}
}
static bool
dbox_cleanup_if_exists(struct mailbox_list *list, const char *path)
{
struct stat st;
if (stat(path, &st) < 0)
return FALSE;
/* check once in a while if there are temp files to clean up */
if (st.st_atime > st.st_ctime + DBOX_TMP_DELETE_SECS) {
/* there haven't been any changes to this directory since we
last checked it. */
} else if (st.st_atime < ioloop_time - DBOX_TMP_SCAN_SECS) {
/* time to scan */
const char *prefix =
mailbox_list_get_global_temp_prefix(list);
(void)unlink_old_files(path, prefix,
ioloop_time - DBOX_TMP_DELETE_SECS);
}
return TRUE;
}
int dbox_mailbox_open(struct mailbox *box)
{
struct dbox_storage *storage = (struct dbox_storage *)box->storage;
if (box->input != NULL) {
mail_storage_set_critical(box->storage,
"dbox doesn't support streamed mailboxes");
return -1;
}
if (dbox_cleanup_if_exists(box->list, box->path)) {
return index_storage_mailbox_open(box);
} else if (errno == ENOENT) {
if (strcmp(box->name, "INBOX") == 0 &&
(box->list->ns->flags & NAMESPACE_FLAG_INBOX) != 0) {
/* INBOX always exists, create it */
if (storage->v.mailbox_create_indexes(box, NULL) < 0)
return -1;
return box->opened ? 0 :
index_storage_mailbox_open(box);
}
mail_storage_set_error(box->storage, MAIL_ERROR_NOTFOUND,
T_MAIL_ERR_MAILBOX_NOT_FOUND(box->name));
return -1;
} else if (errno == EACCES) {
mail_storage_set_critical(box->storage, "%s",
mail_error_eacces_msg("stat", box->path));
return -1;
} else {
mail_storage_set_critical(box->storage,
"stat(%s) failed: %m", box->path);
return -1;
}
}
static const char *
dbox_get_alt_path(struct mailbox_list *list, const char *path)
{
struct mail_storage *storage = list->ns->storage;
const char *root;
unsigned int len;
if (list->set.alt_dir == NULL ||
(storage->class_flags & MAIL_STORAGE_CLASS_FLAG_UNIQUE_ROOT) != 0)
return NULL;
root = mailbox_list_get_path(list, NULL, MAILBOX_LIST_PATH_TYPE_DIR);
len = strlen(root);
if (strncmp(path, root, len) != 0 && path[len] == '/') {
/* can't determine the alt path - shouldn't happen */
return NULL;
}
return t_strconcat(list->set.alt_dir, path + len, NULL);
}
int dbox_mailbox_create(struct mailbox *box,
const struct mailbox_update *update, bool directory)
{
struct dbox_storage *storage = (struct dbox_storage *)box->storage;
const char *path, *alt_path, *origin;
struct stat st;
path = mailbox_list_get_path(box->list, box->name,
directory ? MAILBOX_LIST_PATH_TYPE_DIR :
MAILBOX_LIST_PATH_TYPE_MAILBOX);
if (stat(path, &st) == 0) {
mail_storage_set_error(box->storage, MAIL_ERROR_EXISTS,
"Mailbox already exists");
return -1;
}
if (directory) {
mode_t mode;
gid_t gid;
mailbox_list_get_dir_permissions(box->list, NULL, &mode,
&gid, &origin);
if (mkdir_parents_chgrp(path, mode, gid, origin) == 0)
return 0;
else if (errno == EEXIST) {
mail_storage_set_error(box->storage, MAIL_ERROR_EXISTS,
"Mailbox already exists");
} else if (!mail_storage_set_error_from_errno(box->storage)) {
mail_storage_set_critical(box->storage,
"mkdir(%s) failed: %m", path);
}
return -1;
}
/* make sure the alt path doesn't exist yet. it shouldn't (except with
race conditions with RENAME/DELETE), but if something crashed and
left it lying around we don't want to start overwriting files in
it. */
alt_path = dbox_get_alt_path(box->list, path);
if (alt_path != NULL && stat(alt_path, &st) == 0) {
mail_storage_set_error(box->storage, MAIL_ERROR_EXISTS,
"Mailbox already exists");
return -1;
}
return storage->v.mailbox_create_indexes(box, update);
}
int dbox_list_iter_is_mailbox(struct mailbox_list_iterate_context *ctx ATTR_UNUSED,
const char *dir, const char *fname,
const char *mailbox_name ATTR_UNUSED,
enum mailbox_list_file_type type,
enum mailbox_info_flags *flags)
{
const char *path, *maildir_path;
struct stat st, st2;
int ret = 1;
/* 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) {
/* it's a file */
*flags |= MAILBOX_NOSELECT | MAILBOX_NOINFERIORS;
return 0;
}
/* need to stat() then */
path = t_strconcat(dir, "/", fname, NULL);
if (stat(path, &st) == 0) {
if (!S_ISDIR(st.st_mode)) {
/* non-directory */
*flags |= MAILBOX_NOSELECT | MAILBOX_NOINFERIORS;
ret = 0;
} else if (st.st_nlink == 2) {
/* no subdirectories */
*flags |= MAILBOX_NOCHILDREN;
} else if (*ctx->list->set.maildir_name != '\0') {
/* default configuration: we have one directory
containing the mailboxes. if there are 3 links,
either this is a selectable mailbox without children
or non-selectable mailbox with children */
if (st.st_nlink > 3)
*flags |= MAILBOX_CHILDREN;
} else {
/* non-default configuration: all subdirectories are
child mailboxes. */
if (st.st_nlink > 2)
*flags |= MAILBOX_CHILDREN;
}
} else if (errno == ENOENT) {
/* doesn't exist - probably a non-existing subscribed mailbox */
*flags |= MAILBOX_NONEXISTENT;
} else {
/* non-selectable. probably either access denied, or symlink
destination not found. don't bother logging errors. */
*flags |= MAILBOX_NOSELECT;
}
if ((*flags & (MAILBOX_NOSELECT | MAILBOX_NONEXISTENT)) == 0) {
/* make sure it's a selectable mailbox */
maildir_path = t_strconcat(path, "/",
ctx->list->set.maildir_name, NULL);
if (stat(maildir_path, &st2) < 0 || !S_ISDIR(st2.st_mode))
*flags |= MAILBOX_NOSELECT;
if (st.st_nlink == 3 && *ctx->list->set.maildir_name != '\0') {
/* now we know what link count 3 means. */
if ((*flags & MAILBOX_NOSELECT) != 0)
*flags |= MAILBOX_CHILDREN;
else
*flags |= MAILBOX_NOCHILDREN;
}
}
return ret;
}
static int
dbox_list_rename_get_alt_paths(struct mailbox_list *oldlist,
const char *oldname,
struct mailbox_list *newlist,
const char *newname,
enum mailbox_list_path_type path_type,
const char **oldpath_r, const char **newpath_r)
{
const char *path;
path = mailbox_list_get_path(oldlist, oldname, path_type);
*oldpath_r = dbox_get_alt_path(oldlist, path);
if (*oldpath_r == NULL)
return 0;
path = mailbox_list_get_path(newlist, newname, path_type);
*newpath_r = dbox_get_alt_path(newlist, path);
if (*newpath_r == NULL) {
/* destination dbox storage doesn't have alt-path defined.
we can't do the rename easily. */
mailbox_list_set_error(oldlist, MAIL_ERROR_NOTPOSSIBLE,
"Can't rename mailboxes across specified storages.");
return -1;
}
return 1;
}
int dbox_list_rename_mailbox_pre(struct mailbox_list *oldlist,
const char *oldname,
struct mailbox_list *newlist,
const char *newname)
{
const char *alt_oldpath, *alt_newpath;
struct stat st;
int ret;
ret = dbox_list_rename_get_alt_paths(oldlist, oldname, newlist, newname,
MAILBOX_LIST_PATH_TYPE_DIR,
&alt_oldpath, &alt_newpath);
if (ret <= 0)
return ret;
if (stat(alt_newpath, &st) == 0) {
/* race condition or a directory left there lying around?
safest to just report error. */
mailbox_list_set_error(oldlist, MAIL_ERROR_EXISTS,
"Target mailbox already exists");
return -1;
} else if (errno != ENOENT) {
mailbox_list_set_critical(oldlist, "stat(%s) failed: %m",
alt_newpath);
return -1;
}
return 0;
}
int dbox_list_rename_mailbox(struct mailbox_list *oldlist, const char *oldname,
struct mailbox_list *newlist, const char *newname,
bool rename_children)
{
enum mailbox_list_path_type path_type;
const char *alt_oldpath, *alt_newpath, *path;
int ret;
path_type = rename_children ? MAILBOX_LIST_PATH_TYPE_DIR :
MAILBOX_LIST_PATH_TYPE_MAILBOX;
ret = dbox_list_rename_get_alt_paths(oldlist, oldname, newlist, newname,
path_type, &alt_oldpath,
&alt_newpath);
if (ret <= 0)
return ret;
if (rename(alt_oldpath, alt_newpath) == 0) {
/* ok */
if (!rename_children) {
path = mailbox_list_get_path(oldlist, oldname,
MAILBOX_LIST_PATH_TYPE_DIR);
if (rmdir(path) < 0 &&
errno != ENOENT && errno != ENOTEMPTY) {
mailbox_list_set_critical(oldlist,
"rmdir(%s) failed: %m", path);
}
}
} else if (errno != ENOENT) {
/* renaming is done already, so just log the error */
mailbox_list_set_critical(oldlist, "rename(%s, %s) failed: %m",
alt_oldpath, alt_newpath);
}
return 0;
}
static const char *dbox_get_trash_dest(const char *trash_dir)
{
const char *path;
unsigned char randbuf[16];
struct stat st;
do {
random_fill_weak(randbuf, sizeof(randbuf));
path = t_strconcat(trash_dir, "/",
binary_to_hex(randbuf, sizeof(randbuf)), NULL);
} while (lstat(path, &st) == 0);
return path;
}
int dbox_list_delete_mailbox1(struct mailbox_list *list, const char *name,
const char **trash_dest_r)
{
struct stat st;
const char *path, *trash_dir, *trash_dest;
int ret;
path = mailbox_list_get_path(list, name,
MAILBOX_LIST_PATH_TYPE_MAILBOX);
trash_dir = mailbox_list_get_path(list, NULL,
MAILBOX_LIST_PATH_TYPE_DIR);
trash_dir = t_strconcat(trash_dir, "/"DBOX_TRASH_DIR_NAME, NULL);
trash_dest = *trash_dest_r = dbox_get_trash_dest(trash_dir);
/* first try renaming the actual mailbox to trash directory */
ret = rename(path, trash_dest);
if (ret < 0 && errno == ENOENT) {
/* either source mailbox doesn't exist or trash directory
doesn't exist. try creating the trash and retrying. */
const char *origin;
mode_t mode;
gid_t gid;
mailbox_list_get_dir_permissions(list, NULL, &mode,
&gid, &origin);
if (mkdir_parents_chgrp(trash_dir, mode, gid, origin) < 0 &&
errno != EEXIST) {
mailbox_list_set_critical(list,
"mkdir(%s) failed: %m", trash_dir);
return -1;
}
ret = rename(path, trash_dest);
}
if (ret == 0)
return 1;
else if (errno != ENOENT) {
mailbox_list_set_critical(list, "stat(%s) failed: %m", path);
return -1;
} else {
/* mailbox not found - what about the directory? */
path = mailbox_list_get_path(list, name,
MAILBOX_LIST_PATH_TYPE_DIR);
if (stat(path, &st) == 0) {
/* delete the directory */
} else if (errno == ENOENT) {
mailbox_list_set_error(list, MAIL_ERROR_NOTFOUND,
T_MAIL_ERR_MAILBOX_NOT_FOUND(name));
return -1;
} else if (!mailbox_list_set_error_from_errno(list)) {
mailbox_list_set_critical(list, "stat(%s) failed: %m",
path);
return -1;
}
return 0;
}
}
int dbox_list_delete_mailbox2(struct mailbox_list *list, const char *name,
int ret, const char *trash_dest)
{
const char *path, *alt_path;
bool deleted = FALSE;
path = mailbox_list_get_path(list, name,
MAILBOX_LIST_PATH_TYPE_MAILBOX);
if (ret > 0) {
if (unlink_directory(trash_dest, TRUE) < 0) {
mailbox_list_set_critical(list,
"unlink_directory(%s) failed: %m", trash_dest);
ret = -1;
}
/* if there's an alt path, delete it too */
alt_path = dbox_get_alt_path(list, path);
if (alt_path != NULL) {
if (unlink_directory(alt_path, TRUE) < 0) {
mailbox_list_set_critical(list,
"unlink_directory(%s) failed: %m", alt_path);
ret = -1;
}
}
/* try to delete the parent directory also */
deleted = TRUE;
path = mailbox_list_get_path(list, name,
MAILBOX_LIST_PATH_TYPE_DIR);
}
alt_path = dbox_get_alt_path(list, path);
if (alt_path != NULL)
(void)rmdir(alt_path);
if (rmdir(path) == 0)
return ret;
else if (errno == ENOTEMPTY) {
if (deleted)
return ret;
mailbox_list_set_error(list, MAIL_ERROR_NOTFOUND,
t_strdup_printf("Directory %s isn't empty, "
"can't delete it.", name));
} else if (!mailbox_list_set_error_from_errno(list)) {
mailbox_list_set_critical(list, "rmdir() failed for %s: %m",
path);
}
return -1;
}