diff mbox series

[RFC,4/8] NFSD: Refactor NFSv3 CREATE

Message ID 165047934027.1829.4170855794285748158.stgit@manet.1015granger.net (mailing list archive)
State New, archived
Headers show
Series Make NFSv4 OPEN(CREATE) less brittle | expand

Commit Message

Chuck Lever III April 20, 2022, 6:29 p.m. UTC
The NFSv3 CREATE and NFSv4 OPEN(CREATE) use cases are about to
diverge such that it makes sense to split do_nfsd_create() into one
version for NFSv3 and one for NFSv4.

As a first step, copy do_nfsd_create() to nfs3proc.c and remove
NFSv4-specific logic.

One immediate legibility benefit is that the logic for handling
NFSv3 createhow is now quite straightforward. NFSv4 createhow
has some subtleties that IMO do not belong in generic code.

Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
 fs/nfsd/nfs3proc.c |  127 ++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 121 insertions(+), 6 deletions(-)

Comments

J. Bruce Fields April 20, 2022, 7:10 p.m. UTC | #1
On Wed, Apr 20, 2022 at 02:29:00PM -0400, Chuck Lever wrote:
> The NFSv3 CREATE and NFSv4 OPEN(CREATE) use cases are about to
> diverge such that it makes sense to split do_nfsd_create() into one
> version for NFSv3 and one for NFSv4.
> 
> As a first step, copy do_nfsd_create() to nfs3proc.c and remove
> NFSv4-specific logic.
> 
> One immediate legibility benefit is that the logic for handling
> NFSv3 createhow is now quite straightforward. NFSv4 createhow
> has some subtleties that IMO do not belong in generic code.

That makes sense to me, though just eyeballing the two resulting
functions, you end up with a *lot* of duplication.  I wonder if it'd be
possible to keep the two paths free of complications from each other
while sharing more code, e.g. if there are logical blocks of code that
could now be pulled out into common helpers.

--b.

