aboutsummaryrefslogtreecommitdiffstats
path: root/fs/nfsd
diff options
context:
space:
mode:
authorAndrew Elble <aweits@rit.edu>2015-10-15 12:07:28 -0400
committerJ. Bruce Fields <bfields@redhat.com>2015-11-10 09:29:44 -0500
commit34ed9872e745fa56f10e9bef2cf3d2336c6c8816 (patch)
treef3e26b44fd4f97de1f2d9c82727c60dce8f8b925 /fs/nfsd
parent3e80dbcda7f3e1e349a779d7a14c0e08677c39fa (diff)
nfsd: eliminate sending duplicate and repeated delegations
We've observed the nfsd server in a state where there are multiple delegations on the same nfs4_file for the same client. The nfs client does attempt to DELEGRETURN these when they are presented to it - but apparently under some (unknown) circumstances the client does not manage to return all of them. This leads to the eventual attempt to CB_RECALL more than one delegation with the same nfs filehandle to the same client. The first recall will succeed, but the next recall will fail with NFS4ERR_BADHANDLE. This leads to the server having delegations on cl_revoked that the client has no way to FREE or DELEGRETURN, with resulting inability to recover. The state manager on the server will continually assert SEQ4_STATUS_RECALLABLE_STATE_REVOKED, and the state manager on the client will be looping unable to satisfy the server. List discussion also reports a race between OPEN and DELEGRETURN that will be avoided by only sending the delegation once to the client. This is also logically in accordance with RFC5561 9.1.1 and 10.2. So, let's: 1.) Not hand out duplicate delegations. 2.) Only send them to the client once. RFC 5561: 9.1.1: "Delegations and layouts, on the other hand, are not associated with a specific owner but are associated with the client as a whole (identified by a client ID)." 10.2: "...the stateid for a delegation is associated with a client ID and may be used on behalf of all the open-owners for the given client. A delegation is made to the client as a whole and not to any specific process or thread of control within it." Reported-by: Eric Meddaugh <etmsys@rit.edu> Cc: Trond Myklebust <trond.myklebust@primarydata.com> Cc: Olga Kornievskaia <aglo@umich.edu> Signed-off-by: Andrew Elble <aweits@rit.edu> Cc: stable@vger.kernel.org Signed-off-by: J. Bruce Fields <bfields@redhat.com>
Diffstat (limited to 'fs/nfsd')
-rw-r--r--fs/nfsd/nfs4state.c94
1 files changed, 84 insertions, 10 deletions
diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
index 0a697158a4ca..6411c3421870 100644
--- a/fs/nfsd/nfs4state.c
+++ b/fs/nfsd/nfs4state.c
@@ -778,16 +778,68 @@ void nfs4_unhash_stid(struct nfs4_stid *s)
778 s->sc_type = 0; 778 s->sc_type = 0;
779} 779}
780 780
781static void 781/**
782 * nfs4_get_existing_delegation - Discover if this delegation already exists
783 * @clp: a pointer to the nfs4_client we're granting a delegation to
784 * @fp: a pointer to the nfs4_file we're granting a delegation on
785 *
786 * Return:
787 * On success: NULL if an existing delegation was not found.
788 *
789 * On error: -EAGAIN if one was previously granted to this nfs4_client
790 * for this nfs4_file.
791 *
792 */
793
794static int
795nfs4_get_existing_delegation(struct nfs4_client *clp, struct nfs4_file *fp)
796{
797 struct nfs4_delegation *searchdp = NULL;
798 struct nfs4_client *searchclp = NULL;
799
800 lockdep_assert_held(&state_lock);
801 lockdep_assert_held(&fp->fi_lock);
802
803 list_for_each_entry(searchdp, &fp->fi_delegations, dl_perfile) {
804 searchclp = searchdp->dl_stid.sc_client;
805 if (clp == searchclp) {
806 return -EAGAIN;
807 }
808 }
809 return 0;
810}
811
812/**
813 * hash_delegation_locked - Add a delegation to the appropriate lists
814 * @dp: a pointer to the nfs4_delegation we are adding.
815 * @fp: a pointer to the nfs4_file we're granting a delegation on
816 *
817 * Return:
818 * On success: NULL if the delegation was successfully hashed.
819 *
820 * On error: -EAGAIN if one was previously granted to this
821 * nfs4_client for this nfs4_file. Delegation is not hashed.
822 *
823 */
824
825static int
782hash_delegation_locked(struct nfs4_delegation *dp, struct nfs4_file *fp) 826hash_delegation_locked(struct nfs4_delegation *dp, struct nfs4_file *fp)
783{ 827{
828 int status;
829 struct nfs4_client *clp = dp->dl_stid.sc_client;
830
784 lockdep_assert_held(&state_lock); 831 lockdep_assert_held(&state_lock);
785 lockdep_assert_held(&fp->fi_lock); 832 lockdep_assert_held(&fp->fi_lock);
786 833
834 status = nfs4_get_existing_delegation(clp, fp);
835 if (status)
836 return status;
837 ++fp->fi_delegees;
787 atomic_inc(&dp->dl_stid.sc_count); 838 atomic_inc(&dp->dl_stid.sc_count);
788 dp->dl_stid.sc_type = NFS4_DELEG_STID; 839 dp->dl_stid.sc_type = NFS4_DELEG_STID;
789 list_add(&dp->dl_perfile, &fp->fi_delegations); 840 list_add(&dp->dl_perfile, &fp->fi_delegations);
790 list_add(&dp->dl_perclnt, &dp->dl_stid.sc_client->cl_delegations); 841 list_add(&dp->dl_perclnt, &clp->cl_delegations);
842 return 0;
791} 843}
792 844
793static bool 845static bool
@@ -3969,6 +4021,18 @@ static struct file_lock *nfs4_alloc_init_lease(struct nfs4_file *fp, int flag)
3969 return fl; 4021 return fl;
3970} 4022}
3971 4023
4024/**
4025 * nfs4_setlease - Obtain a delegation by requesting lease from vfs layer
4026 * @dp: a pointer to the nfs4_delegation we're adding.
4027 *
4028 * Return:
4029 * On success: Return code will be 0 on success.
4030 *
4031 * On error: -EAGAIN if there was an existing delegation.
4032 * nonzero if there is an error in other cases.
4033 *
4034 */
4035
3972static int nfs4_setlease(struct nfs4_delegation *dp) 4036static int nfs4_setlease(struct nfs4_delegation *dp)
3973{ 4037{
3974 struct nfs4_file *fp = dp->dl_stid.sc_file; 4038 struct nfs4_file *fp = dp->dl_stid.sc_file;
@@ -4000,16 +4064,19 @@ static int nfs4_setlease(struct nfs4_delegation *dp)
4000 goto out_unlock; 4064 goto out_unlock;
4001 /* Race breaker */ 4065 /* Race breaker */
4002 if (fp->fi_deleg_file) { 4066 if (fp->fi_deleg_file) {
4003 status = 0; 4067 status = hash_delegation_locked(dp, fp);
4004 ++fp->fi_delegees;
4005 hash_delegation_locked(dp, fp);
4006 goto out_unlock; 4068 goto out_unlock;
4007 } 4069 }
4008 fp->fi_deleg_file = filp; 4070 fp->fi_deleg_file = filp;
4009 fp->fi_delegees = 1; 4071 fp->fi_delegees = 0;
4010 hash_delegation_locked(dp, fp); 4072 status = hash_delegation_locked(dp, fp);
4011 spin_unlock(&fp->fi_lock); 4073 spin_unlock(&fp->fi_lock);
4012 spin_unlock(&state_lock); 4074 spin_unlock(&state_lock);
4075 if (status) {
4076 /* Should never happen, this is a new fi_deleg_file */
4077 WARN_ON_ONCE(1);
4078 goto out_fput;
4079 }
4013 return 0; 4080 return 0;
4014out_unlock: 4081out_unlock:
4015 spin_unlock(&fp->fi_lock); 4082 spin_unlock(&fp->fi_lock);
@@ -4029,6 +4096,15 @@ nfs4_set_delegation(struct nfs4_client *clp, struct svc_fh *fh,
4029 if (fp->fi_had_conflict) 4096 if (fp->fi_had_conflict)
4030 return ERR_PTR(-EAGAIN); 4097 return ERR_PTR(-EAGAIN);
4031 4098
4099 spin_lock(&state_lock);
4100 spin_lock(&fp->fi_lock);
4101 status = nfs4_get_existing_delegation(clp, fp);
4102 spin_unlock(&fp->fi_lock);
4103 spin_unlock(&state_lock);
4104
4105 if (status)
4106 return ERR_PTR(status);
4107
4032 dp = alloc_init_deleg(clp, fh, odstate); 4108 dp = alloc_init_deleg(clp, fh, odstate);
4033 if (!dp) 4109 if (!dp)
4034 return ERR_PTR(-ENOMEM); 4110 return ERR_PTR(-ENOMEM);
@@ -4047,9 +4123,7 @@ nfs4_set_delegation(struct nfs4_client *clp, struct svc_fh *fh,
4047 status = -EAGAIN; 4123 status = -EAGAIN;
4048 goto out_unlock; 4124 goto out_unlock;
4049 } 4125 }
4050 ++fp->fi_delegees; 4126 status = hash_delegation_locked(dp, fp);
4051 hash_delegation_locked(dp, fp);
4052 status = 0;
4053out_unlock: 4127out_unlock:
4054 spin_unlock(&fp->fi_lock); 4128 spin_unlock(&fp->fi_lock);
4055 spin_unlock(&state_lock); 4129 spin_unlock(&state_lock);