changeset 5784:9493c7f1ebca HEAD

Updating index file by overwriting changed parts didn't work correctly.
author Timo Sirainen <tss@iki.fi>
date Wed, 20 Jun 2007 01:34:18 +0300
parents 92bc0a7580f6
children bcf58c66a099
files src/lib-index/mail-index-map.c src/lib-index/mail-index-private.h src/lib-index/mail-index-write.c
diffstat 3 files changed, 93 insertions(+), 27 deletions(-) [+]
line wrap: on
line diff
--- a/src/lib-index/mail-index-map.c	Wed Jun 20 01:08:13 2007 +0300
+++ b/src/lib-index/mail-index-map.c	Wed Jun 20 01:34:18 2007 +0300
@@ -697,8 +697,13 @@
 		return ret;
 	}
 
+	index->last_read_log_file_seq = new_map->hdr.log_file_seq;
+	index->last_read_log_file_head_offset =
+		new_map->hdr.log_file_head_offset;
 	index->last_read_log_file_tail_offset =
 		new_map->hdr.log_file_tail_offset;
+	index->last_read_stat = st;
+
 	mail_index_unmap(index, map);
 	*map = new_map;
 	return 1;
--- a/src/lib-index/mail-index-private.h	Wed Jun 20 01:08:13 2007 +0300
+++ b/src/lib-index/mail-index-private.h	Wed Jun 20 01:34:18 2007 +0300
@@ -6,6 +6,8 @@
 #include "mail-index-view-private.h"
 #include "mail-index-transaction-private.h"
 
+#include <sys/stat.h>
+
 struct mail_transaction_header;
 struct mail_transaction_log_view;
 struct mail_index_sync_map_ctx;
@@ -158,9 +160,14 @@
 
 	struct mail_index_map *map;
 	uint32_t indexid;
-	/* last known log_file_tail_offset in main index file. used for
-	   optimizing main index updates. */
+	/* last_read_log_file_* contains the seq/offsets we last read from
+	   the main index file's headers. these are used ro figure out when
+	   the main index file should be updated, and if we can update it
+	   by writing on top of it or if we need to recreate it. */
+	uint32_t last_read_log_file_seq;
+	uint32_t last_read_log_file_head_offset;
 	uint32_t last_read_log_file_tail_offset;
+	struct stat last_read_stat;
 
 	int lock_type, shared_lock_count, excl_lock_count;
 	unsigned int lock_id;
--- a/src/lib-index/mail-index-write.c	Wed Jun 20 01:08:13 2007 +0300
+++ b/src/lib-index/mail-index-write.c	Wed Jun 20 01:34:18 2007 +0300
@@ -1,12 +1,17 @@
 /* Copyright (C) 2003-2007 Timo Sirainen */
 
 #include "lib.h"
+#include "read-full.h"
 #include "write-full.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_recreate(struct mail_index *index)
 {
 	struct mail_index_map *map = index->map;
@@ -68,27 +73,6 @@
 	if (MAIL_INDEX_IS_IN_MEMORY(index))
 		return 0;
 
-	/* write records. */
-	if (map->write_seq_first != 0) {
-		size_t rec_offset =
-			(map->write_seq_first-1) * map->hdr.record_size;
-
-		if (pwrite_full(index->fd,
-				CONST_PTR_OFFSET(map->records, rec_offset),
-				(map->write_seq_last -
-				 map->write_seq_first + 1) *
-				map->hdr.record_size,
-				map->hdr.header_size + rec_offset) < 0)
-			return -1;
-	}
-
-	/* write base header. it has changed practically always, so
-	   map->write_base_header might not be TRUE here in all situations.
-	   It's used only to figure out if we want to write the map at all. */
-	base_size = I_MIN(map->hdr.base_header_size, sizeof(map->hdr));
-	if (pwrite_full(index->fd, &map->hdr, base_size, 0) < 0)
-		return -1;
-
 	/* write extended headers */
 	if (map->write_ext_header) {
 		base_size = map->hdr.base_header_size;
@@ -98,9 +82,51 @@
 				base_size) < 0)
 			return -1;
 	}
