/* Copyright (c) 2004-2018 Dovecot authors, see the included COPYING file */
#include "lib.h"
#include "ioloop.h"
#include "array.h"
#include "mmap-util.h"
#include "mail-index-modseq.h"
#include "mail-index-view-private.h"
#include "mail-index-sync-private.h"
#include "mail-transaction-log.h"
#include "mail-transaction-log-private.h"
/* If we have less than this many bytes to sync from log file, don't bother
reading the main index */
static void
{
&prev_seq, &prev_offset);
if (prev_seq == 0) {
/* handling lost changes in view syncing */
return;
}
if (!eol) {
/* previous transaction was an extension introduction.
we probably came here from
mail_index_sync_ext_reset(). if there are any more
views which want to continue syncing it needs the
intro. so back up a bit more.
don't do this in case the last transaction in the
log is the extension intro, so we don't keep trying
to sync it over and over again. */
}
} else {
}
}
}
struct mail_index_map *map)
{
}
static struct mail_index_map *
{
}
return map;
}
struct mail_index_map *
{
}
static int
const char **error_r)
{
/* different seen-flag */
if (hdr->seen_messages_count == 0) {
*error_r = "Seen counter wrong";
return -1;
}
} else {
*error_r = "Seen counter wrong";
return -1;
}
}
}
/* different deleted-flag */
if ((old_flags & MAIL_DELETED) == 0) {
*error_r = "Deleted counter wrong";
return -1;
}
} else {
if (hdr->deleted_messages_count == 0 ||
*error_r = "Deleted counter wrong";
return -1;
}
if (--hdr->deleted_messages_count == 0)
}
}
return 0;
}
static void
{
const char *error;
unsigned int i, count;
for (i = 0; i < count; i++) {
continue;
&error) < 0)
}
}
static void
{
const char *error;
} else {
&error) < 0)
}
}
static void
{
unsigned int i, count;
for (i = 0; i < count; i++) {
if ((flags & MAIL_DELETED) != 0 &&
}
}
static void
{
/* FIXME: does expunge handler's return value matter?
we probably shouldn't disallow expunges if the
handler returns failure.. should it be just changed
to return void? */
}
}
}
static bool
{
/* call expunge handlers only when syncing index file */
return FALSE;
if (!ctx->expunge_handlers_set)
return FALSE;
return TRUE;
}
static void
{
unsigned int i, count;
if (count == 0)
return;
/* call the expunge handlers first */
if (sync_expunge_handlers_init(ctx)) {
for (i = 0; i < count; i++) {
}
}
prev_seq2 = 0;
dest_seq1 = 1;
for (i = 0; i < count; i++) {
}
/* @UNSAFE: move (prev_seq2+1) .. (seq1-1) to its
final location in the map if necessary */
dest_seq1 += move_count;
}
}
/* Final stragglers */
if (orig_rec_count > prev_seq2) {
}
}
{
void *ret;
return ret;
}
{
return FALSE;
/* we'll return TRUE if this modseq change was written within the
transaction that was just committed */
return FALSE;
if (prev_log_offset < trans_start_offset ||
return FALSE;
return TRUE;
}
static int
const struct mail_transaction_modseq_update *u,
unsigned int size)
{
int ret;
for (; u < end; u++) {
if (u->uid == 0)
seq = 0;
continue;
u->modseq_low32;
if (ret < 0) {
"modseqs updated before they were enabled");
return -1;
}
}
return 1;
}
struct mail_index_sync_map_ctx *ctx)
{
void *dest;
"Append with UID %u, but next_uid = %u",
return -1;
}
/* move to memory. the mapping is written when unlocking so we don't
waste time re-mmap()ing multiple times or waste space growing index
file too large */
/* the flags may have changed since it was added to map.
use the updated flags already, so flag counters won't get
broken. */
} else {
/* don't rely on buffer->used being at the correct position.
at least expunges can move it */
}
if ((new_flags & MAIL_INDEX_MAIL_FLAG_DIRTY) != 0 &&
return 1;
}
struct mail_index_sync_map_ctx *ctx)
{
return 1;
if (!MAIL_TRANSACTION_FLAG_UPDATE_IS_INTERNAL(u)) {
u->add_flags | u->remove_flags,
}
if ((u->add_flags & MAIL_INDEX_MAIL_FLAG_DIRTY) != 0 &&
flag_mask = ~u->remove_flags;
if (((u->add_flags | u->remove_flags) &
(MAIL_SEEN | MAIL_DELETED)) == 0) {
/* we're not modifying any counted/lowwatered flags */
}
} else {
}
}
return 1;
}
struct mail_index_sync_map_ctx *ctx)
{
"Header update outside range: %u + %u > %u",
return -1;
}
/* @UNSAFE */
u + 1, u->size);
}
/* next_uid update tried to shrink its value. this can happen
in some race conditions with e.g. with dsync, so just
silently ignore it. */
}
/* the tail offset updates are intended for internal transaction
log handling. we'll update the offset in the header only when
the sync is finished. */
return 1;
}
static int
const struct mail_transaction_header *hdr,
const void *data)
{
int ret = 0;
case MAIL_TRANSACTION_APPEND: {
if (ret <= 0)
break;
}
break;
}
case MAIL_TRANSACTION_EXPUNGE:
/* this is simply a request for expunge */
break;
}
}
break;
}
/* this is simply a request for expunge */
break;
}
}
break;
}
case MAIL_TRANSACTION_FLAG_UPDATE: {
if (ret <= 0)
break;
}
break;
}
case MAIL_TRANSACTION_HEADER_UPDATE: {
unsigned int i;
if (ret <= 0)
break;
if ((i % 4) != 0)
i += 4 - (i % 4);
}
break;
}
case MAIL_TRANSACTION_EXT_INTRO: {
unsigned int i;
&prev_seq, &prev_offset);
/* should be just extra padding */
break;
}
/* name_size checked by _log_view_next() */
if (ret <= 0)
break;
if ((i % 4) != 0)
i += 4 - (i % 4);
}
break;
}
case MAIL_TRANSACTION_EXT_RESET: {
/* old versions have only new_reset_id */
"ext reset: invalid record size");
ret = -1;
break;
}
break;
}
case MAIL_TRANSACTION_EXT_HDR_UPDATE: {
unsigned int i;
"ext hdr update: invalid record size");
ret = -1;
break;
}
if (ret <= 0)
break;
if ((i % 4) != 0)
i += 4 - (i % 4);
}
break;
}
unsigned int i;
"ext hdr update: invalid record size");
ret = -1;
break;
}
if (ret <= 0)
break;
if ((i % 4) != 0)
i += 4 - (i % 4);
}
break;
}
case MAIL_TRANSACTION_EXT_REC_UPDATE: {
unsigned int i, record_size;
"Extension record updated "
"without intro prefix");
ret = -1;
break;
}
if (ctx->cur_ext_ignore) {
ret = 1;
break;
}
/* the record is padded to 32bits in the transaction log */
"ext rec update: invalid record size");
ret = -1;
break;
}
if (ret <= 0)
break;
}
break;
}
case MAIL_TRANSACTION_EXT_ATOMIC_INC: {
"Extension record updated "
"without intro prefix");
ret = -1;
break;
}
if (ctx->cur_ext_ignore) {
ret = 1;
break;
}
if (ret <= 0)
break;
}
break;
}
case MAIL_TRANSACTION_KEYWORD_UPDATE: {
break;
}
case MAIL_TRANSACTION_KEYWORD_RESET: {
break;
}
case MAIL_TRANSACTION_MODSEQ_UPDATE: {
break;
}
/* next sync finishes the deletion */
} else {
/* transaction log reading handles this */
}
break;
break;
break;
break;
default:
"Unknown transaction record type 0x%x",
ret = -1;
break;
}
return ret;
}
const struct mail_transaction_header *hdr,
const void *data)
{
int ret;
T_BEGIN {
} T_END;
return ret;
}
struct mail_index_view *view,
{
}
{
}
{
return;
/* do we have dirty flags anymore? */
break;
}
}
}
#ifdef DEBUG
{
del++;
}
seen++;
else
}
}
#endif
const char *sync_reason)
{
const void *tdata;
int ret;
return 0;
}
/* see if we'd prefer to reopen the index file instead of
syncing the current map from the transaction log.
don't check this if mmap is disabled, because reopening
index causes sync to get lost. */
/* we don't know the index's size, so use the
smallest index size we're willing to read */
} else {
}
/* this isn't necessary correct currently, but it should be
close enough */
if (log_size > start_offset &&
return 0;
}
if (ret <= 0) {
/* if we failed because of a syscall error, make sure
we return a failure. */
return -1;
}
(void)mail_index_fsck(index);
}
/* can't use it. sync by re-reading index. */
return 0;
}
/* we're reading more from log than we would have preferred.
remember that we probably want to rewrite index soon. */
}
/* view referenced the map. avoid unnecessary map cloning by
unreferencing the map while view exists. */
if (had_dirty)
/* if syncing updates the header, it updates hdr_copy_buf
and updates hdr_base to hdr_copy_buf. so the buffer must
initially contain a valid header or we'll break it when
writing it. */
}
&prev_seq, &prev_offset);
if (reset) {
/* Reset the entire index. Leave only indexid and
log_file_seq. */
&prev_seq, &prev_offset);
}
/* FIXME: when transaction sync lock is removed, we'll need to handle
the case when a transaction is committed while mailbox is being
synced ([synced transactions][new transaction][ext transaction]).
this means int_offset contains [synced] and ext_offset contains
all */
&tdata)) > 0) {
&prev_seq, &prev_offset);
/* this has been synced already. we're here only to call
expunge handlers and extension update handlers. */
continue;
continue;
}
/* we'll just skip over broken entries */
}
if (had_dirty)
#ifdef DEBUG
#endif
/* transaction log tracks internally the current tail offset.
besides using header updates, it also updates the offset to skip
over following external transactions to avoid extra unneeded log
reading. */
}
if (!MAIL_INDEX_MAP_IS_IN_MEMORY(map)) {
}
/* restore refcount before closing the view. this is necessary also
if map got cloned, because view closing would otherwise destroy it */
"Synchronization corrupted index header %s: %s",
(void)mail_index_fsck(index);
} else if (sync_map_ctx.errors) {
/* make sure the index looks valid now */
(void)mail_index_fsck(index);
}
}