changeset 4023:b19ccbaa3802 HEAD

Try to handle ESTALE NFS errors the best way we can.
author Timo Sirainen <timo.sirainen@movial.fi>
date Thu, 16 Feb 2006 17:23:00 +0200
parents 2f26567685ff
children 7d7b0f427d68
files src/lib-index/mail-cache.c src/lib-index/mail-index-private.h src/lib-index/mail-index-sync-keywords.c src/lib-index/mail-index-view.c src/lib-index/mail-index.c src/lib-index/mail-transaction-log.c src/lib-storage/index/maildir/maildir-keywords.c src/lib-storage/index/maildir/maildir-uidlist.c src/lib-storage/subscription-file/subscription-file.c src/lib/Makefile.am src/lib/file-cache.c src/lib/safe-open.c src/lib/safe-open.h
diffstat 13 files changed, 442 insertions(+), 180 deletions(-) [+]
line wrap: on
line diff
--- a/src/lib-index/mail-cache.c	Thu Feb 16 16:58:31 2006 +0200
+++ b/src/lib-index/mail-cache.c	Thu Feb 16 17:23:00 2006 +0200
@@ -180,8 +180,15 @@
 
 		ret = file_cache_read(cache->file_cache, offset, size);
 		if (ret < 0) {
-			// FIXME: ESTALE
-			mail_cache_set_syscall_error(cache, "read()");
+                        /* In case of ESTALE we'll simply fail without error
+                           messages. The caller will then just have to
+                           fallback to generating the value itself.
+
+                           We can't simply reopen the cache flie, because
+                           using it requires also having updated file
+                           offsets. */
+                        if (errno != ESTALE)
+                                mail_cache_set_syscall_error(cache, "read()");
 			return -1;
 		}
 
--- a/src/lib-index/mail-index-private.h	Thu Feb 16 16:58:31 2006 +0200
+++ b/src/lib-index/mail-index-private.h	Thu Feb 16 17:23:00 2006 +0200
@@ -246,8 +246,8 @@
 const struct mail_index_ext *
 mail_index_view_get_ext(struct mail_index_view *view, uint32_t ext_id);
 
-int mail_index_map_read_keywords(struct mail_index *index,
-				 struct mail_index_map *map);
+int mail_index_map_parse_keywords(struct mail_index *index,
+                                  struct mail_index_map *map);
 
 int mail_index_fix_header(struct mail_index *index, struct mail_index_map *map,
 			  struct mail_index_header *hdr, const char **error_r);
--- a/src/lib-index/mail-index-sync-keywords.c	Thu Feb 16 16:58:31 2006 +0200
+++ b/src/lib-index/mail-index-sync-keywords.c	Thu Feb 16 17:23:00 2006 +0200
@@ -16,7 +16,7 @@
 	unsigned int i, count, keyword_idx;
 
 	if (!ctx->keywords_read) {
-		if (mail_index_map_read_keywords(ctx->view->index, map) < 0)
+		if (mail_index_map_parse_keywords(ctx->view->index, map) < 0)
 			return -1;
 		ctx->keywords_read = TRUE;
 	}
@@ -298,8 +298,8 @@
 	}
 
 	if (!ctx->keywords_read) {
-		if (mail_index_map_read_keywords(ctx->view->index,
-						 ctx->view->map) < 0)
+		if (mail_index_map_parse_keywords(ctx->view->index,
+                                                  ctx->view->map) < 0)
 			return -1;
 		ctx->keywords_read = TRUE;
 	}
--- a/src/lib-index/mail-index-view.c	Thu Feb 16 16:58:31 2006 +0200
+++ b/src/lib-index/mail-index-view.c	Thu Feb 16 17:23:00 2006 +0200
@@ -526,11 +526,10 @@
 				continue;
 
 			if (idx >= keyword_count) {
-				/* keyword header is updated, re-read
-				   it so we know what this one is
-				   called */
-				if (mail_index_map_read_keywords(view->index,
-								 map) < 0)
+				/* keyword header was updated, parse it again
+				   it so we know what this keyword is called */
+				if (mail_index_map_parse_keywords(view->index,
+                                                                  map) < 0)
 					return -1;
 
 				if (!array_is_created(&map->keyword_idx_map))
--- a/src/lib-index/mail-index.c	Thu Feb 16 16:58:31 2006 +0200
+++ b/src/lib-index/mail-index.c	Thu Feb 16 17:23:00 2006 +0200
@@ -5,6 +5,7 @@
 #include "buffer.h"
 #include "hash.h"
 #include "mmap-util.h"
+#include "safe-open.h"
 #include "read-full.h"
 #include "write-full.h"
 #include "mail-index-private.h"
@@ -266,8 +267,8 @@
 	return MAIL_INDEX_HEADER_SIZE_ALIGN(size) - size;
 }
 
-static int mail_index_read_extensions(struct mail_index *index,
-				      struct mail_index_map *map)
+static int mail_index_parse_extensions(struct mail_index *index,
+                                       struct mail_index_map *map)
 {
 	const struct mail_index_ext_header *ext_hdr;
 	unsigned int i, old_count;
@@ -377,8 +378,8 @@
 	return TRUE;
 }
 
