Skip to content

Commit

Permalink
NFS: support RCU_WALK in nfs_permission()
Browse files Browse the repository at this point in the history
nfs_permission makes two calls which are not always safe in RCU_WALK,
rpc_lookup_cred and nfs_do_access.

The second can easily be made rcu-safe by aborting with -ECHILD before
making the RPC call.

The former can be made rcu-safe by calling rpc_lookup_cred_nonblock()
instead.
As this will almost always succeed, we use it even when RCU_WALK
isn't being used as it still saves some spinlocks in a common case.
We only fall back to rpc_lookup_cred() if rpc_lookup_cred_nonblock()
fails and MAY_NOT_BLOCK isn't set.

This optimisation (always trying rpc_lookup_cred_nonblock()) is
particularly important when a security module is active.
In that case inode_permission() may return -ECHILD from
security_inode_permission() even though ->permission() succeeded in
RCU_WALK mode.
This leads to may_lookup() retrying inode_permission after performing
unlazy_walk().  The spinlock that rpc_lookup_cred() takes is often
more expensive than anything security_inode_permission() does, so that
spinlock becomes the main bottleneck.

Signed-off-by: NeilBrown <neilb@suse.de>
Signed-off-by: Trond Myklebust <trond.myklebust@primarydata.com>
  • Loading branch information
NeilBrown authored and Trond Myklebust committed Aug 3, 2014
1 parent bd95608 commit f3324a2
Showing 1 changed file with 20 additions and 8 deletions.
28 changes: 20 additions & 8 deletions fs/nfs/dir.c
Original file line number Diff line number Diff line change
Expand Up @@ -2316,6 +2316,10 @@ static int nfs_do_access(struct inode *inode, struct rpc_cred *cred, int mask)
if (status == 0)
goto out_cached;

status = -ECHILD;
if (mask & MAY_NOT_BLOCK)
goto out;

/* Be clever: ask server to check for all possible rights */
cache.mask = MAY_EXEC | MAY_WRITE | MAY_READ;
cache.cred = cred;
Expand Down Expand Up @@ -2392,15 +2396,23 @@ int nfs_permission(struct inode *inode, int mask)
if (!NFS_PROTO(inode)->access)
goto out_notsup;

if (mask & MAY_NOT_BLOCK)
return -ECHILD;

cred = rpc_lookup_cred();
if (!IS_ERR(cred)) {
res = nfs_do_access(inode, cred, mask);
put_rpccred(cred);
} else
/* Always try fast lookups first */
rcu_read_lock();
cred = rpc_lookup_cred_nonblock();
if (!IS_ERR(cred))
res = nfs_do_access(inode, cred, mask|MAY_NOT_BLOCK);
else
res = PTR_ERR(cred);
rcu_read_unlock();
if (res == -ECHILD && !(mask & MAY_NOT_BLOCK)) {
/* Fast lookup failed, try the slow way */
cred = rpc_lookup_cred();
if (!IS_ERR(cred)) {
res = nfs_do_access(inode, cred, mask);
put_rpccred(cred);
} else
res = PTR_ERR(cred);
}
out:
if (!res && (mask & MAY_EXEC) && !execute_ok(inode))
res = -EACCES;
Expand Down

0 comments on commit f3324a2

Please sign in to comment.