maildir-sync-index.c revision d61a5e0e4ff58d1aa613f0d51161e5bb0f092514
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen/* Copyright (C) 2007 Timo Sirainen */
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen#include "lib.h"
767431e5084a037c4dbefdf30ebfa03c84b1f449Timo Sirainen#include "ioloop.h"
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen#include "array.h"
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen#include "maildir-storage.h"
5fb3f13537dffd15a31e997da133a721c0728af8Timo Sirainen#include "index-sync-changes.h"
97437f768d1a3e6134fed1971202803fd250eef2Timo Sirainen#include "maildir-uidlist.h"
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen#include "maildir-keywords.h"
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen#include "maildir-filename.h"
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen#include "maildir-sync.h"
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen
cf63dc8723b971cc80638fccbf494d961cbafc7fTimo Sirainen#include <stdio.h>
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen#include <unistd.h>
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen
e09c7dc961cb9cab04ec7cc79215c2f6318fbde0Timo Sirainenstruct maildir_index_sync_context {
23878bd03d1de531e3261a25598beec621351910Timo Sirainen struct maildir_mailbox *mbox;
23878bd03d1de531e3261a25598beec621351910Timo Sirainen struct maildir_sync_context *maildir_sync_ctx;
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen struct mail_index_view *view;
cf63dc8723b971cc80638fccbf494d961cbafc7fTimo Sirainen struct mail_index_sync_ctx *sync_ctx;
cf63dc8723b971cc80638fccbf494d961cbafc7fTimo Sirainen struct maildir_keywords_sync_ctx *keywords_sync_ctx;
cf63dc8723b971cc80638fccbf494d961cbafc7fTimo Sirainen struct mail_index_transaction *trans;
cf63dc8723b971cc80638fccbf494d961cbafc7fTimo Sirainen
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen struct maildir_uidlist_sync_ctx *uidlist_sync_ctx;
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen struct index_sync_changes_context *sync_changes;
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen enum mail_flags flags;
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen ARRAY_TYPE(keyword_indexes) keywords;
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen uint32_t seq, uid;
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen bool changed;
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen};
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainenstruct maildir_keywords_sync_ctx *
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainenmaildir_sync_get_keywords_sync_ctx(struct maildir_index_sync_context *ctx)
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen{
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen return ctx->keywords_sync_ctx;
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen}
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainenstatic int maildir_expunge(struct maildir_mailbox *mbox, const char *path,
7358272563d8ef77366447708ab0e58c0cff4151Timo Sirainen struct maildir_index_sync_context *ctx)
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen{
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen struct mailbox *box = &mbox->ibox.box;
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen
7358272563d8ef77366447708ab0e58c0cff4151Timo Sirainen if (unlink(path) == 0) {
7358272563d8ef77366447708ab0e58c0cff4151Timo Sirainen if (box->v.sync_notify != NULL) {
97437f768d1a3e6134fed1971202803fd250eef2Timo Sirainen box->v.sync_notify(box, ctx->uid,
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen MAILBOX_SYNC_TYPE_EXPUNGE);
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen }
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen mail_index_expunge(ctx->trans, ctx->seq);
23878bd03d1de531e3261a25598beec621351910Timo Sirainen ctx->changed = TRUE;
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen return 1;
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen }
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen if (errno == ENOENT)
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen return 0;
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen mail_storage_set_critical(&mbox->storage->storage,
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen "unlink(%s) failed: %m", path);
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen return -1;
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen}
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainenstatic int maildir_sync_flags(struct maildir_mailbox *mbox, const char *path,
0f9a8663b0ff6fe30389d02284a2b002c40914ebTimo Sirainen struct maildir_index_sync_context *ctx)
bd417d416988d11a6b555b9aa57779e7ed976951Timo Sirainen{
a9efdb661eb7a8a33aacfdcc3486dcc675a21543Timo Sirainen struct mailbox *box = &mbox->ibox.box;
a9efdb661eb7a8a33aacfdcc3486dcc675a21543Timo Sirainen const char *dir, *fname, *newfname, *newpath;
fab850a6aee4aaef4f4795bd7946807a3ba45041Timo Sirainen enum mailbox_sync_type sync_type = 0;
bd417d416988d11a6b555b9aa57779e7ed976951Timo Sirainen uint8_t flags8;
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen fname = strrchr(path, '/');
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen i_assert(fname != NULL);
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen fname++;
97437f768d1a3e6134fed1971202803fd250eef2Timo Sirainen dir = t_strdup_until(path, fname);
97437f768d1a3e6134fed1971202803fd250eef2Timo Sirainen
97afa073e3e1e0301dc41173ec34beb08edcce50Timo Sirainen /* get the current flags and keywords */
97afa073e3e1e0301dc41173ec34beb08edcce50Timo Sirainen maildir_filename_get_flags(ctx->keywords_sync_ctx,
b8b085f7bc6f1c0367802a9f00062bbbd981690dTimo Sirainen fname, &ctx->flags, &ctx->keywords);
b8b085f7bc6f1c0367802a9f00062bbbd981690dTimo Sirainen
b8b085f7bc6f1c0367802a9f00062bbbd981690dTimo Sirainen /* apply changes */
94ba4820927b906b333e39445c1508a29387c3aaTimo Sirainen flags8 = ctx->flags;
cf63dc8723b971cc80638fccbf494d961cbafc7fTimo Sirainen index_sync_changes_apply(ctx->sync_changes, NULL,
23878bd03d1de531e3261a25598beec621351910Timo Sirainen &flags8, &ctx->keywords, &sync_type);
23878bd03d1de531e3261a25598beec621351910Timo Sirainen ctx->flags = flags8;
23878bd03d1de531e3261a25598beec621351910Timo Sirainen
23878bd03d1de531e3261a25598beec621351910Timo Sirainen /* and try renaming with the new name */
23878bd03d1de531e3261a25598beec621351910Timo Sirainen newfname = maildir_filename_set_flags(ctx->keywords_sync_ctx, fname,
23878bd03d1de531e3261a25598beec621351910Timo Sirainen ctx->flags, &ctx->keywords);
23878bd03d1de531e3261a25598beec621351910Timo Sirainen newpath = t_strconcat(dir, newfname, NULL);
23878bd03d1de531e3261a25598beec621351910Timo Sirainen if (rename(path, newpath) == 0) {
23878bd03d1de531e3261a25598beec621351910Timo Sirainen if (box->v.sync_notify != NULL)
23878bd03d1de531e3261a25598beec621351910Timo Sirainen box->v.sync_notify(box, ctx->uid, sync_type);
23878bd03d1de531e3261a25598beec621351910Timo Sirainen
23878bd03d1de531e3261a25598beec621351910Timo Sirainen ctx->changed = TRUE;
ecbbdf594f9329fc15a182bd6c7c4a7fb144ed74Timo Sirainen return 1;
ecbbdf594f9329fc15a182bd6c7c4a7fb144ed74Timo Sirainen }
b8b085f7bc6f1c0367802a9f00062bbbd981690dTimo Sirainen if (errno == ENOENT)
94ba4820927b906b333e39445c1508a29387c3aaTimo Sirainen return 0;
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen
if (!ENOSPACE(errno) && errno != EACCES) {
mail_storage_set_critical(&mbox->storage->storage,
"rename(%s, %s) failed: %m", path, newpath);
}
return -1;
}
static void maildir_handle_uid_insertion(struct maildir_index_sync_context *ctx,
enum maildir_uidlist_rec_flag uflags,
const char *filename, uint32_t uid)
{
int ret;
if ((uflags & MAILDIR_UIDLIST_REC_FLAG_NONSYNCED) != 0) {
/* partial syncing */
return;
}
/* most likely a race condition: we read the maildir, then someone else
expunged messages and committed changes to index. so, this message
shouldn't actually exist. */
if ((uflags & MAILDIR_UIDLIST_REC_FLAG_RACING) == 0) {
/* mark it racy and check in next sync */
ctx->mbox->maildir_hdr.cur_check_time = 0;
maildir_uidlist_add_flags(ctx->mbox->uidlist, filename,
MAILDIR_UIDLIST_REC_FLAG_RACING);
return;
}
if (ctx->uidlist_sync_ctx == NULL) {
ret = maildir_uidlist_sync_init(ctx->mbox->uidlist,
MAILDIR_UIDLIST_SYNC_PARTIAL,
&ctx->uidlist_sync_ctx);
i_assert(ret > 0);
}
uflags &= (MAILDIR_UIDLIST_REC_FLAG_NEW_DIR |
MAILDIR_UIDLIST_REC_FLAG_RECENT);
maildir_uidlist_sync_remove(ctx->uidlist_sync_ctx, filename);
ret = maildir_uidlist_sync_next(ctx->uidlist_sync_ctx,
filename, uflags);
i_assert(ret > 0);
/* give the new UID to it immediately */
maildir_uidlist_sync_finish(ctx->uidlist_sync_ctx);
i_warning("Maildir %s: Expunged message reappeared, giving a new UID "
"(old uid=%u, file=%s)", ctx->mbox->path, uid, filename);
}
int maildir_sync_index_begin(struct maildir_mailbox *mbox,
struct maildir_sync_context *maildir_sync_ctx,
struct maildir_index_sync_context **ctx_r)
{
struct maildir_index_sync_context *ctx;
struct mail_index_sync_ctx *sync_ctx;
struct mail_index_view *view;
struct mail_index_transaction *trans;
if (mail_index_sync_begin(mbox->ibox.index, &sync_ctx, &view, &trans,
(uint32_t)-1, (uoff_t)-1, 0) <= 0) {
mail_storage_set_index_error(&mbox->ibox);
return -1;
}
ctx = i_new(struct maildir_index_sync_context, 1);
ctx->mbox = mbox;
ctx->maildir_sync_ctx = maildir_sync_ctx;
ctx->sync_ctx = sync_ctx;
ctx->view = view;
ctx->trans = trans;
ctx->keywords_sync_ctx =
maildir_keywords_sync_init(mbox->keywords, mbox->ibox.index);
ctx->sync_changes = index_sync_changes_init(&mbox->ibox, ctx->sync_ctx,
ctx->view, ctx->trans,
mbox->ibox.readonly);
*ctx_r = ctx;
return 0;
}
int maildir_sync_index_finish(struct maildir_index_sync_context **_ctx,
bool failed, bool cancel)
{
struct maildir_index_sync_context *ctx = *_ctx;
struct maildir_mailbox *mbox = ctx->mbox;
int ret = failed ? -1 : 0;
*_ctx = NULL;
if (ret < 0 || cancel)
mail_index_sync_rollback(&ctx->sync_ctx);
else {
/* Set syncing_commit=TRUE so that if any sync callbacks try
to access mails which got lost (eg. expunge callback trying
to open the file which was just unlinked) we don't try to
start a second index sync and crash. */
mbox->syncing_commit = TRUE;
if (mail_index_sync_commit(&ctx->sync_ctx) < 0) {
mail_storage_set_index_error(&mbox->ibox);
ret = -1;
} else {
mbox->ibox.commit_log_file_seq = 0;
mbox->ibox.commit_log_file_offset = 0;
}
mbox->syncing_commit = FALSE;
}
maildir_keywords_sync_deinit(ctx->keywords_sync_ctx);
ctx->keywords_sync_ctx = NULL;
index_sync_changes_deinit(&ctx->sync_changes);
i_free(ctx);
return ret;
}
static void
maildir_index_update_ext_header(struct maildir_mailbox *mbox,
struct mail_index_transaction *trans)
{
const void *data;
size_t data_size;
if (mail_index_get_header_ext(mbox->ibox.view, mbox->maildir_ext_id,
&data, &data_size) < 0)
data_size = 0;
if (data_size == sizeof(mbox->maildir_hdr) &&
memcmp(data, &mbox->maildir_hdr, data_size) == 0) {
/* nothing changed */
} else {
mail_index_update_header_ext(trans, mbox->maildir_ext_id, 0,
&mbox->maildir_hdr,
sizeof(mbox->maildir_hdr));
}
}
int maildir_sync_index(struct maildir_index_sync_context *ctx,
bool partial)
{
struct maildir_mailbox *mbox = ctx->mbox;
struct mail_index_view *view = ctx->view;
struct maildir_uidlist_iter_ctx *iter;
struct mail_index_transaction *trans = ctx->trans;
const struct mail_index_header *hdr;
struct mail_index_header empty_hdr;
const struct mail_index_record *rec;
uint32_t seq, uid, prev_uid;
enum maildir_uidlist_rec_flag uflags;
const char *filename;
ARRAY_TYPE(keyword_indexes) idx_keywords;
uint32_t uid_validity, next_uid;
unsigned int changes = 0;
int ret = 0;
bool expunged, full_rescan = FALSE;
i_assert(!mbox->syncing_commit);
i_assert(maildir_uidlist_is_locked(ctx->mbox->uidlist));
hdr = mail_index_get_header(view);
uid_validity = maildir_uidlist_get_uid_validity(mbox->uidlist);
if (uid_validity != hdr->uid_validity &&
uid_validity != 0 && hdr->uid_validity != 0) {
/* uidvalidity changed and mailbox isn't being initialized,
reset mailbox so we can add all messages as new */
i_warning("Maildir %s: UIDVALIDITY changed (%u -> %u)",
mbox->path, hdr->uid_validity, uid_validity);
mail_index_reset(trans);
memset(&empty_hdr, 0, sizeof(empty_hdr));
empty_hdr.next_uid = 1;
hdr = &empty_hdr;
}
mbox->syncing_commit = TRUE;
seq = prev_uid = 0;
t_array_init(&ctx->keywords, MAILDIR_MAX_KEYWORDS);
t_array_init(&idx_keywords, MAILDIR_MAX_KEYWORDS);
iter = maildir_uidlist_iter_init(mbox->uidlist);
while (maildir_uidlist_iter_next(iter, &uid, &uflags, &filename)) {
maildir_filename_get_flags(ctx->keywords_sync_ctx, filename,
&ctx->flags, &ctx->keywords);
i_assert(uid > prev_uid);
prev_uid = uid;
/* the private flags are kept only in indexes. don't use them
at all even for newly seen mails */
ctx->flags &= ~mbox->private_flags_mask;
if ((uflags & MAILDIR_UIDLIST_REC_FLAG_RECENT) != 0 &&
(uflags & MAILDIR_UIDLIST_REC_FLAG_NEW_DIR) != 0 &&
(uflags & MAILDIR_UIDLIST_REC_FLAG_MOVED) == 0) {
/* mail is recent for next session as well */
ctx->flags |= MAIL_RECENT;
}
__again:
ctx->seq = ++seq;
ctx->uid = uid;
if (seq > hdr->messages_count) {
if (uid < hdr->next_uid) {
maildir_handle_uid_insertion(ctx, uflags,
filename, uid);
seq--;
continue;
}
mail_index_append(trans, uid, &seq);
mail_index_update_flags(trans, seq, MODIFY_REPLACE,
ctx->flags);
if (array_count(&ctx->keywords) > 0) {
struct mail_keywords *kw;
kw = mail_index_keywords_create_from_indexes(
trans, &ctx->keywords);
mail_index_update_keywords(trans, seq,
MODIFY_REPLACE, kw);
mail_index_keywords_free(&kw);
}
continue;
}
if (mail_index_lookup(view, seq, &rec) < 0) {
mail_storage_set_index_error(&mbox->ibox);
ret = -1;
break;
}
if (uid > rec->uid) {
/* expunged */
mail_index_expunge(trans, seq);
goto __again;
}
if (uid < rec->uid) {
maildir_handle_uid_insertion(ctx, uflags,
filename, uid);
seq--;
continue;
}
if (index_sync_changes_read(ctx->sync_changes, rec->uid,
&expunged) < 0) {
ret = -1;
break;
}
if (expunged) {
if (maildir_file_do(ctx->mbox, ctx->uid,
maildir_expunge, ctx) >= 0) {
/* successful expunge */
mail_index_expunge(trans, ctx->seq);
}
if ((++changes % MAILDIR_SLOW_MOVE_COUNT) == 0)
maildir_sync_notify(ctx->maildir_sync_ctx);
continue;
}
/* the private flags are stored only in indexes, keep them */
ctx->flags |= rec->flags & mbox->private_flags_mask;
if ((rec->flags & MAIL_RECENT) != 0) {
index_mailbox_set_recent(&mbox->ibox, seq);
if (mbox->ibox.keep_recent) {
ctx->flags |= MAIL_RECENT;
} else {
mail_index_update_flags(trans, seq,
MODIFY_REMOVE,
MAIL_RECENT);
}
}
if ((uflags & MAILDIR_UIDLIST_REC_FLAG_NONSYNCED) != 0) {
/* partial syncing */
if ((ctx->flags & MAIL_RECENT) != 0) {
/* we last saw this mail in new/, but it's
not there anymore. possibly expunged,
make sure. */
full_rescan = TRUE;
}
continue;
}
if (index_sync_changes_have(ctx->sync_changes)) {
/* apply flag changes to maildir */
if (maildir_file_do(ctx->mbox, ctx->uid,
maildir_sync_flags, ctx) < 0)
ctx->flags |= MAIL_INDEX_MAIL_FLAG_DIRTY;
if ((++changes % MAILDIR_SLOW_MOVE_COUNT) == 0)
maildir_sync_notify(ctx->maildir_sync_ctx);
}
if ((rec->flags & MAIL_INDEX_MAIL_FLAG_DIRTY) != 0) {
/* we haven't been able to update maildir with this
record's flag changes. don't sync them. */
continue;
}
if ((ctx->flags & ~MAIL_RECENT) !=
(rec->flags & (MAIL_FLAGS_MASK^MAIL_RECENT))) {
/* FIXME: this is wrong if there's pending changes in
transaction log already. it gets fixed in next sync
however.. */
mail_index_update_flags(trans, seq, MODIFY_REPLACE,
ctx->flags);
} else if ((ctx->flags & MAIL_RECENT) == 0 &&
(rec->flags & MAIL_RECENT) != 0) {
/* just remove recent flag */
mail_index_update_flags(trans, seq, MODIFY_REMOVE,
MAIL_RECENT);
}
/* update keywords if they have changed */
if (mail_index_lookup_keywords(view, seq, &idx_keywords) < 0) {
mail_storage_set_index_error(&mbox->ibox);
ret = -1;
break;
}
if (!index_keyword_array_cmp(&ctx->keywords, &idx_keywords)) {
struct mail_keywords *kw;
kw = mail_index_keywords_create_from_indexes(
trans, &ctx->keywords);
mail_index_update_keywords(trans, seq,
MODIFY_REPLACE, kw);
mail_index_keywords_free(&kw);
}
}
maildir_uidlist_iter_deinit(iter);
mbox->syncing_commit = FALSE;
if (ctx->uidlist_sync_ctx != NULL) {
if (maildir_uidlist_sync_deinit(&ctx->uidlist_sync_ctx) < 0)
ret = -1;
}
if (mbox->ibox.box.v.sync_notify != NULL)
mbox->ibox.box.v.sync_notify(&mbox->ibox.box, 0, 0);
if (!partial) {
/* expunge the rest */
for (seq++; seq <= hdr->messages_count; seq++)
mail_index_expunge(trans, seq);
/* next_uid must be updated only in non-partial syncs since
partial syncs don't add the new mails to index. also we'll
have to do it here before syncing index records, since after
that the uidlist's next_uid value may have changed. */
next_uid = maildir_uidlist_get_next_uid(mbox->uidlist);
i_assert(next_uid > prev_uid);
if (hdr->next_uid < next_uid) {
mail_index_update_header(trans,
offsetof(struct mail_index_header, next_uid),
&next_uid, sizeof(next_uid), FALSE);
}
}
if (ctx->changed)
ctx->mbox->maildir_hdr.cur_mtime = time(NULL);
maildir_index_update_ext_header(ctx->mbox, trans);
if (hdr->uid_validity == 0) {
/* get the initial uidvalidity */
uid_validity = maildir_uidlist_get_uid_validity(mbox->uidlist);
if (uid_validity == 0) {
uid_validity = ioloop_time;
maildir_uidlist_set_uid_validity(mbox->uidlist,
uid_validity, 0);
}
} else if (uid_validity == 0) {
maildir_uidlist_set_uid_validity(mbox->uidlist,
hdr->uid_validity,
hdr->next_uid);
}
if (uid_validity != hdr->uid_validity && uid_validity != 0) {
mail_index_update_header(trans,
offsetof(struct mail_index_header, uid_validity),
&uid_validity, sizeof(uid_validity), TRUE);
}
return ret < 0 ? -1 : (full_rescan ? 0 : 1);
}