-int mail_index_map_read_keywords(struct mail_index *index,
-				 struct mail_index_map *map)
+int mail_index_map_parse_keywords(struct mail_index *index,
+                                  struct mail_index_map *map)
 {
 	const struct mail_index_ext *ext;
 	const struct mail_index_keyword_header *kw_hdr;
@@ -486,7 +487,7 @@
 {
 	/* Make sure all the keywords are in index->keywords. It's quick to do
 	   if nothing has changed. */
-	(void)mail_index_map_read_keywords(index, index->map);
+	(void)mail_index_map_parse_keywords(index, index->map);
 
 	return &index->keywords;
 }
@@ -546,7 +547,7 @@
 	    hdr->first_deleted_uid_lowwater > hdr->next_uid)
 		return 0;
 
-	return mail_index_read_extensions(index, map);
+	return mail_index_parse_extensions(index, map);
 }
 
 static void mail_index_map_clear(struct mail_index *index,
@@ -662,20 +663,25 @@
 }
 
 static int mail_index_read_header(struct mail_index *index,
-				  struct mail_index_header *hdr, size_t *pos_r)
+				  void *buf, size_t buf_size, size_t *pos_r)
 {
 	size_t pos;
 	int ret;
 
-	memset(hdr, 0, sizeof(*hdr));
+	memset(buf, 0, sizeof(struct mail_index_header));
 
-	ret = 1;
-	for (pos = 0; ret > 0 && pos < sizeof(*hdr); ) {
-		ret = pread(index->fd, PTR_OFFSET(hdr, pos),
-			    sizeof(*hdr) - pos, pos);
+        /* try to read the whole header, but it's not necessarily an error to
+	   read less since the older versions of the index format could be
+	   smaller. Request reading up to buf_size, but accept if we only got
+	   the header. */
+        pos = 0;
+	do {
+		ret = pread(index->fd, PTR_OFFSET(buf, pos),
+			    buf_size - pos, pos);
 		if (ret > 0)
 			pos += ret;
-	}
+	} while (ret > 0 && pos < sizeof(struct mail_index_header));
+
 	*pos_r = pos;
 	return ret;
 }