> 
> Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
> ---
>  fs/nfsd/nfs3proc.c |  127 ++++++++++++++++++++++++++++++++++++++++++++++++++--
>  1 file changed, 121 insertions(+), 6 deletions(-)
> 
> diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c
> index 981a2a71c5af..981a3a7a6e16 100644
> --- a/fs/nfsd/nfs3proc.c
> +++ b/fs/nfsd/nfs3proc.c
> @@ -8,6 +8,7 @@
>  #include <linux/fs.h>
>  #include <linux/ext2_fs.h>
>  #include <linux/magic.h>
> +#include <linux/namei.h>
>  
>  #include "cache.h"
>  #include "xdr3.h"
> @@ -220,10 +221,126 @@ nfsd3_proc_write(struct svc_rqst *rqstp)
>  }
>  
>  /*
> - * With NFSv3, CREATE processing is a lot easier than with NFSv2.
> - * At least in theory; we'll see how it fares in practice when the
> - * first reports about SunOS compatibility problems start to pour in...
> + * Implement NFSv3's unchecked, guarded, and exclusive CREATE
> + * semantics for regular files. Except for the created file,
> + * this operation is stateless on the server.
> + *
> + * Upon return, caller must release @fhp and @resfhp.
>   */
> +static __be32
> +nfsd3_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
> +		  struct svc_fh *resfhp, struct nfsd3_createargs *argp)
> +{
> +	struct iattr *iap = &argp->attrs;
> +	struct dentry *parent, *child;
> +	__u32 v_mtime, v_atime;
> +	struct inode *inode;
> +	__be32 status;
> +	int host_err;
> +
> +	if (isdotent(argp->name, argp->len))
> +		return nfserr_exist;
> +	if (!(iap->ia_valid & ATTR_MODE))
> +		iap->ia_mode = 0;
> +
> +	status = fh_verify(rqstp, fhp, S_IFDIR, NFSD_MAY_EXEC);
> +	if (status != nfs_ok)
> +		return status;
> +
> +	parent = fhp->fh_dentry;
> +	inode = d_inode(parent);
> +
> +	host_err = fh_want_write(fhp);
> +	if (host_err)
> +		return nfserrno(host_err);
> +
> +	fh_lock_nested(fhp, I_MUTEX_PARENT);
> +
> +	child = lookup_one_len(argp->name, parent, argp->len);
> +	if (IS_ERR(child)) {
> +		status = nfserrno(PTR_ERR(child));
> +		goto out;
> +	}
> +
> +	if (d_really_is_negative(child)) {
> +		status = fh_verify(rqstp, fhp, S_IFDIR, NFSD_MAY_CREATE);
> +		if (status != nfs_ok)
> +			goto out;
> +	}
> +
> +	status = fh_compose(resfhp, fhp->fh_export, child, fhp);
> +	if (status != nfs_ok)
> +		goto out;
> +
> +	v_mtime = 0;
> +	v_atime = 0;
> +	if (argp->createmode == NFS3_CREATE_EXCLUSIVE) {
> +		u32 *verifier = (u32 *)argp->verf;
> +
> +		/*
> +		 * Solaris 7 gets confused (bugid 4218508) if these have
> +		 * the high bit set, as do xfs filesystems without the
> +		 * "bigtime" feature. So just clear the high bits.
> +		 */
> +		v_mtime = verifier[0] & 0x7fffffff;
> +		v_atime = verifier[1] & 0x7fffffff;
> +	}
> +
> +	if (d_really_is_positive(child)) {
> +		status = nfs_ok;
> +
> +		switch (argp->createmode) {
> +		case NFS3_CREATE_UNCHECKED:
> +			if (!d_is_reg(child))
> +				break;
> +			iap->ia_valid &= ATTR_SIZE;
> +			goto set_attr;
> +		case NFS3_CREATE_GUARDED:
> +			status = nfserr_exist;
> +			break;
> +		case NFS3_CREATE_EXCLUSIVE:
> +			if (d_inode(child)->i_mtime.tv_sec == v_mtime &&
> +			    d_inode(child)->i_atime.tv_sec == v_atime &&
> +			    d_inode(child)->i_size == 0) {
> +				break;
> +			}
> +			status = nfserr_exist;
> +		}
> +		goto out;
> +	}
> +
> +	if (!IS_POSIXACL(inode))
> +		iap->ia_mode &= ~current_umask();
> +
> +	host_err = vfs_create(&init_user_ns, inode, child, iap->ia_mode, true);
> +	if (host_err < 0) {
> +		status = nfserrno(host_err);
> +		goto out;
> +	}
> +
> +	/* A newly created file already has a file size of zero. */
> +	if ((iap->ia_valid & ATTR_SIZE) && (iap->ia_size == 0))
> +		iap->ia_valid &= ~ATTR_SIZE;
> +	if (argp->createmode == NFS3_CREATE_EXCLUSIVE) {
> +		iap->ia_valid = ATTR_MTIME | ATTR_ATIME |
> +				ATTR_MTIME_SET | ATTR_ATIME_SET;
> +		iap->ia_mtime.tv_sec = v_mtime;
> +		iap->ia_atime.tv_sec = v_atime;
> +		iap->ia_mtime.tv_nsec = 0;
> +		iap->ia_atime.tv_nsec = 0;
> +	}
> +
> +set_attr:
> +	status = nfsd_create_setattr(rqstp, fhp, resfhp, iap);
> +
> +out:
> +	fh_unlock(fhp);
> +	if (child && !IS_ERR(child))
> +		dput(child);
> +	fh_drop_write(fhp);
> +	return status;
> +}
> +
>  static __be32
>  nfsd3_proc_create(struct svc_rqst *rqstp)
>  {
> @@ -239,9 +356,7 @@ nfsd3_proc_create(struct svc_rqst *rqstp)
>  	dirfhp = fh_copy(&resp->dirfh, &argp->fh);
>  	newfhp = fh_init(&resp->fh, NFS3_FHSIZE);
>  
> -	resp->status = do_nfsd_create(rqstp, dirfhp, argp->name, argp->len,
> -				      &argp->attrs, newfhp, argp->createmode,
> -				      (u32 *)argp->verf, NULL, NULL);
> +	resp->status = nfsd3_create_file(rqstp, dirfhp, newfhp, argp);
>  	return rpc_success;
>  }
>  
>
Chuck Lever III April 20, 2022, 7:31 p.m. UTC | #2
> On Apr 20, 2022, at 3:10 PM, J. Bruce Fields <bfields@fieldses.org> wrote:
> 
> On Wed, Apr 20, 2022 at 02:29:00PM -0400, Chuck Lever wrote:
>> The NFSv3 CREATE and NFSv4 OPEN(CREATE) use cases are about to
>> diverge such that it makes sense to split do_nfsd_create() into one
>> version for NFSv3 and one for NFSv4.
>> 
>> As a first step, copy do_nfsd_create() to nfs3proc.c and remove
>> NFSv4-specific logic.
>> 
>> One immediate legibility benefit is that the logic for handling
>> NFSv3 createhow is now quite straightforward. NFSv4 createhow
>> has some subtleties that IMO do not belong in generic code.
> 
> That makes sense to me, though just eyeballing the two resulting
> functions, you end up with a *lot* of duplication.

