diff options
author | NeilBrown <neilb@suse.de> | 2014-07-13 21:28:20 -0400 |
---|---|---|
committer | Trond Myklebust <trond.myklebust@primarydata.com> | 2014-08-03 17:14:12 -0400 |
commit | f3324a2a94c229831cfd42d871902cd4a9bd5e0f (patch) | |
tree | 287019bb1cce4518d714574bc224eba8a8ce098c | |
parent | bd95608053b7f7813351b0defc0e3e7ef8cf2803 (diff) |
NFS: support RCU_WALK in nfs_permission()
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>
-rw-r--r-- | fs/nfs/dir.c | 28 |
1 files changed, 20 insertions, 8 deletions
diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c index ea12e58dfd85..8a3c36984fc4 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c | |||
@@ -2316,6 +2316,10 @@ static int nfs_do_access(struct inode *inode, struct rpc_cred *cred, int mask) | |||
2316 | if (status == 0) | 2316 | if (status == 0) |
2317 | goto out_cached; | 2317 | goto out_cached; |
2318 | 2318 | ||
2319 | status = -ECHILD; | ||
2320 | if (mask & MAY_NOT_BLOCK) | ||
2321 | goto out; | ||
2322 | |||
2319 | /* Be clever: ask server to check for all possible rights */ | 2323 | /* Be clever: ask server to check for all possible rights */ |
2320 | cache.mask = MAY_EXEC | MAY_WRITE | MAY_READ; | 2324 | cache.mask = MAY_EXEC | MAY_WRITE | MAY_READ; |
2321 | cache.cred = cred; | 2325 | cache.cred = cred; |
@@ -2392,15 +2396,23 @@ force_lookup: | |||
2392 | if (!NFS_PROTO(inode)->access) | 2396 | if (!NFS_PROTO(inode)->access) |
2393 | goto out_notsup; | 2397 | goto out_notsup; |
2394 | 2398 | ||
2395 | if (mask & MAY_NOT_BLOCK) | 2399 | /* Always try fast lookups first */ |
2396 | return -ECHILD; | 2400 | rcu_read_lock(); |
2397 | 2401 | cred = rpc_lookup_cred_nonblock(); | |
2398 | cred = rpc_lookup_cred(); | 2402 | if (!IS_ERR(cred)) |
2399 | if (!IS_ERR(cred)) { | 2403 | res = nfs_do_access(inode, cred, mask|MAY_NOT_BLOCK); |
2400 | res = nfs_do_access(inode, cred, mask); | 2404 | else |
2401 | put_rpccred(cred); | ||
2402 | } else | ||
2403 | res = PTR_ERR(cred); | 2405 | res = PTR_ERR(cred); |
2406 | rcu_read_unlock(); | ||
2407 | if (res == -ECHILD && !(mask & MAY_NOT_BLOCK)) { | ||
2408 | /* Fast lookup failed, try the slow way */ | ||
2409 | cred = rpc_lookup_cred(); | ||
2410 | if (!IS_ERR(cred)) { | ||
2411 | res = nfs_do_access(inode, cred, mask); | ||
2412 | put_rpccred(cred); | ||
2413 | } else | ||
2414 | res = PTR_ERR(cred); | ||
2415 | } | ||
2404 | out: | 2416 | out: |
2405 | if (!res && (mask & MAY_EXEC) && !execute_ok(inode)) | 2417 | if (!res && (mask & MAY_EXEC) && !execute_ok(inode)) |
2406 | res = -EACCES; | 2418 | res = -EACCES; |