mirror of
				https://github.com/torvalds/linux.git
				synced 2025-10-31 16:48:26 +02:00 
			
		
		
		
	NFSD: Update NFSv3 READDIR entry encoders to use struct xdr_stream
The benefit of the xdr_stream helpers is that they transparently handle encoding an XDR data item that crosses page boundaries. Most of the open-coded logic to do that here can be eliminated. A sub-buffer and sub-stream are set up as a sink buffer for the directory entry encoder. As an entry is encoded, it is added to the end of the content in this buffer/stream. The total length of the directory list is tracked in the buffer's @len field. When it comes time to encode the Reply, the sub-buffer is merged into rq_res's page array at the correct place using xdr_write_pages(). Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
This commit is contained in:
		
							parent
							
								
									e4ccfe3014
								
							
						
					
					
						commit
						7f87fc2d34
					
				
					 3 changed files with 185 additions and 30 deletions
				
			
		|  | @ -441,18 +441,30 @@ static void nfsd3_init_dirlist_pages(struct svc_rqst *rqstp, | |||
| 				     struct nfsd3_readdirres *resp, | ||||
| 				     int count) | ||||
| { | ||||
| 	struct xdr_buf *buf = &resp->dirlist; | ||||
| 	struct xdr_stream *xdr = &resp->xdr; | ||||
| 
 | ||||
| 	count = min_t(u32, count, svc_max_payload(rqstp)); | ||||
| 
 | ||||
| 	/* Convert byte count to number of words (i.e. >> 2),
 | ||||
| 	 * and reserve room for the NULL ptr & eof flag (-2 words) */ | ||||
| 	resp->buflen = (count >> 2) - 2; | ||||
| 	memset(buf, 0, sizeof(*buf)); | ||||
| 
 | ||||
| 	resp->pages = rqstp->rq_next_page; | ||||
| 	resp->buffer = page_address(*resp->pages); | ||||
| 	/* Reserve room for the NULL ptr & eof flag (-2 words) */ | ||||
| 	buf->buflen = count - XDR_UNIT * 2; | ||||
| 	buf->pages = rqstp->rq_next_page; | ||||
| 	while (count > 0) { | ||||
| 		rqstp->rq_next_page++; | ||||
| 		count -= PAGE_SIZE; | ||||
| 	} | ||||
| 
 | ||||
| 	/* This is xdr_init_encode(), but it assumes that
 | ||||
| 	 * the head kvec has already been consumed. */ | ||||
| 	xdr_set_scratch_buffer(xdr, NULL, 0); | ||||
| 	xdr->buf = buf; | ||||
| 	xdr->page_ptr = buf->pages; | ||||
| 	xdr->iov = NULL; | ||||
| 	xdr->p = page_address(*buf->pages); | ||||
| 	xdr->end = xdr->p + (PAGE_SIZE >> 2); | ||||
| 	xdr->rqst = NULL; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  | @ -471,16 +483,13 @@ nfsd3_proc_readdir(struct svc_rqst *rqstp) | |||
| 
 | ||||
| 	nfsd3_init_dirlist_pages(rqstp, resp, argp->count); | ||||
| 
 | ||||
| 	/* Read directory and encode entries on the fly */ | ||||
| 	fh_copy(&resp->fh, &argp->fh); | ||||
| 
 | ||||
| 	resp->count = 0; | ||||
| 	resp->common.err = nfs_ok; | ||||
| 	resp->cookie_offset = 0; | ||||
| 	resp->rqstp = rqstp; | ||||
| 	offset = argp->cookie; | ||||
| 
 | ||||
| 	resp->status = nfsd_readdir(rqstp, &resp->fh, &offset, | ||||
| 				    &resp->common, nfs3svc_encode_entry); | ||||
| 				    &resp->common, nfs3svc_encode_entry3); | ||||
| 	memcpy(resp->verf, argp->verf, 8); | ||||
| 	nfs3svc_encode_cookie3(resp, offset); | ||||
| 
 | ||||
|  | @ -504,11 +513,9 @@ nfsd3_proc_readdirplus(struct svc_rqst *rqstp) | |||
| 
 | ||||
| 	nfsd3_init_dirlist_pages(rqstp, resp, argp->count); | ||||
| 
 | ||||
| 	/* Read directory and encode entries on the fly */ | ||||
| 	fh_copy(&resp->fh, &argp->fh); | ||||
| 
 | ||||
