changeset 6829:dbab5e592577 HEAD

NFS cache flushing APIs changed and backend implements it a bit differently. Hopefully this works now more correctly.
author Timo Sirainen <tss@iki.fi>
date Sun, 18 Nov 2007 08:40:59 +0200
parents 8470d0845fc2
children 3a41003bb1ae
files src/lib-index/mail-cache-compress.c src/lib-index/mail-cache-private.h src/lib-index/mail-cache-sync-update.c src/lib-index/mail-cache.c src/lib-index/mail-hash.c src/lib-index/mail-index-lock.c src/lib-index/mail-index-map.c src/lib-index/mail-index-private.h src/lib-index/mail-index.c src/lib-index/mail-transaction-log-file.c src/lib-index/mail-transaction-log.c src/lib-index/mailbox-list-index-sync.c src/lib-index/mailbox-list-index.c src/lib-storage/index/maildir/maildir-keywords.c src/lib-storage/index/maildir/maildir-uidlist.c src/lib-storage/index/mbox/mbox-lock.c src/lib-storage/mailbox-list.c src/lib/file-dotlock.c src/lib/nfs-workarounds.c src/lib/nfs-workarounds.h
diffstat 20 files changed, 243 insertions(+), 214 deletions(-) [+]
line wrap: on
line diff
--- a/src/lib-index/mail-cache-compress.c	Sun Nov 18 08:01:54 2007 +0200
+++ b/src/lib-index/mail-cache-compress.c	Sun Nov 18 08:40:59 2007 +0200
@@ -354,9 +354,6 @@
 	unsigned int i, count;
 	int fd, ret;
 
-	if (!MAIL_CACHE_IS_UNUSABLE(cache))
-		mail_cache_flush_read_cache(cache, TRUE);
-
 	/* get the latest info on fields */
 	if (mail_cache_header_fields_read(cache) < 0)
 		return -1;
@@ -460,6 +457,11 @@
 	if (cache->index->lock_method == FILE_LOCK_METHOD_DOTLOCK) {
 		/* we're using dotlocking, cache file creation itself creates
 		   the dotlock file we need. */
+		if (!MAIL_CACHE_IS_UNUSABLE(cache)) {
+			mail_index_flush_read_cache(cache->index,
+						    cache->filepath, cache->fd,
+						    FALSE);
+		}
 		return mail_cache_compress_locked(cache, trans, &unlock);
 	}
 
--- a/src/lib-index/mail-cache-private.h	Sun Nov 18 08:01:54 2007 +0200
+++ b/src/lib-index/mail-cache-private.h	Sun Nov 18 08:40:59 2007 +0200
@@ -220,7 +220,6 @@
 /* Returns -1 if cache is / just got corrupted, 0 if ok. */
 int mail_cache_unlock(struct mail_cache *cache);
 
-void mail_cache_flush_read_cache(struct mail_cache *cache, bool just_locked);
 int mail_cache_write(struct mail_cache *cache, const void *data, size_t size,
 		     uoff_t offset);
 
--- a/src/lib-index/mail-cache-sync-update.c	Sun Nov 18 08:01:54 2007 +0200
+++ b/src/lib-index/mail-cache-sync-update.c	Sun Nov 18 08:40:59 2007 +0200
@@ -11,7 +11,7 @@
 
 	unsigned int locked:1;
 	unsigned int lock_failed:1;
