Commit e6f81075 authored by Trond Myklebust's avatar Trond Myklebust

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: default avatarTrond Myklebust <Trond.Myklebust@netapp.com>
parent 99fadcd7
...@@ -174,11 +174,11 @@ int nfs_inode_set_delegation(struct inode *inode, struct rpc_cred *cred, struct ...@@ -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 ...@@ -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: ...@@ -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) ...@@ -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 ...@@ -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); ...@@ -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) ...@@ -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) ...@@ -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 = { ...@@ -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 ...@@ -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 ...@@ -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:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment