Linux Kernel Selftest development
 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>, NeilBrown <neil@brown.name>
Subject: [PATCH v5 2/2] selftests/fuse: add test for FUSE_OPEN_ATOMIC
Date: Wed, 28 Oct 2026 20:04:59 +0000	[thread overview]
Message-ID: <20261028200459.2922951-3-jamz@amazon.com> (raw)
In-Reply-To: <20261028200459.2922951-1-jamz@amazon.com>

Add atomic_open_test. A raw /dev/fuse server counts the requests the
kernel sends for open() and create, and each test checks both the result
and that FUSE_OPEN_ATOMIC replaces LOOKUP + OPEN or LOOKUP + CREATE
where it should: new, existing and missing names, directories, O_TRUNC,
permission checks after the reply, and a reply the kernel rejects.

Each test runs against a server that replies -ENOSYS to
FUSE_OPEN_ATOMIC, one that implements it, and one that also sets
FUSE_ATOMIC_O_TRUNC, in its own mount namespace. The tests skip on
kernels older than protocol 7.48.

Assisted-by: Claude:claude-opus-5-5 [Claude-Code]
Signed-off-by: Jimmy Zuber <jamz@amazon.com>
---
 .../selftests/filesystems/fuse/.gitignore     |   1 +
 .../selftests/filesystems/fuse/Makefile       |   3 +-
 .../filesystems/fuse/atomic_open_test.c       | 552 ++++++++++++++++++
 3 files changed, 555 insertions(+), 1 deletion(-)
 create mode 100644 tools/testing/selftests/filesystems/fuse/atomic_open_test.c

diff --git a/tools/testing/selftests/filesystems/fuse/.gitignore b/tools/testing/selftests/filesystems/fuse/.gitignore
index 7c787313e69b6..faac6d969ef94 100644
--- a/tools/testing/selftests/filesystems/fuse/.gitignore
+++ b/tools/testing/selftests/filesystems/fuse/.gitignore
@@ -1,4 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0-only
+atomic_open_test
 fuse_mnt
 fusectl_test
 test_syncfs
diff --git a/tools/testing/selftests/filesystems/fuse/Makefile b/tools/testing/selftests/filesystems/fuse/Makefile
index e3ae3acff1139..8390d6019513b 100644
--- a/tools/testing/selftests/filesystems/fuse/Makefile
+++ b/tools/testing/selftests/filesystems/fuse/Makefile
@@ -3,7 +3,7 @@
 CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES)
 
 TEST_GEN_PROGS := fusectl_test test_syncfs
-TEST_GEN_PROGS += write_extend_eof_test
+TEST_GEN_PROGS += write_extend_eof_test atomic_open_test
 TEST_GEN_PROGS += fuse_acl_cache_test
 TEST_GEN_PROGS += fuse_symlink_cache_test
 TEST_GEN_PROGS += fuse_readdir_cache_test
@@ -12,6 +12,7 @@ TEST_GEN_FILES := fuse_mnt
 include ../../lib.mk
 
 $(OUTPUT)/write_extend_eof_test: LDLIBS += -lpthread
+$(OUTPUT)/atomic_open_test: LDLIBS += -lpthread
 
 VAR_CFLAGS := $(shell pkg-config fuse3 --cflags 2>/dev/null)
 ifeq ($(VAR_CFLAGS),)
