NFSv4: Safe return of delegations. Make sure we return delegations in a safe (and asynchronous) manner inside nfs4_clear_inode(). Signed-off-by: Trond Myklebust --- fs/nfs/delegation.c | 25 ++++++++++++++++++ fs/nfs/delegation.h | 1 fs/nfs/inode.c | 6 ++-- fs/nfs/nfs4proc.c | 70 ++++++++++++++++++++++++++++++++-------------------- 4 files changed, 73 insertions(+), 29 deletions(-) Index: linux-2.6.12-rc1/fs/nfs/inode.c =================================================================== --- linux-2.6.12-rc1.orig/fs/nfs/inode.c +++ linux-2.6.12-rc1/fs/nfs/inode.c @@ -1573,9 +1573,6 @@ static void nfs4_clear_inode(struct inod { struct nfs_inode *nfsi = NFS_I(inode); - /* If we are holding a delegation, return it! */ - if (nfsi->delegation != NULL) - nfs_inode_return_delegation(inode); /* First call standard NFS clear_inode() code */ nfs_clear_inode(inode); /* Now clear out any remaining state */ @@ -1593,6 +1590,9 @@ static void nfs4_clear_inode(struct inod BUG_ON(atomic_read(&state->count) != 1); nfs4_close_state(state, state->state); } + /* If we are holding a delegation, return it! */ + if (nfsi->delegation != NULL) + nfs_inode_clear_delegation(inode); } Index: linux-2.6.12-rc1/fs/nfs/nfs4proc.c =================================================================== --- linux-2.6.12-rc1.orig/fs/nfs/nfs4proc.c +++ linux-2.6.12-rc1/fs/nfs/nfs4proc.c @@ -2518,38 +2518,56 @@ nfs4_proc_setclientid_confirm(struct nfs return status; } -static int _nfs4_proc_delegreturn(struct inode *inode, struct rpc_cred *cred, const nfs4_stateid *stateid) +struct nfs4_delegreturn_data { + struct nfs_server *server; + struct nfs_fh fhandle; + nfs4_stateid stateid; + struct nfs4_delegreturnargs args; +}; + +static void nfs4_delegreturn_done(struct rpc_task *task) { - struct nfs4_delegreturnargs args = { - .fhandle = NFS_FH(inode), - .stateid = stateid, - }; - struct rpc_message msg = { - .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_DELEGRETURN], - .rpc_argp = &args, - .rpc_cred = cred, - }; + struct nfs4_delegreturn_data *calldata = (struct nfs4_delegreturn_data *)task->tk_calldata; - return rpc_call_sync(NFS_CLIENT(inode), &msg, 0); + switch (task->tk_status) { + case 0: + break; + case -NFS4ERR_STALE_STATEID: + case -NFS4ERR_EXPIRED: + nfs4_schedule_state_recovery(calldata->server->nfs4_state); + break; + default: + if (nfs4_async_handle_error(task, calldata->server) == -EAGAIN) { + rpc_restart_call(task); + return; + } + } + kfree(calldata); } int nfs4_proc_delegreturn(struct inode *inode, struct rpc_cred *cred, const nfs4_stateid *stateid) { - struct nfs_server *server = NFS_SERVER(inode); - struct nfs4_exception exception = { }; - int err; - do { - err = _nfs4_proc_delegreturn(inode, cred, stateid); - switch (err) { - case -NFS4ERR_STALE_STATEID: - case -NFS4ERR_EXPIRED: - nfs4_schedule_state_recovery(server->nfs4_state); - case 0: - return 0; - } - err = nfs4_handle_exception(server, err, &exception); - } while (exception.retry); - return err; + struct nfs4_delegreturn_data *calldata; + struct rpc_message msg = { + .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_DELEGRETURN], + .rpc_cred = cred, + }; + int status = -ENOMEM; + + calldata = kmalloc(sizeof(*calldata), GFP_NOFS); + if (calldata == NULL) + goto out; + calldata->server = NFS_SERVER(inode); + nfs_copy_fh(&calldata->fhandle, NFS_FH(inode)); + memcpy(&calldata->stateid, stateid, sizeof(calldata->stateid)); + calldata->args.fhandle = &calldata->fhandle; + calldata->args.stateid = &calldata->stateid; + msg.rpc_argp = &calldata->args; + status = rpc_call_async(NFS_CLIENT(inode), &msg, 0, nfs4_delegreturn_done, calldata); + if (status != 0) + kfree(calldata); +out: + return status; } #define NFS4_LOCK_MINTIMEOUT (1 * HZ) Index: linux-2.6.12-rc1/fs/nfs/delegation.c =================================================================== --- linux-2.6.12-rc1.orig/fs/nfs/delegation.c +++ linux-2.6.12-rc1/fs/nfs/delegation.c @@ -195,6 +195,31 @@ restart: } /* + * Return a delegation for clear_inode() + */ +void nfs_inode_clear_delegation(struct inode *inode) +{ + struct nfs4_client *clp = NFS_SERVER(inode)->nfs4_state; + struct nfs_inode *nfsi = NFS_I(inode); + struct nfs_delegation *delegation; + + down_read(&clp->cl_sem); + spin_lock(&clp->cl_lock); + delegation = nfsi->delegation; + if (delegation != NULL) { + list_del_init(&delegation->super_list); + nfsi->delegation = NULL; + nfsi->delegation_state = 0; + } + spin_unlock(&clp->cl_lock); + up_read(&clp->cl_sem); + if (delegation != NULL) { + nfs4_proc_delegreturn(inode, delegation->cred, &delegation->stateid); + nfs_free_delegation(delegation); + } +} + +/* * Return all delegations following an NFS4ERR_CB_PATH_DOWN error. */ void nfs_handle_cb_pathdown(struct nfs4_client *clp) Index: linux-2.6.12-rc1/fs/nfs/delegation.h =================================================================== --- linux-2.6.12-rc1.orig/fs/nfs/delegation.h +++ linux-2.6.12-rc1/fs/nfs/delegation.h @@ -27,6 +27,7 @@ int nfs_inode_set_delegation(struct inod 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_async_inode_return_delegation(struct inode *inode, const nfs4_stateid *stateid); +void nfs_inode_clear_delegation(struct inode *inode); struct inode *nfs_delegation_find_inode(struct nfs4_client *clp, const struct nfs_fh *fhandle); void nfs_return_all_delegations(struct super_block *sb);