root/tools/testing/selftests/filesystems/fuse/fusectl_test.c
// 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