All of lore.kernel.org
 help / color / mirror / Atom feed
From: Jimmy Zuber <jamz@amazon.com>
To: Miklos Szeredi <miklos@szeredi.hu>
Cc: <fuse-devel@lists.linux.dev>, <linux-kselftest@vger.kernel.org>,
	"Shuah Khan" <shuah@kernel.org>,
	Bernd Schubert <bernd@bsbernd.com>,
	Jim Harris <jiharris@nvidia.com>,
	Horst Birthelmer <horst@birthelmer.de>,
	Luis Henriques <luis@igalia.com>
Subject: [PATCH 1/2] fuse: skip LOOKUP before exclusive create
Date: Tue, 20 Oct 2026 15:34:31 +0000	[thread overview]
Message-ID: <20261020153432.3812719-2-jamz@amazon.com> (raw)
In-Reply-To: <20261020153432.3812719-1-jamz@amazon.com>

mkdir(), mknod(), symlink(), link() and renameat2() with RENAME_NOREPLACE
look the new name up with FUSE_LOOKUP before sending the request, which
costs a round trip to the server.

Add an INIT flag, FUSE_EXCL_CREATE, with which the server fails
FUSE_MKNOD, FUSE_MKDIR, FUSE_SYMLINK, FUSE_LINK, and FUSE_RENAME2 with
RENAME_NOREPLACE, with -EEXIST for an existing name.  When the server
sets it, the lookup of a name that is about to be created returns
without sending FUSE_LOOKUP, and an existing name is reported by the
-EEXIST of the request.  After a failed request the name stays
uncached, so the next lookup goes to the server.

A rename that may replace its target looks the target up, so that the
kernel can check the permission to replace it.

Assisted-by: Claude:claude-opus-5-5 [Claude-Code]
Signed-off-by: Jimmy Zuber <jamz@amazon.com>
---
 fs/fuse/dir.c             | 10 ++++++++++
 fs/fuse/fuse_i.h          |  3 +++
 fs/fuse/inode.c           |  4 +++-
 include/uapi/linux/fuse.h | 10 +++++++++-
 4 files changed, 25 insertions(+), 2 deletions(-)

diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index f48fafccce4b..e491e6b3215a 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -627,6 +627,16 @@ static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry,
 		return ERR_PTR(-EIO);
 
 	fc = get_fuse_conn_super(dir->i_sb);
+
+	/* The request fails with -EEXIST if the name exists */
+	if (fc->excl_create &&
+	    (flags & (LOOKUP_CREATE | LOOKUP_EXCL)) ==
+	    (LOOKUP_CREATE | LOOKUP_EXCL)) {
+		if (entry->d_name.len > fc->name_max)
+			return ERR_PTR(-ENAMETOOLONG);
+		return NULL;
+	}
+
 	epoch = atomic_read(&fc->epoch);
 
 	locked = fuse_lock_inode(dir);
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 8546855386b5..babbacdfb757 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -509,6 +509,9 @@ struct fuse_conn {
 	 */
 	unsigned atomic_o_trunc:1;
 
+	/** @excl_create: Create requests check for an existing name */
+	unsigned excl_create:1;
+
 	/**
 	 * @export_support: Filesystem supports NFS exporting.
 	 * Only set in INIT
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index cbb10e19e7e8..1f1be0c521a1 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -1432,6 +1432,8 @@ static void process_init_reply(struct fuse_args *args, int error)
 
 			if (fuse_syncfs_enable(fc, flags))
 				fc->sync_fs = 1;
+			if (flags & FUSE_EXCL_CREATE)
+				fc->excl_create = 1;
 		} else {
 			ra_pages = fc->max_read / PAGE_SIZE;
 			fc->no_lock = 1;
@@ -1490,7 +1492,7 @@ static struct fuse_init_args *fuse_new_init(struct fuse_mount *fm)
 		FUSE_SECURITY_CTX | FUSE_CREATE_SUPP_GROUP |
 		FUSE_HAS_EXPIRE_ONLY | FUSE_DIRECT_IO_ALLOW_MMAP |
 		FUSE_NO_EXPORT_SUPPORT | FUSE_HAS_RESEND | FUSE_ALLOW_IDMAP |
-		FUSE_REQUEST_TIMEOUT;
+		FUSE_REQUEST_TIMEOUT | FUSE_EXCL_CREATE;
 #ifdef CONFIG_FUSE_VDAX
 	if (fm->fc->vdax)
 		flags |= FUSE_MAP_ALIGNMENT;
diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
index 10a7f31c4bdf..8c079d3080bf 100644
--- a/include/uapi/linux/fuse.h
+++ b/include/uapi/linux/fuse.h
@@ -251,6 +251,9 @@
  *
  *  7.47
  *  - add FUSE_HAS_SYNCFS opt-in flag for privileged userspace servers
+ *
+ *  7.48
+ *  - add FUSE_EXCL_CREATE
  */
 
 #ifndef _LINUX_FUSE_H
@@ -286,7 +289,7 @@
 #define FUSE_KERNEL_VERSION 7
 
 /** Minor version number of this interface */
-#define FUSE_KERNEL_MINOR_VERSION 47
+#define FUSE_KERNEL_MINOR_VERSION 48
 
 /** The node ID of the root inode */
 #define FUSE_ROOT_ID 1
@@ -473,6 +476,10 @@ struct fuse_file_lock {
  *		with CAP_SYS_ADMIN in the initial user namespace (the same
  *		privilege that mounting virtiofs or fuseblk requires).
  *		Insufficiently privileged servers ignore it.
+ * FUSE_EXCL_CREATE: FUSE_MKNOD, FUSE_MKDIR, FUSE_SYMLINK, FUSE_LINK and
+ *		FUSE_RENAME2 with RENAME_NOREPLACE fail with -EEXIST for an
+ *		existing name, so the kernel sends them without a LOOKUP of
+ *		the name
  */
 #define FUSE_ASYNC_READ		(1 << 0)
 #define FUSE_POSIX_LOCKS	(1 << 1)
@@ -522,6 +529,7 @@ struct fuse_file_lock {
 #define FUSE_REQUEST_TIMEOUT	(1ULL << 42)
 #define FUSE_HAS_IO_URING_BUFPOOL (1ULL << 43)
 #define FUSE_HAS_SYNCFS		(1ULL << 44)
+#define FUSE_EXCL_CREATE	(1ULL << 45)
 
 /**
  * CUSE INIT request/reply flags
-- 
2.50.1


  reply	other threads:[~2026-10-20 15:35 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-20 15:34 [PATCH 0/2] fuse: skip LOOKUP before exclusive create Jimmy Zuber
2026-10-20 15:34 ` Jimmy Zuber [this message]
2026-10-20 15:34 ` [PATCH 2/2] selftests/fuse: test FUSE_EXCL_CREATE Jimmy Zuber

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261020153432.3812719-2-jamz@amazon.com \
    --to=jamz@amazon.com \
    --cc=bernd@bsbernd.com \
    --cc=fuse-devel@lists.linux.dev \
    --cc=horst@birthelmer.de \
    --cc=jiharris@nvidia.com \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=luis@igalia.com \
    --cc=miklos@szeredi.hu \
    --cc=shuah@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.