diff options
Diffstat (limited to 'fs/nfsd/nfs4state.c')
| -rw-r--r-- | fs/nfsd/nfs4state.c | 119 | 
1 files changed, 84 insertions, 35 deletions
diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index d5694987f86f..88c347957da5 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -633,18 +633,6 @@ find_readable_file(struct nfs4_file *f)  	return ret;  } -static struct nfsd_file * -find_rw_file(struct nfs4_file *f) -{ -	struct nfsd_file *ret; - -	spin_lock(&f->fi_lock); -	ret = nfsd_file_get(f->fi_fds[O_RDWR]); -	spin_unlock(&f->fi_lock); - -	return ret; -} -  struct nfsd_file *  find_any_file(struct nfs4_file *f)  { @@ -1218,15 +1206,20 @@ nfs4_inc_and_copy_stateid(stateid_t *dst, struct nfs4_stid *stid)  static void put_deleg_file(struct nfs4_file *fp)  { +	struct nfsd_file *rnf = NULL;  	struct nfsd_file *nf = NULL;  	spin_lock(&fp->fi_lock); -	if (--fp->fi_delegees == 0) +	if (--fp->fi_delegees == 0) {  		swap(nf, fp->fi_deleg_file); +		swap(rnf, fp->fi_rdeleg_file); +	}  	spin_unlock(&fp->fi_lock);  	if (nf)  		nfsd_file_put(nf); +	if (rnf) +		nfs4_file_put_access(fp, NFS4_SHARE_ACCESS_READ);  }  static void nfs4_unlock_deleg_lease(struct nfs4_delegation *dp) @@ -3872,7 +3865,6 @@ static __be32 check_forechannel_attrs(struct nfsd4_channel_attrs *ca, struct nfs  	ca->headerpadsz = 0;  	ca->maxreq_sz = min_t(u32, ca->maxreq_sz, maxrpc);  	ca->maxresp_sz = min_t(u32, ca->maxresp_sz, maxrpc); -	ca->maxops = min_t(u32, ca->maxops, NFSD_MAX_OPS_PER_COMPOUND);  	ca->maxresp_cached = min_t(u32, ca->maxresp_cached,  			NFSD_SLOT_CACHE_SIZE + NFSD_MIN_HDR_SEQ_SZ);  	ca->maxreqs = min_t(u32, ca->maxreqs, NFSD_MAX_SLOTS_PER_SESSION); @@ -4697,10 +4689,16 @@ nfsd4_setclientid_confirm(struct svc_rqst *rqstp,  	}  	status = nfs_ok;  	if (conf) { -		old = unconf; -		unhash_client_locked(old); -		nfsd4_change_callback(conf, &unconf->cl_cb_conn); -	} else { +		if (get_client_locked(conf) == nfs_ok) { +			old = unconf; +			unhash_client_locked(old); +			nfsd4_change_callback(conf, &unconf->cl_cb_conn); +		} else { +			conf = NULL; +		} +	} + +	if (!conf) {  		old = find_confirmed_client_by_name(&unconf->cl_name, nn);  		if (old) {  			status = nfserr_clid_inuse; @@ -4717,10 +4715,14 @@ nfsd4_setclientid_confirm(struct svc_rqst *rqstp,  			}  			trace_nfsd_clid_replaced(&old->cl_clientid);  		} +		status = get_client_locked(unconf); +		if (status != nfs_ok) { +			old = NULL; +			goto out; +		}  		move_to_confirmed(unconf);  		conf = unconf;  	} -	get_client_locked(conf);  	spin_unlock(&nn->client_lock);  	if (conf == unconf)  		fsnotify_dentry(conf->cl_nfsd_info_dentry, FS_MODIFY); @@ -4750,6 +4752,7 @@ static void nfsd4_file_init(const struct svc_fh *fh, struct nfs4_file *fp)  	INIT_LIST_HEAD(&fp->fi_clnt_odstate);  	fh_copy_shallow(&fp->fi_fhandle, &fh->fh_handle);  	fp->fi_deleg_file = NULL; +	fp->fi_rdeleg_file = NULL;  	fp->fi_had_conflict = false;  	fp->fi_share_deny = 0;  	memset(fp->fi_fds, 0, sizeof(fp->fi_fds)); @@ -6000,14 +6003,19 @@ nfs4_set_delegation(struct nfsd4_open *open, struct nfs4_ol_stateid *stp,  	 *  "An OPEN_DELEGATE_WRITE delegation allows the client to handle,  	 *   on its own, all opens."  	 * -	 * Furthermore the client can use a write delegation for most READ -	 * operations as well, so we require a O_RDWR file here. +	 * Furthermore, section 9.1.2 says: +	 * +	 *  "In the case of READ, the server may perform the corresponding +	 *  check on the access mode, or it may choose to allow READ for +	 *  OPEN4_SHARE_ACCESS_WRITE, to accommodate clients whose WRITE +	 *  implementation may unavoidably do reads (e.g., due to buffer +	 *  cache constraints)."  	 * -	 * Offer a write delegation in the case of a BOTH open, and ensure -	 * we get the O_RDWR descriptor. +	 *  We choose to offer a write delegation for OPEN with the +	 *  OPEN4_SHARE_ACCESS_WRITE access mode to accommodate such clients.  	 */ -	if ((open->op_share_access & NFS4_SHARE_ACCESS_BOTH) == NFS4_SHARE_ACCESS_BOTH) { -		nf = find_rw_file(fp); +	if (open->op_share_access & NFS4_SHARE_ACCESS_WRITE) { +		nf = find_writeable_file(fp);  		dl_type = deleg_ts ? OPEN_DELEGATE_WRITE_ATTRS_DELEG : OPEN_DELEGATE_WRITE;  	} @@ -6138,7 +6146,7 @@ static bool  nfs4_delegation_stat(struct nfs4_delegation *dp, struct svc_fh *currentfh,  		     struct kstat *stat)  { -	struct nfsd_file *nf = find_rw_file(dp->dl_stid.sc_file); +	struct nfsd_file *nf = find_writeable_file(dp->dl_stid.sc_file);  	struct path path;  	int rc; @@ -6157,6 +6165,34 @@ nfs4_delegation_stat(struct nfs4_delegation *dp, struct svc_fh *currentfh,  }  /* + * Add NFS4_SHARE_ACCESS_READ to the write delegation granted on OPEN + * with NFS4_SHARE_ACCESS_WRITE by allocating separate nfsd_file and + * struct file to be used for read with delegation stateid. + * + */ +static bool +nfsd4_add_rdaccess_to_wrdeleg(struct svc_rqst *rqstp, struct nfsd4_open *open, +			      struct svc_fh *fh, struct nfs4_ol_stateid *stp) +{ +	struct nfs4_file *fp; +	struct nfsd_file *nf = NULL; + +	if ((open->op_share_access & NFS4_SHARE_ACCESS_BOTH) == +			NFS4_SHARE_ACCESS_WRITE) { +		if (nfsd_file_acquire_opened(rqstp, fh, NFSD_MAY_READ, NULL, &nf)) +			return (false); +		fp = stp->st_stid.sc_file; +		spin_lock(&fp->fi_lock); +		__nfs4_file_get_access(fp, NFS4_SHARE_ACCESS_READ); +		fp = stp->st_stid.sc_file; +		fp->fi_fds[O_RDONLY] = nf; +		fp->fi_rdeleg_file = nf; +		spin_unlock(&fp->fi_lock); +	} +	return true; +} + +/*   * The Linux NFS server does not offer write delegations to NFSv4.0   * clients in order to avoid conflicts between write delegations and   * GETATTRs requesting CHANGE or SIZE attributes. @@ -6181,8 +6217,9 @@ nfs4_delegation_stat(struct nfs4_delegation *dp, struct svc_fh *currentfh,   * open or lock state.   */  static void -nfs4_open_delegation(struct nfsd4_open *open, struct nfs4_ol_stateid *stp, -		     struct svc_fh *currentfh) +nfs4_open_delegation(struct svc_rqst *rqstp, struct nfsd4_open *open, +		     struct nfs4_ol_stateid *stp, struct svc_fh *currentfh, +		     struct svc_fh *fh)  {  	struct nfs4_openowner *oo = openowner(stp->st_stateowner);  	bool deleg_ts = nfsd4_want_deleg_timestamps(open); @@ -6227,7 +6264,8 @@ nfs4_open_delegation(struct nfsd4_open *open, struct nfs4_ol_stateid *stp,  	memcpy(&open->op_delegate_stateid, &dp->dl_stid.sc_stateid, sizeof(dp->dl_stid.sc_stateid));  	if (open->op_share_access & NFS4_SHARE_ACCESS_WRITE) { -		if (!nfs4_delegation_stat(dp, currentfh, &stat)) { +		if (!nfsd4_add_rdaccess_to_wrdeleg(rqstp, open, fh, stp) || +				!nfs4_delegation_stat(dp, currentfh, &stat)) {  			nfs4_put_stid(&dp->dl_stid);  			destroy_delegation(dp);  			goto out_no_deleg; @@ -6322,6 +6360,20 @@ nfsd4_process_open2(struct svc_rqst *rqstp, struct svc_fh *current_fh, struct nf  		status = nfs4_check_deleg(cl, open, &dp);  		if (status)  			goto out; +		if (dp && nfsd4_is_deleg_cur(open) && +				(dp->dl_stid.sc_file != fp)) { +			/* +			 * RFC8881 section 8.2.4 mandates the server to return +			 * NFS4ERR_BAD_STATEID if the selected table entry does +			 * not match the current filehandle. However returning +			 * NFS4ERR_BAD_STATEID in the OPEN can cause the client +			 * to repeatedly retry the operation with the same +			 * stateid, since the stateid itself is valid. To avoid +			 * this situation NFSD returns NFS4ERR_INVAL instead. +			 */ +			status = nfserr_inval; +			goto out; +		}  		stp = nfsd4_find_and_lock_existing_open(fp, open);  	} else {  		open->op_file = NULL; @@ -6383,7 +6435,8 @@ 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(open, stp, &resp->cstate.current_fh); +	nfs4_open_delegation(rqstp, open, stp, +		&resp->cstate.current_fh, current_fh);  	/*  	 * If there is an existing open stateid, it must be updated and @@ -7076,7 +7129,7 @@ nfsd4_lookup_stateid(struct nfsd4_compound_state *cstate,  		return_revoked = true;  	if (typemask & SC_TYPE_DELEG)  		/* Always allow REVOKED for DELEG so we can -		 * retturn the appropriate error. +		 * return the appropriate error.  		 */  		statusmask |= SC_STATUS_REVOKED; @@ -7119,10 +7172,6 @@ nfs4_find_file(struct nfs4_stid *s, int flags)  	switch (s->sc_type) {  	case SC_TYPE_DELEG: -		spin_lock(&s->sc_file->fi_lock); -		ret = nfsd_file_get(s->sc_file->fi_deleg_file); -		spin_unlock(&s->sc_file->fi_lock); -		break;  	case SC_TYPE_OPEN:  	case SC_TYPE_LOCK:  		if (flags & RD_STATE)  | 