| 	resp->count = 0; | ||||
| 	resp->common.err = nfs_ok; | ||||
| 	resp->cookie_offset = 0; | ||||
| 	resp->rqstp = rqstp; | ||||
| 	offset = argp->cookie; | ||||
| 
 | ||||
|  | @ -522,7 +529,7 @@ nfsd3_proc_readdirplus(struct svc_rqst *rqstp) | |||
| 	} | ||||
| 
 | ||||
| 	resp->status = nfsd_readdir(rqstp, &resp->fh, &offset, | ||||
| 				    &resp->common, nfs3svc_encode_entry_plus); | ||||
| 				    &resp->common, nfs3svc_encode_entryplus3); | ||||
| 	memcpy(resp->verf, argp->verf, 8); | ||||
| 	nfs3svc_encode_cookie3(resp, offset); | ||||
| 
 | ||||
|  |  | |||
|  | @ -1139,6 +1139,7 @@ nfs3svc_encode_readdirres(struct svc_rqst *rqstp, __be32 *p) | |||
| { | ||||
| 	struct xdr_stream *xdr = &rqstp->rq_res_stream; | ||||
| 	struct nfsd3_readdirres *resp = rqstp->rq_resp; | ||||
| 	struct xdr_buf *dirlist = &resp->dirlist; | ||||
| 
 | ||||
| 	if (!svcxdr_encode_nfsstat3(xdr, resp->status)) | ||||
| 		return 0; | ||||
|  | @ -1148,7 +1149,7 @@ nfs3svc_encode_readdirres(struct svc_rqst *rqstp, __be32 *p) | |||
| 			return 0; | ||||
| 		if (!svcxdr_encode_cookieverf3(xdr, resp->verf)) | ||||
| 			return 0; | ||||
| 		xdr_write_pages(xdr, resp->pages, 0, resp->count << 2); | ||||
| 		xdr_write_pages(xdr, dirlist->pages, 0, dirlist->len); | ||||
| 		/* no more entries */ | ||||
| 		if (xdr_stream_encode_item_absent(xdr) < 0) | ||||
| 			return 0; | ||||
|  | @ -1240,21 +1241,18 @@ static __be32 *encode_entryplus_baggage(struct nfsd3_readdirres *cd, __be32 *p, | |||
|  * @resp: readdir result context | ||||
|  * @offset: offset cookie to encode | ||||
|  * | ||||
|  * The buffer space for the offset cookie has already been reserved | ||||
|  * by svcxdr_encode_entry3_common(). | ||||
|  */ | ||||
| void nfs3svc_encode_cookie3(struct nfsd3_readdirres *resp, u64 offset) | ||||
| { | ||||
| 	if (!resp->offset) | ||||
| 		return; | ||||
| 	__be64 cookie = cpu_to_be64(offset); | ||||
| 
 | ||||
| 	if (resp->offset1) { | ||||
| 		/* we ended up with offset on a page boundary */ | ||||
| 		*resp->offset = cpu_to_be32(offset >> 32); | ||||
| 		*resp->offset1 = cpu_to_be32(offset & 0xffffffff); | ||||
| 		resp->offset1 = NULL; | ||||
| 	} else { | ||||
| 		xdr_encode_hyper(resp->offset, offset); | ||||
| 	} | ||||
| 	resp->offset = NULL; | ||||
| 	if (!resp->cookie_offset) | ||||
| 		return; | ||||
| 	write_bytes_to_xdr_buf(&resp->dirlist, resp->cookie_offset, &cookie, | ||||
| 			       sizeof(cookie)); | ||||
| 	resp->cookie_offset = 0; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  | @ -1403,6 +1401,150 @@ nfs3svc_encode_entry_plus(void *cd, const char *name, | |||
| 	return encode_entry(cd, name, namlen, offset, ino, d_type, 1); | ||||
| } | ||||
| 
 | ||||
| static bool | ||||
| svcxdr_encode_entry3_common(struct nfsd3_readdirres *resp, const char *name, | ||||
| 			    int namlen, loff_t offset, u64 ino) | ||||
| { | ||||
| 	struct xdr_buf *dirlist = &resp->dirlist; | ||||
| 	struct xdr_stream *xdr = &resp->xdr; | ||||
| 
 | ||||
| 	if (xdr_stream_encode_item_present(xdr) < 0) | ||||
| 		return false; | ||||
| 	/* fileid */ | ||||
| 	if (xdr_stream_encode_u64(xdr, ino) < 0) | ||||
| 		return false; | ||||
| 	/* name */ | ||||
| 	if (xdr_stream_encode_opaque(xdr, name, min(namlen, NFS3_MAXNAMLEN)) < 0) | ||||
| 		return false; | ||||
| 	/* cookie */ | ||||
| 	resp->cookie_offset = dirlist->len; | ||||
| 	if (xdr_stream_encode_u64(xdr, NFS_OFFSET_MAX) < 0) | ||||
| 		return false; | ||||
| 
 | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * nfs3svc_encode_entry3 - encode one NFSv3 READDIR entry | ||||
|  * @data: directory context | ||||
|  * @name: name of the object to be encoded | ||||
|  * @namlen: length of that name, in bytes | ||||
|  * @offset: the offset of the previous entry | ||||
|  * @ino: the fileid of this entry | ||||
|  * @d_type: unused | ||||
|  * | ||||
|  * Return values: | ||||
|  *   %0: Entry was successfully encoded. | ||||
|  *   %-EINVAL: An encoding problem occured, secondary status code in resp->common.err | ||||
|  * | ||||
|  * On exit, the following fields are updated: | ||||
|  *   - resp->xdr | ||||
|  *   - resp->common.err | ||||
|  *   - resp->cookie_offset | ||||
|  */ | ||||
| int nfs3svc_encode_entry3(void *data, const char *name, int namlen, | ||||
| 			  loff_t offset, u64 ino, unsigned int d_type) | ||||
| { | ||||
| 	struct readdir_cd *ccd = data; | ||||
| 	struct nfsd3_readdirres *resp = container_of(ccd, | ||||
| 						     struct nfsd3_readdirres, | ||||
| 						     common); | ||||
| 	unsigned int starting_length = resp->dirlist.len; | ||||
| 
 | ||||
| 	/* The offset cookie for the previous entry */ | ||||
| 	nfs3svc_encode_cookie3(resp, offset); | ||||
| 
 | ||||
| 	if (!svcxdr_encode_entry3_common(resp, name, namlen, offset, ino)) | ||||
| 		goto out_toosmall; | ||||
| 
 | ||||
| 	xdr_commit_encode(&resp->xdr); | ||||
| 	resp->common.err = nfs_ok; | ||||
| 	return 0; | ||||
| 
 | ||||
| out_toosmall: | ||||
| 	resp->cookie_offset = 0; | ||||
| 	resp->common.err = nfserr_toosmall; | ||||
| 	resp->dirlist.len = starting_length; | ||||
| 	return -EINVAL; | ||||
| } | ||||
| 
 | ||||
| static bool | ||||
| svcxdr_encode_entry3_plus(struct nfsd3_readdirres *resp, const char *name, | ||||
| 			  int namlen, u64 ino) | ||||
| { | ||||
| 	struct xdr_stream *xdr = &resp->xdr; | ||||
| 	struct svc_fh *fhp = &resp->scratch; | ||||
| 	bool result; | ||||
| 
 | ||||
| 	result = false; | ||||
| 	fh_init(fhp, NFS3_FHSIZE); | ||||
| 	if (compose_entry_fh(resp, fhp, name, namlen, ino) != nfs_ok) | ||||
| 		goto out_noattrs; | ||||
| 
 | ||||
| 	if (!svcxdr_encode_post_op_attr(resp->rqstp, xdr, fhp)) | ||||
| 		goto out; | ||||
| 	if (!svcxdr_encode_post_op_fh3(xdr, fhp)) | ||||
| 		goto out; | ||||
| 	result = true; | ||||
| 
 | ||||
| out: | ||||
| 	fh_put(fhp); | ||||
| 	return result; | ||||
| 
 | ||||
| out_noattrs: | ||||
| 	if (xdr_stream_encode_item_absent(xdr) < 0) | ||||
| 		return false; | ||||
| 	if (xdr_stream_encode_item_absent(xdr) < 0) | ||||
| 		return false; | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * nfs3svc_encode_entryplus3 - encode one NFSv3 READDIRPLUS entry | ||||
|  * @data: directory context | ||||
|  * @name: name of the object to be encoded | ||||
|  * @namlen: length of that name, in bytes | ||||
|  * @offset: the offset of the previous entry | ||||
|  * @ino: the fileid of this entry | ||||
|  * @d_type: unused | ||||
|  * | ||||
|  * Return values: | ||||
|  *   %0: Entry was successfully encoded. | ||||
|  *   %-EINVAL: An encoding problem occured, secondary status code in resp->common.err | ||||
|  * | ||||
|  * On exit, the following fields are updated: | ||||
|  *   - resp->xdr | ||||
|  *   - resp->common.err | ||||
|  *   - resp->cookie_offset | ||||
|  */ | ||||
| int nfs3svc_encode_entryplus3(void *data, const char *name, int namlen, | ||||
| 			      loff_t offset, u64 ino, unsigned int d_type) | ||||
| { | ||||
| 	struct readdir_cd *ccd = data; | ||||
| 	struct nfsd3_readdirres *resp = container_of(ccd, | ||||
| 						     struct nfsd3_readdirres, | ||||
| 						     common); | ||||
| 	unsigned int starting_length = resp->dirlist.len; | ||||
| 
 | ||||
| 	/* The offset cookie for the previous entry */ | ||||
| 	nfs3svc_encode_cookie3(resp, offset); | ||||
| 
 | ||||
| 	if (!svcxdr_encode_entry3_common(resp, name, namlen, offset, ino)) | ||||
| 		goto out_toosmall; | ||||
| 	if (!svcxdr_encode_entry3_plus(resp, name, namlen, ino)) | ||||
| 		goto out_toosmall; | ||||
| 
 | ||||
| 	xdr_commit_encode(&resp->xdr); | ||||
| 	resp->common.err = nfs_ok; | ||||
| 	return 0; | ||||
| 
 | ||||
| out_toosmall: | ||||
| 	resp->cookie_offset = 0; | ||||
| 	resp->common.err = nfserr_toosmall; | ||||
| 	resp->dirlist.len = starting_length; | ||||
| 	return -EINVAL; | ||||
| } | ||||
| 
 | ||||
| static bool | ||||
| svcxdr_encode_fsstat3resok(struct xdr_stream *xdr, | ||||
| 			   const struct nfsd3_fsstatres *resp) | ||||
|  |  | |||
|  | @ -169,20 +169,22 @@ struct nfsd3_linkres { | |||
| }; | ||||
| 
 | ||||
| struct nfsd3_readdirres { | ||||
| 	/* Components of the reply */ | ||||
| 	__be32			status; | ||||
| 	struct svc_fh		fh; | ||||
| 	/* Just to save kmalloc on every readdirplus entry (svc_fh is a
 | ||||
| 	 * little large for the stack): */ | ||||
| 	struct svc_fh		scratch; | ||||
| 	int			count; | ||||
| 	__be32			verf[2]; | ||||
| 	struct page		**pages; | ||||
| 
 | ||||
| 	/* Used to encode the reply's entry list */ | ||||
| 	struct xdr_stream	xdr; | ||||
| 	struct xdr_buf		dirlist; | ||||
| 	struct svc_fh		scratch; | ||||
| 	struct readdir_cd	common; | ||||
| 	__be32 *		buffer; | ||||
| 	int			buflen; | ||||
| 	__be32 *		offset; | ||||
| 	__be32 *		offset1; | ||||
| 	unsigned int		cookie_offset; | ||||
| 	struct svc_rqst *	rqstp; | ||||
| 
 | ||||
| }; | ||||
|  | @ -309,6 +311,10 @@ int nfs3svc_encode_entry(void *, const char *name, | |||
| int nfs3svc_encode_entry_plus(void *, const char *name, | ||||
| 				int namlen, loff_t offset, u64 ino, | ||||
| 				unsigned int); | ||||
| int nfs3svc_encode_entry3(void *data, const char *name, int namlen, | ||||
| 			  loff_t offset, u64 ino, unsigned int d_type); | ||||
| int nfs3svc_encode_entryplus3(void *data, const char *name, int namlen, | ||||
| 			      loff_t offset, u64 ino, unsigned int d_type); | ||||
| /* Helper functions for NFSv3 ACL code */ | ||||
| __be32 *nfs3svc_encode_post_op_attr(struct svc_rqst *rqstp, __be32 *p, | ||||
| 				struct svc_fh *fhp); | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 Chuck Lever
						Chuck Lever