Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ec71feae authored by Linus Torvalds's avatar Linus Torvalds
Browse files
Pull NFS client fixes from Trond Myklebust:
 "Highlights include:

   - Stable fix for a data corruption case due to incorrect cache
     validation
   - Fix a couple of false positive cache invalidations
   - Fix NFSv4 security negotiation issues"

* tag 'nfs-for-3.16-2' of git://git.linux-nfs.org/projects/trondmy/linux-nfs:
  NFSv4: test SECINFO RPC_AUTH_GSS pseudoflavors for support
  NFS Return -EPERM if no supported or matching SECINFO flavor
  NFS check the return of nfs4_negotiate_security in nfs4_submount
  NFS: Don't mark the data cache as invalid if it has been flushed
  NFS: Clear NFS_INO_REVAL_PAGECACHE when we update the file size
  nfs: Fix cache_validity check in nfs_write_pageuptodate()
parents 456febd2 66b06860
Loading
Loading
Loading
Loading
+41 −35
Original line number Diff line number Diff line
@@ -147,6 +147,17 @@ int nfs_sync_mapping(struct address_space *mapping)
	return ret;
}

static void nfs_set_cache_invalid(struct inode *inode, unsigned long flags)
{
	struct nfs_inode *nfsi = NFS_I(inode);

	if (inode->i_mapping->nrpages == 0)
		flags &= ~NFS_INO_INVALID_DATA;
	nfsi->cache_validity |= flags;
	if (flags & NFS_INO_INVALID_DATA)
		nfs_fscache_invalidate(inode);
}

/*
 * Invalidate the local caches
 */
@@ -162,17 +173,16 @@ static void nfs_zap_caches_locked(struct inode *inode)

	memset(NFS_I(inode)->cookieverf, 0, sizeof(NFS_I(inode)->cookieverf));
	if (S_ISREG(mode) || S_ISDIR(mode) || S_ISLNK(mode)) {
		nfs_fscache_invalidate(inode);
		nfsi->cache_validity |= NFS_INO_INVALID_ATTR
		nfs_set_cache_invalid(inode, NFS_INO_INVALID_ATTR
					| NFS_INO_INVALID_DATA
					| NFS_INO_INVALID_ACCESS
					| NFS_INO_INVALID_ACL
					| NFS_INO_REVAL_PAGECACHE;
					| NFS_INO_REVAL_PAGECACHE);
	} else
		nfsi->cache_validity |= NFS_INO_INVALID_ATTR
		nfs_set_cache_invalid(inode, NFS_INO_INVALID_ATTR
					| NFS_INO_INVALID_ACCESS
					| NFS_INO_INVALID_ACL
					| NFS_INO_REVAL_PAGECACHE;
					| NFS_INO_REVAL_PAGECACHE);
	nfs_zap_label_cache_locked(nfsi);
}

