Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

selftests: filesystems: Add functional test for the abort file in fusectl

This patch add a simple functional test for the "abort" file
in fusectlfs (/sys/fs/fuse/connections/ID/abort).

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_OBhmSY=Q@mail.gmail.com/
Signed-off-by: Chen Linxuan <chenlinxuan@uniontech.com>
Acked-by: Shuah Khan <skhan@linuxfoundation.org>
Reviewed-by: Amir Goldstein <amir73il@gmail.com>
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>

authored by

Chen Linxuan and committed by
Miklos Szeredi
1a7b1378 e49a6828

+312
+1
MAINTAINERS
··· 10064 10064 F: Documentation/filesystems/fuse* 10065 10065 F: fs/fuse/ 10066 10066 F: include/uapi/linux/fuse.h 10067 + F: tools/testing/selftests/filesystems/fuse/ 10067 10068 10068 10069 FUTEX SUBSYSTEM 10069 10070 M: Thomas Gleixner <tglx@linutronix.de>
+1
tools/testing/selftests/Makefile
··· 36 36 TARGETS += filesystems/overlayfs 37 37 TARGETS += filesystems/statmount 38 38 TARGETS += filesystems/mount-notify 39 + TARGETS += filesystems/fuse 39 40 TARGETS += firmware 40 41 TARGETS += fpu 41 42 TARGETS += ftrace
+3
tools/testing/selftests/filesystems/fuse/.gitignore
··· 1 + # SPDX-License-Identifier: GPL-2.0-only 2 + fuse_mnt 3 + fusectl_test
+21
tools/testing/selftests/filesystems/fuse/Makefile
··· 1 + # SPDX-License-Identifier: GPL-2.0-or-later 2 + 3 + CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES) 4 + 5 + TEST_GEN_PROGS := fusectl_test 6 + TEST_GEN_FILES := fuse_mnt 7 + 8 + include ../../lib.mk 9 + 10 + VAR_CFLAGS := $(shell pkg-config fuse --cflags 2>/dev/null) 11 + ifeq ($(VAR_CFLAGS),) 12 + VAR_CFLAGS := -D_FILE_OFFSET_BITS=64 -I/usr/include/fuse 13 + endif 14 + 15 + VAR_LDLIBS := $(shell pkg-config fuse --libs 2>/dev/null) 16 + ifeq ($(VAR_LDLIBS),) 17 + VAR_LDLIBS := -lfuse -pthread 18 + endif 19 + 20 + $(OUTPUT)/fuse_mnt: CFLAGS += $(VAR_CFLAGS) 21 + $(OUTPUT)/fuse_mnt: LDLIBS += $(VAR_LDLIBS)
+146
tools/testing/selftests/filesystems/fuse/fuse_mnt.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * fusectl test file-system 4 + * Creates a simple FUSE filesystem with a single read-write file (/test) 5 + */ 6 + 7 + #define FUSE_USE_VERSION 26 8 + 9 + #include <fuse.h> 10 + #include <stdio.h> 11 + #include <string.h> 12 + #include <errno.h> 13 + #include <fcntl.h> 14 + #include <stdlib.h> 15 + #include <unistd.h> 16 + 17 + #define MAX(a, b) ((a) > (b) ? (a) : (b)) 18 + 19 + static char *content; 20 + static size_t content_size = 0; 21 + static const char test_path[] = "/test"; 22 + 23 + static int test_getattr(const char *path, struct stat *st) 24 + { 25 + memset(st, 0, sizeof(*st)); 26 + 27 + if (!strcmp(path, "/")) { 28 + st->st_mode = S_IFDIR | 0755; 29 + st->st_nlink = 2; 30 + return 0; 31 + } 32 + 33 + if (!strcmp(path, test_path)) { 34 + st->st_mode = S_IFREG | 0664; 35 + st->st_nlink = 1; 36 + st->st_size = content_size; 37 + return 0; 38 + } 39 + 40 + return -ENOENT; 41 + } 42 + 43 + static int test_readdir(const char *path, void *buf, fuse_fill_dir_t filler, 44 + off_t offset, struct fuse_file_info *fi) 45 + { 46 + if (strcmp(path, "/")) 47 + return -ENOENT; 48 + 49 + filler(buf, ".", NULL, 0); 50 + filler(buf, "..", NULL, 0); 51 + filler(buf, test_path + 1, NULL, 0); 52 + 53 + return 0; 54 + } 55 + 56 + static int test_open(const char *path, struct fuse_file_info *fi) 57 + { 58 + if (strcmp(path, test_path)) 59 + return -ENOENT; 60 + 61 + return 0; 62 + } 63 + 64 + static int test_read(const char *path, char *buf, size_t size, off_t offset, 65 + struct fuse_file_info *fi) 66 + { 67 + if (strcmp(path, test_path) != 0) 68 + return -ENOENT; 69 + 70 + if (!content || content_size == 0) 71 + return 0; 72 + 73 + if (offset >= content_size) 74 + return 0; 75 + 76 + if (offset + size > content_size) 77 + size = content_size - offset; 78 + 79 + memcpy(buf, content + offset, size); 80 + 81 + return size; 82 + } 83 + 84 + static int test_write(const char *path, const char *buf, size_t size, 85 + off_t offset, struct fuse_file_info *fi) 86 + { 87 + size_t new_size; 88 + 89 + if (strcmp(path, test_path) != 0) 90 + return -ENOENT; 91 + 92 + if(offset > content_size) 93 + return -EINVAL; 94 + 95 + new_size = MAX(offset + size, content_size); 96 + 97 + if (new_size > content_size) 98 + content = realloc(content, new_size); 99 + 100 + content_size = new_size; 101 + 102 + if (!content) 103 + return -ENOMEM; 104 + 105 + memcpy(content + offset, buf, size); 106 + 107 + return size; 108 + } 109 + 110 + static int test_truncate(const char *path, off_t size) 111 + { 112 + if (strcmp(path, test_path) != 0) 113 + return -ENOENT; 114 + 115 + if (size == 0) { 116 + free(content); 117 + content = NULL; 118 + content_size = 0; 119 + return 0; 120 + } 121 + 122 + content = realloc(content, size); 123 + 124 + if (!content) 125 + return -ENOMEM; 126 + 127 + if (size > content_size) 128 + memset(content + content_size, 0, size - content_size); 129 + 130 + content_size = size; 131 + return 0; 132 + } 133 + 134 + static struct fuse_operations memfd_ops = { 135 + .getattr = test_getattr, 136 + .readdir = test_readdir, 137 + .open = test_open, 138 + .read = test_read, 139 + .write = test_write, 140 + .truncate = test_truncate, 141 + }; 142 + 143 + int main(int argc, char *argv[]) 144 + { 145 + return fuse_main(argc, argv, &memfd_ops, NULL); 146 + }
+140
tools/testing/selftests/filesystems/fuse/fusectl_test.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-or-later 2 + // Copyright (c) 2025 Chen Linxuan <chenlinxuan@uniontech.com> 3 + 4 + #define _GNU_SOURCE 5 + 6 + #include <errno.h> 7 + #include <fcntl.h> 8 + #include <stdio.h> 9 + #include <stdlib.h> 10 + #include <string.h> 11 + #include <sys/mount.h> 12 + #include <sys/stat.h> 13 + #include <sys/types.h> 14 + #include <sys/wait.h> 15 + #include <unistd.h> 16 + #include <dirent.h> 17 + #include <sched.h> 18 + #include <linux/limits.h> 19 + 20 + #include "../../kselftest_harness.h" 21 + 22 + #define FUSECTL_MOUNTPOINT "/sys/fs/fuse/connections" 23 + #define FUSE_MOUNTPOINT "/tmp/fuse_mnt_XXXXXX" 24 + #define FUSE_DEVICE "/dev/fuse" 25 + #define FUSECTL_TEST_VALUE "1" 26 + 27 + static void write_file(struct __test_metadata *const _metadata, 28 + const char *path, const char *val) 29 + { 30 + int fd = open(path, O_WRONLY); 31 + size_t len = strlen(val); 32 + 33 + ASSERT_GE(fd, 0); 34 + ASSERT_EQ(write(fd, val, len), len); 35 + ASSERT_EQ(close(fd), 0); 36 + } 37 + 38 + FIXTURE(fusectl){ 39 + char fuse_mountpoint[sizeof(FUSE_MOUNTPOINT)]; 40 + int connection; 41 + }; 42 + 43 + FIXTURE_SETUP(fusectl) 44 + { 45 + const char *fuse_mnt_prog = "./fuse_mnt"; 46 + int status, pid; 47 + struct stat statbuf; 48 + uid_t uid = getuid(); 49 + gid_t gid = getgid(); 50 + char buf[32]; 51 + 52 + /* Setup userns */ 53 + ASSERT_EQ(unshare(CLONE_NEWNS|CLONE_NEWUSER), 0); 54 + sprintf(buf, "0 %d 1", uid); 55 + write_file(_metadata, "/proc/self/uid_map", buf); 56 + write_file(_metadata, "/proc/self/setgroups", "deny"); 57 + sprintf(buf, "0 %d 1", gid); 58 + write_file(_metadata, "/proc/self/gid_map", buf); 59 + ASSERT_EQ(mount("", "/", NULL, MS_REC|MS_PRIVATE, NULL), 0); 60 + 61 + strcpy(self->fuse_mountpoint, FUSE_MOUNTPOINT); 62 + 63 + if (!mkdtemp(self->fuse_mountpoint)) 64 + SKIP(return, 65 + "Failed to create FUSE mountpoint %s", 66 + strerror(errno)); 67 + 68 + if (access(FUSECTL_MOUNTPOINT, F_OK)) 69 + SKIP(return, 70 + "FUSE control filesystem not mounted"); 71 + 72 + pid = fork(); 73 + if (pid < 0) 74 + SKIP(return, 75 + "Failed to fork FUSE daemon process: %s", 76 + strerror(errno)); 77 + 78 + if (pid == 0) { 79 + execlp(fuse_mnt_prog, fuse_mnt_prog, self->fuse_mountpoint, NULL); 80 + exit(errno); 81 + } 82 + 83 + waitpid(pid, &status, 0); 84 + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { 85 + SKIP(return, 86 + "Failed to start FUSE daemon %s", 87 + strerror(WEXITSTATUS(status))); 88 + } 89 + 90 + if (stat(self->fuse_mountpoint, &statbuf)) 91 + SKIP(return, 92 + "Failed to stat FUSE mountpoint %s", 93 + strerror(errno)); 94 + 95 + self->connection = statbuf.st_dev; 96 + } 97 + 98 + FIXTURE_TEARDOWN(fusectl) 99 + { 100 + umount2(self->fuse_mountpoint, MNT_DETACH); 101 + rmdir(self->fuse_mountpoint); 102 + } 103 + 104 + TEST_F(fusectl, abort) 105 + { 106 + char path_buf[PATH_MAX]; 107 + int abort_fd, test_fd, ret; 108 + 109 + sprintf(path_buf, "/sys/fs/fuse/connections/%d/abort", self->connection); 110 + 111 + ASSERT_EQ(0, access(path_buf, F_OK)); 112 + 113 + abort_fd = open(path_buf, O_WRONLY); 114 + ASSERT_GE(abort_fd, 0); 115 + 116 + sprintf(path_buf, "%s/test", self->fuse_mountpoint); 117 + 118 + test_fd = open(path_buf, O_RDWR); 119 + ASSERT_GE(test_fd, 0); 120 + 121 + ret = read(test_fd, path_buf, sizeof(path_buf)); 122 + ASSERT_EQ(ret, 0); 123 + 124 + ret = write(test_fd, "test", sizeof("test")); 125 + ASSERT_EQ(ret, sizeof("test")); 126 + 127 + ret = lseek(test_fd, 0, SEEK_SET); 128 + ASSERT_GE(ret, 0); 129 + 130 + ret = write(abort_fd, FUSECTL_TEST_VALUE, sizeof(FUSECTL_TEST_VALUE)); 131 + ASSERT_GT(ret, 0); 132 + 133 + close(abort_fd); 134 + 135 + ret = read(test_fd, path_buf, sizeof(path_buf)); 136 + ASSERT_EQ(ret, -1); 137 + ASSERT_EQ(errno, ENOTCONN); 138 + } 139 + 140 + TEST_HARNESS_MAIN