+
+	/* write records. */
+	if (map->write_seq_first != 0) {
+		size_t rec_offset =
+			(map->write_seq_first-1) * map->hdr.record_size;
+		size_t recs_size = map->hdr.record_size *
+			(map->write_seq_last - map->write_seq_first + 1);
+
+		if (pwrite_full(index->fd,
+				CONST_PTR_OFFSET(map->records, rec_offset),
+				recs_size,
+				map->hdr.header_size + rec_offset) < 0)
+			return -1;
+	}
+
+	/* Write base header last. If we happen to crash in above pwrites, it
+	   doesn't matter because we haven't yet written log file offsets, so
+	   all the changes will be re-applied and the header/data state will
+	   stay valid.
+
+	   The base header changes practically always, so
+	   map->write_base_header might not be TRUE here in all situations.
+	   It's used only to figure out if we want to write the map at all. */
+	base_size = I_MIN(map->hdr.base_header_size, sizeof(map->hdr));
+	if (pwrite_full(index->fd, &map->hdr, base_size, 0) < 0)
+		return -1;
 	return 0;
 }
 
+static bool mail_index_has_last_changed(struct mail_index *index)
+{
+	struct mail_index_header hdr;
+	int ret;
+
+	if ((ret = pread_full(index->fd, &hdr, sizeof(hdr), 0)) <= 0) {
+		if (ret < 0 && errno != ESTALE)
+			mail_index_set_syscall_error(index, "pread_full()");
+		return TRUE;
+	}
+
+	return hdr.log_file_head_offset !=
+		index->last_read_log_file_head_offset ||
+		hdr.log_file_seq != index->last_read_log_file_seq;
+}
+
 #define mail_index_map_has_changed(map) \
 	((map)->write_base_header || (map)->write_ext_header || \
 	 (map)->write_seq_first != 0)
@@ -109,6 +135,7 @@
 {
 	struct mail_index_map *map = index->map;
 	const struct mail_index_header *hdr = &map->hdr;
+	struct stat st;
 	unsigned int lock_id;
 
 	if (!mail_index_map_has_changed(map))
@@ -118,15 +145,41 @@
 		/* header size growed. we can't update this file anymore. */
 		map->write_atomic = TRUE;
 	}
-	if (index->fd == -1) {
+	if (index->fd == -1 || index->last_read_log_file_seq == 0) {
 		/* index file doesn't exist, it's corrupted or we haven't
 		   opened it for some reason */
 		map->write_atomic = TRUE;
 	}
+
+	if (index->last_read_stat.st_size < MAIL_INDEX_MIN_UPDATE_SIZE ||
+	    (map->write_seq_last - map->write_seq_first + 1) +
+	    MAIL_INDEX_MAX_OVERWRITE_NEG_SEQ_COUNT >= map->records_count) {
+		/* the file is so small that we don't even bother trying to
+		   update it / changes are so large we might as well recreate */
+		map->write_atomic = TRUE;
+	}
+
+	if (!map->write_atomic) {
+		/* we can't update the file unless it's the same as it was
+		   when we last read it. this is the first quick check before
+		   locking. */
+		if (stat(index->filepath, &st) < 0) {
+			if (errno != ENOENT)
+				mail_index_set_syscall_error(index, "stat()");
+			map->write_atomic = TRUE;
+		} else if (st.st_ino != index->last_read_stat.st_ino ||
+			   !CMP_ST_CTIME(&st, &index->last_read_stat))
+			map->write_atomic = TRUE;
+	}
+
 	if (!map->write_atomic) {
 		if (mail_index_try_lock_exclusive(index, &lock_id) <= 0) {
-			/* locking failed, rewrite */
+			/* locking failed, recreate */
 			map->write_atomic = TRUE;
+		} else if (mail_index_has_last_changed(index)) {
+			/* changed, we can't trust updating it anymore */
+			map->write_atomic = TRUE;
+			mail_index_unlock(index, lock_id);
 		}
 	}
 
@@ -139,13 +192,14 @@
 		}
 	} else {
 		if (mail_index_write_map_over(index) < 0) {
-			mail_index_set_error(index,
-				"pwrite_full(%s) failed: %m", index->filepath);
+			mail_index_set_syscall_error(index, "pwrite_full()");
 			mail_index_set_inconsistent(index);
 		}
 		mail_index_unlock(index, lock_id);
 	}
 
+	index->last_read_log_file_seq = hdr->log_file_seq;
+	index->last_read_log_file_head_offset = hdr->log_file_head_offset;
 	index->last_read_log_file_tail_offset = hdr->log_file_tail_offset;
 
 	map->write_atomic = FALSE;