diff options
Diffstat (limited to 'fs/nfs')
| -rw-r--r-- | fs/nfs/Kconfig | 8 | ||||
| -rw-r--r-- | fs/nfs/Makefile | 1 | ||||
| -rw-r--r-- | fs/nfs/callback.c | 31 | ||||
| -rw-r--r-- | fs/nfs/callback.h | 1 | ||||
| -rw-r--r-- | fs/nfs/client.c | 130 | ||||
| -rw-r--r-- | fs/nfs/dir.c | 9 | ||||
| -rw-r--r-- | fs/nfs/file.c | 75 | ||||
| -rw-r--r-- | fs/nfs/fscache-index.c | 337 | ||||
| -rw-r--r-- | fs/nfs/fscache.c | 523 | ||||
| -rw-r--r-- | fs/nfs/fscache.h | 220 | ||||
| -rw-r--r-- | fs/nfs/getroot.c | 4 | ||||
| -rw-r--r-- | fs/nfs/inode.c | 323 | ||||
| -rw-r--r-- | fs/nfs/internal.h | 8 | ||||
| -rw-r--r-- | fs/nfs/iostat.h | 18 | ||||
| -rw-r--r-- | fs/nfs/nfs2xdr.c | 9 | ||||
| -rw-r--r-- | fs/nfs/nfs3proc.c | 7 | ||||
| -rw-r--r-- | fs/nfs/nfs3xdr.c | 37 | ||||
| -rw-r--r-- | fs/nfs/nfs4proc.c | 49 | ||||
| -rw-r--r-- | fs/nfs/nfs4state.c | 10 | ||||
| -rw-r--r-- | fs/nfs/nfs4xdr.c | 213 | ||||
| -rw-r--r-- | fs/nfs/pagelist.c | 11 | ||||
| -rw-r--r-- | fs/nfs/proc.c | 1 | ||||
| -rw-r--r-- | fs/nfs/read.c | 27 | ||||
| -rw-r--r-- | fs/nfs/super.c | 49 | ||||
| -rw-r--r-- | fs/nfs/write.c | 53 | 
25 files changed, 1798 insertions, 356 deletions
diff --git a/fs/nfs/Kconfig b/fs/nfs/Kconfig index 36fe20d6eba..e67f3ec0773 100644 --- a/fs/nfs/Kconfig +++ b/fs/nfs/Kconfig @@ -84,3 +84,11 @@ config ROOT_NFS  	  <file:Documentation/filesystems/nfsroot.txt>.  	  Most people say N here. + +config NFS_FSCACHE +	bool "Provide NFS client caching support (EXPERIMENTAL)" +	depends on EXPERIMENTAL +	depends on NFS_FS=m && FSCACHE || NFS_FS=y && FSCACHE=y +	help +	  Say Y here if you want NFS data to be cached locally on disc through +	  the general filesystem cache manager diff --git a/fs/nfs/Makefile b/fs/nfs/Makefile index ac6170c594a..845159814de 100644 --- a/fs/nfs/Makefile +++ b/fs/nfs/Makefile @@ -15,3 +15,4 @@ nfs-$(CONFIG_NFS_V4)	+= nfs4proc.o nfs4xdr.o nfs4state.o nfs4renewd.o \  			   callback.o callback_xdr.o callback_proc.o \  			   nfs4namespace.o  nfs-$(CONFIG_SYSCTL) += sysctl.o +nfs-$(CONFIG_NFS_FSCACHE) += fscache.o fscache-index.o diff --git a/fs/nfs/callback.c b/fs/nfs/callback.c index 3e634f2a108..a886e692ddd 100644 --- a/fs/nfs/callback.c +++ b/fs/nfs/callback.c @@ -38,19 +38,10 @@ static struct svc_program nfs4_callback_program;  unsigned int nfs_callback_set_tcpport;  unsigned short nfs_callback_tcpport; +unsigned short nfs_callback_tcpport6;  static const int nfs_set_port_min = 0;  static const int nfs_set_port_max = 65535; -/* - * If the kernel has IPv6 support available, always listen for - * both AF_INET and AF_INET6 requests. - */ -#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) -static const sa_family_t	nfs_callback_family = AF_INET6; -#else -static const sa_family_t	nfs_callback_family = AF_INET; -#endif -  static int param_set_port(const char *val, struct kernel_param *kp)  {  	char *endp; @@ -116,19 +107,29 @@ int nfs_callback_up(void)  	mutex_lock(&nfs_callback_mutex);  	if (nfs_callback_info.users++ || nfs_callback_info.task != NULL)  		goto out; -	serv = svc_create(&nfs4_callback_program, NFS4_CALLBACK_BUFSIZE, -				nfs_callback_family, NULL); +	serv = svc_create(&nfs4_callback_program, NFS4_CALLBACK_BUFSIZE, NULL);  	ret = -ENOMEM;  	if (!serv)  		goto out_err; -	ret = svc_create_xprt(serv, "tcp", nfs_callback_set_tcpport, -			      SVC_SOCK_ANONYMOUS); +	ret = svc_create_xprt(serv, "tcp", PF_INET, +				nfs_callback_set_tcpport, SVC_SOCK_ANONYMOUS);  	if (ret <= 0)  		goto out_err;  	nfs_callback_tcpport = ret;  	dprintk("NFS: Callback listener port = %u (af %u)\n", -			nfs_callback_tcpport, nfs_callback_family); +			nfs_callback_tcpport, PF_INET); + +#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) +	ret = svc_create_xprt(serv, "tcp", PF_INET6, +				nfs_callback_set_tcpport, SVC_SOCK_ANONYMOUS); +	if (ret > 0) { +		nfs_callback_tcpport6 = ret; +		dprintk("NFS: Callback listener port = %u (af %u)\n", +				nfs_callback_tcpport6, PF_INET6); +	} else if (ret != -EAFNOSUPPORT) +		goto out_err; +#endif	/* defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) */  	nfs_callback_info.rqst = svc_prepare_thread(serv, &serv->sv_pools[0]);  	if (IS_ERR(nfs_callback_info.rqst)) { diff --git a/fs/nfs/callback.h b/fs/nfs/callback.h index bb25d2135ff..e110e286a26 100644 --- a/fs/nfs/callback.h +++ b/fs/nfs/callback.h @@ -72,5 +72,6 @@ extern void nfs_callback_down(void);  extern unsigned int nfs_callback_set_tcpport;  extern unsigned short nfs_callback_tcpport; +extern unsigned short nfs_callback_tcpport6;  #endif /* __LINUX_FS_NFS_CALLBACK_H */ diff --git a/fs/nfs/client.c b/fs/nfs/client.c index 2277421656e..75c9cd2aa11 100644 --- a/fs/nfs/client.c +++ b/fs/nfs/client.c @@ -45,6 +45,7 @@  #include "delegation.h"  #include "iostat.h"  #include "internal.h" +#include "fscache.h"  #define NFSDBG_FACILITY		NFSDBG_CLIENT @@ -154,6 +155,8 @@ static struct nfs_client *nfs_alloc_client(const struct nfs_client_initdata *cl_  	if (!IS_ERR(cred))  		clp->cl_machine_cred = cred; +	nfs_fscache_get_client_cookie(clp); +  	return clp;  error_3: @@ -187,6 +190,8 @@ static void nfs_free_client(struct nfs_client *clp)  	nfs4_shutdown_client(clp); +	nfs_fscache_release_client_cookie(clp); +  	/* -EIO all pending I/O */  	if (!IS_ERR(clp->cl_rpcclient))  		rpc_shutdown_client(clp->cl_rpcclient); @@ -224,38 +229,6 @@ void nfs_put_client(struct nfs_client *clp)  }  #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) -static const struct in6_addr *nfs_map_ipv4_addr(const struct sockaddr *sa, struct in6_addr *addr_mapped) -{ -	switch (sa->sa_family) { -		default: -			return NULL; -		case AF_INET6: -			return &((const struct sockaddr_in6 *)sa)->sin6_addr; -			break; -		case AF_INET: -			ipv6_addr_set_v4mapped(((const struct sockaddr_in *)sa)->sin_addr.s_addr, -					addr_mapped); -			return addr_mapped; -	} -} - -static int nfs_sockaddr_match_ipaddr(const struct sockaddr *sa1, -		const struct sockaddr *sa2) -{ -	const struct in6_addr *addr1; -	const struct in6_addr *addr2; -	struct in6_addr addr1_mapped; -	struct in6_addr addr2_mapped; - -	addr1 = nfs_map_ipv4_addr(sa1, &addr1_mapped); -	if (likely(addr1 != NULL)) { -		addr2 = nfs_map_ipv4_addr(sa2, &addr2_mapped); -		if (likely(addr2 != NULL)) -			return ipv6_addr_equal(addr1, addr2); -	} -	return 0; -} -  /*   * Test if two ip6 socket addresses refer to the same socket by   * comparing relevant fields. The padding bytes specifically, are not @@ -267,38 +240,21 @@ static int nfs_sockaddr_match_ipaddr(const struct sockaddr *sa1,   *   * The caller should ensure both socket addresses are AF_INET6.   */ -static int nfs_sockaddr_cmp_ip6(const struct sockaddr *sa1, -				const struct sockaddr *sa2) +static int nfs_sockaddr_match_ipaddr6(const struct sockaddr *sa1, +				      const struct sockaddr *sa2)  { -	const struct sockaddr_in6 *saddr1 = (const struct sockaddr_in6 *)sa1; -	const struct sockaddr_in6 *saddr2 = (const struct sockaddr_in6 *)sa2; +	const struct sockaddr_in6 *sin1 = (const struct sockaddr_in6 *)sa1; +	const struct sockaddr_in6 *sin2 = (const struct sockaddr_in6 *)sa2; -	if (!ipv6_addr_equal(&saddr1->sin6_addr, -			     &saddr1->sin6_addr)) +	if (ipv6_addr_scope(&sin1->sin6_addr) == IPV6_ADDR_SCOPE_LINKLOCAL && +	    sin1->sin6_scope_id != sin2->sin6_scope_id)  		return 0; -	if (ipv6_addr_scope(&saddr1->sin6_addr) == IPV6_ADDR_SCOPE_LINKLOCAL && -	    saddr1->sin6_scope_id != saddr2->sin6_scope_id) -		return 0; -	return saddr1->sin6_port == saddr2->sin6_port; -} -#else -static int nfs_sockaddr_match_ipaddr4(const struct sockaddr_in *sa1, -				 const struct sockaddr_in *sa2) -{ -	return sa1->sin_addr.s_addr == sa2->sin_addr.s_addr; -} -static int nfs_sockaddr_match_ipaddr(const struct sockaddr *sa1, -				 const struct sockaddr *sa2) -{ -	if (unlikely(sa1->sa_family != AF_INET || sa2->sa_family != AF_INET)) -		return 0; -	return nfs_sockaddr_match_ipaddr4((const struct sockaddr_in *)sa1, -			(const struct sockaddr_in *)sa2); +	return ipv6_addr_equal(&sin1->sin6_addr, &sin1->sin6_addr);  } - -static int nfs_sockaddr_cmp_ip6(const struct sockaddr * sa1, -				const struct sockaddr * sa2) +#else	/* !defined(CONFIG_IPV6) && !defined(CONFIG_IPV6_MODULE) */ +static int nfs_sockaddr_match_ipaddr6(const struct sockaddr *sa1, +				      const struct sockaddr *sa2)  {  	return 0;  } @@ -311,20 +267,57 @@ static int nfs_sockaddr_cmp_ip6(const struct sockaddr * sa1,   *   * The caller should ensure both socket addresses are AF_INET.   */ +static int nfs_sockaddr_match_ipaddr4(const struct sockaddr *sa1, +				      const struct sockaddr *sa2) +{ +	const struct sockaddr_in *sin1 = (const struct sockaddr_in *)sa1; +	const struct sockaddr_in *sin2 = (const struct sockaddr_in *)sa2; + +	return sin1->sin_addr.s_addr == sin2->sin_addr.s_addr; +} + +static int nfs_sockaddr_cmp_ip6(const struct sockaddr *sa1, +				const struct sockaddr *sa2) +{ +	const struct sockaddr_in6 *sin1 = (const struct sockaddr_in6 *)sa1; +	const struct sockaddr_in6 *sin2 = (const struct sockaddr_in6 *)sa2; + +	return nfs_sockaddr_match_ipaddr6(sa1, sa2) && +		(sin1->sin6_port == sin2->sin6_port); +} +  static int nfs_sockaddr_cmp_ip4(const struct sockaddr *sa1,  				const struct sockaddr *sa2)  { -	const struct sockaddr_in *saddr1 = (const struct sockaddr_in *)sa1; -	const struct sockaddr_in *saddr2 = (const struct sockaddr_in *)sa2; +	const struct sockaddr_in *sin1 = (const struct sockaddr_in *)sa1; +	const struct sockaddr_in *sin2 = (const struct sockaddr_in *)sa2; + +	return nfs_sockaddr_match_ipaddr4(sa1, sa2) && +		(sin1->sin_port == sin2->sin_port); +} -	if (saddr1->sin_addr.s_addr != saddr2->sin_addr.s_addr) +/* + * Test if two socket addresses represent the same actual socket, + * by comparing (only) relevant fields, excluding the port number. + */ +static int nfs_sockaddr_match_ipaddr(const struct sockaddr *sa1, +				     const struct sockaddr *sa2) +{ +	if (sa1->sa_family != sa2->sa_family)  		return 0; -	return saddr1->sin_port == saddr2->sin_port; + +	switch (sa1->sa_family) { +	case AF_INET: +		return nfs_sockaddr_match_ipaddr4(sa1, sa2); +	case AF_INET6: +		return nfs_sockaddr_match_ipaddr6(sa1, sa2); +	} +	return 0;  }  /*   * Test if two socket addresses represent the same actual socket, - * by comparing (only) relevant fields. + * by comparing (only) relevant fields, including the port number.   */  static int nfs_sockaddr_cmp(const struct sockaddr *sa1,  			    const struct sockaddr *sa2) @@ -772,6 +765,7 @@ static int nfs_init_server(struct nfs_server *server,  	/* Initialise the client representation from the mount data */  	server->flags = data->flags; +	server->options = data->options;  	if (data->rsize)  		server->rsize = nfs_block_size(data->rsize, NULL); @@ -1160,6 +1154,7 @@ static int nfs4_init_server(struct nfs_server *server,  	/* Initialise the client representation from the mount data */  	server->flags = data->flags;  	server->caps |= NFS_CAP_ATOMIC_OPEN; +	server->options = data->options;  	/* Get a client record */  	error = nfs4_set_client(server, @@ -1571,7 +1566,7 @@ static int nfs_volume_list_show(struct seq_file *m, void *v)  	/* display header on line 1 */  	if (v == &nfs_volume_list) { -		seq_puts(m, "NV SERVER   PORT DEV     FSID\n"); +		seq_puts(m, "NV SERVER   PORT DEV     FSID              FSC\n");  		return 0;  	}  	/* display one transport per line on subsequent lines */ @@ -1585,12 +1580,13 @@ static int nfs_volume_list_show(struct seq_file *m, void *v)  		 (unsigned long long) server->fsid.major,  		 (unsigned long long) server->fsid.minor); -	seq_printf(m, "v%u %s %s %-7s %-17s\n", +	seq_printf(m, "v%u %s %s %-7s %-17s %s\n",  		   clp->rpc_ops->version,  		   rpc_peeraddr2str(clp->cl_rpcclient, RPC_DISPLAY_HEX_ADDR),  		   rpc_peeraddr2str(clp->cl_rpcclient, RPC_DISPLAY_HEX_PORT),  		   dev, -		   fsid); +		   fsid, +		   nfs_server_fscache_state(server));  	return 0;  } diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c index 78bf72fc1db..370b190a09d 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c @@ -1624,8 +1624,7 @@ static int nfs_rename(struct inode *old_dir, struct dentry *old_dentry,  		} else if (atomic_read(&new_dentry->d_count) > 1)  			/* dentry still busy? */  			goto out; -	} else -		nfs_drop_nlink(new_inode); +	}  go_ahead:  	/* @@ -1638,10 +1637,8 @@ go_ahead:  	}  	nfs_inode_return_delegation(old_inode); -	if (new_inode != NULL) { +	if (new_inode != NULL)  		nfs_inode_return_delegation(new_inode); -		d_delete(new_dentry); -	}  	error = NFS_PROTO(old_dir)->rename(old_dir, &old_dentry->d_name,  					   new_dir, &new_dentry->d_name); @@ -1650,6 +1647,8 @@ out:  	if (rehash)  		d_rehash(rehash);  	if (!error) { +		if (new_inode != NULL) +			nfs_drop_nlink(new_inode);  		d_move(old_dentry, new_dentry);  		nfs_set_verifier(new_dentry,  					nfs_save_change_attribute(new_dir)); diff --git a/fs/nfs/file.c b/fs/nfs/file.c index 90f292b520d..3523b895eb4 100644 --- a/fs/nfs/file.c +++ b/fs/nfs/file.c @@ -35,6 +35,7 @@  #include "delegation.h"  #include "internal.h"  #include "iostat.h" +#include "fscache.h"  #define NFSDBG_FACILITY		NFSDBG_FILE @@ -64,11 +65,7 @@ const struct file_operations nfs_file_operations = {  	.write		= do_sync_write,  	.aio_read	= nfs_file_read,  	.aio_write	= nfs_file_write, -#ifdef CONFIG_MMU  	.mmap		= nfs_file_mmap, -#else -	.mmap		= generic_file_mmap, -#endif  	.open		= nfs_file_open,  	.flush		= nfs_file_flush,  	.release	= nfs_file_release, @@ -141,9 +138,6 @@ nfs_file_release(struct inode *inode, struct file *filp)  			dentry->d_parent->d_name.name,  			dentry->d_name.name); -	/* Ensure that dirty pages are flushed out with the right creds */ -	if (filp->f_mode & FMODE_WRITE) -		nfs_wb_all(dentry->d_inode);  	nfs_inc_stats(inode, NFSIOS_VFSRELEASE);  	return nfs_release(inode, filp);  } @@ -235,7 +229,6 @@ nfs_file_flush(struct file *file, fl_owner_t id)  	struct nfs_open_context *ctx = nfs_file_open_context(file);  	struct dentry	*dentry = file->f_path.dentry;  	struct inode	*inode = dentry->d_inode; -	int		status;  	dprintk("NFS: flush(%s/%s)\n",  			dentry->d_parent->d_name.name, @@ -245,11 +238,8 @@ nfs_file_flush(struct file *file, fl_owner_t id)  		return 0;  	nfs_inc_stats(inode, NFSIOS_VFSFLUSH); -	/* Ensure that data+attribute caches are up to date after close() */ -	status = nfs_do_fsync(ctx, inode); -	if (!status) -		nfs_revalidate_inode(NFS_SERVER(inode), inode); -	return status; +	/* Flush writes to the server and return any errors */ +	return nfs_do_fsync(ctx, inode);  }  static ssize_t @@ -304,11 +294,13 @@ nfs_file_mmap(struct file * file, struct vm_area_struct * vma)  	dprintk("NFS: mmap(%s/%s)\n",  		dentry->d_parent->d_name.name, dentry->d_name.name); -	status = nfs_revalidate_mapping(inode, file->f_mapping); +	/* Note: generic_file_mmap() returns ENOSYS on nommu systems +	 *       so we call that before revalidating the mapping +	 */ +	status = generic_file_mmap(file, vma);  	if (!status) {  		vma->vm_ops = &nfs_file_vm_ops; -		vma->vm_flags |= VM_CAN_NONLINEAR; -		file_accessed(file); +		status = nfs_revalidate_mapping(inode, file->f_mapping);  	}  	return status;  } @@ -354,6 +346,15 @@ static int nfs_write_begin(struct file *file, struct address_space *mapping,  		file->f_path.dentry->d_name.name,  		mapping->host->i_ino, len, (long long) pos); +	/* +	 * Prevent starvation issues if someone is doing a consistency +	 * sync-to-disk +	 */ +	ret = wait_on_bit(&NFS_I(mapping->host)->flags, NFS_INO_FLUSHING, +			nfs_wait_bit_killable, TASK_KILLABLE); +	if (ret) +		return ret; +  	page = grab_cache_page_write_begin(mapping, index, flags);  	if (!page)  		return -ENOMEM; @@ -409,6 +410,13 @@ static int nfs_write_end(struct file *file, struct address_space *mapping,  	return copied;  } +/* + * Partially or wholly invalidate a page + * - Release the private state associated with a page if undergoing complete + *   page invalidation + * - Called if either PG_private or PG_fscache is set on the page + * - Caller holds page lock + */  static void nfs_invalidate_page(struct page *page, unsigned long offset)  {  	dfprintk(PAGECACHE, "NFS: invalidate_page(%p, %lu)\n", page, offset); @@ -417,23 +425,43 @@ static void nfs_invalidate_page(struct page *page, unsigned long offset)  		return;  	/* Cancel any unstarted writes on this page */  	nfs_wb_page_cancel(page->mapping->host, page); + +	nfs_fscache_invalidate_page(page, page->mapping->host);  } +/* + * Attempt to release the private state associated with a page + * - Called if either PG_private or PG_fscache is set on the page + * - Caller holds page lock + * - Return true (may release page) or false (may not) + */  static int nfs_release_page(struct page *page, gfp_t gfp)  {  	dfprintk(PAGECACHE, "NFS: release_page(%p)\n", page);  	/* If PagePrivate() is set, then the page is not freeable */ -	return 0; +	if (PagePrivate(page)) +		return 0; +	return nfs_fscache_release_page(page, gfp);  } +/* + * Attempt to clear the private state associated with a page when an error + * occurs that requires the cached contents of an inode to be written back or + * destroyed + * - Called if either PG_private or fscache is set on the page + * - Caller holds page lock + * - Return 0 if successful, -error otherwise + */  static int nfs_launder_page(struct page *page)  {  	struct inode *inode = page->mapping->host; +	struct nfs_inode *nfsi = NFS_I(inode);  	dfprintk(PAGECACHE, "NFS: launder_page(%ld, %llu)\n",  		inode->i_ino, (long long)page_offset(page)); +	nfs_fscache_wait_on_page_write(nfsi, page);  	return nfs_wb_page(inode, page);  } @@ -451,8 +479,14 @@ const struct address_space_operations nfs_file_aops = {  	.launder_page = nfs_launder_page,  }; -static int nfs_vm_page_mkwrite(struct vm_area_struct *vma, struct page *page) +/* + * Notification that a PTE pointing to an NFS page is about to be made + * writable, implying that someone is about to modify the page through a + * shared-writable mapping + */ +static int nfs_vm_page_mkwrite(struct vm_area_struct *vma, struct vm_fault *vmf)  { +	struct page *page = vmf->page;  	struct file *filp = vma->vm_file;  	struct dentry *dentry = filp->f_path.dentry;  	unsigned pagelen; @@ -464,6 +498,9 @@ static int nfs_vm_page_mkwrite(struct vm_area_struct *vma, struct page *page)  		filp->f_mapping->host->i_ino,  		(long long)page_offset(page)); +	/* make sure the cache has finished storing the page */ +	nfs_fscache_wait_on_page_write(NFS_I(dentry->d_inode), page); +  	lock_page(page);  	mapping = page->mapping;  	if (mapping != dentry->d_inode->i_mapping) @@ -483,6 +520,8 @@ static int nfs_vm_page_mkwrite(struct vm_area_struct *vma, struct page *page)  		ret = pagelen;  out_unlock:  	unlock_page(page); +	if (ret) +		ret = VM_FAULT_SIGBUS;  	return ret;  } diff --git a/fs/nfs/fscache-index.c b/fs/nfs/fscache-index.c new file mode 100644 index 00000000000..5b1006480bc --- /dev/null +++ b/fs/nfs/fscache-index.c @@ -0,0 +1,337 @@ +/* NFS FS-Cache index structure definition + * + * Copyright (C) 2008 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/nfs_fs.h> +#include <linux/nfs_fs_sb.h> +#include <linux/in6.h> + +#include "internal.h" +#include "fscache.h" + +#define NFSDBG_FACILITY		NFSDBG_FSCACHE + +/* + * Define the NFS filesystem for FS-Cache.  Upon registration FS-Cache sticks + * the cookie for the top-level index object for NFS into here.  The top-level + * index can than have other cache objects inserted into it. + */ +struct fscache_netfs nfs_fscache_netfs = { +	.name		= "nfs", +	.version	= 0, +}; + +/* + * Register NFS for caching + */ +int nfs_fscache_register(void) +{ +	return fscache_register_netfs(&nfs_fscache_netfs); +} + +/* + * Unregister NFS for caching + */ +void nfs_fscache_unregister(void) +{ +	fscache_unregister_netfs(&nfs_fscache_netfs); +} + +/* + * Layout of the key for an NFS server cache object. + */ +struct nfs_server_key { +	uint16_t	nfsversion;		/* NFS protocol version */ +	uint16_t	family;			/* address family */ +	uint16_t	port;			/* IP port */ +	union { +		struct in_addr	ipv4_addr;	/* IPv4 address */ +		struct in6_addr ipv6_addr;	/* IPv6 address */ +	} addr[0]; +}; + +/* + * Generate a key to describe a server in the main NFS index + * - We return the length of the key, or 0 if we can't generate one + */ +static uint16_t nfs_server_get_key(const void *cookie_netfs_data, +				   void *buffer, uint16_t bufmax) +{ +	const struct nfs_client *clp = cookie_netfs_data; +	const struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) &clp->cl_addr; +	const struct sockaddr_in *sin = (struct sockaddr_in *) &clp->cl_addr; +	struct nfs_server_key *key = buffer; +	uint16_t len = sizeof(struct nfs_server_key); + +	key->nfsversion = clp->rpc_ops->version; +	key->family = clp->cl_addr.ss_family; + +	memset(key, 0, len); + +	switch (clp->cl_addr.ss_family) { +	case AF_INET: +		key->port = sin->sin_port; +		key->addr[0].ipv4_addr = sin->sin_addr; +		len += sizeof(key->addr[0].ipv4_addr); +		break; + +	case AF_INET6: +		key->port = sin6->sin6_port; +		key->addr[0].ipv6_addr = sin6->sin6_addr; +		len += sizeof(key->addr[0].ipv6_addr); +		break; + +	default: +		printk(KERN_WARNING "NFS: Unknown network family '%d'\n", +		       clp->cl_addr.ss_family); +		len = 0; +		break; +	} + +	return len; +} + +/* + * Define the server object for FS-Cache.  This is used to describe a server + * object to fscache_acquire_cookie().  It is keyed by the NFS protocol and + * server address parameters. + */ +const struct fscache_cookie_def nfs_fscache_server_index_def = { +	.name		= "NFS.server", +	.type 		= FSCACHE_COOKIE_TYPE_INDEX, +	.get_key	= nfs_server_get_key, +}; + +/* + * Generate a key to describe a superblock key in the main NFS index + */ +static uint16_t nfs_super_get_key(const void *cookie_netfs_data, +				  void *buffer, uint16_t bufmax) +{ +	const struct nfs_fscache_key *key; +	const struct nfs_server *nfss = cookie_netfs_data; +	uint16_t len; + +	key = nfss->fscache_key; +	len = sizeof(key->key) + key->key.uniq_len; +	if (len > bufmax) { +		len = 0; +	} else { +		memcpy(buffer, &key->key, sizeof(key->key)); +		memcpy(buffer + sizeof(key->key), +		       key->key.uniquifier, key->key.uniq_len); +	} + +	return len; +} + +/* + * Define the superblock object for FS-Cache.  This is used to describe a + * superblock object to fscache_acquire_cookie().  It is keyed by all the NFS + * parameters that might cause a separate superblock. + */ +const struct fscache_cookie_def nfs_fscache_super_index_def = { +	.name		= "NFS.super", +	.type 		= FSCACHE_COOKIE_TYPE_INDEX, +	.get_key	= nfs_super_get_key, +}; + +/* + * Definition of the auxiliary data attached to NFS inode storage objects + * within the cache. + * + * The contents of this struct are recorded in the on-disk local cache in the + * auxiliary data attached to the data storage object backing an inode.  This + * permits coherency to be managed when a new inode binds to an already extant + * cache object. + */ +struct nfs_fscache_inode_auxdata { +	struct timespec	mtime; +	struct timespec	ctime; +	loff_t		size; +	u64		change_attr; +}; + +/* + * Generate a key to describe an NFS inode in an NFS server's index + */ +static uint16_t nfs_fscache_inode_get_key(const void *cookie_netfs_data, +					  void *buffer, uint16_t bufmax) +{ +	const struct nfs_inode *nfsi = cookie_netfs_data; +	uint16_t nsize; + +	/* use the inode's NFS filehandle as the key */ +	nsize = nfsi->fh.size; +	memcpy(buffer, nfsi->fh.data, nsize); +	return nsize; +} + +/* + * Get certain file attributes from the netfs data + * - This function can be absent for an index + * - Not permitted to return an error + * - The netfs data from the cookie being used as the source is presented + */ +static void nfs_fscache_inode_get_attr(const void *cookie_netfs_data, +				       uint64_t *size) +{ +	const struct nfs_inode *nfsi = cookie_netfs_data; + +	*size = nfsi->vfs_inode.i_size; +} + +/* + * Get the auxiliary data from netfs data + * - This function can be absent if the index carries no state data + * - Should store the auxiliary data in the buffer + * - Should return the amount of amount stored + * - Not permitted to return an error + * - The netfs data from the cookie being used as the source is presented + */ +static uint16_t nfs_fscache_inode_get_aux(const void *cookie_netfs_data, +					  void *buffer, uint16_t bufmax) +{ +	struct nfs_fscache_inode_auxdata auxdata; +	const struct nfs_inode *nfsi = cookie_netfs_data; + +	memset(&auxdata, 0, sizeof(auxdata)); +	auxdata.size = nfsi->vfs_inode.i_size; +	auxdata.mtime = nfsi->vfs_inode.i_mtime; +	auxdata.ctime = nfsi->vfs_inode.i_ctime; + +	if (NFS_SERVER(&nfsi->vfs_inode)->nfs_client->rpc_ops->version == 4) +		auxdata.change_attr = nfsi->change_attr; + +	if (bufmax > sizeof(auxdata)) +		bufmax = sizeof(auxdata); + +	memcpy(buffer, &auxdata, bufmax); +	return bufmax; +} + +/* + * Consult the netfs about the state of an object + * - This function can be absent if the index carries no state data + * - The netfs data from the cookie being used as the target is + *   presented, as is the auxiliary data + */ +static +enum fscache_checkaux nfs_fscache_inode_check_aux(void *cookie_netfs_data, +						  const void *data, +						  uint16_t datalen) +{ +	struct nfs_fscache_inode_auxdata auxdata; +	struct nfs_inode *nfsi = cookie_netfs_data; + +	if (datalen != sizeof(auxdata)) +		return FSCACHE_CHECKAUX_OBSOLETE; + +	memset(&auxdata, 0, sizeof(auxdata)); +	auxdata.size = nfsi->vfs_inode.i_size; +	auxdata.mtime = nfsi->vfs_inode.i_mtime; +	auxdata.ctime = nfsi->vfs_inode.i_ctime; + +	if (NFS_SERVER(&nfsi->vfs_inode)->nfs_client->rpc_ops->version == 4) +		auxdata.change_attr = nfsi->change_attr; + +	if (memcmp(data, &auxdata, datalen) != 0) +		return FSCACHE_CHECKAUX_OBSOLETE; + +	return FSCACHE_CHECKAUX_OKAY; +} + +/* + * Indication from FS-Cache that the cookie is no longer cached + * - This function is called when the backing store currently caching a cookie + *   is removed + * - The netfs should use this to clean up any markers indicating cached pages + * - This is mandatory for any object that may have data + */ +static void nfs_fscache_inode_now_uncached(void *cookie_netfs_data) +{ +	struct nfs_inode *nfsi = cookie_netfs_data; +	struct pagevec pvec; +	pgoff_t first; +	int loop, nr_pages; + +	pagevec_init(&pvec, 0); +	first = 0; + +	dprintk("NFS: nfs_inode_now_uncached: nfs_inode 0x%p\n", nfsi); + +	for (;;) { +		/* grab a bunch of pages to unmark */ +		nr_pages = pagevec_lookup(&pvec, +					  nfsi->vfs_inode.i_mapping, +					  first, +					  PAGEVEC_SIZE - pagevec_count(&pvec)); +		if (!nr_pages) +			break; + +		for (loop = 0; loop < nr_pages; loop++) +			ClearPageFsCache(pvec.pages[loop]); + +		first = pvec.pages[nr_pages - 1]->index + 1; + +		pvec.nr = nr_pages; +		pagevec_release(&pvec); +		cond_resched(); +	} +} + +/* + * Get an extra reference on a read context. + * - This function can be absent if the completion function doesn't require a + *   context. + * - The read context is passed back to NFS in the event that a data read on the + *   cache fails with EIO - in which case the server must be contacted to + *   retrieve the data, which requires the read context for security. + */ +static void nfs_fh_get_context(void *cookie_netfs_data, void *context) +{ +	get_nfs_open_context(context); +} + +/* + * Release an extra reference on a read context. + * - This function can be absent if the completion function doesn't require a + *   context. + */ +static void nfs_fh_put_context(void *cookie_netfs_data, void *context) +{ +	if (context) +		put_nfs_open_context(context); +} + +/* + * Define the inode object for FS-Cache.  This is used to describe an inode + * object to fscache_acquire_cookie().  It is keyed by the NFS file handle for + * an inode. + * + * Coherency is managed by comparing the copies of i_size, i_mtime and i_ctime + * held in the cache auxiliary data for the data storage object with those in + * the inode struct in memory. + */ +const struct fscache_cookie_def nfs_fscache_inode_object_def = { +	.name		= "NFS.fh", +	.type		= FSCACHE_COOKIE_TYPE_DATAFILE, +	.get_key	= nfs_fscache_inode_get_key, +	.get_attr	= nfs_fscache_inode_get_attr, +	.get_aux	= nfs_fscache_inode_get_aux, +	.check_aux	= nfs_fscache_inode_check_aux, +	.now_uncached	= nfs_fscache_inode_now_uncached, +	.get_context	= nfs_fh_get_context, +	.put_context	= nfs_fh_put_context, +}; diff --git a/fs/nfs/fscache.c b/fs/nfs/fscache.c new file mode 100644 index 00000000000..379be678cb7 --- /dev/null +++ b/fs/nfs/fscache.c @@ -0,0 +1,523 @@ +/* NFS filesystem cache interface + * + * Copyright (C) 2008 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/mm.h> +#include <linux/nfs_fs.h> +#include <linux/nfs_fs_sb.h> +#include <linux/in6.h> +#include <linux/seq_file.h> + +#include "internal.h" +#include "iostat.h" +#include "fscache.h" + +#define NFSDBG_FACILITY		NFSDBG_FSCACHE + +static struct rb_root nfs_fscache_keys = RB_ROOT; +static DEFINE_SPINLOCK(nfs_fscache_keys_lock); + +/* + * Get the per-client index cookie for an NFS client if the appropriate mount + * flag was set + * - We always try and get an index cookie for the client, but get filehandle + *   cookies on a per-superblock basis, depending on the mount flags + */ +void nfs_fscache_get_client_cookie(struct nfs_client *clp) +{ +	/* create a cache index for looking up filehandles */ +	clp->fscache = fscache_acquire_cookie(nfs_fscache_netfs.primary_index, +					      &nfs_fscache_server_index_def, +					      clp); +	dfprintk(FSCACHE, "NFS: get client cookie (0x%p/0x%p)\n", +		 clp, clp->fscache); +} + +/* + * Dispose of a per-client cookie + */ +void nfs_fscache_release_client_cookie(struct nfs_client *clp) +{ +	dfprintk(FSCACHE, "NFS: releasing client cookie (0x%p/0x%p)\n", +		 clp, clp->fscache); + +	fscache_relinquish_cookie(clp->fscache, 0); +	clp->fscache = NULL; +} + +/* + * Get the cache cookie for an NFS superblock.  We have to handle + * uniquification here because the cache doesn't do it for us. + */ +void nfs_fscache_get_super_cookie(struct super_block *sb, +				  struct nfs_parsed_mount_data *data) +{ +	struct nfs_fscache_key *key, *xkey; +	struct nfs_server *nfss = NFS_SB(sb); +	struct rb_node **p, *parent; +	const char *uniq = data->fscache_uniq ?: ""; +	int diff, ulen; + +	ulen = strlen(uniq); +	key = kzalloc(sizeof(*key) + ulen, GFP_KERNEL); +	if (!key) +		return; + +	key->nfs_client = nfss->nfs_client; +	key->key.super.s_flags = sb->s_flags & NFS_MS_MASK; +	key->key.nfs_server.flags = nfss->flags; +	key->key.nfs_server.rsize = nfss->rsize; +	key->key.nfs_server.wsize = nfss->wsize; +	key->key.nfs_server.acregmin = nfss->acregmin; +	key->key.nfs_server.acregmax = nfss->acregmax; +	key->key.nfs_server.acdirmin = nfss->acdirmin; +	key->key.nfs_server.acdirmax = nfss->acdirmax; +	key->key.nfs_server.fsid = nfss->fsid; +	key->key.rpc_auth.au_flavor = nfss->client->cl_auth->au_flavor; + +	key->key.uniq_len = ulen; +	memcpy(key->key.uniquifier, uniq, ulen); + +	spin_lock(&nfs_fscache_keys_lock); +	p = &nfs_fscache_keys.rb_node; +	parent = NULL; +	while (*p) { +		parent = *p; +		xkey = rb_entry(parent, struct nfs_fscache_key, node); + +		if (key->nfs_client < xkey->nfs_client) +			goto go_left; +		if (key->nfs_client > xkey->nfs_client) +			goto go_right; + +		diff = memcmp(&key->key, &xkey->key, sizeof(key->key)); +		if (diff < 0) +			goto go_left; +		if (diff > 0) +			goto go_right; + +		if (key->key.uniq_len == 0) +			goto non_unique; +		diff = memcmp(key->key.uniquifier, +			      xkey->key.uniquifier, +			      key->key.uniq_len); +		if (diff < 0) +			goto go_left; +		if (diff > 0) +			goto go_right; +		goto non_unique; + +	go_left: +		p = &(*p)->rb_left; +		continue; +	go_right: +		p = &(*p)->rb_right; +	} + +	rb_link_node(&key->node, parent, p); +	rb_insert_color(&key->node, &nfs_fscache_keys); +	spin_unlock(&nfs_fscache_keys_lock); +	nfss->fscache_key = key; + +	/* create a cache index for looking up filehandles */ +	nfss->fscache = fscache_acquire_cookie(nfss->nfs_client->fscache, +					       &nfs_fscache_super_index_def, +					       nfss); +	dfprintk(FSCACHE, "NFS: get superblock cookie (0x%p/0x%p)\n", +		 nfss, nfss->fscache); +	return; + +non_unique: +	spin_unlock(&nfs_fscache_keys_lock); +	kfree(key); +	nfss->fscache_key = NULL; +	nfss->fscache = NULL; +	printk(KERN_WARNING "NFS:" +	       " Cache request denied due to non-unique superblock keys\n"); +} + +/* + * release a per-superblock cookie + */ +void nfs_fscache_release_super_cookie(struct super_block *sb) +{ +	struct nfs_server *nfss = NFS_SB(sb); + +	dfprintk(FSCACHE, "NFS: releasing superblock cookie (0x%p/0x%p)\n", +		 nfss, nfss->fscache); + +	fscache_relinquish_cookie(nfss->fscache, 0); +	nfss->fscache = NULL; + +	if (nfss->fscache_key) { +		spin_lock(&nfs_fscache_keys_lock); +		rb_erase(&nfss->fscache_key->node, &nfs_fscache_keys); +		spin_unlock(&nfs_fscache_keys_lock); +		kfree(nfss->fscache_key); +		nfss->fscache_key = NULL; +	} +} + +/* + * Initialise the per-inode cache cookie pointer for an NFS inode. + */ +void nfs_fscache_init_inode_cookie(struct inode *inode) +{ +	NFS_I(inode)->fscache = NULL; +	if (S_ISREG(inode->i_mode)) +		set_bit(NFS_INO_FSCACHE, &NFS_I(inode)->flags); +} + +/* + * Get the per-inode cache cookie for an NFS inode. + */ +static void nfs_fscache_enable_inode_cookie(struct inode *inode) +{ +	struct super_block *sb = inode->i_sb; +	struct nfs_inode *nfsi = NFS_I(inode); + +	if (nfsi->fscache || !NFS_FSCACHE(inode)) +		return; + +	if ((NFS_SB(sb)->options & NFS_OPTION_FSCACHE)) { +		nfsi->fscache = fscache_acquire_cookie( +			NFS_SB(sb)->fscache, +			&nfs_fscache_inode_object_def, +			nfsi); + +		dfprintk(FSCACHE, "NFS: get FH cookie (0x%p/0x%p/0x%p)\n", +			 sb, nfsi, nfsi->fscache); +	} +} + +/* + * Release a per-inode cookie. + */ +void nfs_fscache_release_inode_cookie(struct inode *inode) +{ +	struct nfs_inode *nfsi = NFS_I(inode); + +	dfprintk(FSCACHE, "NFS: clear cookie (0x%p/0x%p)\n", +		 nfsi, nfsi->fscache); + +	fscache_relinquish_cookie(nfsi->fscache, 0); +	nfsi->fscache = NULL; +} + +/* + * Retire a per-inode cookie, destroying the data attached to it. + */ +void nfs_fscache_zap_inode_cookie(struct inode *inode) +{ +	struct nfs_inode *nfsi = NFS_I(inode); + +	dfprintk(FSCACHE, "NFS: zapping cookie (0x%p/0x%p)\n", +		 nfsi, nfsi->fscache); + +	fscache_relinquish_cookie(nfsi->fscache, 1); +	nfsi->fscache = NULL; +} + +/* + * Turn off the cache with regard to a per-inode cookie if opened for writing, + * invalidating all the pages in the page cache relating to the associated + * inode to clear the per-page caching. + */ +static void nfs_fscache_disable_inode_cookie(struct inode *inode) +{ +	clear_bit(NFS_INO_FSCACHE, &NFS_I(inode)->flags); + +	if (NFS_I(inode)->fscache) { +		dfprintk(FSCACHE, +			 "NFS: nfsi 0x%p turning cache off\n", NFS_I(inode)); + +		/* Need to invalidate any mapped pages that were read in before +		 * turning off the cache. +		 */ +		if (inode->i_mapping && inode->i_mapping->nrpages) +			invalidate_inode_pages2(inode->i_mapping); + +		nfs_fscache_zap_inode_cookie(inode); +	} +} + +/* + * wait_on_bit() sleep function for uninterruptible waiting + */ +static int nfs_fscache_wait_bit(void *flags) +{ +	schedule(); +	return 0; +} + +/* + * Lock against someone else trying to also acquire or relinquish a cookie + */ +static inline void nfs_fscache_inode_lock(struct inode *inode) +{ +	struct nfs_inode *nfsi = NFS_I(inode); + +	while (test_and_set_bit(NFS_INO_FSCACHE_LOCK, &nfsi->flags)) +		wait_on_bit(&nfsi->flags, NFS_INO_FSCACHE_LOCK, +			    nfs_fscache_wait_bit, TASK_UNINTERRUPTIBLE); +} + +/* + * Unlock cookie management lock + */ +static inline void nfs_fscache_inode_unlock(struct inode *inode) +{ +	struct nfs_inode *nfsi = NFS_I(inode); + +	smp_mb__before_clear_bit(); +	clear_bit(NFS_INO_FSCACHE_LOCK, &nfsi->flags); +	smp_mb__after_clear_bit(); +	wake_up_bit(&nfsi->flags, NFS_INO_FSCACHE_LOCK); +} + +/* + * Decide if we should enable or disable local caching for this inode. + * - For now, with NFS, only regular files that are open read-only will be able + *   to use the cache. + * - May be invoked multiple times in parallel by parallel nfs_open() functions. + */ +void nfs_fscache_set_inode_cookie(struct inode *inode, struct file *filp) +{ +	if (NFS_FSCACHE(inode)) { +		nfs_fscache_inode_lock(inode); +		if ((filp->f_flags & O_ACCMODE) != O_RDONLY) +			nfs_fscache_disable_inode_cookie(inode); +		else +			nfs_fscache_enable_inode_cookie(inode); +		nfs_fscache_inode_unlock(inode); +	} +} + +/* + * Replace a per-inode cookie due to revalidation detecting a file having + * changed on the server. + */ +void nfs_fscache_reset_inode_cookie(struct inode *inode) +{ +	struct nfs_inode *nfsi = NFS_I(inode); +	struct nfs_server *nfss = NFS_SERVER(inode); +	struct fscache_cookie *old = nfsi->fscache; + +	nfs_fscache_inode_lock(inode); +	if (nfsi->fscache) { +		/* retire the current fscache cache and get a new one */ +		fscache_relinquish_cookie(nfsi->fscache, 1); + +		nfsi->fscache = fscache_acquire_cookie( +			nfss->nfs_client->fscache, +			&nfs_fscache_inode_object_def, +			nfsi); + +		dfprintk(FSCACHE, +			 "NFS: revalidation new cookie (0x%p/0x%p/0x%p/0x%p)\n", +			 nfss, nfsi, old, nfsi->fscache); +	} +	nfs_fscache_inode_unlock(inode); +} + +/* + * Release the caching state associated with a page, if the page isn't busy + * interacting with the cache. + * - Returns true (can release page) or false (page busy). + */ +int nfs_fscache_release_page(struct page *page, gfp_t gfp) +{ +	struct nfs_inode *nfsi = NFS_I(page->mapping->host); +	struct fscache_cookie *cookie = nfsi->fscache; + +	BUG_ON(!cookie); + +	if (fscache_check_page_write(cookie, page)) { +		if (!(gfp & __GFP_WAIT)) +			return 0; +		fscache_wait_on_page_write(cookie, page); +	} + +	if (PageFsCache(page)) { +		dfprintk(FSCACHE, "NFS: fscache releasepage (0x%p/0x%p/0x%p)\n", +			 cookie, page, nfsi); + +		fscache_uncache_page(cookie, page); +		nfs_add_fscache_stats(page->mapping->host, +				      NFSIOS_FSCACHE_PAGES_UNCACHED, 1); +	} + +	return 1; +} + +/* + * Release the caching state associated with a page if undergoing complete page + * invalidation. + */ +void __nfs_fscache_invalidate_page(struct page *page, struct inode *inode) +{ +	struct nfs_inode *nfsi = NFS_I(inode); +	struct fscache_cookie *cookie = nfsi->fscache; + +	BUG_ON(!cookie); + +	dfprintk(FSCACHE, "NFS: fscache invalidatepage (0x%p/0x%p/0x%p)\n", +		 cookie, page, nfsi); + +	fscache_wait_on_page_write(cookie, page); + +	BUG_ON(!PageLocked(page)); +	fscache_uncache_page(cookie, page); +	nfs_add_fscache_stats(page->mapping->host, +			      NFSIOS_FSCACHE_PAGES_UNCACHED, 1); +} + +/* + * Handle completion of a page being read from the cache. + * - Called in process (keventd) context. + */ +static void nfs_readpage_from_fscache_complete(struct page *page, +					       void *context, +					       int error) +{ +	dfprintk(FSCACHE, +		 "NFS: readpage_from_fscache_complete (0x%p/0x%p/%d)\n", +		 page, context, error); + +	/* if the read completes with an error, we just unlock the page and let +	 * the VM reissue the readpage */ +	if (!error) { +		SetPageUptodate(page); +		unlock_page(page); +	} else { +		error = nfs_readpage_async(context, page->mapping->host, page); +		if (error) +			unlock_page(page); +	} +} + +/* + * Retrieve a page from fscache + */ +int __nfs_readpage_from_fscache(struct nfs_open_context *ctx, +				struct inode *inode, struct page *page) +{ +	int ret; + +	dfprintk(FSCACHE, +		 "NFS: readpage_from_fscache(fsc:%p/p:%p(i:%lx f:%lx)/0x%p)\n", +		 NFS_I(inode)->fscache, page, page->index, page->flags, inode); + +	ret = fscache_read_or_alloc_page(NFS_I(inode)->fscache, +					 page, +					 nfs_readpage_from_fscache_complete, +					 ctx, +					 GFP_KERNEL); + +	switch (ret) { +	case 0: /* read BIO submitted (page in fscache) */ +		dfprintk(FSCACHE, +			 "NFS:    readpage_from_fscache: BIO submitted\n"); +		nfs_add_fscache_stats(inode, NFSIOS_FSCACHE_PAGES_READ_OK, 1); +		return ret; + +	case -ENOBUFS: /* inode not in cache */ +	case -ENODATA: /* page not in cache */ +		nfs_add_fscache_stats(inode, NFSIOS_FSCACHE_PAGES_READ_FAIL, 1); +		dfprintk(FSCACHE, +			 "NFS:    readpage_from_fscache %d\n", ret); +		return 1; + +	default: +		dfprintk(FSCACHE, "NFS:    readpage_from_fscache %d\n", ret); +		nfs_add_fscache_stats(inode, NFSIOS_FSCACHE_PAGES_READ_FAIL, 1); +	} +	return ret; +} + +/* + * Retrieve a set of pages from fscache + */ +int __nfs_readpages_from_fscache(struct nfs_open_context *ctx, +				 struct inode *inode, +				 struct address_space *mapping, +				 struct list_head *pages, +				 unsigned *nr_pages) +{ +	int ret, npages = *nr_pages; + +	dfprintk(FSCACHE, "NFS: nfs_getpages_from_fscache (0x%p/%u/0x%p)\n", +		 NFS_I(inode)->fscache, npages, inode); + +	ret = fscache_read_or_alloc_pages(NFS_I(inode)->fscache, +					  mapping, pages, nr_pages, +					  nfs_readpage_from_fscache_complete, +					  ctx, +					  mapping_gfp_mask(mapping)); +	if (*nr_pages < npages) +		nfs_add_fscache_stats(inode, NFSIOS_FSCACHE_PAGES_READ_OK, +				      npages); +	if (*nr_pages > 0) +		nfs_add_fscache_stats(inode, NFSIOS_FSCACHE_PAGES_READ_FAIL, +				      *nr_pages); + +	switch (ret) { +	case 0: /* read submitted to the cache for all pages */ +		BUG_ON(!list_empty(pages)); +		BUG_ON(*nr_pages != 0); +		dfprintk(FSCACHE, +			 "NFS: nfs_getpages_from_fscache: submitted\n"); + +		return ret; + +	case -ENOBUFS: /* some pages aren't cached and can't be */ +	case -ENODATA: /* some pages aren't cached */ +		dfprintk(FSCACHE, +			 "NFS: nfs_getpages_from_fscache: no page: %d\n", ret); +		return 1; + +	default: +		dfprintk(FSCACHE, +			 "NFS: nfs_getpages_from_fscache: ret  %d\n", ret); +	} + +	return ret; +} + +/* + * Store a newly fetched page in fscache + * - PG_fscache must be set on the page + */ +void __nfs_readpage_to_fscache(struct inode *inode, struct page *page, int sync) +{ +	int ret; + +	dfprintk(FSCACHE, +		 "NFS: readpage_to_fscache(fsc:%p/p:%p(i:%lx f:%lx)/%d)\n", +		 NFS_I(inode)->fscache, page, page->index, page->flags, sync); + +	ret = fscache_write_page(NFS_I(inode)->fscache, page, GFP_KERNEL); +	dfprintk(FSCACHE, +		 "NFS:     readpage_to_fscache: p:%p(i:%lu f:%lx) ret %d\n", +		 page, page->index, page->flags, ret); + +	if (ret != 0) { +		fscache_uncache_page(NFS_I(inode)->fscache, page); +		nfs_add_fscache_stats(inode, +				      NFSIOS_FSCACHE_PAGES_WRITTEN_FAIL, 1); +		nfs_add_fscache_stats(inode, NFSIOS_FSCACHE_PAGES_UNCACHED, 1); +	} else { +		nfs_add_fscache_stats(inode, +				      NFSIOS_FSCACHE_PAGES_WRITTEN_OK, 1); +	} +} diff --git a/fs/nfs/fscache.h b/fs/nfs/fscache.h new file mode 100644 index 00000000000..6e809bb0ff0 --- /dev/null +++ b/fs/nfs/fscache.h @@ -0,0 +1,220 @@ +/* NFS filesystem cache interface definitions + * + * Copyright (C) 2008 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +#ifndef _NFS_FSCACHE_H +#define _NFS_FSCACHE_H + +#include <linux/nfs_fs.h> +#include <linux/nfs_mount.h> +#include <linux/nfs4_mount.h> +#include <linux/fscache.h> + +#ifdef CONFIG_NFS_FSCACHE + +/* + * set of NFS FS-Cache objects that form a superblock key + */ +struct nfs_fscache_key { +	struct rb_node		node; +	struct nfs_client	*nfs_client;	/* the server */ + +	/* the elements of the unique key - as used by nfs_compare_super() and +	 * nfs_compare_mount_options() to distinguish superblocks */ +	struct { +		struct { +			unsigned long	s_flags;	/* various flags +							 * (& NFS_MS_MASK) */ +		} super; + +		struct { +			struct nfs_fsid fsid; +			int		flags; +			unsigned int	rsize;		/* read size */ +			unsigned int	wsize;		/* write size */ +			unsigned int	acregmin;	/* attr cache timeouts */ +			unsigned int	acregmax; +			unsigned int	acdirmin; +			unsigned int	acdirmax; +		} nfs_server; + +		struct { +			rpc_authflavor_t au_flavor; +		} rpc_auth; + +		/* uniquifier - can be used if nfs_server.flags includes +		 * NFS_MOUNT_UNSHARED  */ +		u8 uniq_len; +		char uniquifier[0]; +	} key; +}; + +/* + * fscache-index.c + */ +extern struct fscache_netfs nfs_fscache_netfs; +extern const struct fscache_cookie_def nfs_fscache_server_index_def; +extern const struct fscache_cookie_def nfs_fscache_super_index_def; +extern const struct fscache_cookie_def nfs_fscache_inode_object_def; + +extern int nfs_fscache_register(void); +extern void nfs_fscache_unregister(void); + +/* + * fscache.c + */ +extern void nfs_fscache_get_client_cookie(struct nfs_client *); +extern void nfs_fscache_release_client_cookie(struct nfs_client *); + +extern void nfs_fscache_get_super_cookie(struct super_block *, +					 struct nfs_parsed_mount_data *); +extern void nfs_fscache_release_super_cookie(struct super_block *); + +extern void nfs_fscache_init_inode_cookie(struct inode *); +extern void nfs_fscache_release_inode_cookie(struct inode *); +extern void nfs_fscache_zap_inode_cookie(struct inode *); +extern void nfs_fscache_set_inode_cookie(struct inode *, struct file *); +extern void nfs_fscache_reset_inode_cookie(struct inode *); + +extern void __nfs_fscache_invalidate_page(struct page *, struct inode *); +extern int nfs_fscache_release_page(struct page *, gfp_t); + +extern int __nfs_readpage_from_fscache(struct nfs_open_context *, +				       struct inode *, struct page *); +extern int __nfs_readpages_from_fscache(struct nfs_open_context *, +					struct inode *, struct address_space *, +					struct list_head *, unsigned *); +extern void __nfs_readpage_to_fscache(struct inode *, struct page *, int); + +/* + * wait for a page to complete writing to the cache + */ +static inline void nfs_fscache_wait_on_page_write(struct nfs_inode *nfsi, +						  struct page *page) +{ +	if (PageFsCache(page)) +		fscache_wait_on_page_write(nfsi->fscache, page); +} + +/* + * release the caching state associated with a page if undergoing complete page + * invalidation + */ +static inline void nfs_fscache_invalidate_page(struct page *page, +					       struct inode *inode) +{ +	if (PageFsCache(page)) +		__nfs_fscache_invalidate_page(page, inode); +} + +/* + * Retrieve a page from an inode data storage object. + */ +static inline int nfs_readpage_from_fscache(struct nfs_open_context *ctx, +					    struct inode *inode, +					    struct page *page) +{ +	if (NFS_I(inode)->fscache) +		return __nfs_readpage_from_fscache(ctx, inode, page); +	return -ENOBUFS; +} + +/* + * Retrieve a set of pages from an inode data storage object. + */ +static inline int nfs_readpages_from_fscache(struct nfs_open_context *ctx, +					     struct inode *inode, +					     struct address_space *mapping, +					     struct list_head *pages, +					     unsigned *nr_pages) +{ +	if (NFS_I(inode)->fscache) +		return __nfs_readpages_from_fscache(ctx, inode, mapping, pages, +						    nr_pages); +	return -ENOBUFS; +} + +/* + * Store a page newly fetched from the server in an inode data storage object + * in the cache. + */ +static inline void nfs_readpage_to_fscache(struct inode *inode, +					   struct page *page, +					   int sync) +{ +	if (PageFsCache(page)) +		__nfs_readpage_to_fscache(inode, page, sync); +} + +/* + * indicate the client caching state as readable text + */ +static inline const char *nfs_server_fscache_state(struct nfs_server *server) +{ +	if (server->fscache && (server->options & NFS_OPTION_FSCACHE)) +		return "yes"; +	return "no "; +} + + +#else /* CONFIG_NFS_FSCACHE */ +static inline int nfs_fscache_register(void) { return 0; } +static inline void nfs_fscache_unregister(void) {} + +static inline void nfs_fscache_get_client_cookie(struct nfs_client *clp) {} +static inline void nfs_fscache_release_client_cookie(struct nfs_client *clp) {} + +static inline void nfs_fscache_get_super_cookie( +	struct super_block *sb, +	struct nfs_parsed_mount_data *data) +{ +} +static inline void nfs_fscache_release_super_cookie(struct super_block *sb) {} + +static inline void nfs_fscache_init_inode_cookie(struct inode *inode) {} +static inline void nfs_fscache_release_inode_cookie(struct inode *inode) {} +static inline void nfs_fscache_zap_inode_cookie(struct inode *inode) {} +static inline void nfs_fscache_set_inode_cookie(struct inode *inode, +						struct file *filp) {} +static inline void nfs_fscache_reset_inode_cookie(struct inode *inode) {} + +static inline int nfs_fscache_release_page(struct page *page, gfp_t gfp) +{ +	return 1; /* True: may release page */ +} +static inline void nfs_fscache_invalidate_page(struct page *page, +					       struct inode *inode) {} +static inline void nfs_fscache_wait_on_page_write(struct nfs_inode *nfsi, +						  struct page *page) {} + +static inline int nfs_readpage_from_fscache(struct nfs_open_context *ctx, +					    struct inode *inode, +					    struct page *page) +{ +	return -ENOBUFS; +} +static inline int nfs_readpages_from_fscache(struct nfs_open_context *ctx, +					     struct inode *inode, +					     struct address_space *mapping, +					     struct list_head *pages, +					     unsigned *nr_pages) +{ +	return -ENOBUFS; +} +static inline void nfs_readpage_to_fscache(struct inode *inode, +					   struct page *page, int sync) {} + +static inline const char *nfs_server_fscache_state(struct nfs_server *server) +{ +	return "no "; +} + +#endif /* CONFIG_NFS_FSCACHE */ +#endif /* _NFS_FSCACHE_H */ diff --git a/fs/nfs/getroot.c b/fs/nfs/getroot.c index b7c9b2df1f2..46177cb8706 100644 --- a/fs/nfs/getroot.c +++ b/fs/nfs/getroot.c @@ -156,7 +156,7 @@ int nfs4_path_walk(struct nfs_server *server,  		return ret;  	} -	if (fattr.type != NFDIR) { +	if (!S_ISDIR(fattr.mode)) {  		printk(KERN_ERR "nfs4_get_root:"  		       " getroot encountered non-directory\n");  		return -ENOTDIR; @@ -213,7 +213,7 @@ eat_dot_dir:  		return ret;  	} -	if (fattr.type != NFDIR) { +	if (!S_ISDIR(fattr.mode)) {  		printk(KERN_ERR "nfs4_get_root:"  		       " lookupfh encountered non-directory\n");  		return -ENOTDIR; diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c index 0c381686171..64f87194d39 100644 --- a/fs/nfs/inode.c +++ b/fs/nfs/inode.c @@ -46,6 +46,7 @@  #include "delegation.h"  #include "iostat.h"  #include "internal.h" +#include "fscache.h"  #define NFSDBG_FACILITY		NFSDBG_VFS @@ -66,6 +67,18 @@ nfs_fattr_to_ino_t(struct nfs_fattr *fattr)  }  /** + * nfs_wait_bit_killable - helper for functions that are sleeping on bit locks + * @word: long word containing the bit lock + */ +int nfs_wait_bit_killable(void *word) +{ +	if (fatal_signal_pending(current)) +		return -ERESTARTSYS; +	schedule(); +	return 0; +} + +/**   * nfs_compat_user_ino64 - returns the user-visible inode number   * @fileid: 64-bit fileid   * @@ -109,6 +122,7 @@ void nfs_clear_inode(struct inode *inode)  	BUG_ON(!list_empty(&NFS_I(inode)->open_files));  	nfs_zap_acl_cache(inode);  	nfs_access_zap_cache(inode); +	nfs_fscache_release_inode_cookie(inode);  }  /** @@ -249,13 +263,10 @@ nfs_fhget(struct super_block *sb, struct nfs_fh *fh, struct nfs_fattr *fattr)  	struct inode *inode = ERR_PTR(-ENOENT);  	unsigned long hash; -	if ((fattr->valid & NFS_ATTR_FATTR) == 0) +	if ((fattr->valid & NFS_ATTR_FATTR_FILEID) == 0)  		goto out_no_inode; - -	if (!fattr->nlink) { -		printk("NFS: Buggy server - nlink == 0!\n"); +	if ((fattr->valid & NFS_ATTR_FATTR_TYPE) == 0)  		goto out_no_inode; -	}  	hash = nfs_fattr_to_ino_t(fattr); @@ -291,7 +302,8 @@ nfs_fhget(struct super_block *sb, struct nfs_fh *fh, struct nfs_fattr *fattr)  			    && fattr->size <= NFS_LIMIT_READDIRPLUS)  				set_bit(NFS_INO_ADVISE_RDPLUS, &NFS_I(inode)->flags);  			/* Deal with crossing mountpoints */ -			if (!nfs_fsid_equal(&NFS_SB(sb)->fsid, &fattr->fsid)) { +			if ((fattr->valid & NFS_ATTR_FATTR_FSID) +					&& !nfs_fsid_equal(&NFS_SB(sb)->fsid, &fattr->fsid)) {  				if (fattr->valid & NFS_ATTR_FATTR_V4_REFERRAL)  					inode->i_op = &nfs_referral_inode_operations;  				else @@ -304,30 +316,49 @@ nfs_fhget(struct super_block *sb, struct nfs_fh *fh, struct nfs_fattr *fattr)  		else  			init_special_inode(inode, inode->i_mode, fattr->rdev); +		memset(&inode->i_atime, 0, sizeof(inode->i_atime)); +		memset(&inode->i_mtime, 0, sizeof(inode->i_mtime)); +		memset(&inode->i_ctime, 0, sizeof(inode->i_ctime)); +		nfsi->change_attr = 0; +		inode->i_size = 0; +		inode->i_nlink = 0; +		inode->i_uid = -2; +		inode->i_gid = -2; +		inode->i_blocks = 0; +		memset(nfsi->cookieverf, 0, sizeof(nfsi->cookieverf)); +  		nfsi->read_cache_jiffies = fattr->time_start;  		nfsi->attr_gencount = fattr->gencount; -		inode->i_atime = fattr->atime; -		inode->i_mtime = fattr->mtime; -		inode->i_ctime = fattr->ctime; -		if (fattr->valid & NFS_ATTR_FATTR_V4) +		if (fattr->valid & NFS_ATTR_FATTR_ATIME) +			inode->i_atime = fattr->atime; +		if (fattr->valid & NFS_ATTR_FATTR_MTIME) +			inode->i_mtime = fattr->mtime; +		if (fattr->valid & NFS_ATTR_FATTR_CTIME) +			inode->i_ctime = fattr->ctime; +		if (fattr->valid & NFS_ATTR_FATTR_CHANGE)  			nfsi->change_attr = fattr->change_attr; -		inode->i_size = nfs_size_to_loff_t(fattr->size); -		inode->i_nlink = fattr->nlink; -		inode->i_uid = fattr->uid; -		inode->i_gid = fattr->gid; -		if (fattr->valid & (NFS_ATTR_FATTR_V3 | NFS_ATTR_FATTR_V4)) { +		if (fattr->valid & NFS_ATTR_FATTR_SIZE) +			inode->i_size = nfs_size_to_loff_t(fattr->size); +		if (fattr->valid & NFS_ATTR_FATTR_NLINK) +			inode->i_nlink = fattr->nlink; +		if (fattr->valid & NFS_ATTR_FATTR_OWNER) +			inode->i_uid = fattr->uid; +		if (fattr->valid & NFS_ATTR_FATTR_GROUP) +			inode->i_gid = fattr->gid; +		if (fattr->valid & NFS_ATTR_FATTR_BLOCKS_USED) +			inode->i_blocks = fattr->du.nfs2.blocks; +		if (fattr->valid & NFS_ATTR_FATTR_SPACE_USED) {  			/*  			 * report the blocks in 512byte units  			 */  			inode->i_blocks = nfs_calc_block_size(fattr->du.nfs3.used); -		} else { -			inode->i_blocks = fattr->du.nfs2.blocks;  		}  		nfsi->attrtimeo = NFS_MINATTRTIMEO(inode);  		nfsi->attrtimeo_timestamp = now; -		memset(nfsi->cookieverf, 0, sizeof(nfsi->cookieverf));  		nfsi->access_cache = RB_ROOT; +		nfs_fscache_init_inode_cookie(inode); +  		unlock_new_inode(inode);  	} else  		nfs_refresh_inode(inode, fattr); @@ -514,6 +545,32 @@ int nfs_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat)  	return err;  } +/** + * nfs_close_context - Common close_context() routine NFSv2/v3 + * @ctx: pointer to context + * @is_sync: is this a synchronous close + * + * always ensure that the attributes are up to date if we're mounted + * with close-to-open semantics + */ +void nfs_close_context(struct nfs_open_context *ctx, int is_sync) +{ +	struct inode *inode; +	struct nfs_server *server; + +	if (!(ctx->mode & FMODE_WRITE)) +		return; +	if (!is_sync) +		return; +	inode = ctx->path.dentry->d_inode; +	if (!list_empty(&NFS_I(inode)->open_files)) +		return; +	server = NFS_SERVER(inode); +	if (server->flags & NFS_MOUNT_NOCTO) +		return; +	nfs_revalidate_inode(server, inode); +} +  static struct nfs_open_context *alloc_nfs_open_context(struct vfsmount *mnt, struct dentry *dentry, struct rpc_cred *cred)  {  	struct nfs_open_context *ctx; @@ -540,24 +597,15 @@ struct nfs_open_context *get_nfs_open_context(struct nfs_open_context *ctx)  	return ctx;  } -static void __put_nfs_open_context(struct nfs_open_context *ctx, int wait) +static void __put_nfs_open_context(struct nfs_open_context *ctx, int is_sync)  { -	struct inode *inode; - -	if (ctx == NULL) -		return; +	struct inode *inode = ctx->path.dentry->d_inode; -	inode = ctx->path.dentry->d_inode;  	if (!atomic_dec_and_lock(&ctx->count, &inode->i_lock))  		return;  	list_del(&ctx->list);  	spin_unlock(&inode->i_lock); -	if (ctx->state != NULL) { -		if (wait) -			nfs4_close_sync(&ctx->path, ctx->state, ctx->mode); -		else -			nfs4_close_state(&ctx->path, ctx->state, ctx->mode); -	} +	NFS_PROTO(inode)->close_context(ctx, is_sync);  	if (ctx->cred != NULL)  		put_rpccred(ctx->cred);  	path_put(&ctx->path); @@ -642,6 +690,7 @@ int nfs_open(struct inode *inode, struct file *filp)  	ctx->mode = filp->f_mode;  	nfs_file_set_open_context(filp, ctx);  	put_nfs_open_context(ctx); +	nfs_fscache_set_inode_cookie(inode, filp);  	return 0;  } @@ -670,9 +719,6 @@ __nfs_revalidate_inode(struct nfs_server *server, struct inode *inode)  	if (NFS_STALE(inode))  		goto out; -	if (NFS_STALE(inode)) -		goto out; -  	nfs_inc_stats(inode, NFSIOS_INODEREVALIDATE);  	status = NFS_PROTO(inode)->getattr(server, NFS_FH(inode), &fattr);  	if (status != 0) { @@ -745,6 +791,7 @@ static int nfs_invalidate_mapping_nolock(struct inode *inode, struct address_spa  		memset(nfsi->cookieverf, 0, sizeof(nfsi->cookieverf));  	spin_unlock(&inode->i_lock);  	nfs_inc_stats(inode, NFSIOS_DATAINVALIDATE); +	nfs_fscache_reset_inode_cookie(inode);  	dfprintk(PAGECACHE, "NFS: (%s/%Ld) data cache invalidated\n",  			inode->i_sb->s_id, (long long)NFS_FILEID(inode));  	return 0; @@ -815,25 +862,31 @@ static void nfs_wcc_update_inode(struct inode *inode, struct nfs_fattr *fattr)  {  	struct nfs_inode *nfsi = NFS_I(inode); -	if ((fattr->valid & NFS_ATTR_WCC_V4) != 0 && -			nfsi->change_attr == fattr->pre_change_attr) { +	if ((fattr->valid & NFS_ATTR_FATTR_PRECHANGE) +			&& (fattr->valid & NFS_ATTR_FATTR_CHANGE) +			&& nfsi->change_attr == fattr->pre_change_attr) {  		nfsi->change_attr = fattr->change_attr;  		if (S_ISDIR(inode->i_mode))  			nfsi->cache_validity |= NFS_INO_INVALID_DATA;  	}  	/* If we have atomic WCC data, we may update some attributes */ -	if ((fattr->valid & NFS_ATTR_WCC) != 0) { -		if (timespec_equal(&inode->i_ctime, &fattr->pre_ctime)) +	if ((fattr->valid & NFS_ATTR_FATTR_PRECTIME) +			&& (fattr->valid & NFS_ATTR_FATTR_CTIME) +			&& timespec_equal(&inode->i_ctime, &fattr->pre_ctime))  			memcpy(&inode->i_ctime, &fattr->ctime, sizeof(inode->i_ctime)); -		if (timespec_equal(&inode->i_mtime, &fattr->pre_mtime)) { + +	if ((fattr->valid & NFS_ATTR_FATTR_PREMTIME) +			&& (fattr->valid & NFS_ATTR_FATTR_MTIME) +			&& timespec_equal(&inode->i_mtime, &fattr->pre_mtime)) {  			memcpy(&inode->i_mtime, &fattr->mtime, sizeof(inode->i_mtime));  			if (S_ISDIR(inode->i_mode))  				nfsi->cache_validity |= NFS_INO_INVALID_DATA; -		} -		if (i_size_read(inode) == nfs_size_to_loff_t(fattr->pre_size) && -		    nfsi->npages == 0) -			i_size_write(inode, nfs_size_to_loff_t(fattr->size));  	} +	if ((fattr->valid & NFS_ATTR_FATTR_PRESIZE) +			&& (fattr->valid & NFS_ATTR_FATTR_SIZE) +			&& i_size_read(inode) == nfs_size_to_loff_t(fattr->pre_size) +			&& nfsi->npages == 0) +			i_size_write(inode, nfs_size_to_loff_t(fattr->size));  }  /** @@ -853,35 +906,39 @@ static int nfs_check_inode_attributes(struct inode *inode, struct nfs_fattr *fat  	/* Has the inode gone and changed behind our back? */ -	if (nfsi->fileid != fattr->fileid -			|| (inode->i_mode & S_IFMT) != (fattr->mode & S_IFMT)) { +	if ((fattr->valid & NFS_ATTR_FATTR_FILEID) && nfsi->fileid != fattr->fileid) +		return -EIO; +	if ((fattr->valid & NFS_ATTR_FATTR_TYPE) && (inode->i_mode & S_IFMT) != (fattr->mode & S_IFMT))  		return -EIO; -	} -	if ((fattr->valid & NFS_ATTR_FATTR_V4) != 0 && +	if ((fattr->valid & NFS_ATTR_FATTR_CHANGE) != 0 &&  			nfsi->change_attr != fattr->change_attr)  		invalid |= NFS_INO_INVALID_ATTR|NFS_INO_REVAL_PAGECACHE;  	/* Verify a few of the more important attributes */ -	if (!timespec_equal(&inode->i_mtime, &fattr->mtime)) +	if ((fattr->valid & NFS_ATTR_FATTR_MTIME) && !timespec_equal(&inode->i_mtime, &fattr->mtime))  		invalid |= NFS_INO_INVALID_ATTR|NFS_INO_REVAL_PAGECACHE; -	cur_size = i_size_read(inode); - 	new_isize = nfs_size_to_loff_t(fattr->size); -	if (cur_size != new_isize && nfsi->npages == 0) -		invalid |= NFS_INO_INVALID_ATTR|NFS_INO_REVAL_PAGECACHE; +	if (fattr->valid & NFS_ATTR_FATTR_SIZE) { +		cur_size = i_size_read(inode); +		new_isize = nfs_size_to_loff_t(fattr->size); +		if (cur_size != new_isize && nfsi->npages == 0) +			invalid |= NFS_INO_INVALID_ATTR|NFS_INO_REVAL_PAGECACHE; +	}  	/* Have any file permissions changed? */ -	if ((inode->i_mode & S_IALLUGO) != (fattr->mode & S_IALLUGO) -			|| inode->i_uid != fattr->uid -			|| inode->i_gid != fattr->gid) +	if ((fattr->valid & NFS_ATTR_FATTR_MODE) && (inode->i_mode & S_IALLUGO) != (fattr->mode & S_IALLUGO)) +		invalid |= NFS_INO_INVALID_ATTR | NFS_INO_INVALID_ACCESS | NFS_INO_INVALID_ACL; +	if ((fattr->valid & NFS_ATTR_FATTR_OWNER) && inode->i_uid != fattr->uid) +		invalid |= NFS_INO_INVALID_ATTR | NFS_INO_INVALID_ACCESS | NFS_INO_INVALID_ACL; +	if ((fattr->valid & NFS_ATTR_FATTR_GROUP) && inode->i_gid != fattr->gid)  		invalid |= NFS_INO_INVALID_ATTR | NFS_INO_INVALID_ACCESS | NFS_INO_INVALID_ACL;  	/* Has the link count changed? */ -	if (inode->i_nlink != fattr->nlink) +	if ((fattr->valid & NFS_ATTR_FATTR_NLINK) && inode->i_nlink != fattr->nlink)  		invalid |= NFS_INO_INVALID_ATTR; -	if (!timespec_equal(&inode->i_atime, &fattr->atime)) +	if ((fattr->valid & NFS_ATTR_FATTR_ATIME) && !timespec_equal(&inode->i_atime, &fattr->atime))  		invalid |= NFS_INO_INVALID_ATIME;  	if (invalid != 0) @@ -893,11 +950,15 @@ static int nfs_check_inode_attributes(struct inode *inode, struct nfs_fattr *fat  static int nfs_ctime_need_update(const struct inode *inode, const struct nfs_fattr *fattr)  { +	if (!(fattr->valid & NFS_ATTR_FATTR_CTIME)) +		return 0;  	return timespec_compare(&fattr->ctime, &inode->i_ctime) > 0;  }  static int nfs_size_need_update(const struct inode *inode, const struct nfs_fattr *fattr)  { +	if (!(fattr->valid & NFS_ATTR_FATTR_SIZE)) +		return 0;  	return nfs_size_to_loff_t(fattr->size) > i_size_read(inode);  } @@ -975,6 +1036,7 @@ int nfs_refresh_inode(struct inode *inode, struct nfs_fattr *fattr)  	spin_lock(&inode->i_lock);  	status = nfs_refresh_inode_locked(inode, fattr);  	spin_unlock(&inode->i_lock); +  	return status;  } @@ -1033,20 +1095,31 @@ int nfs_post_op_update_inode_force_wcc(struct inode *inode, struct nfs_fattr *fa  	/* Don't do a WCC update if these attributes are already stale */  	if ((fattr->valid & NFS_ATTR_FATTR) == 0 ||  			!nfs_inode_attrs_need_update(inode, fattr)) { -		fattr->valid &= ~(NFS_ATTR_WCC_V4|NFS_ATTR_WCC); +		fattr->valid &= ~(NFS_ATTR_FATTR_PRECHANGE +				| NFS_ATTR_FATTR_PRESIZE +				| NFS_ATTR_FATTR_PREMTIME +				| NFS_ATTR_FATTR_PRECTIME);  		goto out_noforce;  	} -	if ((fattr->valid & NFS_ATTR_FATTR_V4) != 0 && -			(fattr->valid & NFS_ATTR_WCC_V4) == 0) { +	if ((fattr->valid & NFS_ATTR_FATTR_CHANGE) != 0 && +			(fattr->valid & NFS_ATTR_FATTR_PRECHANGE) == 0) {  		fattr->pre_change_attr = NFS_I(inode)->change_attr; -		fattr->valid |= NFS_ATTR_WCC_V4; +		fattr->valid |= NFS_ATTR_FATTR_PRECHANGE;  	} -	if ((fattr->valid & NFS_ATTR_FATTR) != 0 && -			(fattr->valid & NFS_ATTR_WCC) == 0) { +	if ((fattr->valid & NFS_ATTR_FATTR_CTIME) != 0 && +			(fattr->valid & NFS_ATTR_FATTR_PRECTIME) == 0) {  		memcpy(&fattr->pre_ctime, &inode->i_ctime, sizeof(fattr->pre_ctime)); +		fattr->valid |= NFS_ATTR_FATTR_PRECTIME; +	} +	if ((fattr->valid & NFS_ATTR_FATTR_MTIME) != 0 && +			(fattr->valid & NFS_ATTR_FATTR_PREMTIME) == 0) {  		memcpy(&fattr->pre_mtime, &inode->i_mtime, sizeof(fattr->pre_mtime)); +		fattr->valid |= NFS_ATTR_FATTR_PREMTIME; +	} +	if ((fattr->valid & NFS_ATTR_FATTR_SIZE) != 0 && +			(fattr->valid & NFS_ATTR_FATTR_PRESIZE) == 0) {  		fattr->pre_size = i_size_read(inode); -		fattr->valid |= NFS_ATTR_WCC; +		fattr->valid |= NFS_ATTR_FATTR_PRESIZE;  	}  out_noforce:  	status = nfs_post_op_update_inode_locked(inode, fattr); @@ -1078,18 +1151,18 @@ static int nfs_update_inode(struct inode *inode, struct nfs_fattr *fattr)  			__func__, inode->i_sb->s_id, inode->i_ino,  			atomic_read(&inode->i_count), fattr->valid); -	if (nfsi->fileid != fattr->fileid) +	if ((fattr->valid & NFS_ATTR_FATTR_FILEID) && nfsi->fileid != fattr->fileid)  		goto out_fileid;  	/*  	 * Make sure the inode's type hasn't changed.  	 */ -	if ((inode->i_mode & S_IFMT) != (fattr->mode & S_IFMT)) +	if ((fattr->valid & NFS_ATTR_FATTR_TYPE) && (inode->i_mode & S_IFMT) != (fattr->mode & S_IFMT))  		goto out_changed;  	server = NFS_SERVER(inode);  	/* Update the fsid? */ -	if (S_ISDIR(inode->i_mode) && +	if (S_ISDIR(inode->i_mode) && (fattr->valid & NFS_ATTR_FATTR_FSID) &&  			!nfs_fsid_equal(&server->fsid, &fattr->fsid) &&  			!test_bit(NFS_INO_MOUNTPOINT, &nfsi->flags))  		server->fsid = fattr->fsid; @@ -1099,14 +1172,27 @@ static int nfs_update_inode(struct inode *inode, struct nfs_fattr *fattr)  	 */  	nfsi->read_cache_jiffies = fattr->time_start; -	nfsi->cache_validity &= ~(NFS_INO_INVALID_ATTR | NFS_INO_INVALID_ATIME -			| NFS_INO_REVAL_PAGECACHE); +	if ((fattr->valid & NFS_ATTR_FATTR_CHANGE) || (fattr->valid & (NFS_ATTR_FATTR_MTIME|NFS_ATTR_FATTR_CTIME))) +	    nfsi->cache_validity &= ~(NFS_INO_INVALID_ATTR +		    | NFS_INO_INVALID_ATIME +		    | NFS_INO_REVAL_PAGECACHE);  	/* Do atomic weak cache consistency updates */  	nfs_wcc_update_inode(inode, fattr);  	/* More cache consistency checks */ -	if (!(fattr->valid & NFS_ATTR_FATTR_V4)) { +	if (fattr->valid & NFS_ATTR_FATTR_CHANGE) { +		if (nfsi->change_attr != fattr->change_attr) { +			dprintk("NFS: change_attr change on server for file %s/%ld\n", +					inode->i_sb->s_id, inode->i_ino); +			invalid |= NFS_INO_INVALID_ATTR|NFS_INO_INVALID_DATA|NFS_INO_INVALID_ACCESS|NFS_INO_INVALID_ACL; +			if (S_ISDIR(inode->i_mode)) +				nfs_force_lookup_revalidate(inode); +			nfsi->change_attr = fattr->change_attr; +		} +	} + +	if (fattr->valid & NFS_ATTR_FATTR_MTIME) {  		/* NFSv2/v3: Check if the mtime agrees */  		if (!timespec_equal(&inode->i_mtime, &fattr->mtime)) {  			dprintk("NFS: mtime change on server for file %s/%ld\n", @@ -1114,59 +1200,80 @@ static int nfs_update_inode(struct inode *inode, struct nfs_fattr *fattr)  			invalid |= NFS_INO_INVALID_ATTR|NFS_INO_INVALID_DATA;  			if (S_ISDIR(inode->i_mode))  				nfs_force_lookup_revalidate(inode); +			memcpy(&inode->i_mtime, &fattr->mtime, sizeof(inode->i_mtime));  		} +	} +	if (fattr->valid & NFS_ATTR_FATTR_CTIME) {  		/* If ctime has changed we should definitely clear access+acl caches */ -		if (!timespec_equal(&inode->i_ctime, &fattr->ctime)) +		if (!timespec_equal(&inode->i_ctime, &fattr->ctime)) {  			invalid |= NFS_INO_INVALID_ATTR|NFS_INO_INVALID_ACCESS|NFS_INO_INVALID_ACL; -	} else if (nfsi->change_attr != fattr->change_attr) { -		dprintk("NFS: change_attr change on server for file %s/%ld\n", -				inode->i_sb->s_id, inode->i_ino); -		invalid |= NFS_INO_INVALID_ATTR|NFS_INO_INVALID_DATA|NFS_INO_INVALID_ACCESS|NFS_INO_INVALID_ACL; -		if (S_ISDIR(inode->i_mode)) -			nfs_force_lookup_revalidate(inode); +			/* and probably clear data for a directory too as utimes can cause +			 * havoc with our cache. +			 */ +			if (S_ISDIR(inode->i_mode)) { +				invalid |= NFS_INO_INVALID_DATA; +				nfs_force_lookup_revalidate(inode); +			} +			memcpy(&inode->i_ctime, &fattr->ctime, sizeof(inode->i_ctime)); +		}  	}  	/* Check if our cached file size is stale */ - 	new_isize = nfs_size_to_loff_t(fattr->size); -	cur_isize = i_size_read(inode); -	if (new_isize != cur_isize) { -		/* Do we perhaps have any outstanding writes, or has -		 * the file grown beyond our last write? */ -		if (nfsi->npages == 0 || new_isize > cur_isize) { -			i_size_write(inode, new_isize); -			invalid |= NFS_INO_INVALID_ATTR|NFS_INO_INVALID_DATA; +	if (fattr->valid & NFS_ATTR_FATTR_SIZE) { +		new_isize = nfs_size_to_loff_t(fattr->size); +		cur_isize = i_size_read(inode); +		if (new_isize != cur_isize) { +			/* Do we perhaps have any outstanding writes, or has +			 * the file grown beyond our last write? */ +			if (nfsi->npages == 0 || new_isize > cur_isize) { +				i_size_write(inode, new_isize); +				invalid |= NFS_INO_INVALID_ATTR|NFS_INO_INVALID_DATA; +			} +			dprintk("NFS: isize change on server for file %s/%ld\n", +					inode->i_sb->s_id, inode->i_ino);  		} -		dprintk("NFS: isize change on server for file %s/%ld\n", -				inode->i_sb->s_id, inode->i_ino);  	} -	memcpy(&inode->i_mtime, &fattr->mtime, sizeof(inode->i_mtime)); -	memcpy(&inode->i_ctime, &fattr->ctime, sizeof(inode->i_ctime)); -	memcpy(&inode->i_atime, &fattr->atime, sizeof(inode->i_atime)); -	nfsi->change_attr = fattr->change_attr; - -	if ((inode->i_mode & S_IALLUGO) != (fattr->mode & S_IALLUGO) || -	    inode->i_uid != fattr->uid || -	    inode->i_gid != fattr->gid) -		invalid |= NFS_INO_INVALID_ATTR|NFS_INO_INVALID_ACCESS|NFS_INO_INVALID_ACL; +	if (fattr->valid & NFS_ATTR_FATTR_ATIME) +		memcpy(&inode->i_atime, &fattr->atime, sizeof(inode->i_atime)); -	if (inode->i_nlink != fattr->nlink) -		invalid |= NFS_INO_INVALID_ATTR; +	if (fattr->valid & NFS_ATTR_FATTR_MODE) { +		if ((inode->i_mode & S_IALLUGO) != (fattr->mode & S_IALLUGO)) { +			invalid |= NFS_INO_INVALID_ATTR|NFS_INO_INVALID_ACCESS|NFS_INO_INVALID_ACL; +			inode->i_mode = fattr->mode; +		} +	} +	if (fattr->valid & NFS_ATTR_FATTR_OWNER) { +		if (inode->i_uid != fattr->uid) { +			invalid |= NFS_INO_INVALID_ATTR|NFS_INO_INVALID_ACCESS|NFS_INO_INVALID_ACL; +			inode->i_uid = fattr->uid; +		} +	} +	if (fattr->valid & NFS_ATTR_FATTR_GROUP) { +		if (inode->i_gid != fattr->gid) { +			invalid |= NFS_INO_INVALID_ATTR|NFS_INO_INVALID_ACCESS|NFS_INO_INVALID_ACL; +			inode->i_gid = fattr->gid; +		} +	} -	inode->i_mode = fattr->mode; -	inode->i_nlink = fattr->nlink; -	inode->i_uid = fattr->uid; -	inode->i_gid = fattr->gid; +	if (fattr->valid & NFS_ATTR_FATTR_NLINK) { +		if (inode->i_nlink != fattr->nlink) { +			invalid |= NFS_INO_INVALID_ATTR; +			if (S_ISDIR(inode->i_mode)) +				invalid |= NFS_INO_INVALID_DATA; +			inode->i_nlink = fattr->nlink; +		} +	} -	if (fattr->valid & (NFS_ATTR_FATTR_V3 | NFS_ATTR_FATTR_V4)) { +	if (fattr->valid & NFS_ATTR_FATTR_SPACE_USED) {  		/*  		 * report the blocks in 512byte units  		 */  		inode->i_blocks = nfs_calc_block_size(fattr->du.nfs3.used); - 	} else { - 		inode->i_blocks = fattr->du.nfs2.blocks;   	} +	if (fattr->valid & NFS_ATTR_FATTR_BLOCKS_USED) +		inode->i_blocks = fattr->du.nfs2.blocks;  	/* Update attrtimeo value if we're out of the unstable period */  	if (invalid & NFS_INO_INVALID_ATTR) { @@ -1274,7 +1381,6 @@ static void init_once(void *foo)  	INIT_LIST_HEAD(&nfsi->access_cache_entry_lru);  	INIT_LIST_HEAD(&nfsi->access_cache_inode_lru);  	INIT_RADIX_TREE(&nfsi->nfs_page_tree, GFP_ATOMIC); -	nfsi->ncommit = 0;  	nfsi->npages = 0;  	atomic_set(&nfsi->silly_count, 1);  	INIT_HLIST_HEAD(&nfsi->silly_list); @@ -1337,6 +1443,10 @@ static int __init init_nfs_fs(void)  {  	int err; +	err = nfs_fscache_register(); +	if (err < 0) +		goto out7; +  	err = nfsiod_start();  	if (err)  		goto out6; @@ -1389,6 +1499,8 @@ out4:  out5:  	nfsiod_stop();  out6: +	nfs_fscache_unregister(); +out7:  	return err;  } @@ -1399,6 +1511,7 @@ static void __exit exit_nfs_fs(void)  	nfs_destroy_readpagecache();  	nfs_destroy_inodecache();  	nfs_destroy_nfspagecache(); +	nfs_fscache_unregister();  #ifdef CONFIG_PROC_FS  	rpc_proc_unregister("nfs");  #endif diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h index 340ede8f608..e4d6a8348ad 100644 --- a/fs/nfs/internal.h +++ b/fs/nfs/internal.h @@ -5,6 +5,8 @@  #include <linux/mount.h>  #include <linux/security.h> +#define NFS_MS_MASK (MS_RDONLY|MS_NOSUID|MS_NODEV|MS_NOEXEC|MS_SYNCHRONOUS) +  struct nfs_string;  /* Maximum number of readahead requests @@ -37,10 +39,12 @@ struct nfs_parsed_mount_data {  	int			acregmin, acregmax,  				acdirmin, acdirmax;  	int			namlen; +	unsigned int		options;  	unsigned int		bsize;  	unsigned int		auth_flavor_len;  	rpc_authflavor_t	auth_flavors[1];  	char			*client_address; +	char			*fscache_uniq;  	struct {  		struct sockaddr_storage	address; @@ -152,6 +156,9 @@ extern __be32 *nfs4_decode_dirent(__be32 *p, struct nfs_entry *entry, int plus);  extern struct rpc_procinfo nfs4_procedures[];  #endif +/* proc.c */ +void nfs_close_context(struct nfs_open_context *ctx, int is_sync); +  /* dir.c */  extern int nfs_access_cache_shrinker(int nr_to_scan, gfp_t gfp_mask); @@ -165,6 +172,7 @@ extern void nfs_clear_inode(struct inode *);  extern void nfs4_clear_inode(struct inode *);  #endif  void nfs_zap_acl_cache(struct inode *inode); +extern int nfs_wait_bit_killable(void *word);  /* super.c */  void nfs_parse_ip_address(char *, size_t, struct sockaddr *, size_t *); diff --git a/fs/nfs/iostat.h b/fs/nfs/iostat.h index a3695281003..a2ab2529b5c 100644 --- a/fs/nfs/iostat.h +++ b/fs/nfs/iostat.h @@ -16,6 +16,9 @@  struct nfs_iostats {  	unsigned long long	bytes[__NFSIOS_BYTESMAX]; +#ifdef CONFIG_NFS_FSCACHE +	unsigned long long	fscache[__NFSIOS_FSCACHEMAX]; +#endif  	unsigned long		events[__NFSIOS_COUNTSMAX];  } ____cacheline_aligned; @@ -57,6 +60,21 @@ static inline void nfs_add_stats(const struct inode *inode,  	nfs_add_server_stats(NFS_SERVER(inode), stat, addend);  } +#ifdef CONFIG_NFS_FSCACHE +static inline void nfs_add_fscache_stats(struct inode *inode, +					 enum nfs_stat_fscachecounters stat, +					 unsigned long addend) +{ +	struct nfs_iostats *iostats; +	int cpu; + +	cpu = get_cpu(); +	iostats = per_cpu_ptr(NFS_SERVER(inode)->io_stats, cpu); +	iostats->fscache[stat] += addend; +	put_cpu_no_resched(); +} +#endif +  static inline struct nfs_iostats *nfs_alloc_iostats(void)  {  	return alloc_percpu(struct nfs_iostats); diff --git a/fs/nfs/nfs2xdr.c b/fs/nfs/nfs2xdr.c index 28bab67d151..c862c9340f9 100644 --- a/fs/nfs/nfs2xdr.c +++ b/fs/nfs/nfs2xdr.c @@ -120,8 +120,8 @@ xdr_decode_time(__be32 *p, struct timespec *timep)  static __be32 *  xdr_decode_fattr(__be32 *p, struct nfs_fattr *fattr)  { -	u32 rdev; -	fattr->type = (enum nfs_ftype) ntohl(*p++); +	u32 rdev, type; +	type = ntohl(*p++);  	fattr->mode = ntohl(*p++);  	fattr->nlink = ntohl(*p++);  	fattr->uid = ntohl(*p++); @@ -136,10 +136,9 @@ xdr_decode_fattr(__be32 *p, struct nfs_fattr *fattr)  	p = xdr_decode_time(p, &fattr->atime);  	p = xdr_decode_time(p, &fattr->mtime);  	p = xdr_decode_time(p, &fattr->ctime); -	fattr->valid |= NFS_ATTR_FATTR; +	fattr->valid |= NFS_ATTR_FATTR_V2;  	fattr->rdev = new_decode_dev(rdev); -	if (fattr->type == NFCHR && rdev == NFS2_FIFO_DEV) { -		fattr->type = NFFIFO; +	if (type == NFCHR && rdev == NFS2_FIFO_DEV) {  		fattr->mode = (fattr->mode & ~S_IFMT) | S_IFIFO;  		fattr->rdev = 0;  	} diff --git a/fs/nfs/nfs3proc.c b/fs/nfs/nfs3proc.c index c55be7a7679..d0cc5ce0edf 100644 --- a/fs/nfs/nfs3proc.c +++ b/fs/nfs/nfs3proc.c @@ -328,7 +328,7 @@ nfs3_proc_create(struct inode *dir, struct dentry *dentry, struct iattr *sattr,  		data->arg.create.verifier[1] = current->pid;  	} -	sattr->ia_mode &= ~current->fs->umask; +	sattr->ia_mode &= ~current_umask();  	for (;;) {  		status = nfs3_do_create(dir, dentry, data); @@ -528,7 +528,7 @@ nfs3_proc_mkdir(struct inode *dir, struct dentry *dentry, struct iattr *sattr)  	dprintk("NFS call  mkdir %s\n", dentry->d_name.name); -	sattr->ia_mode &= ~current->fs->umask; +	sattr->ia_mode &= ~current_umask();  	data = nfs3_alloc_createdata();  	if (data == NULL) @@ -639,7 +639,7 @@ nfs3_proc_mknod(struct inode *dir, struct dentry *dentry, struct iattr *sattr,  	dprintk("NFS call  mknod %s %u:%u\n", dentry->d_name.name,  			MAJOR(rdev), MINOR(rdev)); -	sattr->ia_mode &= ~current->fs->umask; +	sattr->ia_mode &= ~current_umask();  	data = nfs3_alloc_createdata();  	if (data == NULL) @@ -834,4 +834,5 @@ const struct nfs_rpc_ops nfs_v3_clientops = {  	.commit_done	= nfs3_commit_done,  	.lock		= nfs3_proc_lock,  	.clear_acl_cache = nfs3_forget_cached_acls, +	.close_context	= nfs_close_context,  }; diff --git a/fs/nfs/nfs3xdr.c b/fs/nfs/nfs3xdr.c index 6cdeacffde4..e6a1932c711 100644 --- a/fs/nfs/nfs3xdr.c +++ b/fs/nfs/nfs3xdr.c @@ -91,19 +91,15 @@  /*   * Map file type to S_IFMT bits   */ -static struct { -	unsigned int	mode; -	unsigned int	nfs2type; -} nfs_type2fmt[] = { -      { 0,		NFNON	}, -      { S_IFREG,	NFREG	}, -      { S_IFDIR,	NFDIR	}, -      { S_IFBLK,	NFBLK	}, -      { S_IFCHR,	NFCHR	}, -      { S_IFLNK,	NFLNK	}, -      { S_IFSOCK,	NFSOCK	}, -      { S_IFIFO,	NFFIFO	}, -      { 0,		NFBAD	} +static const umode_t nfs_type2fmt[] = { +	[NF3BAD] = 0, +	[NF3REG] = S_IFREG, +	[NF3DIR] = S_IFDIR, +	[NF3BLK] = S_IFBLK, +	[NF3CHR] = S_IFCHR, +	[NF3LNK] = S_IFLNK, +	[NF3SOCK] = S_IFSOCK, +	[NF3FIFO] = S_IFIFO,  };  /* @@ -148,13 +144,12 @@ static __be32 *  xdr_decode_fattr(__be32 *p, struct nfs_fattr *fattr)  {  	unsigned int	type, major, minor; -	int		fmode; +	umode_t		fmode;  	type = ntohl(*p++); -	if (type >= NF3BAD) -		type = NF3BAD; -	fmode = nfs_type2fmt[type].mode; -	fattr->type = nfs_type2fmt[type].nfs2type; +	if (type > NF3FIFO) +		type = NF3NON; +	fmode = nfs_type2fmt[type];  	fattr->mode = (ntohl(*p++) & ~S_IFMT) | fmode;  	fattr->nlink = ntohl(*p++);  	fattr->uid = ntohl(*p++); @@ -177,7 +172,7 @@ xdr_decode_fattr(__be32 *p, struct nfs_fattr *fattr)  	p = xdr_decode_time3(p, &fattr->ctime);  	/* Update the mode bits */ -	fattr->valid |= (NFS_ATTR_FATTR | NFS_ATTR_FATTR_V3); +	fattr->valid |= NFS_ATTR_FATTR_V3;  	return p;  } @@ -233,7 +228,9 @@ xdr_decode_wcc_attr(__be32 *p, struct nfs_fattr *fattr)  	p = xdr_decode_hyper(p, &fattr->pre_size);  	p = xdr_decode_time3(p, &fattr->pre_mtime);  	p = xdr_decode_time3(p, &fattr->pre_ctime); -	fattr->valid |= NFS_ATTR_WCC; +	fattr->valid |= NFS_ATTR_FATTR_PRESIZE +		| NFS_ATTR_FATTR_PREMTIME +		| NFS_ATTR_FATTR_PRECTIME;  	return p;  } diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c index 8dde84b988d..a4d24268029 100644 --- a/fs/nfs/nfs4proc.c +++ b/fs/nfs/nfs4proc.c @@ -193,14 +193,6 @@ static void nfs4_setup_readdir(u64 cookie, __be32 *verifier, struct dentry *dent  	kunmap_atomic(start, KM_USER0);  } -static int nfs4_wait_bit_killable(void *word) -{ -	if (fatal_signal_pending(current)) -		return -ERESTARTSYS; -	schedule(); -	return 0; -} -  static int nfs4_wait_clnt_recover(struct nfs_client *clp)  {  	int res; @@ -208,7 +200,7 @@ static int nfs4_wait_clnt_recover(struct nfs_client *clp)  	might_sleep();  	res = wait_on_bit(&clp->cl_state, NFS4CLNT_MANAGER_RUNNING, -			nfs4_wait_bit_killable, TASK_KILLABLE); +			nfs_wait_bit_killable, TASK_KILLABLE);  	return res;  } @@ -1439,7 +1431,7 @@ int nfs4_do_close(struct path *path, struct nfs4_state *state, int wait)  	if (calldata->arg.seqid == NULL)  		goto out_free_calldata;  	calldata->arg.fmode = 0; -	calldata->arg.bitmask = server->attr_bitmask; +	calldata->arg.bitmask = server->cache_consistency_bitmask;  	calldata->res.fattr = &calldata->fattr;  	calldata->res.seqid = calldata->arg.seqid;  	calldata->res.server = server; @@ -1509,7 +1501,7 @@ nfs4_atomic_open(struct inode *dir, struct dentry *dentry, struct nameidata *nd)  		attr.ia_mode = nd->intent.open.create_mode;  		attr.ia_valid = ATTR_MODE;  		if (!IS_POSIXACL(dir)) -			attr.ia_mode &= ~current->fs->umask; +			attr.ia_mode &= ~current_umask();  	} else {  		attr.ia_valid = 0;  		BUG_ON(nd->intent.open.flags & O_CREAT); @@ -1580,6 +1572,15 @@ out_drop:  	return 0;  } +void nfs4_close_context(struct nfs_open_context *ctx, int is_sync) +{ +	if (ctx->state == NULL) +		return; +	if (is_sync) +		nfs4_close_sync(&ctx->path, ctx->state, ctx->mode); +	else +		nfs4_close_state(&ctx->path, ctx->state, ctx->mode); +}  static int _nfs4_server_capabilities(struct nfs_server *server, struct nfs_fh *fhandle)  { @@ -1600,6 +1601,9 @@ static int _nfs4_server_capabilities(struct nfs_server *server, struct nfs_fh *f  			server->caps |= NFS_CAP_HARDLINKS;  		if (res.has_symlinks != 0)  			server->caps |= NFS_CAP_SYMLINKS; +		memcpy(server->cache_consistency_bitmask, res.attr_bitmask, sizeof(server->cache_consistency_bitmask)); +		server->cache_consistency_bitmask[0] &= FATTR4_WORD0_CHANGE|FATTR4_WORD0_SIZE; +		server->cache_consistency_bitmask[1] &= FATTR4_WORD1_TIME_METADATA|FATTR4_WORD1_TIME_MODIFY;  		server->acl_bitmask = res.acl_bitmask;  	}  	return status; @@ -2079,7 +2083,7 @@ static void nfs4_proc_unlink_setup(struct rpc_message *msg, struct inode *dir)  	struct nfs_removeargs *args = msg->rpc_argp;  	struct nfs_removeres *res = msg->rpc_resp; -	args->bitmask = server->attr_bitmask; +	args->bitmask = server->cache_consistency_bitmask;  	res->server = server;  	msg->rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_REMOVE];  } @@ -2323,7 +2327,7 @@ static int _nfs4_proc_readdir(struct dentry *dentry, struct rpc_cred *cred,  		.pages = &page,  		.pgbase = 0,  		.count = count, -		.bitmask = NFS_SERVER(dentry->d_inode)->attr_bitmask, +		.bitmask = NFS_SERVER(dentry->d_inode)->cache_consistency_bitmask,  	};  	struct nfs4_readdir_res res;  	struct rpc_message msg = { @@ -2552,7 +2556,7 @@ static void nfs4_proc_write_setup(struct nfs_write_data *data, struct rpc_messag  {  	struct nfs_server *server = NFS_SERVER(data->inode); -	data->args.bitmask = server->attr_bitmask; +	data->args.bitmask = server->cache_consistency_bitmask;  	data->res.server = server;  	data->timestamp   = jiffies; @@ -2575,7 +2579,7 @@ static void nfs4_proc_commit_setup(struct nfs_write_data *data, struct rpc_messa  {  	struct nfs_server *server = NFS_SERVER(data->inode); -	data->args.bitmask = server->attr_bitmask; +	data->args.bitmask = server->cache_consistency_bitmask;  	data->res.server = server;  	msg->rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_COMMIT];  } @@ -3678,6 +3682,19 @@ ssize_t nfs4_listxattr(struct dentry *dentry, char *buf, size_t buflen)  	return len;  } +static void nfs_fixup_referral_attributes(struct nfs_fattr *fattr) +{ +	if (!((fattr->valid & NFS_ATTR_FATTR_FILEID) && +		(fattr->valid & NFS_ATTR_FATTR_FSID) && +		(fattr->valid & NFS_ATTR_FATTR_V4_REFERRAL))) +		return; + +	fattr->valid |= NFS_ATTR_FATTR_TYPE | NFS_ATTR_FATTR_MODE | +		NFS_ATTR_FATTR_NLINK; +	fattr->mode = S_IFDIR | S_IRUGO | S_IXUGO; +	fattr->nlink = 2; +} +  int nfs4_proc_fs_locations(struct inode *dir, const struct qstr *name,  		struct nfs4_fs_locations *fs_locations, struct page *page)  { @@ -3704,6 +3721,7 @@ int nfs4_proc_fs_locations(struct inode *dir, const struct qstr *name,  	fs_locations->server = server;  	fs_locations->nlocations = 0;  	status = rpc_call_sync(server->client, &msg, 0); +	nfs_fixup_referral_attributes(&fs_locations->fattr);  	dprintk("%s: returned status = %d\n", __func__, status);  	return status;  } @@ -3767,6 +3785,7 @@ const struct nfs_rpc_ops nfs_v4_clientops = {  	.commit_done	= nfs4_commit_done,  	.lock		= nfs4_proc_lock,  	.clear_acl_cache = nfs4_zap_acl_attr, +	.close_context  = nfs4_close_context,  };  /* diff --git a/fs/nfs/nfs4state.c b/fs/nfs/nfs4state.c index 2022fe47966..0298e909559 100644 --- a/fs/nfs/nfs4state.c +++ b/fs/nfs/nfs4state.c @@ -62,8 +62,14 @@ static LIST_HEAD(nfs4_clientid_list);  static int nfs4_init_client(struct nfs_client *clp, struct rpc_cred *cred)  { -	int status = nfs4_proc_setclientid(clp, NFS4_CALLBACK, -			nfs_callback_tcpport, cred); +	unsigned short port; +	int status; + +	port = nfs_callback_tcpport; +	if (clp->cl_addr.ss_family == AF_INET6) +		port = nfs_callback_tcpport6; + +	status = nfs4_proc_setclientid(clp, NFS4_CALLBACK, port, cred);  	if (status == 0)  		status = nfs4_proc_setclientid_confirm(clp, cred);  	if (status == 0) diff --git a/fs/nfs/nfs4xdr.c b/fs/nfs/nfs4xdr.c index d1e4c8f8a0a..1690f0e44b9 100644 --- a/fs/nfs/nfs4xdr.c +++ b/fs/nfs/nfs4xdr.c @@ -522,20 +522,17 @@ static int nfs4_stat_to_errno(int);  				 decode_lookup_maxsz + \  				 decode_fs_locations_maxsz) -static struct { -	unsigned int	mode; -	unsigned int	nfs2type; -} nfs_type2fmt[] = { -	{ 0,		NFNON	     }, -	{ S_IFREG,	NFREG	     }, -	{ S_IFDIR,	NFDIR	     }, -	{ S_IFBLK,	NFBLK	     }, -	{ S_IFCHR,	NFCHR	     }, -	{ S_IFLNK,	NFLNK	     }, -	{ S_IFSOCK,	NFSOCK	     }, -	{ S_IFIFO,	NFFIFO	     }, -	{ 0,		NFNON	     }, -	{ 0,		NFNON	     }, +static const umode_t nfs_type2fmt[] = { +	[NF4BAD] = 0, +	[NF4REG] = S_IFREG, +	[NF4DIR] = S_IFDIR, +	[NF4BLK] = S_IFBLK, +	[NF4CHR] = S_IFCHR, +	[NF4LNK] = S_IFLNK, +	[NF4SOCK] = S_IFSOCK, +	[NF4FIFO] = S_IFIFO, +	[NF4ATTRDIR] = 0, +	[NF4NAMEDATTR] = 0,  };  struct compound_hdr { @@ -2160,6 +2157,7 @@ static int decode_attr_supported(struct xdr_stream *xdr, uint32_t *bitmap, uint3  static int decode_attr_type(struct xdr_stream *xdr, uint32_t *bitmap, uint32_t *type)  {  	__be32 *p; +	int ret = 0;  	*type = 0;  	if (unlikely(bitmap[0] & (FATTR4_WORD0_TYPE - 1U))) @@ -2172,14 +2170,16 @@ static int decode_attr_type(struct xdr_stream *xdr, uint32_t *bitmap, uint32_t *  			return -EIO;  		}  		bitmap[0] &= ~FATTR4_WORD0_TYPE; +		ret = NFS_ATTR_FATTR_TYPE;  	} -	dprintk("%s: type=0%o\n", __func__, nfs_type2fmt[*type].nfs2type); -	return 0; +	dprintk("%s: type=0%o\n", __func__, nfs_type2fmt[*type]); +	return ret;  }  static int decode_attr_change(struct xdr_stream *xdr, uint32_t *bitmap, uint64_t *change)  {  	__be32 *p; +	int ret = 0;  	*change = 0;  	if (unlikely(bitmap[0] & (FATTR4_WORD0_CHANGE - 1U))) @@ -2188,15 +2188,17 @@ static int decode_attr_change(struct xdr_stream *xdr, uint32_t *bitmap, uint64_t  		READ_BUF(8);  		READ64(*change);  		bitmap[0] &= ~FATTR4_WORD0_CHANGE; +		ret = NFS_ATTR_FATTR_CHANGE;  	}  	dprintk("%s: change attribute=%Lu\n", __func__,  			(unsigned long long)*change); -	return 0; +	return ret;  }  static int decode_attr_size(struct xdr_stream *xdr, uint32_t *bitmap, uint64_t *size)  {  	__be32 *p; +	int ret = 0;  	*size = 0;  	if (unlikely(bitmap[0] & (FATTR4_WORD0_SIZE - 1U))) @@ -2205,9 +2207,10 @@ static int decode_attr_size(struct xdr_stream *xdr, uint32_t *bitmap, uint64_t *  		READ_BUF(8);  		READ64(*size);  		bitmap[0] &= ~FATTR4_WORD0_SIZE; +		ret = NFS_ATTR_FATTR_SIZE;  	}  	dprintk("%s: file size=%Lu\n", __func__, (unsigned long long)*size); -	return 0; +	return ret;  }  static int decode_attr_link_support(struct xdr_stream *xdr, uint32_t *bitmap, uint32_t *res) @@ -2245,6 +2248,7 @@ static int decode_attr_symlink_support(struct xdr_stream *xdr, uint32_t *bitmap,  static int decode_attr_fsid(struct xdr_stream *xdr, uint32_t *bitmap, struct nfs_fsid *fsid)  {  	__be32 *p; +	int ret = 0;  	fsid->major = 0;  	fsid->minor = 0; @@ -2255,11 +2259,12 @@ static int decode_attr_fsid(struct xdr_stream *xdr, uint32_t *bitmap, struct nfs  		READ64(fsid->major);  		READ64(fsid->minor);  		bitmap[0] &= ~FATTR4_WORD0_FSID; +		ret = NFS_ATTR_FATTR_FSID;  	}  	dprintk("%s: fsid=(0x%Lx/0x%Lx)\n", __func__,  			(unsigned long long)fsid->major,  			(unsigned long long)fsid->minor); -	return 0; +	return ret;  }  static int decode_attr_lease_time(struct xdr_stream *xdr, uint32_t *bitmap, uint32_t *res) @@ -2297,6 +2302,7 @@ static int decode_attr_aclsupport(struct xdr_stream *xdr, uint32_t *bitmap, uint  static int decode_attr_fileid(struct xdr_stream *xdr, uint32_t *bitmap, uint64_t *fileid)  {  	__be32 *p; +	int ret = 0;  	*fileid = 0;  	if (unlikely(bitmap[0] & (FATTR4_WORD0_FILEID - 1U))) @@ -2305,14 +2311,16 @@ static int decode_attr_fileid(struct xdr_stream *xdr, uint32_t *bitmap, uint64_t  		READ_BUF(8);  		READ64(*fileid);  		bitmap[0] &= ~FATTR4_WORD0_FILEID; +		ret = NFS_ATTR_FATTR_FILEID;  	}  	dprintk("%s: fileid=%Lu\n", __func__, (unsigned long long)*fileid); -	return 0; +	return ret;  }  static int decode_attr_mounted_on_fileid(struct xdr_stream *xdr, uint32_t *bitmap, uint64_t *fileid)  {  	__be32 *p; +	int ret = 0;  	*fileid = 0;  	if (unlikely(bitmap[1] & (FATTR4_WORD1_MOUNTED_ON_FILEID - 1U))) @@ -2321,9 +2329,10 @@ static int decode_attr_mounted_on_fileid(struct xdr_stream *xdr, uint32_t *bitma  		READ_BUF(8);  		READ64(*fileid);  		bitmap[1] &= ~FATTR4_WORD1_MOUNTED_ON_FILEID; +		ret = NFS_ATTR_FATTR_FILEID;  	}  	dprintk("%s: fileid=%Lu\n", __func__, (unsigned long long)*fileid); -	return 0; +	return ret;  }  static int decode_attr_files_avail(struct xdr_stream *xdr, uint32_t *bitmap, uint64_t *res) @@ -2479,6 +2488,8 @@ static int decode_attr_fs_locations(struct xdr_stream *xdr, uint32_t *bitmap, st  		if (res->nlocations < NFS4_FS_LOCATIONS_MAXENTRIES)  			res->nlocations++;  	} +	if (res->nlocations != 0) +		status = NFS_ATTR_FATTR_V4_REFERRAL;  out:  	dprintk("%s: fs_locations done, error = %d\n", __func__, status);  	return status; @@ -2580,26 +2591,30 @@ static int decode_attr_maxwrite(struct xdr_stream *xdr, uint32_t *bitmap, uint32  	return status;  } -static int decode_attr_mode(struct xdr_stream *xdr, uint32_t *bitmap, uint32_t *mode) +static int decode_attr_mode(struct xdr_stream *xdr, uint32_t *bitmap, umode_t *mode)  { +	uint32_t tmp;  	__be32 *p; +	int ret = 0;  	*mode = 0;  	if (unlikely(bitmap[1] & (FATTR4_WORD1_MODE - 1U)))  		return -EIO;  	if (likely(bitmap[1] & FATTR4_WORD1_MODE)) {  		READ_BUF(4); -		READ32(*mode); -		*mode &= ~S_IFMT; +		READ32(tmp); +		*mode = tmp & ~S_IFMT;  		bitmap[1] &= ~FATTR4_WORD1_MODE; +		ret = NFS_ATTR_FATTR_MODE;  	}  	dprintk("%s: file mode=0%o\n", __func__, (unsigned int)*mode); -	return 0; +	return ret;  }  static int decode_attr_nlink(struct xdr_stream *xdr, uint32_t *bitmap, uint32_t *nlink)  {  	__be32 *p; +	int ret = 0;  	*nlink = 1;  	if (unlikely(bitmap[1] & (FATTR4_WORD1_NUMLINKS - 1U))) @@ -2608,15 +2623,17 @@ static int decode_attr_nlink(struct xdr_stream *xdr, uint32_t *bitmap, uint32_t  		READ_BUF(4);  		READ32(*nlink);  		bitmap[1] &= ~FATTR4_WORD1_NUMLINKS; +		ret = NFS_ATTR_FATTR_NLINK;  	}  	dprintk("%s: nlink=%u\n", __func__, (unsigned int)*nlink); -	return 0; +	return ret;  }  static int decode_attr_owner(struct xdr_stream *xdr, uint32_t *bitmap, struct nfs_client *clp, uint32_t *uid)  {  	uint32_t len;  	__be32 *p; +	int ret = 0;  	*uid = -2;  	if (unlikely(bitmap[1] & (FATTR4_WORD1_OWNER - 1U))) @@ -2626,7 +2643,9 @@ static int decode_attr_owner(struct xdr_stream *xdr, uint32_t *bitmap, struct nf  		READ32(len);  		READ_BUF(len);  		if (len < XDR_MAX_NETOBJ) { -			if (nfs_map_name_to_uid(clp, (char *)p, len, uid) != 0) +			if (nfs_map_name_to_uid(clp, (char *)p, len, uid) == 0) +				ret = NFS_ATTR_FATTR_OWNER; +			else  				dprintk("%s: nfs_map_name_to_uid failed!\n",  						__func__);  		} else @@ -2635,13 +2654,14 @@ static int decode_attr_owner(struct xdr_stream *xdr, uint32_t *bitmap, struct nf  		bitmap[1] &= ~FATTR4_WORD1_OWNER;  	}  	dprintk("%s: uid=%d\n", __func__, (int)*uid); -	return 0; +	return ret;  }  static int decode_attr_group(struct xdr_stream *xdr, uint32_t *bitmap, struct nfs_client *clp, uint32_t *gid)  {  	uint32_t len;  	__be32 *p; +	int ret = 0;  	*gid = -2;  	if (unlikely(bitmap[1] & (FATTR4_WORD1_OWNER_GROUP - 1U))) @@ -2651,7 +2671,9 @@ static int decode_attr_group(struct xdr_stream *xdr, uint32_t *bitmap, struct nf  		READ32(len);  		READ_BUF(len);  		if (len < XDR_MAX_NETOBJ) { -			if (nfs_map_group_to_gid(clp, (char *)p, len, gid) != 0) +			if (nfs_map_group_to_gid(clp, (char *)p, len, gid) == 0) +				ret = NFS_ATTR_FATTR_GROUP; +			else  				dprintk("%s: nfs_map_group_to_gid failed!\n",  						__func__);  		} else @@ -2660,13 +2682,14 @@ static int decode_attr_group(struct xdr_stream *xdr, uint32_t *bitmap, struct nf  		bitmap[1] &= ~FATTR4_WORD1_OWNER_GROUP;  	}  	dprintk("%s: gid=%d\n", __func__, (int)*gid); -	return 0; +	return ret;  }  static int decode_attr_rdev(struct xdr_stream *xdr, uint32_t *bitmap, dev_t *rdev)  {  	uint32_t major = 0, minor = 0;  	__be32 *p; +	int ret = 0;  	*rdev = MKDEV(0,0);  	if (unlikely(bitmap[1] & (FATTR4_WORD1_RAWDEV - 1U))) @@ -2681,9 +2704,10 @@ static int decode_attr_rdev(struct xdr_stream *xdr, uint32_t *bitmap, dev_t *rde  		if (MAJOR(tmp) == major && MINOR(tmp) == minor)  			*rdev = tmp;  		bitmap[1] &= ~ FATTR4_WORD1_RAWDEV; +		ret = NFS_ATTR_FATTR_RDEV;  	}  	dprintk("%s: rdev=(0x%x:0x%x)\n", __func__, major, minor); -	return 0; +	return ret;  }  static int decode_attr_space_avail(struct xdr_stream *xdr, uint32_t *bitmap, uint64_t *res) @@ -2740,6 +2764,7 @@ static int decode_attr_space_total(struct xdr_stream *xdr, uint32_t *bitmap, uin  static int decode_attr_space_used(struct xdr_stream *xdr, uint32_t *bitmap, uint64_t *used)  {  	__be32 *p; +	int ret = 0;  	*used = 0;  	if (unlikely(bitmap[1] & (FATTR4_WORD1_SPACE_USED - 1U))) @@ -2748,10 +2773,11 @@ static int decode_attr_space_used(struct xdr_stream *xdr, uint32_t *bitmap, uint  		READ_BUF(8);  		READ64(*used);  		bitmap[1] &= ~FATTR4_WORD1_SPACE_USED; +		ret = NFS_ATTR_FATTR_SPACE_USED;  	}  	dprintk("%s: space used=%Lu\n", __func__,  			(unsigned long long)*used); -	return 0; +	return ret;  }  static int decode_attr_time(struct xdr_stream *xdr, struct timespec *time) @@ -2778,6 +2804,8 @@ static int decode_attr_time_access(struct xdr_stream *xdr, uint32_t *bitmap, str  		return -EIO;  	if (likely(bitmap[1] & FATTR4_WORD1_TIME_ACCESS)) {  		status = decode_attr_time(xdr, time); +		if (status == 0) +			status = NFS_ATTR_FATTR_ATIME;  		bitmap[1] &= ~FATTR4_WORD1_TIME_ACCESS;  	}  	dprintk("%s: atime=%ld\n", __func__, (long)time->tv_sec); @@ -2794,6 +2822,8 @@ static int decode_attr_time_metadata(struct xdr_stream *xdr, uint32_t *bitmap, s  		return -EIO;  	if (likely(bitmap[1] & FATTR4_WORD1_TIME_METADATA)) {  		status = decode_attr_time(xdr, time); +		if (status == 0) +			status = NFS_ATTR_FATTR_CTIME;  		bitmap[1] &= ~FATTR4_WORD1_TIME_METADATA;  	}  	dprintk("%s: ctime=%ld\n", __func__, (long)time->tv_sec); @@ -2810,6 +2840,8 @@ static int decode_attr_time_modify(struct xdr_stream *xdr, uint32_t *bitmap, str  		return -EIO;  	if (likely(bitmap[1] & FATTR4_WORD1_TIME_MODIFY)) {  		status = decode_attr_time(xdr, time); +		if (status == 0) +			status = NFS_ATTR_FATTR_MTIME;  		bitmap[1] &= ~FATTR4_WORD1_TIME_MODIFY;  	}  	dprintk("%s: mtime=%ld\n", __func__, (long)time->tv_sec); @@ -2994,63 +3026,116 @@ static int decode_getfattr(struct xdr_stream *xdr, struct nfs_fattr *fattr, cons  	uint32_t attrlen,  		 bitmap[2] = {0},  		 type; -	int status, fmode = 0; +	int status; +	umode_t fmode = 0;  	uint64_t fileid; -	if ((status = decode_op_hdr(xdr, OP_GETATTR)) != 0) -		goto xdr_error; -	if ((status = decode_attr_bitmap(xdr, bitmap)) != 0) +	status = decode_op_hdr(xdr, OP_GETATTR); +	if (status < 0)  		goto xdr_error; -	fattr->bitmap[0] = bitmap[0]; -	fattr->bitmap[1] = bitmap[1]; +	status = decode_attr_bitmap(xdr, bitmap); +	if (status < 0) +		goto xdr_error; -	if ((status = decode_attr_length(xdr, &attrlen, &savep)) != 0) +	status = decode_attr_length(xdr, &attrlen, &savep); +	if (status < 0)  		goto xdr_error; -	if ((status = decode_attr_type(xdr, bitmap, &type)) != 0) +	status = decode_attr_type(xdr, bitmap, &type); +	if (status < 0)  		goto xdr_error; -	fattr->type = nfs_type2fmt[type].nfs2type; -	fmode = nfs_type2fmt[type].mode; +	fattr->mode = 0; +	if (status != 0) { +		fattr->mode |= nfs_type2fmt[type]; +		fattr->valid |= status; +	} -	if ((status = decode_attr_change(xdr, bitmap, &fattr->change_attr)) != 0) +	status = decode_attr_change(xdr, bitmap, &fattr->change_attr); +	if (status < 0)  		goto xdr_error; -	if ((status = decode_attr_size(xdr, bitmap, &fattr->size)) != 0) +	fattr->valid |= status; + +	status = decode_attr_size(xdr, bitmap, &fattr->size); +	if (status < 0)  		goto xdr_error; -	if ((status = decode_attr_fsid(xdr, bitmap, &fattr->fsid)) != 0) +	fattr->valid |= status; + +	status = decode_attr_fsid(xdr, bitmap, &fattr->fsid); +	if (status < 0)  		goto xdr_error; -	if ((status = decode_attr_fileid(xdr, bitmap, &fattr->fileid)) != 0) +	fattr->valid |= status; + +	status = decode_attr_fileid(xdr, bitmap, &fattr->fileid); +	if (status < 0)  		goto xdr_error; -	if ((status = decode_attr_fs_locations(xdr, bitmap, container_of(fattr, +	fattr->valid |= status; + +	status = decode_attr_fs_locations(xdr, bitmap, container_of(fattr,  						struct nfs4_fs_locations, -						fattr))) != 0) +						fattr)); +	if (status < 0)  		goto xdr_error; -	if ((status = decode_attr_mode(xdr, bitmap, &fattr->mode)) != 0) +	fattr->valid |= status; + +	status = decode_attr_mode(xdr, bitmap, &fmode); +	if (status < 0)  		goto xdr_error; -	fattr->mode |= fmode; -	if ((status = decode_attr_nlink(xdr, bitmap, &fattr->nlink)) != 0) +	if (status != 0) { +		fattr->mode |= fmode; +		fattr->valid |= status; +	} + +	status = decode_attr_nlink(xdr, bitmap, &fattr->nlink); +	if (status < 0)  		goto xdr_error; -	if ((status = decode_attr_owner(xdr, bitmap, server->nfs_client, &fattr->uid)) != 0) +	fattr->valid |= status; + +	status = decode_attr_owner(xdr, bitmap, server->nfs_client, &fattr->uid); +	if (status < 0)  		goto xdr_error; -	if ((status = decode_attr_group(xdr, bitmap, server->nfs_client, &fattr->gid)) != 0) +	fattr->valid |= status; + +	status = decode_attr_group(xdr, bitmap, server->nfs_client, &fattr->gid); +	if (status < 0)  		goto xdr_error; -	if ((status = decode_attr_rdev(xdr, bitmap, &fattr->rdev)) != 0) +	fattr->valid |= status; + +	status = decode_attr_rdev(xdr, bitmap, &fattr->rdev); +	if (status < 0)  		goto xdr_error; -	if ((status = decode_attr_space_used(xdr, bitmap, &fattr->du.nfs3.used)) != 0) +	fattr->valid |= status; + +	status = decode_attr_space_used(xdr, bitmap, &fattr->du.nfs3.used); +	if (status < 0)  		goto xdr_error; -	if ((status = decode_attr_time_access(xdr, bitmap, &fattr->atime)) != 0) +	fattr->valid |= status; + +	status = decode_attr_time_access(xdr, bitmap, &fattr->atime); +	if (status < 0)  		goto xdr_error; -	if ((status = decode_attr_time_metadata(xdr, bitmap, &fattr->ctime)) != 0) +	fattr->valid |= status; + +	status = decode_attr_time_metadata(xdr, bitmap, &fattr->ctime); +	if (status < 0)  		goto xdr_error; -	if ((status = decode_attr_time_modify(xdr, bitmap, &fattr->mtime)) != 0) +	fattr->valid |= status; + +	status = decode_attr_time_modify(xdr, bitmap, &fattr->mtime); +	if (status < 0)  		goto xdr_error; -	if ((status = decode_attr_mounted_on_fileid(xdr, bitmap, &fileid)) != 0) +	fattr->valid |= status; + +	status = decode_attr_mounted_on_fileid(xdr, bitmap, &fileid); +	if (status < 0)  		goto xdr_error; -	if (fattr->fileid == 0 && fileid != 0) +	if (status != 0 && !(fattr->valid & status)) {  		fattr->fileid = fileid; -	if ((status = verify_attr_len(xdr, savep, attrlen)) == 0) -		fattr->valid = NFS_ATTR_FATTR | NFS_ATTR_FATTR_V3 | NFS_ATTR_FATTR_V4; +		fattr->valid |= status; +	} + +	status = verify_attr_len(xdr, savep, attrlen);  xdr_error:  	dprintk("%s: xdr returned %d\n", __func__, -status);  	return status; @@ -4078,9 +4163,7 @@ static int nfs4_xdr_dec_setattr(struct rpc_rqst *rqstp, __be32 *p, struct nfs_se  	status = decode_setattr(&xdr, res);  	if (status)  		goto out; -	status = decode_getfattr(&xdr, res->fattr, res->server); -	if (status == NFS4ERR_DELAY) -		status = 0; +	decode_getfattr(&xdr, res->fattr, res->server);  out:  	return status;  } diff --git a/fs/nfs/pagelist.c b/fs/nfs/pagelist.c index 7f079209d70..e2975939126 100644 --- a/fs/nfs/pagelist.c +++ b/fs/nfs/pagelist.c @@ -176,17 +176,6 @@ void nfs_release_request(struct nfs_page *req)  	kref_put(&req->wb_kref, nfs_free_request);  } -static int nfs_wait_bit_killable(void *word) -{ -	int ret = 0; - -	if (fatal_signal_pending(current)) -		ret = -ERESTARTSYS; -	else -		schedule(); -	return ret; -} -  /**   * nfs_wait_on_request - Wait for a request to complete.   * @req: request to wait upon. diff --git a/fs/nfs/proc.c b/fs/nfs/proc.c index 193465210d7..7be72d90d49 100644 --- a/fs/nfs/proc.c +++ b/fs/nfs/proc.c @@ -663,4 +663,5 @@ const struct nfs_rpc_ops nfs_v2_clientops = {  	.commit_setup	= nfs_proc_commit_setup,  	.lock		= nfs_proc_lock,  	.lock_check_bounds = nfs_lock_check_bounds, +	.close_context	= nfs_close_context,  }; diff --git a/fs/nfs/read.c b/fs/nfs/read.c index f856004bb7f..4ace3c50a8e 100644 --- a/fs/nfs/read.c +++ b/fs/nfs/read.c @@ -24,6 +24,7 @@  #include "internal.h"  #include "iostat.h" +#include "fscache.h"  #define NFSDBG_FACILITY		NFSDBG_PAGECACHE @@ -111,8 +112,8 @@ static void nfs_readpage_truncate_uninitialised_page(struct nfs_read_data *data)  	}  } -static int nfs_readpage_async(struct nfs_open_context *ctx, struct inode *inode, -		struct page *page) +int nfs_readpage_async(struct nfs_open_context *ctx, struct inode *inode, +		       struct page *page)  {  	LIST_HEAD(one_request);  	struct nfs_page	*new; @@ -139,6 +140,11 @@ static int nfs_readpage_async(struct nfs_open_context *ctx, struct inode *inode,  static void nfs_readpage_release(struct nfs_page *req)  { +	struct inode *d_inode = req->wb_context->path.dentry->d_inode; + +	if (PageUptodate(req->wb_page)) +		nfs_readpage_to_fscache(d_inode, req->wb_page, 0); +  	unlock_page(req->wb_page);  	dprintk("NFS: read done (%s/%Ld %d@%Ld)\n", @@ -510,8 +516,15 @@ int nfs_readpage(struct file *file, struct page *page)  	} else  		ctx = get_nfs_open_context(nfs_file_open_context(file)); +	if (!IS_SYNC(inode)) { +		error = nfs_readpage_from_fscache(ctx, inode, page); +		if (error == 0) +			goto out; +	} +  	error = nfs_readpage_async(ctx, inode, page); +out:  	put_nfs_open_context(ctx);  	return error;  out_unlock: @@ -584,6 +597,15 @@ int nfs_readpages(struct file *filp, struct address_space *mapping,  			return -EBADF;  	} else  		desc.ctx = get_nfs_open_context(nfs_file_open_context(filp)); + +	/* attempt to read as many of the pages as possible from the cache +	 * - this returns -ENOBUFS immediately if the cookie is negative +	 */ +	ret = nfs_readpages_from_fscache(desc.ctx, inode, mapping, +					 pages, &nr_pages); +	if (ret == 0) +		goto read_complete; /* all pages were read */ +  	if (rsize < PAGE_CACHE_SIZE)  		nfs_pageio_init(&pgio, inode, nfs_pagein_multi, rsize, 0);  	else @@ -594,6 +616,7 @@ int nfs_readpages(struct file *filp, struct address_space *mapping,  	nfs_pageio_complete(&pgio);  	npages = (pgio.pg_bytes_written + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT;  	nfs_add_stats(inode, NFSIOS_READPAGES, npages); +read_complete:  	put_nfs_open_context(desc.ctx);  out:  	return ret; diff --git a/fs/nfs/super.c b/fs/nfs/super.c index d6686f4786d..82eaadbff40 100644 --- a/fs/nfs/super.c +++ b/fs/nfs/super.c @@ -60,6 +60,7 @@  #include "delegation.h"  #include "iostat.h"  #include "internal.h" +#include "fscache.h"  #define NFSDBG_FACILITY		NFSDBG_VFS @@ -76,6 +77,7 @@ enum {  	Opt_rdirplus, Opt_nordirplus,  	Opt_sharecache, Opt_nosharecache,  	Opt_resvport, Opt_noresvport, +	Opt_fscache, Opt_nofscache,  	/* Mount options that take integer arguments */  	Opt_port, @@ -93,6 +95,7 @@ enum {  	Opt_sec, Opt_proto, Opt_mountproto, Opt_mounthost,  	Opt_addr, Opt_mountaddr, Opt_clientaddr,  	Opt_lookupcache, +	Opt_fscache_uniq,  	/* Special mount options */  	Opt_userspace, Opt_deprecated, Opt_sloppy, @@ -132,6 +135,9 @@ static const match_table_t nfs_mount_option_tokens = {  	{ Opt_nosharecache, "nosharecache" },  	{ Opt_resvport, "resvport" },  	{ Opt_noresvport, "noresvport" }, +	{ Opt_fscache, "fsc" }, +	{ Opt_fscache_uniq, "fsc=%s" }, +	{ Opt_nofscache, "nofsc" },  	{ Opt_port, "port=%u" },  	{ Opt_rsize, "rsize=%u" }, @@ -563,6 +569,8 @@ static void nfs_show_mount_options(struct seq_file *m, struct nfs_server *nfss,  	if (clp->rpc_ops->version == 4)  		seq_printf(m, ",clientaddr=%s", clp->cl_ipaddr);  #endif +	if (nfss->options & NFS_OPTION_FSCACHE) +		seq_printf(m, ",fsc");  }  /* @@ -641,6 +649,10 @@ static int nfs_show_stats(struct seq_file *m, struct vfsmount *mnt)  			totals.events[i] += stats->events[i];  		for (i = 0; i < __NFSIOS_BYTESMAX; i++)  			totals.bytes[i] += stats->bytes[i]; +#ifdef CONFIG_NFS_FSCACHE +		for (i = 0; i < __NFSIOS_FSCACHEMAX; i++) +			totals.fscache[i] += stats->fscache[i]; +#endif  		preempt_enable();  	} @@ -651,6 +663,13 @@ static int nfs_show_stats(struct seq_file *m, struct vfsmount *mnt)  	seq_printf(m, "\n\tbytes:\t");  	for (i = 0; i < __NFSIOS_BYTESMAX; i++)  		seq_printf(m, "%Lu ", totals.bytes[i]); +#ifdef CONFIG_NFS_FSCACHE +	if (nfss->options & NFS_OPTION_FSCACHE) { +		seq_printf(m, "\n\tfsc:\t"); +		for (i = 0; i < __NFSIOS_FSCACHEMAX; i++) +			seq_printf(m, "%Lu ", totals.bytes[i]); +	} +#endif  	seq_printf(m, "\n");  	rpc_print_iostats(m, nfss->client); @@ -1018,6 +1037,7 @@ static int nfs_parse_mount_options(char *raw,  		case Opt_rdma:  			mnt->flags |= NFS_MOUNT_TCP; /* for side protocols */  			mnt->nfs_server.protocol = XPRT_TRANSPORT_RDMA; +			xprt_load_transport(p);  			break;  		case Opt_acl:  			mnt->flags &= ~NFS_MOUNT_NOACL; @@ -1043,6 +1063,24 @@ static int nfs_parse_mount_options(char *raw,  		case Opt_noresvport:  			mnt->flags |= NFS_MOUNT_NORESVPORT;  			break; +		case Opt_fscache: +			mnt->options |= NFS_OPTION_FSCACHE; +			kfree(mnt->fscache_uniq); +			mnt->fscache_uniq = NULL; +			break; +		case Opt_nofscache: +			mnt->options &= ~NFS_OPTION_FSCACHE; +			kfree(mnt->fscache_uniq); +			mnt->fscache_uniq = NULL; +			break; +		case Opt_fscache_uniq: +			string = match_strdup(args); +			if (!string) +				goto out_nomem; +			kfree(mnt->fscache_uniq); +			mnt->fscache_uniq = string; +			mnt->options |= NFS_OPTION_FSCACHE; +			break;  		/*  		 * options that take numeric values @@ -1205,12 +1243,14 @@ static int nfs_parse_mount_options(char *raw,  				/* vector side protocols to TCP */  				mnt->flags |= NFS_MOUNT_TCP;  				mnt->nfs_server.protocol = XPRT_TRANSPORT_RDMA; +				xprt_load_transport(string);  				break;  			default:  				errors++;  				dfprintk(MOUNT, "NFS:   unrecognized "  						"transport protocol\n");  			} +			kfree(string);  			break;  		case Opt_mountproto:  			string = match_strdup(args); @@ -1218,7 +1258,6 @@ static int nfs_parse_mount_options(char *raw,  				goto out_nomem;  			token = match_token(string,  					    nfs_xprt_protocol_tokens, args); -			kfree(string);  			switch (token) {  			case Opt_xprt_udp: @@ -1868,8 +1907,6 @@ static void nfs_clone_super(struct super_block *sb,   	nfs_initialise_sb(sb);  } -#define NFS_MS_MASK (MS_RDONLY|MS_NOSUID|MS_NODEV|MS_NOEXEC|MS_SYNCHRONOUS) -  static int nfs_compare_mount_options(const struct super_block *s, const struct nfs_server *b, int flags)  {  	const struct nfs_server *a = s->s_fs_info; @@ -2034,6 +2071,7 @@ static int nfs_get_sb(struct file_system_type *fs_type,  	if (!s->s_root) {  		/* initial superblock/root creation */  		nfs_fill_super(s, data); +		nfs_fscache_get_super_cookie(s, data);  	}  	mntroot = nfs_get_root(s, mntfh); @@ -2054,6 +2092,7 @@ static int nfs_get_sb(struct file_system_type *fs_type,  out:  	kfree(data->nfs_server.hostname);  	kfree(data->mount_server.hostname); +	kfree(data->fscache_uniq);  	security_free_mnt_opts(&data->lsm_opts);  out_free_fh:  	kfree(mntfh); @@ -2081,6 +2120,7 @@ static void nfs_kill_super(struct super_block *s)  	bdi_unregister(&server->backing_dev_info);  	kill_anon_super(s); +	nfs_fscache_release_super_cookie(s);  	nfs_free_server(server);  } @@ -2388,6 +2428,7 @@ static int nfs4_get_sb(struct file_system_type *fs_type,  	if (!s->s_root) {  		/* initial superblock/root creation */  		nfs4_fill_super(s); +		nfs_fscache_get_super_cookie(s, data);  	}  	mntroot = nfs4_get_root(s, mntfh); @@ -2409,6 +2450,7 @@ out:  	kfree(data->client_address);  	kfree(data->nfs_server.export_path);  	kfree(data->nfs_server.hostname); +	kfree(data->fscache_uniq);  	security_free_mnt_opts(&data->lsm_opts);  out_free_fh:  	kfree(mntfh); @@ -2435,6 +2477,7 @@ static void nfs4_kill_super(struct super_block *sb)  	kill_anon_super(sb);  	nfs4_renewd_prepare_shutdown(server); +	nfs_fscache_release_super_cookie(sb);  	nfs_free_server(server);  } diff --git a/fs/nfs/write.c b/fs/nfs/write.c index 9f9845859fc..e560a78995a 100644 --- a/fs/nfs/write.c +++ b/fs/nfs/write.c @@ -313,19 +313,34 @@ static int nfs_writepages_callback(struct page *page, struct writeback_control *  int nfs_writepages(struct address_space *mapping, struct writeback_control *wbc)  {  	struct inode *inode = mapping->host; +	unsigned long *bitlock = &NFS_I(inode)->flags;  	struct nfs_pageio_descriptor pgio;  	int err; +	/* Stop dirtying of new pages while we sync */ +	err = wait_on_bit_lock(bitlock, NFS_INO_FLUSHING, +			nfs_wait_bit_killable, TASK_KILLABLE); +	if (err) +		goto out_err; +  	nfs_inc_stats(inode, NFSIOS_VFSWRITEPAGES);  	nfs_pageio_init_write(&pgio, inode, wb_priority(wbc));  	err = write_cache_pages(mapping, wbc, nfs_writepages_callback, &pgio);  	nfs_pageio_complete(&pgio); + +	clear_bit_unlock(NFS_INO_FLUSHING, bitlock); +	smp_mb__after_clear_bit(); +	wake_up_bit(bitlock, NFS_INO_FLUSHING); +  	if (err < 0) -		return err; -	if (pgio.pg_error < 0) -		return pgio.pg_error; +		goto out_err; +	err = pgio.pg_error; +	if (err < 0) +		goto out_err;  	return 0; +out_err: +	return err;  }  /* @@ -404,7 +419,6 @@ nfs_mark_request_commit(struct nfs_page *req)  	struct nfs_inode *nfsi = NFS_I(inode);  	spin_lock(&inode->i_lock); -	nfsi->ncommit++;  	set_bit(PG_CLEAN, &(req)->wb_flags);  	radix_tree_tag_set(&nfsi->nfs_page_tree,  			req->wb_index, @@ -524,6 +538,12 @@ static void nfs_cancel_commit_list(struct list_head *head)  }  #if defined(CONFIG_NFS_V3) || defined(CONFIG_NFS_V4) +static int +nfs_need_commit(struct nfs_inode *nfsi) +{ +	return radix_tree_tagged(&nfsi->nfs_page_tree, NFS_PAGE_TAG_COMMIT); +} +  /*   * nfs_scan_commit - Scan an inode for commit requests   * @inode: NFS inode to scan @@ -538,16 +558,18 @@ static int  nfs_scan_commit(struct inode *inode, struct list_head *dst, pgoff_t idx_start, unsigned int npages)  {  	struct nfs_inode *nfsi = NFS_I(inode); -	int res = 0; -	if (nfsi->ncommit != 0) { -		res = nfs_scan_list(nfsi, dst, idx_start, npages, -				NFS_PAGE_TAG_COMMIT); -		nfsi->ncommit -= res; -	} -	return res; +	if (!nfs_need_commit(nfsi)) +		return 0; + +	return nfs_scan_list(nfsi, dst, idx_start, npages, NFS_PAGE_TAG_COMMIT);  }  #else +static inline int nfs_need_commit(struct nfs_inode *nfsi) +{ +	return 0; +} +  static inline int nfs_scan_commit(struct inode *inode, struct list_head *dst, pgoff_t idx_start, unsigned int npages)  {  	return 0; @@ -820,7 +842,7 @@ static int nfs_write_rpcsetup(struct nfs_page *req,  	data->args.stable  = NFS_UNSTABLE;  	if (how & FLUSH_STABLE) {  		data->args.stable = NFS_DATA_SYNC; -		if (!NFS_I(inode)->ncommit) +		if (!nfs_need_commit(NFS_I(inode)))  			data->args.stable = NFS_FILE_SYNC;  	} @@ -1425,18 +1447,13 @@ static int nfs_write_mapping(struct address_space *mapping, int how)  {  	struct writeback_control wbc = {  		.bdi = mapping->backing_dev_info, -		.sync_mode = WB_SYNC_NONE, +		.sync_mode = WB_SYNC_ALL,  		.nr_to_write = LONG_MAX,  		.range_start = 0,  		.range_end = LLONG_MAX,  		.for_writepages = 1,  	}; -	int ret; -	ret = __nfs_write_mapping(mapping, &wbc, how); -	if (ret < 0) -		return ret; -	wbc.sync_mode = WB_SYNC_ALL;  	return __nfs_write_mapping(mapping, &wbc, how);  }  |