nfsd_rename() can kick off a CB_RECALL (via vfs_rename() -> leases_conflict()) if a delegation is present. Before returning NFS4ERR_DELAY, give the client holding that delegation a chance to return it and then retry the nfsd_rename() again, once. Link: https://bugzilla.linux-nfs.org/show_bug.cgi?id=354 Tested-by: Igor Mammedov <imammedo@xxxxxxxxxx> Signed-off-by: Chuck Lever <chuck.lever@xxxxxxxxxx> --- fs/nfsd/nfs4proc.c | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c index 5b18a71f1043..ed3c6ef875c3 100644 --- a/fs/nfsd/nfs4proc.c +++ b/fs/nfsd/nfs4proc.c @@ -1056,17 +1056,33 @@ nfsd4_rename(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, { struct nfsd4_rename *rename = &u->rename; __be32 status; + int retries; if (opens_in_grace(SVC_NET(rqstp))) return nfserr_grace; - status = nfsd_rename(rqstp, &cstate->save_fh, rename->rn_sname, - rename->rn_snamelen, &cstate->current_fh, - rename->rn_tname, rename->rn_tnamelen); - if (status) - return status; - set_change_info(&rename->rn_sinfo, &cstate->current_fh); - set_change_info(&rename->rn_tinfo, &cstate->save_fh); - return nfs_ok; + + retries = 1; + do { + status = nfsd_rename(rqstp, &cstate->save_fh, rename->rn_sname, + rename->rn_snamelen, &cstate->current_fh, + rename->rn_tname, rename->rn_tnamelen); + if (status == nfs_ok) { + set_change_info(&rename->rn_sinfo, &cstate->current_fh); + set_change_info(&rename->rn_tinfo, &cstate->save_fh); + break; + } + if (status != nfserr_jukebox) + break; + if (!retries--) + break; + if (!nfsd4_wait_for_delegreturn(rqstp, &cstate->current_fh)) + break; + + fh_clear_pre_post_attrs(&cstate->save_fh); + fh_clear_pre_post_attrs(&cstate->current_fh); + } while (1); + + return status; } static __be32