mail-cache-transaction.c revision c58c35722c23363b83db38641a928274def96725
/* Copyright (c) 2003-2012 Dovecot authors, see the included COPYING file */
#include "lib.h"
#include "ioloop.h"
#include "array.h"
#include "buffer.h"
#include "module-context.h"
#include "file-cache.h"
#include "file-set-size.h"
#include "read-full.h"
#include "write-full.h"
#include "mail-cache-private.h"
#include "mail-index-transaction-private.h"
#include <stddef.h>
#define CACHE_TRANS_CONTEXT(obj) \
struct mail_cache_transaction_ctx {
struct mail_index_transaction_vfuncs super;
struct mail_cache *cache;
struct mail_cache_view *view;
struct mail_index_transaction *trans;
unsigned int tried_compression:1;
unsigned int changes:1;
};
static void mail_index_transaction_cache_reset(struct mail_index_transaction *t)
{
}
static int
{
/* a failed cache commit isn't important enough to fail the entire
index transaction, so we'll just ignore it */
(void)mail_cache_transaction_commit(&ctx);
}
static void
{
}
struct mail_cache_transaction_ctx *
struct mail_index_transaction *t)
{
struct mail_cache_transaction_ctx *ctx;
return ctx;
return ctx;
}
{
ctx->last_rec_pos = 0;
}
{
if (ctx->bytes_written > 0) {
/* we already wrote to the cache file. we can't (or don't want
to) delete that data, so just mark it as deleted space */
if (mail_cache_transaction_lock(ctx) > 0) {
}
}
}
static int
{
struct mail_index_view *view;
struct mail_index_transaction *trans;
int ret;
ret = -1;
} else {
}
return ret;
}
static void
{
const struct mail_index_ext *ext;
int i;
(void)mail_cache_open_and_verify(cache);
return;
}
/* see if we should try to reopen the cache file */
for (i = 0;; i++) {
if (MAIL_CACHE_IS_UNUSABLE(cache))
return;
/* index doesn't have a cache extension, but the cache
file exists (corrupted indexes fixed?). fix it. */
if (i == 2)
break;
} else {
break;
/* index offsets don't match the cache file */
/* the cache file appears to be too old.
reopening should help. */
if (mail_cache_reopen(cache) != 0)
break;
}
}
/* cache file sequence might be broken. it's also possible
that it was just compressed and we just haven't yet seen
the changes in index. try if refreshing index helps.
if not, compress the cache file. */
if (i == 0) {
if (ctx->tried_compression)
break;
/* get the latest reset ID */
return;
} else {
i_assert(i == 1);
(void)mail_cache_transaction_compress(ctx);
}
}
}
{
int ret;
if (ret < 0)
return -1;
if (mail_cache_transaction_compress(ctx) < 0)
return -1;
return mail_cache_transaction_lock(ctx);
} else {
return 0;
}
}
if (ctx->cache_file_seq == 0) {
if (mail_cache_unlock(cache) < 0)
return -1;
return 0;
}
return 1;
}
static int
{
/* write the cache_offsets to index file. records' prev_offset
is updated to point to old cache record when index is being
synced. */
for (i = 0; i < seq_count; i++) {
&write_offset, &old_offset);
if (old_offset != 0) {
/* we added records for this message multiple
times in this same uncommitted transaction.
only the new one will be written to
transaction log, we need to do the linking
ourself here. */
if (old_offset > write_offset) {
write_offset) < 0)
return -1;
} else {
/* if we're combining multiple transactions,
make sure the one with the smallest offset
is written into index. this is required for
non-file-mmaped cache to work properly. */
&old_offset, NULL);
old_offset) < 0)
return -1;
}
}
}
return 0;
}
static int
{
int ret;
if (mail_cache_transaction_lock(ctx) <= 0)
return -1;
/* first write the actual data to cache file */
ret = -1;
else {
/* update records' cache offsets to index */
}
ret = -1;
/* drop the written data from buffer */
ctx->last_rec_pos = 0;
return ret;
}
static void
{
struct mail_cache_record *rec;
void *data;
/* FIXME: here would be a good place to set prev_offset to
avoid doing it later, but avoid circular prev_offsets
when cache is updated multiple times within the same
transaction */
}
static void
{
struct mail_cache_record new_rec;
/* update previously added cache record's size */
ctx->cache_data =
}
}
{
int ret = 0;
if (mail_cache_transaction_flush(ctx) < 0)
ret = -1;
else {
/* successfully wrote everything */
ctx->bytes_written = 0;
}
/* Here would be a good place to do fdatasync() to make sure
everything is written before offsets are updated to index.
However it slows down I/O unneededly and we're pretty good
at catching and fixing cache corruption, so we no longer do
it. */
}
return ret;
}
static int
{
return -1;
return -1;
}
}
/* find offset to the previous header's "next_offset" field */
return -1;
/* update the next_offset offset, so our new header will be found */
return -1;
/* we're adding the first field. hdr_copy needs to be kept
in sync so unlocking won't overwrite it. */
}
return 0;
}
{
unsigned int i;
/* we want to avoid adding all the fields one by one to the cache file,
so just add all of them at once in here. the unused ones get dropped
later when compressing. */
for (i = 0; i < cache->fields_count; i++) {
if (set)
}
}
unsigned int field_idx)
{
int ret;
if (mail_cache_transaction_lock(ctx) <= 0) {
if (MAIL_CACHE_IS_UNUSABLE(cache))
return -1;
/* if we compressed the cache, the field should be there now.
it's however possible that someone else just compressed it
and we only reopened the cache file. */
return 0;
/* need to add it */
if (mail_cache_transaction_lock(ctx) <= 0)
return -1;
}
/* re-read header to make sure we don't lose any fields. */
if (mail_cache_header_fields_read(cache) < 0) {
(void)mail_cache_unlock(cache);
return -1;
}
/* it was already added */
if (mail_cache_unlock(cache) < 0)
return -1;
return 0;
}
T_BEGIN {
} T_END;
if (ret == 0) {
/* we wrote all the headers, so there are no pending changes */
}
"Cache file %s: Newly added field got "
ret = -1;
}
if (mail_cache_unlock(cache) < 0)
ret = -1;
return ret;
}
{
unsigned int fixed_size;
int ret;
return;
if (ctx->cache_file_seq == 0) {
/* cache was compressed within this transaction */
}
/* we'll have to add this field to headers */
if (ret < 0)
return;
if (ctx->cache_file_seq == 0)
}
/* remember roughly what we have modified, so cache lookups can
look into transactions to see changes. */
}
/* remember that this value exists, in case we try to look it up */
if (fixed_size == (unsigned int)-1)
full_size += sizeof(data_size32);
ctx->last_rec_pos > 0) {
/* time to flush our buffer. if flushing fails because the
cache file had been compressed and was reopened, return
without adding the cached data since cache_data buffer
doesn't contain the cache_rec anymore. */
if (mail_cache_transaction_flush(ctx) < 0) {
/* make sure the transaction is reset, so we don't
constantly try to flush for each call to this
function */
return;
}
}
if (fixed_size == -1U) {
sizeof(data_size32));
}
if ((data_size & 3) != 0)
}
{
switch (decision) {
case MAIL_CACHE_DECISION_NO:
return FALSE;
case MAIL_CACHE_DECISION_TEMP:
/* add it only if it's newer than what we would drop when
compressing */
if (ctx->first_new_seq == 0) {
ctx->first_new_seq =
}
return FALSE;
break;
default:
break;
}
}
{
return FALSE;
}
{
}
{
const struct mail_cache_record *rec;
if (MAIL_CACHE_IS_UNUSABLE(cache))
return -1;
/* this function is called for each added cache record (or cache
extension record update actually) with new_offset pointing to the
new record and old_offset pointing to the previous record.
we want to keep the old and new records linked so both old and new
cached data is found. normally they are already linked correctly.
the problem only comes when multiple processes are adding cache
records at the same time. we'd rather not lose those additions, so
force the linking order to be new_offset -> old_offset if it isn't
already. */
return -1;
"Cache record offset %u points outside file",
return -1;
}
/* link is already correct */
return 0;
}
return -1;
return 0;
}
{
const struct mail_cache_record *rec;
struct mail_cache_loop_track loop_track;
int ret = 0;
/* we'll only update the deleted_space in header. we can't really
do any actual deleting as other processes might still be using
the data. also it's actually useful as some index views are still
able to ask cached data from messages that have already been
expunged. */
while (offset != 0 &&
"record list is circular");
return -1;
}
}
return ret;
}
{
int ret;
T_BEGIN {
} T_END;
return ret;
}