Mercurial > dovecot > original-hg > dovecot-1.2
changeset 5970:a290b84d144a HEAD
Added nfs_flush_attr_cache() and nfs_flush_read_cache().
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Thu, 12 Jul 2007 23:54:13 +0300 |
parents | 29770d8a013b |
children | 2bc25f61431f |
files | src/lib/nfs-workarounds.c src/lib/nfs-workarounds.h |
diffstat | 2 files changed, 154 insertions(+), 3 deletions(-) [+] |
line wrap: on
line diff
--- 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" @@ -7,6 +34,10 @@ #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), void *context) @@ -96,3 +127,110 @@ { 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 +}
--- 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