#include <linux/fs.h>
 #include <linux/ext2_fs.h>
 #include <linux/magic.h>
+#include <linux/namei.h>
 
 #include "cache.h"
 #include "xdr3.h"
 }
 
 /*
- * 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)
 {
        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;
 }