]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
VFS/knfsd: Teach dentry_create() to use atomic_open()
authorBenjamin Coddington <bcodding@hammerspace.com>
Thu, 27 Nov 2025 16:02:05 +0000 (11:02 -0500)
committerChristian Brauner <brauner@kernel.org>
Mon, 15 Dec 2025 13:12:45 +0000 (14:12 +0100)
While knfsd offers combined exclusive create and open results to clients,
on some filesystems those results may not be atomic.  This behavior can be
observed.  For example, an open O_CREAT with mode 0 will succeed in creating
the file but unexpectedly return -EACCES from vfs_open().

Additionally reducing the number of remote RPC calls required for O_CREAT
on network filesystem provides a performance benefit in the open path.

Teach knfsd's helper dentry_create() to use atomic_open() for filesystems
that support it.  The previously const @path is passed up to atomic_open()
and may be modified depending on whether an existing entry was found or if
the atomic_open() returned an error and consumed the passed-in dentry.

Signed-off-by: Benjamin Coddington <bcodding@hammerspace.com>
Link: https://patch.msgid.link/8e449bfb64ab055abb9fd82641a171531415a88c.1764259052.git.bcodding@hammerspace.com
Reviewed-by: Jeff Layton <jlayton@kernel.org>
Reviewed-by: Chuck Lever <chuck.lever@oracle.com>
Signed-off-by: Christian Brauner <brauner@kernel.org>
fs/namei.c
fs/nfsd/nfs4proc.c
include/linux/fs.h

index 761257e3da2cabcf7ee333dc18c1305a1caab8bb..aefb21bc0944e393f79834825bf64cc7278ff511 100644 (file)
@@ -4953,25 +4953,54 @@ EXPORT_SYMBOL(start_creating_user_path);
  * On success, returns a "struct file *". Otherwise a ERR_PTR
  * is returned.
  */
-struct file *dentry_create(const struct path *path, int flags, umode_t mode,
+struct file *dentry_create(struct path *path, int flags, umode_t mode,
                           const struct cred *cred)
 {
-       struct file *f;
-       int error;
+       struct file *file __free(fput) = NULL;
+       struct dentry *dentry = path->dentry;
+       struct dentry *dir = dentry->d_parent;
+       struct inode *dir_inode = d_inode(dir);
+       struct mnt_idmap *idmap;
+       int error, create_error;
 
-       f = alloc_empty_file(flags, cred);
-       if (IS_ERR(f))
-               return f;
+       file = alloc_empty_file(flags, cred);
+       if (IS_ERR(file))
+               return file;
 
-       error = vfs_create(mnt_idmap(path->mnt), path->dentry, mode, NULL);
-       if (!error)
-               error = vfs_open(path, f);
+       idmap = mnt_idmap(path->mnt);
 
-       if (unlikely(error)) {
-               fput(f);
-               return ERR_PTR(error);
+       if (dir_inode->i_op->atomic_open) {
+               path->dentry = dir;
+               mode = vfs_prepare_mode(idmap, dir_inode, mode, S_IALLUGO, S_IFREG);
+
+               create_error = may_o_create(idmap, path, dentry, mode);
+               if (create_error)
+                       flags &= ~O_CREAT;
+
+               dentry = atomic_open(path, dentry, file, flags, mode);
+               error = PTR_ERR_OR_ZERO(dentry);
+
+               if (unlikely(create_error) && error == -ENOENT)
+                       error = create_error;
+
+               if (!error) {
+                       if (file->f_mode & FMODE_CREATED)
+                               fsnotify_create(dir->d_inode, dentry);
+                       if (file->f_mode & FMODE_OPENED)
+                               fsnotify_open(file);
+               }
+
+               path->dentry = dentry;
+
+       } else {
+               error = vfs_create(mnt_idmap(path->mnt), path->dentry, mode, NULL);
+               if (!error)
+                       error = vfs_open(path, file);
        }
-       return f;
+       if (unlikely(error))
+               return ERR_PTR(error);
+
+       return no_free_ptr(file);
 }
 EXPORT_SYMBOL(dentry_create);
 
index b748009175837c6ea6442ac674d4662ebe4c0472..6aa22b3b2f4394ea5df423561e46a9d4934b2e01 100644 (file)
@@ -194,7 +194,7 @@ static inline bool nfsd4_create_is_exclusive(int createmode)
 }
 
 static __be32
-nfsd4_vfs_create(struct svc_fh *fhp, struct dentry *child,
+nfsd4_vfs_create(struct svc_fh *fhp, struct dentry **child,
                 struct nfsd4_open *open)
 {
        struct file *filp;
@@ -202,6 +202,9 @@ nfsd4_vfs_create(struct svc_fh *fhp, struct dentry *child,
        int oflags;
 
        oflags = O_CREAT | O_LARGEFILE;
+       if (nfsd4_create_is_exclusive(open->op_createmode))
+               oflags |= O_EXCL;
+
        switch (open->op_share_access & NFS4_SHARE_ACCESS_BOTH) {
        case NFS4_SHARE_ACCESS_WRITE:
                oflags |= O_WRONLY;
@@ -214,9 +217,11 @@ nfsd4_vfs_create(struct svc_fh *fhp, struct dentry *child,
        }
 
        path.mnt = fhp->fh_export->ex_path.mnt;
-       path.dentry = child;
+       path.dentry = *child;
        filp = dentry_create(&path, oflags, open->op_iattr.ia_mode,
                             current_cred());
+       *child = path.dentry;
+
        if (IS_ERR(filp))
                return nfserrno(PTR_ERR(filp));
 
@@ -350,7 +355,7 @@ nfsd4_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
        status = fh_fill_pre_attrs(fhp);
        if (status != nfs_ok)
                goto out;
-       status = nfsd4_vfs_create(fhp, child, open);
+       status = nfsd4_vfs_create(fhp, &child, open);
        if (status != nfs_ok)
                goto out;
        open->op_created = true;
index 04ceeca12a0d5caadb68643bf68b7a78e17c08d4..1cb3385ee85202d44b8cb58fe902221d9e150e57 100644 (file)
@@ -2457,7 +2457,7 @@ struct file *dentry_open(const struct path *path, int flags,
                         const struct cred *creds);
 struct file *dentry_open_nonotify(const struct path *path, int flags,
                                  const struct cred *cred);
-struct file *dentry_create(const struct path *path, int flags, umode_t mode,
+struct file *dentry_create(struct path *path, int flags, umode_t mode,
                           const struct cred *cred);
 const struct path *backing_file_user_path(const struct file *f);