mail-cache-transaction.c revision 0dffa25d211be541ee3c953b23566a1a990789df
/* Copyright (c) 2003-2016 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_rec {
};
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 records_written;
bool tried_compression:1;
bool 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->records_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;
struct mail_cache_compress_lock *lock;
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;
}
const struct mail_cache_record *
unsigned int seq,
unsigned int *trans_next_idx)
{
const struct mail_cache_transaction_rec *recs;
unsigned int i, count;
for (i = *trans_next_idx; i < count; i++) {
*trans_next_idx = i + 1;
recs[i].cache_data_pos);
}
}
*trans_next_idx = i + 1;
/* update the unfinished record's (temporary) size and
return it */
ctx->last_rec_pos);
}
return NULL;
}
static int
{
const struct mail_cache_transaction_rec *recs;
/* 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, NULL);
}
return 0;
}
static int
{
struct mail_index_map *map;
struct mail_cache_record *rec;
const struct mail_cache_transaction_rec *recs;
const uint32_t *prev_offsetp;
const void *data;
for (i = 0; i < seq_count; i++) {
if (*offsetp != 0)
prev_offset = *offsetp;
else {
prev_offsetp = data;
prev_offset = 0;
&reset_id) &&
else
prev_offset = 0;
if (prev_offset >= write_offset) {
"Cache record offset points outside existing file");
return -1;
}
}
if (prev_offset != 0) {
/* link this record to previous one */
} else {
}
*offsetp = write_offset;
}
return 0;
}
static int
{
uint32_t write_offset = 0;
int ret = 0;
/* we had done some changes, but they were aborted. */
return 0;
}
if (mail_cache_transaction_lock(ctx) <= 0)
return -1;
/* we need to get the final write offset for linking records */
if (!ESTALE_FSTAT(errno))
ret = -1;
ret = -1;
} else {
ret = -1;
}
/* write to cache file */
if (ret < 0 ||
ret = -1;
else {
/* update records' cache offsets to index */
ctx->records_written++;
}
ret = -1;
/* drop the written data from buffer */
ctx->last_rec_pos = 0;
return ret;
}
static size_t
{
struct mail_cache_record *rec;
void *data;
}
static void
{
struct mail_cache_transaction_rec *trans_rec;
if (size > MAIL_CACHE_RECORD_MAX_SIZE) {
return;
}
}
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->records_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
{
offset = 0;
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;
sizeof(unsigned int),
(field_idx+1) * sizeof(unsigned int));
}
return 0;
}
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) {
else
}
}
/* 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 == UINT_MAX)
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 == UINT_MAX) {
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;
}