About 200 lines. I would feel a little more agreeable to
this if we didn't already have a separate "create" function
for NFSv2 (ie, nfsd_proc_create).


> I wonder if it'd be
> possible to keep the two paths free of complications from each other
> while sharing more code, e.g. if there are logical blocks of code that
> could now be pulled out into common helpers.

I'm open to suggestions, but after the final patch in this
series, I don't see much else that is meaningful that can be
re-used by both. nfsd_create_setattr() was the one area that
seemed both common and heavyweight. The other areas are just
lightweight sanity checks.

And honestly, in this case, I don't think these code paths
are well-served by aggressive code de-duplication. The code
in each case is more readable and less brittle this way. The
NFSv4 code path now has some comments that mark the subtle
differences with NFSv3 exclusive create, and now you can't
break NFSv3 CREATE by making a change to NFSv4 OPEN, which
is far more complex.


--
Chuck Lever
J. Bruce Fields April 21, 2022, 4:37 p.m. UTC | #3
On Wed, Apr 20, 2022 at 07:31:09PM +0000, Chuck Lever III wrote:
> > I wonder if it'd be
> > possible to keep the two paths free of complications from each other
> > while sharing more code, e.g. if there are logical blocks of code that
> > could now be pulled out into common helpers.
> 
> I'm open to suggestions, but after the final patch in this
> series, I don't see much else that is meaningful that can be
> re-used by both. nfsd_create_setattr() was the one area that
> seemed both common and heavyweight. The other areas are just
> lightweight sanity checks.
> 
> And honestly, in this case, I don't think these code paths
> are well-served by aggressive code de-duplication. The code
> in each case is more readable and less brittle this way. The
> NFSv4 code path now has some comments that mark the subtle
> differences with NFSv3 exclusive create, and now you can't
> break NFSv3 CREATE by making a change to NFSv4 OPEN, which
> is far more complex.

I can live with that.

Also, this passes all my usual regression tests, FWIW.

--b.
diff mbox series

Patch

diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c
index 981a2a71c5af..981a3a7a6e16 100644
--- a/fs/nfsd/nfs3proc.c
+++ b/fs/nfsd/nfs3proc.c
@@ -8,6 +8,7 @@ 
 #include <linux/fs.h>
 #include <linux/ext2_fs.h>
 #include <linux/magic.h>
+#include <linux/namei.h>
 
 #include "cache.h"
 #include "xdr3.h"
@@ -220,10 +221,126 @@  nfsd3_proc_write(struct svc_rqst *rqstp)
 }
 
 /*
- * With NFSv3, CREATE processing is a lot easier than with NFSv2.
- * At least in theory; we'll see how it fares in practice when the
- * first reports about SunOS compatibility problems start to pour in...
+ * Implement NFSv3's unchecked, guarded, and exclusive CREATE
+ * semantics for regular files. Except for the created file,
+ * this operation is stateless on the server.
+ *
+ * Upon return, caller must release @fhp and @resfhp.
  */
