NFS: Add an asynchronous delegreturn operation for use in nfs_clear_inode
Otherwise, there is a potential deadlock if the last dput() from an NFSv4 close() or other asynchronous operation leads to nfs_clear_inode calling the synchronous delegreturn. Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
This commit is contained in:
@@ -174,11 +174,11 @@ int nfs_inode_set_delegation(struct inode *inode, struct rpc_cred *cred, struct
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int nfs_do_return_delegation(struct inode *inode, struct nfs_delegation *delegation)
|
static int nfs_do_return_delegation(struct inode *inode, struct nfs_delegation *delegation, int issync)
|
||||||
{
|
{
|
||||||
int res = 0;
|
int res = 0;
|
||||||
|
|
||||||
res = nfs4_proc_delegreturn(inode, delegation->cred, &delegation->stateid);
|
res = nfs4_proc_delegreturn(inode, delegation->cred, &delegation->stateid, issync);
|
||||||
nfs_free_delegation(delegation);
|
nfs_free_delegation(delegation);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@@ -208,7 +208,7 @@ static int __nfs_inode_return_delegation(struct inode *inode, struct nfs_delegat
|
|||||||
up_read(&clp->cl_sem);
|
up_read(&clp->cl_sem);
|
||||||
nfs_msync_inode(inode);
|
nfs_msync_inode(inode);
|
||||||
|
|
||||||
return nfs_do_return_delegation(inode, delegation);
|
return nfs_do_return_delegation(inode, delegation, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct nfs_delegation *nfs_detach_delegation_locked(struct nfs_inode *nfsi, const nfs4_stateid *stateid)
|
static struct nfs_delegation *nfs_detach_delegation_locked(struct nfs_inode *nfsi, const nfs4_stateid *stateid)
|
||||||
@@ -228,6 +228,27 @@ nomatch:
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This function returns the delegation without reclaiming opens
|
||||||
|
* or protecting against delegation reclaims.
|
||||||
|
* It is therefore really only safe to be called from
|
||||||
|
* nfs4_clear_inode()
|
||||||
|
*/
|
||||||
|
void nfs_inode_return_delegation_noreclaim(struct inode *inode)
|
||||||
|
{
|
||||||
|
struct nfs_client *clp = NFS_SERVER(inode)->nfs_client;
|
||||||
|
struct nfs_inode *nfsi = NFS_I(inode);
|
||||||
|
struct nfs_delegation *delegation;
|
||||||
|
|
||||||
|
if (rcu_dereference(nfsi->delegation) != NULL) {
|
||||||
|
spin_lock(&clp->cl_lock);
|
||||||
|
delegation = nfs_detach_delegation_locked(nfsi, NULL);
|
||||||
|
spin_unlock(&clp->cl_lock);
|
||||||
|
if (delegation != NULL)
|
||||||
|
nfs_do_return_delegation(inode, delegation, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int nfs_inode_return_delegation(struct inode *inode)
|
int nfs_inode_return_delegation(struct inode *inode)
|
||||||
{
|
{
|
||||||
struct nfs_client *clp = NFS_SERVER(inode)->nfs_client;
|
struct nfs_client *clp = NFS_SERVER(inode)->nfs_client;
|
||||||
@@ -388,7 +409,7 @@ static int recall_thread(void *data)
|
|||||||
nfs_msync_inode(inode);
|
nfs_msync_inode(inode);
|
||||||
|
|
||||||
if (delegation != NULL)
|
if (delegation != NULL)
|
||||||
nfs_do_return_delegation(inode, delegation);
|
nfs_do_return_delegation(inode, delegation, 1);
|
||||||
iput(inode);
|
iput(inode);
|
||||||
module_put_and_exit(0);
|
module_put_and_exit(0);
|
||||||
}
|
}
|
||||||
|
@@ -29,6 +29,7 @@ int nfs_inode_set_delegation(struct inode *inode, struct rpc_cred *cred, struct
|
|||||||
void nfs_inode_reclaim_delegation(struct inode *inode, struct rpc_cred *cred, struct nfs_openres *res);
|
void nfs_inode_reclaim_delegation(struct inode *inode, struct rpc_cred *cred, struct nfs_openres *res);
|
||||||
int nfs_inode_return_delegation(struct inode *inode);
|
int nfs_inode_return_delegation(struct inode *inode);
|
||||||
int nfs_async_inode_return_delegation(struct inode *inode, const nfs4_stateid *stateid);
|
int nfs_async_inode_return_delegation(struct inode *inode, const nfs4_stateid *stateid);
|
||||||
|
void nfs_inode_return_delegation_noreclaim(struct inode *inode);
|
||||||
|
|
||||||
struct inode *nfs_delegation_find_inode(struct nfs_client *clp, const struct nfs_fh *fhandle);
|
struct inode *nfs_delegation_find_inode(struct nfs_client *clp, const struct nfs_fh *fhandle);
|
||||||
void nfs_return_all_delegations(struct super_block *sb);
|
void nfs_return_all_delegations(struct super_block *sb);
|
||||||
@@ -39,7 +40,7 @@ void nfs_delegation_mark_reclaim(struct nfs_client *clp);
|
|||||||
void nfs_delegation_reap_unclaimed(struct nfs_client *clp);
|
void nfs_delegation_reap_unclaimed(struct nfs_client *clp);
|
||||||
|
|
||||||
/* NFSv4 delegation-related procedures */
|
/* NFSv4 delegation-related procedures */
|
||||||
int nfs4_proc_delegreturn(struct inode *inode, struct rpc_cred *cred, const nfs4_stateid *stateid);
|
int nfs4_proc_delegreturn(struct inode *inode, struct rpc_cred *cred, const nfs4_stateid *stateid, int issync);
|
||||||
int nfs4_open_delegation_recall(struct nfs_open_context *ctx, struct nfs4_state *state, const nfs4_stateid *stateid);
|
int nfs4_open_delegation_recall(struct nfs_open_context *ctx, struct nfs4_state *state, const nfs4_stateid *stateid);
|
||||||
int nfs4_lock_delegation_recall(struct nfs4_state *state, struct file_lock *fl);
|
int nfs4_lock_delegation_recall(struct nfs4_state *state, struct file_lock *fl);
|
||||||
int nfs4_copy_delegation_stateid(nfs4_stateid *dst, struct inode *inode);
|
int nfs4_copy_delegation_stateid(nfs4_stateid *dst, struct inode *inode);
|
||||||
|
@@ -864,7 +864,6 @@ static int nfs_dentry_delete(struct dentry *dentry)
|
|||||||
*/
|
*/
|
||||||
static void nfs_dentry_iput(struct dentry *dentry, struct inode *inode)
|
static void nfs_dentry_iput(struct dentry *dentry, struct inode *inode)
|
||||||
{
|
{
|
||||||
nfs_inode_return_delegation(inode);
|
|
||||||
if (S_ISDIR(inode->i_mode))
|
if (S_ISDIR(inode->i_mode))
|
||||||
/* drop any readdir cache as it could easily be old */
|
/* drop any readdir cache as it could easily be old */
|
||||||
NFS_I(inode)->cache_validity |= NFS_INO_INVALID_DATA;
|
NFS_I(inode)->cache_validity |= NFS_INO_INVALID_DATA;
|
||||||
|
@@ -1145,7 +1145,7 @@ static int nfs_update_inode(struct inode *inode, struct nfs_fattr *fattr)
|
|||||||
void nfs4_clear_inode(struct inode *inode)
|
void nfs4_clear_inode(struct inode *inode)
|
||||||
{
|
{
|
||||||
/* If we are holding a delegation, return it! */
|
/* If we are holding a delegation, return it! */
|
||||||
nfs_inode_return_delegation(inode);
|
nfs_inode_return_delegation_noreclaim(inode);
|
||||||
/* First call standard NFS clear_inode() code */
|
/* First call standard NFS clear_inode() code */
|
||||||
nfs_clear_inode(inode);
|
nfs_clear_inode(inode);
|
||||||
}
|
}
|
||||||
|
@@ -2991,7 +2991,7 @@ static const struct rpc_call_ops nfs4_delegreturn_ops = {
|
|||||||
.rpc_release = nfs4_delegreturn_release,
|
.rpc_release = nfs4_delegreturn_release,
|
||||||
};
|
};
|
||||||
|
|
||||||
static int _nfs4_proc_delegreturn(struct inode *inode, struct rpc_cred *cred, const nfs4_stateid *stateid)
|
static int _nfs4_proc_delegreturn(struct inode *inode, struct rpc_cred *cred, const nfs4_stateid *stateid, int issync)
|
||||||
{
|
{
|
||||||
struct nfs4_delegreturndata *data;
|
struct nfs4_delegreturndata *data;
|
||||||
struct nfs_server *server = NFS_SERVER(inode);
|
struct nfs_server *server = NFS_SERVER(inode);
|
||||||
@@ -3006,7 +3006,7 @@ static int _nfs4_proc_delegreturn(struct inode *inode, struct rpc_cred *cred, co
|
|||||||
.callback_ops = &nfs4_delegreturn_ops,
|
.callback_ops = &nfs4_delegreturn_ops,
|
||||||
.flags = RPC_TASK_ASYNC,
|
.flags = RPC_TASK_ASYNC,
|
||||||
};
|
};
|
||||||
int status;
|
int status = 0;
|
||||||
|
|
||||||
data = kmalloc(sizeof(*data), GFP_KERNEL);
|
data = kmalloc(sizeof(*data), GFP_KERNEL);
|
||||||
if (data == NULL)
|
if (data == NULL)
|
||||||
@@ -3028,23 +3028,27 @@ static int _nfs4_proc_delegreturn(struct inode *inode, struct rpc_cred *cred, co
|
|||||||
task = rpc_run_task(&task_setup_data);
|
task = rpc_run_task(&task_setup_data);
|
||||||
if (IS_ERR(task))
|
if (IS_ERR(task))
|
||||||
return PTR_ERR(task);
|
return PTR_ERR(task);
|
||||||
|
if (!issync)
|
||||||
|
goto out;
|
||||||
status = nfs4_wait_for_completion_rpc_task(task);
|
status = nfs4_wait_for_completion_rpc_task(task);
|
||||||
if (status == 0) {
|
if (status != 0)
|
||||||
status = data->rpc_status;
|
goto out;
|
||||||
if (status == 0)
|
status = data->rpc_status;
|
||||||
nfs_refresh_inode(inode, &data->fattr);
|
if (status != 0)
|
||||||
}
|
goto out;
|
||||||
|
nfs_refresh_inode(inode, &data->fattr);
|
||||||
|
out:
|
||||||
rpc_put_task(task);
|
rpc_put_task(task);
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
int nfs4_proc_delegreturn(struct inode *inode, struct rpc_cred *cred, const nfs4_stateid *stateid)
|
int nfs4_proc_delegreturn(struct inode *inode, struct rpc_cred *cred, const nfs4_stateid *stateid, int issync)
|
||||||
{
|
{
|
||||||
struct nfs_server *server = NFS_SERVER(inode);
|
struct nfs_server *server = NFS_SERVER(inode);
|
||||||
struct nfs4_exception exception = { };
|
struct nfs4_exception exception = { };
|
||||||
int err;
|
int err;
|
||||||
do {
|
do {
|
||||||
err = _nfs4_proc_delegreturn(inode, cred, stateid);
|
err = _nfs4_proc_delegreturn(inode, cred, stateid, issync);
|
||||||
switch (err) {
|
switch (err) {
|
||||||
case -NFS4ERR_STALE_STATEID:
|
case -NFS4ERR_STALE_STATEID:
|
||||||
case -NFS4ERR_EXPIRED:
|
case -NFS4ERR_EXPIRED:
|
||||||
|
Reference in New Issue
Block a user