changeset 6857:41911abe6fa7 HEAD

NFS cache flushing updates.
author Timo Sirainen <tss@iki.fi>
date Sun, 25 Nov 2007 15:47:36 +0200
parents a249d916f6e6
children 78922561e9ea
files src/lib-index/mail-cache.c src/lib-index/mail-hash.c 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.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/file-dotlock.c src/lib/nfs-workarounds.c src/lib/nfs-workarounds.h
diffstat 12 files changed, 124 insertions(+), 106 deletions(-) [+]
line wrap: on
line diff
--- a/src/lib-index/mail-cache.c	Sun Nov 25 15:05:31 2007 +0200
+++ b/src/lib-index/mail-cache.c	Sun Nov 25 15:47:36 2007 +0200
@@ -110,7 +110,7 @@
 	/* see if the file has changed */
 	if (cache->index->nfs_flush) {
 		i_assert(!cache->locked);
-		nfs_flush_attr_cache_unlocked(cache->filepath);
+		nfs_flush_file_handle_cache(cache->filepath);
 	}
 	if (nfs_safe_stat(cache->filepath, &st) < 0) {
 		mail_cache_set_syscall_error(cache, "stat()");
--- a/src/lib-index/mail-hash.c	Sun Nov 25 15:05:31 2007 +0200
+++ b/src/lib-index/mail-hash.c	Sun Nov 25 15:47:36 2007 +0200
@@ -657,7 +657,7 @@
 		return mail_hash_reopen(hash);
 
 	if (hash->index->nfs_flush)
-		nfs_flush_attr_cache_unlocked(hash->filepath);
+		nfs_flush_file_handle_cache(hash->filepath);
 
 	if (nfs_safe_stat(hash->filepath, &st) < 0) {
 		if (errno != ENOENT) {
--- a/src/lib-index/mail-index.c	Sun Nov 25 15:05:31 2007 +0200
+++ b/src/lib-index/mail-index.c	Sun Nov 25 15:47:36 2007 +0200
@@ -472,7 +472,7 @@
 		return mail_index_try_open_only(index);
 
 	if (index->nfs_flush)
-		nfs_flush_attr_cache_unlocked(index->filepath);
+		nfs_flush_file_handle_cache(index->filepath);
 	if (nfs_safe_stat(index->filepath, &st2) < 0) {
 		if (errno == ENOENT)
 			return 0;
--- a/src/lib-index/mail-transaction-log-file.c	Sun Nov 25 15:05:31 2007 +0200
+++ b/src/lib-index/mail-transaction-log-file.c	Sun Nov 25 15:47:36 2007 +0200
@@ -434,8 +434,12 @@
 	int fd, ret;
 	bool rename_existing;
 
-	if (index->nfs_flush)
-		nfs_flush_attr_cache_unlocked(file->filepath);
+	if (index->nfs_flush) {
+		/* although we check also mtime and file size below, it's done
+		   only to fix broken log files. we don't bother flushing
+		   attribute cache just for that. */
+		nfs_flush_file_handle_cache(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
--- a/src/lib-index/mail-transaction-log.c	Sun Nov 25 15:05:31 2007 +0200
+++ b/src/lib-index/mail-transaction-log.c	Sun Nov 25 15:47:36 2007 +0200
@@ -279,12 +279,8 @@
 
 	path = t_strconcat(log->index->filepath,
 			   MAIL_TRANSACTION_LOG_SUFFIX, NULL);
-	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 (log->index->nfs_flush && nfs_flush)
+		nfs_flush_file_handle_cache(path);
 	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.c	Sun Nov 25 15:05:31 2007 +0200
+++ b/src/lib-index/mailbox-list-index.c	Sun Nov 25 15:47:36 2007 +0200
@@ -224,7 +224,7 @@
 		return 1;
 
 	if (index->mail_index->nfs_flush)
-		nfs_flush_attr_cache_unlocked(index->filepath);
+		nfs_flush_file_handle_cache(index->filepath);
 
 	if (nfs_safe_stat(index->filepath, &st1) < 0) {
 		if (errno == ENOENT || errno == ESTALE)
--- a/src/lib-storage/index/maildir/maildir-keywords.c	Sun Nov 25 15:05:31 2007 +0200
+++ b/src/lib-storage/index/maildir/maildir-keywords.c	Sun Nov 25 15:47:36 2007 +0200
@@ -117,8 +117,11 @@
            we rely on stat()'s timestamp and don't bother handling ESTALE
            errors. */
 
-	if ((mk->storage->flags & MAIL_STORAGE_FLAG_NFS_FLUSH_STORAGE) != 0)
-		nfs_flush_attr_cache_unlocked(mk->path);
+	if ((mk->storage->flags & MAIL_STORAGE_FLAG_NFS_FLUSH_STORAGE) != 0) {
+		/* file is updated only by replacing it, no need to flush
+		   attribute cache */
+		nfs_flush_file_handle_cache(mk->path);
+	}
 
 	if (nfs_safe_stat(mk->path, &st) < 0) {
 		if (errno == ENOENT) {
--- a/src/lib-storage/index/maildir/maildir-uidlist.c	Sun Nov 25 15:05:31 2007 +0200
+++ b/src/lib-storage/index/maildir/maildir-uidlist.c	Sun Nov 25 15:47:36 2007 +0200
@@ -687,6 +687,7 @@
 		if (!nfs_flush)
 			uidlist->nfs_dirty_refresh = TRUE;
 		else {
+			nfs_flush_file_handle_cache(uidlist->path);
 			nfs_flush_attr_cache_unlocked(uidlist->path);
 			uidlist->nfs_dirty_refresh = FALSE;
 		}
--- a/src/lib-storage/index/mbox/mbox-lock.c	Sun Nov 25 15:05:31 2007 +0200
+++ b/src/lib-storage/index/mbox/mbox-lock.c	Sun Nov 25 15:47:36 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_maybe_locked(mbox->path);
+			nfs_flush_file_handle_cache(mbox->path);
 		if (nfs_safe_stat(mbox->path, &st) < 0) {
 			mbox_set_syscall_error(mbox, "stat()");
 			return -1;
@@ -568,9 +568,12 @@
 		if ((mbox->storage->storage.flags &
 		     MAIL_STORAGE_FLAG_NFS_FLUSH_STORAGE) != 0) {
 			if (fcntl_locked) {
+				nfs_flush_attr_cache_fd_locked(mbox->path,
+							       mbox->mbox_fd);
 				nfs_flush_read_cache_locked(mbox->path,
 							    mbox->mbox_fd);
 			} else {
+				nfs_flush_attr_cache_unlocked(mbox->path);
 				nfs_flush_read_cache_unlocked(mbox->path,
 							      mbox->mbox_fd);
 			}
--- a/src/lib/file-dotlock.c	Sun Nov 25 15:05:31 2007 +0200
+++ b/src/lib/file-dotlock.c	Sun Nov 25 15:47:36 2007 +0200
@@ -158,8 +158,10 @@
 
 	/* 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)
+	if (lock_info->set->nfs_flush && lock_info->lock_stated) {
+		nfs_flush_file_handle_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) {
@@ -248,8 +250,10 @@
 
 		/* possibly stale lock file. check also the timestamp of the
 		   file we're protecting. */
-		if (lock_info->set->nfs_flush)
+		if (lock_info->set->nfs_flush) {
+			nfs_flush_file_handle_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 25 15:05:31 2007 +0200
+++ b/src/lib/nfs-workarounds.c	Sun Nov 25 15:47:36 2007 +0200
@@ -41,12 +41,12 @@
 #  define ATTRCACHE_FLUSH_CHOWN_UID_1
 #endif
 
+static void nfs_flush_file_handle_cache_parent_dir(const char *path);
+
 static int
 nfs_safe_do(const char *path, int (*callback)(const char *path, void *context),
 	    void *context)
 {
-        const char *dir = NULL;
-        struct stat st;
         unsigned int i;
 	int ret;
 
@@ -57,26 +57,9 @@
                         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);
-		}
-		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
-                           with the original ESTALE error and let our caller
-                           handle it. */
-                        errno = ESTALE;
-                        break;
-                }
-
-                /* directory still exists, try reopening */
+		   can't internally revalidate the NFS file handle. Flush the
+		   file handle and try again */
+		nfs_flush_file_handle_cache(path);
         }
         t_pop();
         return ret;
@@ -178,29 +161,58 @@
 			/* ESTALE causes the OS to flush the attr cache */
 			return;
 		}
-		if (errno != ENOENT) {
-			i_error("nfs_flush_chown_uid: stat(%s) failed: %m",
-				path);
+		if (likely(errno == ENOENT)) {
+			nfs_flush_file_handle_cache_parent_dir(path);
 			return;
 		}
-
-		/* flush a negative cache entry. use effective UID to chown.
-		   it probably doesn't really matter what UID is used, because
-		   as long as we're not root we don't have permission to really
-		   change it anyway */
-		uid = geteuid();
+		i_error("nfs_flush_chown_uid: stat(%s) failed: %m", path);
+		return;
 	}
 #endif
 	if (chown(path, uid, (gid_t)-1) < 0) {
-		if (errno == ESTALE || errno == EACCES ||
-		    errno == EPERM || errno == ENOENT) {
+		if (errno == ESTALE || errno == EPERM || errno == ENOENT) {
 			/* attr cache is flushed */
 			return;
 		}
+		if (likely(errno == ENOENT)) {
+			nfs_flush_file_handle_cache_parent_dir(path);
+			return;
+		}
 		i_error("nfs_flush_chown_uid: chown(%s) failed: %m", path);
 	}
 }
 
+#ifdef __FreeBSD__
+static bool nfs_flush_fchown_uid(const char *path, int fd)
+{
+	uid_t uid;
+#ifndef ATTRCACHE_FLUSH_CHOWN_UID_1
+	struct stat st;
+
+	if (fstat(fd, &st) < 0) {
+		if (likely(errno == ESTALE))
+			return FALSE;
+		i_error("nfs_flush_attr_cache_fchown: fstat(%s) failed: %m",
+			path);
+		return TRUE;
+	}
+	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_attr_cache_fd_locked: fchown(%s) failed: %m",
+			path);
+	}
+	return TRUE;
+}
+#endif
+
 #ifdef READ_CACHE_FLUSH_FCNTL
 static void nfs_flush_fcntl(const char *path, int fd)
 {
@@ -230,47 +242,25 @@
 }
 #endif
 
-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;
 
+	/* Try to flush the attribute cache the nice way first. */
 	fd = open(path, O_RDONLY);
 	if (fd != -1)
 		(void)close(fd);
 	else if (errno == ESTALE) {
 		/* this already flushed the cache */
-	} else
-#endif
-	{
+	} else {
 		/* most likely ENOENT, which means a negative cache hit.
-		   flush the parent directory's attribute cache. */
-		nfs_flush_attr_cache_parent_dir(path);
+		   flush the file handles for its parent directory. */
+		nfs_flush_file_handle_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);
 }
 
@@ -278,49 +268,66 @@
 				    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;
+	/* FreeBSD doesn't flush attribute cache with fcntl(), so we have
+	   to do it ourself. */
+	return nfs_flush_fchown_uid(path, fd);
 #else
+	/* Linux and Solaris are fine. */
 	return TRUE;
 #endif
 }
 
-void nfs_flush_attr_cache_dir(const char *path)
+static bool nfs_flush_file_handle_cache_dir(const char *path)
 {
-#ifdef __FreeBSD__
-	/* Unfortunately rmdir() seems to be the only way to flush a
-	   directory's attribute cache. */
+#ifdef __linux__
+	/* chown()ing parent is the safest way to handle this */
+	nfs_flush_chown_uid(path);
+#else
+	/* rmdir() is the only choice with FreeBSD and Solaris */
 	if (unlikely(rmdir(path) == 0)) {
-		if (mkdir(path, 0600) == 0) {
-			i_warning("nfs_flush_dir: rmdir(%s) unexpectedly "
+		if (mkdir(path, 0700) == 0) {
+			i_warning("nfs_flush_file_handle_cache_dir: "
+				  "rmdir(%s) unexpectedly "
 				  "removed the dir. recreated.", path);
 		} else {
-			i_error("nfs_flush_dir: rmdir(%s) unexpectedly "
-				"removed the dir. mkdir() failed: %m", path);
+			i_warning("nfs_flush_file_handle_cache_dir: "
+				  "rmdir(%s) unexpectedly "
+				  "removed the dir. mkdir() failed: %m", path);
 		}
-	} else if (likely(errno == ESTALE || errno == ENOENT ||
-			  errno == ENOTEMPTY)) {
+	} else if (errno == ESTALE || errno == ENOTDIR || errno == ENOTEMPTY) {
 		/* expected failures */
+	} else if (errno == ENOENT) {
+		return FALSE;
 	} else {
-		i_error("nfs_flush_dir: rmdir(%s) failed: %m", path);
+		i_error("nfs_flush_file_handle_cache_dir: "
+			"rmdir(%s) failed: %m", path);
 	}
-#else
-	int fd;
+#endif
+	return TRUE;
+}
+
+static void nfs_flush_file_handle_cache_parent_dir(const char *path)
+{
+	const char *p;
 
-	fd = open(path, O_RDONLY);
-	if (fd != -1)
-		(void)close(fd);
+	p = strrchr(path, '/');
+	if (p == NULL)
+		nfs_flush_file_handle_cache_dir(".");
+	else {
+		t_push();
+		nfs_flush_file_handle_cache_dir(t_strdup_until(path, p));
+		t_pop();
+	}
+}
+
+void nfs_flush_file_handle_cache(const char *path)
+{
+#ifdef __FreeBSD__
+	/* Try to handle this more safely by rmdir()ing the file itself */
+	if (nfs_flush_file_handle_cache_dir(path))
+		return;
 #endif
+	nfs_flush_file_handle_cache_parent_dir(path);
 }
 
 void nfs_flush_read_cache_locked(const char *path ATTR_UNUSED,
--- a/src/lib/nfs-workarounds.h	Sun Nov 25 15:05:31 2007 +0200
+++ b/src/lib/nfs-workarounds.h	Sun Nov 25 15:47:36 2007 +0200
@@ -28,8 +28,8 @@
    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_locked(const char *path, int fd);
-/* Flush a directory's attribute cache. */
-void nfs_flush_attr_cache_dir(const char *path);
+/* Flush file handle cache for given file. */
+void nfs_flush_file_handle_cache(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. */