diff --git a/fs/nfs/file.c b/fs/nfs/file.c index 02795a01c7ef..03fd1dcc96bd 100644 --- a/fs/nfs/file.c +++ b/fs/nfs/file.c @@ -632,6 +632,8 @@ ssize_t nfs_file_write(struct kiocb *iocb, struct iov_iter *from) goto out; } + nfs_clear_invalid_mapping(file->f_mapping); + since = filemap_sample_wb_err(file->f_mapping); nfs_start_io_write(inode); result = generic_write_checks(iocb, from); diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c index 522aa10a1a3e..2533053d764a 100644 --- a/fs/nfs/inode.c +++ b/fs/nfs/inode.c @@ -1257,6 +1257,63 @@ static int nfs_invalidate_mapping(struct inode *inode, struct address_space *map return 0; } +/** + * nfs_clear_invalid_mapping - Conditionally clear a mapping + * @mapping: pointer to mapping + * + * If the NFS_INO_INVALID_DATA inode flag is set, clear the mapping. + */ +int nfs_clear_invalid_mapping(struct address_space *mapping) +{ + struct inode *inode = mapping->host; + struct nfs_inode *nfsi = NFS_I(inode); + unsigned long *bitlock = &nfsi->flags; + int ret = 0; + + /* + * We must clear NFS_INO_INVALID_DATA first to ensure that + * invalidations that come in while we're shooting down the mappings + * are respected. But, that leaves a race window where one revalidator + * can clear the flag, and then another checks it before the mapping + * gets invalidated. Fix that by serializing access to this part of + * the function. + * + * At the same time, we need to allow other tasks to see whether we + * might be in the middle of invalidating the pages, so we only set + * the bit lock here if it looks like we're going to be doing that. + */ + for (;;) { + ret = wait_on_bit_action(bitlock, NFS_INO_INVALIDATING, + nfs_wait_bit_killable, TASK_KILLABLE); + if (ret) + goto out; + spin_lock(&inode->i_lock); + if (test_bit(NFS_INO_INVALIDATING, bitlock)) { + spin_unlock(&inode->i_lock); + continue; + } + if (nfsi->cache_validity & NFS_INO_INVALID_DATA) + break; + spin_unlock(&inode->i_lock); + goto out; + } + + set_bit(NFS_INO_INVALIDATING, bitlock); + smp_wmb(); + nfsi->cache_validity &= + ~(NFS_INO_INVALID_DATA | NFS_INO_DATA_INVAL_DEFER); + spin_unlock(&inode->i_lock); + trace_nfs_invalidate_mapping_enter(inode); + ret = nfs_invalidate_mapping(inode, mapping); + trace_nfs_invalidate_mapping_exit(inode, ret); + + clear_bit_unlock(NFS_INO_INVALIDATING, bitlock); + smp_mb__after_atomic(); + wake_up_bit(bitlock, NFS_INO_INVALIDATING); +out: + return ret; +} + bool nfs_mapping_need_revalidate_inode(struct inode *inode) { return nfs_check_cache_invalid(inode, NFS_INO_REVAL_PAGECACHE) || @@ -1289,65 +1346,19 @@ out: * @inode: pointer to host inode * @mapping: pointer to mapping */ -int nfs_revalidate_mapping(struct inode *inode, - struct address_space *mapping) +int nfs_revalidate_mapping(struct inode *inode, struct address_space *mapping) { - struct nfs_inode *nfsi = NFS_I(inode); - unsigned long *bitlock = &nfsi->flags; - int ret = 0; - /* swapfiles are not supposed to be shared. */ if (IS_SWAPFILE(inode)) - goto out; + return 0; if (nfs_mapping_need_revalidate_inode(inode)) { - ret = __nfs_revalidate_inode(NFS_SERVER(inode), inode); + int ret = __nfs_revalidate_inode(NFS_SERVER(inode), inode); if (ret < 0) - goto out; + return ret; } - /* - * We must clear NFS_INO_INVALID_DATA first to ensure that - * invalidations that come in while we're shooting down the mappings - * are respected. But, that leaves a race window where one revalidator - * can clear the flag, and then another checks it before the mapping - * gets invalidated. Fix that by serializing access to this part of - * the function. - * - * At the same time, we need to allow other tasks to see whether we - * might be in the middle of invalidating the pages, so we only set - * the bit lock here if it looks like we're going to be doing that. - */ - for (;;) { - ret = wait_on_bit_action(bitlock, NFS_INO_INVALIDATING, - nfs_wait_bit_killable, TASK_KILLABLE); - if (ret) - goto out; - spin_lock(&inode->i_lock); - if (test_bit(NFS_INO_INVALIDATING, bitlock)) { - spin_unlock(&inode->i_lock); - continue; - } - if (nfsi->cache_validity & NFS_INO_INVALID_DATA) - break; - spin_unlock(&inode->i_lock); - goto out; - } - - set_bit(NFS_INO_INVALIDATING, bitlock); - smp_wmb(); - nfsi->cache_validity &= ~(NFS_INO_INVALID_DATA| - NFS_INO_DATA_INVAL_DEFER); - spin_unlock(&inode->i_lock); - trace_nfs_invalidate_mapping_enter(inode); - ret = nfs_invalidate_mapping(inode, mapping); - trace_nfs_invalidate_mapping_exit(inode, ret); - - clear_bit_unlock(NFS_INO_INVALIDATING, bitlock); - smp_mb__after_atomic(); - wake_up_bit(bitlock, NFS_INO_INVALIDATING); -out: - return ret; + return nfs_clear_invalid_mapping(mapping); } static bool nfs_file_has_writers(struct nfs_inode *nfsi) diff --git a/include/linux/nfs_fs.h b/include/linux/nfs_fs.h index 3cfcf219e96b..2c662857247a 100644 --- a/include/linux/nfs_fs.h +++ b/include/linux/nfs_fs.h @@ -387,6 +387,7 @@ extern int nfs_open(struct inode *, struct file *); extern int nfs_attribute_cache_expired(struct inode *inode); extern int nfs_revalidate_inode(struct nfs_server *server, struct inode *inode); extern int __nfs_revalidate_inode(struct nfs_server *, struct inode *); +extern int nfs_clear_invalid_mapping(struct address_space *mapping); extern bool nfs_mapping_need_revalidate_inode(struct inode *inode); extern int nfs_revalidate_mapping(struct inode *inode, struct address_space *mapping); extern int nfs_revalidate_mapping_rcu(struct inode *inode);