| // 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 <sched.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" | |
| static void write_file(struct __test_metadata *const _metadata, | |
| const char *path, const char *val) | |
| { | |
| int fd = open(path, O_WRONLY); | |
| size_t len = strlen(val); | |
| ASSERT_GE(fd, 0); | |
| ASSERT_EQ(write(fd, val, len), len); | |
| ASSERT_EQ(close(fd), 0); | |
| } | |
| 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; | |
| uid_t uid = getuid(); | |
| gid_t gid = getgid(); | |
| char buf[32]; | |
| /* Setup userns */ | |
| ASSERT_EQ(unshare(CLONE_NEWNS|CLONE_NEWUSER), 0); | |
| sprintf(buf, "0 %d 1", uid); | |
| write_file(_metadata, "/proc/self/uid_map", buf); | |
| write_file(_metadata, "/proc/self/setgroups", "deny"); | |
| sprintf(buf, "0 %d 1", gid); | |
| write_file(_metadata, "/proc/self/gid_map", buf); | |
| ASSERT_EQ(mount("", "/", NULL, MS_REC|MS_PRIVATE, NULL), 0); | |
| 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) | |
| { | |
| umount2(self->fuse_mountpoint, MNT_DETACH); | |
| 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_EQ(ret, -1); | |
| ASSERT_EQ(errno, ENOTCONN); | |
| } | |
| TEST_HARNESS_MAIN |