view src/lib-index/mail-cache-fields.c @ 2929:ba9062032877 HEAD

Locking fixes and cleanups
author Timo Sirainen <tss@iki.fi>
date Sat, 04 Dec 2004 23:55:41 +0200
parents d416b6d0a7ee
children 2ab037df6cf3
line wrap: on
line source

/* Copyright (C) 2004 Timo Sirainen */

#include "lib.h"
#include "buffer.h"
#include "hash.h"
#include "file-cache.h"
#include "write-full.h"
#include "mail-cache-private.h"

#include <stddef.h>

#define CACHE_HDR_PREFETCH 1024

void mail_cache_register_fields(struct mail_cache *cache,
				struct mail_cache_field *fields,
				size_t fields_count)
{
	void *orig_key, *orig_value;
	unsigned int new_idx;
	size_t i;

	new_idx = cache->fields_count;
	for (i = 0; i < fields_count; i++) {
		if (hash_lookup_full(cache->field_name_hash, fields[i].name,
				     &orig_key, &orig_value)) {
			fields[i].idx =
				POINTER_CAST_TO(orig_value, unsigned int);
			continue;
		}

		fields[i].idx = new_idx++;
	}

	if (new_idx == cache->fields_count)
		return;

	/* @UNSAFE */
	cache->fields = i_realloc(cache->fields,
				  cache->fields_count * sizeof(*cache->fields),
				  new_idx * sizeof(*cache->fields));
	cache->field_file_map =
		i_realloc(cache->field_file_map,
			  cache->fields_count * sizeof(*cache->field_file_map),
			  new_idx * sizeof(*cache->field_file_map));

	for (i = 0; i < fields_count; i++) {
		unsigned int idx = fields[i].idx;

		if (idx < cache->fields_count)
			continue;

		/* new index - save it */
		cache->fields[idx].field = fields[i];
		cache->fields[idx].field.name =
			p_strdup(cache->field_pool, fields[i].name);
		cache->field_file_map[idx] = (uint32_t)-1;

		switch (cache->fields[idx].field.type) {
		case MAIL_CACHE_FIELD_FIXED_SIZE:
		case MAIL_CACHE_FIELD_BITMASK:
			break;
		case MAIL_CACHE_FIELD_VARIABLE_SIZE:
		case MAIL_CACHE_FIELD_STRING:
		case MAIL_CACHE_FIELD_HEADER:
			cache->fields[idx].field.field_size = (unsigned int)-1;
			break;
		}

		hash_insert(cache->field_name_hash,
			    (char *)cache->fields[idx].field.name,
			    POINTER_CAST(idx));
	}
	cache->fields_count = new_idx;
}

unsigned int
mail_cache_register_lookup(struct mail_cache *cache, const char *name)
{
	void *orig_key, *orig_value;

	if (hash_lookup_full(cache->field_name_hash, name,
			     &orig_key, &orig_value))
		return POINTER_CAST_TO(orig_value, unsigned int);
	else
		return (unsigned int)-1;
}

static int mail_cache_header_fields_get_offset(struct mail_cache *cache,
					       uint32_t *offset_r)
{
	const struct mail_cache_header_fields *field_hdr;
	uint32_t offset, next_offset;

	if (MAIL_CACHE_IS_UNUSABLE(cache)) {
		*offset_r = 0;
		return 0;
	}

	/* find the latest header */
	offset = 0;
	next_offset =
		mail_index_offset_to_uint32(cache->hdr->field_header_offset);
	while (next_offset != 0) {
		if (next_offset == offset) {
			mail_cache_set_corrupted(cache,
				"next_offset in field header loops");
			return -1;
		}
		offset = next_offset;

		if (cache->file_cache != NULL) {
			/* we can't trust that the cached data is valid */
			file_cache_invalidate(cache->file_cache, offset,
					      sizeof(*field_hdr) +
					      CACHE_HDR_PREFETCH);
		}
		if (mail_cache_map(cache, offset,
				   sizeof(*field_hdr) + CACHE_HDR_PREFETCH) < 0)
			return -1;

		field_hdr = CONST_PTR_OFFSET(cache->data, offset);
		next_offset =
			mail_index_offset_to_uint32(field_hdr->next_offset);
	}

	*offset_r = offset;
	return 0;
}

int mail_cache_header_fields_read(struct mail_cache *cache)
{
	const struct mail_cache_header_fields *field_hdr = NULL;
	struct mail_cache_field field;
	const uint32_t *last_used, *sizes;
	const uint8_t *types, *decisions;
	const char *p, *names, *end;
	void *orig_key, *orig_value;
	uint32_t offset, i;

	if (mail_cache_header_fields_get_offset(cache, &offset) < 0)
		return -1;

	if (offset == 0) {
		/* no fields - the file is empty */
		return 0;
	}

	field_hdr = CONST_PTR_OFFSET(cache->data, offset);
	if (offset + field_hdr->size > cache->mmap_length) {
		mail_cache_set_corrupted(cache,
					 "field header points outside file");
		return -1;
	}

	/* check the fixed size of the header. name[] has to be checked
	   separately */
	if (field_hdr->size < sizeof(*field_hdr) +
	    field_hdr->fields_count * (sizeof(uint32_t)*2 + 1 + 2)) {
		mail_cache_set_corrupted(cache, "invalid field header size");
		return -1;
	}

	if (field_hdr->size > sizeof(*field_hdr) + CACHE_HDR_PREFETCH) {
		if (cache->file_cache != NULL) {
			/* we can't trust that the cached data is valid */
			file_cache_invalidate(cache->file_cache, offset,
					      sizeof(*field_hdr) +
					      CACHE_HDR_PREFETCH);
		}
		if (mail_cache_map(cache, offset, field_hdr->size) < 0)
			return -1;
	}
	field_hdr = CONST_PTR_OFFSET(cache->data, offset);

	cache->file_field_map =
		i_realloc(cache->file_field_map,
			  cache->file_fields_count * sizeof(unsigned int),
			  field_hdr->fields_count * sizeof(unsigned int));
	cache->file_fields_count = field_hdr->fields_count;

	last_used = CONST_PTR_OFFSET(field_hdr, MAIL_CACHE_FIELD_LAST_USED());
	sizes = CONST_PTR_OFFSET(field_hdr,
		MAIL_CACHE_FIELD_SIZE(field_hdr->fields_count));
	types = CONST_PTR_OFFSET(field_hdr,
		MAIL_CACHE_FIELD_TYPE(field_hdr->fields_count));
	decisions = CONST_PTR_OFFSET(field_hdr,
		MAIL_CACHE_FIELD_DECISION(field_hdr->fields_count));
	names = CONST_PTR_OFFSET(field_hdr,
		MAIL_CACHE_FIELD_NAMES(field_hdr->fields_count));
	end = CONST_PTR_OFFSET(field_hdr, field_hdr->size);

	/* clear the old mapping */
	for (i = 0; i < cache->fields_count; i++)
		cache->field_file_map[i] = (uint32_t)-1;

	memset(&field, 0, sizeof(field));
	for (i = 0; i < field_hdr->fields_count; i++) {
		for (p = names; p != end && *p != '\0'; p++) ;
		if (p == end || *names == '\0') {
			mail_cache_set_corrupted(cache,
				"field header names corrupted");
			return -1;
		}

		if (hash_lookup_full(cache->field_name_hash, names,
				     &orig_key, &orig_value)) {
			/* already exists, see if decision can be updated */
			field.idx = POINTER_CAST_TO(orig_value, unsigned int);
			if (!cache->fields[field.idx].decision_dirty) {
				cache->fields[field.idx].field.decision =
					decisions[i];
			}
			if (cache->fields[field.idx].field.type != types[i]) {
				mail_cache_set_corrupted(cache,
					"registered field type changed");
				return -1;
			}
		} else {
			field.name = names;
			field.type = types[i];
			field.field_size = sizes[i];
			field.decision = decisions[i];
			mail_cache_register_fields(cache, &field, 1);
		}
		cache->field_file_map[field.idx] = i;
		cache->file_field_map[i] = field.idx;

		/* update last_used if it's newer than ours */
		if (last_used[i] > cache->fields[field.idx].last_used)
			cache->fields[field.idx].last_used = last_used[i];

                names = p + 1;
	}
	return 0;
}

static void copy_to_buf(struct mail_cache *cache, buffer_t *dest,
			size_t offset, size_t size)
{
	const void *data;
	unsigned int i, field;

	for (i = 0; i < cache->file_fields_count; i++) {
		field = cache->file_field_map[i];
                data = CONST_PTR_OFFSET(&cache->fields[field], offset);
		buffer_append(dest, data, size);
	}
	for (i = 0; i < cache->fields_count; i++) {
		if (cache->field_file_map[i] != (uint32_t)-1)
			continue;
		data = CONST_PTR_OFFSET(&cache->fields[i], offset);
		buffer_append(dest, data, size);
	}
}