-	unsigned int nfs_attr_cache_flushed:1;
+	unsigned int nfs_read_cache_flushed:1;
 };
 
 static void mail_cache_handler_deinit(struct mail_index_sync_map_ctx *sync_ctx,
@@ -127,10 +127,12 @@
 
 	ctx = mail_cache_handler_init(context);
 	if (cache->file_cache != NULL && !MAIL_CACHE_IS_UNUSABLE(cache)) {
-		/* flush attribute cache only once per sync */
-		if (!ctx->nfs_attr_cache_flushed && cache->index->nfs_flush) {
-			ctx->nfs_attr_cache_flushed = TRUE;
-			mail_cache_flush_read_cache(cache, FALSE);
+		/* flush read cache only once per sync */
+		if (!ctx->nfs_read_cache_flushed && cache->index->nfs_flush) {
+			ctx->nfs_read_cache_flushed = TRUE;
+			mail_index_flush_read_cache(cache->index,
+						    cache->filepath, cache->fd,
+						    cache->locked);
 		}
 		/* don't invalidate anything that's already been invalidated
 		   within this sync. */
@@ -177,7 +179,11 @@
 
 void mail_cache_sync_lost_handler(struct mail_index *index)
 {
-	if (!MAIL_CACHE_IS_UNUSABLE(index->cache))
-		mail_cache_flush_read_cache(index->cache, FALSE);
-	file_cache_invalidate(index->cache->file_cache, 0, (uoff_t)-1);
+	struct mail_cache *cache = index->cache;
+
+	if (!MAIL_CACHE_IS_UNUSABLE(cache)) {
+		mail_index_flush_read_cache(cache->index, cache->filepath,
+					    cache->fd, cache->locked);
+	}
+	file_cache_invalidate(cache->file_cache, 0, (uoff_t)-1);
 }
--- a/src/lib-index/mail-cache.c	Sun Nov 18 08:01:54 2007 +0200
+++ b/src/lib-index/mail-cache.c	Sun Nov 18 08:40:59 2007 +0200
@@ -78,12 +78,6 @@
 	if (cache->file_cache == NULL)
 		return;
 
-	if (cache->index->nfs_flush) {
-		nfs_flush_attr_cache_fd(cache->filepath, cache->fd);
-		nfs_flush_read_cache(cache->filepath, cache->fd,
-				     F_UNLCK, FALSE);
-	}
-
 	file_cache_set_fd(cache->file_cache, cache->fd);
 
 	if (fstat(cache->fd, &st) == 0)
@@ -114,22 +108,33 @@
 		return TRUE;
 
 	/* see if the file has changed */
-	if (cache->index->nfs_flush)
-		nfs_flush_attr_cache(cache->filepath);
+	if (cache->index->nfs_flush) {
+		i_assert(!cache->locked);
+		nfs_flush_attr_cache_unlocked(cache->filepath);
+	}
 	if (nfs_safe_stat(cache->filepath, &st) < 0) {
 		mail_cache_set_syscall_error(cache, "stat()");
 		return TRUE;
 	}
+
+	if (st.st_ino != cache->st_ino ||
+	    !CMP_DEV_T(st.st_dev, cache->st_dev)) {
+		/* file changed */
+		return TRUE;
+	}
+
 	if (cache->index->nfs_flush) {
 		/* if the old file has been deleted, the new file may have
-		   the same inode as the old one, but we'll catch this by
-		   flushing attribute cache and seeing if it fails with
-		   ESTALE */
-		if (!nfs_flush_attr_cache_fd(cache->filepath, cache->fd))
-			return TRUE;
+		   the same inode as the old one. we'll catch this here by
+		   checking if fstat() fails with ESTALE */
+		if (fstat(cache->fd, &st) < 0) {
+			if (errno == ESTALE)
+				return TRUE;
+			mail_cache_set_syscall_error(cache, "fstat()");
+			return FALSE;
+		}
 	}
-	return st.st_ino != cache->st_ino ||
-		!CMP_DEV_T(st.st_dev, cache->st_dev);
+	return FALSE;
 }
 
 int mail_cache_reopen(struct mail_cache *cache)
@@ -450,23 +455,6 @@
 	i_free(cache);
 }
 
-void mail_cache_flush_read_cache(struct mail_cache *cache, bool just_locked)
-{
-	if (!cache->index->nfs_flush)
-		return;
-
-	/* Assume flock() is independent of fcntl() locks. This isn't true
-	   with Linux 2.6 NFS, but with it there's no point in using flock() */
-	if (cache->locked &&
-	    cache->index->lock_method == FILE_LOCK_METHOD_FCNTL) {
-		nfs_flush_read_cache(cache->filepath, cache->fd,
-				     F_WRLCK, just_locked);
-	} else {
-		nfs_flush_read_cache(cache->filepath, cache->fd,
-				     F_UNLCK, FALSE);
-	}
-}
-
 static int mail_cache_lock_file(struct mail_cache *cache)
 {
 	int ret;
@@ -490,10 +478,8 @@
 	if (ret <= 0)
 		return ret;
 
-	if (cache->index->nfs_flush &&
-	    cache->index->lock_method != FILE_LOCK_METHOD_FCNTL)
-		nfs_flush_attr_cache_fd(cache->filepath, cache->fd);
-	mail_cache_flush_read_cache(cache, TRUE);
+	mail_index_flush_read_cache(cache->index, cache->filepath, cache->fd,
+				    TRUE);
 	return 1;
 }
 
--- a/src/lib-index/mail-hash.c	Sun Nov 18 08:01:54 2007 +0200
+++ b/src/lib-index/mail-hash.c	Sun Nov 18 08:40:59 2007 +0200
@@ -242,6 +242,8 @@
 {
 	int ret;
 
+	i_assert(hash->locked);
+
 	if (hash->mmap_base == NULL) {
 		if (file_size < size)
 			file_size = size;
@@ -269,9 +271,6 @@
 		hash->mmap_size = size;
 	}
 
-	if (hash->index->nfs_flush)
-		nfs_flush_read_cache(hash->filepath, hash->fd, F_UNLCK, FALSE);
-
 	ret = pread_full(hash->fd, hash->mmap_base, size, 0);
 	if (ret < 0) {
 		mail_hash_set_syscall_error(hash, "pread_full()");
@@ -356,8 +355,7 @@
 	struct stat st;
 	size_t size;
 
-	if (hash->index->nfs_flush)
-		nfs_flush_attr_cache_fd(hash->filepath, hash->fd);
+	i_assert(hash->locked);
 
 	if (fstat(hash->fd, &st) < 0) {
 		mail_hash_set_syscall_error(hash, "fstat()");
@@ -408,23 +406,29 @@
 
 static int mail_hash_file_lock(struct mail_hash *hash, int lock_type)
 {
+	int ret;
+
 	i_assert(hash->fd != -1);
 
 	if (hash->index->lock_method != FILE_LOCK_METHOD_DOTLOCK) {
 		i_assert(hash->file_lock == NULL);
-		return mail_index_lock_fd(hash->index, hash->filepath, hash->fd,
-					  lock_type, MAIL_HASH_TIMEOUT_SECS,
-					  &hash->file_lock);
+		ret = mail_index_lock_fd(hash->index, hash->filepath, hash->fd,
+					 lock_type, MAIL_HASH_TIMEOUT_SECS,
+					 &hash->file_lock);
 	} else {
 		i_assert(hash->dotlock == NULL);
-		if (file_dotlock_create(&hash->dotlock_settings,
-					hash->filepath, 0,
-					&hash->dotlock) < 0) {
-			mail_hash_set_syscall_error(hash, "open()");
-			return -1;
+		ret = file_dotlock_create(&hash->dotlock_settings,
+					  hash->filepath, 0, &hash->dotlock);
+		if (ret < 0) {
+			mail_hash_set_syscall_error(hash,
+						    "file_dotlock_create()");
 		}
-		return 0;
 	}
+	if (ret > 0) {
+		mail_index_flush_read_cache(hash->index, hash->filepath,
+					    hash->fd, TRUE);
+	}
+	return ret;
 }
 
 static void mail_hash_file_unlock(struct mail_hash *hash)
@@ -653,20 +657,25 @@
 		return mail_hash_reopen(hash);
 
 	if (hash->index->nfs_flush)
-		nfs_flush_attr_cache(hash->filepath);
+		nfs_flush_attr_cache_unlocked(hash->filepath);
 
 	if (nfs_safe_stat(hash->filepath, &st) < 0) {
-		if (errno == ENOENT)
-			return mail_hash_reopen(hash);
-
-		if (errno != ESTALE) {
+		if (errno != ENOENT) {
 			mail_hash_set_syscall_error(hash, "stat()");
 			return -1;
 		}
-		/* if ESTALE is returned, it most likely means it was rebuilt */
-	} else {
-		if (st.st_ino == hash->ino && CMP_DEV_T(st.st_dev, hash->dev))
+	} else if (st.st_ino == hash->ino &&
+		   CMP_DEV_T(st.st_dev, hash->dev)) {
+		/* the file looks the same */
+		if (!hash->index->nfs_flush)
 			return 0;
+
+		if (fstat(hash->fd, &st) == 0)
+			return 0;
+		if (errno != ESTALE) {
+			mail_hash_set_syscall_error(hash, "fstat()");
+			return -1;
+		}
 	}
 	return mail_hash_reopen(hash);
 }
@@ -894,6 +903,7 @@
 {
 	uint32_t hash_key = key == NULL ? 0 : hash->key_hash_cb(key);
 
+	i_assert(hash->locked);
 	i_assert((key == NULL && hash->rec_hash_cb(value) == 0) ||
 		 (key != NULL && hash_key != 0 &&
 		  hash->rec_hash_cb(value) != 0));
--- a/src/lib-index/mail-index-lock.c	Sun Nov 18 08:01:54 2007 +0200
+++ b/src/lib-index/mail-index-lock.c	Sun Nov 18 08:40:59 2007 +0200
@@ -120,19 +120,20 @@
 	return 1;
 }
 
-static void mail_index_flush_read_cache(struct mail_index *index)
+void mail_index_flush_read_cache(struct mail_index *index, const char *path,
+				 int fd, bool locked)
 {
 	if (!index->nfs_flush)
 		return;
 
-	/* Assume flock() is independent of fcntl() locks. This isn't true
-	   with Linux 2.6 NFS, but with it there's no point in using flock() */
-	if (index->lock_method == FILE_LOCK_METHOD_FCNTL) {
-		nfs_flush_read_cache(index->filepath, index->fd,
-				     index->lock_type, TRUE);
+	/* Assume flock() is emulated with fcntl(), because that's how most
+	   OSes work nowadays. */
+	if (locked &&
+	    (index->lock_method == FILE_LOCK_METHOD_FCNTL ||
+	     index->lock_method == FILE_LOCK_METHOD_FLOCK)) {
+		nfs_flush_read_cache_locked(path, fd);
 	} else {
-		nfs_flush_read_cache(index->filepath, index->fd,
-				     F_UNLCK, FALSE);
+		nfs_flush_read_cache_unlocked(path, fd);
 	}
 }
 
@@ -143,7 +144,8 @@
 	ret = mail_index_lock(index, F_RDLCK, MAIL_INDEX_SHARED_LOCK_TIMEOUT,
 			      lock_id_r);
 	if (ret > 0) {
-		mail_index_flush_read_cache(index);
+		mail_index_flush_read_cache(index, index->filepath,
+					    index->fd, TRUE);
 		return 0;
 	}
 	if (ret < 0)
@@ -161,8 +163,10 @@
 {
 	int ret;
 
-	if ((ret = mail_index_lock(index, F_WRLCK, 0, lock_id_r)) > 0)
-		mail_index_flush_read_cache(index);
+	if ((ret = mail_index_lock(index, F_WRLCK, 0, lock_id_r)) > 0) {
+		mail_index_flush_read_cache(index, index->filepath,
+					    index->fd, TRUE);
+	}
 	return ret;
 }
 
--- a/src/lib-index/mail-index-map.c	Sun Nov 18 08:01:54 2007 +0200
+++ b/src/lib-index/mail-index-map.c	Sun Nov 18 08:40:59 2007 +0200
@@ -811,8 +811,8 @@
 	if (mail_index_lock_shared(index, &lock_id) < 0)
 		return -1;
 
-	if (index->nfs_flush && index->lock_method != FILE_LOCK_METHOD_FCNTL)
-		nfs_flush_attr_cache_fd(index->filepath, index->fd);
+	if (index->nfs_flush)
+		nfs_flush_attr_cache_fd_locked(index->filepath, index->fd);
 
 	if (fstat(index->fd, &st) == 0)
 		file_size = st.st_size;
--- a/src/lib-index/mail-index-private.h	Sun Nov 18 08:01:54 2007 +0200
+++ b/src/lib-index/mail-index-private.h	Sun Nov 18 08:40:59 2007 +0200
@@ -244,6 +244,9 @@
 /* Update/rewrite the main index file from index->map */
 void mail_index_write(struct mail_index *index, bool want_rotate);
 
+void mail_index_flush_read_cache(struct mail_index *index, const char *path,
+				 int fd, bool locked);
+
 /* Returns 0 = ok, -1 = error. */
 int mail_index_lock_shared(struct mail_index *index, unsigned int *lock_id_r);
 /* Returns 1 = ok, 0 = already locked, -1 = error. */
--- a/src/lib-index/mail-index.c	Sun Nov 18 08:01:54 2007 +0200
+++ b/src/lib-index/mail-index.c	Sun Nov 18 08:40:59 2007 +0200
@@ -248,9 +248,6 @@
 	i_assert(index->fd == -1);
 	i_assert(!MAIL_INDEX_IS_IN_MEMORY(index));
 
-	if (index->nfs_flush)
-		nfs_flush_attr_cache(index->filepath);
-
         /* Note that our caller must close index->fd by itself. */
 	if (index->readonly)
 		errno = EACCES;
@@ -465,6 +462,7 @@
 {
 	struct stat st1, st2;
 
+	i_assert(index->shared_lock_count == 0 || !index->nfs_flush);
 	i_assert(index->excl_lock_count == 0);
 
 	if (MAIL_INDEX_IS_IN_MEMORY(index))
@@ -474,30 +472,19 @@
 		return mail_index_try_open_only(index);
 
 	if (index->nfs_flush)
-		nfs_flush_attr_cache(index->filepath);
+		nfs_flush_attr_cache_unlocked(index->filepath);
 	if (nfs_safe_stat(index->filepath, &st2) < 0) {
 		if (errno == ENOENT)
 			return 0;
-
 		return mail_index_set_syscall_error(index, "stat()");
 	}
 
-	if (index->nfs_flush) {
-		if (!nfs_flush_attr_cache_fd(index->filepath, index->fd)) {
-			/* deleted/recreated, reopen */
-			mail_index_close_file(index);
-			return mail_index_try_open_only(index);
-		}
-	}
 	if (fstat(index->fd, &st1) < 0) {
 		if (errno != ESTALE)
 			return mail_index_set_syscall_error(index, "fstat()");
 		/* deleted/recreated, reopen */
-		mail_index_close_file(index);
-		return mail_index_try_open_only(index);
-	}
-
-	if (st1.st_ino == st2.st_ino && CMP_DEV_T(st1.st_dev, st2.st_dev)) {
+	} else if (st1.st_ino == st2.st_ino &&
+		   CMP_DEV_T(st1.st_dev, st2.st_dev)) {
 		/* the same file */
 		return 1;
 	}
--- a/src/lib-index/mail-transaction-log-file.c	Sun Nov 18 08:01:54 2007 +0200
+++ b/src/lib-index/mail-transaction-log-file.c	Sun Nov 18 08:40:59 2007 +0200
@@ -435,7 +435,7 @@
 	bool rename_existing;
 
 	if (index->nfs_flush)
-		nfs_flush_attr_cache(file->filepath);
+		nfs_flush_attr_cache_unlocked(file->filepath);
 
 	/* log creation is locked now - see if someone already created it.
 	   note that if we're rotating, we need to keep the log locked until
@@ -794,11 +794,9 @@
 			return -1;
 		}
 
-		if (file->log->index->nfs_flush) {
-			/* The size field will be updated soon */
-			nfs_flush_read_cache(file->filepath, file->fd,
-					     F_UNLCK, FALSE);
-		}
+		/* The size field will be updated soon */
+		mail_index_flush_read_cache(file->log->index, file->filepath,
+					    file->fd, file->locked);
 	}
 
 	if (file->next != NULL &&
@@ -920,7 +918,7 @@
 	   if file is locked, the attribute cache was already flushed when
 	   refreshing the log. */
 	if (file->log->index->nfs_flush && nfs_flush)
-		nfs_flush_attr_cache_fd(file->filepath, file->fd);
+		nfs_flush_attr_cache_fd_locked(file->filepath, file->fd);
 
 	if (file->buffer != NULL && file->buffer_offset > start_offset) {
 		/* we have to insert missing data to beginning of buffer */
--- a/src/lib-index/mail-transaction-log.c	Sun Nov 18 08:01:54 2007 +0200
+++ b/src/lib-index/mail-transaction-log.c	Sun Nov 18 08:40:59 2007 +0200
@@ -279,8 +279,12 @@
 
 	path = t_strconcat(log->index->filepath,
 			   MAIL_TRANSACTION_LOG_SUFFIX, NULL);
-	if (log->index->nfs_flush && nfs_flush)
-		nfs_flush_attr_cache(path);
+	if (log->index->nfs_flush && nfs_flush) {
+		if (!log->head->locked)
+			nfs_flush_attr_cache_unlocked(path);
+		else
+			nfs_flush_attr_cache_dir(log->index->dir);
+	}
 	if (nfs_safe_stat(path, &st) < 0) {
 		if (errno != ENOENT) {
 			mail_index_file_set_syscall_error(log->index, path,
--- a/src/lib-index/mailbox-list-index-sync.c	Sun Nov 18 08:01:54 2007 +0200
+++ b/src/lib-index/mailbox-list-index-sync.c	Sun Nov 18 08:40:59 2007 +0200
@@ -365,10 +365,8 @@
 				  MAIL_INDEX_SYNC_FLAG_AVOID_FLAG_UPDATES) < 0)
 		return -1;
 
-	if (ctx->index->mail_index->nfs_flush) {
-		nfs_flush_read_cache(ctx->index->filepath, ctx->index->fd,
-				     F_UNLCK, FALSE);
-	}
+	mail_index_flush_read_cache(ctx->index->mail_index, ctx->index->filepath,
+				    ctx->index->fd, FALSE);
 
 	/* we should have only external transactions in here, for which we
 	   don't need to do anything but write them to the index */
--- a/src/lib-index/mailbox-list-index.c	Sun Nov 18 08:01:54 2007 +0200
+++ b/src/lib-index/mailbox-list-index.c	Sun Nov 18 08:40:59 2007 +0200
@@ -58,11 +58,6 @@
 
 static void mailbox_list_index_unmap(struct mailbox_list_index *index)
 {
-	if (index->mail_index->nfs_flush && index->fd != -1) {
-		nfs_flush_read_cache(index->filepath, index->fd,
-				     F_UNLCK, FALSE);
-	}
-
 	if (index->file_cache != NULL)
 		file_cache_invalidate(index->file_cache, 0, (uoff_t)-1);
 
@@ -229,16 +224,18 @@
 		return 1;
 
 	if (index->mail_index->nfs_flush)
-		nfs_flush_attr_cache(index->filepath);
+		nfs_flush_attr_cache_unlocked(index->filepath);
 
 	if (nfs_safe_stat(index->filepath, &st1) < 0) {
-		if (errno == ENOENT)
+		if (errno == ENOENT || errno == ESTALE)
 			return 1;
 
 		mailbox_list_index_set_syscall_error(index, "stat()");
 		return -1;
 	}
 	if (fstat(index->fd, &st2) < 0) {
+		if (errno == ESTALE)
+			return 1;
 		mailbox_list_index_set_syscall_error(index, "fstat()");
 		return -1;
 	}
--- a/src/lib-storage/index/maildir/maildir-keywords.c	Sun Nov 18 08:01:54 2007 +0200
+++ b/src/lib-storage/index/maildir/maildir-keywords.c	Sun Nov 18 08:40:59 2007 +0200
@@ -118,7 +118,7 @@
            errors. */
 
 	if ((mk->storage->flags & MAIL_STORAGE_FLAG_NFS_FLUSH_STORAGE) != 0)
-		nfs_flush_attr_cache(mk->path);
+		nfs_flush_attr_cache_unlocked(mk->path);
 
 	if (nfs_safe_stat(mk->path, &st) < 0) {
 		if (errno == ENOENT) {
--- a/src/lib-storage/index/maildir/maildir-uidlist.c	Sun Nov 18 08:01:54 2007 +0200
+++ b/src/lib-storage/index/maildir/maildir-uidlist.c	Sun Nov 18 08:40:59 2007 +0200
@@ -653,9 +653,14 @@
 		/* NFS: either the file hasn't been changed, or it has already
 		   been deleted and the inodes just happen to be the same.
 		   check if the fd is still valid. */
-		if (!nfs_flush_attr_cache_fd(uidlist->path, uidlist->fd)) {
-			*recreated_r = TRUE;
-			return 1;
+		if (fstat(uidlist->fd, &st) < 0) {
+			if (errno == ESTALE) {
+				*recreated_r = TRUE;
+				return 1;
+			}
+			mail_storage_set_critical(storage,
+				"fstat(%s) failed: %m", uidlist->path);
+			return -1;
 		}
 	}
 
@@ -682,7 +687,7 @@
 		if (!nfs_flush)
 			uidlist->nfs_dirty_refresh = TRUE;
 		else {
-			nfs_flush_attr_cache(uidlist->path);
+			nfs_flush_attr_cache_unlocked(uidlist->path);
 			uidlist->nfs_dirty_refresh = FALSE;
 		}
 	}
@@ -704,7 +709,6 @@
 			break;
 		/* ESTALE - try reopening and rereading */
 		maildir_uidlist_close(uidlist);
-		nfs_flush_attr_cache(uidlist->path);
         }
 	if (ret >= 0)
 		uidlist->initial_read = TRUE;
--- a/src/lib-storage/index/mbox/mbox-lock.c	Sun Nov 18 08:01:54 2007 +0200
+++ b/src/lib-storage/index/mbox/mbox-lock.c	Sun Nov 18 08:40:59 2007 +0200
@@ -173,7 +173,7 @@
 	if (mbox->mbox_fd != -1) {
 		if ((mbox->storage->storage.flags &
 		     MAIL_STORAGE_FLAG_NFS_FLUSH_STORAGE) != 0)
-			nfs_flush_attr_cache(mbox->path);
+			nfs_flush_attr_cache_maybe_locked(mbox->path);
 		if (nfs_safe_stat(mbox->path, &st) < 0) {
 			mbox_set_syscall_error(mbox, "stat()");
 			return -1;
@@ -567,9 +567,13 @@
 
 		if ((mbox->storage->storage.flags &
 		     MAIL_STORAGE_FLAG_NFS_FLUSH_STORAGE) != 0) {
-			nfs_flush_read_cache(mbox->path, mbox->mbox_fd,
-					     fcntl_locked ? lock_type : F_UNLCK,
-					     fcntl_locked);
+			if (fcntl_locked) {
+				nfs_flush_read_cache_locked(mbox->path,
+							    mbox->mbox_fd);
+			} else {
+				nfs_flush_read_cache_unlocked(mbox->path,
+							      mbox->mbox_fd);
+			}
 		}
 
 		mbox->mbox_lock_id += 2;
--- a/src/lib-storage/mailbox-list.c	Sun Nov 18 08:01:54 2007 +0200
+++ b/src/lib-storage/mailbox-list.c	Sun Nov 18 08:40:59 2007 +0200
@@ -242,6 +242,7 @@
 	if (list->file_create_mode != (mode_t)-1) {
 		*mode_r = list->file_create_mode;
 		*gid_r = list->file_create_gid;
+		return;
 	}
 
 	path = mailbox_list_get_path(list, NULL, MAILBOX_LIST_PATH_TYPE_DIR);
--- a/src/lib/file-dotlock.c	Sun Nov 18 08:01:54 2007 +0200
+++ b/src/lib/file-dotlock.c	Sun Nov 18 08:40:59 2007 +0200
@@ -159,7 +159,7 @@
 	/* don't waste time flushing attribute cache the first time we're here.
 	   if it's stale we'll get back here soon. */
 	if (lock_info->set->nfs_flush && lock_info->lock_stated)
-		nfs_flush_attr_cache(lock_info->lock_path);
+		nfs_flush_attr_cache_unlocked(lock_info->lock_path);
 
 	lock_info->lock_stated = TRUE;
 	if (nfs_safe_lstat(lock_info->lock_path, &st) < 0) {
@@ -249,7 +249,7 @@
 		/* possibly stale lock file. check also the timestamp of the
 		   file we're protecting. */
 		if (lock_info->set->nfs_flush)
-			nfs_flush_attr_cache(lock_info->path);
+			nfs_flush_attr_cache_maybe_locked(lock_info->path);
 		if (nfs_safe_stat(lock_info->path, &st) < 0) {
 			if (errno == ENOENT) {
 				/* file doesn't exist. treat it as if
--- a/src/lib/nfs-workarounds.c	Sun Nov 18 08:01:54 2007 +0200
+++ b/src/lib/nfs-workarounds.c	Sun Nov 18 08:40:59 2007 +0200
@@ -34,10 +34,9 @@
 #include <unistd.h>
 #include <sys/stat.h>
 
-#ifdef __linux__
+#if defined (__linux__) || defined(__sun)
 #  define READ_CACHE_FLUSH_FCNTL
 #endif
-
 #if defined(__FreeBSD__) || defined(__sun)
 #  define ATTRCACHE_FLUSH_CHOWN_UID_1
 #endif
@@ -67,7 +66,7 @@
                                 break;
                         dir = t_strdup_until(path, dir);
 		}
-		nfs_flush_attr_cache(path);
+		nfs_flush_attr_cache_dir(path);
                 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
@@ -163,38 +162,6 @@
 	return 0;
 }
 
-static bool nfs_flush_fchown_uid(const char *path, int fd)
-{
-	struct stat st;
-	uid_t uid;
-
-	if (fstat(fd, &st) < 0) {
-		if (likely(errno == ESTALE)) {
-			/* ESTALE causes the OS to flush the attr cache */
-			return FALSE;
-		}
-		i_error("nfs_flush_fchown_uid: fstat(%s) failed: %m", path);
-		return TRUE;
-	}
-#ifdef ATTRCACHE_FLUSH_CHOWN_UID_1
-	uid = (uid_t)-1;
-#else
-	uid = st.st_uid;
-#endif
-	if (fchown(fd, uid, (gid_t)-1) < 0) {
-		if (errno == ESTALE)
-			return FALSE;
-		if (likely(errno == EACCES || errno == EPERM)) {
-			/* attr cache is flushed */
-			return TRUE;
-		}
-
-		i_error("nfs_flush_fchown_uid: fchown(%s) failed: %m", path);
-	}
-	return TRUE;
-}
-
-#ifndef __FreeBSD__
 static void nfs_flush_chown_uid(const char *path)
 {
 	uid_t uid;
@@ -211,7 +178,7 @@
 			/* ESTALE causes the OS to flush the attr cache */
 			return;
 		}
-		if (unlikely(errno != ENOENT)) {
+		if (errno != ENOENT) {
 			i_error("nfs_flush_chown_uid: stat(%s) failed: %m",
 				path);
 			return;
@@ -225,18 +192,17 @@
 	}
 #endif
 	if (chown(path, uid, (gid_t)-1) < 0) {
-		if (likely(errno == ESTALE || errno == EACCES ||
-			   errno == EPERM || errno == ENOENT)) {
+		if (errno == ESTALE || errno == EACCES ||
+		    errno == EPERM || errno == ENOENT) {
 			/* attr cache is flushed */
 			return;
 		}
 		i_error("nfs_flush_chown_uid: chown(%s) failed: %m", path);
 	}
 }
-#endif
 
 #ifdef READ_CACHE_FLUSH_FCNTL
-static void nfs_flush_fcntl(const char *path, int fd, int old_lock_type)
+static void nfs_flush_fcntl(const char *path, int fd)
 {
 	struct flock fl;
 	int ret;
@@ -245,7 +211,7 @@
 	   again. It should succeed just fine. If was was unlocked, we'll
 	   have to get a lock and then unlock it. Linux 2.6 flushes read cache
 	   only when read/write locking succeeded. */
-	fl.l_type = old_lock_type != F_UNLCK ? old_lock_type : F_RDLCK;
+	fl.l_type = F_RDLCK;
 	fl.l_whence = SEEK_SET;
 	fl.l_start = 0;
 	fl.l_len = 0;
@@ -259,14 +225,77 @@
 		return;
 	}
 
-	if (old_lock_type == F_UNLCK) {
-		fl.l_type = F_UNLCK;
-		(void)fcntl(fd, F_SETLKW, &fl);
-	}
+	fl.l_type = F_UNLCK;
+	(void)fcntl(fd, F_SETLKW, &fl);
 }
 #endif
 
-static void nfs_flush_attr_cache_dir(const char *path)
+static void nfs_flush_attr_cache_parent_dir(const char *path)
+{
+	const char *p;
+
+	p = strrchr(path, '/');
+	if (p == NULL)
+		nfs_flush_attr_cache_dir(".");
+	else {
+		t_push();
+		nfs_flush_attr_cache_dir(t_strdup_until(path, p));
+		t_pop();
+	}
+}
+
+void nfs_flush_attr_cache_unlocked(const char *path)
+{
+#ifndef __FreeBSD__
+	int fd;
+
+	fd = open(path, O_RDONLY);
+	if (fd != -1)
+		(void)close(fd);
+	else if (errno == ESTALE) {
+		/* this already flushed the cache */
+	} else
+#endif
+	{
+		/* most likely ENOENT, which means a negative cache hit.
+		   flush the parent directory's attribute cache. */
+		nfs_flush_attr_cache_parent_dir(path);
+	}
+}
+
+void nfs_flush_attr_cache_maybe_locked(const char *path)
+{
+	/* first we'll flush the parent directory to get the file handle
+	   cache flushed */
+	nfs_flush_attr_cache_parent_dir(path);
+
+	/* after we now know the latest file handle, flush its attribute
+	   cache */
+	nfs_flush_chown_uid(path);
+}
+
+bool nfs_flush_attr_cache_fd_locked(const char *path ATTR_UNUSED,
+				    int fd ATTR_UNUSED)
+{
+#ifdef __FreeBSD__
+	if (fchown(fd, (uid_t)-1, (gid_t)-1) < 0) {
+		if (errno == ESTALE)
+			return FALSE;
+		if (likely(errno == EACCES || errno == EPERM)) {
+			/* attr cache is flushed */
+			return TRUE;
+		}
+
+		i_error("nfs_flush_attr_cache_fd_locked: fchown(%s) failed: %m",
+			path);
+	}
+	return TRUE;
+#else
+	return TRUE;
+#endif
+}
+
+void nfs_flush_attr_cache_dir(const char *path)
 {
 #ifdef __FreeBSD__
 	/* Unfortunately rmdir() seems to be the only way to flush a
@@ -286,38 +315,31 @@
 		i_error("nfs_flush_dir: rmdir(%s) failed: %m", path);
 	}
 #else
-	nfs_flush_chown_uid(path);
+	int fd;
+
+	fd = open(path, O_RDONLY);
+	if (fd != -1)
+		(void)close(fd);
 #endif
 }
 
-void nfs_flush_attr_cache(const char *path)
+void nfs_flush_read_cache_locked(const char *path ATTR_UNUSED,
+				 int fd ATTR_UNUSED)
 {
-	const char *p;
-
-	p = strrchr(path, '/');
-	if (p == NULL)
-		nfs_flush_attr_cache_dir(".");
-	else {
-		t_push();
-		nfs_flush_attr_cache_dir(t_strdup_until(path, p));
-		t_pop();
-	}
+#ifdef READ_CACHE_FLUSH_FCNTL
+	/* already flushed when fcntl() was called */
+#else
+	/* we can only hope that underlying filesystem uses micro/nanosecond
+	   resolution so that attribute cache flushing notices mtime changes */
+	nfs_flush_attr_cache_fd_locked(path, fd);
+#endif
 }
 
-bool nfs_flush_attr_cache_fd(const char *path, int fd)
-{
-	return nfs_flush_fchown_uid(path, fd);
-}
-
-void nfs_flush_read_cache(const char *path, int fd,
-			  int lock_type ATTR_UNUSED,
-			  bool just_locked ATTR_UNUSED)
+void nfs_flush_read_cache_unlocked(const char *path, int fd)
 {
 #ifdef READ_CACHE_FLUSH_FCNTL
-	if (!just_locked)
-		nfs_flush_fcntl(path, fd, lock_type);
+	nfs_flush_fcntl(path, fd);
 #else
-	/* FreeBSD, Solaris */
-	nfs_flush_fchown_uid(path, fd);
+	nfs_flush_read_cache_locked(path, fd);
 #endif
 }
--- a/src/lib/nfs-workarounds.h	Sun Nov 18 08:01:54 2007 +0200
+++ b/src/lib/nfs-workarounds.h	Sun Nov 18 08:40:59 2007 +0200
@@ -19,18 +19,22 @@
    is 1, otherwise stat() first to find it out. */
 int nfs_safe_link(const char *oldpath, const char *newpath, bool links1);
 
-/* Flush attribute cache for given path. This actually flushes the parent
-   directory's attribute cache to make sure that the file handle also gets
-   refreshed. */
-void nfs_flush_attr_cache(const char *path);
-/* Flush attribute cache for given file descriptor.
+/* Flush attribute cache for given path. The file must not be fcntl locked or
+   the locks may get dropped. */
+void nfs_flush_attr_cache_unlocked(const char *path);
+/* Flush attribute cache for given path. The file may be fcntl locked. */
+void nfs_flush_attr_cache_maybe_locked(const char *path);
+/* Flush attribute cache for a fcntl locked file descriptor. If locking flushes
+   the attribute cache with the running OS, this function does nothing.
    The given path is used only for logging. */
-bool nfs_flush_attr_cache_fd(const char *path, int fd);
-/* Flush read cache for given fd. lock_type must be set to the file's current
-   fcntl locking state (F_UNLCK, F_RDLCK, F_WRLCK). Set just_locked=TRUE if the
-   file was locked at the same time as read cache flush was wanted (to avoid
-   re-locking the file unneededly). */
-void nfs_flush_read_cache(const char *path, int fd,
-			  int lock_type, bool just_locked);
+bool nfs_flush_attr_cache_fd_locked(const char *path, int fd);
+/* Flush a directory's attribute cache. */
+void nfs_flush_attr_cache_dir(const char *path);
+
+/* Flush read cache for fd that was just fcntl locked. If the OS flushes
+   read cache when fcntl locking file, this function does nothing. */
+void nfs_flush_read_cache_locked(const char *path, int fd);
+/* Flush read cache for fd that doesn't have fcntl locks. */
+void nfs_flush_read_cache_unlocked(const char *path, int fd);
 
 #endif