mail-cache.c revision fd2f5fbc1f07aa93e2214a28cdf02437fb7d06c8
0N/A/* Copyright (C) 2003-2004 Timo Sirainen */
0N/A
0N/A#include "lib.h"
0N/A#include "byteorder.h"
0N/A#include "file-lock.h"
0N/A#include "file-set-size.h"
115N/A#include "mmap-util.h"
0N/A#include "write-full.h"
0N/A#include "mail-cache-private.h"
0N/A
0N/A#include <unistd.h>
0N/A
115N/Aunsigned int mail_cache_field_sizes[32] = {
116N/A sizeof(enum mail_cache_record_flag),
0N/A sizeof(struct mail_sent_date),
116N/A sizeof(time_t),
0N/A sizeof(uoff_t),
0N/A
0N/A 0, 0, 0, 0, 0, 0, 0, 0,
115N/A
115N/A /* variable sized */
115N/A (unsigned int)-1, (unsigned int)-1, (unsigned int)-1, (unsigned int)-1,
13N/A (unsigned int)-1, (unsigned int)-1, (unsigned int)-1, (unsigned int)-1,
0N/A (unsigned int)-1, (unsigned int)-1, (unsigned int)-1, (unsigned int)-1,
0N/A (unsigned int)-1, (unsigned int)-1, (unsigned int)-1, (unsigned int)-1,
0N/A (unsigned int)-1, (unsigned int)-1, (unsigned int)-1, (unsigned int)-1
0N/A};
0N/A
0N/Aenum mail_cache_field mail_cache_header_fields[MAIL_CACHE_HEADERS_COUNT] = {
0N/A MAIL_CACHE_HEADERS1,
0N/A MAIL_CACHE_HEADERS2,
0N/A MAIL_CACHE_HEADERS3,
0N/A MAIL_CACHE_HEADERS4
0N/A};
0N/A
115N/Auint32_t mail_cache_uint32_to_offset(uint32_t offset)
115N/A{
115N/A unsigned char buf[4];
115N/A
115N/A i_assert(offset < 0x40000000);
0N/A i_assert((offset & 3) == 0);
0N/A
0N/A offset >>= 2;
0N/A buf[0] = 0x80 | ((offset & 0x0fe00000) >> 21);
0N/A buf[1] = 0x80 | ((offset & 0x001fc000) >> 14);
0N/A buf[2] = 0x80 | ((offset & 0x00003f80) >> 7);
0N/A buf[3] = 0x80 | (offset & 0x0000007f);
0N/A return *((uint32_t *) buf);
299N/A}
0N/A
299N/Auint32_t mail_cache_offset_to_uint32(uint32_t offset)
299N/A{
0N/A const unsigned char *buf = (const unsigned char *) &offset;
0N/A
0N/A if ((offset & 0x80808080) != 0x80808080)
0N/A return 0;
0N/A
205N/A return (((uint32_t)buf[3] & 0x7f) << 2) |
0N/A (((uint32_t)buf[2] & 0x7f) << 9) |
0N/A (((uint32_t)buf[1] & 0x7f) << 16) |
0N/A (((uint32_t)buf[0] & 0x7f) << 23);
0N/A}
0N/A
0N/Aunsigned int mail_cache_field_index(enum mail_cache_field field)
0N/A{
0N/A unsigned int i, num;
0N/A
0N/A for (i = 0, num = 1; i < 32; i++, num <<= 1) {
0N/A if (field == num)
0N/A return i;
0N/A }
0N/A i_unreached();
0N/A}
0N/A
0N/Avoid mail_cache_set_syscall_error(struct mail_cache *cache,
0N/A const char *function)
0N/A{
0N/A i_assert(function != NULL);
0N/A
0N/A if (ENOSPACE(errno)) {
115N/A cache->index->nodiskspace = TRUE;
0N/A return;
116N/A }
0N/A
0N/A mail_index_set_error(cache->index,
0N/A "%s failed with index cache file %s: %m",
0N/A function, cache->filepath);
0N/A}
0N/A
0N/Avoid mail_cache_set_corrupted(struct mail_cache *cache, const char *fmt, ...)
0N/A{
0N/A va_list va;
0N/A
0N/A (void)unlink(cache->filepath);
0N/A mail_cache_file_close(cache);
0N/A
0N/A va_start(va, fmt);
0N/A t_push();
116N/A mail_index_set_error(cache->index, "Corrupted index cache file %s: %s",
116N/A cache->filepath, t_strdup_vprintf(fmt, va));
116N/A t_pop();
116N/A va_end(va);
116N/A}
116N/A
116N/Avoid mail_cache_file_close(struct mail_cache *cache)
116N/A{
116N/A if (cache->mmap_base != NULL) {
116N/A if (munmap(cache->mmap_base, cache->mmap_length) < 0)
116N/A mail_cache_set_syscall_error(cache, "munmap()");
116N/A }
116N/A
116N/A cache->mmap_base = NULL;
116N/A cache->hdr = NULL;
164N/A cache->mmap_length = 0;
116N/A
116N/A if (cache->fd != -1) {
116N/A if (close(cache->fd) < 0)
116N/A mail_cache_set_syscall_error(cache, "close()");
116N/A cache->fd = -1;
116N/A }
116N/A}
116N/A
116N/Aint mail_cache_reopen(struct mail_cache *cache)
116N/A{
116N/A if (MAIL_CACHE_IS_UNUSABLE(cache) && cache->need_compress) {
116N/A /* unusable, we're just waiting for compression */
116N/A return 0;
116N/A }
120N/A
120N/A mail_cache_file_close(cache);
177N/A
177N/A cache->fd = open(cache->filepath, O_RDWR);
120N/A if (cache->fd == -1) {
177N/A if (errno == ENOENT)
177N/A cache->need_compress = TRUE;
177N/A else
177N/A mail_cache_set_syscall_error(cache, "open()");
177N/A return -1;
177N/A }
177N/A
177N/A if (mail_cache_map(cache, 0, 0) < 0)
177N/A return -1;
177N/A
177N/A if (cache->hdr->file_seq != cache->index->hdr->cache_file_seq) {
177N/A /* still different - maybe a race condition or maybe the
177N/A file_seq really is corrupted. either way, this shouldn't
177N/A happen often so we'll just mark cache to be compressed
177N/A later which fixes this. */
177N/A cache->need_compress = TRUE;
120N/A return 0;
0N/A }
0N/A
0N/A return 1;
0N/A}
0N/A
0N/Astatic int mmap_verify_header(struct mail_cache *cache)
0N/A{
0N/A struct mail_cache_header *hdr;
0N/A uint32_t used_file_size;
0N/A
0N/A /* check that the header is still ok */
0N/A if (cache->mmap_length < sizeof(struct mail_cache_header)) {
0N/A mail_cache_set_corrupted(cache, "File too small");
0N/A return FALSE;
0N/A }
0N/A cache->hdr = hdr = cache->mmap_base;
295N/A
295N/A if (cache->hdr->indexid != cache->index->indexid) {
295N/A /* index id changed */
295N/A mail_cache_set_corrupted(cache, "indexid changed");
295N/A return FALSE;
295N/A }
295N/A
295N/A if (cache->trans_ctx != NULL) {
295N/A /* we've updated used_file_size, do nothing */
295N/A return TRUE;
295N/A }
295N/A
295N/A /* only check the header if we're locked */
295N/A if (cache->locks == 0)
295N/A return TRUE;
295N/A
295N/A used_file_size = nbo_to_uint32(hdr->used_file_size);
295N/A if (used_file_size < sizeof(struct mail_cache_header)) {
295N/A mail_cache_set_corrupted(cache, "used_file_size too small");
295N/A return FALSE;
295N/A }
295N/A if ((used_file_size % sizeof(uint32_t)) != 0) {
295N/A mail_cache_set_corrupted(cache, "used_file_size not aligned");
295N/A return FALSE;
295N/A }
301N/A
301N/A if (used_file_size > cache->mmap_length) {
301N/A mail_cache_set_corrupted(cache, "used_file_size too large");
301N/A return FALSE;
301N/A }
336N/A return TRUE;
301N/A}
301N/A
301N/Aint mail_cache_map(struct mail_cache *cache, size_t offset, size_t size)
301N/A{
301N/A if (size == 0)
301N/A size = sizeof(struct mail_cache_header);
301N/A
301N/A if (offset < cache->mmap_length &&
301N/A size <= cache->mmap_length - offset) {
301N/A /* already mapped */
301N/A return 0;
301N/A }
301N/A
301N/A if (cache->mmap_base != NULL) {
301N/A if (cache->locks != 0) {
301N/A /* in the middle of transaction - write the changes */
301N/A if (msync(cache->mmap_base, cache->mmap_length,
301N/A MS_SYNC) < 0) {
301N/A mail_cache_set_syscall_error(cache, "msync()");
301N/A return -1;
301N/A }
301N/A }
301N/A
301N/A if (munmap(cache->mmap_base, cache->mmap_length) < 0)
301N/A mail_cache_set_syscall_error(cache, "munmap()");
301N/A } else {
301N/A if (cache->fd == -1) {
301N/A /* unusable, waiting for compression */
301N/A i_assert(cache->need_compress);
301N/A return -1;
301N/A }
301N/A }
301N/A
301N/A /* map the whole file */
301N/A cache->hdr = NULL;
301N/A cache->mmap_length = 0;
301N/A
301N/A cache->mmap_base = mmap_rw_file(cache->fd, &cache->mmap_length);
301N/A if (cache->mmap_base == MAP_FAILED) {
301N/A cache->mmap_base = NULL;
301N/A mail_cache_set_syscall_error(cache, "mmap()");
301N/A return -1;
301N/A }
0N/A
0N/A if (!mmap_verify_header(cache)) {
0N/A cache->need_compress = TRUE;
0N/A return -1;
0N/A }
13N/A
136N/A return 0;
}
static int mail_cache_open_and_verify(struct mail_cache *cache)
{
cache->filepath = i_strconcat(cache->index->filepath,
MAIL_CACHE_FILE_PREFIX, NULL);
cache->fd = open(cache->filepath, O_RDWR);
if (cache->fd == -1) {
if (errno == ENOENT) {
cache->need_compress = TRUE;
return 0;
}
mail_cache_set_syscall_error(cache, "open()");
return -1;
}
return mail_cache_map(cache, 0, sizeof(struct mail_cache_header));
}
struct mail_cache *mail_cache_open_or_create(struct mail_index *index)
{
struct mail_cache *cache;
cache = i_new(struct mail_cache, 1);
cache->index = index;
cache->fd = -1;
cache->split_header_pool = pool_alloconly_create("Headers", 512);
if (!index->mmap_disable) {
if (mail_cache_open_and_verify(cache) < 0) {
/* failed for some reason - doesn't really matter,
it's disabled for now. */
mail_cache_file_close(cache);
}
}
return cache;
}
void mail_cache_free(struct mail_cache *cache)
{
i_assert(cache->trans_ctx == NULL);
mail_cache_file_close(cache);
pool_unref(cache->split_header_pool);
i_free(cache->filepath);
i_free(cache);
}
void mail_cache_set_defaults(struct mail_cache *cache,
enum mail_cache_field default_cache_fields,
enum mail_cache_field never_cache_fields)
{
cache->default_cache_fields = default_cache_fields;
cache->never_cache_fields = never_cache_fields;
}
int mail_cache_lock(struct mail_cache *cache, int nonblock)
{
int i, ret;
if (cache->locks != 0)
return 1;
if (MAIL_CACHE_IS_UNUSABLE(cache))
return 0;
if (cache->hdr->file_seq != cache->index->hdr->cache_file_seq) {
/* we want the latest cache file */
if ((ret = mail_cache_reopen(cache)) <= 0)
return ret;
}
for (i = 0; i < 3; i++) {
if (nonblock) {
ret = file_try_lock(cache->fd, F_WRLCK);
if (ret < 0) {
mail_cache_set_syscall_error(cache,
"file_try_lock()");
}
} else {
ret = file_wait_lock(cache->fd, F_WRLCK);
if (ret <= 0) {
mail_cache_set_syscall_error(cache,
"file_wait_lock()");
}
}
if (ret <= 0)
break;
if (cache->hdr->file_seq == cache->index->hdr->cache_file_seq) {
/* got it */
cache->locks++;
break;
}
/* okay, so it was just compressed. try again. */
mail_cache_unlock(cache);
if ((ret = mail_cache_reopen(cache)) <= 0)
return ret;
ret = 0;
}
return ret;
}
int mail_cache_unlock(struct mail_cache *cache)
{
if (--cache->locks > 0)
return 0;
if (file_wait_lock(cache->fd, F_UNLCK) <= 0) {
mail_cache_set_syscall_error(cache, "file_wait_lock(F_UNLCK)");
return -1;
}
return 0;
}
int mail_cache_is_locked(struct mail_cache *cache)
{
return cache->locks > 0;
}
struct mail_cache_view *
mail_cache_view_open(struct mail_cache *cache, struct mail_index_view *iview)
{
struct mail_cache_view *view;
view = i_new(struct mail_cache_view, 1);
view->cache = cache;
view->view = iview;
return view;
}
void mail_cache_view_close(struct mail_cache_view *view)
{
i_free(view);
}