+static __be32
+nfsd3_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
+		  struct svc_fh *resfhp, struct nfsd3_createargs *argp)
+{
+	struct iattr *iap = &argp->attrs;
+	struct dentry *parent, *child;
+	__u32 v_mtime, v_atime;
+	struct inode *inode;
+	__be32 status;
+	int host_err;
+
+	if (isdotent(argp->name, argp->len))
+		return nfserr_exist;
+	if (!(iap->ia_valid & ATTR_MODE))
+		iap->ia_mode = 0;
+
+	status = fh_verify(rqstp, fhp, S_IFDIR, NFSD_MAY_EXEC);
+	if (status != nfs_ok)
+		return status;
+
+	parent = fhp->fh_dentry;
+	inode = d_inode(parent);
+
+	host_err = fh_want_write(fhp);
+	if (host_err)
+		return nfserrno(host_err);
+
+	fh_lock_nested(fhp, I_MUTEX_PARENT);
+
+	child = lookup_one_len(argp->name, parent, argp->len);
+	if (IS_ERR(child)) {
+		status = nfserrno(PTR_ERR(child));
+		goto out;
+	}
+
+	if (d_really_is_negative(child)) {
+		status = fh_verify(rqstp, fhp, S_IFDIR, NFSD_MAY_CREATE);
+		if (status != nfs_ok)
+			goto out;
+	}
+
+	status = fh_compose(resfhp, fhp->fh_export, child, fhp);
+	if (status != nfs_ok)
+		goto out;
+
+	v_mtime = 0;
+	v_atime = 0;
+	if (argp->createmode == NFS3_CREATE_EXCLUSIVE) {
+		u32 *verifier = (u32 *)argp->verf;
+
+		/*
+		 * Solaris 7 gets confused (bugid 4218508) if these have
+		 * the high bit set, as do xfs filesystems without the
+		 * "bigtime" feature. So just clear the high bits.
+		 */
+		v_mtime = verifier[0] & 0x7fffffff;
+		v_atime = verifier[1] & 0x7fffffff;
+	}
+
+	if (d_really_is_positive(child)) {
+		status = nfs_ok;
+
+		switch (argp->createmode) {
+		case NFS3_CREATE_UNCHECKED:
+			if (!d_is_reg(child))
+				break;
+			iap->ia_valid &= ATTR_SIZE;
+			goto set_attr;
+		case NFS3_CREATE_GUARDED:
+			status = nfserr_exist;
+			break;
+		case NFS3_CREATE_EXCLUSIVE:
+			if (d_inode(child)->i_mtime.tv_sec == v_mtime &&
+			    d_inode(child)->i_atime.tv_sec == v_atime &&
+			    d_inode(child)->i_size == 0) {
+				break;
+			}
+			status = nfserr_exist;
+		}
+		goto out;
+	}
+
+	if (!IS_POSIXACL(inode))
+		iap->ia_mode &= ~current_umask();
+
+	host_err = vfs_create(&init_user_ns, inode, child, iap->ia_mode, true);
+	if (host_err < 0) {
+		status = nfserrno(host_err);
+		goto out;
+	}
+
+	/* A newly created file already has a file size of zero. */
+	if ((iap->ia_valid & ATTR_SIZE) && (iap->ia_size == 0))
+		iap->ia_valid &= ~ATTR_SIZE;
+	if (argp->createmode == NFS3_CREATE_EXCLUSIVE) {
+		iap->ia_valid = ATTR_MTIME | ATTR_ATIME |
+				ATTR_MTIME_SET | ATTR_ATIME_SET;
+		iap->ia_mtime.tv_sec = v_mtime;
+		iap->ia_atime.tv_sec = v_atime;
+		iap->ia_mtime.tv_nsec = 0;
+		iap->ia_atime.tv_nsec = 0;
+	}
+
+set_attr:
+	status = nfsd_create_setattr(rqstp, fhp, resfhp, iap);
+
+out:
+	fh_unlock(fhp);
+	if (child && !IS_ERR(child))
+		dput(child);
+	fh_drop_write(fhp);
+	return status;
+}
+
 static __be32
 nfsd3_proc_create(struct svc_rqst *rqstp)
 {
@@ -239,9 +356,7 @@  nfsd3_proc_create(struct svc_rqst *rqstp)
 	dirfhp = fh_copy(&resp->dirfh, &argp->fh);
 	newfhp = fh_init(&resp->fh, NFS3_FHSIZE);
 
-	resp->status = do_nfsd_create(rqstp, dirfhp, argp->name, argp->len,
-				      &argp->attrs, newfhp, argp->createmode,
-				      (u32 *)argp->verf, NULL, NULL);
+	resp->status = nfsd3_create_file(rqstp, dirfhp, newfhp, argp);
 	return rpc_success;
 }