static void copy_to_buf_byte(struct mail_cache *cache, buffer_t *dest,
			     size_t offset)
{
	const int *data;
	unsigned int i, field;
	uint8_t byte;

	for (i = 0; i < cache->file_fields_count; i++) {
		field = cache->file_field_map[i];
                data = CONST_PTR_OFFSET(&cache->fields[field], offset);
		byte = (uint8_t)*data;
		buffer_append(dest, &byte, 1);
	}
	for (i = 0; i < cache->fields_count; i++) {
		if (cache->field_file_map[i] != (uint32_t)-1)
			continue;
		data = CONST_PTR_OFFSET(&cache->fields[i], offset);
		byte = (uint8_t)*data;
		buffer_append(dest, &byte, 1);
	}
}

static int mail_cache_header_fields_update_locked(struct mail_cache *cache)
{
	buffer_t *buffer;
	uint32_t i, offset;
	int ret = 0;

	if (mail_cache_header_fields_read(cache) < 0 ||
	    mail_cache_header_fields_get_offset(cache, &offset) < 0)
		return -1;

	t_push();
	buffer = buffer_create_dynamic(pool_datastack_create(), 256);

	copy_to_buf(cache, buffer,
		    offsetof(struct mail_cache_field_private, last_used),
		    sizeof(uint32_t));
	ret = pwrite_full(cache->fd, buffer->data,
			  sizeof(uint32_t) * cache->file_fields_count,
			  offset + MAIL_CACHE_FIELD_LAST_USED());
	if (ret == 0) {
		buffer_set_used_size(buffer, 0);
		copy_to_buf_byte(cache, buffer,
				 offsetof(struct mail_cache_field, decision));

		ret = pwrite_full(cache->fd, buffer->data,
			sizeof(uint8_t) * cache->file_fields_count, offset +
			MAIL_CACHE_FIELD_DECISION(cache->file_fields_count));

		if (ret == 0) {
			for (i = 0; i < cache->file_fields_count; i++)
				cache->fields[i].decision_dirty = FALSE;
		}
	}
	t_pop();

	if (ret == 0)
		cache->field_header_write_pending = FALSE;

	return ret;
}

int mail_cache_header_fields_update(struct mail_cache *cache)
{
	int ret;

	if (cache->locked)
		return mail_cache_header_fields_update_locked(cache);

	if (mail_cache_lock(cache) <= 0)
		return -1;

	ret = mail_cache_header_fields_update_locked(cache);
	mail_cache_unlock(cache);
	return ret;
}

void mail_cache_header_fields_get(struct mail_cache *cache, buffer_t *dest)
{
	struct mail_cache_header_fields hdr;
	unsigned int field;
	const char *name;
	uint32_t i;

	memset(&hdr, 0, sizeof(hdr));
	hdr.fields_count = cache->fields_count;
	buffer_append(dest, &hdr, sizeof(hdr));

	/* we have to keep the field order for the existing fields. */
	copy_to_buf(cache, dest,
		    offsetof(struct mail_cache_field_private, last_used),
		    sizeof(uint32_t));
	copy_to_buf(cache, dest, offsetof(struct mail_cache_field, field_size),
		    sizeof(uint32_t));
	copy_to_buf_byte(cache, dest, offsetof(struct mail_cache_field, type));
	copy_to_buf_byte(cache, dest,
			 offsetof(struct mail_cache_field, decision));

	i_assert(buffer_get_used_size(dest) == sizeof(hdr) +
		 (sizeof(uint32_t)*2 + 2) * hdr.fields_count);

	for (i = 0; i < cache->file_fields_count; i++) {
		field = cache->file_field_map[i];
		name = cache->fields[field].field.name;
		buffer_append(dest, name, strlen(name)+1);
	}
	for (i = 0; i < cache->fields_count; i++) {
		if (cache->field_file_map[i] != (uint32_t)-1)
			continue;
		name = cache->fields[i].field.name;
		buffer_append(dest, name, strlen(name)+1);
	}

	hdr.size = buffer_get_used_size(dest);
	buffer_write(dest, 0, &hdr, sizeof(hdr));

	if ((hdr.size & 3) != 0)
		buffer_append(dest, null4, 4 - (hdr.size & 3));
}

int mail_cache_header_fields_get_next_offset(struct mail_cache *cache,
					     uint32_t *offset_r)
{
	if (mail_cache_header_fields_get_offset(cache, offset_r) < 0)
		return -1;

	if (*offset_r == 0) {
		*offset_r = offsetof(struct mail_cache_header,
				     field_header_offset);
	} else {
		*offset_r += offsetof(struct mail_cache_header_fields,
				      next_offset);
	}
	return 0;
}