# HG changeset patch # User Timo Sirainen # Date 1195998456 -7200 # Node ID 41911abe6fa79bf17498b023fc707ec59814097c # Parent a249d916f6e6cb3ffa83c3343db167f471db3e62 NFS cache flushing updates. diff -r a249d916f6e6 -r 41911abe6fa7 src/lib-index/mail-cache.c --- 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()"); diff -r a249d916f6e6 -r 41911abe6fa7 src/lib-index/mail-hash.c --- 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) { diff -r a249d916f6e6 -r 41911abe6fa7 src/lib-index/mail-index.c --- 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; diff -r a249d916f6e6 -r 41911abe6fa7 src/lib-index/mail-transaction-log-file.c --- 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 diff -r a249d916f6e6 -r 41911abe6fa7 src/lib-index/mail-transaction-log.c --- 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, diff -r a249d916f6e6 -r 41911abe6fa7 src/lib-index/mailbox-list-index.c --- 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) diff -r a249d916f6e6 -r 41911abe6fa7 src/lib-storage/index/maildir/maildir-keywords.c --- 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) { diff -r a249d916f6e6 -r 41911abe6fa7 src/lib-storage/index/maildir/maildir-uidlist.c --- 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; } diff -r a249d916f6e6 -r 41911abe6fa7 src/lib-storage/index/mbox/mbox-lock.c --- 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); } diff -r a249d916f6e6 -r 41911abe6fa7 src/lib/file-dotlock.c --- 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 diff -r a249d916f6e6 -r 41911abe6fa7 src/lib/nfs-workarounds.c --- 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, diff -r a249d916f6e6 -r 41911abe6fa7 src/lib/nfs-workarounds.h --- 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. */