@@ -683,7 +689,8 @@
 static int mail_index_read_map(struct mail_index *index,
 			       struct mail_index_map *map, bool *retry_r)
 {
-	struct mail_index_header hdr;
+	const struct mail_index_header *hdr;
+	unsigned char buf[512];
 	void *data = NULL;
 	ssize_t ret;
 	size_t pos, records_size;
@@ -691,39 +698,46 @@
 	i_assert(map->mmap_base == NULL);
 
 	*retry_r = FALSE;
-	ret = mail_index_read_header(index, &hdr, &pos);
+	ret = mail_index_read_header(index, buf, sizeof(buf), &pos);
+	hdr = (const struct mail_index_header *)buf;
 
 	if (pos > (ssize_t)offsetof(struct mail_index_header, major_version) &&
-	    hdr.major_version != MAIL_INDEX_MAJOR_VERSION) {
+	    hdr->major_version != MAIL_INDEX_MAJOR_VERSION) {
 		/* major version change - handle silently */
 		return 0;
 	}
 
 	if (ret >= 0 && pos >= MAIL_INDEX_HEADER_MIN_SIZE &&
-	    (ret > 0 || pos >= hdr.base_header_size)) {
-		if (hdr.base_header_size < MAIL_INDEX_HEADER_MIN_SIZE ||
-		    hdr.header_size < hdr.base_header_size) {
+	    (ret > 0 || pos >= hdr->base_header_size)) {
+		if (hdr->base_header_size < MAIL_INDEX_HEADER_MIN_SIZE ||
+		    hdr->header_size < hdr->base_header_size) {
 			mail_index_set_error(index, "Corrupted index file %s: "
 				"Corrupted header sizes (base %u, full %u)",
-				index->filepath, hdr.base_header_size,
-				hdr.header_size);
+				index->filepath, hdr->base_header_size,
+				hdr->header_size);
 			return 0;
 		}
 
+		if (pos > hdr->header_size)
+			pos = hdr->header_size;
+
+		/* place the base header into memory. */
 		buffer_reset(map->hdr_copy_buf);
-		buffer_append(map->hdr_copy_buf, &hdr, hdr.base_header_size);
+		buffer_append(map->hdr_copy_buf, buf, pos);
 
-		/* @UNSAFE */
-		data = buffer_append_space_unsafe(map->hdr_copy_buf,
-						  hdr.header_size -
-						  hdr.base_header_size);
-		ret = pread_full(index->fd, data,
-				 hdr.header_size - hdr.base_header_size,
-				 hdr.base_header_size);
+		if (pos != hdr->header_size) {
+			/* @UNSAFE: read the rest of the header into memory */
+			data = buffer_append_space_unsafe(map->hdr_copy_buf,
+							  hdr->header_size -
+							  pos);
+			ret = pread_full(index->fd, data,
+					 hdr->header_size - pos, pos);
+		}
 	}
 
 	if (ret > 0) {
-		records_size = hdr.messages_count * hdr.record_size;
+		/* header read, read the records now. */
+		records_size = hdr->messages_count * hdr->record_size;
 
 		if (map->buffer == NULL) {
 			map->buffer = buffer_create_dynamic(default_pool,
@@ -735,11 +749,12 @@
 		data = buffer_append_space_unsafe(map->buffer, records_size);
 
 		ret = pread_full(index->fd, data, records_size,
-				 hdr.header_size);
+				 hdr->header_size);
 	}
 
 	if (ret < 0) {
 		if (errno == ESTALE) {
+			/* a new index file was renamed over this one. */
 			*retry_r = TRUE;
 			return 0;
 		}
@@ -754,13 +769,13 @@
 	}
 
 	map->records = data;
-	map->records_count = hdr.messages_count;
+	map->records_count = hdr->messages_count;
 
-	mail_index_map_copy_hdr(map, &hdr);
+	mail_index_map_copy_hdr(map, hdr);
 	map->hdr_base = map->hdr_copy_buf->data;
 
-	index->sync_log_file_seq = hdr.log_file_seq;
-	index->sync_log_file_offset = hdr.log_file_int_offset;
+	index->sync_log_file_seq = hdr->log_file_seq;
+	index->sync_log_file_offset = hdr->log_file_int_offset;
 	return 1;
 }
 
@@ -784,7 +799,7 @@
 	if (sync_to_index) {
 		/* read the real log position where we are supposed to be
 		   synced */
-		ret = mail_index_read_header(index, &hdr, &pos);
+		ret = mail_index_read_header(index, &hdr, sizeof(hdr), &pos);
 		if (ret < 0 && errno != ESTALE) {
 			mail_index_set_syscall_error(index, "pread()");
 			return -1;
@@ -904,17 +919,17 @@
 	for (i = 0; i < count; i++)
 		(*handlers[i])(index);
 
-	for (i = 0; i < MAIL_INDEX_ESTALE_RETRY_COUNT; i++) {
+	for (i = 0;; i++) {
 		ret = mail_index_read_map(index, *map, &retry);
-		if (ret != 0 || !retry)
+		if (ret != 0 || !retry || i == MAIL_INDEX_ESTALE_RETRY_COUNT)
 			return ret;
 
 		/* ESTALE - reopen index file */
-		if (close(index->fd) < 0)
+                if (close(index->fd) < 0)
 			mail_index_set_syscall_error(index, "close()");
 		index->fd = -1;
 
-		ret = mail_index_try_open_only(index);
+                ret = mail_index_try_open_only(index);
 		if (ret <= 0) {
 			if (ret == 0) {
 				/* the file was lost */
@@ -1053,6 +1068,7 @@
 				 struct mail_index_header *hdr_r)
 {
 	size_t pos;
+	unsigned int i;
 	int ret;
 
 	if (!index->mmap_disable) {
@@ -1061,8 +1077,30 @@
 			*hdr_r = *index->hdr;
 		else
 			memset(hdr_r, 0, sizeof(*hdr_r));
-	} else {
-		ret = mail_index_read_header(index, hdr_r, &pos);
+		return ret;
+	}
+
+	for (i = 0;; i++) {
+		ret = mail_index_read_header(index, hdr_r, sizeof(*hdr_r),
+					     &pos);
+		if (ret >= 0 || errno != ESTALE ||
+		    i == MAIL_INDEX_ESTALE_RETRY_COUNT)
+			break;
+
+		/* ESTALE - reopen index file */
+                if (close(index->fd) < 0)
+			mail_index_set_syscall_error(index, "close()");
+		index->fd = -1;
+
+		ret = mail_index_try_open_only(index);
+		if (ret <= 0) {
+			if (ret == 0) {
+				/* the file was lost */
+				errno = ENOENT;
+				mail_index_set_syscall_error(index, "open()");
+			}
+			return -1;
+		}
 	}
 	return ret;
 }
@@ -1151,25 +1189,19 @@
 
 static int mail_index_try_open_only(struct mail_index *index)
 {
-	int i;
-
 	i_assert(!MAIL_INDEX_IS_IN_MEMORY(index));
 
-	for (i = 0; i < 3; i++) {
-		index->fd = open(index->filepath, O_RDWR);
-		if (index->fd == -1 && errno == EACCES) {
-			index->fd = open(index->filepath, O_RDONLY);
-			index->readonly = TRUE;
-		}
-		if (index->fd != -1 || errno != ESTALE)
-			break;
+        /* Note that our caller must close index->fd by itself.
+           mail_index_reopen() for example wants to revert back to old
+           index file if opening the new one fails. */
+	index->fd = safe_open(index->filepath, O_RDWR);
+	index->readonly = FALSE;
 
-		/* May happen with some OSes with NFS. Try again, although
-		   there's still a race condition with another computer
-		   creating the index file again. However, we can't try forever
-		   as ESTALE happens also if index directory has been deleted
-		   from server.. */
+	if (index->fd == -1 && errno == EACCES) {
+		index->fd = open(index->filepath, O_RDONLY);
+		index->readonly = TRUE;
 	}
+
 	if (index->fd == -1) {
 		if (errno != ENOENT)
 			return mail_index_set_syscall_error(index, "open()");
@@ -1186,6 +1218,8 @@
 	unsigned int lock_id;
 	int ret;
 
+        i_assert(index->fd == -1);
+
 	if (lock_id_r != NULL)
 		*lock_id_r = 0;
 
@@ -1612,8 +1646,15 @@
 	if (MAIL_INDEX_IS_IN_MEMORY(index))
 		return 0;
 
-	if (fstat(index->fd, &st1) < 0)
+	if (fstat(index->fd, &st1) < 0) {
+		if (errno == ESTALE) {
+			/* deleted, reopen */
+			if (mail_index_reopen(index, -1) < 0)
+				return -1;
+			return 1;
+		}
 		return mail_index_set_syscall_error(index, "fstat()");
+	}
 	if (stat(index->filepath, &st2) < 0) {
 		mail_index_set_syscall_error(index, "stat()");
 		if (errno != ENOENT)
--- a/src/lib-index/mail-transaction-log.c	Thu Feb 16 16:58:31 2006 +0200
+++ b/src/lib-index/mail-transaction-log.c	Thu Feb 16 17:23:00 2006 +0200
@@ -4,6 +4,8 @@
 #include "ioloop.h"
 #include "buffer.h"
 #include "file-dotlock.h"
+#include "safe-open.h"
+#include "close-keep-errno.h"
 #include "read-full.h"
 #include "write-full.h"
 #include "mmap-util.h"
@@ -278,6 +280,8 @@
 static void
 mail_transaction_log_file_free(struct mail_transaction_log_file *file)
 {
+        int old_errno = errno;
+
 	mail_transaction_log_file_unlock(file);
 
 	if (file == file->log->head)
@@ -305,7 +309,9 @@
 	}
 
 	i_free(file->filepath);
-	i_free(file);
+        i_free(file);
+
+        errno = old_errno;
 }
 
 int mail_transaction_log_move_to_memory(struct mail_transaction_log *log)
@@ -357,10 +363,11 @@
 
 	ret = pread_full(file->fd, &file->hdr, sizeof(file->hdr), 0);
 	if (ret < 0) {
-		// FIXME: handle ESTALE
-		mail_index_file_set_syscall_error(file->log->index,
-						  file->filepath,
-						  "pread_full()");
+                if (errno != ESTALE) {
+                        mail_index_file_set_syscall_error(file->log->index,
+                                                          file->filepath,
+                                                          "pread_full()");
+                }
 		return -1;
 	}
 	if (ret == 0) {
@@ -478,7 +485,7 @@
 
 static int
 mail_transaction_log_file_create2(struct mail_transaction_log *log,
-				  const char *path, int fd,
+				  const char *path, int new_fd,
 				  struct dotlock **dotlock,
 				  dev_t dev, ino_t ino, uoff_t file_size)
 {
@@ -486,28 +493,31 @@
 	struct mail_transaction_log_header hdr;
 	struct stat st;
 	const char *path2;
-	int fd2, ret;
+	int old_fd, ret;
 	bool found;
 
 	/* log creation is locked now - see if someone already created it */
-	fd2 = open(path, O_RDWR);
-	if (fd2 != -1) {
-		if ((ret = fstat(fd2, &st)) < 0) {
-			mail_index_file_set_syscall_error(index, path,
-							  "fstat()");
+	old_fd = safe_open(path, O_RDWR);
+	if (old_fd != -1) {
+		if ((ret = fstat(old_fd, &st)) < 0) {
+                        mail_index_file_set_syscall_error(index, path,
+                                                          "fstat()");
 		} else if (st.st_ino == ino && CMP_DEV_T(st.st_dev, dev) &&
 			   (uoff_t)st.st_size == file_size) {
 			/* same file, still broken */
 		} else {
-			(void)file_dotlock_delete(dotlock);
-			return fd2;
+                        /* file changed, use the new file */
+                        (void)file_dotlock_delete(dotlock);
+			return old_fd;
 		}
 
-		(void)close(fd2);
-		fd2 = -1;
+		(void)close(old_fd);
+		old_fd = -1;
 
-		if (ret < 0)
-			return -1;
+                if (ret < 0) {
+                        /* fstat() failure, return after closing fd.. */
+                        return -1;
+                }
 		found = TRUE;
 	} else if (errno != ENOENT) {
 		mail_index_file_set_syscall_error(index, path, "open()");
@@ -519,7 +529,7 @@
 	if (mail_transaction_log_init_hdr(log, &hdr) < 0)
 		return -1;
 
-	if (write_full(fd, &hdr, sizeof(hdr)) < 0) {
+	if (write_full(new_fd, &hdr, sizeof(hdr)) < 0) {
 		mail_index_file_set_syscall_error(index, path,
 						  "write_full()");
 		return -1;
@@ -529,7 +539,8 @@
 	if (found) {
 		path2 = t_strconcat(path, ".2", NULL);
 		if (rename(path, path2) < 0) {
-			i_error("rename(%s, %s) failed: %m", path, path2);
+                        mail_index_set_error(index,
+                        	"rename(%s, %s) failed: %m", path, path2);
 			/* ignore the error. we don't care that much about the
 			   second log file and we're going to overwrite this
 			   first one. */
@@ -541,7 +552,7 @@
 		return -1;
 
 	/* success */
-	return fd;
+	return new_fd;
 }
 
 static int
@@ -551,7 +562,7 @@
 {
 	struct dotlock *dotlock;
         mode_t old_mask;
-	int fd, fd2;
+	int fd;
 
 	i_assert(!MAIL_INDEX_IS_IN_MEMORY(log->index));
 
@@ -574,14 +585,16 @@
 		return -1;
 	}
 
-	fd2 = mail_transaction_log_file_create2(log, path, fd, &dotlock,
-						dev, ino, file_size);
-	if (fd2 < 0) {
+        /* either fd gets used or the dotlock gets deleted and returned fd
+           is for the existing file */
+        fd = mail_transaction_log_file_create2(log, path, fd, &dotlock,
+                                               dev, ino, file_size);
+	if (fd < 0) {
 		if (dotlock != NULL)
 			(void)file_dotlock_delete(&dotlock);
 		return -1;
 	}
-	return fd2;
+	return fd;
 }
 
 static void
@@ -640,8 +653,11 @@
 	*file_r = NULL;
 
 	if (fstat(fd, &st) < 0) {
-		mail_index_file_set_syscall_error(log->index, path, "fstat()");
-		(void)close(fd);
+                if (errno != ESTALE) {
+                        mail_index_file_set_syscall_error(log->index, path,
+                                                          "fstat()");
+                }
+		close_keep_errno(fd);
 		return -1;
 	}
 
@@ -668,15 +684,20 @@
 
 static struct mail_transaction_log_file *
 mail_transaction_log_file_fd_open_or_create(struct mail_transaction_log *log,
-					    const char *path, int fd)
+                                            const char *path, int fd,
+                                            bool *retry_r)
 {
 	struct mail_transaction_log_file *file;
 	struct stat st;
 	int ret;
 
+        *retry_r = FALSE;
+
 	ret = mail_transaction_log_file_fd_open(log, &file, path, fd, TRUE);
-	if (ret < 0)
-		return NULL;
+        if (ret < 0) {
+                *retry_r = errno == ESTALE;
+                return NULL;
+        }
 
 	if (ret == 0) {
 		/* corrupted header */
@@ -686,7 +707,7 @@
 		if (fd == -1)
 			ret = -1;
 		else if (fstat(fd, &st) < 0) {
-			mail_index_file_set_syscall_error(log->index, path,
+                        mail_index_file_set_syscall_error(log->index, path,
 							  "fstat()");
 			(void)close(fd);
 			fd = -1;
@@ -707,6 +728,7 @@
 		}
 	}
 	if (ret <= 0) {
+                *retry_r = errno == ESTALE;
 		mail_transaction_log_file_free(file);
 		return NULL;
 	}
@@ -742,25 +764,39 @@
 mail_transaction_log_file_open_or_create(struct mail_transaction_log *log,
 					 const char *path)
 {
-	int fd;
+        struct mail_transaction_log_file *file;
+        unsigned int i;
+        bool retry;
+        int fd;
 
 	if (MAIL_INDEX_IS_IN_MEMORY(log->index))
 		return mail_transaction_log_file_alloc_in_memory(log);
 
-	fd = open(path, O_RDWR);
-	if (fd == -1) {
-		if (errno != ENOENT) {
-			mail_index_file_set_syscall_error(log->index, path,
-							  "open()");
-			return NULL;
-		}
+        for (i = 0; ; i++) {
+                fd = safe_open(path, O_RDWR);
+                if (fd == -1) {
+                        if (errno != ENOENT) {
+                                mail_index_file_set_syscall_error(log->index,
+                                                                  path,
+                                                                  "open()");
+                                return NULL;
+                        }
 
-		fd = mail_transaction_log_file_create(log, path, 0, 0, 0);
-		if (fd == -1)
-			return NULL;
-	}
+                        /* doesn't exist, try creating it */
+                        fd = mail_transaction_log_file_create(log, path,
+                                                              0, 0, 0);
+                        if (fd == -1)
+                                return NULL;
+                }
 
-	return mail_transaction_log_file_fd_open_or_create(log, path, fd);
+                file = mail_transaction_log_file_fd_open_or_create(log, path,
+                                                                   fd, &retry);
+                if (file != NULL || !retry ||
+                    i == MAIL_INDEX_ESTALE_RETRY_COUNT)
+                        return file;
+
+                /* ESTALE - retry */
+        }
 }
 
 static struct mail_transaction_log_file *
@@ -768,21 +804,34 @@
 			       const char *path)
 {
 	struct mail_transaction_log_file *file;
-	int fd, ret;
+        unsigned int i;
+        int fd, ret;
 
-	fd = open(path, O_RDWR);
-	if (fd == -1) {
-		mail_index_file_set_syscall_error(log->index, path, "open()");
-		return NULL;
-	}
+        for (i = 0;; i++) {
+                fd = safe_open(path, O_RDWR);
+                if (fd == -1) {
+                        mail_index_file_set_syscall_error(log->index, path,
+                                                          "open()");
+                        return NULL;
+                }
 
-	ret = mail_transaction_log_file_fd_open(log, &file, path, fd, TRUE);
-	if (ret <= 0) {
-		/* error / corrupted */
-		if (ret == 0)
-			mail_transaction_log_file_free(file);
-		return NULL;
-	}
+                ret = mail_transaction_log_file_fd_open(log, &file, path,
+                                                        fd, TRUE);
+                if (ret > 0)
+                        break;
+
+                /* error / corrupted */
+                if (ret == 0)
+                        mail_transaction_log_file_free(file);
+                else {
+                        if (errno == ESTALE &&
+                            i < MAIL_INDEX_ESTALE_RETRY_COUNT) {
+                                /* try again */
+                                continue;
+                        }
+                }
+                return NULL;
+        }
 
 	mail_transaction_log_file_add_to_head(file);
 	return file;
@@ -807,7 +856,8 @@
 {
 	struct mail_transaction_log_file *file;
 	const char *path = log->head->filepath;
-	struct stat st;
+        struct stat st;
+        bool retry;
 	int fd;
 
 	i_assert(log->head->locked);
@@ -815,6 +865,8 @@
 	if (MAIL_INDEX_IS_IN_MEMORY(log->index))
 		file = mail_transaction_log_file_alloc_in_memory(log);
 	else {
+                /* we're locked, we shouldn't need to worry about ESTALE
+                   problems in here. */
 		if (fstat(log->head->fd, &st) < 0) {
 			mail_index_file_set_syscall_error(log->index, path,
 							  "fstat()");
@@ -827,10 +879,16 @@
 		if (fd == -1)
 			return -1;
 
-		file = mail_transaction_log_file_fd_open_or_create(log,
-								   path, fd);
-		if (file == NULL)
-			return -1;
+                file = mail_transaction_log_file_fd_open_or_create(log, path,
+                                                                   fd, &retry);
+                if (file == NULL) {
+                        if (retry) {
+                                mail_index_set_error(log->index,
+                                	"%s: ESTALE unexpected while locked",
+                                        path);
+                        }
+                        return -1;
+                }
 	}
 
 	if (lock) {
@@ -920,7 +978,7 @@
 	/* see if we have it in log.2 file */
 	path = t_strconcat(log->index->filepath,
 			   MAIL_TRANSACTION_LOG_SUFFIX".2", NULL);
-	fd = open(path, O_RDWR);
+	fd = safe_open(path, O_RDWR);
 	if (fd == -1) {
 		if (errno == ENOENT)
 			return 0;
@@ -930,7 +988,12 @@
 	}
 
 	if (fstat(fd, &st) < 0) {
-		mail_index_file_set_syscall_error(log->index, path, "fstat()");
+                if (errno == ESTALE) {
+                        /* treat as "doesn't exist" */
+                        (void)close(fd);
+                        return 0;
+                }
+                mail_index_file_set_syscall_error(log->index, path, "fstat()");
 		return -1;
 	}
 
@@ -954,7 +1017,11 @@
 			}
 			mail_transaction_log_file_free(file);
 			return 0;
-		}
+                }
+                if (errno == ESTALE) {
+                        /* treat as "doesn't exist" */
+                        return 0;
+                }
 		return -1;
 	}
 
--- a/src/lib-storage/index/maildir/maildir-keywords.c	Thu Feb 16 16:58:31 2006 +0200
+++ b/src/lib-storage/index/maildir/maildir-keywords.c	Thu Feb 16 17:23:00 2006 +0200
@@ -83,6 +83,10 @@
 	const char **strp;
 	int fd, idx;
 
+        /* Remember that we rely on uidlist file locking in here. That's why
+           we rely on stat()'s timestamp and don't bother handling ESTALE
+           errors. */
+
 	if (stat(mk->path, &st) < 0) {
 		if (errno == ENOENT) {
 			maildir_keywords_clear(mk);
--- a/src/lib-storage/index/maildir/maildir-uidlist.c	Thu Feb 16 16:58:31 2006 +0200
+++ b/src/lib-storage/index/maildir/maildir-uidlist.c	Thu Feb 16 17:23:00 2006 +0200
@@ -7,6 +7,8 @@
 #include "istream.h"
 #include "str.h"
 #include "file-dotlock.h"
+#include "close-keep-errno.h"
+#include "safe-open.h"
 #include "write-full.h"
 #include "maildir-storage.h"
 #include "maildir-uidlist.h"
@@ -16,6 +18,10 @@
 #include <sys/stat.h>
 #include <utime.h>
 
+/* NFS: How many times to retry reading dovecot-uidlist file if ESTALE
+   error occurs in the middle of reading it */
+#define UIDLIST_ESTALE_RETRY_COUNT 10
+
 /* how many seconds to wait before overriding uidlist.lock */
 #define UIDLIST_LOCK_STALE_TIMEOUT (60*2)
 
@@ -240,7 +246,8 @@
 	return 1;
 }
 
-int maildir_uidlist_update(struct maildir_uidlist *uidlist)
+static int
+maildir_uidlist_update_read(struct maildir_uidlist *uidlist, bool *retry_r)
 {
 	struct mail_storage *storage = STORAGE(uidlist->mbox->storage);
 	const char *line;
@@ -249,35 +256,25 @@
 	struct stat st;
 	int fd, ret;
 
-	if (uidlist->last_mtime != 0) {
-		if (stat(uidlist->fname, &st) < 0) {
-			if (errno != ENOENT) {
-				mail_storage_set_critical(storage,
-					"stat(%s) failed: %m", uidlist->fname);
-				return -1;
-			}
-			return 0;
-		}
+        *retry_r = FALSE;
 
-		if (st.st_mtime == uidlist->last_mtime) {
-			/* unchanged */
-			return 1;
-		}
-	}
-
-	fd = open(uidlist->fname, O_RDONLY);
+	fd = safe_open(uidlist->fname, O_RDONLY);
 	if (fd == -1) {
 		if (errno != ENOENT) {
 			mail_storage_set_critical(storage,
 				"open(%s) failed: %m", uidlist->fname);
 			return -1;
 		}
-		uidlist->initial_read = TRUE;
 		return 0;
 	}
 
 	if (fstat(fd, &st) < 0) {
-		mail_storage_set_critical(storage,
+                close_keep_errno(fd);
+                if (errno == ESTALE) {
+                        *retry_r = TRUE;
+                        return -1;
+                }
+                mail_storage_set_critical(storage,
 			"fstat(%s) failed: %m", uidlist->fname);
 		return -1;
 	}
@@ -295,10 +292,13 @@
 
 	/* get header */
 	line = i_stream_read_next_line(input);
-	if (line == NULL || sscanf(line, "%u %u %u", &uidlist->version,
-				   &uid_validity, &next_uid) != 3 ||
-	    uidlist->version < 1 || uidlist->version > 2) {
-		/* broken file */
+        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->fname, uidlist->version);
@@ -320,19 +320,62 @@
 				ret = 0;
 				break;
 			}
+                }
+                if (input->stream_errno != 0)
+                        ret = -1;
+	}
+
+        if (ret == 0) {
+                /* file is broken */
+                (void)unlink(uidlist->fname);
+                uidlist->last_mtime = 0;
+        } else if (ret > 0) {
+                /* success */
+		uidlist->last_mtime = st.st_mtime;
+        } else {
+                /* I/O error */
+                if (input->stream_errno == ESTALE)
+                        *retry_r = TRUE;
+        }
+
+	i_stream_unref(&input);
+	return ret;
+}
+
+int maildir_uidlist_update(struct maildir_uidlist *uidlist)
+{
+	struct mail_storage *storage = STORAGE(uidlist->mbox->storage);
+        struct stat st;
+        unsigned int i;
+        bool retry;
+        int ret;
+
+	if (uidlist->last_mtime != 0) {
+		if (stat(uidlist->fname, &st) < 0) {
+			if (errno != ENOENT) {
+				mail_storage_set_critical(storage,
+					"stat(%s) failed: %m", uidlist->fname);
+				return -1;
+			}
+			return 0;
+		}
+
+		if (st.st_mtime == uidlist->last_mtime) {
+			/* unchanged */
+			return 1;
 		}
 	}
 
-	if (ret != 0)
-		uidlist->last_mtime = st.st_mtime;
-	else {
-		(void)unlink(uidlist->fname);
-                uidlist->last_mtime = 0;
-	}
-
-	i_stream_unref(&input);
-	uidlist->initial_read = TRUE;
-	return ret;
+        for (i = 0; ; i++) {
+                ret = maildir_uidlist_update_read(uidlist, &retry);
+                if (!retry || i == UIDLIST_ESTALE_RETRY_COUNT) {
+                        if (ret >= 0)
+                                uidlist->initial_read = TRUE;
+                        break;
+                }
+                /* ESTALE - try reopening and rereading */
+        }
+        return ret;
 }
 
 static const struct maildir_uidlist_rec *
--- a/src/lib-storage/subscription-file/subscription-file.c	Thu Feb 16 16:58:31 2006 +0200
+++ b/src/lib-storage/subscription-file/subscription-file.c	Thu Feb 16 17:23:00 2006 +0200
@@ -3,6 +3,7 @@
 #include "lib.h"
 #include "istream.h"
 #include "ostream.h"
+#include "safe-open.h"
 #include "file-dotlock.h"
 #include "mail-storage-private.h"
 #include "subscription-file.h"
@@ -12,6 +13,7 @@
 
 #define MAX_MAILBOX_LENGTH PATH_MAX
 
+#define SUBSCRIPTION_FILE_ESTALE_RETRY_COUNT 10
 #define SUBSCRIPTION_FILE_LOCK_TIMEOUT 120
 #define SUBSCRIPTION_FILE_CHANGE_TIMEOUT 30
 
@@ -40,17 +42,23 @@
 }
 
 static const char *next_line(struct mail_storage *storage, const char *path,
-			     struct istream *input, bool *failed)
+			     struct istream *input, bool *failed_r)
 {
 	const char *line;
 
-	*failed = FALSE;
+	*failed_r = FALSE;
 	if (input == NULL)
 		return NULL;
 
 	while ((line = i_stream_next_line(input)) == NULL) {
-		switch (i_stream_read(input)) {
+                switch (i_stream_read(input)) {
 		case -1:
+                        if (input->stream_errno != 0 &&
+                            input->stream_errno != ESTALE) {
+                                subsfile_set_syscall_error(storage,
+                                                           "read()", path);
+                                *failed_r = TRUE;
+                        }
 			return NULL;
 		case -2:
 			/* mailbox name too large */
@@ -58,7 +66,7 @@
 				"Subscription file %s contains lines longer "
 				"than %u characters", path,
 				MAX_MAILBOX_LENGTH);
-			*failed = TRUE;
+			*failed_r = TRUE;
 			return NULL;
 		}
 	}
@@ -85,7 +93,6 @@
 	dotlock_set.timeout = SUBSCRIPTION_FILE_LOCK_TIMEOUT;
 	dotlock_set.stale_timeout = SUBSCRIPTION_FILE_CHANGE_TIMEOUT;
 
-	/* FIXME: set lock notification callback */
 	fd_out = file_dotlock_open(&dotlock_set, path, 0, &dotlock);
 	if (fd_out == -1) {
 		if (errno == EAGAIN) {
@@ -98,7 +105,7 @@
 		return -1;
 	}
 
-	fd_in = open(path, O_RDONLY);
+	fd_in = safe_open(path, O_RDONLY);
 	if (fd_in == -1 && errno != ENOENT) {
 		subsfile_set_syscall_error(storage, "open()", path);
 		(void)file_dotlock_delete(&dotlock);
@@ -165,7 +172,7 @@
 	pool_t pool;
 	int fd;
 
-	fd = open(path, O_RDONLY);
+	fd = safe_open(path, O_RDONLY);
 	if (fd == -1 && errno != ENOENT) {
 		subsfile_set_syscall_error(storage, "open()", path);
 		return NULL;
@@ -196,8 +203,42 @@
 
 const char *subsfile_list_next(struct subsfile_list_context *ctx)
 {
-	if (ctx->failed || ctx->input == NULL)
+        const char *line;
+        unsigned int i;
+        int fd;
+
+        if (ctx->failed || ctx->input == NULL)
 		return NULL;
 
-	return next_line(ctx->storage, ctx->path, ctx->input, &ctx->failed);
+        for (i = 0;; i++) {
+                line = next_line(ctx->storage, ctx->path, ctx->input,
+                                 &ctx->failed);
+                if (ctx->input->stream_errno != ESTALE ||
+                    i == SUBSCRIPTION_FILE_ESTALE_RETRY_COUNT)
+                        break;
+
+                /* Reopen the subscription file and re-send everything.
+                   this isn't the optimal behavior, but it's allowed by
+                   IMAP and this way we don't have to read everything into
+                   memory or try to play any guessing games. */
+                i_stream_unref(&ctx->input);
+
+                fd = safe_open(ctx->path, O_RDONLY);
+                if (fd == -1) {
+                        /* In case of ENOENT all the subscriptions got lost.
+                           Just return end of subscriptions list in that
+                           case. */
+                        if (errno != ENOENT) {
+                                subsfile_set_syscall_error(ctx->storage,
+                                                           "open()",
+                                                           ctx->path);
+                                ctx->failed = TRUE;
+                        }
+                        return NULL;
+                }
+
+                ctx->input = i_stream_create_file(fd, ctx->pool,
+                                                  MAX_MAILBOX_LENGTH, TRUE);
+        }
+        return line;
 }
--- a/src/lib/Makefile.am	Thu Feb 16 16:58:31 2006 +0200
+++ b/src/lib/Makefile.am	Thu Feb 16 17:23:00 2006 +0200
@@ -65,6 +65,7 @@
 	restrict-process-size.c \
 	safe-memset.c \
 	safe-mkdir.c \
+	safe-open.c \
 	sendfile-util.c \
 	seq-range-array.c \
 	sha1.c \
@@ -133,6 +134,7 @@
 	restrict-process-size.h \
 	safe-memset.h \
 	safe-mkdir.h \
+	safe-open.h \
 	sendfile-util.h \
 	seq-range-array.h \
 	sha1.h \
--- a/src/lib/file-cache.c	Thu Feb 16 16:58:31 2006 +0200
+++ b/src/lib/file-cache.c	Thu Feb 16 17:23:00 2006 +0200
@@ -107,8 +107,9 @@
 		   more than needed */
 		struct stat st;
 
-		if (fstat(cache->fd, &st) < 0) {
-			i_error("fstat(file_cache) failed: %m");
+                if (fstat(cache->fd, &st) < 0) {
+                        if (errno != ESTALE)
+                                i_error("fstat(file_cache) failed: %m");
 			return -1;
 		}
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/safe-open.c	Thu Feb 16 17:23:00 2006 +0200
@@ -0,0 +1,50 @@
+/* Copyright (c) 2006 Timo Sirainen */
+
+#include "lib.h"
+#include "safe-open.h"
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#define NFS_OPEN_RETRY_COUNT 10
+
+int safe_open(const char *path, int flags)
+{
+        const char *dir = NULL;
+        struct stat st;
+        unsigned int i;
+        int fd;
+
+        i_assert((flags & O_CREAT) == 0);
+
+        t_push();
+        for (i = 1;; i++) {
+                fd = open(path, flags);
+                if (fd != -1 || errno != ESTALE || i == NFS_OPEN_RETRY_COUNT)
+                        break;
+
+                /* ESTALE: Some operating systems may fail with this if they
+                   can't internally revalidating the NFS handle. It may also
+                   happen if the parent directory has been deleted. If the
+                   directory still exists, try reopening the file. */
+                if (dir == NULL) {
+                        dir = strrchr(path, '/');
+                        if (dir == NULL)
+                                break;
+                        dir = t_strdup_until(path, dir);
+                }
+                if (stat(dir, &st) < 0) {
+                        /* maybe it's gone or something else bad happened to
+                           it. in any case we can't open the file, so fail
+                           with the original ESTALE error and let our caller
+                           handle it. */
+                        errno = ESTALE;
+                        break;
+                }
+
+                /* directory still exists, try reopening */
+        }
+        t_pop();
+        return fd;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib/safe-open.h	Thu Feb 16 17:23:00 2006 +0200
@@ -0,0 +1,7 @@
+#ifndef __SAFE_OPEN_H
+#define __SAFE_OPEN_H
+
+/* open() with some NFS workarounds */
+int safe_open(const char *path, int flags);
+
+#endif