dovecot: Added nfs_flush_attr_cache() and nfs_flush_read_cache().

dovecot at dovecot.org dovecot at dovecot.org
Fri Jul 13 00:18:31 EEST 2007


details:   http://hg.dovecot.org/dovecot/rev/a290b84d144a
changeset: 5970:a290b84d144a
user:      Timo Sirainen <tss at iki.fi>
date:      Thu Jul 12 23:54:13 2007 +0300
description:
Added nfs_flush_attr_cache() and nfs_flush_read_cache().

diffstat:

2 files changed, 154 insertions(+), 3 deletions(-)
src/lib/nfs-workarounds.c |  140 ++++++++++++++++++++++++++++++++++++++++++++-
src/lib/nfs-workarounds.h |   17 ++++-

diffs (187 lines):

diff -r 29770d8a013b -r a290b84d144a src/lib/nfs-workarounds.c
--- a/src/lib/nfs-workarounds.c	Thu Jul 12 23:52:03 2007 +0300
+++ b/src/lib/nfs-workarounds.c	Thu Jul 12 23:54:13 2007 +0300
@@ -1,4 +1,31 @@
-/* Copyright (c) 2006 Timo Sirainen */
+/* Copyright (c) 2006-2007 Timo Sirainen */
+
+/*
+   These tests were done with various Linux 2.6 kernels, FreeBSD 6.2 and
+   Solaris 8 and 10.
+
+   Attribute cache is usually flushed with chown()ing or fchown()ing the file.
+   The safest way would be to use uid=-1 gid=-1, but this doesn't work with
+   Linux (it does with FreeBSD 6.2 and Solaris). So we'll first get the
+   file's owner and use it. As long as we're not root the file's owner can't
+   change accidentally. If would be possible to also use chmod()/fchmod(), but
+   that's riskier since it could actually cause an unwanted change.
+
+   Write cache can be flushed with fdatasync(). It's all we need, but other
+   tested alternatives are: fcntl locking (Linux 2.6, Solaris),
+   fchown() (Solaris) and dup()+close() (Linux 2.6, Solaris).
+
+   Read cache flushing is more problematic. There's no universal way to do it.
+   The working methods are:
+
+   Linux 2.6: fcntl(), O_DIRECT
+   Solaris: fchown(), fcntl(), dup()+close()
+   FreeBSD 6.2: fchown()
+
+   fchown() can be easily used for Solaris and FreeBSD, but Linux requires
+   playing with locks. O_DIRECT requires CONFIG_NFS_DIRECTIO to be enabled, so
+   we can't always use it.
+*/
 
 #include "lib.h"
 #include "nfs-workarounds.h"
@@ -6,6 +33,10 @@
 #include <fcntl.h>
 #include <unistd.h>
 #include <sys/stat.h>
+
+#ifdef __linux__
+#  define READ_CACHE_FLUSH_FCNTL
+#endif
 
 static int
 nfs_safe_do(const char *path, int (*callback)(const char *path, void *context),
@@ -96,3 +127,110 @@ int nfs_safe_lstat(const char *path, str
 {
 	return nfs_safe_do(path, nfs_safe_lstat_callback, buf);
 }
+
+static void nfs_flush_fchown_uid(const char *path, int fd)
+{
+	struct stat st;
+
+	if (fstat(fd, &st) < 0) {
+		if (errno == ESTALE) {
+			/* ESTALE causes the OS to flush the attr cache */
+			return;
+		}
+		i_error("nfs_flush_fchown_uid: fstat(%s) failed: %m", path);
+		return;
+	}
+	if (fchown(fd, st.st_uid, (gid_t)-1) < 0) {
+		if (errno == ESTALE || errno == EACCES || errno == EPERM) {
+			/* attr cache is flushed */
+			return;
+		}
+
+		i_error("nfs_flush_fchown_uid: fchown(%s) failed: %m", path);
+	}
+}
+
+static void nfs_flush_chown_uid(const char *path)
+{
+	struct stat st;
+
+	if (stat(path, &st) < 0) {
+		if (errno == ESTALE) {
+			/* ESTALE causes the OS to flush the attr cache */
+			return;
+		}
+		if (errno != ENOENT) {
+			i_error("nfs_flush_chown_uid: stat(%s) failed: %m",
+				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 */
+		st.st_uid = geteuid();
+	}
+	if (chown(path, st.st_uid, (gid_t)-1) < 0) {
+		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);
+	}
+}
+
+#ifdef READ_CACHE_FLUSH_FCNTL
+static void nfs_flush_fcntl(const char *path, int fd, int old_lock_type)
+{
+	struct flock fl;
+	int ret;
+
+	/* If the file was already locked, we'll just get the same lock
+	   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_whence = SEEK_SET;
+	fl.l_start = 0;
+	fl.l_len = 0;
+
+	alarm(60);
+	ret = fcntl(fd, F_SETLKW, &fl);
+	alarm(0);
+
+	if (ret < 0) {
+		i_error("nfs_flush_fcntl: fcntl(%s, F_RDLCK) failed: %m", path);
+		return;
+	}
+
+	if (old_lock_type == F_UNLCK) {
+		fl.l_type = F_UNLCK;
+		(void)fcntl(fd, F_SETLKW, &fl);
+	}
+}
+#endif
+
+void nfs_flush_attr_cache(const char *path)
+{
+	nfs_flush_chown_uid(path);
+}
+
+void nfs_flush_attr_cache_fd(const char *path, int fd)
+{
+	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__)
+{
+#ifdef READ_CACHE_FLUSH_FCNTL
+	if (!just_locked)
+		nfs_flush_fcntl(path, fd, lock_type);
+#else
+	/* FreeBSD, Solaris */
+	nfs_flush_fchown_uid(path, fd);
+#endif
+}
diff -r 29770d8a013b -r a290b84d144a src/lib/nfs-workarounds.h
--- a/src/lib/nfs-workarounds.h	Thu Jul 12 23:52:03 2007 +0300
+++ b/src/lib/nfs-workarounds.h	Thu Jul 12 23:54:13 2007 +0300
@@ -8,10 +8,23 @@
    file and retrying the operation. */
 #define NFS_ESTALE_RETRY_COUNT 10
 
-/* open() with some NFS workarounds */
+/* Same as open(), but try to handle ESTALE errors. */
 int nfs_safe_open(const char *path, int flags);
-/* stat() with some NFS workarounds */
+/* Same as stat(), but try to handle ESTALE errors.
+   Doesn't flush attribute cache. */
 int nfs_safe_stat(const char *path, struct stat *buf);
 int nfs_safe_lstat(const char *path, struct stat *buf);
 
+/* Flush attribute cache for given path */
+void nfs_flush_attr_cache(const char *path);
+/* Flush attribute cache for given file descriptor.
+   The given path is used only for logging. */
+void 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);
+
 #endif


More information about the dovecot-cvs mailing list