changeset 5933:fdc7e47ccea3 HEAD

dovecot-uidlist can now be updated by appending to it. It's recreated only if there have been enough expunged messages.
author Timo Sirainen <tss@iki.fi>
date Mon, 09 Jul 2007 05:43:03 +0300
parents 6ac8d6c93d34
children 181aa61c182a
files src/lib-storage/index/maildir/maildir-uidlist.c
diffstat 1 files changed, 191 insertions(+), 83 deletions(-) [+]
line wrap: on
line diff
--- a/src/lib-storage/index/maildir/maildir-uidlist.c	Mon Jul 09 04:40:54 2007 +0300
+++ b/src/lib-storage/index/maildir/maildir-uidlist.c	Mon Jul 09 05:43:03 2007 +0300
@@ -27,6 +27,8 @@
 /* how many seconds to wait before overriding uidlist.lock */
 #define UIDLIST_LOCK_STALE_TIMEOUT (60*2)
 
+#define UIDLIST_COMPRESS_PERCENTAGE 75
+
 #define UIDLIST_IS_LOCKED(uidlist) \
 	((uidlist)->lock_count > 0)
 
@@ -44,6 +46,7 @@
 	int fd;
 	dev_t fd_dev;
 	ino_t fd_ino;
+	off_t fd_size;
 
 	unsigned int lock_count;
 
@@ -57,8 +60,11 @@
 
 	unsigned int version;
 	unsigned int uid_validity, next_uid, prev_read_uid, last_seen_uid;
+	unsigned int read_records_count;
 	uint32_t first_recent_uid;
+	uoff_t last_read_offset;
 
+	unsigned int recreate:1;
 	unsigned int initial_read:1;
 	unsigned int initial_sync:1;
 };
@@ -193,6 +199,7 @@
 		uidlist->fd = -1;
 		uidlist->fd_ino = 0;
 	}
+	uidlist->last_read_offset = 0;
 }
 
 void maildir_uidlist_deinit(struct maildir_uidlist *uidlist)
@@ -250,13 +257,6 @@
 	}
         uidlist->last_seen_uid = uid;
 
-	if (uid >= uidlist->next_uid) {
-                mail_storage_set_critical(&uidlist->mbox->storage->storage,
-			"UID larger than next_uid in file %s (%u >= %u)",
-			uidlist->path, uid, uidlist->next_uid);
-		return 0;
-	}
-
 	while (*line == ' ') line++;
 
 	if (uidlist->version == 2) {
@@ -281,29 +281,76 @@
 	return 1;
 }
 
+static int maildir_uidlist_read_header(struct maildir_uidlist *uidlist,
+				       struct istream *input)
+{
+	unsigned int uid_validity, next_uid;
+	const char *line;
+
+	line = i_stream_read_next_line(input);
+        if (line == NULL) {
+                /* I/O error / empty file */
+                return input->stream_errno == 0 ? 0 : -1;
+	}
+
+	if (sscanf(line, "%u %u %u", &uidlist->version,
+		   &uid_validity, &next_uid) != 3 ||
+	    uidlist->version < 1 || uidlist->version > 2) {
+		/* broken file */
+		mail_storage_set_critical(&uidlist->mbox->storage->storage,
+			"Corrupted header in file %s (version = %u)",
+			uidlist->path, uidlist->version);
+		return 0;
+	}
+	if (uid_validity == 0 || next_uid == 0) {
+		mail_storage_set_critical(&uidlist->mbox->storage->storage,
+			"%s: Broken header (uidvalidity = %u, next_uid=%u)",
+			uidlist->path, uid_validity, next_uid);
+		return 0;
+	}
+
+	uidlist->uid_validity = uid_validity;
+	uidlist->next_uid = next_uid;
+	return 1;
+}
+
 static int
 maildir_uidlist_update_read(struct maildir_uidlist *uidlist,
 			    bool *retry_r, bool try_retry)
 {
 	struct mail_storage *storage = &uidlist->mbox->storage->storage;
 	const char *line;
-	unsigned int uid_validity, next_uid;
+	unsigned int orig_next_uid;
 	struct istream *input;
 	struct stat st;
+	uoff_t last_read_offset;
 	int fd, ret;
 
 	*retry_r = FALSE;
 
-	maildir_uidlist_close(uidlist);
-
-	fd = nfs_safe_open(uidlist->path, O_RDONLY);
-	if (fd == -1) {
-		if (errno != ENOENT) {
+	if (uidlist->fd == -1) {
+		fd = nfs_safe_open(uidlist->path, O_RDWR);
+		if (fd == -1) {
+			if (errno != ENOENT) {
+				mail_storage_set_critical(storage,
+					"open(%s) failed: %m", uidlist->path);
+				return -1;
+			}
+			return 0;
+		}
+		last_read_offset = 0;
+	} else {
+		/* the file was updated */
+		fd = uidlist->fd;
+		if (lseek(fd, 0, SEEK_SET) < 0) {
 			mail_storage_set_critical(storage,
-				"open(%s) failed: %m", uidlist->path);
+				"lseek(%s) failed: %m", uidlist->path);
 			return -1;
 		}
-		return 0;
+		uidlist->fd = -1;
+		uidlist->fd_ino = 0;
+		last_read_offset = uidlist->last_read_offset;
+		uidlist->last_read_offset = 0;
 	}
 
 	if (fstat(fd, &st) < 0) {
@@ -325,49 +372,38 @@
 							    st.st_size/8));
 	}
 
