Commit d68886ba authored by Sergey Bashirov's avatar Sergey Bashirov Committed by Chuck Lever
Browse files

NFSD: Fix last write offset handling in layoutcommit



The data type of loca_last_write_offset is newoffset4 and is switched
on a boolean value, no_newoffset, that indicates if a previous write
occurred or not. If no_newoffset is FALSE, an offset is not given.
This means that client does not try to update the file size. Thus,
server should not try to calculate new file size and check if it fits
into the segment range. See RFC 8881, section 12.5.4.2.

Sometimes the current incorrect logic may cause clients to hang when
trying to sync an inode. If layoutcommit fails, the client marks the
inode as dirty again.

Fixes: 9cf514cc ("nfsd: implement pNFS operations")
Cc: stable@vger.kernel.org
Co-developed-by: default avatarKonstantin Evtushenko <koevtushenko@yandex.com>
Signed-off-by: default avatarKonstantin Evtushenko <koevtushenko@yandex.com>
Signed-off-by: default avatarSergey Bashirov <sergeybashirov@gmail.com>
Reviewed-by: default avatarChristoph Hellwig <hch@lst.de>
Reviewed-by: default avatarJeff Layton <jlayton@kernel.org>
Signed-off-by: default avatarChuck Lever <chuck.lever@oracle.com>
parent f963cf2b
Loading
Loading
Loading
Loading
+2 −3
Original line number Diff line number Diff line
@@ -118,7 +118,6 @@ nfsd4_block_commit_blocks(struct inode *inode, struct nfsd4_layoutcommit *lcp,
		struct iomap *iomaps, int nr_iomaps)
{
	struct timespec64 mtime = inode_get_mtime(inode);
	loff_t new_size = lcp->lc_last_wr + 1;
	struct iattr iattr = { .ia_valid = 0 };
	int error;

@@ -128,9 +127,9 @@ nfsd4_block_commit_blocks(struct inode *inode, struct nfsd4_layoutcommit *lcp,
	iattr.ia_valid |= ATTR_ATIME | ATTR_CTIME | ATTR_MTIME;
	iattr.ia_atime = iattr.ia_ctime = iattr.ia_mtime = lcp->lc_mtime;

	if (new_size > i_size_read(inode)) {
	if (lcp->lc_size_chg) {
		iattr.ia_valid |= ATTR_SIZE;
		iattr.ia_size = new_size;
		iattr.ia_size = lcp->lc_newsize;
	}

	error = inode->i_sb->s_export_op->commit_blocks(inode, iomaps,
+15 −15
Original line number Diff line number Diff line
@@ -2475,7 +2475,6 @@ nfsd4_layoutcommit(struct svc_rqst *rqstp,
	const struct nfsd4_layout_seg *seg = &lcp->lc_seg;
	struct svc_fh *current_fh = &cstate->current_fh;
	const struct nfsd4_layout_ops *ops;
	loff_t new_size = lcp->lc_last_wr + 1;
	struct inode *inode;
	struct nfs4_layout_stateid *ls;
	__be32 nfserr;
@@ -2491,13 +2490,21 @@ nfsd4_layoutcommit(struct svc_rqst *rqstp,
		goto out;
	inode = d_inode(current_fh->fh_dentry);

	lcp->lc_size_chg = false;
	if (lcp->lc_newoffset) {
		loff_t new_size = lcp->lc_last_wr + 1;

		nfserr = nfserr_inval;
		if (new_size <= seg->offset)
			goto out;
		if (new_size > seg->offset + seg->length)
			goto out;
	if (!lcp->lc_newoffset && new_size > i_size_read(inode))
		goto out;

		if (new_size > i_size_read(inode)) {
			lcp->lc_size_chg = true;
			lcp->lc_newsize = new_size;
		}
	}

	nfserr = nfsd4_preprocess_layout_stateid(rqstp, cstate, &lcp->lc_sid,
						false, lcp->lc_layout_type,
@@ -2513,13 +2520,6 @@ nfsd4_layoutcommit(struct svc_rqst *rqstp,
	/* LAYOUTCOMMIT does not require any serialization */
	mutex_unlock(&ls->ls_mutex);

	if (new_size > i_size_read(inode)) {
		lcp->lc_size_chg = true;
		lcp->lc_newsize = new_size;
	} else {
		lcp->lc_size_chg = false;
	}

	nfserr = ops->proc_layoutcommit(inode, rqstp, lcp);
	nfs4_put_stid(&ls->ls_stid);
out: