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>alistair/sunxi64-5.4-dsi
parent
8b6a666a97
commit
a0753c2900
54
fs/afs/dir.c
54
fs/afs/dir.c
|
@ -965,6 +965,58 @@ static struct dentry *afs_lookup(struct inode *dir, struct dentry *dentry,
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check the validity of a dentry under RCU conditions.
|
||||||
|
*/
|
||||||
|
static int afs_d_revalidate_rcu(struct dentry *dentry)
|
||||||
|
{
|
||||||
|
struct afs_vnode *dvnode, *vnode;
|
||||||
|
struct dentry *parent;
|
||||||
|
struct inode *dir, *inode;
|
||||||
|
long dir_version, de_version;
|
||||||
|
|
||||||
|
_enter("%p", dentry);
|
||||||
|
|
||||||
|
/* Check the parent directory is still valid first. */
|
||||||
|
parent = READ_ONCE(dentry->d_parent);
|
||||||
|
dir = d_inode_rcu(parent);
|
||||||
|
if (!dir)
|
||||||
|
return -ECHILD;
|
||||||
|
dvnode = AFS_FS_I(dir);
|
||||||
|
if (test_bit(AFS_VNODE_DELETED, &dvnode->flags))
|
||||||
|
return -ECHILD;
|
||||||
|
|
||||||
|
if (!afs_check_validity(dvnode))
|
||||||
|
return -ECHILD;
|
||||||
|
|
||||||
|
/* We only need to invalidate a dentry if the server's copy changed
|
||||||
|
* behind our back. If we made the change, it's no problem. Note that
|
||||||
|
* on a 32-bit system, we only have 32 bits in the dentry to store the
|
||||||
|
* version.
|
||||||
|
*/
|
||||||
|
dir_version = (long)READ_ONCE(dvnode->status.data_version);
|
||||||
|
de_version = (long)READ_ONCE(dentry->d_fsdata);
|
||||||
|
if (de_version != dir_version) {
|
||||||
|
dir_version = (long)READ_ONCE(dvnode->invalid_before);
|
||||||
|
if (de_version - dir_version < 0)
|
||||||
|
return -ECHILD;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check to see if the vnode referred to by the dentry still
|
||||||
|
* has a callback.
|
||||||
|
*/
|
||||||
|
if (d_really_is_positive(dentry)) {
|
||||||
|
inode = d_inode_rcu(dentry);
|
||||||
|
if (inode) {
|
||||||
|
vnode = AFS_FS_I(inode);
|
||||||
|
if (!afs_check_validity(vnode))
|
||||||
|
return -ECHILD;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1; /* Still valid */
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* check that a dentry lookup hit has found a valid entry
|
* check that a dentry lookup hit has found a valid entry
|
||||||
* - NOTE! the hit can be a negative hit too, so we can't assume we have an
|
* - NOTE! the hit can be a negative hit too, so we can't assume we have an
|
||||||
|
@ -982,7 +1034,7 @@ static int afs_d_revalidate(struct dentry *dentry, unsigned int flags)
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
if (flags & LOOKUP_RCU)
|
if (flags & LOOKUP_RCU)
|
||||||
return -ECHILD;
|
return afs_d_revalidate_rcu(dentry);
|
||||||
|
|
||||||
if (d_really_is_positive(dentry)) {
|
if (d_really_is_positive(dentry)) {
|
||||||
vnode = AFS_FS_I(d_inode(dentry));
|
vnode = AFS_FS_I(d_inode(dentry));
|
||||||
|
|
|
@ -303,6 +303,40 @@ someone_else_changed_it:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool afs_check_permit_rcu(struct afs_vnode *vnode, struct key *key,
|
||||||
|
afs_access_t *_access)
|
||||||
|
{
|
||||||
|
const struct afs_permits *permits;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
_enter("{%llx:%llu},%x",
|
||||||
|
vnode->fid.vid, vnode->fid.vnode, key_serial(key));
|
||||||
|
|
||||||
|
/* check the permits to see if we've got one yet */
|
||||||
|
if (key == vnode->volume->cell->anonymous_key) {
|
||||||
|
*_access = vnode->status.anon_access;
|
||||||
|
_leave(" = t [anon %x]", *_access);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
permits = rcu_dereference(vnode->permit_cache);
|
||||||
|
if (permits) {
|
||||||
|
for (i = 0; i < permits->nr_permits; i++) {
|
||||||
|
if (permits->permits[i].key < key)
|
||||||
|
continue;
|
||||||
|
if (permits->permits[i].key > key)
|
||||||
|
break;
|
||||||
|
|
||||||
|
*_access = permits->permits[i].access;
|
||||||
|
_leave(" = %u [perm %x]", !permits->invalidated, *_access);
|
||||||
|
return !permits->invalidated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_leave(" = f");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* check with the fileserver to see if the directory or parent directory is
|
* check with the fileserver to see if the directory or parent directory is
|
||||||
* permitted to be accessed with this authorisation, and if so, what access it
|
* 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)
|
||||||
struct afs_vnode *vnode = AFS_FS_I(inode);
|
struct afs_vnode *vnode = AFS_FS_I(inode);
|
||||||
afs_access_t uninitialized_var(access);
|
afs_access_t uninitialized_var(access);
|
||||||
struct key *key;
|
struct key *key;
|
||||||
int ret;
|
int ret = 0;
|
||||||
|
|
||||||
if (mask & MAY_NOT_BLOCK)
|
|
||||||
return -ECHILD;
|
|
||||||
|
|
||||||
_enter("{{%llx:%llu},%lx},%x,",
|
_enter("{{%llx:%llu},%lx},%x,",
|
||||||
vnode->fid.vid, vnode->fid.vnode, vnode->flags, mask);
|
vnode->fid.vid, vnode->fid.vnode, vnode->flags, mask);
|
||||||
|
|
||||||
key = afs_request_key(vnode->volume->cell);
|
if (mask & MAY_NOT_BLOCK) {
|
||||||
if (IS_ERR(key)) {
|
key = afs_request_key_rcu(vnode->volume->cell);
|
||||||
_leave(" = %ld [key]", PTR_ERR(key));
|
if (IS_ERR(key))
|
||||||
return PTR_ERR(key);
|
return -ECHILD;
|
||||||
|
|
||||||
|
ret = -ECHILD;
|
||||||
|
if (!afs_check_validity(vnode) ||
|
||||||
|
!afs_check_permit_rcu(vnode, key, &access))
|
||||||
|
goto error;
|
||||||
|
} else {
|
||||||
|
key = afs_request_key(vnode->volume->cell);
|
||||||
|
if (IS_ERR(key)) {
|
||||||
|
_leave(" = %ld [key]", PTR_ERR(key));
|
||||||
|
return PTR_ERR(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = afs_validate(vnode, key);
|
||||||
|
if (ret < 0)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
/* check the permits to see if we've got one yet */
|
||||||
|
ret = afs_check_permit(vnode, key, &access);
|
||||||
|
if (ret < 0)
|
||||||
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = afs_validate(vnode, key);
|
|
||||||
if (ret < 0)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
/* check the permits to see if we've got one yet */
|
|
||||||
ret = afs_check_permit(vnode, key, &access);
|
|
||||||
if (ret < 0)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
/* interpret the access mask */
|
/* interpret the access mask */
|
||||||
_debug("REQ %x ACC %x on %s",
|
_debug("REQ %x ACC %x on %s",
|
||||||
mask, access, S_ISDIR(inode->i_mode) ? "dir" : "file");
|
mask, access, S_ISDIR(inode->i_mode) ? "dir" : "file");
|
||||||
|
|
||||||
|
ret = 0;
|
||||||
if (S_ISDIR(inode->i_mode)) {
|
if (S_ISDIR(inode->i_mode)) {
|
||||||
if (mask & (MAY_EXEC | MAY_READ | MAY_CHDIR)) {
|
if (mask & (MAY_EXEC | MAY_READ | MAY_CHDIR)) {
|
||||||
if (!(access & AFS_ACE_LOOKUP))
|
if (!(access & AFS_ACE_LOOKUP))
|
||||||
|
|
Loading…
Reference in New Issue