diff options
author | David Howells <dhowells@redhat.com> | 2019-05-20 03:48:46 -0400 |
---|---|---|
committer | David Howells <dhowells@redhat.com> | 2019-09-02 06:43:54 -0400 |
commit | a0753c29004f4983e303abce019f29e183b1ee48 (patch) | |
tree | 0227fe9742afaf92caddb7e5f086cf5f474becf0 | |
parent | 8b6a666a97544bf307190a05947742b8357aa962 (diff) |
afs: Support RCU pathwalk
Make afs_permission() and afs_d_revalidate() do initial checks in RCU-mode
pathwalk to reduce latency in pathwalk elements that get done multiple
times. We don't need to query the server unless we've received a
notification from it that something has changed or the callback has
expired.
This requires that we can request a key and check permits under RCU
conditions if we need to.
Signed-off-by: David Howells <dhowells@redhat.com>
-rw-r--r-- | fs/afs/dir.c | 54 | ||||
-rw-r--r-- | fs/afs/security.c | 75 |
2 files changed, 112 insertions, 17 deletions
diff --git a/fs/afs/dir.c b/fs/afs/dir.c index 139b4e3cc946..cc12772d0a4d 100644 --- a/fs/afs/dir.c +++ b/fs/afs/dir.c | |||
@@ -966,6 +966,58 @@ static struct dentry *afs_lookup(struct inode *dir, struct dentry *dentry, | |||
966 | } | 966 | } |
967 | 967 | ||
968 | /* | 968 | /* |
969 | * Check the validity of a dentry under RCU conditions. | ||
970 | */ | ||
971 | static int afs_d_revalidate_rcu(struct dentry *dentry) | ||
972 | { | ||
973 | struct afs_vnode *dvnode, *vnode; | ||
974 | struct dentry *parent; | ||
975 | struct inode *dir, *inode; | ||
976 | long dir_version, de_version; | ||
977 | |||
978 | _enter("%p", dentry); | ||
979 | |||
980 | /* Check the parent directory is still valid first. */ | ||
981 | parent = READ_ONCE(dentry->d_parent); | ||
982 | dir = d_inode_rcu(parent); | ||
983 | if (!dir) | ||
984 | return -ECHILD; | ||
985 | dvnode = AFS_FS_I(dir); | ||
986 | if (test_bit(AFS_VNODE_DELETED, &dvnode->flags)) | ||
987 | return -ECHILD; | ||
988 | |||
989 | if (!afs_check_validity(dvnode)) | ||
990 | return -ECHILD; | ||
991 | |||
992 | /* We only need to invalidate a dentry if the server's copy changed | ||
993 | * behind our back. If we made the change, it's no problem. Note that | ||
994 | * on a 32-bit system, we only have 32 bits in the dentry to store the | ||
995 | * version. | ||
996 | */ | ||
997 | dir_version = (long)READ_ONCE(dvnode->status.data_version); | ||
998 | de_version = (long)READ_ONCE(dentry->d_fsdata); | ||
999 | if (de_version != dir_version) { | ||
1000 | dir_version = (long)READ_ONCE(dvnode->invalid_before); | ||
1001 | if (de_version - dir_version < 0) | ||
1002 | return -ECHILD; | ||
1003 | } | ||
1004 | |||
1005 | /* Check to see if the vnode referred to by the dentry still | ||
1006 | * has a callback. | ||
1007 | */ | ||
1008 | if (d_really_is_positive(dentry)) { | ||
1009 | inode = d_inode_rcu(dentry); | ||
1010 | if (inode) { | ||
1011 | vnode = AFS_FS_I(inode); | ||
1012 | if (!afs_check_validity(vnode)) | ||
1013 | return -ECHILD; | ||
1014 | } | ||
1015 | } | ||
1016 | |||
1017 | return 1; /* Still valid */ | ||
1018 | } | ||
1019 | |||
1020 | /* | ||
969 | * check that a dentry lookup hit has found a valid entry | 1021 | * check that a dentry lookup hit has found a valid entry |
970 | * - NOTE! the hit can be a negative hit too, so we can't assume we have an | 1022 | * - NOTE! the hit can be a negative hit too, so we can't assume we have an |
971 | * inode | 1023 | * inode |
@@ -982,7 +1034,7 @@ static int afs_d_revalidate(struct dentry *dentry, unsigned int flags) | |||
982 | int ret; | 1034 | int ret; |
983 | 1035 | ||
984 | if (flags & LOOKUP_RCU) | 1036 | if (flags & LOOKUP_RCU) |
985 | return -ECHILD; | 1037 | return afs_d_revalidate_rcu(dentry); |
986 | 1038 | ||
987 | if (d_really_is_positive(dentry)) { | 1039 | if (d_really_is_positive(dentry)) { |
988 | vnode = AFS_FS_I(d_inode(dentry)); | 1040 | vnode = AFS_FS_I(d_inode(dentry)); |
diff --git a/fs/afs/security.c b/fs/afs/security.c index ef2fd34ba282..ce9de1e6742b 100644 --- a/fs/afs/security.c +++ b/fs/afs/security.c | |||
@@ -303,6 +303,40 @@ someone_else_changed_it: | |||
303 | return; | 303 | return; |
304 | } | 304 | } |
305 | 305 | ||
306 | static bool afs_check_permit_rcu(struct afs_vnode *vnode, struct key *key, | ||
307 | afs_access_t *_access) | ||
308 | { | ||
309 | const struct afs_permits *permits; | ||
310 | int i; | ||
311 | |||
312 | _enter("{%llx:%llu},%x", | ||
313 | vnode->fid.vid, vnode->fid.vnode, key_serial(key)); | ||
314 | |||
315 | /* check the permits to see if we've got one yet */ | ||
316 | if (key == vnode->volume->cell->anonymous_key) { | ||
317 | *_access = vnode->status.anon_access; | ||
318 | _leave(" = t [anon %x]", *_access); | ||
319 | return true; | ||
320 | } | ||
321 | |||
322 | permits = rcu_dereference(vnode->permit_cache); | ||
323 | if (permits) { | ||
324 | for (i = 0; i < permits->nr_permits; i++) { | ||
325 | if (permits->permits[i].key < key) | ||
326 | continue; | ||
327 | if (permits->permits[i].key > key) | ||
328 | break; | ||
329 | |||
330 | *_access = permits->permits[i].access; | ||
331 | _leave(" = %u [perm %x]", !permits->invalidated, *_access); | ||
332 | return !permits->invalidated; | ||
333 | } | ||
334 | } | ||
335 | |||
336 | _leave(" = f"); | ||
337 | return false; | ||
338 | } | ||
339 | |||
306 | /* | 340 | /* |
307 | * check with the fileserver to see if the directory or parent directory is | 341 | * check with the fileserver to see if the directory or parent directory is |
308 | * permitted to be accessed with this authorisation, and if so, what access it | 342 | * permitted to be accessed with this authorisation, and if so, what access it |
@@ -369,33 +403,42 @@ int afs_permission(struct inode *inode, int mask) | |||
369 | struct afs_vnode *vnode = AFS_FS_I(inode); | 403 | struct afs_vnode *vnode = AFS_FS_I(inode); |
370 | afs_access_t uninitialized_var(access); | 404 | afs_access_t uninitialized_var(access); |
371 | struct key *key; | 405 | struct key *key; |
372 | int ret; | 406 | int ret = 0; |
373 | |||
374 | if (mask & MAY_NOT_BLOCK) | ||
375 | return -ECHILD; | ||
376 | 407 | ||
377 | _enter("{{%llx:%llu},%lx},%x,", | 408 | _enter("{{%llx:%llu},%lx},%x,", |
378 | vnode->fid.vid, vnode->fid.vnode, vnode->flags, mask); | 409 | vnode->fid.vid, vnode->fid.vnode, vnode->flags, mask); |
379 | 410 | ||
380 | key = afs_request_key(vnode->volume->cell); | 411 | if (mask & MAY_NOT_BLOCK) { |
381 | if (IS_ERR(key)) { | 412 | key = afs_request_key_rcu(vnode->volume->cell); |
382 | _leave(" = %ld [key]", PTR_ERR(key)); | 413 | if (IS_ERR(key)) |
383 | return PTR_ERR(key); | 414 | return -ECHILD; |
384 | } | ||
385 | 415 | ||
386 | ret = afs_validate(vnode, key); | 416 | ret = -ECHILD; |
387 | if (ret < 0) | 417 | if (!afs_check_validity(vnode) || |
388 | goto error; | 418 | !afs_check_permit_rcu(vnode, key, &access)) |
419 | goto error; | ||
420 | } else { | ||
421 | key = afs_request_key(vnode->volume->cell); | ||
422 | if (IS_ERR(key)) { | ||
423 | _leave(" = %ld [key]", PTR_ERR(key)); | ||
424 | return PTR_ERR(key); | ||
425 | } | ||
389 | 426 | ||
390 | /* check the permits to see if we've got one yet */ | 427 | ret = afs_validate(vnode, key); |
391 | ret = afs_check_permit(vnode, key, &access); | 428 | if (ret < 0) |
392 | if (ret < 0) | 429 | goto error; |
393 | goto error; | 430 | |
431 | /* check the permits to see if we've got one yet */ | ||
432 | ret = afs_check_permit(vnode, key, &access); | ||
433 | if (ret < 0) | ||
434 | goto error; | ||
435 | } | ||
394 | 436 | ||
395 | /* interpret the access mask */ | 437 | /* interpret the access mask */ |
396 | _debug("REQ %x ACC %x on %s", | 438 | _debug("REQ %x ACC %x on %s", |
397 | mask, access, S_ISDIR(inode->i_mode) ? "dir" : "file"); | 439 | mask, access, S_ISDIR(inode->i_mode) ? "dir" : "file"); |
398 | 440 | ||
441 | ret = 0; | ||
399 | if (S_ISDIR(inode->i_mode)) { | 442 | if (S_ISDIR(inode->i_mode)) { |
400 | if (mask & (MAY_EXEC | MAY_READ | MAY_CHDIR)) { | 443 | if (mask & (MAY_EXEC | MAY_READ | MAY_CHDIR)) { |
401 | if (!(access & AFS_ACE_LOOKUP)) | 444 | if (!(access & AFS_ACE_LOOKUP)) |