-	uidlist->version = 0;
-
 	input = i_stream_create_file(fd, default_pool, 4096, FALSE);
+	i_stream_seek(input, uidlist->last_read_offset);
 
-	/* get header */
-	line = i_stream_read_next_line(input);
-        if (line == NULL) {
-                /* I/O error / empty file */
-                ret = input->stream_errno == 0 ? 0 : -1;
-        } else if (sscanf(line, "%u %u %u", &uidlist->version,
-                          &uid_validity, &next_uid) != 3 ||
-                   uidlist->version < 1 || uidlist->version > 2) {
-                /* broken file */
-                mail_storage_set_critical(storage,
-			"Corrupted header in file %s (version = %u)",
-			uidlist->path, uidlist->version);
-		ret = 0;
-	} else if (uid_validity == uidlist->uid_validity &&
-		   next_uid < uidlist->next_uid) {
-                mail_storage_set_critical(storage,
-			"%s: next_uid was lowered (%u -> %u)",
-			uidlist->path, uidlist->next_uid, next_uid);
-		ret = 0;
-	} else if (uid_validity == 0 || next_uid == 0) {
-                mail_storage_set_critical(storage,
-			"%s: Broken header (uidvalidity = %u, next_uid=%u)",
-			uidlist->path, uid_validity, next_uid);
-		ret = 0;
-	} else {
-		uidlist->uid_validity = uid_validity;
-		uidlist->next_uid = next_uid;
+	orig_next_uid = uidlist->next_uid;
+	ret = input->v_offset != 0 ? 1 :
+		maildir_uidlist_read_header(uidlist, input);
+	if (ret > 0) {
 		uidlist->prev_read_uid = 0;
 		uidlist->change_counter++;
+		uidlist->read_records_count = 0;
 
 		ret = 1;
 		while ((line = i_stream_read_next_line(input)) != NULL) {
+			uidlist->read_records_count++;
 			if (!maildir_uidlist_next(uidlist, line)) {
 				ret = 0;
 				break;
 			}
                 }
-                if (input->stream_errno != 0)
+		if (input->stream_errno != 0)
                         ret = -1;
+
+		if (uidlist->next_uid <= uidlist->prev_read_uid)
+			uidlist->next_uid = uidlist->prev_read_uid + 1;
+		if (uidlist->next_uid < orig_next_uid) {
+			mail_storage_set_critical(storage,
+				"%s: next_uid was lowered (%u -> %u)",
+				uidlist->path, orig_next_uid,
+				uidlist->next_uid);
+			uidlist->recreate = TRUE;
+			uidlist->next_uid = orig_next_uid;
+		}
 	}
 
         if (ret == 0) {
@@ -378,6 +414,8 @@
 		uidlist->fd = fd;
 		uidlist->fd_dev = st.st_dev;
 		uidlist->fd_ino = st.st_ino;
+		uidlist->fd_size = st.st_size;
+		uidlist->last_read_offset = input->v_offset;
         } else {
                 /* I/O error */
                 if (input->stream_errno == ESTALE && try_retry)
@@ -397,41 +435,62 @@
 	return ret;
 }
 
-int maildir_uidlist_refresh(struct maildir_uidlist *uidlist)
+static int
+maildir_uidlist_has_changed(struct maildir_uidlist *uidlist, bool *recreated_r)
 {
 	struct mail_storage *storage = &uidlist->mbox->storage->storage;
         struct stat st;
+
+	*recreated_r = FALSE;
+
+	/* FIXME: nfs attribute cache flush */
+	if (nfs_safe_stat(uidlist->path, &st) < 0) {
+		if (errno != ENOENT) {
+			mail_storage_set_critical(storage,
+				"stat(%s) failed: %m", uidlist->path);
+			return -1;
+		}
+		return 0;
+	}
+
+	if (st.st_ino != uidlist->fd_ino ||
+	    !CMP_DEV_T(st.st_dev, uidlist->fd_dev)) {
+		/* file recreated */
+		*recreated_r = TRUE;
+		return 1;
+	} else if (st.st_size != uidlist->fd_size) {
+		/* file modified but not recreated */
+		return 1;
+	} else {
+		/* unchanged */
+		return 0;
+	}
+}
+
+int maildir_uidlist_refresh(struct maildir_uidlist *uidlist)
+{
         unsigned int i;
-        bool retry;
+        bool retry, recreated;
         int ret;
 
 	if (uidlist->fd != -1) {
-		if (nfs_safe_stat(uidlist->path, &st) < 0) {
-			if (errno != ENOENT) {
-				mail_storage_set_critical(storage,
-					"stat(%s) failed: %m", uidlist->path);
-				return -1;
-			}
-			return 0;
-		}
+		ret = maildir_uidlist_has_changed(uidlist, &recreated);
+		if (ret <= 0)
+			return ret;
 
-		if (st.st_ino == uidlist->fd_ino &&
-		    CMP_DEV_T(st.st_dev, uidlist->fd_dev)) {
-			/* unchanged */
-			return 1;
-		}
+		if (recreated)
+			maildir_uidlist_close(uidlist);
 	}
 
         for (i = 0; ; i++) {
 		ret = maildir_uidlist_update_read(uidlist, &retry,
 						i < UIDLIST_ESTALE_RETRY_COUNT);
-                if (!retry) {
-                        if (ret >= 0)
-                                uidlist->initial_read = TRUE;
-                        break;
-                }
+		if (!retry)
+			break;
                 /* ESTALE - try reopening and rereading */
         }
+	if (ret >= 0)
+		uidlist->initial_read = TRUE;
         return ret;
 }
 
@@ -561,8 +620,9 @@
 		uidlist->next_uid = next_uid;
 }
 
