view src/lib-index/mail-index-write.c @ 22326:ba1a35c5ead7

lib-index: Track .log.2 rotation time in index header This avoids unnecessarily stat()ing the file. Also it's a bit better since it's tracking the actual rotation time, not the mtime of what the .log file happened to have at the time of rotation. The initial rotation timestamp is written only to the dovecot.index header without going through dovecot.index.log. This works, because the dovecot.index is written practically always after a log rotation. For the rare cases when it doesn't happen, the dovecot.index.log.2 just gets deleted later after the next log rotation.
author Timo Sirainen <timo.sirainen@dovecot.fi>
date Tue, 11 Jul 2017 15:35:16 +0300
parents 2e2563132d5f
children cb108f786fb4
line wrap: on
line source

/* Copyright (c) 2003-2017 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "read-full.h"
#include "write-full.h"
#include "ostream.h"
#include "mail-index-private.h"
#include "mail-transaction-log-private.h"

#include <stdio.h>

#define MAIL_INDEX_MIN_UPDATE_SIZE 1024
/* if we're updating >= count-n messages, recreate the index */
#define MAIL_INDEX_MAX_OVERWRITE_NEG_SEQ_COUNT 10

static int mail_index_create_backup(struct mail_index *index)
{
	const char *backup_path, *tmp_backup_path;
	int ret;

	if (index->fd != -1) {
		/* we very much want to avoid creating a backup file that
		   hasn't been written to disk yet */
		if (fdatasync(index->fd) < 0) {
			mail_index_set_error(index, "fdatasync(%s) failed: %m",
					     index->filepath);
			return -1;
		}
	}

	backup_path = t_strconcat(index->filepath, ".backup", NULL);
	tmp_backup_path = t_strconcat(backup_path, ".tmp", NULL);
	ret = link(index->filepath, tmp_backup_path);
	if (ret < 0 && errno == EEXIST) {
		if (unlink(tmp_backup_path) < 0 && errno != ENOENT) {
			mail_index_set_error(index, "unlink(%s) failed: %m",
					     tmp_backup_path);
			return -1;
		}
		ret = link(index->filepath, tmp_backup_path);
	}
	if (ret < 0) {
		if (errno == ENOENT) {
			/* no dovecot.index file, ignore */
			return 0;
		}
		mail_index_set_error(index, "link(%s, %s) failed: %m",
				     index->filepath, tmp_backup_path);
		return -1;
	}

	if (rename(tmp_backup_path, backup_path) < 0) {
		mail_index_set_error(index, "rename(%s, %s) failed: %m",
				     tmp_backup_path, backup_path);
		return -1;
	}
	return 0;
}

static int mail_index_recreate(struct mail_index *index)
{
	struct mail_index_map *map = index->map;
	struct ostream *output;
	unsigned int base_size;
	const char *path;
	int ret = 0, fd;

	i_assert(!MAIL_INDEX_IS_IN_MEMORY(index));
	i_assert(map->hdr.indexid == index->indexid);
	i_assert((map->hdr.flags & MAIL_INDEX_HDR_FLAG_CORRUPTED) == 0);
	i_assert(index->indexid != 0);

	fd = mail_index_create_tmp_file(index, index->filepath, &path);
	if (fd == -1)
		return -1;

	output = o_stream_create_fd_file(fd, 0, FALSE);
	o_stream_cork(output);

	base_size = I_MIN(map->hdr.base_header_size, sizeof(map->hdr));
	o_stream_nsend(output, &map->hdr, base_size);
	o_stream_nsend(output, CONST_PTR_OFFSET(map->hdr_base, base_size),
		       map->hdr.header_size - base_size);
	o_stream_nsend(output, map->rec_map->records,
		       map->rec_map->records_count * map->hdr.record_size);
	o_stream_nflush(output);
	if (o_stream_nfinish(output) < 0) {
		mail_index_file_set_syscall_error(index, path, "write()");
		ret = -1;
	}
	o_stream_destroy(&output);

	if (ret == 0 && index->fsync_mode != FSYNC_MODE_NEVER) {
		if (fdatasync(fd) < 0) {
			mail_index_file_set_syscall_error(index, path,
							  "fdatasync()");
			ret = -1;
		}
	}

	if (close(fd) < 0) {
		mail_index_file_set_syscall_error(index, path, "close()");
		ret = -1;
	}

	if ((index->flags & MAIL_INDEX_OPEN_FLAG_KEEP_BACKUPS) != 0)
		(void)mail_index_create_backup(index);

	if (ret == 0 && rename(path, index->filepath) < 0) {
		mail_index_set_error(index, "rename(%s, %s) failed: %m",
				     path, index->filepath);
		ret = -1;
	}

	if (ret < 0)
		i_unlink(path);
	return ret;
}

void mail_index_write(struct mail_index *index, bool want_rotate)
{
	struct mail_index_map *map = index->map;
	struct mail_index_header *hdr = &map->hdr;

	i_assert(index->log_sync_locked);

	if (index->readonly)
		return;

	/* rotate the .log before writing index, so the index will point to
	   the latest log. */
	if (want_rotate &&
	    hdr->log_file_seq == index->log->head->hdr.file_seq &&
	    hdr->log_file_tail_offset == hdr->log_file_head_offset) {
		if (mail_transaction_log_rotate(index->log, FALSE) == 0) {
			struct mail_transaction_log_file *file =
				index->log->head;
			i_assert(file->hdr.prev_file_seq == hdr->log_file_seq);
			i_assert(file->hdr.prev_file_offset == hdr->log_file_head_offset);
			hdr->log_file_seq = file->hdr.file_seq;
			hdr->log_file_head_offset =
				hdr->log_file_tail_offset = file->hdr.hdr_size;
			/* Assume .log.2 was created successfully. If it
			   wasn't, it just causes an extra stat() and gets
			   fixed later on. */
			hdr->log2_rotate_time = ioloop_time;
		}
	}

	if (!MAIL_INDEX_IS_IN_MEMORY(index)) {
		if (mail_index_recreate(index) < 0) {
			(void)mail_index_move_to_memory(index);
			return;
		}
	}

	index->last_read_log_file_seq = hdr->log_file_seq;
	index->last_read_log_file_tail_offset = hdr->log_file_tail_offset;
}