On Thu, May 15, 2025 at 9:35 AM Chen Linxuan chenlinxuan@uniontech.com wrote:
This patch add a simple functional test for the "about" file in fusectlfs (/sys/fs/fuse/connections/ID/about).
A simple fuse daemon is added for testing.
Related discussion can be found in the link below.
Link: https://lore.kernel.org/all/CAOQ4uxjKFXOKQxPpxtS6G_nR0tpw95w0GiO68UcWg_OBhmS... Cc: Amir Goldstein amir73il@gmail.com Signed-off-by: Chen Linxuan chenlinxuan@uniontech.com
MAINTAINERS | 1 + tools/testing/selftests/Makefile | 1 + .../selftests/filesystems/fusectl/.gitignore | 3 + .../selftests/filesystems/fusectl/Makefile | 21 +++ .../selftests/filesystems/fusectl/fuse_mnt.c | 146 ++++++++++++++++++ .../filesystems/fusectl/fusectl_test.c | 115 ++++++++++++++ 6 files changed, 287 insertions(+) create mode 100644 tools/testing/selftests/filesystems/fusectl/.gitignore create mode 100644 tools/testing/selftests/filesystems/fusectl/Makefile create mode 100644 tools/testing/selftests/filesystems/fusectl/fuse_mnt.c create mode 100644 tools/testing/selftests/filesystems/fusectl/fusectl_test.c
diff --git a/MAINTAINERS b/MAINTAINERS index f21f1dabb5fe1..efc6c89113b95 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9740,6 +9740,7 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/fuse.git F: Documentation/filesystems/fuse.rst F: fs/fuse/ F: include/uapi/linux/fuse.h +F: tools/testing/selftests/filesystems/fusectl
FUTEX SUBSYSTEM M: Thomas Gleixner tglx@linutronix.de diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index 80fb84fa3cfcb..a9bfefa961889 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -36,6 +36,7 @@ TARGETS += filesystems/fat TARGETS += filesystems/overlayfs TARGETS += filesystems/statmount TARGETS += filesystems/mount-notify +TARGETS += filesystems/fusectl TARGETS += firmware TARGETS += fpu TARGETS += ftrace diff --git a/tools/testing/selftests/filesystems/fusectl/.gitignore b/tools/testing/selftests/filesystems/fusectl/.gitignore new file mode 100644 index 0000000000000..3e72e742d08e8 --- /dev/null +++ b/tools/testing/selftests/filesystems/fusectl/.gitignore @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only +fuse_mnt +fusectl_test diff --git a/tools/testing/selftests/filesystems/fusectl/Makefile b/tools/testing/selftests/filesystems/fusectl/Makefile new file mode 100644 index 0000000000000..612aad69a93aa --- /dev/null +++ b/tools/testing/selftests/filesystems/fusectl/Makefile @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: GPL-2.0-or-later
+CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES)
+TEST_GEN_PROGS := fusectl_test +TEST_GEN_FILES := fuse_mnt
+include ../../lib.mk
+VAR_CFLAGS := $(shell pkg-config fuse --cflags 2>/dev/null) +ifeq ($(VAR_CFLAGS),) +VAR_CFLAGS := -D_FILE_OFFSET_BITS=64 -I/usr/include/fuse +endif
+VAR_LDLIBS := $(shell pkg-config fuse --libs 2>/dev/null) +ifeq ($(VAR_LDLIBS),) +VAR_LDLIBS := -lfuse -pthread +endif
+$(OUTPUT)/fuse_mnt: CFLAGS += $(VAR_CFLAGS) +$(OUTPUT)/fuse_mnt: LDLIBS += $(VAR_LDLIBS) diff --git a/tools/testing/selftests/filesystems/fusectl/fuse_mnt.c b/tools/testing/selftests/filesystems/fusectl/fuse_mnt.c new file mode 100644 index 0000000000000..d12b17f30fadc --- /dev/null +++ b/tools/testing/selftests/filesystems/fusectl/fuse_mnt.c @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: GPL-2.0 +/*
- fusectl test file-system
- Creates a simple FUSE filesystem with a single read-write file (/test)
- */
+#define FUSE_USE_VERSION 26
+#include <fuse.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <unistd.h>
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+static char *content; +static size_t content_size = 0; +static const char test_path[] = "/test";
+static int test_getattr(const char *path, struct stat *st) +{
memset(st, 0, sizeof(*st));
if (!strcmp(path, "/")) {
st->st_mode = S_IFDIR | 0755;
st->st_nlink = 2;
return 0;
}
if (!strcmp(path, test_path)) {
st->st_mode = S_IFREG | 0664;
st->st_nlink = 1;
st->st_size = content_size;
return 0;
}
return -ENOENT;
+}
+static int test_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
off_t offset, struct fuse_file_info *fi)
+{
if (strcmp(path, "/"))
return -ENOENT;
filler(buf, ".", NULL, 0);
filler(buf, "..", NULL, 0);
filler(buf, test_path + 1, NULL, 0);
return 0;
+}
+static int test_open(const char *path, struct fuse_file_info *fi) +{
if (strcmp(path, test_path))
return -ENOENT;
return 0;
+}
+static int test_read(const char *path, char *buf, size_t size, off_t offset,
struct fuse_file_info *fi)
+{
if (strcmp(path, test_path) != 0)
return -ENOENT;
if (!content || content_size == 0)
return 0;
if (offset >= content_size)
return 0;
if (offset + size > content_size)
size = content_size - offset;
memcpy(buf, content + offset, size);
return size;
+}
+static int test_write(const char *path, const char *buf, size_t size,
off_t offset, struct fuse_file_info *fi)
+{
size_t new_size;
if (strcmp(path, test_path) != 0)
return -ENOENT;
if(offset > content_size)
return -EINVAL;
new_size = MAX(offset + size, content_size);
if (new_size > content_size)
content = realloc(content, new_size);
content_size = new_size;
if (!content)
return -ENOMEM;
memcpy(content + offset, buf, size);
return size;
+}
+static int test_truncate(const char *path, off_t size) +{
if (strcmp(path, test_path) != 0)
return -ENOENT;
if (size == 0) {
free(content);
content = NULL;
content_size = 0;
return 0;
}
content = realloc(content, size);
if (!content)
return -ENOMEM;
if (size > content_size)
memset(content + content_size, 0, size - content_size);
content_size = size;
return 0;
+}
+static struct fuse_operations memfd_ops = {
.getattr = test_getattr,
.readdir = test_readdir,
.open = test_open,
.read = test_read,
.write = test_write,
.truncate = test_truncate,
+};
+int main(int argc, char *argv[]) +{
return fuse_main(argc, argv, &memfd_ops, NULL);
+} diff --git a/tools/testing/selftests/filesystems/fusectl/fusectl_test.c b/tools/testing/selftests/filesystems/fusectl/fusectl_test.c new file mode 100644 index 0000000000000..8ff130ae43122 --- /dev/null +++ b/tools/testing/selftests/filesystems/fusectl/fusectl_test.c @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright (c) 2025 Chen Linxuan chenlinxuan@uniontech.com
+#define _GNU_SOURCE
+#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mount.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> +#include <dirent.h> +#include <linux/limits.h>
+#include "../../kselftest_harness.h"
+#define FUSECTL_MOUNTPOINT "/sys/fs/fuse/connections" +#define FUSE_MOUNTPOINT "/tmp/fuse_mnt_XXXXXX" +#define FUSE_DEVICE "/dev/fuse" +#define FUSECTL_TEST_VALUE "1"
+FIXTURE(fusectl){
char fuse_mountpoint[sizeof(FUSE_MOUNTPOINT)];
int connection;
+};
+FIXTURE_SETUP(fusectl) +{
const char *fuse_mnt_prog = "./fuse_mnt";
int status, pid;
struct stat statbuf;
strcpy(self->fuse_mountpoint, FUSE_MOUNTPOINT);
if (!mkdtemp(self->fuse_mountpoint))
SKIP(return,
"Failed to create FUSE mountpoint %s",
strerror(errno));
if (access(FUSECTL_MOUNTPOINT, F_OK))
SKIP(return,
"FUSE control filesystem not mounted");
pid = fork();
if (pid < 0)
SKIP(return,
"Failed to fork FUSE daemon process: %s",
strerror(errno));
if (pid == 0) {
execlp(fuse_mnt_prog, fuse_mnt_prog, self->fuse_mountpoint, NULL);
exit(errno);
}
waitpid(pid, &status, 0);
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
SKIP(return,
"Failed to start FUSE daemon %s",
strerror(WEXITSTATUS(status)));
}
if (stat(self->fuse_mountpoint, &statbuf))
SKIP(return,
"Failed to stat FUSE mountpoint %s",
strerror(errno));
self->connection = statbuf.st_dev;
+}
+FIXTURE_TEARDOWN(fusectl) +{
umount(self->fuse_mountpoint);
rmdir(self->fuse_mountpoint);
+}
+TEST_F(fusectl, abort) +{
char path_buf[PATH_MAX];
int abort_fd, test_fd, ret;
sprintf(path_buf, "/sys/fs/fuse/connections/%d/abort", self->connection);
ASSERT_EQ(0, access(path_buf, F_OK));
abort_fd = open(path_buf, O_WRONLY);
ASSERT_GE(abort_fd, 0);
sprintf(path_buf, "%s/test", self->fuse_mountpoint);
test_fd = open(path_buf, O_RDWR);
ASSERT_GE(test_fd, 0);
ret = read(test_fd, path_buf, sizeof(path_buf));
ASSERT_EQ(ret, 0);
ret = write(test_fd, "test", sizeof("test"));
ASSERT_EQ(ret, sizeof("test"));
ret = lseek(test_fd, 0, SEEK_SET);
ASSERT_GE(ret, 0);
ret = write(abort_fd, FUSECTL_TEST_VALUE, sizeof(FUSECTL_TEST_VALUE));
ASSERT_GT(ret, 0);
close(abort_fd);
ret = read(test_fd, path_buf, sizeof(path_buf));
ASSERT_LT(ret, 0);
Nice! I guess you could also verify errno == ENOTCONN or whatever it is in this case.
Thanks, Amir.