| // 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 |