diff --git a/tools/testing/selftests/filesystems/fuse/atomic_open_test.c b/tools/testing/selftests/filesystems/fuse/atomic_open_test.c
new file mode 100644
index 0000000000000..a42870ee85a04
--- /dev/null
+++ b/tools/testing/selftests/filesystems/fuse/atomic_open_test.c
@@ -0,0 +1,552 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Test the requests that open() and create send with and without
+ * FUSE_OPEN_ATOMIC, against a raw /dev/fuse server.
+ *
+ * Requires root to mount fuse.
+ */
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <pthread.h>
+#include <sched.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <sys/wait.h>
+#include <linux/fuse.h>
+
+#include "../../kselftest_harness.h"
+
+#define MAX_WRITE	(128 * 1024)
+#define MAX_ENTRIES	16
+#define NOBODY		65534
+
+struct entry {
+	uint64_t parent;
+	char name[64];
+	uint64_t nodeid;
+	uint32_t mode;
+};
+
+/* Server state, shared with the responder thread */
+struct server {
+	int fd;
+	int atomic, atomic_o_trunc;
+	/* Protocol minor version of the kernel, from FUSE_INIT */
+	uint32_t minor;
+	struct entry ent[MAX_ENTRIES];
+	int nent;
+	int nr_lookup, nr_create, nr_open, nr_open_atomic, nr_setattr;
+	int nr_enosys;
+	/* Requests that carried O_TRUNC */
+	int nr_trunc;
+	/* Send fuse_open_out without backing_id_64, as older servers do */
+	int short_open;
+	/* Reply to FUSE_OPEN_ATOMIC with an opened file and a zero nodeid */
+	int open_no_nodeid;
+};
+
+static size_t open_out_size(struct server *s)
+{
+	if (s->short_open)
+		return offsetof(struct fuse_open_out, backing_id_64);
+	return sizeof(struct fuse_open_out);
+}
+
+static void reply(int fd, uint64_t unique, int error, void *data, size_t len)
+{
+	struct fuse_out_header oh = {
+		.len = sizeof(oh) + (data ? len : 0),
+		.error = error,
+		.unique = unique,
+	};
+	struct iovec iov[2] = { { &oh, sizeof(oh) }, { data, len } };
+
+	/* Errors here are teardown races (device closed on unmount) */
+	if (writev(fd, iov, data ? 2 : 1) < 0)
+		return;
+}
+
+static struct entry *find_in(struct server *s, uint64_t parent,
+			     const char *name)
+{
+	int i;
+
+	for (i = 0; i < s->nent; i++)
+		if (s->ent[i].parent == parent && !strcmp(s->ent[i].name, name))
+			return &s->ent[i];
+	return NULL;
+}
+
+static struct entry *add_in(struct server *s, uint64_t parent,
+			    const char *name, uint32_t mode)
+{
+	struct entry *e = &s->ent[s->nent];
+
+	if (s->nent >= MAX_ENTRIES)
+		return NULL;
+	e->parent = parent;
+	snprintf(e->name, sizeof(e->name), "%.63s", name);
+	e->nodeid = FUSE_ROOT_ID + 1 + s->nent++;
+	e->mode = mode;
+	return e;
+}
+
+static struct entry *add(struct server *s, const char *name, uint32_t mode)
+{
+	return add_in(s, FUSE_ROOT_ID, name, mode);
+}
+
+/* A NULL entry is a negative entry, cached like a found one */
+static void fill_entry(struct fuse_entry_out *out, struct entry *e)
+{
+	memset(out, 0, sizeof(*out));
+	out->entry_valid = 3600;
+	if (!e)
+		return;
+	out->nodeid = e->nodeid;
+	out->attr_valid = 3600;
+	out->attr.ino = e->nodeid;
+	out->attr.mode = e->mode;
+	out->attr.nlink = S_ISDIR(e->mode) ? 2 : 1;
+	out->attr.blksize = 4096;
+}
+
+static void do_open_atomic(struct server *s, struct fuse_in_header *ih)
+{
+	struct fuse_create_in *in = (void *)(ih + 1);
+	const char *name = (char *)(in + 1);
+	struct entry *e = find_in(s, ih->nodeid, name);
+	struct {
+		struct fuse_entry_out entry;
+		struct fuse_open_out open;
+	} out = {0};
+	size_t len = sizeof(out.entry) + open_out_size(s);
+
+	if (ih->opcode == FUSE_OPEN_ATOMIC && !s->atomic) {
+		s->nr_enosys++;
+		return reply(s->fd, ih->unique, -ENOSYS, NULL, 0);
+	}
+	if (ih->opcode == FUSE_CREATE)
+		s->nr_create++;
+	else
+		s->nr_open_atomic++;
+	if (in->flags & O_TRUNC)
+		s->nr_trunc++;
+	if (e && (in->flags & O_CREAT) && (in->flags & O_EXCL))
+		return reply(s->fd, ih->unique, -EEXIST, NULL, 0);
+	if (e && !(in->flags & O_CREAT) && s->open_no_nodeid) {
+		fill_entry(&out.entry, NULL);
+		return reply(s->fd, ih->unique, 0, &out, len);
+	}
+	if (!e && !(in->flags & O_CREAT)) {
+		fill_entry(&out.entry, NULL);
+		return reply(s->fd, ih->unique, 0, &out.entry,
+			     sizeof(out.entry));
+	}
+	if (e && !S_ISREG(e->mode)) {
+		fill_entry(&out.entry, e);
+		return reply(s->fd, ih->unique, 0, &out.entry,
+			     sizeof(out.entry));
+	}
+	if (!e) {
+		e = add_in(s, ih->nodeid, name, S_IFREG | in->mode);
+		if (!e)
+			return reply(s->fd, ih->unique, -ENOSPC, NULL, 0);
+		if (ih->opcode == FUSE_OPEN_ATOMIC)
+			out.open.open_flags = FOPEN_CREATED;
+	}
+	fill_entry(&out.entry, e);
+	reply(s->fd, ih->unique, 0, &out, len);
+}
+
+static void *server_thread(void *arg)
+{
+	struct server *s = arg;
+	static char buf[MAX_WRITE + 4096];
+
+	for (;;) {
+		ssize_t n = read(s->fd, buf, sizeof(buf));
+		struct fuse_in_header *ih = (void *)buf;
+
+		if (n < 0) {
+			if (errno == EINTR || errno == EAGAIN)
+				continue;
+			return NULL;	/* device closed on unmount */
+		}
+		if (n < (ssize_t)sizeof(*ih))
+			continue;
+
+		switch (ih->opcode) {
+		case FUSE_INIT: {
+			struct fuse_init_in *in = (void *)(ih + 1);
+			struct fuse_init_out out = {0};
+
+			s->minor = in->minor;
+			out.major = FUSE_KERNEL_VERSION;
+			out.minor = FUSE_KERNEL_MINOR_VERSION;
+			out.max_readahead = in->max_readahead;
+			out.max_write = MAX_WRITE;
+			out.max_background = 16;
+			out.congestion_threshold = 12;
+			if (s->atomic_o_trunc)
+				out.flags = FUSE_ATOMIC_O_TRUNC;
+			reply(s->fd, ih->unique, 0, &out, sizeof(out));
+			break;
+		}
+		case FUSE_GETATTR:
+		case FUSE_SETATTR: {
+			struct fuse_attr_out out = {0};
+			struct fuse_entry_out eo;
+			int i;
+
+			if (ih->opcode == FUSE_SETATTR)
+				s->nr_setattr++;
+			out.attr_valid = 3600;
+			out.attr.ino = FUSE_ROOT_ID;
+			out.attr.mode = S_IFDIR | 0777;
+			out.attr.nlink = 2;
+			for (i = 0; i < s->nent; i++) {
+				if (s->ent[i].nodeid == ih->nodeid) {
+					fill_entry(&eo, &s->ent[i]);
+					out.attr = eo.attr;
+					out.attr_valid = eo.attr_valid;
+				}
+			}
+			reply(s->fd, ih->unique, 0, &out, sizeof(out));
+			break;
+		}
+		case FUSE_LOOKUP: {
+			struct fuse_entry_out out;
+			struct entry *e;
+
+			s->nr_lookup++;
+			e = find_in(s, ih->nodeid, (char *)(ih + 1));
+			fill_entry(&out, e);
+			reply(s->fd, ih->unique, 0, &out, sizeof(out));
+			break;
+		}
+		case FUSE_CREATE:
+		case FUSE_OPEN_ATOMIC:
+			do_open_atomic(s, ih);
+			break;
+		case FUSE_OPEN: {
+			struct fuse_open_in *in = (void *)(ih + 1);
+			struct fuse_open_out out = {0};
+
+			s->nr_open++;
+			if (in->flags & O_TRUNC)
+				s->nr_trunc++;
+			reply(s->fd, ih->unique, 0, &out, open_out_size(s));
+			break;
+		}
+		case FUSE_OPENDIR: {
+			struct fuse_open_out out = {0};
+
+			reply(s->fd, ih->unique, 0, &out, open_out_size(s));
+			break;
+		}
+		case FUSE_RELEASE:
+		case FUSE_FLUSH:
+		case FUSE_RELEASEDIR:
+			reply(s->fd, ih->unique, 0, NULL, 0);
+			break;
+		case FUSE_FORGET:
+		case FUSE_BATCH_FORGET:
+			break;
+		default:
+			reply(s->fd, ih->unique, -ENOSYS, NULL, 0);
+			break;
+		}
+	}
+}
+
+FIXTURE(fuse)
+{
+	struct server *srv;
+	char dir[64];
+	int mounted;
+};
+
+FIXTURE_VARIANT(fuse)
+{
+	int atomic;
+	int atomic_o_trunc;
+};
+
+FIXTURE_VARIANT_ADD(fuse, atomic)
+{
+	.atomic = 1,
+};
+
+FIXTURE_VARIANT_ADD(fuse, atomic_trunc)
+{
+	.atomic = 1,
+	.atomic_o_trunc = 1,
+};
+
+FIXTURE_VARIANT_ADD(fuse, plain)
+{
+	.atomic = 0,
+};
+
+static void cleanup(FIXTURE_DATA(fuse) *self)
+{
+	if (self->mounted)
+		umount2(self->dir, MNT_DETACH);
+	self->mounted = 0;
+	/* The detached server thread may still use self->srv */
+	if (self->srv && self->srv != MAP_FAILED && self->srv->fd >= 0)
+		close(self->srv->fd);
+	if (self->srv && self->srv != MAP_FAILED)
+		self->srv->fd = -1;
+	if (self->dir[0])
+		rmdir(self->dir);
+	self->dir[0] = '\0';
+}
+
+FIXTURE_SETUP(fuse)
+{
+	char opts[128];
+	struct stat st;
+	pthread_t t;
+	int ret;
+
+	if (geteuid() != 0)
+		SKIP(return, "need root to mount fuse");
+
+	/* Mounts made by the test go away with its mount namespace */
+	ASSERT_EQ(0, unshare(CLONE_NEWNS));
+	ASSERT_EQ(0, mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL));
+
+	self->srv = mmap(NULL, sizeof(*self->srv), PROT_READ | PROT_WRITE,
+			 MAP_SHARED | MAP_ANONYMOUS, -1, 0);
+	ASSERT_NE(MAP_FAILED, self->srv);
+	self->srv->fd = -1;
+	self->srv->atomic = variant->atomic;
+	self->srv->atomic_o_trunc = variant->atomic_o_trunc;
+	add(self->srv, "ro", S_IFREG | 0444);
+	add(self->srv, "rw", S_IFREG | 0644);
+	add(self->srv, "dir", S_IFDIR | 0755);
+
+	self->srv->fd = open("/dev/fuse", O_RDWR);
+	if (self->srv->fd < 0 && (errno == ENOENT || errno == ENODEV)) {
+		cleanup(self);
+		SKIP(return, "no /dev/fuse");
+	}
+	ASSERT_GE(self->srv->fd, 0);
+
+	strcpy(self->dir, "/tmp/fuse_aopen_XXXXXX");
+	if (!mkdtemp(self->dir)) {
+		self->dir[0] = '\0';
+		cleanup(self);
+	}
+	ASSERT_NE('\0', self->dir[0]);
+
+	snprintf(opts, sizeof(opts),
+		 "fd=%d,rootmode=40000,user_id=0,group_id=0,default_permissions,allow_other",
+		 self->srv->fd);
+	ret = mount("fuse", self->dir, "fuse", 0, opts);
+	if (ret)
+		cleanup(self);
+	ASSERT_EQ(0, ret);
+	self->mounted = 1;
+
+	ret = pthread_create(&t, NULL, server_thread, self->srv);
+	if (!ret) {
+		pthread_detach(t);
+		/* Wait for FUSE_INIT: the root GETATTR is queued behind it */
+		ret = stat(self->dir, &st);
+	}
+	/* The harness skips teardown when setup fails or skips */
+	if (ret || self->srv->minor < 48)
+		cleanup(self);
+	ASSERT_EQ(0, ret);
+	if (self->srv->minor < 48)
+		SKIP(return, "kernel without FUSE_OPEN_ATOMIC (protocol 7.48)");
+}
+
+FIXTURE_TEARDOWN(fuse)
+{
+	cleanup(self);
+}
+
+/* Open @name as an unprivileged user and return the errno, or 0 */
+static int open_as_nobody(struct __test_metadata *_metadata, const char *dir,
+			  const char *name, int flags)
+{
+	char path[128];
+	int status, fd;
+	pid_t pid;
+
+	snprintf(path, sizeof(path), "%s/%s", dir, name);
+	pid = fork();
+	ASSERT_GE(pid, 0);
+	if (pid == 0) {
+		if (setgroups(0, NULL) || setgid(NOBODY) || setuid(NOBODY))
+			_exit(255);
+		fd = open(path, flags, 0644);
+		_exit(fd < 0 ? errno : 0);
+	}
+	ASSERT_EQ(pid, waitpid(pid, &status, 0));
+	ASSERT_TRUE(WIFEXITED(status));
+	/* 255: the child could not drop its privileges */
+	ASSERT_NE(255, WEXITSTATUS(status));
+	return WEXITSTATUS(status);
+}
+
+static int open_at(const char *dir, const char *name, int flags)
+{
+	char path[128];
+
+	snprintf(path, sizeof(path), "%s/%s", dir, name);
+	return open(path, flags, 0644);
+}
+
+static void open_close(struct __test_metadata *_metadata, const char *dir,
+		       const char *name, int flags)
+{
+	int fd = open_at(dir, name, flags);
+
+	ASSERT_GE(fd, 0);
+	close(fd);
+}
+
+TEST_F(fuse, create_new)
+{
+	open_close(_metadata, self->dir, "a", O_CREAT | O_WRONLY);
+	open_close(_metadata, self->dir, "b", O_CREAT | O_EXCL | O_WRONLY);
+
+	EXPECT_EQ(0, self->srv->nr_open);
+	EXPECT_EQ(variant->atomic ? 2 : 0, self->srv->nr_open_atomic);
+	EXPECT_EQ(variant->atomic ? 0 : 2, self->srv->nr_lookup);
+	EXPECT_EQ(variant->atomic ? 0 : 2, self->srv->nr_create);
+}
+
+TEST_F(fuse, create_existing)
+{
+	ASSERT_EQ(-1, open_at(self->dir, "ro", O_CREAT | O_EXCL | O_WRONLY));
+	EXPECT_EQ(EEXIST, errno);
+	/* The VFS checks the open after the reply */
+	EXPECT_EQ(EACCES, open_as_nobody(_metadata, self->dir, "ro",
+					 O_CREAT | O_RDWR));
+	EXPECT_EQ(0, self->srv->nr_create);
+}
+
+TEST_F(fuse, open_existing)
+{
+	/* The VFS checks the open after the reply */
+	EXPECT_EQ(EACCES, open_as_nobody(_metadata, self->dir, "ro", O_RDWR));
+	open_close(_metadata, self->dir, "rw", O_RDONLY);
+	ASSERT_EQ(-1, open_at(self->dir, "missing", O_RDONLY));
+	EXPECT_EQ(ENOENT, errno);
+	/* A zero nodeid is cached like a negative LOOKUP */
+	ASSERT_EQ(-1, open_at(self->dir, "missing", O_RDONLY));
+	EXPECT_EQ(ENOENT, errno);
+
+	EXPECT_EQ(variant->atomic ? 0 : 1, self->srv->nr_open);
+	EXPECT_EQ(variant->atomic ? 3 : 0, self->srv->nr_open_atomic);
+	EXPECT_EQ(variant->atomic ? 0 : 3, self->srv->nr_lookup);
+}
+
+TEST_F(fuse, open_trunc)
+{
+	open_close(_metadata, self->dir, "rw", O_WRONLY | O_TRUNC);
+	/* As a shell "> ro" */
+	open_close(_metadata, self->dir, "ro", O_CREAT | O_TRUNC | O_WRONLY);
+
+	EXPECT_EQ(0, self->srv->nr_create);
+	if (variant->atomic_o_trunc) {
+		/* FUSE_OPEN truncates, after a LOOKUP */
+		EXPECT_EQ(0, self->srv->nr_open_atomic);
+		EXPECT_EQ(2, self->srv->nr_lookup);
+		EXPECT_EQ(2, self->srv->nr_open);
+		EXPECT_EQ(2, self->srv->nr_trunc);
+		EXPECT_EQ(0, self->srv->nr_setattr);
+	} else if (variant->atomic) {
+		/* The server opens the file and SETATTR truncates it */
+		EXPECT_EQ(2, self->srv->nr_open_atomic);
+		EXPECT_EQ(0, self->srv->nr_lookup);
+		EXPECT_EQ(0, self->srv->nr_open);
+		EXPECT_EQ(0, self->srv->nr_trunc);
+		EXPECT_EQ(2, self->srv->nr_setattr);
+	} else {
+		EXPECT_EQ(2, self->srv->nr_lookup);
+		EXPECT_EQ(2, self->srv->nr_open);
+		EXPECT_EQ(2, self->srv->nr_setattr);
+	}
+}
+
+TEST_F(fuse, open_directory)
+{
+	/* The server returns the entry only, and the VFS opens it */
+	open_close(_metadata, self->dir, "dir", O_RDONLY);
+	EXPECT_EQ(variant->atomic ? 1 : 0, self->srv->nr_open_atomic);
+	EXPECT_EQ(variant->atomic ? 0 : 1, self->srv->nr_lookup);
+}
+
+TEST_F(fuse, short_open_out)
+{
+	self->srv->short_open = 1;
+	open_close(_metadata, self->dir, "a", O_CREAT | O_WRONLY);
+	open_close(_metadata, self->dir, "rw", O_RDWR);
+
+	EXPECT_EQ(variant->atomic ? 2 : 0, self->srv->nr_open_atomic);
+	EXPECT_EQ(variant->atomic ? 0 : 1, self->srv->nr_open);
+}
+
+TEST_F(fuse, open_reply_no_nodeid)
+{
+	int fd;
+
+	/* An opened file needs a nodeid */
+	self->srv->open_no_nodeid = 1;
+	fd = open_at(self->dir, "rw", O_RDONLY);
+	if (variant->atomic) {
+		ASSERT_EQ(-1, fd);
+		EXPECT_EQ(EIO, errno);
+	} else {
+		ASSERT_GE(fd, 0);
+		close(fd);
+	}
+}
+
+TEST_F(fuse, open_atomic_enosys)
+{
+	/* After -ENOSYS the kernel sends FUSE_LOOKUP */
+	self->srv->atomic = 0;
+	open_close(_metadata, self->dir, "rw", O_RDONLY);
+	open_close(_metadata, self->dir, "a", O_CREAT | O_WRONLY);
+
+	EXPECT_EQ(1, self->srv->nr_enosys);
+	EXPECT_EQ(0, self->srv->nr_open_atomic);
+	EXPECT_EQ(2, self->srv->nr_lookup);
+	EXPECT_EQ(1, self->srv->nr_open);
+	EXPECT_EQ(1, self->srv->nr_create);
+}
+
+TEST_F(fuse, name_too_long)
+{
+	char path[1200];
+	int len;
+
+	/* Longer than the 1024 byte limit of a server without FUSE_MAX_PAGES */
+	len = snprintf(path, sizeof(path), "%s/", self->dir);
+	memset(path + len, 'x', 1100);
+	path[len + 1100] = '\0';
+	ASSERT_EQ(-1, open(path, O_CREAT | O_WRONLY, 0644));
+	EXPECT_EQ(ENAMETOOLONG, errno);
+	EXPECT_EQ(0, self->srv->nr_enosys);
+	EXPECT_EQ(0, self->srv->nr_open_atomic);
+	EXPECT_EQ(0, self->srv->nr_create);
+}
+
+TEST_HARNESS_MAIN
-- 
2.50.1


      parent reply	other threads:[~2026-10-28 20:06 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-28 20:04 [PATCH v5 0/2] fuse: atomic open Jimmy Zuber
2026-10-28 20:04 ` [PATCH v5 1/2] fuse: add FUSE_OPEN_ATOMIC Jimmy Zuber
2026-10-28 20:04 ` Jimmy Zuber [this message]

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=20261028200459.2922951-3-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=neil@brown.name \
    --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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox