diff options
| author | 2014-11-19 21:32:12 +1100 | |
|---|---|---|
| committer | 2014-11-19 21:32:12 +1100 | |
| commit | b10778a00d40b3d9fdaaf5891e802794781ff71c (patch) | |
| tree | 6ba4cbac86eecedc3f30650e7f764ecf00c83898 /fs/nfsd/nfs4state.c | |
| parent | integrity: do zero padding of the key id (diff) | |
| parent | Linux 3.17 (diff) | |
| download | linux-dev-b10778a00d40b3d9fdaaf5891e802794781ff71c.tar.xz linux-dev-b10778a00d40b3d9fdaaf5891e802794781ff71c.zip | |
Merge commit 'v3.17' into next
Diffstat (limited to 'fs/nfsd/nfs4state.c')
| -rw-r--r-- | fs/nfsd/nfs4state.c | 3096 | 
1 files changed, 2120 insertions, 976 deletions
| diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index 2204e1fe5725..2e80a59e7e91 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -70,13 +70,11 @@ static u64 current_sessionid = 1;  #define CURRENT_STATEID(stateid) (!memcmp((stateid), ¤tstateid, sizeof(stateid_t)))  /* forward declarations */ -static int check_for_locks(struct nfs4_file *filp, struct nfs4_lockowner *lowner); +static bool check_for_locks(struct nfs4_file *fp, struct nfs4_lockowner *lowner); +static void nfs4_free_ol_stateid(struct nfs4_stid *stid);  /* Locking: */ -/* Currently used for almost all code touching nfsv4 state: */ -static DEFINE_MUTEX(client_mutex); -  /*   * Currently used for the del_recall_lru and file hash table.  In an   * effort to decrease the scope of the client_mutex, this spinlock may @@ -84,18 +82,18 @@ static DEFINE_MUTEX(client_mutex);   */  static DEFINE_SPINLOCK(state_lock); +/* + * A waitqueue for all in-progress 4.0 CLOSE operations that are waiting for + * the refcount on the open stateid to drop. + */ +static DECLARE_WAIT_QUEUE_HEAD(close_wq); +  static struct kmem_cache *openowner_slab;  static struct kmem_cache *lockowner_slab;  static struct kmem_cache *file_slab;  static struct kmem_cache *stateid_slab;  static struct kmem_cache *deleg_slab; -void -nfs4_lock_state(void) -{ -	mutex_lock(&client_mutex); -} -  static void free_session(struct nfsd4_session *);  static bool is_session_dead(struct nfsd4_session *ses) @@ -103,12 +101,6 @@ static bool is_session_dead(struct nfsd4_session *ses)  	return ses->se_flags & NFS4_SESSION_DEAD;  } -void nfsd4_put_session(struct nfsd4_session *ses) -{ -	if (atomic_dec_and_test(&ses->se_ref) && is_session_dead(ses)) -		free_session(ses); -} -  static __be32 mark_session_dead_locked(struct nfsd4_session *ses, int ref_held_by_me)  {  	if (atomic_read(&ses->se_ref) > ref_held_by_me) @@ -117,46 +109,17 @@ static __be32 mark_session_dead_locked(struct nfsd4_session *ses, int ref_held_b  	return nfs_ok;  } -static __be32 nfsd4_get_session_locked(struct nfsd4_session *ses) -{ -	if (is_session_dead(ses)) -		return nfserr_badsession; -	atomic_inc(&ses->se_ref); -	return nfs_ok; -} - -void -nfs4_unlock_state(void) -{ -	mutex_unlock(&client_mutex); -} -  static bool is_client_expired(struct nfs4_client *clp)  {  	return clp->cl_time == 0;  } -static __be32 mark_client_expired_locked(struct nfs4_client *clp) -{ -	if (atomic_read(&clp->cl_refcount)) -		return nfserr_jukebox; -	clp->cl_time = 0; -	return nfs_ok; -} - -static __be32 mark_client_expired(struct nfs4_client *clp) +static __be32 get_client_locked(struct nfs4_client *clp)  {  	struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id); -	__be32 ret; -	spin_lock(&nn->client_lock); -	ret = mark_client_expired_locked(clp); -	spin_unlock(&nn->client_lock); -	return ret; -} +	lockdep_assert_held(&nn->client_lock); -static __be32 get_client_locked(struct nfs4_client *clp) -{  	if (is_client_expired(clp))  		return nfserr_expired;  	atomic_inc(&clp->cl_refcount); @@ -197,13 +160,17 @@ renew_client(struct nfs4_client *clp)  static void put_client_renew_locked(struct nfs4_client *clp)  { +	struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id); + +	lockdep_assert_held(&nn->client_lock); +  	if (!atomic_dec_and_test(&clp->cl_refcount))  		return;  	if (!is_client_expired(clp))  		renew_client_locked(clp);  } -void put_client_renew(struct nfs4_client *clp) +static void put_client_renew(struct nfs4_client *clp)  {  	struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id); @@ -214,6 +181,79 @@ void put_client_renew(struct nfs4_client *clp)  	spin_unlock(&nn->client_lock);  } +static __be32 nfsd4_get_session_locked(struct nfsd4_session *ses) +{ +	__be32 status; + +	if (is_session_dead(ses)) +		return nfserr_badsession; +	status = get_client_locked(ses->se_client); +	if (status) +		return status; +	atomic_inc(&ses->se_ref); +	return nfs_ok; +} + +static void nfsd4_put_session_locked(struct nfsd4_session *ses) +{ +	struct nfs4_client *clp = ses->se_client; +	struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id); + +	lockdep_assert_held(&nn->client_lock); + +	if (atomic_dec_and_test(&ses->se_ref) && is_session_dead(ses)) +		free_session(ses); +	put_client_renew_locked(clp); +} + +static void nfsd4_put_session(struct nfsd4_session *ses) +{ +	struct nfs4_client *clp = ses->se_client; +	struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id); + +	spin_lock(&nn->client_lock); +	nfsd4_put_session_locked(ses); +	spin_unlock(&nn->client_lock); +} + +static int +same_owner_str(struct nfs4_stateowner *sop, struct xdr_netobj *owner) +{ +	return (sop->so_owner.len == owner->len) && +		0 == memcmp(sop->so_owner.data, owner->data, owner->len); +} + +static struct nfs4_openowner * +find_openstateowner_str_locked(unsigned int hashval, struct nfsd4_open *open, +			struct nfs4_client *clp) +{ +	struct nfs4_stateowner *so; + +	lockdep_assert_held(&clp->cl_lock); + +	list_for_each_entry(so, &clp->cl_ownerstr_hashtbl[hashval], +			    so_strhash) { +		if (!so->so_is_open_owner) +			continue; +		if (same_owner_str(so, &open->op_owner)) { +			atomic_inc(&so->so_count); +			return openowner(so); +		} +	} +	return NULL; +} + +static struct nfs4_openowner * +find_openstateowner_str(unsigned int hashval, struct nfsd4_open *open, +			struct nfs4_client *clp) +{ +	struct nfs4_openowner *oo; + +	spin_lock(&clp->cl_lock); +	oo = find_openstateowner_str_locked(hashval, open, clp); +	spin_unlock(&clp->cl_lock); +	return oo; +}  static inline u32  opaque_hashval(const void *ptr, int nbytes) @@ -236,10 +276,11 @@ static void nfsd4_free_file(struct nfs4_file *f)  static inline void  put_nfs4_file(struct nfs4_file *fi)  { +	might_lock(&state_lock); +  	if (atomic_dec_and_lock(&fi->fi_ref, &state_lock)) {  		hlist_del(&fi->fi_hash);  		spin_unlock(&state_lock); -		iput(fi->fi_inode);  		nfsd4_free_file(fi);  	}  } @@ -250,7 +291,80 @@ get_nfs4_file(struct nfs4_file *fi)  	atomic_inc(&fi->fi_ref);  } -static int num_delegations; +static struct file * +__nfs4_get_fd(struct nfs4_file *f, int oflag) +{ +	if (f->fi_fds[oflag]) +		return get_file(f->fi_fds[oflag]); +	return NULL; +} + +static struct file * +find_writeable_file_locked(struct nfs4_file *f) +{ +	struct file *ret; + +	lockdep_assert_held(&f->fi_lock); + +	ret = __nfs4_get_fd(f, O_WRONLY); +	if (!ret) +		ret = __nfs4_get_fd(f, O_RDWR); +	return ret; +} + +static struct file * +find_writeable_file(struct nfs4_file *f) +{ +	struct file *ret; + +	spin_lock(&f->fi_lock); +	ret = find_writeable_file_locked(f); +	spin_unlock(&f->fi_lock); + +	return ret; +} + +static struct file *find_readable_file_locked(struct nfs4_file *f) +{ +	struct file *ret; + +	lockdep_assert_held(&f->fi_lock); + +	ret = __nfs4_get_fd(f, O_RDONLY); +	if (!ret) +		ret = __nfs4_get_fd(f, O_RDWR); +	return ret; +} + +static struct file * +find_readable_file(struct nfs4_file *f) +{ +	struct file *ret; + +	spin_lock(&f->fi_lock); +	ret = find_readable_file_locked(f); +	spin_unlock(&f->fi_lock); + +	return ret; +} + +static struct file * +find_any_file(struct nfs4_file *f) +{ +	struct file *ret; + +	spin_lock(&f->fi_lock); +	ret = __nfs4_get_fd(f, O_RDWR); +	if (!ret) { +		ret = __nfs4_get_fd(f, O_WRONLY); +		if (!ret) +			ret = __nfs4_get_fd(f, O_RDONLY); +	} +	spin_unlock(&f->fi_lock); +	return ret; +} + +static atomic_long_t num_delegations;  unsigned long max_delegations;  /* @@ -262,12 +376,11 @@ unsigned long max_delegations;  #define OWNER_HASH_SIZE             (1 << OWNER_HASH_BITS)  #define OWNER_HASH_MASK             (OWNER_HASH_SIZE - 1) -static unsigned int ownerstr_hashval(u32 clientid, struct xdr_netobj *ownername) +static unsigned int ownerstr_hashval(struct xdr_netobj *ownername)  {  	unsigned int ret;  	ret = opaque_hashval(ownername->data, ownername->len); -	ret += clientid;  	return ret & OWNER_HASH_MASK;  } @@ -275,75 +388,124 @@ static unsigned int ownerstr_hashval(u32 clientid, struct xdr_netobj *ownername)  #define FILE_HASH_BITS                   8  #define FILE_HASH_SIZE                  (1 << FILE_HASH_BITS) -static unsigned int file_hashval(struct inode *ino) +static unsigned int nfsd_fh_hashval(struct knfsd_fh *fh) +{ +	return jhash2(fh->fh_base.fh_pad, XDR_QUADLEN(fh->fh_size), 0); +} + +static unsigned int file_hashval(struct knfsd_fh *fh) +{ +	return nfsd_fh_hashval(fh) & (FILE_HASH_SIZE - 1); +} + +static bool nfsd_fh_match(struct knfsd_fh *fh1, struct knfsd_fh *fh2)  { -	/* XXX: why are we hashing on inode pointer, anyway? */ -	return hash_ptr(ino, FILE_HASH_BITS); +	return fh1->fh_size == fh2->fh_size && +		!memcmp(fh1->fh_base.fh_pad, +				fh2->fh_base.fh_pad, +				fh1->fh_size);  }  static struct hlist_head file_hashtbl[FILE_HASH_SIZE]; -static void __nfs4_file_get_access(struct nfs4_file *fp, int oflag) +static void +__nfs4_file_get_access(struct nfs4_file *fp, u32 access)  { -	WARN_ON_ONCE(!(fp->fi_fds[oflag] || fp->fi_fds[O_RDWR])); -	atomic_inc(&fp->fi_access[oflag]); +	lockdep_assert_held(&fp->fi_lock); + +	if (access & NFS4_SHARE_ACCESS_WRITE) +		atomic_inc(&fp->fi_access[O_WRONLY]); +	if (access & NFS4_SHARE_ACCESS_READ) +		atomic_inc(&fp->fi_access[O_RDONLY]);  } -static void nfs4_file_get_access(struct nfs4_file *fp, int oflag) +static __be32 +nfs4_file_get_access(struct nfs4_file *fp, u32 access)  { -	if (oflag == O_RDWR) { -		__nfs4_file_get_access(fp, O_RDONLY); -		__nfs4_file_get_access(fp, O_WRONLY); -	} else -		__nfs4_file_get_access(fp, oflag); +	lockdep_assert_held(&fp->fi_lock); + +	/* Does this access mode make sense? */ +	if (access & ~NFS4_SHARE_ACCESS_BOTH) +		return nfserr_inval; + +	/* Does it conflict with a deny mode already set? */ +	if ((access & fp->fi_share_deny) != 0) +		return nfserr_share_denied; + +	__nfs4_file_get_access(fp, access); +	return nfs_ok;  } -static void nfs4_file_put_fd(struct nfs4_file *fp, int oflag) +static __be32 nfs4_file_check_deny(struct nfs4_file *fp, u32 deny)  { -	if (fp->fi_fds[oflag]) { -		fput(fp->fi_fds[oflag]); -		fp->fi_fds[oflag] = NULL; +	/* Common case is that there is no deny mode. */ +	if (deny) { +		/* Does this deny mode make sense? */ +		if (deny & ~NFS4_SHARE_DENY_BOTH) +			return nfserr_inval; + +		if ((deny & NFS4_SHARE_DENY_READ) && +		    atomic_read(&fp->fi_access[O_RDONLY])) +			return nfserr_share_denied; + +		if ((deny & NFS4_SHARE_DENY_WRITE) && +		    atomic_read(&fp->fi_access[O_WRONLY])) +			return nfserr_share_denied;  	} +	return nfs_ok;  }  static void __nfs4_file_put_access(struct nfs4_file *fp, int oflag)  { -	if (atomic_dec_and_test(&fp->fi_access[oflag])) { -		nfs4_file_put_fd(fp, oflag); +	might_lock(&fp->fi_lock); + +	if (atomic_dec_and_lock(&fp->fi_access[oflag], &fp->fi_lock)) { +		struct file *f1 = NULL; +		struct file *f2 = NULL; + +		swap(f1, fp->fi_fds[oflag]);  		if (atomic_read(&fp->fi_access[1 - oflag]) == 0) -			nfs4_file_put_fd(fp, O_RDWR); +			swap(f2, fp->fi_fds[O_RDWR]); +		spin_unlock(&fp->fi_lock); +		if (f1) +			fput(f1); +		if (f2) +			fput(f2);  	}  } -static void nfs4_file_put_access(struct nfs4_file *fp, int oflag) +static void nfs4_file_put_access(struct nfs4_file *fp, u32 access)  { -	if (oflag == O_RDWR) { -		__nfs4_file_put_access(fp, O_RDONLY); +	WARN_ON_ONCE(access & ~NFS4_SHARE_ACCESS_BOTH); + +	if (access & NFS4_SHARE_ACCESS_WRITE)  		__nfs4_file_put_access(fp, O_WRONLY); -	} else -		__nfs4_file_put_access(fp, oflag); +	if (access & NFS4_SHARE_ACCESS_READ) +		__nfs4_file_put_access(fp, O_RDONLY);  } -static struct nfs4_stid *nfs4_alloc_stid(struct nfs4_client *cl, struct -kmem_cache *slab) +static struct nfs4_stid *nfs4_alloc_stid(struct nfs4_client *cl, +					 struct kmem_cache *slab)  { -	struct idr *stateids = &cl->cl_stateids;  	struct nfs4_stid *stid;  	int new_id; -	stid = kmem_cache_alloc(slab, GFP_KERNEL); +	stid = kmem_cache_zalloc(slab, GFP_KERNEL);  	if (!stid)  		return NULL; -	new_id = idr_alloc_cyclic(stateids, stid, 0, 0, GFP_KERNEL); +	idr_preload(GFP_KERNEL); +	spin_lock(&cl->cl_lock); +	new_id = idr_alloc_cyclic(&cl->cl_stateids, stid, 0, 0, GFP_NOWAIT); +	spin_unlock(&cl->cl_lock); +	idr_preload_end();  	if (new_id < 0)  		goto out_free;  	stid->sc_client = cl; -	stid->sc_type = 0;  	stid->sc_stateid.si_opaque.so_id = new_id;  	stid->sc_stateid.si_opaque.so_clid = cl->cl_clientid;  	/* Will be incremented before return to client: */ -	stid->sc_stateid.si_generation = 0; +	atomic_set(&stid->sc_count, 1);  	/*  	 * It shouldn't be a problem to reuse an opaque stateid value. @@ -360,9 +522,24 @@ out_free:  	return NULL;  } -static struct nfs4_ol_stateid * nfs4_alloc_stateid(struct nfs4_client *clp) +static struct nfs4_ol_stateid * nfs4_alloc_open_stateid(struct nfs4_client *clp)  { -	return openlockstateid(nfs4_alloc_stid(clp, stateid_slab)); +	struct nfs4_stid *stid; +	struct nfs4_ol_stateid *stp; + +	stid = nfs4_alloc_stid(clp, stateid_slab); +	if (!stid) +		return NULL; + +	stp = openlockstateid(stid); +	stp->st_stid.sc_free = nfs4_free_ol_stateid; +	return stp; +} + +static void nfs4_free_deleg(struct nfs4_stid *stid) +{ +	kmem_cache_free(deleg_slab, stid); +	atomic_long_dec(&num_delegations);  }  /* @@ -379,10 +556,11 @@ static struct nfs4_ol_stateid * nfs4_alloc_stateid(struct nfs4_client *clp)   * Each filter is 256 bits.  We hash the filehandle to 32bit and use the   * low 3 bytes as hash-table indices.   * - * 'state_lock', which is always held when block_delegations() is called, + * 'blocked_delegations_lock', which is always taken in block_delegations(),   * is used to manage concurrent access.  Testing does not need the lock   * except when swapping the two filters.   */ +static DEFINE_SPINLOCK(blocked_delegations_lock);  static struct bloom_pair {  	int	entries, old_entries;  	time_t	swap_time; @@ -398,7 +576,7 @@ static int delegation_blocked(struct knfsd_fh *fh)  	if (bd->entries == 0)  		return 0;  	if (seconds_since_boot() - bd->swap_time > 30) { -		spin_lock(&state_lock); +		spin_lock(&blocked_delegations_lock);  		if (seconds_since_boot() - bd->swap_time > 30) {  			bd->entries -= bd->old_entries;  			bd->old_entries = bd->entries; @@ -407,7 +585,7 @@ static int delegation_blocked(struct knfsd_fh *fh)  			bd->new = 1-bd->new;  			bd->swap_time = seconds_since_boot();  		} -		spin_unlock(&state_lock); +		spin_unlock(&blocked_delegations_lock);  	}  	hash = arch_fast_hash(&fh->fh_base, fh->fh_size, 0);  	if (test_bit(hash&255, bd->set[0]) && @@ -430,69 +608,73 @@ static void block_delegations(struct knfsd_fh *fh)  	hash = arch_fast_hash(&fh->fh_base, fh->fh_size, 0); +	spin_lock(&blocked_delegations_lock);  	__set_bit(hash&255, bd->set[bd->new]);  	__set_bit((hash>>8)&255, bd->set[bd->new]);  	__set_bit((hash>>16)&255, bd->set[bd->new]);  	if (bd->entries == 0)  		bd->swap_time = seconds_since_boot();  	bd->entries += 1; +	spin_unlock(&blocked_delegations_lock);  }  static struct nfs4_delegation * -alloc_init_deleg(struct nfs4_client *clp, struct nfs4_ol_stateid *stp, struct svc_fh *current_fh) +alloc_init_deleg(struct nfs4_client *clp, struct svc_fh *current_fh)  {  	struct nfs4_delegation *dp; +	long n;  	dprintk("NFSD alloc_init_deleg\n"); -	if (num_delegations > max_delegations) -		return NULL; +	n = atomic_long_inc_return(&num_delegations); +	if (n < 0 || n > max_delegations) +		goto out_dec;  	if (delegation_blocked(¤t_fh->fh_handle)) -		return NULL; +		goto out_dec;  	dp = delegstateid(nfs4_alloc_stid(clp, deleg_slab));  	if (dp == NULL) -		return dp; +		goto out_dec; + +	dp->dl_stid.sc_free = nfs4_free_deleg;  	/*  	 * delegation seqid's are never incremented.  The 4.1 special  	 * meaning of seqid 0 isn't meaningful, really, but let's avoid  	 * 0 anyway just for consistency and use 1:  	 */  	dp->dl_stid.sc_stateid.si_generation = 1; -	num_delegations++;  	INIT_LIST_HEAD(&dp->dl_perfile);  	INIT_LIST_HEAD(&dp->dl_perclnt);  	INIT_LIST_HEAD(&dp->dl_recall_lru); -	dp->dl_file = NULL;  	dp->dl_type = NFS4_OPEN_DELEGATE_READ; -	fh_copy_shallow(&dp->dl_fh, ¤t_fh->fh_handle); -	dp->dl_time = 0; -	atomic_set(&dp->dl_count, 1); -	nfsd4_init_callback(&dp->dl_recall); +	INIT_WORK(&dp->dl_recall.cb_work, nfsd4_run_cb_recall);  	return dp; +out_dec: +	atomic_long_dec(&num_delegations); +	return NULL;  } -static void remove_stid(struct nfs4_stid *s) +void +nfs4_put_stid(struct nfs4_stid *s)  { -	struct idr *stateids = &s->sc_client->cl_stateids; +	struct nfs4_file *fp = s->sc_file; +	struct nfs4_client *clp = s->sc_client; -	idr_remove(stateids, s->sc_stateid.si_opaque.so_id); -} +	might_lock(&clp->cl_lock); -static void nfs4_free_stid(struct kmem_cache *slab, struct nfs4_stid *s) -{ -	kmem_cache_free(slab, s); -} - -void -nfs4_put_delegation(struct nfs4_delegation *dp) -{ -	if (atomic_dec_and_test(&dp->dl_count)) { -		nfs4_free_stid(deleg_slab, &dp->dl_stid); -		num_delegations--; +	if (!atomic_dec_and_lock(&s->sc_count, &clp->cl_lock)) { +		wake_up_all(&close_wq); +		return;  	} +	idr_remove(&clp->cl_stateids, s->sc_stateid.si_opaque.so_id); +	spin_unlock(&clp->cl_lock); +	s->sc_free(s); +	if (fp) +		put_nfs4_file(fp);  }  static void nfs4_put_deleg_lease(struct nfs4_file *fp)  { +	lockdep_assert_held(&state_lock); +  	if (!fp->fi_lease)  		return;  	if (atomic_dec_and_test(&fp->fi_delegees)) { @@ -512,54 +694,54 @@ static void  hash_delegation_locked(struct nfs4_delegation *dp, struct nfs4_file *fp)  {  	lockdep_assert_held(&state_lock); +	lockdep_assert_held(&fp->fi_lock); +	atomic_inc(&dp->dl_stid.sc_count);  	dp->dl_stid.sc_type = NFS4_DELEG_STID;  	list_add(&dp->dl_perfile, &fp->fi_delegations);  	list_add(&dp->dl_perclnt, &dp->dl_stid.sc_client->cl_delegations);  } -/* Called under the state lock. */  static void -unhash_delegation(struct nfs4_delegation *dp) +unhash_delegation_locked(struct nfs4_delegation *dp)  { -	spin_lock(&state_lock); -	list_del_init(&dp->dl_perclnt); -	list_del_init(&dp->dl_perfile); -	list_del_init(&dp->dl_recall_lru); -	spin_unlock(&state_lock); -	if (dp->dl_file) { -		nfs4_put_deleg_lease(dp->dl_file); -		put_nfs4_file(dp->dl_file); -		dp->dl_file = NULL; -	} -} - +	struct nfs4_file *fp = dp->dl_stid.sc_file; +	lockdep_assert_held(&state_lock); -static void destroy_revoked_delegation(struct nfs4_delegation *dp) -{ +	dp->dl_stid.sc_type = NFS4_CLOSED_DELEG_STID; +	/* Ensure that deleg break won't try to requeue it */ +	++dp->dl_time; +	spin_lock(&fp->fi_lock); +	list_del_init(&dp->dl_perclnt);  	list_del_init(&dp->dl_recall_lru); -	remove_stid(&dp->dl_stid); -	nfs4_put_delegation(dp); +	list_del_init(&dp->dl_perfile); +	spin_unlock(&fp->fi_lock); +	if (fp) +		nfs4_put_deleg_lease(fp);  }  static void destroy_delegation(struct nfs4_delegation *dp)  { -	unhash_delegation(dp); -	remove_stid(&dp->dl_stid); -	nfs4_put_delegation(dp); +	spin_lock(&state_lock); +	unhash_delegation_locked(dp); +	spin_unlock(&state_lock); +	nfs4_put_stid(&dp->dl_stid);  }  static void revoke_delegation(struct nfs4_delegation *dp)  {  	struct nfs4_client *clp = dp->dl_stid.sc_client; +	WARN_ON(!list_empty(&dp->dl_recall_lru)); +  	if (clp->cl_minorversion == 0) -		destroy_delegation(dp); +		nfs4_put_stid(&dp->dl_stid);  	else { -		unhash_delegation(dp);  		dp->dl_stid.sc_type = NFS4_REVOKED_DELEG_STID; +		spin_lock(&clp->cl_lock);  		list_add(&dp->dl_recall_lru, &clp->cl_revoked); +		spin_unlock(&clp->cl_lock);  	}  } @@ -607,57 +789,62 @@ bmap_to_share_mode(unsigned long bmap) {  	return access;  } -static bool -test_share(struct nfs4_ol_stateid *stp, struct nfsd4_open *open) { -	unsigned int access, deny; - -	access = bmap_to_share_mode(stp->st_access_bmap); -	deny = bmap_to_share_mode(stp->st_deny_bmap); -	if ((access & open->op_share_deny) || (deny & open->op_share_access)) -		return false; -	return true; -} -  /* set share access for a given stateid */  static inline void  set_access(u32 access, struct nfs4_ol_stateid *stp)  { -	__set_bit(access, &stp->st_access_bmap); +	unsigned char mask = 1 << access; + +	WARN_ON_ONCE(access > NFS4_SHARE_ACCESS_BOTH); +	stp->st_access_bmap |= mask;  }  /* clear share access for a given stateid */  static inline void  clear_access(u32 access, struct nfs4_ol_stateid *stp)  { -	__clear_bit(access, &stp->st_access_bmap); +	unsigned char mask = 1 << access; + +	WARN_ON_ONCE(access > NFS4_SHARE_ACCESS_BOTH); +	stp->st_access_bmap &= ~mask;  }  /* test whether a given stateid has access */  static inline bool  test_access(u32 access, struct nfs4_ol_stateid *stp)  { -	return test_bit(access, &stp->st_access_bmap); +	unsigned char mask = 1 << access; + +	return (bool)(stp->st_access_bmap & mask);  }  /* set share deny for a given stateid */  static inline void -set_deny(u32 access, struct nfs4_ol_stateid *stp) +set_deny(u32 deny, struct nfs4_ol_stateid *stp)  { -	__set_bit(access, &stp->st_deny_bmap); +	unsigned char mask = 1 << deny; + +	WARN_ON_ONCE(deny > NFS4_SHARE_DENY_BOTH); +	stp->st_deny_bmap |= mask;  }  /* clear share deny for a given stateid */  static inline void -clear_deny(u32 access, struct nfs4_ol_stateid *stp) +clear_deny(u32 deny, struct nfs4_ol_stateid *stp)  { -	__clear_bit(access, &stp->st_deny_bmap); +	unsigned char mask = 1 << deny; + +	WARN_ON_ONCE(deny > NFS4_SHARE_DENY_BOTH); +	stp->st_deny_bmap &= ~mask;  }  /* test whether a given stateid is denying specific access */  static inline bool -test_deny(u32 access, struct nfs4_ol_stateid *stp) +test_deny(u32 deny, struct nfs4_ol_stateid *stp)  { -	return test_bit(access, &stp->st_deny_bmap); +	unsigned char mask = 1 << deny; + +	return (bool)(stp->st_deny_bmap & mask);  }  static int nfs4_access_to_omode(u32 access) @@ -674,138 +861,283 @@ static int nfs4_access_to_omode(u32 access)  	return O_RDONLY;  } +/* + * A stateid that had a deny mode associated with it is being released + * or downgraded. Recalculate the deny mode on the file. + */ +static void +recalculate_deny_mode(struct nfs4_file *fp) +{ +	struct nfs4_ol_stateid *stp; + +	spin_lock(&fp->fi_lock); +	fp->fi_share_deny = 0; +	list_for_each_entry(stp, &fp->fi_stateids, st_perfile) +		fp->fi_share_deny |= bmap_to_share_mode(stp->st_deny_bmap); +	spin_unlock(&fp->fi_lock); +} + +static void +reset_union_bmap_deny(u32 deny, struct nfs4_ol_stateid *stp) +{ +	int i; +	bool change = false; + +	for (i = 1; i < 4; i++) { +		if ((i & deny) != i) { +			change = true; +			clear_deny(i, stp); +		} +	} + +	/* Recalculate per-file deny mode if there was a change */ +	if (change) +		recalculate_deny_mode(stp->st_stid.sc_file); +} +  /* release all access and file references for a given stateid */  static void  release_all_access(struct nfs4_ol_stateid *stp)  {  	int i; +	struct nfs4_file *fp = stp->st_stid.sc_file; + +	if (fp && stp->st_deny_bmap != 0) +		recalculate_deny_mode(fp);  	for (i = 1; i < 4; i++) {  		if (test_access(i, stp)) -			nfs4_file_put_access(stp->st_file, -					     nfs4_access_to_omode(i)); +			nfs4_file_put_access(stp->st_stid.sc_file, i);  		clear_access(i, stp);  	}  } -static void unhash_generic_stateid(struct nfs4_ol_stateid *stp) +static void nfs4_put_stateowner(struct nfs4_stateowner *sop)  { +	struct nfs4_client *clp = sop->so_client; + +	might_lock(&clp->cl_lock); + +	if (!atomic_dec_and_lock(&sop->so_count, &clp->cl_lock)) +		return; +	sop->so_ops->so_unhash(sop); +	spin_unlock(&clp->cl_lock); +	kfree(sop->so_owner.data); +	sop->so_ops->so_free(sop); +} + +static void unhash_ol_stateid(struct nfs4_ol_stateid *stp) +{ +	struct nfs4_file *fp = stp->st_stid.sc_file; + +	lockdep_assert_held(&stp->st_stateowner->so_client->cl_lock); + +	spin_lock(&fp->fi_lock);  	list_del(&stp->st_perfile); +	spin_unlock(&fp->fi_lock);  	list_del(&stp->st_perstateowner);  } -static void close_generic_stateid(struct nfs4_ol_stateid *stp) +static void nfs4_free_ol_stateid(struct nfs4_stid *stid)  { +	struct nfs4_ol_stateid *stp = openlockstateid(stid); +  	release_all_access(stp); -	put_nfs4_file(stp->st_file); -	stp->st_file = NULL; +	if (stp->st_stateowner) +		nfs4_put_stateowner(stp->st_stateowner); +	kmem_cache_free(stateid_slab, stid);  } -static void free_generic_stateid(struct nfs4_ol_stateid *stp) +static void nfs4_free_lock_stateid(struct nfs4_stid *stid)  { -	remove_stid(&stp->st_stid); -	nfs4_free_stid(stateid_slab, &stp->st_stid); +	struct nfs4_ol_stateid *stp = openlockstateid(stid); +	struct nfs4_lockowner *lo = lockowner(stp->st_stateowner); +	struct file *file; + +	file = find_any_file(stp->st_stid.sc_file); +	if (file) +		filp_close(file, (fl_owner_t)lo); +	nfs4_free_ol_stateid(stid);  } -static void release_lock_stateid(struct nfs4_ol_stateid *stp) +/* + * Put the persistent reference to an already unhashed generic stateid, while + * holding the cl_lock. If it's the last reference, then put it onto the + * reaplist for later destruction. + */ +static void put_ol_stateid_locked(struct nfs4_ol_stateid *stp, +				       struct list_head *reaplist)  { -	struct file *file; +	struct nfs4_stid *s = &stp->st_stid; +	struct nfs4_client *clp = s->sc_client; + +	lockdep_assert_held(&clp->cl_lock); -	unhash_generic_stateid(stp); +	WARN_ON_ONCE(!list_empty(&stp->st_locks)); + +	if (!atomic_dec_and_test(&s->sc_count)) { +		wake_up_all(&close_wq); +		return; +	} + +	idr_remove(&clp->cl_stateids, s->sc_stateid.si_opaque.so_id); +	list_add(&stp->st_locks, reaplist); +} + +static void unhash_lock_stateid(struct nfs4_ol_stateid *stp) +{ +	struct nfs4_openowner *oo = openowner(stp->st_openstp->st_stateowner); + +	lockdep_assert_held(&oo->oo_owner.so_client->cl_lock); + +	list_del_init(&stp->st_locks); +	unhash_ol_stateid(stp);  	unhash_stid(&stp->st_stid); -	file = find_any_file(stp->st_file); -	if (file) -		locks_remove_posix(file, (fl_owner_t)lockowner(stp->st_stateowner)); -	close_generic_stateid(stp); -	free_generic_stateid(stp);  } -static void unhash_lockowner(struct nfs4_lockowner *lo) +static void release_lock_stateid(struct nfs4_ol_stateid *stp)  { -	struct nfs4_ol_stateid *stp; +	struct nfs4_openowner *oo = openowner(stp->st_openstp->st_stateowner); -	list_del(&lo->lo_owner.so_strhash); -	list_del(&lo->lo_perstateid); -	list_del(&lo->lo_owner_ino_hash); -	while (!list_empty(&lo->lo_owner.so_stateids)) { -		stp = list_first_entry(&lo->lo_owner.so_stateids, -				struct nfs4_ol_stateid, st_perstateowner); -		release_lock_stateid(stp); -	} +	spin_lock(&oo->oo_owner.so_client->cl_lock); +	unhash_lock_stateid(stp); +	spin_unlock(&oo->oo_owner.so_client->cl_lock); +	nfs4_put_stid(&stp->st_stid);  } -static void nfs4_free_lockowner(struct nfs4_lockowner *lo) +static void unhash_lockowner_locked(struct nfs4_lockowner *lo)  { -	kfree(lo->lo_owner.so_owner.data); -	kmem_cache_free(lockowner_slab, lo); +	struct nfs4_client *clp = lo->lo_owner.so_client; + +	lockdep_assert_held(&clp->cl_lock); + +	list_del_init(&lo->lo_owner.so_strhash); +} + +/* + * Free a list of generic stateids that were collected earlier after being + * fully unhashed. + */ +static void +free_ol_stateid_reaplist(struct list_head *reaplist) +{ +	struct nfs4_ol_stateid *stp; +	struct nfs4_file *fp; + +	might_sleep(); + +	while (!list_empty(reaplist)) { +		stp = list_first_entry(reaplist, struct nfs4_ol_stateid, +				       st_locks); +		list_del(&stp->st_locks); +		fp = stp->st_stid.sc_file; +		stp->st_stid.sc_free(&stp->st_stid); +		if (fp) +			put_nfs4_file(fp); +	}  }  static void release_lockowner(struct nfs4_lockowner *lo)  { -	unhash_lockowner(lo); -	nfs4_free_lockowner(lo); +	struct nfs4_client *clp = lo->lo_owner.so_client; +	struct nfs4_ol_stateid *stp; +	struct list_head reaplist; + +	INIT_LIST_HEAD(&reaplist); + +	spin_lock(&clp->cl_lock); +	unhash_lockowner_locked(lo); +	while (!list_empty(&lo->lo_owner.so_stateids)) { +		stp = list_first_entry(&lo->lo_owner.so_stateids, +				struct nfs4_ol_stateid, st_perstateowner); +		unhash_lock_stateid(stp); +		put_ol_stateid_locked(stp, &reaplist); +	} +	spin_unlock(&clp->cl_lock); +	free_ol_stateid_reaplist(&reaplist); +	nfs4_put_stateowner(&lo->lo_owner);  } -static void -release_stateid_lockowners(struct nfs4_ol_stateid *open_stp) +static void release_open_stateid_locks(struct nfs4_ol_stateid *open_stp, +				       struct list_head *reaplist)  { -	struct nfs4_lockowner *lo; +	struct nfs4_ol_stateid *stp; -	while (!list_empty(&open_stp->st_lockowners)) { -		lo = list_entry(open_stp->st_lockowners.next, -				struct nfs4_lockowner, lo_perstateid); -		release_lockowner(lo); +	while (!list_empty(&open_stp->st_locks)) { +		stp = list_entry(open_stp->st_locks.next, +				struct nfs4_ol_stateid, st_locks); +		unhash_lock_stateid(stp); +		put_ol_stateid_locked(stp, reaplist);  	}  } -static void unhash_open_stateid(struct nfs4_ol_stateid *stp) +static void unhash_open_stateid(struct nfs4_ol_stateid *stp, +				struct list_head *reaplist)  { -	unhash_generic_stateid(stp); -	release_stateid_lockowners(stp); -	close_generic_stateid(stp); +	lockdep_assert_held(&stp->st_stid.sc_client->cl_lock); + +	unhash_ol_stateid(stp); +	release_open_stateid_locks(stp, reaplist);  }  static void release_open_stateid(struct nfs4_ol_stateid *stp)  { -	unhash_open_stateid(stp); -	free_generic_stateid(stp); +	LIST_HEAD(reaplist); + +	spin_lock(&stp->st_stid.sc_client->cl_lock); +	unhash_open_stateid(stp, &reaplist); +	put_ol_stateid_locked(stp, &reaplist); +	spin_unlock(&stp->st_stid.sc_client->cl_lock); +	free_ol_stateid_reaplist(&reaplist);  } -static void unhash_openowner(struct nfs4_openowner *oo) +static void unhash_openowner_locked(struct nfs4_openowner *oo)  { -	struct nfs4_ol_stateid *stp; +	struct nfs4_client *clp = oo->oo_owner.so_client; -	list_del(&oo->oo_owner.so_strhash); -	list_del(&oo->oo_perclient); -	while (!list_empty(&oo->oo_owner.so_stateids)) { -		stp = list_first_entry(&oo->oo_owner.so_stateids, -				struct nfs4_ol_stateid, st_perstateowner); -		release_open_stateid(stp); -	} +	lockdep_assert_held(&clp->cl_lock); + +	list_del_init(&oo->oo_owner.so_strhash); +	list_del_init(&oo->oo_perclient);  }  static void release_last_closed_stateid(struct nfs4_openowner *oo)  { -	struct nfs4_ol_stateid *s = oo->oo_last_closed_stid; +	struct nfsd_net *nn = net_generic(oo->oo_owner.so_client->net, +					  nfsd_net_id); +	struct nfs4_ol_stateid *s; +	spin_lock(&nn->client_lock); +	s = oo->oo_last_closed_stid;  	if (s) { -		free_generic_stateid(s); +		list_del_init(&oo->oo_close_lru);  		oo->oo_last_closed_stid = NULL;  	} -} - -static void nfs4_free_openowner(struct nfs4_openowner *oo) -{ -	kfree(oo->oo_owner.so_owner.data); -	kmem_cache_free(openowner_slab, oo); +	spin_unlock(&nn->client_lock); +	if (s) +		nfs4_put_stid(&s->st_stid);  }  static void release_openowner(struct nfs4_openowner *oo)  { -	unhash_openowner(oo); -	list_del(&oo->oo_close_lru); +	struct nfs4_ol_stateid *stp; +	struct nfs4_client *clp = oo->oo_owner.so_client; +	struct list_head reaplist; + +	INIT_LIST_HEAD(&reaplist); + +	spin_lock(&clp->cl_lock); +	unhash_openowner_locked(oo); +	while (!list_empty(&oo->oo_owner.so_stateids)) { +		stp = list_first_entry(&oo->oo_owner.so_stateids, +				struct nfs4_ol_stateid, st_perstateowner); +		unhash_open_stateid(stp, &reaplist); +		put_ol_stateid_locked(stp, &reaplist); +	} +	spin_unlock(&clp->cl_lock); +	free_ol_stateid_reaplist(&reaplist);  	release_last_closed_stateid(oo); -	nfs4_free_openowner(oo); +	nfs4_put_stateowner(&oo->oo_owner);  }  static inline int @@ -842,7 +1174,7 @@ void nfsd4_bump_seqid(struct nfsd4_compound_state *cstate, __be32 nfserr)  		return;  	if (!seqid_mutating_err(ntohl(nfserr))) { -		cstate->replay_owner = NULL; +		nfsd4_cstate_clear_replay(cstate);  		return;  	}  	if (!so) @@ -1030,10 +1362,8 @@ static void nfsd4_init_conn(struct svc_rqst *rqstp, struct nfsd4_conn *conn, str  	if (ret)  		/* oops; xprt is already down: */  		nfsd4_conn_lost(&conn->cn_xpt_user); -	if (conn->cn_flags & NFS4_CDFC4_BACK) { -		/* callback channel may be back up */ -		nfsd4_probe_callback(ses->se_client); -	} +	/* We may have gained or lost a callback channel: */ +	nfsd4_probe_callback_sync(ses->se_client);  }  static struct nfsd4_conn *alloc_conn_from_crses(struct svc_rqst *rqstp, struct nfsd4_create_session *cses) @@ -1073,9 +1403,6 @@ static void __free_session(struct nfsd4_session *ses)  static void free_session(struct nfsd4_session *ses)  { -	struct nfsd_net *nn = net_generic(ses->se_client->net, nfsd_net_id); - -	lockdep_assert_held(&nn->client_lock);  	nfsd4_del_conns(ses);  	nfsd4_put_drc_mem(&ses->se_fchannel);  	__free_session(ses); @@ -1097,12 +1424,10 @@ static void init_session(struct svc_rqst *rqstp, struct nfsd4_session *new, stru  	new->se_cb_sec = cses->cb_sec;  	atomic_set(&new->se_ref, 0);  	idx = hash_sessionid(&new->se_sessionid); -	spin_lock(&nn->client_lock);  	list_add(&new->se_hash, &nn->sessionid_hashtbl[idx]);  	spin_lock(&clp->cl_lock);  	list_add(&new->se_perclnt, &clp->cl_sessions);  	spin_unlock(&clp->cl_lock); -	spin_unlock(&nn->client_lock);  	if (cses->flags & SESSION4_BACK_CHAN) {  		struct sockaddr *sa = svc_addr(rqstp); @@ -1120,12 +1445,14 @@ static void init_session(struct svc_rqst *rqstp, struct nfsd4_session *new, stru  /* caller must hold client_lock */  static struct nfsd4_session * -find_in_sessionid_hashtbl(struct nfs4_sessionid *sessionid, struct net *net) +__find_in_sessionid_hashtbl(struct nfs4_sessionid *sessionid, struct net *net)  {  	struct nfsd4_session *elem;  	int idx;  	struct nfsd_net *nn = net_generic(net, nfsd_net_id); +	lockdep_assert_held(&nn->client_lock); +  	dump_sessionid(__func__, sessionid);  	idx = hash_sessionid(sessionid);  	/* Search in the appropriate list */ @@ -1140,10 +1467,33 @@ find_in_sessionid_hashtbl(struct nfs4_sessionid *sessionid, struct net *net)  	return NULL;  } +static struct nfsd4_session * +find_in_sessionid_hashtbl(struct nfs4_sessionid *sessionid, struct net *net, +		__be32 *ret) +{ +	struct nfsd4_session *session; +	__be32 status = nfserr_badsession; + +	session = __find_in_sessionid_hashtbl(sessionid, net); +	if (!session) +		goto out; +	status = nfsd4_get_session_locked(session); +	if (status) +		session = NULL; +out: +	*ret = status; +	return session; +} +  /* caller must hold client_lock */  static void  unhash_session(struct nfsd4_session *ses)  { +	struct nfs4_client *clp = ses->se_client; +	struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id); + +	lockdep_assert_held(&nn->client_lock); +  	list_del(&ses->se_hash);  	spin_lock(&ses->se_client->cl_lock);  	list_del(&ses->se_perclnt); @@ -1169,15 +1519,20 @@ STALE_CLIENTID(clientid_t *clid, struct nfsd_net *nn)  static struct nfs4_client *alloc_client(struct xdr_netobj name)  {  	struct nfs4_client *clp; +	int i;  	clp = kzalloc(sizeof(struct nfs4_client), GFP_KERNEL);  	if (clp == NULL)  		return NULL;  	clp->cl_name.data = kmemdup(name.data, name.len, GFP_KERNEL); -	if (clp->cl_name.data == NULL) { -		kfree(clp); -		return NULL; -	} +	if (clp->cl_name.data == NULL) +		goto err_no_name; +	clp->cl_ownerstr_hashtbl = kmalloc(sizeof(struct list_head) * +			OWNER_HASH_SIZE, GFP_KERNEL); +	if (!clp->cl_ownerstr_hashtbl) +		goto err_no_hashtbl; +	for (i = 0; i < OWNER_HASH_SIZE; i++) +		INIT_LIST_HEAD(&clp->cl_ownerstr_hashtbl[i]);  	clp->cl_name.len = name.len;  	INIT_LIST_HEAD(&clp->cl_sessions);  	idr_init(&clp->cl_stateids); @@ -1192,14 +1547,16 @@ static struct nfs4_client *alloc_client(struct xdr_netobj name)  	spin_lock_init(&clp->cl_lock);  	rpc_init_wait_queue(&clp->cl_cb_waitq, "Backchannel slot table");  	return clp; +err_no_hashtbl: +	kfree(clp->cl_name.data); +err_no_name: +	kfree(clp); +	return NULL;  }  static void  free_client(struct nfs4_client *clp)  { -	struct nfsd_net __maybe_unused *nn = net_generic(clp->net, nfsd_net_id); - -	lockdep_assert_held(&nn->client_lock);  	while (!list_empty(&clp->cl_sessions)) {  		struct nfsd4_session *ses;  		ses = list_entry(clp->cl_sessions.next, struct nfsd4_session, @@ -1210,18 +1567,32 @@ free_client(struct nfs4_client *clp)  	}  	rpc_destroy_wait_queue(&clp->cl_cb_waitq);  	free_svc_cred(&clp->cl_cred); +	kfree(clp->cl_ownerstr_hashtbl);  	kfree(clp->cl_name.data);  	idr_destroy(&clp->cl_stateids);  	kfree(clp);  }  /* must be called under the client_lock */ -static inline void +static void  unhash_client_locked(struct nfs4_client *clp)  { +	struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);  	struct nfsd4_session *ses; -	list_del(&clp->cl_lru); +	lockdep_assert_held(&nn->client_lock); + +	/* Mark the client as expired! */ +	clp->cl_time = 0; +	/* Make it invisible */ +	if (!list_empty(&clp->cl_idhash)) { +		list_del_init(&clp->cl_idhash); +		if (test_bit(NFSD4_CLIENT_CONFIRMED, &clp->cl_flags)) +			rb_erase(&clp->cl_namenode, &nn->conf_name_tree); +		else +			rb_erase(&clp->cl_namenode, &nn->unconf_name_tree); +	} +	list_del_init(&clp->cl_lru);  	spin_lock(&clp->cl_lock);  	list_for_each_entry(ses, &clp->cl_sessions, se_perclnt)  		list_del_init(&ses->se_hash); @@ -1229,53 +1600,71 @@ unhash_client_locked(struct nfs4_client *clp)  }  static void -destroy_client(struct nfs4_client *clp) +unhash_client(struct nfs4_client *clp) +{ +	struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id); + +	spin_lock(&nn->client_lock); +	unhash_client_locked(clp); +	spin_unlock(&nn->client_lock); +} + +static __be32 mark_client_expired_locked(struct nfs4_client *clp) +{ +	if (atomic_read(&clp->cl_refcount)) +		return nfserr_jukebox; +	unhash_client_locked(clp); +	return nfs_ok; +} + +static void +__destroy_client(struct nfs4_client *clp)  {  	struct nfs4_openowner *oo;  	struct nfs4_delegation *dp;  	struct list_head reaplist; -	struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id);  	INIT_LIST_HEAD(&reaplist);  	spin_lock(&state_lock);  	while (!list_empty(&clp->cl_delegations)) {  		dp = list_entry(clp->cl_delegations.next, struct nfs4_delegation, dl_perclnt); -		list_del_init(&dp->dl_perclnt); -		list_move(&dp->dl_recall_lru, &reaplist); +		unhash_delegation_locked(dp); +		list_add(&dp->dl_recall_lru, &reaplist);  	}  	spin_unlock(&state_lock);  	while (!list_empty(&reaplist)) {  		dp = list_entry(reaplist.next, struct nfs4_delegation, dl_recall_lru); -		destroy_delegation(dp); +		list_del_init(&dp->dl_recall_lru); +		nfs4_put_stid(&dp->dl_stid);  	} -	list_splice_init(&clp->cl_revoked, &reaplist); -	while (!list_empty(&reaplist)) { +	while (!list_empty(&clp->cl_revoked)) {  		dp = list_entry(reaplist.next, struct nfs4_delegation, dl_recall_lru); -		destroy_revoked_delegation(dp); +		list_del_init(&dp->dl_recall_lru); +		nfs4_put_stid(&dp->dl_stid);  	}  	while (!list_empty(&clp->cl_openowners)) {  		oo = list_entry(clp->cl_openowners.next, struct nfs4_openowner, oo_perclient); +		atomic_inc(&oo->oo_owner.so_count);  		release_openowner(oo);  	}  	nfsd4_shutdown_callback(clp);  	if (clp->cl_cb_conn.cb_xprt)  		svc_xprt_put(clp->cl_cb_conn.cb_xprt); -	list_del(&clp->cl_idhash); -	if (test_bit(NFSD4_CLIENT_CONFIRMED, &clp->cl_flags)) -		rb_erase(&clp->cl_namenode, &nn->conf_name_tree); -	else -		rb_erase(&clp->cl_namenode, &nn->unconf_name_tree); -	spin_lock(&nn->client_lock); -	unhash_client_locked(clp); -	WARN_ON_ONCE(atomic_read(&clp->cl_refcount));  	free_client(clp); -	spin_unlock(&nn->client_lock); +} + +static void +destroy_client(struct nfs4_client *clp) +{ +	unhash_client(clp); +	__destroy_client(clp);  }  static void expire_client(struct nfs4_client *clp)  { +	unhash_client(clp);  	nfsd4_client_record_remove(clp); -	destroy_client(clp); +	__destroy_client(clp);  }  static void copy_verf(struct nfs4_client *target, nfs4_verifier *source) @@ -1408,25 +1797,28 @@ static bool mach_creds_match(struct nfs4_client *cl, struct svc_rqst *rqstp)  	return 0 == strcmp(cl->cl_cred.cr_principal, cr->cr_principal);  } -static void gen_clid(struct nfs4_client *clp, struct nfsd_net *nn) +static void gen_confirm(struct nfs4_client *clp, struct nfsd_net *nn)  { -	static u32 current_clientid = 1; +	__be32 verf[2]; -	clp->cl_clientid.cl_boot = nn->boot_time; -	clp->cl_clientid.cl_id = current_clientid++;  +	/* +	 * This is opaque to client, so no need to byte-swap. Use +	 * __force to keep sparse happy +	 */ +	verf[0] = (__force __be32)get_seconds(); +	verf[1] = (__force __be32)nn->clientid_counter; +	memcpy(clp->cl_confirm.data, verf, sizeof(clp->cl_confirm.data));  } -static void gen_confirm(struct nfs4_client *clp) +static void gen_clid(struct nfs4_client *clp, struct nfsd_net *nn)  { -	__be32 verf[2]; -	static u32 i; - -	verf[0] = (__be32)get_seconds(); -	verf[1] = (__be32)i++; -	memcpy(clp->cl_confirm.data, verf, sizeof(clp->cl_confirm.data)); +	clp->cl_clientid.cl_boot = nn->boot_time; +	clp->cl_clientid.cl_id = nn->clientid_counter++; +	gen_confirm(clp, nn);  } -static struct nfs4_stid *find_stateid(struct nfs4_client *cl, stateid_t *t) +static struct nfs4_stid * +find_stateid_locked(struct nfs4_client *cl, stateid_t *t)  {  	struct nfs4_stid *ret; @@ -1436,16 +1828,21 @@ static struct nfs4_stid *find_stateid(struct nfs4_client *cl, stateid_t *t)  	return ret;  } -static struct nfs4_stid *find_stateid_by_type(struct nfs4_client *cl, stateid_t *t, char typemask) +static struct nfs4_stid * +find_stateid_by_type(struct nfs4_client *cl, stateid_t *t, char typemask)  {  	struct nfs4_stid *s; -	s = find_stateid(cl, t); -	if (!s) -		return NULL; -	if (typemask & s->sc_type) -		return s; -	return NULL; +	spin_lock(&cl->cl_lock); +	s = find_stateid_locked(cl, t); +	if (s != NULL) { +		if (typemask & s->sc_type) +			atomic_inc(&s->sc_count); +		else +			s = NULL; +	} +	spin_unlock(&cl->cl_lock); +	return s;  }  static struct nfs4_client *create_client(struct xdr_netobj name, @@ -1455,7 +1852,6 @@ static struct nfs4_client *create_client(struct xdr_netobj name,  	struct sockaddr *sa = svc_addr(rqstp);  	int ret;  	struct net *net = SVC_NET(rqstp); -	struct nfsd_net *nn = net_generic(net, nfsd_net_id);  	clp = alloc_client(name);  	if (clp == NULL) @@ -1463,17 +1859,14 @@ static struct nfs4_client *create_client(struct xdr_netobj name,  	ret = copy_cred(&clp->cl_cred, &rqstp->rq_cred);  	if (ret) { -		spin_lock(&nn->client_lock);  		free_client(clp); -		spin_unlock(&nn->client_lock);  		return NULL;  	} -	nfsd4_init_callback(&clp->cl_cb_null); +	INIT_WORK(&clp->cl_cb_null.cb_work, nfsd4_run_cb_null);  	clp->cl_time = get_seconds();  	clear_bit(0, &clp->cl_cb_slot_busy);  	copy_verf(clp, verf);  	rpc_copy_addr((struct sockaddr *) &clp->cl_addr, sa); -	gen_confirm(clp);  	clp->cl_cb_session = NULL;  	clp->net = net;  	return clp; @@ -1525,11 +1918,13 @@ add_to_unconfirmed(struct nfs4_client *clp)  	unsigned int idhashval;  	struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id); +	lockdep_assert_held(&nn->client_lock); +  	clear_bit(NFSD4_CLIENT_CONFIRMED, &clp->cl_flags);  	add_clp_to_name_tree(clp, &nn->unconf_name_tree);  	idhashval = clientid_hashval(clp->cl_clientid.cl_id);  	list_add(&clp->cl_idhash, &nn->unconf_id_hashtbl[idhashval]); -	renew_client(clp); +	renew_client_locked(clp);  }  static void @@ -1538,12 +1933,14 @@ move_to_confirmed(struct nfs4_client *clp)  	unsigned int idhashval = clientid_hashval(clp->cl_clientid.cl_id);  	struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id); +	lockdep_assert_held(&nn->client_lock); +  	dprintk("NFSD: move_to_confirm nfs4_client %p\n", clp);  	list_move(&clp->cl_idhash, &nn->conf_id_hashtbl[idhashval]);  	rb_erase(&clp->cl_namenode, &nn->unconf_name_tree);  	add_clp_to_name_tree(clp, &nn->conf_name_tree);  	set_bit(NFSD4_CLIENT_CONFIRMED, &clp->cl_flags); -	renew_client(clp); +	renew_client_locked(clp);  }  static struct nfs4_client * @@ -1556,7 +1953,7 @@ find_client_in_id_table(struct list_head *tbl, clientid_t *clid, bool sessions)  		if (same_clid(&clp->cl_clientid, clid)) {  			if ((bool)clp->cl_minorversion != sessions)  				return NULL; -			renew_client(clp); +			renew_client_locked(clp);  			return clp;  		}  	} @@ -1568,6 +1965,7 @@ find_confirmed_client(clientid_t *clid, bool sessions, struct nfsd_net *nn)  {  	struct list_head *tbl = nn->conf_id_hashtbl; +	lockdep_assert_held(&nn->client_lock);  	return find_client_in_id_table(tbl, clid, sessions);  } @@ -1576,6 +1974,7 @@ find_unconfirmed_client(clientid_t *clid, bool sessions, struct nfsd_net *nn)  {  	struct list_head *tbl = nn->unconf_id_hashtbl; +	lockdep_assert_held(&nn->client_lock);  	return find_client_in_id_table(tbl, clid, sessions);  } @@ -1587,12 +1986,14 @@ static bool clp_used_exchangeid(struct nfs4_client *clp)  static struct nfs4_client *  find_confirmed_client_by_name(struct xdr_netobj *name, struct nfsd_net *nn)  { +	lockdep_assert_held(&nn->client_lock);  	return find_clp_in_name_tree(name, &nn->conf_name_tree);  }  static struct nfs4_client *  find_unconfirmed_client_by_name(struct xdr_netobj *name, struct nfsd_net *nn)  { +	lockdep_assert_held(&nn->client_lock);  	return find_clp_in_name_tree(name, &nn->unconf_name_tree);  } @@ -1642,7 +2043,7 @@ out_err:  /*   * Cache a reply. nfsd4_check_resp_size() has bounded the cache size.   */ -void +static void  nfsd4_store_cache_entry(struct nfsd4_compoundres *resp)  {  	struct xdr_buf *buf = resp->xdr.buf; @@ -1758,7 +2159,8 @@ nfsd4_exchange_id(struct svc_rqst *rqstp,  		  struct nfsd4_compound_state *cstate,  		  struct nfsd4_exchange_id *exid)  { -	struct nfs4_client *unconf, *conf, *new; +	struct nfs4_client *conf, *new; +	struct nfs4_client *unconf = NULL;  	__be32 status;  	char			addr_str[INET6_ADDRSTRLEN];  	nfs4_verifier		verf = exid->verifier; @@ -1787,8 +2189,12 @@ nfsd4_exchange_id(struct svc_rqst *rqstp,  		return nfserr_encr_alg_unsupp;  	} +	new = create_client(exid->clname, rqstp, &verf); +	if (new == NULL) +		return nfserr_jukebox; +  	/* Cases below refer to rfc 5661 section 18.35.4: */ -	nfs4_lock_state(); +	spin_lock(&nn->client_lock);  	conf = find_confirmed_client_by_name(&exid->clname, nn);  	if (conf) {  		bool creds_match = same_creds(&conf->cl_cred, &rqstp->rq_cred); @@ -1813,7 +2219,6 @@ nfsd4_exchange_id(struct svc_rqst *rqstp,  			}  			/* case 6 */  			exid->flags |= EXCHGID4_FLAG_CONFIRMED_R; -			new = conf;  			goto out_copy;  		}  		if (!creds_match) { /* case 3 */ @@ -1821,15 +2226,14 @@ nfsd4_exchange_id(struct svc_rqst *rqstp,  				status = nfserr_clid_inuse;  				goto out;  			} -			expire_client(conf);  			goto out_new;  		}  		if (verfs_match) { /* case 2 */  			conf->cl_exchange_flags |= EXCHGID4_FLAG_CONFIRMED_R; -			new = conf;  			goto out_copy;  		}  		/* case 5, client reboot */ +		conf = NULL;  		goto out_new;  	} @@ -1840,33 +2244,38 @@ nfsd4_exchange_id(struct svc_rqst *rqstp,  	unconf  = find_unconfirmed_client_by_name(&exid->clname, nn);  	if (unconf) /* case 4, possible retry or client restart */ -		expire_client(unconf); +		unhash_client_locked(unconf);  	/* case 1 (normal case) */  out_new: -	new = create_client(exid->clname, rqstp, &verf); -	if (new == NULL) { -		status = nfserr_jukebox; -		goto out; +	if (conf) { +		status = mark_client_expired_locked(conf); +		if (status) +			goto out;  	}  	new->cl_minorversion = cstate->minorversion;  	new->cl_mach_cred = (exid->spa_how == SP4_MACH_CRED);  	gen_clid(new, nn);  	add_to_unconfirmed(new); +	swap(new, conf);  out_copy: -	exid->clientid.cl_boot = new->cl_clientid.cl_boot; -	exid->clientid.cl_id = new->cl_clientid.cl_id; +	exid->clientid.cl_boot = conf->cl_clientid.cl_boot; +	exid->clientid.cl_id = conf->cl_clientid.cl_id; -	exid->seqid = new->cl_cs_slot.sl_seqid + 1; -	nfsd4_set_ex_flags(new, exid); +	exid->seqid = conf->cl_cs_slot.sl_seqid + 1; +	nfsd4_set_ex_flags(conf, exid);  	dprintk("nfsd4_exchange_id seqid %d flags %x\n", -		new->cl_cs_slot.sl_seqid, new->cl_exchange_flags); +		conf->cl_cs_slot.sl_seqid, conf->cl_exchange_flags);  	status = nfs_ok;  out: -	nfs4_unlock_state(); +	spin_unlock(&nn->client_lock); +	if (new) +		expire_client(new); +	if (unconf) +		expire_client(unconf);  	return status;  } @@ -2010,6 +2419,7 @@ nfsd4_create_session(struct svc_rqst *rqstp,  {  	struct sockaddr *sa = svc_addr(rqstp);  	struct nfs4_client *conf, *unconf; +	struct nfs4_client *old = NULL;  	struct nfsd4_session *new;  	struct nfsd4_conn *conn;  	struct nfsd4_clid_slot *cs_slot = NULL; @@ -2035,7 +2445,7 @@ nfsd4_create_session(struct svc_rqst *rqstp,  	if (!conn)  		goto out_free_session; -	nfs4_lock_state(); +	spin_lock(&nn->client_lock);  	unconf = find_unconfirmed_client(&cr_ses->clientid, true, nn);  	conf = find_confirmed_client(&cr_ses->clientid, true, nn);  	WARN_ON_ONCE(conf && unconf); @@ -2054,7 +2464,6 @@ nfsd4_create_session(struct svc_rqst *rqstp,  			goto out_free_conn;  		}  	} else if (unconf) { -		struct nfs4_client *old;  		if (!same_creds(&unconf->cl_cred, &rqstp->rq_cred) ||  		    !rpc_cmp_addr(sa, (struct sockaddr *) &unconf->cl_addr)) {  			status = nfserr_clid_inuse; @@ -2072,10 +2481,11 @@ nfsd4_create_session(struct svc_rqst *rqstp,  		}  		old = find_confirmed_client_by_name(&unconf->cl_name, nn);  		if (old) { -			status = mark_client_expired(old); -			if (status) +			status = mark_client_expired_locked(old); +			if (status) { +				old = NULL;  				goto out_free_conn; -			expire_client(old); +			}  		}  		move_to_confirmed(unconf);  		conf = unconf; @@ -2091,20 +2501,27 @@ nfsd4_create_session(struct svc_rqst *rqstp,  	cr_ses->flags &= ~SESSION4_RDMA;  	init_session(rqstp, new, conf, cr_ses); -	nfsd4_init_conn(rqstp, conn, new); +	nfsd4_get_session_locked(new);  	memcpy(cr_ses->sessionid.data, new->se_sessionid.data,  	       NFS4_MAX_SESSIONID_LEN);  	cs_slot->sl_seqid++;  	cr_ses->seqid = cs_slot->sl_seqid; -	/* cache solo and embedded create sessions under the state lock */ +	/* cache solo and embedded create sessions under the client_lock */  	nfsd4_cache_create_session(cr_ses, cs_slot, status); -	nfs4_unlock_state(); +	spin_unlock(&nn->client_lock); +	/* init connection and backchannel */ +	nfsd4_init_conn(rqstp, conn, new); +	nfsd4_put_session(new); +	if (old) +		expire_client(old);  	return status;  out_free_conn: -	nfs4_unlock_state(); +	spin_unlock(&nn->client_lock);  	free_conn(conn); +	if (old) +		expire_client(old);  out_free_session:  	__free_session(new);  out_release_drc_mem: @@ -2152,17 +2569,16 @@ __be32 nfsd4_bind_conn_to_session(struct svc_rqst *rqstp,  	__be32 status;  	struct nfsd4_conn *conn;  	struct nfsd4_session *session; -	struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id); +	struct net *net = SVC_NET(rqstp); +	struct nfsd_net *nn = net_generic(net, nfsd_net_id);  	if (!nfsd4_last_compound_op(rqstp))  		return nfserr_not_only_op; -	nfs4_lock_state();  	spin_lock(&nn->client_lock); -	session = find_in_sessionid_hashtbl(&bcts->sessionid, SVC_NET(rqstp)); +	session = find_in_sessionid_hashtbl(&bcts->sessionid, net, &status);  	spin_unlock(&nn->client_lock); -	status = nfserr_badsession;  	if (!session) -		goto out; +		goto out_no_session;  	status = nfserr_wrong_cred;  	if (!mach_creds_match(session->se_client, rqstp))  		goto out; @@ -2176,7 +2592,8 @@ __be32 nfsd4_bind_conn_to_session(struct svc_rqst *rqstp,  	nfsd4_init_conn(rqstp, conn, session);  	status = nfs_ok;  out: -	nfs4_unlock_state(); +	nfsd4_put_session(session); +out_no_session:  	return status;  } @@ -2195,9 +2612,9 @@ nfsd4_destroy_session(struct svc_rqst *r,  	struct nfsd4_session *ses;  	__be32 status;  	int ref_held_by_me = 0; -	struct nfsd_net *nn = net_generic(SVC_NET(r), nfsd_net_id); +	struct net *net = SVC_NET(r); +	struct nfsd_net *nn = net_generic(net, nfsd_net_id); -	nfs4_lock_state();  	status = nfserr_not_only_op;  	if (nfsd4_compound_in_session(cstate->session, &sessionid->sessionid)) {  		if (!nfsd4_last_compound_op(r)) @@ -2206,14 +2623,12 @@ nfsd4_destroy_session(struct svc_rqst *r,  	}  	dump_sessionid(__func__, &sessionid->sessionid);  	spin_lock(&nn->client_lock); -	ses = find_in_sessionid_hashtbl(&sessionid->sessionid, SVC_NET(r)); -	status = nfserr_badsession; +	ses = find_in_sessionid_hashtbl(&sessionid->sessionid, net, &status);  	if (!ses)  		goto out_client_lock;  	status = nfserr_wrong_cred;  	if (!mach_creds_match(ses->se_client, r)) -		goto out_client_lock; -	nfsd4_get_session_locked(ses); +		goto out_put_session;  	status = mark_session_dead_locked(ses, 1 + ref_held_by_me);  	if (status)  		goto out_put_session; @@ -2225,11 +2640,10 @@ nfsd4_destroy_session(struct svc_rqst *r,  	spin_lock(&nn->client_lock);  	status = nfs_ok;  out_put_session: -	nfsd4_put_session(ses); +	nfsd4_put_session_locked(ses);  out_client_lock:  	spin_unlock(&nn->client_lock);  out: -	nfs4_unlock_state();  	return status;  } @@ -2300,7 +2714,8 @@ nfsd4_sequence(struct svc_rqst *rqstp,  	struct nfsd4_conn *conn;  	__be32 status;  	int buflen; -	struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id); +	struct net *net = SVC_NET(rqstp); +	struct nfsd_net *nn = net_generic(net, nfsd_net_id);  	if (resp->opcnt != 1)  		return nfserr_sequence_pos; @@ -2314,17 +2729,10 @@ nfsd4_sequence(struct svc_rqst *rqstp,  		return nfserr_jukebox;  	spin_lock(&nn->client_lock); -	status = nfserr_badsession; -	session = find_in_sessionid_hashtbl(&seq->sessionid, SVC_NET(rqstp)); +	session = find_in_sessionid_hashtbl(&seq->sessionid, net, &status);  	if (!session)  		goto out_no_session;  	clp = session->se_client; -	status = get_client_locked(clp); -	if (status) -		goto out_no_session; -	status = nfsd4_get_session_locked(session); -	if (status) -		goto out_put_client;  	status = nfserr_too_many_ops;  	if (nfsd4_session_too_many_ops(rqstp, session)) @@ -2354,6 +2762,7 @@ nfsd4_sequence(struct svc_rqst *rqstp,  			goto out_put_session;  		cstate->slot = slot;  		cstate->session = session; +		cstate->clp = clp;  		/* Return the cached reply status and set cstate->status  		 * for nfsd4_proc_compound processing */  		status = nfsd4_replay_cache_entry(resp, seq); @@ -2388,6 +2797,7 @@ nfsd4_sequence(struct svc_rqst *rqstp,  	cstate->slot = slot;  	cstate->session = session; +	cstate->clp = clp;  out:  	switch (clp->cl_cb_state) { @@ -2408,31 +2818,48 @@ out_no_session:  	spin_unlock(&nn->client_lock);  	return status;  out_put_session: -	nfsd4_put_session(session); -out_put_client: -	put_client_renew_locked(clp); +	nfsd4_put_session_locked(session);  	goto out_no_session;  } +void +nfsd4_sequence_done(struct nfsd4_compoundres *resp) +{ +	struct nfsd4_compound_state *cs = &resp->cstate; + +	if (nfsd4_has_session(cs)) { +		if (cs->status != nfserr_replay_cache) { +			nfsd4_store_cache_entry(resp); +			cs->slot->sl_flags &= ~NFSD4_SLOT_INUSE; +		} +		/* Drop session reference that was taken in nfsd4_sequence() */ +		nfsd4_put_session(cs->session); +	} else if (cs->clp) +		put_client_renew(cs->clp); +} +  __be32  nfsd4_destroy_clientid(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, struct nfsd4_destroy_clientid *dc)  { -	struct nfs4_client *conf, *unconf, *clp; +	struct nfs4_client *conf, *unconf; +	struct nfs4_client *clp = NULL;  	__be32 status = 0;  	struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id); -	nfs4_lock_state(); +	spin_lock(&nn->client_lock);  	unconf = find_unconfirmed_client(&dc->clientid, true, nn);  	conf = find_confirmed_client(&dc->clientid, true, nn);  	WARN_ON_ONCE(conf && unconf);  	if (conf) { -		clp = conf; -  		if (client_has_state(conf)) {  			status = nfserr_clientid_busy;  			goto out;  		} +		status = mark_client_expired_locked(conf); +		if (status) +			goto out; +		clp = conf;  	} else if (unconf)  		clp = unconf;  	else { @@ -2440,12 +2867,15 @@ nfsd4_destroy_clientid(struct svc_rqst *rqstp, struct nfsd4_compound_state *csta  		goto out;  	}  	if (!mach_creds_match(clp, rqstp)) { +		clp = NULL;  		status = nfserr_wrong_cred;  		goto out;  	} -	expire_client(clp); +	unhash_client_locked(clp);  out: -	nfs4_unlock_state(); +	spin_unlock(&nn->client_lock); +	if (clp) +		expire_client(clp);  	return status;  } @@ -2464,7 +2894,6 @@ nfsd4_reclaim_complete(struct svc_rqst *rqstp, struct nfsd4_compound_state *csta  		 return nfs_ok;  	} -	nfs4_lock_state();  	status = nfserr_complete_already;  	if (test_and_set_bit(NFSD4_CLIENT_RECLAIM_COMPLETE,  			     &cstate->session->se_client->cl_flags)) @@ -2484,7 +2913,6 @@ nfsd4_reclaim_complete(struct svc_rqst *rqstp, struct nfsd4_compound_state *csta  	status = nfs_ok;  	nfsd4_client_record_create(cstate->session->se_client);  out: -	nfs4_unlock_state();  	return status;  } @@ -2494,12 +2922,16 @@ nfsd4_setclientid(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,  {  	struct xdr_netobj 	clname = setclid->se_name;  	nfs4_verifier		clverifier = setclid->se_verf; -	struct nfs4_client	*conf, *unconf, *new; +	struct nfs4_client	*conf, *new; +	struct nfs4_client	*unconf = NULL;  	__be32 			status;  	struct nfsd_net		*nn = net_generic(SVC_NET(rqstp), nfsd_net_id); +	new = create_client(clname, rqstp, &clverifier); +	if (new == NULL) +		return nfserr_jukebox;  	/* Cases below refer to rfc 3530 section 14.2.33: */ -	nfs4_lock_state(); +	spin_lock(&nn->client_lock);  	conf = find_confirmed_client_by_name(&clname, nn);  	if (conf) {  		/* case 0: */ @@ -2517,11 +2949,7 @@ nfsd4_setclientid(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,  	}  	unconf = find_unconfirmed_client_by_name(&clname, nn);  	if (unconf) -		expire_client(unconf); -	status = nfserr_jukebox; -	new = create_client(clname, rqstp, &clverifier); -	if (new == NULL) -		goto out; +		unhash_client_locked(unconf);  	if (conf && same_verf(&conf->cl_verifier, &clverifier))  		/* case 1: probable callback update */  		copy_clid(new, conf); @@ -2533,9 +2961,14 @@ nfsd4_setclientid(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,  	setclid->se_clientid.cl_boot = new->cl_clientid.cl_boot;  	setclid->se_clientid.cl_id = new->cl_clientid.cl_id;  	memcpy(setclid->se_confirm.data, new->cl_confirm.data, sizeof(setclid->se_confirm.data)); +	new = NULL;  	status = nfs_ok;  out: -	nfs4_unlock_state(); +	spin_unlock(&nn->client_lock); +	if (new) +		free_client(new); +	if (unconf) +		expire_client(unconf);  	return status;  } @@ -2546,6 +2979,7 @@ nfsd4_setclientid_confirm(struct svc_rqst *rqstp,  			 struct nfsd4_setclientid_confirm *setclientid_confirm)  {  	struct nfs4_client *conf, *unconf; +	struct nfs4_client *old = NULL;  	nfs4_verifier confirm = setclientid_confirm->sc_confirm;   	clientid_t * clid = &setclientid_confirm->sc_clientid;  	__be32 status; @@ -2553,8 +2987,8 @@ nfsd4_setclientid_confirm(struct svc_rqst *rqstp,  	if (STALE_CLIENTID(clid, nn))  		return nfserr_stale_clientid; -	nfs4_lock_state(); +	spin_lock(&nn->client_lock);  	conf = find_confirmed_client(clid, false, nn);  	unconf = find_unconfirmed_client(clid, false, nn);  	/* @@ -2578,22 +3012,30 @@ nfsd4_setclientid_confirm(struct svc_rqst *rqstp,  	}  	status = nfs_ok;  	if (conf) { /* case 1: callback update */ +		old = unconf; +		unhash_client_locked(old);  		nfsd4_change_callback(conf, &unconf->cl_cb_conn); -		nfsd4_probe_callback(conf); -		expire_client(unconf);  	} else { /* case 3: normal case; new or rebooted client */ -		conf = find_confirmed_client_by_name(&unconf->cl_name, nn); -		if (conf) { -			status = mark_client_expired(conf); -			if (status) +		old = find_confirmed_client_by_name(&unconf->cl_name, nn); +		if (old) { +			status = mark_client_expired_locked(old); +			if (status) { +				old = NULL;  				goto out; -			expire_client(conf); +			}  		}  		move_to_confirmed(unconf); -		nfsd4_probe_callback(unconf); +		conf = unconf;  	} +	get_client_locked(conf); +	spin_unlock(&nn->client_lock); +	nfsd4_probe_callback(conf); +	spin_lock(&nn->client_lock); +	put_client_renew_locked(conf);  out: -	nfs4_unlock_state(); +	spin_unlock(&nn->client_lock); +	if (old) +		expire_client(old);  	return status;  } @@ -2603,21 +3045,23 @@ static struct nfs4_file *nfsd4_alloc_file(void)  }  /* OPEN Share state helper functions */ -static void nfsd4_init_file(struct nfs4_file *fp, struct inode *ino) +static void nfsd4_init_file(struct nfs4_file *fp, struct knfsd_fh *fh)  { -	unsigned int hashval = file_hashval(ino); +	unsigned int hashval = file_hashval(fh); + +	lockdep_assert_held(&state_lock);  	atomic_set(&fp->fi_ref, 1); +	spin_lock_init(&fp->fi_lock);  	INIT_LIST_HEAD(&fp->fi_stateids);  	INIT_LIST_HEAD(&fp->fi_delegations); -	fp->fi_inode = igrab(ino); +	fh_copy_shallow(&fp->fi_fhandle, fh);  	fp->fi_had_conflict = false;  	fp->fi_lease = NULL; +	fp->fi_share_deny = 0;  	memset(fp->fi_fds, 0, sizeof(fp->fi_fds));  	memset(fp->fi_access, 0, sizeof(fp->fi_access)); -	spin_lock(&state_lock);  	hlist_add_head(&fp->fi_hash, &file_hashtbl[hashval]); -	spin_unlock(&state_lock);  }  void @@ -2673,6 +3117,28 @@ static void init_nfs4_replay(struct nfs4_replay *rp)  	rp->rp_status = nfserr_serverfault;  	rp->rp_buflen = 0;  	rp->rp_buf = rp->rp_ibuf; +	mutex_init(&rp->rp_mutex); +} + +static void nfsd4_cstate_assign_replay(struct nfsd4_compound_state *cstate, +		struct nfs4_stateowner *so) +{ +	if (!nfsd4_has_session(cstate)) { +		mutex_lock(&so->so_replay.rp_mutex); +		cstate->replay_owner = so; +		atomic_inc(&so->so_count); +	} +} + +void nfsd4_cstate_clear_replay(struct nfsd4_compound_state *cstate) +{ +	struct nfs4_stateowner *so = cstate->replay_owner; + +	if (so != NULL) { +		cstate->replay_owner = NULL; +		mutex_unlock(&so->so_replay.rp_mutex); +		nfs4_put_stateowner(so); +	}  }  static inline void *alloc_stateowner(struct kmem_cache *slab, struct xdr_netobj *owner, struct nfs4_client *clp) @@ -2693,111 +3159,172 @@ static inline void *alloc_stateowner(struct kmem_cache *slab, struct xdr_netobj  	INIT_LIST_HEAD(&sop->so_stateids);  	sop->so_client = clp;  	init_nfs4_replay(&sop->so_replay); +	atomic_set(&sop->so_count, 1);  	return sop;  }  static void hash_openowner(struct nfs4_openowner *oo, struct nfs4_client *clp, unsigned int strhashval)  { -	struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id); +	lockdep_assert_held(&clp->cl_lock); -	list_add(&oo->oo_owner.so_strhash, &nn->ownerstr_hashtbl[strhashval]); +	list_add(&oo->oo_owner.so_strhash, +		 &clp->cl_ownerstr_hashtbl[strhashval]);  	list_add(&oo->oo_perclient, &clp->cl_openowners);  } +static void nfs4_unhash_openowner(struct nfs4_stateowner *so) +{ +	unhash_openowner_locked(openowner(so)); +} + +static void nfs4_free_openowner(struct nfs4_stateowner *so) +{ +	struct nfs4_openowner *oo = openowner(so); + +	kmem_cache_free(openowner_slab, oo); +} + +static const struct nfs4_stateowner_operations openowner_ops = { +	.so_unhash =	nfs4_unhash_openowner, +	.so_free =	nfs4_free_openowner, +}; +  static struct nfs4_openowner * -alloc_init_open_stateowner(unsigned int strhashval, struct nfs4_client *clp, struct nfsd4_open *open) { -	struct nfs4_openowner *oo; +alloc_init_open_stateowner(unsigned int strhashval, struct nfsd4_open *open, +			   struct nfsd4_compound_state *cstate) +{ +	struct nfs4_client *clp = cstate->clp; +	struct nfs4_openowner *oo, *ret;  	oo = alloc_stateowner(openowner_slab, &open->op_owner, clp);  	if (!oo)  		return NULL; +	oo->oo_owner.so_ops = &openowner_ops;  	oo->oo_owner.so_is_open_owner = 1;  	oo->oo_owner.so_seqid = open->op_seqid; -	oo->oo_flags = NFS4_OO_NEW; +	oo->oo_flags = 0; +	if (nfsd4_has_session(cstate)) +		oo->oo_flags |= NFS4_OO_CONFIRMED;  	oo->oo_time = 0;  	oo->oo_last_closed_stid = NULL;  	INIT_LIST_HEAD(&oo->oo_close_lru); -	hash_openowner(oo, clp, strhashval); +	spin_lock(&clp->cl_lock); +	ret = find_openstateowner_str_locked(strhashval, open, clp); +	if (ret == NULL) { +		hash_openowner(oo, clp, strhashval); +		ret = oo; +	} else +		nfs4_free_openowner(&oo->oo_owner); +	spin_unlock(&clp->cl_lock);  	return oo;  }  static void init_open_stateid(struct nfs4_ol_stateid *stp, struct nfs4_file *fp, struct nfsd4_open *open) {  	struct nfs4_openowner *oo = open->op_openowner; +	atomic_inc(&stp->st_stid.sc_count);  	stp->st_stid.sc_type = NFS4_OPEN_STID; -	INIT_LIST_HEAD(&stp->st_lockowners); -	list_add(&stp->st_perstateowner, &oo->oo_owner.so_stateids); -	list_add(&stp->st_perfile, &fp->fi_stateids); +	INIT_LIST_HEAD(&stp->st_locks);  	stp->st_stateowner = &oo->oo_owner; +	atomic_inc(&stp->st_stateowner->so_count);  	get_nfs4_file(fp); -	stp->st_file = fp; +	stp->st_stid.sc_file = fp;  	stp->st_access_bmap = 0;  	stp->st_deny_bmap = 0; -	set_access(open->op_share_access, stp); -	set_deny(open->op_share_deny, stp);  	stp->st_openstp = NULL; +	spin_lock(&oo->oo_owner.so_client->cl_lock); +	list_add(&stp->st_perstateowner, &oo->oo_owner.so_stateids); +	spin_lock(&fp->fi_lock); +	list_add(&stp->st_perfile, &fp->fi_stateids); +	spin_unlock(&fp->fi_lock); +	spin_unlock(&oo->oo_owner.so_client->cl_lock);  } +/* + * In the 4.0 case we need to keep the owners around a little while to handle + * CLOSE replay. We still do need to release any file access that is held by + * them before returning however. + */  static void -move_to_close_lru(struct nfs4_openowner *oo, struct net *net) +move_to_close_lru(struct nfs4_ol_stateid *s, struct net *net)  { -	struct nfsd_net *nn = net_generic(net, nfsd_net_id); +	struct nfs4_ol_stateid *last; +	struct nfs4_openowner *oo = openowner(s->st_stateowner); +	struct nfsd_net *nn = net_generic(s->st_stid.sc_client->net, +						nfsd_net_id);  	dprintk("NFSD: move_to_close_lru nfs4_openowner %p\n", oo); +	/* +	 * We know that we hold one reference via nfsd4_close, and another +	 * "persistent" reference for the client. If the refcount is higher +	 * than 2, then there are still calls in progress that are using this +	 * stateid. We can't put the sc_file reference until they are finished. +	 * Wait for the refcount to drop to 2. Since it has been unhashed, +	 * there should be no danger of the refcount going back up again at +	 * this point. +	 */ +	wait_event(close_wq, atomic_read(&s->st_stid.sc_count) == 2); + +	release_all_access(s); +	if (s->st_stid.sc_file) { +		put_nfs4_file(s->st_stid.sc_file); +		s->st_stid.sc_file = NULL; +	} + +	spin_lock(&nn->client_lock); +	last = oo->oo_last_closed_stid; +	oo->oo_last_closed_stid = s;  	list_move_tail(&oo->oo_close_lru, &nn->close_lru);  	oo->oo_time = get_seconds(); +	spin_unlock(&nn->client_lock); +	if (last) +		nfs4_put_stid(&last->st_stid);  } -static int -same_owner_str(struct nfs4_stateowner *sop, struct xdr_netobj *owner, -							clientid_t *clid) +/* search file_hashtbl[] for file */ +static struct nfs4_file * +find_file_locked(struct knfsd_fh *fh)  { -	return (sop->so_owner.len == owner->len) && -		0 == memcmp(sop->so_owner.data, owner->data, owner->len) && -		(sop->so_client->cl_clientid.cl_id == clid->cl_id); -} +	unsigned int hashval = file_hashval(fh); +	struct nfs4_file *fp; -static struct nfs4_openowner * -find_openstateowner_str(unsigned int hashval, struct nfsd4_open *open, -			bool sessions, struct nfsd_net *nn) -{ -	struct nfs4_stateowner *so; -	struct nfs4_openowner *oo; -	struct nfs4_client *clp; +	lockdep_assert_held(&state_lock); -	list_for_each_entry(so, &nn->ownerstr_hashtbl[hashval], so_strhash) { -		if (!so->so_is_open_owner) -			continue; -		if (same_owner_str(so, &open->op_owner, &open->op_clientid)) { -			oo = openowner(so); -			clp = oo->oo_owner.so_client; -			if ((bool)clp->cl_minorversion != sessions) -				return NULL; -			renew_client(oo->oo_owner.so_client); -			return oo; +	hlist_for_each_entry(fp, &file_hashtbl[hashval], fi_hash) { +		if (nfsd_fh_match(&fp->fi_fhandle, fh)) { +			get_nfs4_file(fp); +			return fp;  		}  	}  	return NULL;  } -/* search file_hashtbl[] for file */  static struct nfs4_file * -find_file(struct inode *ino) +find_file(struct knfsd_fh *fh)  { -	unsigned int hashval = file_hashval(ino);  	struct nfs4_file *fp;  	spin_lock(&state_lock); -	hlist_for_each_entry(fp, &file_hashtbl[hashval], fi_hash) { -		if (fp->fi_inode == ino) { -			get_nfs4_file(fp); -			spin_unlock(&state_lock); -			return fp; -		} +	fp = find_file_locked(fh); +	spin_unlock(&state_lock); +	return fp; +} + +static struct nfs4_file * +find_or_add_file(struct nfs4_file *new, struct knfsd_fh *fh) +{ +	struct nfs4_file *fp; + +	spin_lock(&state_lock); +	fp = find_file_locked(fh); +	if (fp == NULL) { +		nfsd4_init_file(new, fh); +		fp = new;  	}  	spin_unlock(&state_lock); -	return NULL; + +	return fp;  }  /* @@ -2807,47 +3334,53 @@ find_file(struct inode *ino)  static __be32  nfs4_share_conflict(struct svc_fh *current_fh, unsigned int deny_type)  { -	struct inode *ino = current_fh->fh_dentry->d_inode;  	struct nfs4_file *fp; -	struct nfs4_ol_stateid *stp; -	__be32 ret; +	__be32 ret = nfs_ok; -	fp = find_file(ino); +	fp = find_file(¤t_fh->fh_handle);  	if (!fp) -		return nfs_ok; -	ret = nfserr_locked; -	/* Search for conflicting share reservations */ -	list_for_each_entry(stp, &fp->fi_stateids, st_perfile) { -		if (test_deny(deny_type, stp) || -		    test_deny(NFS4_SHARE_DENY_BOTH, stp)) -			goto out; -	} -	ret = nfs_ok; -out: +		return ret; +	/* Check for conflicting share reservations */ +	spin_lock(&fp->fi_lock); +	if (fp->fi_share_deny & deny_type) +		ret = nfserr_locked; +	spin_unlock(&fp->fi_lock);  	put_nfs4_file(fp);  	return ret;  } -static void nfsd_break_one_deleg(struct nfs4_delegation *dp) +void nfsd4_prepare_cb_recall(struct nfs4_delegation *dp)  { -	struct nfs4_client *clp = dp->dl_stid.sc_client; -	struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id); +	struct nfsd_net *nn = net_generic(dp->dl_stid.sc_client->net, +					  nfsd_net_id); -	lockdep_assert_held(&state_lock); -	/* We're assuming the state code never drops its reference +	block_delegations(&dp->dl_stid.sc_file->fi_fhandle); + +	/* +	 * We can't do this in nfsd_break_deleg_cb because it is +	 * already holding inode->i_lock. +	 * +	 * If the dl_time != 0, then we know that it has already been +	 * queued for a lease break. Don't queue it again. +	 */ +	spin_lock(&state_lock); +	if (dp->dl_time == 0) { +		dp->dl_time = get_seconds(); +		list_add_tail(&dp->dl_recall_lru, &nn->del_recall_lru); +	} +	spin_unlock(&state_lock); +} + +static void nfsd_break_one_deleg(struct nfs4_delegation *dp) +{ +	/* +	 * We're assuming the state code never drops its reference  	 * without first removing the lease.  Since we're in this lease  	 * callback (and since the lease code is serialized by the kernel  	 * lock) we know the server hasn't removed the lease yet, we know -	 * it's safe to take a reference: */ -	atomic_inc(&dp->dl_count); - -	list_add_tail(&dp->dl_recall_lru, &nn->del_recall_lru); - -	/* Only place dl_time is set; protected by i_lock: */ -	dp->dl_time = get_seconds(); - -	block_delegations(&dp->dl_fh); - +	 * it's safe to take a reference. +	 */ +	atomic_inc(&dp->dl_stid.sc_count);  	nfsd4_cb_recall(dp);  } @@ -2872,11 +3405,20 @@ static void nfsd_break_deleg_cb(struct file_lock *fl)  	 */  	fl->fl_break_time = 0; -	spin_lock(&state_lock); +	spin_lock(&fp->fi_lock);  	fp->fi_had_conflict = true; -	list_for_each_entry(dp, &fp->fi_delegations, dl_perfile) -		nfsd_break_one_deleg(dp); -	spin_unlock(&state_lock); +	/* +	 * If there are no delegations on the list, then we can't count on this +	 * lease ever being cleaned up. Set the fl_break_time to jiffies so that +	 * time_out_leases will do it ASAP. The fact that fi_had_conflict is now +	 * true should keep any new delegations from being hashed. +	 */ +	if (list_empty(&fp->fi_delegations)) +		fl->fl_break_time = jiffies; +	else +		list_for_each_entry(dp, &fp->fi_delegations, dl_perfile) +			nfsd_break_one_deleg(dp); +	spin_unlock(&fp->fi_lock);  }  static @@ -2904,6 +3446,42 @@ static __be32 nfsd4_check_seqid(struct nfsd4_compound_state *cstate, struct nfs4  	return nfserr_bad_seqid;  } +static __be32 lookup_clientid(clientid_t *clid, +		struct nfsd4_compound_state *cstate, +		struct nfsd_net *nn) +{ +	struct nfs4_client *found; + +	if (cstate->clp) { +		found = cstate->clp; +		if (!same_clid(&found->cl_clientid, clid)) +			return nfserr_stale_clientid; +		return nfs_ok; +	} + +	if (STALE_CLIENTID(clid, nn)) +		return nfserr_stale_clientid; + +	/* +	 * For v4.1+ we get the client in the SEQUENCE op. If we don't have one +	 * cached already then we know this is for is for v4.0 and "sessions" +	 * will be false. +	 */ +	WARN_ON_ONCE(cstate->session); +	spin_lock(&nn->client_lock); +	found = find_confirmed_client(clid, false, nn); +	if (!found) { +		spin_unlock(&nn->client_lock); +		return nfserr_expired; +	} +	atomic_inc(&found->cl_refcount); +	spin_unlock(&nn->client_lock); + +	/* Cache the nfs4_client in cstate! */ +	cstate->clp = found; +	return nfs_ok; +} +  __be32  nfsd4_process_open1(struct nfsd4_compound_state *cstate,  		    struct nfsd4_open *open, struct nfsd_net *nn) @@ -2924,19 +3502,19 @@ nfsd4_process_open1(struct nfsd4_compound_state *cstate,  	if (open->op_file == NULL)  		return nfserr_jukebox; -	strhashval = ownerstr_hashval(clientid->cl_id, &open->op_owner); -	oo = find_openstateowner_str(strhashval, open, cstate->minorversion, nn); +	status = lookup_clientid(clientid, cstate, nn); +	if (status) +		return status; +	clp = cstate->clp; + +	strhashval = ownerstr_hashval(&open->op_owner); +	oo = find_openstateowner_str(strhashval, open, clp);  	open->op_openowner = oo;  	if (!oo) { -		clp = find_confirmed_client(clientid, cstate->minorversion, -					    nn); -		if (clp == NULL) -			return nfserr_expired;  		goto new_owner;  	}  	if (!(oo->oo_flags & NFS4_OO_CONFIRMED)) {  		/* Replace unconfirmed owners without checking for replay. */ -		clp = oo->oo_owner.so_client;  		release_openowner(oo);  		open->op_openowner = NULL;  		goto new_owner; @@ -2944,15 +3522,14 @@ nfsd4_process_open1(struct nfsd4_compound_state *cstate,  	status = nfsd4_check_seqid(cstate, &oo->oo_owner, open->op_seqid);  	if (status)  		return status; -	clp = oo->oo_owner.so_client;  	goto alloc_stateid;  new_owner: -	oo = alloc_init_open_stateowner(strhashval, clp, open); +	oo = alloc_init_open_stateowner(strhashval, open, cstate);  	if (oo == NULL)  		return nfserr_jukebox;  	open->op_openowner = oo;  alloc_stateid: -	open->op_stp = nfs4_alloc_stateid(clp); +	open->op_stp = nfs4_alloc_open_stateid(clp);  	if (!open->op_stp)  		return nfserr_jukebox;  	return nfs_ok; @@ -2994,14 +3571,18 @@ nfs4_check_deleg(struct nfs4_client *cl, struct nfsd4_open *open,  {  	int flags;  	__be32 status = nfserr_bad_stateid; +	struct nfs4_delegation *deleg; -	*dp = find_deleg_stateid(cl, &open->op_delegate_stateid); -	if (*dp == NULL) +	deleg = find_deleg_stateid(cl, &open->op_delegate_stateid); +	if (deleg == NULL)  		goto out;  	flags = share_access_to_flags(open->op_share_access); -	status = nfs4_check_delegmode(*dp, flags); -	if (status) -		*dp = NULL; +	status = nfs4_check_delegmode(deleg, flags); +	if (status) { +		nfs4_put_stid(&deleg->dl_stid); +		goto out; +	} +	*dp = deleg;  out:  	if (!nfsd4_is_deleg_cur(open))  		return nfs_ok; @@ -3011,24 +3592,25 @@ out:  	return nfs_ok;  } -static __be32 -nfs4_check_open(struct nfs4_file *fp, struct nfsd4_open *open, struct nfs4_ol_stateid **stpp) +static struct nfs4_ol_stateid * +nfsd4_find_existing_open(struct nfs4_file *fp, struct nfsd4_open *open)  { -	struct nfs4_ol_stateid *local; +	struct nfs4_ol_stateid *local, *ret = NULL;  	struct nfs4_openowner *oo = open->op_openowner; +	spin_lock(&fp->fi_lock);  	list_for_each_entry(local, &fp->fi_stateids, st_perfile) {  		/* ignore lock owners */  		if (local->st_stateowner->so_is_open_owner == 0)  			continue; -		/* remember if we have seen this open owner */ -		if (local->st_stateowner == &oo->oo_owner) -			*stpp = local; -		/* check for conflicting share reservations */ -		if (!test_share(local, open)) -			return nfserr_share_denied; +		if (local->st_stateowner == &oo->oo_owner) { +			ret = local; +			atomic_inc(&ret->st_stid.sc_count); +			break; +		}  	} -	return nfs_ok; +	spin_unlock(&fp->fi_lock); +	return ret;  }  static inline int nfs4_access_to_access(u32 nfs4_access) @@ -3042,24 +3624,6 @@ static inline int nfs4_access_to_access(u32 nfs4_access)  	return flags;  } -static __be32 nfs4_get_vfs_file(struct svc_rqst *rqstp, struct nfs4_file *fp, -		struct svc_fh *cur_fh, struct nfsd4_open *open) -{ -	__be32 status; -	int oflag = nfs4_access_to_omode(open->op_share_access); -	int access = nfs4_access_to_access(open->op_share_access); - -	if (!fp->fi_fds[oflag]) { -		status = nfsd_open(rqstp, cur_fh, S_IFREG, access, -			&fp->fi_fds[oflag]); -		if (status) -			return status; -	} -	nfs4_file_get_access(fp, oflag); - -	return nfs_ok; -} -  static inline __be32  nfsd4_truncate(struct svc_rqst *rqstp, struct svc_fh *fh,  		struct nfsd4_open *open) @@ -3075,34 +3639,99 @@ nfsd4_truncate(struct svc_rqst *rqstp, struct svc_fh *fh,  	return nfsd_setattr(rqstp, fh, &iattr, 0, (time_t)0);  } -static __be32 -nfs4_upgrade_open(struct svc_rqst *rqstp, struct nfs4_file *fp, struct svc_fh *cur_fh, struct nfs4_ol_stateid *stp, struct nfsd4_open *open) +static __be32 nfs4_get_vfs_file(struct svc_rqst *rqstp, struct nfs4_file *fp, +		struct svc_fh *cur_fh, struct nfs4_ol_stateid *stp, +		struct nfsd4_open *open)  { -	u32 op_share_access = open->op_share_access; -	bool new_access; +	struct file *filp = NULL;  	__be32 status; +	int oflag = nfs4_access_to_omode(open->op_share_access); +	int access = nfs4_access_to_access(open->op_share_access); +	unsigned char old_access_bmap, old_deny_bmap; -	new_access = !test_access(op_share_access, stp); -	if (new_access) { -		status = nfs4_get_vfs_file(rqstp, fp, cur_fh, open); -		if (status) -			return status; +	spin_lock(&fp->fi_lock); + +	/* +	 * Are we trying to set a deny mode that would conflict with +	 * current access? +	 */ +	status = nfs4_file_check_deny(fp, open->op_share_deny); +	if (status != nfs_ok) { +		spin_unlock(&fp->fi_lock); +		goto out;  	} -	status = nfsd4_truncate(rqstp, cur_fh, open); -	if (status) { -		if (new_access) { -			int oflag = nfs4_access_to_omode(op_share_access); -			nfs4_file_put_access(fp, oflag); -		} -		return status; + +	/* set access to the file */ +	status = nfs4_file_get_access(fp, open->op_share_access); +	if (status != nfs_ok) { +		spin_unlock(&fp->fi_lock); +		goto out;  	} -	/* remember the open */ -	set_access(op_share_access, stp); + +	/* Set access bits in stateid */ +	old_access_bmap = stp->st_access_bmap; +	set_access(open->op_share_access, stp); + +	/* Set new deny mask */ +	old_deny_bmap = stp->st_deny_bmap;  	set_deny(open->op_share_deny, stp); +	fp->fi_share_deny |= (open->op_share_deny & NFS4_SHARE_DENY_BOTH); -	return nfs_ok; +	if (!fp->fi_fds[oflag]) { +		spin_unlock(&fp->fi_lock); +		status = nfsd_open(rqstp, cur_fh, S_IFREG, access, &filp); +		if (status) +			goto out_put_access; +		spin_lock(&fp->fi_lock); +		if (!fp->fi_fds[oflag]) { +			fp->fi_fds[oflag] = filp; +			filp = NULL; +		} +	} +	spin_unlock(&fp->fi_lock); +	if (filp) +		fput(filp); + +	status = nfsd4_truncate(rqstp, cur_fh, open); +	if (status) +		goto out_put_access; +out: +	return status; +out_put_access: +	stp->st_access_bmap = old_access_bmap; +	nfs4_file_put_access(fp, open->op_share_access); +	reset_union_bmap_deny(bmap_to_share_mode(old_deny_bmap), stp); +	goto out;  } +static __be32 +nfs4_upgrade_open(struct svc_rqst *rqstp, struct nfs4_file *fp, struct svc_fh *cur_fh, struct nfs4_ol_stateid *stp, struct nfsd4_open *open) +{ +	__be32 status; +	unsigned char old_deny_bmap; + +	if (!test_access(open->op_share_access, stp)) +		return nfs4_get_vfs_file(rqstp, fp, cur_fh, stp, open); + +	/* test and set deny mode */ +	spin_lock(&fp->fi_lock); +	status = nfs4_file_check_deny(fp, open->op_share_deny); +	if (status == nfs_ok) { +		old_deny_bmap = stp->st_deny_bmap; +		set_deny(open->op_share_deny, stp); +		fp->fi_share_deny |= +				(open->op_share_deny & NFS4_SHARE_DENY_BOTH); +	} +	spin_unlock(&fp->fi_lock); + +	if (status != nfs_ok) +		return status; + +	status = nfsd4_truncate(rqstp, cur_fh, open); +	if (status != nfs_ok) +		reset_union_bmap_deny(old_deny_bmap, stp); +	return status; +}  static void  nfs4_set_claim_prev(struct nfsd4_open *open, bool has_session) @@ -3123,7 +3752,7 @@ static bool nfsd4_cb_channel_good(struct nfs4_client *clp)  	return clp->cl_minorversion && clp->cl_cb_state == NFSD4_CB_UNKNOWN;  } -static struct file_lock *nfs4_alloc_init_lease(struct nfs4_delegation *dp, int flag) +static struct file_lock *nfs4_alloc_init_lease(struct nfs4_file *fp, int flag)  {  	struct file_lock *fl; @@ -3135,53 +3764,101 @@ static struct file_lock *nfs4_alloc_init_lease(struct nfs4_delegation *dp, int f  	fl->fl_flags = FL_DELEG;  	fl->fl_type = flag == NFS4_OPEN_DELEGATE_READ? F_RDLCK: F_WRLCK;  	fl->fl_end = OFFSET_MAX; -	fl->fl_owner = (fl_owner_t)(dp->dl_file); +	fl->fl_owner = (fl_owner_t)fp;  	fl->fl_pid = current->tgid;  	return fl;  }  static int nfs4_setlease(struct nfs4_delegation *dp)  { -	struct nfs4_file *fp = dp->dl_file; +	struct nfs4_file *fp = dp->dl_stid.sc_file;  	struct file_lock *fl; -	int status; +	struct file *filp; +	int status = 0; -	fl = nfs4_alloc_init_lease(dp, NFS4_OPEN_DELEGATE_READ); +	fl = nfs4_alloc_init_lease(fp, NFS4_OPEN_DELEGATE_READ);  	if (!fl)  		return -ENOMEM; -	fl->fl_file = find_readable_file(fp); -	status = vfs_setlease(fl->fl_file, fl->fl_type, &fl); -	if (status) -		goto out_free; +	filp = find_readable_file(fp); +	if (!filp) { +		/* We should always have a readable file here */ +		WARN_ON_ONCE(1); +		return -EBADF; +	} +	fl->fl_file = filp; +	status = vfs_setlease(filp, fl->fl_type, &fl); +	if (status) { +		locks_free_lock(fl); +		goto out_fput; +	} +	spin_lock(&state_lock); +	spin_lock(&fp->fi_lock); +	/* Did the lease get broken before we took the lock? */ +	status = -EAGAIN; +	if (fp->fi_had_conflict) +		goto out_unlock; +	/* Race breaker */ +	if (fp->fi_lease) { +		status = 0; +		atomic_inc(&fp->fi_delegees); +		hash_delegation_locked(dp, fp); +		goto out_unlock; +	}  	fp->fi_lease = fl; -	fp->fi_deleg_file = get_file(fl->fl_file); +	fp->fi_deleg_file = filp;  	atomic_set(&fp->fi_delegees, 1); -	spin_lock(&state_lock);  	hash_delegation_locked(dp, fp); +	spin_unlock(&fp->fi_lock);  	spin_unlock(&state_lock);  	return 0; -out_free: -	locks_free_lock(fl); +out_unlock: +	spin_unlock(&fp->fi_lock); +	spin_unlock(&state_lock); +out_fput: +	fput(filp);  	return status;  } -static int nfs4_set_delegation(struct nfs4_delegation *dp, struct nfs4_file *fp) +static struct nfs4_delegation * +nfs4_set_delegation(struct nfs4_client *clp, struct svc_fh *fh, +		    struct nfs4_file *fp)  { +	int status; +	struct nfs4_delegation *dp; +  	if (fp->fi_had_conflict) -		return -EAGAIN; +		return ERR_PTR(-EAGAIN); + +	dp = alloc_init_deleg(clp, fh); +	if (!dp) +		return ERR_PTR(-ENOMEM); +  	get_nfs4_file(fp); -	dp->dl_file = fp; -	if (!fp->fi_lease) -		return nfs4_setlease(dp);  	spin_lock(&state_lock); +	spin_lock(&fp->fi_lock); +	dp->dl_stid.sc_file = fp; +	if (!fp->fi_lease) { +		spin_unlock(&fp->fi_lock); +		spin_unlock(&state_lock); +		status = nfs4_setlease(dp); +		goto out; +	}  	atomic_inc(&fp->fi_delegees);  	if (fp->fi_had_conflict) { -		spin_unlock(&state_lock); -		return -EAGAIN; +		status = -EAGAIN; +		goto out_unlock;  	}  	hash_delegation_locked(dp, fp); +	status = 0; +out_unlock: +	spin_unlock(&fp->fi_lock);  	spin_unlock(&state_lock); -	return 0; +out: +	if (status) { +		nfs4_put_stid(&dp->dl_stid); +		return ERR_PTR(status); +	} +	return dp;  }  static void nfsd4_open_deleg_none_ext(struct nfsd4_open *open, int status) @@ -3212,11 +3889,12 @@ static void nfsd4_open_deleg_none_ext(struct nfsd4_open *open, int status)   * proper support for them.   */  static void -nfs4_open_delegation(struct net *net, struct svc_fh *fh, -		     struct nfsd4_open *open, struct nfs4_ol_stateid *stp) +nfs4_open_delegation(struct svc_fh *fh, struct nfsd4_open *open, +			struct nfs4_ol_stateid *stp)  {  	struct nfs4_delegation *dp; -	struct nfs4_openowner *oo = container_of(stp->st_stateowner, struct nfs4_openowner, oo_owner); +	struct nfs4_openowner *oo = openowner(stp->st_stateowner); +	struct nfs4_client *clp = stp->st_stid.sc_client;  	int cb_up;  	int status = 0; @@ -3235,7 +3913,7 @@ nfs4_open_delegation(struct net *net, struct svc_fh *fh,  			 * Let's not give out any delegations till everyone's  			 * had the chance to reclaim theirs....  			 */ -			if (locks_in_grace(net)) +			if (locks_in_grace(clp->net))  				goto out_no_deleg;  			if (!cb_up || !(oo->oo_flags & NFS4_OO_CONFIRMED))  				goto out_no_deleg; @@ -3254,21 +3932,17 @@ nfs4_open_delegation(struct net *net, struct svc_fh *fh,  		default:  			goto out_no_deleg;  	} -	dp = alloc_init_deleg(oo->oo_owner.so_client, stp, fh); -	if (dp == NULL) +	dp = nfs4_set_delegation(clp, fh, stp->st_stid.sc_file); +	if (IS_ERR(dp))  		goto out_no_deleg; -	status = nfs4_set_delegation(dp, stp->st_file); -	if (status) -		goto out_free;  	memcpy(&open->op_delegate_stateid, &dp->dl_stid.sc_stateid, sizeof(dp->dl_stid.sc_stateid));  	dprintk("NFSD: delegation stateid=" STATEID_FMT "\n",  		STATEID_VAL(&dp->dl_stid.sc_stateid));  	open->op_delegate_type = NFS4_OPEN_DELEGATE_READ; +	nfs4_put_stid(&dp->dl_stid);  	return; -out_free: -	destroy_delegation(dp);  out_no_deleg:  	open->op_delegate_type = NFS4_OPEN_DELEGATE_NONE;  	if (open->op_claim_type == NFS4_OPEN_CLAIM_PREVIOUS && @@ -3301,16 +3975,12 @@ static void nfsd4_deleg_xgrade_none_ext(struct nfsd4_open *open,  	 */  } -/* - * called with nfs4_lock_state() held. - */  __be32  nfsd4_process_open2(struct svc_rqst *rqstp, struct svc_fh *current_fh, struct nfsd4_open *open)  {  	struct nfsd4_compoundres *resp = rqstp->rq_resp;  	struct nfs4_client *cl = open->op_openowner->oo_owner.so_client;  	struct nfs4_file *fp = NULL; -	struct inode *ino = current_fh->fh_dentry->d_inode;  	struct nfs4_ol_stateid *stp = NULL;  	struct nfs4_delegation *dp = NULL;  	__be32 status; @@ -3320,21 +3990,18 @@ nfsd4_process_open2(struct svc_rqst *rqstp, struct svc_fh *current_fh, struct nf  	 * and check for delegations in the process of being recalled.  	 * If not found, create the nfs4_file struct  	 */ -	fp = find_file(ino); -	if (fp) { -		if ((status = nfs4_check_open(fp, open, &stp))) -			goto out; +	fp = find_or_add_file(open->op_file, ¤t_fh->fh_handle); +	if (fp != open->op_file) {  		status = nfs4_check_deleg(cl, open, &dp);  		if (status)  			goto out; +		stp = nfsd4_find_existing_open(fp, open);  	} else { +		open->op_file = NULL;  		status = nfserr_bad_stateid;  		if (nfsd4_is_deleg_cur(open))  			goto out;  		status = nfserr_jukebox; -		fp = open->op_file; -		open->op_file = NULL; -		nfsd4_init_file(fp, ino);  	}  	/* @@ -3347,22 +4014,19 @@ nfsd4_process_open2(struct svc_rqst *rqstp, struct svc_fh *current_fh, struct nf  		if (status)  			goto out;  	} else { -		status = nfs4_get_vfs_file(rqstp, fp, current_fh, open); -		if (status) -			goto out; -		status = nfsd4_truncate(rqstp, current_fh, open); -		if (status) -			goto out;  		stp = open->op_stp;  		open->op_stp = NULL;  		init_open_stateid(stp, fp, open); +		status = nfs4_get_vfs_file(rqstp, fp, current_fh, stp, open); +		if (status) { +			release_open_stateid(stp); +			goto out; +		}  	}  	update_stateid(&stp->st_stid.sc_stateid);  	memcpy(&open->op_stateid, &stp->st_stid.sc_stateid, sizeof(stateid_t));  	if (nfsd4_has_session(&resp->cstate)) { -		open->op_openowner->oo_flags |= NFS4_OO_CONFIRMED; -  		if (open->op_deleg_want & NFS4_SHARE_WANT_NO_DELEG) {  			open->op_delegate_type = NFS4_OPEN_DELEGATE_NONE_EXT;  			open->op_why_no_deleg = WND4_NOT_WANTED; @@ -3374,7 +4038,7 @@ nfsd4_process_open2(struct svc_rqst *rqstp, struct svc_fh *current_fh, struct nf  	* Attempt to hand out a delegation. No error return, because the  	* OPEN succeeds even if we fail.  	*/ -	nfs4_open_delegation(SVC_NET(rqstp), current_fh, open, stp); +	nfs4_open_delegation(current_fh, open, stp);  nodeleg:  	status = nfs_ok; @@ -3397,41 +4061,27 @@ out:  	if (!(open->op_openowner->oo_flags & NFS4_OO_CONFIRMED) &&  	    !nfsd4_has_session(&resp->cstate))  		open->op_rflags |= NFS4_OPEN_RESULT_CONFIRM; +	if (dp) +		nfs4_put_stid(&dp->dl_stid); +	if (stp) +		nfs4_put_stid(&stp->st_stid);  	return status;  } -void nfsd4_cleanup_open_state(struct nfsd4_open *open, __be32 status) +void nfsd4_cleanup_open_state(struct nfsd4_compound_state *cstate, +			      struct nfsd4_open *open, __be32 status)  {  	if (open->op_openowner) { -		struct nfs4_openowner *oo = open->op_openowner; +		struct nfs4_stateowner *so = &open->op_openowner->oo_owner; -		if (!list_empty(&oo->oo_owner.so_stateids)) -			list_del_init(&oo->oo_close_lru); -		if (oo->oo_flags & NFS4_OO_NEW) { -			if (status) { -				release_openowner(oo); -				open->op_openowner = NULL; -			} else -				oo->oo_flags &= ~NFS4_OO_NEW; -		} +		nfsd4_cstate_assign_replay(cstate, so); +		nfs4_put_stateowner(so);  	}  	if (open->op_file)  		nfsd4_free_file(open->op_file);  	if (open->op_stp) -		free_generic_stateid(open->op_stp); -} - -static __be32 lookup_clientid(clientid_t *clid, bool session, struct nfsd_net *nn, struct nfs4_client **clp) -{ -	struct nfs4_client *found; - -	if (STALE_CLIENTID(clid, nn)) -		return nfserr_stale_clientid; -	found = find_confirmed_client(clid, session, nn); -	if (clp) -		*clp = found; -	return found ? nfs_ok : nfserr_expired; +		nfs4_put_stid(&open->op_stp->st_stid);  }  __be32 @@ -3442,19 +4092,18 @@ nfsd4_renew(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,  	__be32 status;  	struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id); -	nfs4_lock_state();  	dprintk("process_renew(%08x/%08x): starting\n",   			clid->cl_boot, clid->cl_id); -	status = lookup_clientid(clid, cstate->minorversion, nn, &clp); +	status = lookup_clientid(clid, cstate, nn);  	if (status)  		goto out; +	clp = cstate->clp;  	status = nfserr_cb_path_down;  	if (!list_empty(&clp->cl_delegations)  			&& clp->cl_cb_state != NFSD4_CB_UP)  		goto out;  	status = nfs_ok;  out: -	nfs4_unlock_state();  	return status;  } @@ -3483,12 +4132,11 @@ nfs4_laundromat(struct nfsd_net *nn)  	struct nfs4_client *clp;  	struct nfs4_openowner *oo;  	struct nfs4_delegation *dp; +	struct nfs4_ol_stateid *stp;  	struct list_head *pos, *next, reaplist;  	time_t cutoff = get_seconds() - nn->nfsd4_lease;  	time_t t, new_timeo = nn->nfsd4_lease; -	nfs4_lock_state(); -  	dprintk("NFSD: laundromat service - starting\n");  	nfsd4_end_grace(nn);  	INIT_LIST_HEAD(&reaplist); @@ -3505,13 +4153,14 @@ nfs4_laundromat(struct nfsd_net *nn)  				clp->cl_clientid.cl_id);  			continue;  		} -		list_move(&clp->cl_lru, &reaplist); +		list_add(&clp->cl_lru, &reaplist);  	}  	spin_unlock(&nn->client_lock);  	list_for_each_safe(pos, next, &reaplist) {  		clp = list_entry(pos, struct nfs4_client, cl_lru);  		dprintk("NFSD: purging unused client (clientid %08x)\n",  			clp->cl_clientid.cl_id); +		list_del_init(&clp->cl_lru);  		expire_client(clp);  	}  	spin_lock(&state_lock); @@ -3524,24 +4173,37 @@ nfs4_laundromat(struct nfsd_net *nn)  			new_timeo = min(new_timeo, t);  			break;  		} -		list_move(&dp->dl_recall_lru, &reaplist); +		unhash_delegation_locked(dp); +		list_add(&dp->dl_recall_lru, &reaplist);  	}  	spin_unlock(&state_lock); -	list_for_each_safe(pos, next, &reaplist) { -		dp = list_entry (pos, struct nfs4_delegation, dl_recall_lru); +	while (!list_empty(&reaplist)) { +		dp = list_first_entry(&reaplist, struct nfs4_delegation, +					dl_recall_lru); +		list_del_init(&dp->dl_recall_lru);  		revoke_delegation(dp);  	} -	list_for_each_safe(pos, next, &nn->close_lru) { -		oo = container_of(pos, struct nfs4_openowner, oo_close_lru); -		if (time_after((unsigned long)oo->oo_time, (unsigned long)cutoff)) { + +	spin_lock(&nn->client_lock); +	while (!list_empty(&nn->close_lru)) { +		oo = list_first_entry(&nn->close_lru, struct nfs4_openowner, +					oo_close_lru); +		if (time_after((unsigned long)oo->oo_time, +			       (unsigned long)cutoff)) {  			t = oo->oo_time - cutoff;  			new_timeo = min(new_timeo, t);  			break;  		} -		release_openowner(oo); +		list_del_init(&oo->oo_close_lru); +		stp = oo->oo_last_closed_stid; +		oo->oo_last_closed_stid = NULL; +		spin_unlock(&nn->client_lock); +		nfs4_put_stid(&stp->st_stid); +		spin_lock(&nn->client_lock);  	} +	spin_unlock(&nn->client_lock); +  	new_timeo = max_t(time_t, new_timeo, NFSD_LAUNDROMAT_MINTIMEOUT); -	nfs4_unlock_state();  	return new_timeo;  } @@ -3564,7 +4226,7 @@ laundromat_main(struct work_struct *laundry)  static inline __be32 nfs4_check_fh(struct svc_fh *fhp, struct nfs4_ol_stateid *stp)  { -	if (fhp->fh_dentry->d_inode != stp->st_file->fi_inode) +	if (!nfsd_fh_match(&fhp->fh_handle, &stp->st_stid.sc_file->fi_fhandle))  		return nfserr_bad_stateid;  	return nfs_ok;  } @@ -3666,10 +4328,10 @@ static __be32 nfsd4_validate_stateid(struct nfs4_client *cl, stateid_t *stateid)  {  	struct nfs4_stid *s;  	struct nfs4_ol_stateid *ols; -	__be32 status; +	__be32 status = nfserr_bad_stateid;  	if (ZERO_STATEID(stateid) || ONE_STATEID(stateid)) -		return nfserr_bad_stateid; +		return status;  	/* Client debugging aid. */  	if (!same_clid(&stateid->si_opaque.so_clid, &cl->cl_clientid)) {  		char addr_str[INET6_ADDRSTRLEN]; @@ -3677,53 +4339,62 @@ static __be32 nfsd4_validate_stateid(struct nfs4_client *cl, stateid_t *stateid)  				 sizeof(addr_str));  		pr_warn_ratelimited("NFSD: client %s testing state ID "  					"with incorrect client ID\n", addr_str); -		return nfserr_bad_stateid; +		return status;  	} -	s = find_stateid(cl, stateid); +	spin_lock(&cl->cl_lock); +	s = find_stateid_locked(cl, stateid);  	if (!s) -		return nfserr_bad_stateid; +		goto out_unlock;  	status = check_stateid_generation(stateid, &s->sc_stateid, 1);  	if (status) -		return status; +		goto out_unlock;  	switch (s->sc_type) {  	case NFS4_DELEG_STID: -		return nfs_ok; +		status = nfs_ok; +		break;  	case NFS4_REVOKED_DELEG_STID: -		return nfserr_deleg_revoked; +		status = nfserr_deleg_revoked; +		break;  	case NFS4_OPEN_STID:  	case NFS4_LOCK_STID:  		ols = openlockstateid(s);  		if (ols->st_stateowner->so_is_open_owner  	    			&& !(openowner(ols->st_stateowner)->oo_flags  						& NFS4_OO_CONFIRMED)) -			return nfserr_bad_stateid; -		return nfs_ok; +			status = nfserr_bad_stateid; +		else +			status = nfs_ok; +		break;  	default:  		printk("unknown stateid type %x\n", s->sc_type); +		/* Fallthrough */  	case NFS4_CLOSED_STID: -		return nfserr_bad_stateid; +	case NFS4_CLOSED_DELEG_STID: +		status = nfserr_bad_stateid;  	} +out_unlock: +	spin_unlock(&cl->cl_lock); +	return status;  } -static __be32 nfsd4_lookup_stateid(stateid_t *stateid, unsigned char typemask, -				   struct nfs4_stid **s, bool sessions, -				   struct nfsd_net *nn) +static __be32 +nfsd4_lookup_stateid(struct nfsd4_compound_state *cstate, +		     stateid_t *stateid, unsigned char typemask, +		     struct nfs4_stid **s, struct nfsd_net *nn)  { -	struct nfs4_client *cl;  	__be32 status;  	if (ZERO_STATEID(stateid) || ONE_STATEID(stateid))  		return nfserr_bad_stateid; -	status = lookup_clientid(&stateid->si_opaque.so_clid, sessions, -							nn, &cl); +	status = lookup_clientid(&stateid->si_opaque.so_clid, cstate, nn);  	if (status == nfserr_stale_clientid) { -		if (sessions) +		if (cstate->session)  			return nfserr_bad_stateid;  		return nfserr_stale_stateid;  	}  	if (status)  		return status; -	*s = find_stateid_by_type(cl, stateid, typemask); +	*s = find_stateid_by_type(cstate->clp, stateid, typemask);  	if (!*s)  		return nfserr_bad_stateid;  	return nfs_ok; @@ -3754,12 +4425,11 @@ nfs4_preprocess_stateid_op(struct net *net, struct nfsd4_compound_state *cstate,  	if (ZERO_STATEID(stateid) || ONE_STATEID(stateid))  		return check_special_stateids(net, current_fh, stateid, flags); -	nfs4_lock_state(); - -	status = nfsd4_lookup_stateid(stateid, NFS4_DELEG_STID|NFS4_OPEN_STID|NFS4_LOCK_STID, -				      &s, cstate->minorversion, nn); +	status = nfsd4_lookup_stateid(cstate, stateid, +				NFS4_DELEG_STID|NFS4_OPEN_STID|NFS4_LOCK_STID, +				&s, nn);  	if (status) -		goto out; +		return status;  	status = check_stateid_generation(stateid, &s->sc_stateid, nfsd4_has_session(cstate));  	if (status)  		goto out; @@ -3770,12 +4440,13 @@ nfs4_preprocess_stateid_op(struct net *net, struct nfsd4_compound_state *cstate,  		if (status)  			goto out;  		if (filpp) { -			file = dp->dl_file->fi_deleg_file; +			file = dp->dl_stid.sc_file->fi_deleg_file;  			if (!file) {  				WARN_ON_ONCE(1);  				status = nfserr_serverfault;  				goto out;  			} +			get_file(file);  		}  		break;  	case NFS4_OPEN_STID: @@ -3791,10 +4462,12 @@ nfs4_preprocess_stateid_op(struct net *net, struct nfsd4_compound_state *cstate,  		if (status)  			goto out;  		if (filpp) { +			struct nfs4_file *fp = stp->st_stid.sc_file; +  			if (flags & RD_STATE) -				file = find_readable_file(stp->st_file); +				file = find_readable_file(fp);  			else -				file = find_writeable_file(stp->st_file); +				file = find_writeable_file(fp);  		}  		break;  	default: @@ -3803,28 +4476,12 @@ nfs4_preprocess_stateid_op(struct net *net, struct nfsd4_compound_state *cstate,  	}  	status = nfs_ok;  	if (file) -		*filpp = get_file(file); +		*filpp = file;  out: -	nfs4_unlock_state(); +	nfs4_put_stid(s);  	return status;  } -static __be32 -nfsd4_free_lock_stateid(struct nfs4_ol_stateid *stp) -{ -	struct nfs4_lockowner *lo = lockowner(stp->st_stateowner); - -	if (check_for_locks(stp->st_file, lo)) -		return nfserr_locks_held; -	/* -	 * Currently there's a 1-1 lock stateid<->lockowner -	 * correspondance, and we have to delete the lockowner when we -	 * delete the lock stateid: -	 */ -	release_lockowner(lo); -	return nfs_ok; -} -  /*   * Test if the stateid is valid   */ @@ -3835,11 +4492,9 @@ nfsd4_test_stateid(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,  	struct nfsd4_test_stateid_id *stateid;  	struct nfs4_client *cl = cstate->session->se_client; -	nfs4_lock_state();  	list_for_each_entry(stateid, &test_stateid->ts_stateid_list, ts_id_list)  		stateid->ts_id_status =  			nfsd4_validate_stateid(cl, &stateid->ts_id_stateid); -	nfs4_unlock_state();  	return nfs_ok;  } @@ -3851,37 +4506,50 @@ nfsd4_free_stateid(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,  	stateid_t *stateid = &free_stateid->fr_stateid;  	struct nfs4_stid *s;  	struct nfs4_delegation *dp; +	struct nfs4_ol_stateid *stp;  	struct nfs4_client *cl = cstate->session->se_client;  	__be32 ret = nfserr_bad_stateid; -	nfs4_lock_state(); -	s = find_stateid(cl, stateid); +	spin_lock(&cl->cl_lock); +	s = find_stateid_locked(cl, stateid);  	if (!s) -		goto out; +		goto out_unlock;  	switch (s->sc_type) {  	case NFS4_DELEG_STID:  		ret = nfserr_locks_held; -		goto out; +		break;  	case NFS4_OPEN_STID: -	case NFS4_LOCK_STID:  		ret = check_stateid_generation(stateid, &s->sc_stateid, 1);  		if (ret) -			goto out; -		if (s->sc_type == NFS4_LOCK_STID) -			ret = nfsd4_free_lock_stateid(openlockstateid(s)); -		else -			ret = nfserr_locks_held; +			break; +		ret = nfserr_locks_held;  		break; +	case NFS4_LOCK_STID: +		ret = check_stateid_generation(stateid, &s->sc_stateid, 1); +		if (ret) +			break; +		stp = openlockstateid(s); +		ret = nfserr_locks_held; +		if (check_for_locks(stp->st_stid.sc_file, +				    lockowner(stp->st_stateowner))) +			break; +		unhash_lock_stateid(stp); +		spin_unlock(&cl->cl_lock); +		nfs4_put_stid(s); +		ret = nfs_ok; +		goto out;  	case NFS4_REVOKED_DELEG_STID:  		dp = delegstateid(s); -		destroy_revoked_delegation(dp); +		list_del_init(&dp->dl_recall_lru); +		spin_unlock(&cl->cl_lock); +		nfs4_put_stid(s);  		ret = nfs_ok; -		break; -	default: -		ret = nfserr_bad_stateid; +		goto out; +	/* Default falls through and returns nfserr_bad_stateid */  	} +out_unlock: +	spin_unlock(&cl->cl_lock);  out: -	nfs4_unlock_state();  	return ret;  } @@ -3926,20 +4594,24 @@ nfs4_preprocess_seqid_op(struct nfsd4_compound_state *cstate, u32 seqid,  {  	__be32 status;  	struct nfs4_stid *s; +	struct nfs4_ol_stateid *stp = NULL;  	dprintk("NFSD: %s: seqid=%d stateid = " STATEID_FMT "\n", __func__,  		seqid, STATEID_VAL(stateid));  	*stpp = NULL; -	status = nfsd4_lookup_stateid(stateid, typemask, &s, -				      cstate->minorversion, nn); +	status = nfsd4_lookup_stateid(cstate, stateid, typemask, &s, nn);  	if (status)  		return status; -	*stpp = openlockstateid(s); -	if (!nfsd4_has_session(cstate)) -		cstate->replay_owner = (*stpp)->st_stateowner; +	stp = openlockstateid(s); +	nfsd4_cstate_assign_replay(cstate, stp->st_stateowner); -	return nfs4_seqid_op_checks(cstate, stateid, seqid, *stpp); +	status = nfs4_seqid_op_checks(cstate, stateid, seqid, stp); +	if (!status) +		*stpp = stp; +	else +		nfs4_put_stid(&stp->st_stid); +	return status;  }  static __be32 nfs4_preprocess_confirmed_seqid_op(struct nfsd4_compound_state *cstate, u32 seqid, @@ -3947,14 +4619,18 @@ static __be32 nfs4_preprocess_confirmed_seqid_op(struct nfsd4_compound_state *cs  {  	__be32 status;  	struct nfs4_openowner *oo; +	struct nfs4_ol_stateid *stp;  	status = nfs4_preprocess_seqid_op(cstate, seqid, stateid, -						NFS4_OPEN_STID, stpp, nn); +						NFS4_OPEN_STID, &stp, nn);  	if (status)  		return status; -	oo = openowner((*stpp)->st_stateowner); -	if (!(oo->oo_flags & NFS4_OO_CONFIRMED)) +	oo = openowner(stp->st_stateowner); +	if (!(oo->oo_flags & NFS4_OO_CONFIRMED)) { +		nfs4_put_stid(&stp->st_stid);  		return nfserr_bad_stateid; +	} +	*stpp = stp;  	return nfs_ok;  } @@ -3974,8 +4650,6 @@ nfsd4_open_confirm(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,  	if (status)  		return status; -	nfs4_lock_state(); -  	status = nfs4_preprocess_seqid_op(cstate,  					oc->oc_seqid, &oc->oc_req_stateid,  					NFS4_OPEN_STID, &stp, nn); @@ -3984,7 +4658,7 @@ nfsd4_open_confirm(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,  	oo = openowner(stp->st_stateowner);  	status = nfserr_bad_stateid;  	if (oo->oo_flags & NFS4_OO_CONFIRMED) -		goto out; +		goto put_stateid;  	oo->oo_flags |= NFS4_OO_CONFIRMED;  	update_stateid(&stp->st_stid.sc_stateid);  	memcpy(&oc->oc_resp_stateid, &stp->st_stid.sc_stateid, sizeof(stateid_t)); @@ -3993,10 +4667,10 @@ nfsd4_open_confirm(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,  	nfsd4_client_record_create(oo->oo_owner.so_client);  	status = nfs_ok; +put_stateid: +	nfs4_put_stid(&stp->st_stid);  out:  	nfsd4_bump_seqid(cstate, status); -	if (!cstate->replay_owner) -		nfs4_unlock_state();  	return status;  } @@ -4004,7 +4678,7 @@ static inline void nfs4_stateid_downgrade_bit(struct nfs4_ol_stateid *stp, u32 a  {  	if (!test_access(access, stp))  		return; -	nfs4_file_put_access(stp->st_file, nfs4_access_to_omode(access)); +	nfs4_file_put_access(stp->st_stid.sc_file, access);  	clear_access(access, stp);  } @@ -4026,16 +4700,6 @@ static inline void nfs4_stateid_downgrade(struct nfs4_ol_stateid *stp, u32 to_ac  	}  } -static void -reset_union_bmap_deny(unsigned long deny, struct nfs4_ol_stateid *stp) -{ -	int i; -	for (i = 0; i < 4; i++) { -		if ((i & deny) != i) -			clear_deny(i, stp); -	} -} -  __be32  nfsd4_open_downgrade(struct svc_rqst *rqstp,  		     struct nfsd4_compound_state *cstate, @@ -4053,21 +4717,20 @@ nfsd4_open_downgrade(struct svc_rqst *rqstp,  		dprintk("NFSD: %s: od_deleg_want=0x%x ignored\n", __func__,  			od->od_deleg_want); -	nfs4_lock_state();  	status = nfs4_preprocess_confirmed_seqid_op(cstate, od->od_seqid,  					&od->od_stateid, &stp, nn);  	if (status)  		goto out;   	status = nfserr_inval;  	if (!test_access(od->od_share_access, stp)) { -		dprintk("NFSD: access not a subset current bitmap: 0x%lx, input access=%08x\n", +		dprintk("NFSD: access not a subset of current bitmap: 0x%hhx, input access=%08x\n",  			stp->st_access_bmap, od->od_share_access); -		goto out; +		goto put_stateid;  	}  	if (!test_deny(od->od_share_deny, stp)) { -		dprintk("NFSD:deny not a subset current bitmap: 0x%lx, input deny=%08x\n", +		dprintk("NFSD: deny not a subset of current bitmap: 0x%hhx, input deny=%08x\n",  			stp->st_deny_bmap, od->od_share_deny); -		goto out; +		goto put_stateid;  	}  	nfs4_stateid_downgrade(stp, od->od_share_access); @@ -4076,17 +4739,31 @@ nfsd4_open_downgrade(struct svc_rqst *rqstp,  	update_stateid(&stp->st_stid.sc_stateid);  	memcpy(&od->od_stateid, &stp->st_stid.sc_stateid, sizeof(stateid_t));  	status = nfs_ok; +put_stateid: +	nfs4_put_stid(&stp->st_stid);  out:  	nfsd4_bump_seqid(cstate, status); -	if (!cstate->replay_owner) -		nfs4_unlock_state();  	return status;  }  static void nfsd4_close_open_stateid(struct nfs4_ol_stateid *s)  { -	unhash_open_stateid(s); +	struct nfs4_client *clp = s->st_stid.sc_client; +	LIST_HEAD(reaplist); +  	s->st_stid.sc_type = NFS4_CLOSED_STID; +	spin_lock(&clp->cl_lock); +	unhash_open_stateid(s, &reaplist); + +	if (clp->cl_minorversion) { +		put_ol_stateid_locked(s, &reaplist); +		spin_unlock(&clp->cl_lock); +		free_ol_stateid_reaplist(&reaplist); +	} else { +		spin_unlock(&clp->cl_lock); +		free_ol_stateid_reaplist(&reaplist); +		move_to_close_lru(s, clp->net); +	}  }  /* @@ -4097,7 +4774,6 @@ nfsd4_close(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,  	    struct nfsd4_close *close)  {  	__be32 status; -	struct nfs4_openowner *oo;  	struct nfs4_ol_stateid *stp;  	struct net *net = SVC_NET(rqstp);  	struct nfsd_net *nn = net_generic(net, nfsd_net_id); @@ -4105,7 +4781,6 @@ nfsd4_close(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,  	dprintk("NFSD: nfsd4_close on file %pd\n",   			cstate->current_fh.fh_dentry); -	nfs4_lock_state();  	status = nfs4_preprocess_seqid_op(cstate, close->cl_seqid,  					&close->cl_stateid,  					NFS4_OPEN_STID|NFS4_CLOSED_STID, @@ -4113,31 +4788,14 @@ nfsd4_close(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,  	nfsd4_bump_seqid(cstate, status);  	if (status)  		goto out;  -	oo = openowner(stp->st_stateowner);  	update_stateid(&stp->st_stid.sc_stateid);  	memcpy(&close->cl_stateid, &stp->st_stid.sc_stateid, sizeof(stateid_t));  	nfsd4_close_open_stateid(stp); -	if (cstate->minorversion) -		free_generic_stateid(stp); -	else -		oo->oo_last_closed_stid = stp; - -	if (list_empty(&oo->oo_owner.so_stateids)) { -		if (cstate->minorversion) -			release_openowner(oo); -		else { -			/* -			 * In the 4.0 case we need to keep the owners around a -			 * little while to handle CLOSE replay. -			 */ -			move_to_close_lru(oo, SVC_NET(rqstp)); -		} -	} +	/* put reference from nfs4_preprocess_seqid_op */ +	nfs4_put_stid(&stp->st_stid);  out: -	if (!cstate->replay_owner) -		nfs4_unlock_state();  	return status;  } @@ -4154,28 +4812,24 @@ nfsd4_delegreturn(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,  	if ((status = fh_verify(rqstp, &cstate->current_fh, S_IFREG, 0)))  		return status; -	nfs4_lock_state(); -	status = nfsd4_lookup_stateid(stateid, NFS4_DELEG_STID, &s, -				      cstate->minorversion, nn); +	status = nfsd4_lookup_stateid(cstate, stateid, NFS4_DELEG_STID, &s, nn);  	if (status)  		goto out;  	dp = delegstateid(s);  	status = check_stateid_generation(stateid, &dp->dl_stid.sc_stateid, nfsd4_has_session(cstate));  	if (status) -		goto out; +		goto put_stateid;  	destroy_delegation(dp); +put_stateid: +	nfs4_put_stid(&dp->dl_stid);  out: -	nfs4_unlock_state(); -  	return status;  }  #define LOFF_OVERFLOW(start, len)      ((u64)(len) > ~(u64)(start)) -#define LOCKOWNER_INO_HASH_MASK (LOCKOWNER_INO_HASH_SIZE - 1) -  static inline u64  end_offset(u64 start, u64 len)  { @@ -4196,13 +4850,6 @@ last_byte_offset(u64 start, u64 len)  	return end > start ? end - 1: NFS4_MAX_UINT64;  } -static unsigned int lockowner_ino_hashval(struct inode *inode, u32 cl_id, struct xdr_netobj *ownername) -{ -	return (file_hashval(inode) + cl_id -			+ opaque_hashval(ownername->data, ownername->len)) -		& LOCKOWNER_INO_HASH_MASK; -} -  /*   * TODO: Linux file offsets are _signed_ 64-bit quantities, which means that   * we can't properly handle lock requests that go beyond the (2^63 - 1)-th @@ -4255,47 +4902,56 @@ nevermind:  		deny->ld_type = NFS4_WRITE_LT;  } -static bool same_lockowner_ino(struct nfs4_lockowner *lo, struct inode *inode, clientid_t *clid, struct xdr_netobj *owner) +static struct nfs4_lockowner * +find_lockowner_str_locked(clientid_t *clid, struct xdr_netobj *owner, +		struct nfs4_client *clp)  { -	struct nfs4_ol_stateid *lst; +	unsigned int strhashval = ownerstr_hashval(owner); +	struct nfs4_stateowner *so; -	if (!same_owner_str(&lo->lo_owner, owner, clid)) -		return false; -	if (list_empty(&lo->lo_owner.so_stateids)) { -		WARN_ON_ONCE(1); -		return false; +	lockdep_assert_held(&clp->cl_lock); + +	list_for_each_entry(so, &clp->cl_ownerstr_hashtbl[strhashval], +			    so_strhash) { +		if (so->so_is_open_owner) +			continue; +		if (!same_owner_str(so, owner)) +			continue; +		atomic_inc(&so->so_count); +		return lockowner(so);  	} -	lst = list_first_entry(&lo->lo_owner.so_stateids, -			       struct nfs4_ol_stateid, st_perstateowner); -	return lst->st_file->fi_inode == inode; +	return NULL;  }  static struct nfs4_lockowner * -find_lockowner_str(struct inode *inode, clientid_t *clid, -		   struct xdr_netobj *owner, struct nfsd_net *nn) +find_lockowner_str(clientid_t *clid, struct xdr_netobj *owner, +		struct nfs4_client *clp)  { -	unsigned int hashval = lockowner_ino_hashval(inode, clid->cl_id, owner);  	struct nfs4_lockowner *lo; -	list_for_each_entry(lo, &nn->lockowner_ino_hashtbl[hashval], lo_owner_ino_hash) { -		if (same_lockowner_ino(lo, inode, clid, owner)) -			return lo; -	} -	return NULL; +	spin_lock(&clp->cl_lock); +	lo = find_lockowner_str_locked(clid, owner, clp); +	spin_unlock(&clp->cl_lock); +	return lo;  } -static void hash_lockowner(struct nfs4_lockowner *lo, unsigned int strhashval, struct nfs4_client *clp, struct nfs4_ol_stateid *open_stp) +static void nfs4_unhash_lockowner(struct nfs4_stateowner *sop)  { -	struct inode *inode = open_stp->st_file->fi_inode; -	unsigned int inohash = lockowner_ino_hashval(inode, -			clp->cl_clientid.cl_id, &lo->lo_owner.so_owner); -	struct nfsd_net *nn = net_generic(clp->net, nfsd_net_id); +	unhash_lockowner_locked(lockowner(sop)); +} + +static void nfs4_free_lockowner(struct nfs4_stateowner *sop) +{ +	struct nfs4_lockowner *lo = lockowner(sop); -	list_add(&lo->lo_owner.so_strhash, &nn->ownerstr_hashtbl[strhashval]); -	list_add(&lo->lo_owner_ino_hash, &nn->lockowner_ino_hashtbl[inohash]); -	list_add(&lo->lo_perstateid, &open_stp->st_lockowners); +	kmem_cache_free(lockowner_slab, lo);  } +static const struct nfs4_stateowner_operations lockowner_ops = { +	.so_unhash =	nfs4_unhash_lockowner, +	.so_free =	nfs4_free_lockowner, +}; +  /*   * Alloc a lock owner structure.   * Called in nfsd4_lock - therefore, OPEN and OPEN_CONFIRM (if needed) has  @@ -4303,42 +4959,107 @@ static void hash_lockowner(struct nfs4_lockowner *lo, unsigned int strhashval, s   *   * strhashval = ownerstr_hashval   */ -  static struct nfs4_lockowner * -alloc_init_lock_stateowner(unsigned int strhashval, struct nfs4_client *clp, struct nfs4_ol_stateid *open_stp, struct nfsd4_lock *lock) { -	struct nfs4_lockowner *lo; +alloc_init_lock_stateowner(unsigned int strhashval, struct nfs4_client *clp, +			   struct nfs4_ol_stateid *open_stp, +			   struct nfsd4_lock *lock) +{ +	struct nfs4_lockowner *lo, *ret;  	lo = alloc_stateowner(lockowner_slab, &lock->lk_new_owner, clp);  	if (!lo)  		return NULL;  	INIT_LIST_HEAD(&lo->lo_owner.so_stateids);  	lo->lo_owner.so_is_open_owner = 0; -	/* It is the openowner seqid that will be incremented in encode in the -	 * case of new lockowners; so increment the lock seqid manually: */ -	lo->lo_owner.so_seqid = lock->lk_new_lock_seqid + 1; -	hash_lockowner(lo, strhashval, clp, open_stp); +	lo->lo_owner.so_seqid = lock->lk_new_lock_seqid; +	lo->lo_owner.so_ops = &lockowner_ops; +	spin_lock(&clp->cl_lock); +	ret = find_lockowner_str_locked(&clp->cl_clientid, +			&lock->lk_new_owner, clp); +	if (ret == NULL) { +		list_add(&lo->lo_owner.so_strhash, +			 &clp->cl_ownerstr_hashtbl[strhashval]); +		ret = lo; +	} else +		nfs4_free_lockowner(&lo->lo_owner); +	spin_unlock(&clp->cl_lock);  	return lo;  } -static struct nfs4_ol_stateid * -alloc_init_lock_stateid(struct nfs4_lockowner *lo, struct nfs4_file *fp, struct nfs4_ol_stateid *open_stp) +static void +init_lock_stateid(struct nfs4_ol_stateid *stp, struct nfs4_lockowner *lo, +		  struct nfs4_file *fp, struct inode *inode, +		  struct nfs4_ol_stateid *open_stp)  { -	struct nfs4_ol_stateid *stp;  	struct nfs4_client *clp = lo->lo_owner.so_client; -	stp = nfs4_alloc_stateid(clp); -	if (stp == NULL) -		return NULL; +	lockdep_assert_held(&clp->cl_lock); + +	atomic_inc(&stp->st_stid.sc_count);  	stp->st_stid.sc_type = NFS4_LOCK_STID; -	list_add(&stp->st_perfile, &fp->fi_stateids); -	list_add(&stp->st_perstateowner, &lo->lo_owner.so_stateids);  	stp->st_stateowner = &lo->lo_owner; +	atomic_inc(&lo->lo_owner.so_count);  	get_nfs4_file(fp); -	stp->st_file = fp; +	stp->st_stid.sc_file = fp; +	stp->st_stid.sc_free = nfs4_free_lock_stateid;  	stp->st_access_bmap = 0;  	stp->st_deny_bmap = open_stp->st_deny_bmap;  	stp->st_openstp = open_stp; -	return stp; +	list_add(&stp->st_locks, &open_stp->st_locks); +	list_add(&stp->st_perstateowner, &lo->lo_owner.so_stateids); +	spin_lock(&fp->fi_lock); +	list_add(&stp->st_perfile, &fp->fi_stateids); +	spin_unlock(&fp->fi_lock); +} + +static struct nfs4_ol_stateid * +find_lock_stateid(struct nfs4_lockowner *lo, struct nfs4_file *fp) +{ +	struct nfs4_ol_stateid *lst; +	struct nfs4_client *clp = lo->lo_owner.so_client; + +	lockdep_assert_held(&clp->cl_lock); + +	list_for_each_entry(lst, &lo->lo_owner.so_stateids, st_perstateowner) { +		if (lst->st_stid.sc_file == fp) { +			atomic_inc(&lst->st_stid.sc_count); +			return lst; +		} +	} +	return NULL; +} + +static struct nfs4_ol_stateid * +find_or_create_lock_stateid(struct nfs4_lockowner *lo, struct nfs4_file *fi, +			    struct inode *inode, struct nfs4_ol_stateid *ost, +			    bool *new) +{ +	struct nfs4_stid *ns = NULL; +	struct nfs4_ol_stateid *lst; +	struct nfs4_openowner *oo = openowner(ost->st_stateowner); +	struct nfs4_client *clp = oo->oo_owner.so_client; + +	spin_lock(&clp->cl_lock); +	lst = find_lock_stateid(lo, fi); +	if (lst == NULL) { +		spin_unlock(&clp->cl_lock); +		ns = nfs4_alloc_stid(clp, stateid_slab); +		if (ns == NULL) +			return NULL; + +		spin_lock(&clp->cl_lock); +		lst = find_lock_stateid(lo, fi); +		if (likely(!lst)) { +			lst = openlockstateid(ns); +			init_lock_stateid(lst, lo, fi, inode, ost); +			ns = NULL; +			*new = true; +		} +	} +	spin_unlock(&clp->cl_lock); +	if (ns) +		nfs4_put_stid(ns); +	return lst;  }  static int @@ -4350,46 +5071,53 @@ check_lock_length(u64 offset, u64 length)  static void get_lock_access(struct nfs4_ol_stateid *lock_stp, u32 access)  { -	struct nfs4_file *fp = lock_stp->st_file; -	int oflag = nfs4_access_to_omode(access); +	struct nfs4_file *fp = lock_stp->st_stid.sc_file; + +	lockdep_assert_held(&fp->fi_lock);  	if (test_access(access, lock_stp))  		return; -	nfs4_file_get_access(fp, oflag); +	__nfs4_file_get_access(fp, access);  	set_access(access, lock_stp);  } -static __be32 lookup_or_create_lock_state(struct nfsd4_compound_state *cstate, struct nfs4_ol_stateid *ost, struct nfsd4_lock *lock, struct nfs4_ol_stateid **lst, bool *new) +static __be32 +lookup_or_create_lock_state(struct nfsd4_compound_state *cstate, +			    struct nfs4_ol_stateid *ost, +			    struct nfsd4_lock *lock, +			    struct nfs4_ol_stateid **lst, bool *new)  { -	struct nfs4_file *fi = ost->st_file; +	__be32 status; +	struct nfs4_file *fi = ost->st_stid.sc_file;  	struct nfs4_openowner *oo = openowner(ost->st_stateowner);  	struct nfs4_client *cl = oo->oo_owner.so_client; +	struct inode *inode = cstate->current_fh.fh_dentry->d_inode;  	struct nfs4_lockowner *lo;  	unsigned int strhashval; -	struct nfsd_net *nn = net_generic(cl->net, nfsd_net_id); - -	lo = find_lockowner_str(fi->fi_inode, &cl->cl_clientid, -				&lock->v.new.owner, nn); -	if (lo) { -		if (!cstate->minorversion) -			return nfserr_bad_seqid; -		/* XXX: a lockowner always has exactly one stateid: */ -		*lst = list_first_entry(&lo->lo_owner.so_stateids, -				struct nfs4_ol_stateid, st_perstateowner); -		return nfs_ok; + +	lo = find_lockowner_str(&cl->cl_clientid, &lock->v.new.owner, cl); +	if (!lo) { +		strhashval = ownerstr_hashval(&lock->v.new.owner); +		lo = alloc_init_lock_stateowner(strhashval, cl, ost, lock); +		if (lo == NULL) +			return nfserr_jukebox; +	} else { +		/* with an existing lockowner, seqids must be the same */ +		status = nfserr_bad_seqid; +		if (!cstate->minorversion && +		    lock->lk_new_lock_seqid != lo->lo_owner.so_seqid) +			goto out;  	} -	strhashval = ownerstr_hashval(cl->cl_clientid.cl_id, -			&lock->v.new.owner); -	lo = alloc_init_lock_stateowner(strhashval, cl, ost, lock); -	if (lo == NULL) -		return nfserr_jukebox; -	*lst = alloc_init_lock_stateid(lo, fi, ost); + +	*lst = find_or_create_lock_stateid(lo, fi, inode, ost, new);  	if (*lst == NULL) { -		release_lockowner(lo); -		return nfserr_jukebox; +		status = nfserr_jukebox; +		goto out;  	} -	*new = true; -	return nfs_ok; +	status = nfs_ok; +out: +	nfs4_put_stateowner(&lo->lo_owner); +	return status;  }  /* @@ -4401,14 +5129,16 @@ nfsd4_lock(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,  {  	struct nfs4_openowner *open_sop = NULL;  	struct nfs4_lockowner *lock_sop = NULL; -	struct nfs4_ol_stateid *lock_stp; +	struct nfs4_ol_stateid *lock_stp = NULL; +	struct nfs4_ol_stateid *open_stp = NULL; +	struct nfs4_file *fp;  	struct file *filp = NULL;  	struct file_lock *file_lock = NULL;  	struct file_lock *conflock = NULL;  	__be32 status = 0; -	bool new_state = false;  	int lkflg;  	int err; +	bool new = false;  	struct net *net = SVC_NET(rqstp);  	struct nfsd_net *nn = net_generic(net, nfsd_net_id); @@ -4425,11 +5155,7 @@ nfsd4_lock(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,  		return status;  	} -	nfs4_lock_state(); -  	if (lock->lk_is_new) { -		struct nfs4_ol_stateid *open_stp = NULL; -  		if (nfsd4_has_session(cstate))  			/* See rfc 5661 18.10.3: given clientid is ignored: */  			memcpy(&lock->v.new.clientid, @@ -4453,12 +5179,13 @@ nfsd4_lock(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,  						&lock->v.new.clientid))  			goto out;  		status = lookup_or_create_lock_state(cstate, open_stp, lock, -							&lock_stp, &new_state); -	} else +							&lock_stp, &new); +	} else {  		status = nfs4_preprocess_seqid_op(cstate,  				       lock->lk_old_lock_seqid,  				       &lock->lk_old_lock_stateid,  				       NFS4_LOCK_STID, &lock_stp, nn); +	}  	if (status)  		goto out;  	lock_sop = lockowner(lock_stp->st_stateowner); @@ -4482,20 +5209,25 @@ nfsd4_lock(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,  		goto out;  	} +	fp = lock_stp->st_stid.sc_file;  	locks_init_lock(file_lock);  	switch (lock->lk_type) {  		case NFS4_READ_LT:  		case NFS4_READW_LT: -			filp = find_readable_file(lock_stp->st_file); +			spin_lock(&fp->fi_lock); +			filp = find_readable_file_locked(fp);  			if (filp)  				get_lock_access(lock_stp, NFS4_SHARE_ACCESS_READ); +			spin_unlock(&fp->fi_lock);  			file_lock->fl_type = F_RDLCK;  			break;  		case NFS4_WRITE_LT:  		case NFS4_WRITEW_LT: -			filp = find_writeable_file(lock_stp->st_file); +			spin_lock(&fp->fi_lock); +			filp = find_writeable_file_locked(fp);  			if (filp)  				get_lock_access(lock_stp, NFS4_SHARE_ACCESS_WRITE); +			spin_unlock(&fp->fi_lock);  			file_lock->fl_type = F_WRLCK;  			break;  		default: @@ -4544,11 +5276,27 @@ nfsd4_lock(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,  		break;  	}  out: -	if (status && new_state) -		release_lockowner(lock_sop); +	if (filp) +		fput(filp); +	if (lock_stp) { +		/* Bump seqid manually if the 4.0 replay owner is openowner */ +		if (cstate->replay_owner && +		    cstate->replay_owner != &lock_sop->lo_owner && +		    seqid_mutating_err(ntohl(status))) +			lock_sop->lo_owner.so_seqid++; + +		/* +		 * If this is a new, never-before-used stateid, and we are +		 * returning an error, then just go ahead and release it. +		 */ +		if (status && new) +			release_lock_stateid(lock_stp); + +		nfs4_put_stid(&lock_stp->st_stid); +	} +	if (open_stp) +		nfs4_put_stid(&open_stp->st_stid);  	nfsd4_bump_seqid(cstate, status); -	if (!cstate->replay_owner) -		nfs4_unlock_state();  	if (file_lock)  		locks_free_lock(file_lock);  	if (conflock) @@ -4580,9 +5328,8 @@ __be32  nfsd4_lockt(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,  	    struct nfsd4_lockt *lockt)  { -	struct inode *inode;  	struct file_lock *file_lock = NULL; -	struct nfs4_lockowner *lo; +	struct nfs4_lockowner *lo = NULL;  	__be32 status;  	struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id); @@ -4592,10 +5339,8 @@ nfsd4_lockt(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,  	if (check_lock_length(lockt->lt_offset, lockt->lt_length))  		 return nfserr_inval; -	nfs4_lock_state(); -  	if (!nfsd4_has_session(cstate)) { -		status = lookup_clientid(&lockt->lt_clientid, false, nn, NULL); +		status = lookup_clientid(&lockt->lt_clientid, cstate, nn);  		if (status)  			goto out;  	} @@ -4603,7 +5348,6 @@ nfsd4_lockt(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,  	if ((status = fh_verify(rqstp, &cstate->current_fh, S_IFREG, 0)))  		goto out; -	inode = cstate->current_fh.fh_dentry->d_inode;  	file_lock = locks_alloc_lock();  	if (!file_lock) {  		dprintk("NFSD: %s: unable to allocate lock!\n", __func__); @@ -4626,7 +5370,8 @@ nfsd4_lockt(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,  		goto out;  	} -	lo = find_lockowner_str(inode, &lockt->lt_clientid, &lockt->lt_owner, nn); +	lo = find_lockowner_str(&lockt->lt_clientid, &lockt->lt_owner, +				cstate->clp);  	if (lo)  		file_lock->fl_owner = (fl_owner_t)lo;  	file_lock->fl_pid = current->tgid; @@ -4646,7 +5391,8 @@ nfsd4_lockt(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,  		nfs4_set_lock_denied(file_lock, &lockt->lt_denied);  	}  out: -	nfs4_unlock_state(); +	if (lo) +		nfs4_put_stateowner(&lo->lo_owner);  	if (file_lock)  		locks_free_lock(file_lock);  	return status; @@ -4670,23 +5416,21 @@ nfsd4_locku(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,  	if (check_lock_length(locku->lu_offset, locku->lu_length))  		 return nfserr_inval; -	nfs4_lock_state(); -									          	status = nfs4_preprocess_seqid_op(cstate, locku->lu_seqid,  					&locku->lu_stateid, NFS4_LOCK_STID,  					&stp, nn);  	if (status)  		goto out; -	filp = find_any_file(stp->st_file); +	filp = find_any_file(stp->st_stid.sc_file);  	if (!filp) {  		status = nfserr_lock_range; -		goto out; +		goto put_stateid;  	}  	file_lock = locks_alloc_lock();  	if (!file_lock) {  		dprintk("NFSD: %s: unable to allocate lock!\n", __func__);  		status = nfserr_jukebox; -		goto out; +		goto fput;  	}  	locks_init_lock(file_lock);  	file_lock->fl_type = F_UNLCK; @@ -4708,41 +5452,51 @@ nfsd4_locku(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,  	}  	update_stateid(&stp->st_stid.sc_stateid);  	memcpy(&locku->lu_stateid, &stp->st_stid.sc_stateid, sizeof(stateid_t)); - +fput: +	fput(filp); +put_stateid: +	nfs4_put_stid(&stp->st_stid);  out:  	nfsd4_bump_seqid(cstate, status); -	if (!cstate->replay_owner) -		nfs4_unlock_state();  	if (file_lock)  		locks_free_lock(file_lock);  	return status;  out_nfserr:  	status = nfserrno(err); -	goto out; +	goto fput;  }  /*   * returns - * 	1: locks held by lockowner - * 	0: no locks held by lockowner + * 	true:  locks held by lockowner + * 	false: no locks held by lockowner   */ -static int -check_for_locks(struct nfs4_file *filp, struct nfs4_lockowner *lowner) +static bool +check_for_locks(struct nfs4_file *fp, struct nfs4_lockowner *lowner)  {  	struct file_lock **flpp; -	struct inode *inode = filp->fi_inode; -	int status = 0; +	int status = false; +	struct file *filp = find_any_file(fp); +	struct inode *inode; + +	if (!filp) { +		/* Any valid lock stateid should have some sort of access */ +		WARN_ON_ONCE(1); +		return status; +	} + +	inode = file_inode(filp);  	spin_lock(&inode->i_lock);  	for (flpp = &inode->i_flock; *flpp != NULL; flpp = &(*flpp)->fl_next) {  		if ((*flpp)->fl_owner == (fl_owner_t)lowner) { -			status = 1; -			goto out; +			status = true; +			break;  		}  	} -out:  	spin_unlock(&inode->i_lock); +	fput(filp);  	return status;  } @@ -4753,53 +5507,46 @@ nfsd4_release_lockowner(struct svc_rqst *rqstp,  {  	clientid_t *clid = &rlockowner->rl_clientid;  	struct nfs4_stateowner *sop; -	struct nfs4_lockowner *lo; +	struct nfs4_lockowner *lo = NULL;  	struct nfs4_ol_stateid *stp;  	struct xdr_netobj *owner = &rlockowner->rl_owner; -	struct list_head matches; -	unsigned int hashval = ownerstr_hashval(clid->cl_id, owner); +	unsigned int hashval = ownerstr_hashval(owner);  	__be32 status;  	struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id); +	struct nfs4_client *clp;  	dprintk("nfsd4_release_lockowner clientid: (%08x/%08x):\n",  		clid->cl_boot, clid->cl_id); -	nfs4_lock_state(); - -	status = lookup_clientid(clid, cstate->minorversion, nn, NULL); +	status = lookup_clientid(clid, cstate, nn);  	if (status) -		goto out; +		return status; -	status = nfserr_locks_held; -	INIT_LIST_HEAD(&matches); +	clp = cstate->clp; +	/* Find the matching lock stateowner */ +	spin_lock(&clp->cl_lock); +	list_for_each_entry(sop, &clp->cl_ownerstr_hashtbl[hashval], +			    so_strhash) { -	list_for_each_entry(sop, &nn->ownerstr_hashtbl[hashval], so_strhash) { -		if (sop->so_is_open_owner) +		if (sop->so_is_open_owner || !same_owner_str(sop, owner))  			continue; -		if (!same_owner_str(sop, owner, clid)) -			continue; -		list_for_each_entry(stp, &sop->so_stateids, -				st_perstateowner) { -			lo = lockowner(sop); -			if (check_for_locks(stp->st_file, lo)) -				goto out; -			list_add(&lo->lo_list, &matches); + +		/* see if there are still any locks associated with it */ +		lo = lockowner(sop); +		list_for_each_entry(stp, &sop->so_stateids, st_perstateowner) { +			if (check_for_locks(stp->st_stid.sc_file, lo)) { +				status = nfserr_locks_held; +				spin_unlock(&clp->cl_lock); +				return status; +			}  		} + +		atomic_inc(&sop->so_count); +		break;  	} -	/* Clients probably won't expect us to return with some (but not all) -	 * of the lockowner state released; so don't release any until all -	 * have been checked. */ -	status = nfs_ok; -	while (!list_empty(&matches)) { -		lo = list_entry(matches.next, struct nfs4_lockowner, -								lo_list); -		/* unhash_stateowner deletes so_perclient only -		 * for openowners. */ -		list_del(&lo->lo_list); +	spin_unlock(&clp->cl_lock); +	if (lo)  		release_lockowner(lo); -	} -out: -	nfs4_unlock_state();  	return status;  } @@ -4887,34 +5634,123 @@ nfsd4_find_reclaim_client(const char *recdir, struct nfsd_net *nn)  * Called from OPEN. Look for clientid in reclaim list.  */  __be32 -nfs4_check_open_reclaim(clientid_t *clid, bool sessions, struct nfsd_net *nn) +nfs4_check_open_reclaim(clientid_t *clid, +		struct nfsd4_compound_state *cstate, +		struct nfsd_net *nn)  { -	struct nfs4_client *clp; +	__be32 status;  	/* find clientid in conf_id_hashtbl */ -	clp = find_confirmed_client(clid, sessions, nn); -	if (clp == NULL) +	status = lookup_clientid(clid, cstate, nn); +	if (status)  		return nfserr_reclaim_bad; -	return nfsd4_client_record_check(clp) ? nfserr_reclaim_bad : nfs_ok; +	if (nfsd4_client_record_check(cstate->clp)) +		return nfserr_reclaim_bad; + +	return nfs_ok;  }  #ifdef CONFIG_NFSD_FAULT_INJECTION +static inline void +put_client(struct nfs4_client *clp) +{ +	atomic_dec(&clp->cl_refcount); +} -u64 nfsd_forget_client(struct nfs4_client *clp, u64 max) +static struct nfs4_client * +nfsd_find_client(struct sockaddr_storage *addr, size_t addr_size)  { -	if (mark_client_expired(clp)) -		return 0; -	expire_client(clp); -	return 1; +	struct nfs4_client *clp; +	struct nfsd_net *nn = net_generic(current->nsproxy->net_ns, +					  nfsd_net_id); + +	if (!nfsd_netns_ready(nn)) +		return NULL; + +	list_for_each_entry(clp, &nn->client_lru, cl_lru) { +		if (memcmp(&clp->cl_addr, addr, addr_size) == 0) +			return clp; +	} +	return NULL;  } -u64 nfsd_print_client(struct nfs4_client *clp, u64 num) +u64 +nfsd_inject_print_clients(void)  { +	struct nfs4_client *clp; +	u64 count = 0; +	struct nfsd_net *nn = net_generic(current->nsproxy->net_ns, +					  nfsd_net_id);  	char buf[INET6_ADDRSTRLEN]; -	rpc_ntop((struct sockaddr *)&clp->cl_addr, buf, sizeof(buf)); -	printk(KERN_INFO "NFS Client: %s\n", buf); -	return 1; + +	if (!nfsd_netns_ready(nn)) +		return 0; + +	spin_lock(&nn->client_lock); +	list_for_each_entry(clp, &nn->client_lru, cl_lru) { +		rpc_ntop((struct sockaddr *)&clp->cl_addr, buf, sizeof(buf)); +		pr_info("NFS Client: %s\n", buf); +		++count; +	} +	spin_unlock(&nn->client_lock); + +	return count; +} + +u64 +nfsd_inject_forget_client(struct sockaddr_storage *addr, size_t addr_size) +{ +	u64 count = 0; +	struct nfs4_client *clp; +	struct nfsd_net *nn = net_generic(current->nsproxy->net_ns, +					  nfsd_net_id); + +	if (!nfsd_netns_ready(nn)) +		return count; + +	spin_lock(&nn->client_lock); +	clp = nfsd_find_client(addr, addr_size); +	if (clp) { +		if (mark_client_expired_locked(clp) == nfs_ok) +			++count; +		else +			clp = NULL; +	} +	spin_unlock(&nn->client_lock); + +	if (clp) +		expire_client(clp); + +	return count; +} + +u64 +nfsd_inject_forget_clients(u64 max) +{ +	u64 count = 0; +	struct nfs4_client *clp, *next; +	struct nfsd_net *nn = net_generic(current->nsproxy->net_ns, +						nfsd_net_id); +	LIST_HEAD(reaplist); + +	if (!nfsd_netns_ready(nn)) +		return count; + +	spin_lock(&nn->client_lock); +	list_for_each_entry_safe(clp, next, &nn->client_lru, cl_lru) { +		if (mark_client_expired_locked(clp) == nfs_ok) { +			list_add(&clp->cl_lru, &reaplist); +			if (max != 0 && ++count >= max) +				break; +		} +	} +	spin_unlock(&nn->client_lock); + +	list_for_each_entry_safe(clp, next, &reaplist, cl_lru) +		expire_client(clp); + +	return count;  }  static void nfsd_print_count(struct nfs4_client *clp, unsigned int count, @@ -4925,158 +5761,484 @@ static void nfsd_print_count(struct nfs4_client *clp, unsigned int count,  	printk(KERN_INFO "NFS Client: %s has %u %s\n", buf, count, type);  } -static u64 nfsd_foreach_client_lock(struct nfs4_client *clp, u64 max, void (*func)(struct nfs4_lockowner *)) +static void +nfsd_inject_add_lock_to_list(struct nfs4_ol_stateid *lst, +			     struct list_head *collect) +{ +	struct nfs4_client *clp = lst->st_stid.sc_client; +	struct nfsd_net *nn = net_generic(current->nsproxy->net_ns, +					  nfsd_net_id); + +	if (!collect) +		return; + +	lockdep_assert_held(&nn->client_lock); +	atomic_inc(&clp->cl_refcount); +	list_add(&lst->st_locks, collect); +} + +static u64 nfsd_foreach_client_lock(struct nfs4_client *clp, u64 max, +				    struct list_head *collect, +				    void (*func)(struct nfs4_ol_stateid *))  {  	struct nfs4_openowner *oop; -	struct nfs4_lockowner *lop, *lo_next;  	struct nfs4_ol_stateid *stp, *st_next; +	struct nfs4_ol_stateid *lst, *lst_next;  	u64 count = 0; +	spin_lock(&clp->cl_lock);  	list_for_each_entry(oop, &clp->cl_openowners, oo_perclient) { -		list_for_each_entry_safe(stp, st_next, &oop->oo_owner.so_stateids, st_perstateowner) { -			list_for_each_entry_safe(lop, lo_next, &stp->st_lockowners, lo_perstateid) { -				if (func) -					func(lop); -				if (++count == max) -					return count; +		list_for_each_entry_safe(stp, st_next, +				&oop->oo_owner.so_stateids, st_perstateowner) { +			list_for_each_entry_safe(lst, lst_next, +					&stp->st_locks, st_locks) { +				if (func) { +					func(lst); +					nfsd_inject_add_lock_to_list(lst, +								collect); +				} +				++count; +				/* +				 * Despite the fact that these functions deal +				 * with 64-bit integers for "count", we must +				 * ensure that it doesn't blow up the +				 * clp->cl_refcount. Throw a warning if we +				 * start to approach INT_MAX here. +				 */ +				WARN_ON_ONCE(count == (INT_MAX / 2)); +				if (count == max) +					goto out;  			}  		}  	} +out: +	spin_unlock(&clp->cl_lock);  	return count;  } -u64 nfsd_forget_client_locks(struct nfs4_client *clp, u64 max) +static u64 +nfsd_collect_client_locks(struct nfs4_client *clp, struct list_head *collect, +			  u64 max)  { -	return nfsd_foreach_client_lock(clp, max, release_lockowner); +	return nfsd_foreach_client_lock(clp, max, collect, unhash_lock_stateid);  } -u64 nfsd_print_client_locks(struct nfs4_client *clp, u64 max) +static u64 +nfsd_print_client_locks(struct nfs4_client *clp)  { -	u64 count = nfsd_foreach_client_lock(clp, max, NULL); +	u64 count = nfsd_foreach_client_lock(clp, 0, NULL, NULL);  	nfsd_print_count(clp, count, "locked files");  	return count;  } -static u64 nfsd_foreach_client_open(struct nfs4_client *clp, u64 max, void (*func)(struct nfs4_openowner *)) +u64 +nfsd_inject_print_locks(void) +{ +	struct nfs4_client *clp; +	u64 count = 0; +	struct nfsd_net *nn = net_generic(current->nsproxy->net_ns, +						nfsd_net_id); + +	if (!nfsd_netns_ready(nn)) +		return 0; + +	spin_lock(&nn->client_lock); +	list_for_each_entry(clp, &nn->client_lru, cl_lru) +		count += nfsd_print_client_locks(clp); +	spin_unlock(&nn->client_lock); + +	return count; +} + +static void +nfsd_reap_locks(struct list_head *reaplist) +{ +	struct nfs4_client *clp; +	struct nfs4_ol_stateid *stp, *next; + +	list_for_each_entry_safe(stp, next, reaplist, st_locks) { +		list_del_init(&stp->st_locks); +		clp = stp->st_stid.sc_client; +		nfs4_put_stid(&stp->st_stid); +		put_client(clp); +	} +} + +u64 +nfsd_inject_forget_client_locks(struct sockaddr_storage *addr, size_t addr_size) +{ +	unsigned int count = 0; +	struct nfs4_client *clp; +	struct nfsd_net *nn = net_generic(current->nsproxy->net_ns, +						nfsd_net_id); +	LIST_HEAD(reaplist); + +	if (!nfsd_netns_ready(nn)) +		return count; + +	spin_lock(&nn->client_lock); +	clp = nfsd_find_client(addr, addr_size); +	if (clp) +		count = nfsd_collect_client_locks(clp, &reaplist, 0); +	spin_unlock(&nn->client_lock); +	nfsd_reap_locks(&reaplist); +	return count; +} + +u64 +nfsd_inject_forget_locks(u64 max) +{ +	u64 count = 0; +	struct nfs4_client *clp; +	struct nfsd_net *nn = net_generic(current->nsproxy->net_ns, +						nfsd_net_id); +	LIST_HEAD(reaplist); + +	if (!nfsd_netns_ready(nn)) +		return count; + +	spin_lock(&nn->client_lock); +	list_for_each_entry(clp, &nn->client_lru, cl_lru) { +		count += nfsd_collect_client_locks(clp, &reaplist, max - count); +		if (max != 0 && count >= max) +			break; +	} +	spin_unlock(&nn->client_lock); +	nfsd_reap_locks(&reaplist); +	return count; +} + +static u64 +nfsd_foreach_client_openowner(struct nfs4_client *clp, u64 max, +			      struct list_head *collect, +			      void (*func)(struct nfs4_openowner *))  {  	struct nfs4_openowner *oop, *next; +	struct nfsd_net *nn = net_generic(current->nsproxy->net_ns, +						nfsd_net_id);  	u64 count = 0; +	lockdep_assert_held(&nn->client_lock); + +	spin_lock(&clp->cl_lock);  	list_for_each_entry_safe(oop, next, &clp->cl_openowners, oo_perclient) { -		if (func) +		if (func) {  			func(oop); -		if (++count == max) +			if (collect) { +				atomic_inc(&clp->cl_refcount); +				list_add(&oop->oo_perclient, collect); +			} +		} +		++count; +		/* +		 * Despite the fact that these functions deal with +		 * 64-bit integers for "count", we must ensure that +		 * it doesn't blow up the clp->cl_refcount. Throw a +		 * warning if we start to approach INT_MAX here. +		 */ +		WARN_ON_ONCE(count == (INT_MAX / 2)); +		if (count == max)  			break;  	} +	spin_unlock(&clp->cl_lock);  	return count;  } -u64 nfsd_forget_client_openowners(struct nfs4_client *clp, u64 max) +static u64 +nfsd_print_client_openowners(struct nfs4_client *clp)  { -	return nfsd_foreach_client_open(clp, max, release_openowner); +	u64 count = nfsd_foreach_client_openowner(clp, 0, NULL, NULL); + +	nfsd_print_count(clp, count, "openowners"); +	return count;  } -u64 nfsd_print_client_openowners(struct nfs4_client *clp, u64 max) +static u64 +nfsd_collect_client_openowners(struct nfs4_client *clp, +			       struct list_head *collect, u64 max)  { -	u64 count = nfsd_foreach_client_open(clp, max, NULL); -	nfsd_print_count(clp, count, "open files"); -	return count; +	return nfsd_foreach_client_openowner(clp, max, collect, +						unhash_openowner_locked);  } -static u64 nfsd_find_all_delegations(struct nfs4_client *clp, u64 max, -				     struct list_head *victims) +u64 +nfsd_inject_print_openowners(void)  { -	struct nfs4_delegation *dp, *next; +	struct nfs4_client *clp;  	u64 count = 0; +	struct nfsd_net *nn = net_generic(current->nsproxy->net_ns, +						nfsd_net_id); + +	if (!nfsd_netns_ready(nn)) +		return 0; + +	spin_lock(&nn->client_lock); +	list_for_each_entry(clp, &nn->client_lru, cl_lru) +		count += nfsd_print_client_openowners(clp); +	spin_unlock(&nn->client_lock); -	lockdep_assert_held(&state_lock); -	list_for_each_entry_safe(dp, next, &clp->cl_delegations, dl_perclnt) { -		if (victims) -			list_move(&dp->dl_recall_lru, victims); -		if (++count == max) -			break; -	}  	return count;  } -u64 nfsd_forget_client_delegations(struct nfs4_client *clp, u64 max) +static void +nfsd_reap_openowners(struct list_head *reaplist)  { -	struct nfs4_delegation *dp, *next; -	LIST_HEAD(victims); -	u64 count; +	struct nfs4_client *clp; +	struct nfs4_openowner *oop, *next; -	spin_lock(&state_lock); -	count = nfsd_find_all_delegations(clp, max, &victims); -	spin_unlock(&state_lock); +	list_for_each_entry_safe(oop, next, reaplist, oo_perclient) { +		list_del_init(&oop->oo_perclient); +		clp = oop->oo_owner.so_client; +		release_openowner(oop); +		put_client(clp); +	} +} -	list_for_each_entry_safe(dp, next, &victims, dl_recall_lru) -		revoke_delegation(dp); +u64 +nfsd_inject_forget_client_openowners(struct sockaddr_storage *addr, +				     size_t addr_size) +{ +	unsigned int count = 0; +	struct nfs4_client *clp; +	struct nfsd_net *nn = net_generic(current->nsproxy->net_ns, +						nfsd_net_id); +	LIST_HEAD(reaplist); +	if (!nfsd_netns_ready(nn)) +		return count; + +	spin_lock(&nn->client_lock); +	clp = nfsd_find_client(addr, addr_size); +	if (clp) +		count = nfsd_collect_client_openowners(clp, &reaplist, 0); +	spin_unlock(&nn->client_lock); +	nfsd_reap_openowners(&reaplist);  	return count;  } -u64 nfsd_recall_client_delegations(struct nfs4_client *clp, u64 max) +u64 +nfsd_inject_forget_openowners(u64 max)  { -	struct nfs4_delegation *dp, *next; -	LIST_HEAD(victims); -	u64 count; +	u64 count = 0; +	struct nfs4_client *clp; +	struct nfsd_net *nn = net_generic(current->nsproxy->net_ns, +						nfsd_net_id); +	LIST_HEAD(reaplist); -	spin_lock(&state_lock); -	count = nfsd_find_all_delegations(clp, max, &victims); -	list_for_each_entry_safe(dp, next, &victims, dl_recall_lru) -		nfsd_break_one_deleg(dp); -	spin_unlock(&state_lock); +	if (!nfsd_netns_ready(nn)) +		return count; +	spin_lock(&nn->client_lock); +	list_for_each_entry(clp, &nn->client_lru, cl_lru) { +		count += nfsd_collect_client_openowners(clp, &reaplist, +							max - count); +		if (max != 0 && count >= max) +			break; +	} +	spin_unlock(&nn->client_lock); +	nfsd_reap_openowners(&reaplist);  	return count;  } -u64 nfsd_print_client_delegations(struct nfs4_client *clp, u64 max) +static u64 nfsd_find_all_delegations(struct nfs4_client *clp, u64 max, +				     struct list_head *victims)  { +	struct nfs4_delegation *dp, *next; +	struct nfsd_net *nn = net_generic(current->nsproxy->net_ns, +						nfsd_net_id);  	u64 count = 0; +	lockdep_assert_held(&nn->client_lock); +  	spin_lock(&state_lock); -	count = nfsd_find_all_delegations(clp, max, NULL); +	list_for_each_entry_safe(dp, next, &clp->cl_delegations, dl_perclnt) { +		if (victims) { +			/* +			 * It's not safe to mess with delegations that have a +			 * non-zero dl_time. They might have already been broken +			 * and could be processed by the laundromat outside of +			 * the state_lock. Just leave them be. +			 */ +			if (dp->dl_time != 0) +				continue; + +			atomic_inc(&clp->cl_refcount); +			unhash_delegation_locked(dp); +			list_add(&dp->dl_recall_lru, victims); +		} +		++count; +		/* +		 * Despite the fact that these functions deal with +		 * 64-bit integers for "count", we must ensure that +		 * it doesn't blow up the clp->cl_refcount. Throw a +		 * warning if we start to approach INT_MAX here. +		 */ +		WARN_ON_ONCE(count == (INT_MAX / 2)); +		if (count == max) +			break; +	}  	spin_unlock(&state_lock); +	return count; +} + +static u64 +nfsd_print_client_delegations(struct nfs4_client *clp) +{ +	u64 count = nfsd_find_all_delegations(clp, 0, NULL);  	nfsd_print_count(clp, count, "delegations");  	return count;  } -u64 nfsd_for_n_state(u64 max, u64 (*func)(struct nfs4_client *, u64)) +u64 +nfsd_inject_print_delegations(void)  { -	struct nfs4_client *clp, *next; +	struct nfs4_client *clp;  	u64 count = 0; -	struct nfsd_net *nn = net_generic(current->nsproxy->net_ns, nfsd_net_id); +	struct nfsd_net *nn = net_generic(current->nsproxy->net_ns, +						nfsd_net_id);  	if (!nfsd_netns_ready(nn))  		return 0; -	list_for_each_entry_safe(clp, next, &nn->client_lru, cl_lru) { -		count += func(clp, max - count); -		if ((max != 0) && (count >= max)) -			break; +	spin_lock(&nn->client_lock); +	list_for_each_entry(clp, &nn->client_lru, cl_lru) +		count += nfsd_print_client_delegations(clp); +	spin_unlock(&nn->client_lock); + +	return count; +} + +static void +nfsd_forget_delegations(struct list_head *reaplist) +{ +	struct nfs4_client *clp; +	struct nfs4_delegation *dp, *next; + +	list_for_each_entry_safe(dp, next, reaplist, dl_recall_lru) { +		list_del_init(&dp->dl_recall_lru); +		clp = dp->dl_stid.sc_client; +		revoke_delegation(dp); +		put_client(clp);  	} +} +u64 +nfsd_inject_forget_client_delegations(struct sockaddr_storage *addr, +				      size_t addr_size) +{ +	u64 count = 0; +	struct nfs4_client *clp; +	struct nfsd_net *nn = net_generic(current->nsproxy->net_ns, +						nfsd_net_id); +	LIST_HEAD(reaplist); + +	if (!nfsd_netns_ready(nn)) +		return count; + +	spin_lock(&nn->client_lock); +	clp = nfsd_find_client(addr, addr_size); +	if (clp) +		count = nfsd_find_all_delegations(clp, 0, &reaplist); +	spin_unlock(&nn->client_lock); + +	nfsd_forget_delegations(&reaplist);  	return count;  } -struct nfs4_client *nfsd_find_client(struct sockaddr_storage *addr, size_t addr_size) +u64 +nfsd_inject_forget_delegations(u64 max)  { +	u64 count = 0;  	struct nfs4_client *clp; -	struct nfsd_net *nn = net_generic(current->nsproxy->net_ns, nfsd_net_id); +	struct nfsd_net *nn = net_generic(current->nsproxy->net_ns, +						nfsd_net_id); +	LIST_HEAD(reaplist);  	if (!nfsd_netns_ready(nn)) -		return NULL; +		return count; +	spin_lock(&nn->client_lock);  	list_for_each_entry(clp, &nn->client_lru, cl_lru) { -		if (memcmp(&clp->cl_addr, addr, addr_size) == 0) -			return clp; +		count += nfsd_find_all_delegations(clp, max - count, &reaplist); +		if (max != 0 && count >= max) +			break;  	} -	return NULL; +	spin_unlock(&nn->client_lock); +	nfsd_forget_delegations(&reaplist); +	return count;  } +static void +nfsd_recall_delegations(struct list_head *reaplist) +{ +	struct nfs4_client *clp; +	struct nfs4_delegation *dp, *next; + +	list_for_each_entry_safe(dp, next, reaplist, dl_recall_lru) { +		list_del_init(&dp->dl_recall_lru); +		clp = dp->dl_stid.sc_client; +		/* +		 * We skipped all entries that had a zero dl_time before, +		 * so we can now reset the dl_time back to 0. If a delegation +		 * break comes in now, then it won't make any difference since +		 * we're recalling it either way. +		 */ +		spin_lock(&state_lock); +		dp->dl_time = 0; +		spin_unlock(&state_lock); +		nfsd_break_one_deleg(dp); +		put_client(clp); +	} +} + +u64 +nfsd_inject_recall_client_delegations(struct sockaddr_storage *addr, +				      size_t addr_size) +{ +	u64 count = 0; +	struct nfs4_client *clp; +	struct nfsd_net *nn = net_generic(current->nsproxy->net_ns, +						nfsd_net_id); +	LIST_HEAD(reaplist); + +	if (!nfsd_netns_ready(nn)) +		return count; + +	spin_lock(&nn->client_lock); +	clp = nfsd_find_client(addr, addr_size); +	if (clp) +		count = nfsd_find_all_delegations(clp, 0, &reaplist); +	spin_unlock(&nn->client_lock); + +	nfsd_recall_delegations(&reaplist); +	return count; +} + +u64 +nfsd_inject_recall_delegations(u64 max) +{ +	u64 count = 0; +	struct nfs4_client *clp, *next; +	struct nfsd_net *nn = net_generic(current->nsproxy->net_ns, +						nfsd_net_id); +	LIST_HEAD(reaplist); + +	if (!nfsd_netns_ready(nn)) +		return count; + +	spin_lock(&nn->client_lock); +	list_for_each_entry_safe(clp, next, &nn->client_lru, cl_lru) { +		count += nfsd_find_all_delegations(clp, max - count, &reaplist); +		if (max != 0 && ++count >= max) +			break; +	} +	spin_unlock(&nn->client_lock); +	nfsd_recall_delegations(&reaplist); +	return count; +}  #endif /* CONFIG_NFSD_FAULT_INJECTION */  /* @@ -5113,14 +6275,6 @@ static int nfs4_state_create_net(struct net *net)  			CLIENT_HASH_SIZE, GFP_KERNEL);  	if (!nn->unconf_id_hashtbl)  		goto err_unconf_id; -	nn->ownerstr_hashtbl = kmalloc(sizeof(struct list_head) * -			OWNER_HASH_SIZE, GFP_KERNEL); -	if (!nn->ownerstr_hashtbl) -		goto err_ownerstr; -	nn->lockowner_ino_hashtbl = kmalloc(sizeof(struct list_head) * -			LOCKOWNER_INO_HASH_SIZE, GFP_KERNEL); -	if (!nn->lockowner_ino_hashtbl) -		goto err_lockowner_ino;  	nn->sessionid_hashtbl = kmalloc(sizeof(struct list_head) *  			SESSION_HASH_SIZE, GFP_KERNEL);  	if (!nn->sessionid_hashtbl) @@ -5130,10 +6284,6 @@ static int nfs4_state_create_net(struct net *net)  		INIT_LIST_HEAD(&nn->conf_id_hashtbl[i]);  		INIT_LIST_HEAD(&nn->unconf_id_hashtbl[i]);  	} -	for (i = 0; i < OWNER_HASH_SIZE; i++) -		INIT_LIST_HEAD(&nn->ownerstr_hashtbl[i]); -	for (i = 0; i < LOCKOWNER_INO_HASH_SIZE; i++) -		INIT_LIST_HEAD(&nn->lockowner_ino_hashtbl[i]);  	for (i = 0; i < SESSION_HASH_SIZE; i++)  		INIT_LIST_HEAD(&nn->sessionid_hashtbl[i]);  	nn->conf_name_tree = RB_ROOT; @@ -5149,10 +6299,6 @@ static int nfs4_state_create_net(struct net *net)  	return 0;  err_sessionid: -	kfree(nn->lockowner_ino_hashtbl); -err_lockowner_ino: -	kfree(nn->ownerstr_hashtbl); -err_ownerstr:  	kfree(nn->unconf_id_hashtbl);  err_unconf_id:  	kfree(nn->conf_id_hashtbl); @@ -5182,8 +6328,6 @@ nfs4_state_destroy_net(struct net *net)  	}  	kfree(nn->sessionid_hashtbl); -	kfree(nn->lockowner_ino_hashtbl); -	kfree(nn->ownerstr_hashtbl);  	kfree(nn->unconf_id_hashtbl);  	kfree(nn->conf_id_hashtbl);  	put_net(net); @@ -5247,22 +6391,22 @@ nfs4_state_shutdown_net(struct net *net)  	cancel_delayed_work_sync(&nn->laundromat_work);  	locks_end_grace(&nn->nfsd4_manager); -	nfs4_lock_state();  	INIT_LIST_HEAD(&reaplist);  	spin_lock(&state_lock);  	list_for_each_safe(pos, next, &nn->del_recall_lru) {  		dp = list_entry (pos, struct nfs4_delegation, dl_recall_lru); -		list_move(&dp->dl_recall_lru, &reaplist); +		unhash_delegation_locked(dp); +		list_add(&dp->dl_recall_lru, &reaplist);  	}  	spin_unlock(&state_lock);  	list_for_each_safe(pos, next, &reaplist) {  		dp = list_entry (pos, struct nfs4_delegation, dl_recall_lru); -		destroy_delegation(dp); +		list_del_init(&dp->dl_recall_lru); +		nfs4_put_stid(&dp->dl_stid);  	}  	nfsd4_client_tracking_exit(net);  	nfs4_state_destroy_net(net); -	nfs4_unlock_state();  }  void | 
