index-storage.c revision 638600575ee95f2513c683ef09cb188f76eacd22
/* Copyright (c) 2002-2017 Dovecot authors, see the included COPYING file */
#include "lib.h"
#include "array.h"
#include "istream.h"
#include "ostream.h"
#include "ioloop.h"
#include "str.h"
#include "mkdir-parents.h"
#include "dict.h"
#include "mail-index-alloc-cache.h"
#include "mail-index-private.h"
#include "mail-index-modseq.h"
#include "mailbox-log.h"
#include "mailbox-list-private.h"
#include "mail-search-build.h"
#include "index-storage.h"
#include "index-mail.h"
#include "index-attachment.h"
#include "index-thread-private.h"
#include "index-mailbox-size.h"
#include <time.h>
#include <unistd.h>
#define LOCK_NOTIFY_INTERVAL 30
struct index_storage_module index_storage_module =
enum mail_cache_decision_type dec)
{
struct mail_cache_field field;
const char *const *arr;
unsigned int idx;
return;
} else {
i_error("%s: Unknown cache field name '%s', ignoring",
continue;
}
}
}
{
sizeof(global_cache_fields));
/* all caching disabled for now */
return;
}
}
unsigned int secs_left)
{
const char *str;
/* if notify type changes, print the message immediately */
/* first override notification, show it */
} else {
return;
}
}
switch (notify_type) {
case MAILBOX_LOCK_NOTIFY_NONE:
break;
break;
"%u seconds", secs_left);
break;
break;
"will override in %u seconds", secs_left);
break;
}
}
{
}
static int
{
const char *index_dir, *mailbox_path;
&mailbox_path) < 0)
return -1;
&index_dir) <= 0)
box->index_prefix);
return 0;
}
bool auto_boxes ATTR_UNUSED,
enum mailbox_existence *existence_r)
{
}
enum mailbox_existence *existence_r)
{
enum mail_error error;
int ret;
/* see if it's selectable */
if (ret < 0) {
if (error != MAIL_ERROR_NOTFOUND)
return -1;
return 0;
}
if (ret == 0) {
/* no mailboxes in this storage? */
return 0;
}
/* index directory is different - prefer looking it up first
since it might be on a faster storage. since the directory
itself exists also for \NoSelect mailboxes, we'll need to
check the dovecot.index.log existence. */
".log", NULL);
return 0;
}
}
return 0;
}
"stat(%s) failed: %m", path);
return -1;
}
/* see if it's non-selectable */
return 0;
}
return 0;
}
{
const char *cache_dir;
return 0;
return -1;
return -1;
&cache_dir) > 0) {
return -1;
}
struct mail_index_optimization_settings optimization_set = {
.index = {
},
.log = {
},
.cache = {
},
};
return 0;
}
{
int ret;
if (move_to_memory)
if (index_storage_mailbox_alloc_index(box) < 0)
return -1;
/* make sure mail_index_set_permissions() has been called */
(void)mailbox_get_permissions(box);
if (ret <= 0 || move_to_memory) {
if ((index_flags & MAIL_INDEX_OPEN_FLAG_NEVER_IN_MEMORY) != 0) {
return -1;
}
/* try opening once more. it should be created
directly into memory now. */
index_flags) < 0)
i_panic("in-memory index creation failed");
}
}
if ((index_flags & MAIL_INDEX_OPEN_FLAG_NEVER_IN_MEMORY) != 0) {
"Couldn't create index file");
return -1;
}
}
return -1;
}
}
sizeof(struct mailbox_index_vsize), 0,
sizeof(uint64_t));
sizeof(struct mailbox_index_pop3_uidl), 0, 0);
sizeof(uint32_t),
sizeof(uint32_t));
return 0;
}
enum mailbox_flags flags,
const char *index_prefix)
{
static unsigned int mailbox_generation_sequence = 0;
struct index_mailbox_context *ibox;
}
enum mailbox_feature feature)
{
if ((feature & MAILBOX_FEATURE_CONDSTORE) != 0) {
}
return 0;
}
{
}
ibox->sync_last_check = 0;
}
{
}
{
}
static void
const struct mailbox_update *update)
{
const struct mail_cache_field *old_fields;
struct mail_cache_field field;
unsigned int i, j, old_count;
&old_count);
/* There shouldn't be many fields, so don't worry about O(n^2). */
/* see if it's an existing field */
for (j = 0; j < old_count; j++) {
break;
}
if (j != old_count) {
field = old_fields[j];
/* new header */
} else {
/* new unknown field. we can't do anything about
this since we don't know its type */
continue;
}
}
if (array_count(&new_fields) > 0) {
}
}
static int
const struct mailbox_update *update)
{
struct mail_index_transaction *trans;
struct mail_index_view *view;
int ret;
return ret;
if (update->min_highest_modseq != 0 &&
}
return ret;
}
const struct mailbox_update *update)
{
int ret = 0;
if (update->min_highest_pvt_modseq != 0) {
ret = -1;
}
return ret;
}
const struct mailbox_update *update)
{
const struct mail_index_header *hdr;
struct mail_index_view *view;
struct mail_index_transaction *trans;
int ret;
if (mailbox_open(box) < 0)
return -1;
/* make sure we get the latest index info */
if (update->uid_validity != 0 &&
if (hdr->uid_validity != 0) {
/* UIDVALIDITY change requires index to be reset */
}
}
if (update->min_next_uid != 0 &&
}
if (update->min_first_recent_uid != 0 &&
}
if (update->min_highest_modseq != 0 &&
}
return ret < 0 ? -1 :
}
{
const char *path, *p;
enum mailbox_list_path_type type;
enum mailbox_existence existence;
bool create_parent_dir;
int ret;
/* Layout doesn't support creating \NoSelect mailboxes.
Switch to creating a selectable mailbox. */
}
return -1;
if (ret == 0) {
/* layout=none */
"Mailbox creation not supported");
return -1;
}
create_parent_dir = !directory &&
if (create_parent_dir) {
/* we only need to make sure that the parent directory exists */
if (p == NULL)
return 1;
}
return -1;
/* need to also create the directory to index path or
iteration won't find it. */
int ret2;
i_unreached();
return -1;
/* finish partial creation: existed in mail directory,
but not in index directory. */
ret = 1;
}
}
if (ret == 0) {
/* directory already exists */
if (create_parent_dir)
return 1;
might itself exist, but does it have the
cur|new|tmp subdirs? */
return -1;
if (existence != MAILBOX_EXISTENCE_SELECT)
return 1;
}
"Mailbox already exists");
return -1;
}
if (directory) {
/* we only wanted to create the directory and it's done now */
return 0;
}
/* the caller should still create the mailbox */
return 1;
}
{
enum mail_error error;
return 0;
return -1;
}
/* failed directory deletion, but mailbox deletion succeeded.
this was probably maildir++, which internally deleted the
directory as well. add changelog record about that too. */
return 0;
}
static int
enum mail_attribute_type type)
{
struct mailbox_attribute_iter *iter;
const char *key;
int ret = 0;
if (inbox &&
continue;
ret = -1;
break;
}
}
}
if (mailbox_attribute_iter_deinit(&iter) < 0)
ret = -1;
return ret;
}
{
struct mail_search_context *ctx;
struct mailbox_transaction_context *t;
struct mail_search_args *search_args;
if (mailbox_search_deinit(&ctx) < 0) {
return -1;
}
if (mailbox_delete_all_attributes(t, MAIL_ATTRIBUTE_TYPE_PRIVATE) < 0 ||
return -1;
}
if (mailbox_transaction_commit(&t) < 0)
return -1;
/* sync to actually perform the expunges */
return mailbox_sync(box, 0);
}
{
struct mailbox_status status;
/* \noselect mailbox, try deleting only the directory */
return 0;
if (mailbox_is_autocreated(box)) {
/* Return success when trying to delete autocreated
mailbox. The client sees it as existing, so we
shouldn't be returning an error. */
return 0;
}
return -1;
}
/* specifically support symlinked shared mailboxes. a deletion
will simply remove the symlink, not actually expunge any
mails */
return 0;
}
/* we can't easily atomically delete all mails and the mailbox. so:
1) expunge all mails
2) mark the mailbox deleted (modifications after this will fail)
3) check if a race condition between 1) and 2) added any mails:
yes) abort and undelete mailbox
no) finish deleting the mailbox
*/
if (!box->deleting_must_be_empty) {
if (mailbox_expunge_all_data(box) < 0)
return -1;
}
return -1;
return -1;
;
else if (box->deleting_must_be_empty) {
"Mailbox isn't empty");
return -1;
} else {
"New mails were added to mailbox during deletion");
return -1;
}
}
return 1;
}
{
struct mailbox_metadata metadata;
int ret_guid;
/* Make sure the indexes are closed before trying to delete the
directory that contains them. It can still fail with some NFS
implementations if indexes are opened by another session, but
that can't really be helped. */
return -1;
}
if (ret_guid == 0) {
}
return -1;
/* we deleted the mailbox, but couldn't delete the directory
because it has children. that's not an error. */
}
return 0;
}
{
int ret;
return ret;
/* mails have been now successfully deleted. some mailbox formats may
at this point do some other deletion that is required for it.
the _post() deletion will close the index and delete the
directory. */
return index_storage_mailbox_delete_post(box);
}
{
return -1;
}
if (mailbox_open(dest) == 0) {
struct mail_index_transaction *t =
/* can't do much if this fails anyways */
(void)mail_index_transaction_commit(&t);
}
/* we'll track mailbox names, instead of GUIDs. We may be renaming a
non-selectable mailbox (directory), which doesn't even have a GUID */
return 0;
}
{
struct mail_index_transaction *trans =
if (mail_index_transaction_commit(&trans) < 0) {
return -1;
}
return 0;
}
{
}
{
}
{
}
static void
{
const struct mail_cache_field *dest_field;
unsigned int src_field_idx, dest_field_idx;
uint32_t t;
if (dest_field_idx == UINT_MAX) {
/* unknown field */
return;
}
if ((dest_field->decision &
/* field not wanted in destination mailbox */
return;
}
buffer_set_used_size(buf, 0);
/* save date must update when mail is copied */
t = ioloop_time;
buffer_append(buf, &t, sizeof(t));
/* error / not found */
buffer_set_used_size(buf, 0);
} else {
/* FIXME: until mail_cache_lookup() can read unwritten
cached data from buffer, we'll do this optimization
to make quota plugin's work faster */
struct index_mail *imail =
else
}
/* NOTE: we'll want to add also nonexistent headers, which
will keep the buf empty */
}
if (add) {
}
}
static void
{
unsigned int idx;
bool expunged ATTR_UNUSED;
return;
&idx) &&
}
}
{
T_BEGIN {
const struct mailbox_cache_field *field;
&src_metadata) < 0)
i_unreached();
/* the only reason we're doing the destination lookup is to
make sure that the cache file is opened and the cache
decisions are up to date */
&dest_metadata) < 0)
i_unreached();
}
} T_END;
}
{
struct mail_namespace *ns;
const char *subs_name;
else {
/* subscriptions=no namespace, find another one where we can
add the subscription to */
"This namespace has no subscriptions");
return -1;
}
/* use <orig ns prefix><orig storage name> as the
subscription name */
/* drop the common prefix (typically there isn't one) */
}
return -1;
}
/* subscriptions are about names, not about mailboxes. it's possible
to have a subscription to nonexistent mailbox. renames also don't
change subscriptions. so instead of using actual GUIDs, we'll use
hash of the name. */
return 0;
}
{
}
}
{
return;
}
{
}
{
bool ret;
return ret;
}
struct mail_index_sync_ctx **ctx_r,
struct mail_index_view **view_r,
struct mail_index_transaction **trans_r,
enum mail_index_sync_flags flags)
{
int ret;
/* try to avoid locking vsize updates by checking if we see any
expunges */
if (ret <= 0) {
if (ret < 0)
return ret;
}
/* race condition - need to abort the sync and retry with
the vsize locked */
}
return 1;
}
struct mail *cache_dest_mail)
{
do {
break;
break;
i_unreached();
/* handle below */
break;
if (!mail_storage_set_error_from_errno(storage)) {
"save: write(%s) failed: %s",
}
return -1;
}
if (cache_dest_mail != NULL)
/* both tee input readers may consume data from our primary
input stream. we'll have to make sure we don't return with
one of the streams still having data in them. */
} while (i_stream_read(input) > 0);
if (input->stream_errno != 0) {
return -1;
}
return 0;
}
{
/* Close the mail before it's expunged. This allows it to be
reset cleanly. */
/* currently we can't just drop pending cache updates for this one
specific record, so we'll reset the whole cache transaction. */
}
const char *path)
{
const char *index_path;
/* Could be a race condition or could be because ITERINDEX is used
and the index directory exists, but the storage directory doesn't.
Handle the existence inconsistency by creating this directory if
the index directory exists (don't bother checking if ITERINDEX is
set or not - it doesn't matter since either both dirs should exist
or not). */
&index_path) < 0)
return -1;
/* there's no separate index path - mailbox was just deleted */
/* inconsistency - create also the mail directory */
/* race condition - mailbox was just deleted */
} else {
"stat(%s) failed: %m", index_path);
return -1;
}
return -1;
}