@@ -187,8 +197,7 @@ void nfs_zap_mapping(struct inode *inode, struct address_space *mapping)
{
	if (mapping->nrpages != 0) {
		spin_lock(&inode->i_lock);
		NFS_I(inode)->cache_validity |= NFS_INO_INVALID_DATA;
		nfs_fscache_invalidate(inode);
		nfs_set_cache_invalid(inode, NFS_INO_INVALID_DATA);
		spin_unlock(&inode->i_lock);
	}
}
@@ -209,7 +218,7 @@ EXPORT_SYMBOL_GPL(nfs_zap_acl_cache);
void nfs_invalidate_atime(struct inode *inode)
{
	spin_lock(&inode->i_lock);
	NFS_I(inode)->cache_validity |= NFS_INO_INVALID_ATIME;
	nfs_set_cache_invalid(inode, NFS_INO_INVALID_ATIME);
	spin_unlock(&inode->i_lock);
}
EXPORT_SYMBOL_GPL(nfs_invalidate_atime);
@@ -369,7 +378,7 @@ nfs_fhget(struct super_block *sb, struct nfs_fh *fh, struct nfs_fattr *fattr, st
		inode->i_mode = fattr->mode;
		if ((fattr->valid & NFS_ATTR_FATTR_MODE) == 0
				&& nfs_server_capable(inode, NFS_CAP_MODE))
			nfsi->cache_validity |= NFS_INO_INVALID_ATTR;
			nfs_set_cache_invalid(inode, NFS_INO_INVALID_ATTR);
		/* Why so? Because we want revalidate for devices/FIFOs, and
		 * that's precisely what we have in nfs_file_inode_operations.
		 */
@@ -415,36 +424,36 @@ nfs_fhget(struct super_block *sb, struct nfs_fh *fh, struct nfs_fattr *fattr, st
		if (fattr->valid & NFS_ATTR_FATTR_ATIME)
			inode->i_atime = fattr->atime;
		else if (nfs_server_capable(inode, NFS_CAP_ATIME))
			nfsi->cache_validity |= NFS_INO_INVALID_ATTR;
			nfs_set_cache_invalid(inode, NFS_INO_INVALID_ATTR);
		if (fattr->valid & NFS_ATTR_FATTR_MTIME)
			inode->i_mtime = fattr->mtime;
		else if (nfs_server_capable(inode, NFS_CAP_MTIME))
			nfsi->cache_validity |= NFS_INO_INVALID_ATTR;
			nfs_set_cache_invalid(inode, NFS_INO_INVALID_ATTR);
		if (fattr->valid & NFS_ATTR_FATTR_CTIME)
			inode->i_ctime = fattr->ctime;
		else if (nfs_server_capable(inode, NFS_CAP_CTIME))
			nfsi->cache_validity |= NFS_INO_INVALID_ATTR;
			nfs_set_cache_invalid(inode, NFS_INO_INVALID_ATTR);
		if (fattr->valid & NFS_ATTR_FATTR_CHANGE)
			inode->i_version = fattr->change_attr;
		else if (nfs_server_capable(inode, NFS_CAP_CHANGE_ATTR))
			nfsi->cache_validity |= NFS_INO_INVALID_ATTR;
			nfs_set_cache_invalid(inode, NFS_INO_INVALID_ATTR);
		if (fattr->valid & NFS_ATTR_FATTR_SIZE)
			inode->i_size = nfs_size_to_loff_t(fattr->size);
		else
			nfsi->cache_validity |= NFS_INO_INVALID_ATTR
				| NFS_INO_REVAL_PAGECACHE;
			nfs_set_cache_invalid(inode, NFS_INO_INVALID_ATTR
				| NFS_INO_REVAL_PAGECACHE);
		if (fattr->valid & NFS_ATTR_FATTR_NLINK)
			set_nlink(inode, fattr->nlink);
		else if (nfs_server_capable(inode, NFS_CAP_NLINK))
			nfsi->cache_validity |= NFS_INO_INVALID_ATTR;
			nfs_set_cache_invalid(inode, NFS_INO_INVALID_ATTR);
		if (fattr->valid & NFS_ATTR_FATTR_OWNER)
			inode->i_uid = fattr->uid;
		else if (nfs_server_capable(inode, NFS_CAP_OWNER))
			nfsi->cache_validity |= NFS_INO_INVALID_ATTR;
			nfs_set_cache_invalid(inode, NFS_INO_INVALID_ATTR);
		if (fattr->valid & NFS_ATTR_FATTR_GROUP)
			inode->i_gid = fattr->gid;
		else if (nfs_server_capable(inode, NFS_CAP_OWNER_GROUP))
			nfsi->cache_validity |= NFS_INO_INVALID_ATTR;
			nfs_set_cache_invalid(inode, NFS_INO_INVALID_ATTR);
		if (fattr->valid & NFS_ATTR_FATTR_BLOCKS_USED)
			inode->i_blocks = fattr->du.nfs2.blocks;
		if (fattr->valid & NFS_ATTR_FATTR_SPACE_USED) {
@@ -550,6 +559,9 @@ static int nfs_vmtruncate(struct inode * inode, loff_t offset)

	spin_lock(&inode->i_lock);
	i_size_write(inode, offset);
	/* Optimisation */
	if (offset == 0)
		NFS_I(inode)->cache_validity &= ~NFS_INO_INVALID_DATA;
	spin_unlock(&inode->i_lock);

	truncate_pagecache(inode, offset);
@@ -578,7 +590,8 @@ void nfs_setattr_update_inode(struct inode *inode, struct iattr *attr)
			inode->i_uid = attr->ia_uid;
		if ((attr->ia_valid & ATTR_GID) != 0)
			inode->i_gid = attr->ia_gid;
		NFS_I(inode)->cache_validity |= NFS_INO_INVALID_ACCESS|NFS_INO_INVALID_ACL;
		nfs_set_cache_invalid(inode, NFS_INO_INVALID_ACCESS
				| NFS_INO_INVALID_ACL);
		spin_unlock(&inode->i_lock);
	}
	if ((attr->ia_valid & ATTR_SIZE) != 0) {
@@ -1101,7 +1114,7 @@ static unsigned long nfs_wcc_update_inode(struct inode *inode, struct nfs_fattr
			&& inode->i_version == fattr->pre_change_attr) {
		inode->i_version = fattr->change_attr;
		if (S_ISDIR(inode->i_mode))
			nfsi->cache_validity |= NFS_INO_INVALID_DATA;
			nfs_set_cache_invalid(inode, NFS_INO_INVALID_DATA);
		ret |= NFS_INO_INVALID_ATTR;
	}
	/* If we have atomic WCC data, we may update some attributes */
@@ -1117,7 +1130,7 @@ static unsigned long nfs_wcc_update_inode(struct inode *inode, struct nfs_fattr
			&& 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;
			nfs_set_cache_invalid(inode, NFS_INO_INVALID_DATA);
		ret |= NFS_INO_INVALID_ATTR;
	}
	if ((fattr->valid & NFS_ATTR_FATTR_PRESIZE)
@@ -1128,9 +1141,6 @@ static unsigned long nfs_wcc_update_inode(struct inode *inode, struct nfs_fattr
		ret |= NFS_INO_INVALID_ATTR;
	}

	if (nfsi->cache_validity & NFS_INO_INVALID_DATA)
		nfs_fscache_invalidate(inode);

	return ret;
}

@@ -1189,7 +1199,7 @@ static int nfs_check_inode_attributes(struct inode *inode, struct nfs_fattr *fat
		invalid |= NFS_INO_INVALID_ATIME;

	if (invalid != 0)
		nfsi->cache_validity |= invalid;
		nfs_set_cache_invalid(inode, invalid);

	nfsi->read_cache_jiffies = fattr->time_start;
	return 0;
@@ -1402,13 +1412,11 @@ EXPORT_SYMBOL_GPL(nfs_refresh_inode);

static int nfs_post_op_update_inode_locked(struct inode *inode, struct nfs_fattr *fattr)
{
	struct nfs_inode *nfsi = NFS_I(inode);
	unsigned long invalid = NFS_INO_INVALID_ATTR|NFS_INO_REVAL_PAGECACHE;

	nfsi->cache_validity |= NFS_INO_INVALID_ATTR|NFS_INO_REVAL_PAGECACHE;
	if (S_ISDIR(inode->i_mode)) {
		nfsi->cache_validity |= NFS_INO_INVALID_DATA;
		nfs_fscache_invalidate(inode);
	}
	if (S_ISDIR(inode->i_mode))
		invalid |= NFS_INO_INVALID_DATA;
	nfs_set_cache_invalid(inode, invalid);
	if ((fattr->valid & NFS_ATTR_FATTR) == 0)
		return 0;
	return nfs_refresh_inode_locked(inode, fattr);
@@ -1601,6 +1609,7 @@ static int nfs_update_inode(struct inode *inode, struct nfs_fattr *fattr)
			if ((nfsi->npages == 0) || new_isize > cur_isize) {
				i_size_write(inode, new_isize);
				invalid |= NFS_INO_INVALID_ATTR|NFS_INO_INVALID_DATA;
				invalid &= ~NFS_INO_REVAL_PAGECACHE;
			}
			dprintk("NFS: isize change on server for file %s/%ld "
					"(%Ld to %Ld)\n",
@@ -1702,10 +1711,7 @@ static int nfs_update_inode(struct inode *inode, struct nfs_fattr *fattr)
		invalid &= ~NFS_INO_INVALID_DATA;
	if (!NFS_PROTO(inode)->have_delegation(inode, FMODE_READ) ||
			(save_cache_validity & NFS_INO_REVAL_FORCED))
		nfsi->cache_validity |= invalid;

	if (invalid & NFS_INO_INVALID_DATA)
		nfs_fscache_invalidate(inode);
		nfs_set_cache_invalid(inode, invalid);

	return 0;
 out_err:
+1 −1
Original line number Diff line number Diff line
@@ -230,7 +230,7 @@ int nfs_atomic_open(struct inode *, struct dentry *, struct file *,
extern struct file_system_type nfs4_fs_type;

/* nfs4namespace.c */
struct rpc_clnt *nfs4_create_sec_client(struct rpc_clnt *, struct inode *, struct qstr *);
struct rpc_clnt *nfs4_negotiate_security(struct rpc_clnt *, struct inode *, struct qstr *);
struct vfsmount *nfs4_submount(struct nfs_server *, struct dentry *,
			       struct nfs_fh *, struct nfs_fattr *);
int nfs4_replace_transport(struct nfs_server *server,
+57 −45
Original line number Diff line number Diff line
@@ -139,16 +139,22 @@ static size_t nfs_parse_server_name(char *string, size_t len,
 * @server: NFS server struct
 * @flavors: List of security tuples returned by SECINFO procedure
 *
 * Return the pseudoflavor of the first security mechanism in
 * "flavors" that is locally supported.  Return RPC_AUTH_UNIX if
 * no matching flavor is found in the array.  The "flavors" array
 * Return an rpc client that uses the first security mechanism in
 * "flavors" that is locally supported.  The "flavors" array
 * is searched in the order returned from the server, per RFC 3530
 * recommendation.
 * recommendation and each flavor is checked for membership in the
 * sec= mount option list if it exists.
 *
 * Return -EPERM if no matching flavor is found in the array.
 *
 * Please call rpc_shutdown_client() when you are done with this rpc client.
 *
 */
static rpc_authflavor_t nfs_find_best_sec(struct nfs_server *server,
static struct rpc_clnt *nfs_find_best_sec(struct rpc_clnt *clnt,
					  struct nfs_server *server,
					  struct nfs4_secinfo_flavors *flavors)
{
	rpc_authflavor_t pseudoflavor;
	rpc_authflavor_t pflavor;
	struct nfs4_secinfo4 *secinfo;
	unsigned int i;

@@ -159,62 +165,73 @@ static rpc_authflavor_t nfs_find_best_sec(struct nfs_server *server,
		case RPC_AUTH_NULL:
		case RPC_AUTH_UNIX:
		case RPC_AUTH_GSS:
			pseudoflavor = rpcauth_get_pseudoflavor(secinfo->flavor,
			pflavor = rpcauth_get_pseudoflavor(secinfo->flavor,
							&secinfo->flavor_info);
			/* make sure pseudoflavor matches sec= mount opt */
			if (pseudoflavor != RPC_AUTH_MAXFLAVOR &&
			    nfs_auth_info_match(&server->auth_info,
						pseudoflavor))
				return pseudoflavor;
			break;
			/* does the pseudoflavor match a sec= mount opt? */
			if (pflavor != RPC_AUTH_MAXFLAVOR &&
			    nfs_auth_info_match(&server->auth_info, pflavor)) {
				struct rpc_clnt *new;
				struct rpc_cred *cred;

				/* Cloning creates an rpc_auth for the flavor */
				new = rpc_clone_client_set_auth(clnt, pflavor);
				if (IS_ERR(new))
					continue;
				/**
				* Check that the user actually can use the
				* flavor. This is mostly for RPC_AUTH_GSS
				* where cr_init obtains a gss context
				*/
				cred = rpcauth_lookupcred(new->cl_auth, 0);
				if (IS_ERR(cred)) {
					rpc_shutdown_client(new);
					continue;
				}
				put_rpccred(cred);
				return new;
			}

	/* if there were any sec= options then nothing matched */
	if (server->auth_info.flavor_len > 0)
		return -EPERM;

	return RPC_AUTH_UNIX;
		}
	}
	return ERR_PTR(-EPERM);
}

static rpc_authflavor_t nfs4_negotiate_security(struct inode *inode, struct qstr *name)
/**
 * nfs4_negotiate_security - in response to an NFS4ERR_WRONGSEC on lookup,
 * return an rpc_clnt that uses the best available security flavor with
 * respect to the secinfo flavor list and the sec= mount options.
 *
 * @clnt: RPC client to clone
 * @inode: directory inode
 * @name: lookup name
 *
 * Please call rpc_shutdown_client() when you are done with this rpc client.
 */
struct rpc_clnt *
nfs4_negotiate_security(struct rpc_clnt *clnt, struct inode *inode,
					struct qstr *name)
{
	struct page *page;
	struct nfs4_secinfo_flavors *flavors;
	rpc_authflavor_t flavor;
	struct rpc_clnt *new;
	int err;

	page = alloc_page(GFP_KERNEL);
	if (!page)
		return -ENOMEM;
		return ERR_PTR(-ENOMEM);

	flavors = page_address(page);

	err = nfs4_proc_secinfo(inode, name, flavors);
	if (err < 0) {
		flavor = err;
		new = ERR_PTR(err);
		goto out;
	}

	flavor = nfs_find_best_sec(NFS_SERVER(inode), flavors);
	new = nfs_find_best_sec(clnt, NFS_SERVER(inode), flavors);

out:
	put_page(page);
	return flavor;
}

/*
 * Please call rpc_shutdown_client() when you are done with this client.
 */
struct rpc_clnt *nfs4_create_sec_client(struct rpc_clnt *clnt, struct inode *inode,
					struct qstr *name)
{
	rpc_authflavor_t flavor;

	flavor = nfs4_negotiate_security(inode, name);
	if ((int)flavor < 0)
		return ERR_PTR((int)flavor);

	return rpc_clone_client_set_auth(clnt, flavor);
	return new;
}

static struct vfsmount *try_location(struct nfs_clone_mount *mountdata,
@@ -397,11 +414,6 @@ struct vfsmount *nfs4_submount(struct nfs_server *server, struct dentry *dentry,

	if (client->cl_auth->au_flavor != flavor)
		flavor = client->cl_auth->au_flavor;
	else {
		rpc_authflavor_t new = nfs4_negotiate_security(dir, name);
		if ((int)new >= 0)
			flavor = new;
	}
	mnt = nfs_do_submount(dentry, fh, fattr, flavor);
out:
	rpc_shutdown_client(client);
+1 −1
Original line number Diff line number Diff line
@@ -3247,7 +3247,7 @@ static int nfs4_proc_lookup_common(struct rpc_clnt **clnt, struct inode *dir,
			err = -EPERM;
			if (client != *clnt)
				goto out;
			client = nfs4_create_sec_client(client, dir, name);
			client = nfs4_negotiate_security(client, dir, name);
			if (IS_ERR(client))
				return PTR_ERR(client);

+3 −1
Original line number Diff line number Diff line
@@ -934,12 +934,14 @@ static bool nfs_write_pageuptodate(struct page *page, struct inode *inode)

	if (nfs_have_delegated_attributes(inode))
		goto out;
	if (nfsi->cache_validity & (NFS_INO_INVALID_DATA|NFS_INO_REVAL_PAGECACHE))
	if (nfsi->cache_validity & NFS_INO_REVAL_PAGECACHE)
		return false;
	smp_rmb();
	if (test_bit(NFS_INO_INVALIDATING, &nfsi->flags))
		return false;
out:
	if (nfsi->cache_validity & NFS_INO_INVALID_DATA)
		return false;
	return PageUptodate(page) != 0;
}

Loading