-static int maildir_uidlist_rewrite_fd(struct maildir_uidlist *uidlist, int fd,
-				      const char *path)
+static int maildir_uidlist_write_fd(struct maildir_uidlist *uidlist, int fd,
+				    const char *path, unsigned int first_idx,
+				    uoff_t *file_size_r)
 {
 	struct mail_storage *storage = &uidlist->mbox->storage->storage;
 	struct maildir_uidlist_iter_ctx *iter;
@@ -573,32 +633,44 @@
 	const char *filename;
 	int ret;
 
-	uidlist->version = 1;
+	i_assert(fd != -1);
+
 	output = o_stream_create_file(fd, default_pool, 0, FALSE);
+	str = t_str_new(512);
 
-	if (uidlist->uid_validity == 0) {
-		/* Get UIDVALIDITY from index */
-		const struct mail_index_header *hdr;
+	if (output->offset == 0) {
+		i_assert(first_idx == 0);
+		uidlist->version = 1;
+		if (uidlist->uid_validity == 0) {
+			/* Get UIDVALIDITY from index */
+			const struct mail_index_header *hdr;
 
-		hdr = mail_index_get_header(uidlist->mbox->ibox.view);
-		uidlist->uid_validity = hdr->uid_validity;
-		i_assert(uidlist->uid_validity != 0);
+			hdr = mail_index_get_header(uidlist->mbox->ibox.view);
+			uidlist->uid_validity = hdr->uid_validity;
+			i_assert(uidlist->uid_validity != 0);
+		}
+
+		str_printfa(str, "%u %u %u\n", uidlist->version,
+			    uidlist->uid_validity, uidlist->next_uid);
+		o_stream_send(output, str_data(str), str_len(str));
+	} else {
+		i_assert(first_idx != 0);
 	}
 
-	str = t_str_new(512);
-	str_printfa(str, "%u %u %u\n", uidlist->version,
-		    uidlist->uid_validity, uidlist->next_uid);
-	o_stream_send(output, str_data(str), str_len(str));
+	iter = maildir_uidlist_iter_init(uidlist->mbox->uidlist);
+	iter->next += first_idx;
 
-	iter = maildir_uidlist_iter_init(uidlist->mbox->uidlist);
 	while (maildir_uidlist_iter_next(iter, &uid, &flags, &filename)) {
 		str_truncate(str, 0);
 		str_printfa(str, "%u %s\n", uid, filename);
 		o_stream_send(output, str_data(str), str_len(str));
 	}
 	maildir_uidlist_iter_deinit(iter);
+	o_stream_flush(output);
 
 	ret = output->stream_errno == 0 ? 0 : -1;
+
+	*file_size_r = output->offset;
 	o_stream_unref(&output);
 
 	if (ret < 0) {
@@ -625,6 +697,7 @@
 	const char *temp_path;
 	struct stat st;
 	mode_t old_mask;
+	uoff_t file_size;
 	int fd, ret;
 
 	temp_path = t_strconcat(mbox->control_dir,
@@ -647,7 +720,7 @@
 		}
 	}
 
-	ret = maildir_uidlist_rewrite_fd(uidlist, fd, temp_path);
+	ret = maildir_uidlist_write_fd(uidlist, fd, temp_path, 0, &file_size);
 	if (ret == 0) {
 		if (rename(temp_path, uidlist->path) < 0) {
 			mail_storage_set_critical(&mbox->storage->storage,
@@ -667,13 +740,42 @@
 		(void)close(fd);
 		ret = -1;
 	} else {
+		i_assert(file_size == (uoff_t)st.st_size);
 		uidlist->fd = fd;
 		uidlist->fd_dev = st.st_dev;
 		uidlist->fd_ino = st.st_ino;
+		uidlist->fd_size = st.st_size;
+		uidlist->last_read_offset = st.st_size;
 	}
 	return ret;
 }
 
+static int maildir_uidlist_update(struct maildir_uidlist_sync_ctx *ctx)
+{
+	struct maildir_uidlist *uidlist = ctx->uidlist;
+	uoff_t file_size;
+
+	if (ctx->uidlist->recreate || uidlist->fd == -1 ||
+	    (uidlist->read_records_count + ctx->new_files_count) *
+	    UIDLIST_COMPRESS_PERCENTAGE / 100 >= array_count(&uidlist->records))
+		return maildir_uidlist_recreate(uidlist);
+
+	i_assert(ctx->first_new_pos != 0);
+
+	if (lseek(uidlist->fd, 0, SEEK_END) < 0) {
+		mail_storage_set_critical(&uidlist->mbox->storage->storage,
+			"lseek(%s) failed: %m", uidlist->path);
+		return -1;
+	}
+
+	if (maildir_uidlist_write_fd(uidlist, uidlist->fd, uidlist->path,
+				     ctx->first_new_pos, &file_size) < 0)
+		return -1;
+
+	uidlist->last_read_offset = file_size;
+	return 0;
+}
+
 static void maildir_uidlist_mark_all(struct maildir_uidlist *uidlist,
 				     bool nonsynced)
 {
@@ -851,6 +953,9 @@
 
 	i_assert(ctx->partial);
 
+	if (ctx->first_new_pos != 0)
+		ctx->first_new_pos--;
+
 	rec = hash_lookup(ctx->uidlist->files, filename);
 	i_assert(rec != NULL);
 
@@ -863,6 +968,7 @@
 	array_delete(&ctx->uidlist->records, pos - recs, 1);
 
 	ctx->changed = TRUE;
+	ctx->uidlist->recreate = TRUE;
 }
 
 const char *
@@ -943,8 +1049,10 @@
 	uidlist->record_pool = ctx->record_pool;
 	ctx->record_pool = NULL;
 
-	if (ctx->new_files_count != 0)
-		maildir_uidlist_assign_uids(ctx, count - ctx->new_files_count);
+	if (ctx->new_files_count != 0) {
+		ctx->first_new_pos = count - ctx->new_files_count;
+		maildir_uidlist_assign_uids(ctx, ctx->first_new_pos);
+	}
 
 	ctx->uidlist->change_counter++;
 }
@@ -983,7 +1091,7 @@
 
 	if (ctx->changed && !ctx->failed) {
 		t_push();
-		ret = maildir_uidlist_recreate(ctx->uidlist);
+		ret = maildir_uidlist_update(ctx);
 		t_pop();
 	}