| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Copyright 2021 Google LLC | |
| */ | |
| #include "test_fuse.h" | |
| #include <errno.h> | |
| #include <fcntl.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <unistd.h> | |
| #include <sys/mount.h> | |
| #include <sys/stat.h> | |
| #include <sys/wait.h> | |
| #include <linux/unistd.h> | |
| #include <include/uapi/linux/fuse.h> | |
| #include <include/uapi/linux/bpf.h> | |
| bool user_messages; | |
| bool kernel_messages; | |
| static int display_trace(void) | |
| { | |
| int pid = -1; | |
| int tp = -1; | |
| char c; | |
| ssize_t bytes_read; | |
| static char line[256] = {0}; | |
| if (!kernel_messages) | |
| return TEST_SUCCESS; | |
| TEST(pid = fork(), pid != -1); | |
| if (pid != 0) | |
| return pid; | |
| TESTEQUAL(tracing_on(), 0); | |
| TEST(tp = s_open(s_path(tracing_folder(), s("trace_pipe")), | |
| O_RDONLY | O_CLOEXEC), tp != -1); | |
| for (;;) { | |
| TEST(bytes_read = read(tp, &c, sizeof(c)), | |
| bytes_read == 1); | |
| if (c == '\n') { | |
| printf("%s\n", line); | |
| line[0] = 0; | |
| } else | |
| sprintf(line + strlen(line), "%c", c); | |
| } | |
| out: | |
| if (pid == 0) { | |
| close(tp); | |
| exit(TEST_FAILURE); | |
| } | |
| return pid; | |
| } | |
| static const char *fuse_opcode_to_string(int opcode) | |
| { | |
| switch (opcode & FUSE_OPCODE_FILTER) { | |
| case FUSE_LOOKUP: | |
| return "FUSE_LOOKUP"; | |
| case FUSE_FORGET: | |
| return "FUSE_FORGET"; | |
| case FUSE_GETATTR: | |
| return "FUSE_GETATTR"; | |
| case FUSE_SETATTR: | |
| return "FUSE_SETATTR"; | |
| case FUSE_READLINK: | |
| return "FUSE_READLINK"; | |
| case FUSE_SYMLINK: | |
| return "FUSE_SYMLINK"; | |
| case FUSE_MKNOD: | |
| return "FUSE_MKNOD"; | |
| case FUSE_MKDIR: | |
| return "FUSE_MKDIR"; | |
| case FUSE_UNLINK: | |
| return "FUSE_UNLINK"; | |
| case FUSE_RMDIR: | |
| return "FUSE_RMDIR"; | |
| case FUSE_RENAME: | |
| return "FUSE_RENAME"; | |
| case FUSE_LINK: | |
| return "FUSE_LINK"; | |
| case FUSE_OPEN: | |
| return "FUSE_OPEN"; | |
| case FUSE_READ: | |
| return "FUSE_READ"; | |
| case FUSE_WRITE: | |
| return "FUSE_WRITE"; | |
| case FUSE_STATFS: | |
| return "FUSE_STATFS"; | |
| case FUSE_RELEASE: | |
| return "FUSE_RELEASE"; | |
| case FUSE_FSYNC: | |
| return "FUSE_FSYNC"; | |
| case FUSE_SETXATTR: | |
| return "FUSE_SETXATTR"; | |
| case FUSE_GETXATTR: | |
| return "FUSE_GETXATTR"; | |
| case FUSE_LISTXATTR: | |
| return "FUSE_LISTXATTR"; | |
| case FUSE_REMOVEXATTR: | |
| return "FUSE_REMOVEXATTR"; | |
| case FUSE_FLUSH: | |
| return "FUSE_FLUSH"; | |
| case FUSE_INIT: | |
| return "FUSE_INIT"; | |
| case FUSE_OPENDIR: | |
| return "FUSE_OPENDIR"; | |
| case FUSE_READDIR: | |
| return "FUSE_READDIR"; | |
| case FUSE_RELEASEDIR: | |
| return "FUSE_RELEASEDIR"; | |
| case FUSE_FSYNCDIR: | |
| return "FUSE_FSYNCDIR"; | |
| case FUSE_GETLK: | |
| return "FUSE_GETLK"; | |
| case FUSE_SETLK: | |
| return "FUSE_SETLK"; | |
| case FUSE_SETLKW: | |
| return "FUSE_SETLKW"; | |
| case FUSE_ACCESS: | |
| return "FUSE_ACCESS"; | |
| case FUSE_CREATE: | |
| return "FUSE_CREATE"; | |
| case FUSE_INTERRUPT: | |
| return "FUSE_INTERRUPT"; | |
| case FUSE_BMAP: | |
| return "FUSE_BMAP"; | |
| case FUSE_DESTROY: | |
| return "FUSE_DESTROY"; | |
| case FUSE_IOCTL: | |
| return "FUSE_IOCTL"; | |
| case FUSE_POLL: | |
| return "FUSE_POLL"; | |
| case FUSE_NOTIFY_REPLY: | |
| return "FUSE_NOTIFY_REPLY"; | |
| case FUSE_BATCH_FORGET: | |
| return "FUSE_BATCH_FORGET"; | |
| case FUSE_FALLOCATE: | |
| return "FUSE_FALLOCATE"; | |
| case FUSE_READDIRPLUS: | |
| return "FUSE_READDIRPLUS"; | |
| case FUSE_RENAME2: | |
| return "FUSE_RENAME2"; | |
| case FUSE_LSEEK: | |
| return "FUSE_LSEEK"; | |
| case FUSE_COPY_FILE_RANGE: | |
| return "FUSE_COPY_FILE_RANGE"; | |
| case FUSE_SETUPMAPPING: | |
| return "FUSE_SETUPMAPPING"; | |
| case FUSE_REMOVEMAPPING: | |
| return "FUSE_REMOVEMAPPING"; | |
| //case FUSE_SYNCFS: | |
| // return "FUSE_SYNCFS"; | |
| case CUSE_INIT: | |
| return "CUSE_INIT"; | |
| case CUSE_INIT_BSWAP_RESERVED: | |
| return "CUSE_INIT_BSWAP_RESERVED"; | |
| case FUSE_INIT_BSWAP_RESERVED: | |
| return "FUSE_INIT_BSWAP_RESERVED"; | |
| } | |
| return "?"; | |
| } | |
| static int parse_options(int argc, char *const *argv) | |
| { | |
| signed char c; | |
| while ((c = getopt(argc, argv, "kuv")) != -1) | |
| switch (c) { | |
| case 'v': | |
| test_options.verbose = true; | |
| break; | |
| case 'u': | |
| user_messages = true; | |
| break; | |
| case 'k': | |
| kernel_messages = true; | |
| break; | |
| default: | |
| return -EINVAL; | |
| } | |
| return 0; | |
| } | |
| int main(int argc, char *argv[]) | |
| { | |
| int result = TEST_FAILURE; | |
| int trace_pid = -1; | |
| char *mount_dir = NULL; | |
| char *src_dir = NULL; | |
| int bpf_fd = -1; | |
| int src_fd = -1; | |
| int fuse_dev = -1; | |
| struct map_relocation *map_relocations = NULL; | |
| size_t map_count = 0; | |
| int i; | |
| if (geteuid() != 0) | |
| ksft_print_msg("Not a root, might fail to mount.\n"); | |
| TESTEQUAL(parse_options(argc, argv), 0); | |
| TEST(trace_pid = display_trace(), trace_pid != -1); | |
| delete_dir_tree("fd-src", true); | |
| TEST(src_dir = setup_mount_dir("fd-src"), src_dir); | |
| delete_dir_tree("fd-dst", true); | |
| TEST(mount_dir = setup_mount_dir("fd-dst"), mount_dir); | |
| TESTEQUAL(install_elf_bpf("fd_bpf.bpf", "test_daemon", &bpf_fd, | |
| &map_relocations, &map_count), 0); | |
| TEST(src_fd = open("fd-src", O_DIRECTORY | O_RDONLY | O_CLOEXEC), | |
| src_fd != -1); | |
| TESTSYSCALL(mkdirat(src_fd, "show", 0777)); | |
| TESTSYSCALL(mkdirat(src_fd, "hide", 0777)); | |
| for (i = 0; i < map_count; ++i) | |
| if (!strcmp(map_relocations[i].name, "test_map")) { | |
| uint32_t key = 23; | |
| uint32_t value = 1234; | |
| union bpf_attr attr = { | |
| .map_fd = map_relocations[i].fd, | |
| .key = ptr_to_u64(&key), | |
| .value = ptr_to_u64(&value), | |
| .flags = BPF_ANY, | |
| }; | |
| TESTSYSCALL(syscall(__NR_bpf, BPF_MAP_UPDATE_ELEM, | |
| &attr, sizeof(attr))); | |
| } | |
| TESTEQUAL(mount_fuse(mount_dir, bpf_fd, src_fd, &fuse_dev), 0); | |
| if (fork()) | |
| return 0; | |
| for (;;) { | |
| uint8_t bytes_in[FUSE_MIN_READ_BUFFER]; | |
| uint8_t bytes_out[FUSE_MIN_READ_BUFFER] __attribute__((unused)); | |
| struct fuse_in_header *in_header = | |
| (struct fuse_in_header *)bytes_in; | |
| ssize_t res = read(fuse_dev, bytes_in, sizeof(bytes_in)); | |
| if (res == -1) | |
| break; | |
| switch (in_header->opcode) { | |
| case FUSE_LOOKUP | FUSE_PREFILTER: { | |
| char *name = (char *)(bytes_in + sizeof(*in_header)); | |
| if (user_messages) | |
| printf("Lookup %s\n", name); | |
| if (!strcmp(name, "hide")) | |
| TESTFUSEOUTERROR(-ENOENT); | |
| else | |
| TESTFUSEOUTREAD(name, strlen(name) + 1); | |
| break; | |
| } | |
| default: | |
| if (user_messages) { | |
| printf("opcode is %d (%s)\n", in_header->opcode, | |
| fuse_opcode_to_string( | |
| in_header->opcode)); | |
| } | |
| break; | |
| } | |
| } | |
| result = TEST_SUCCESS; | |
| out: | |
| for (i = 0; i < map_count; ++i) { | |
| free(map_relocations[i].name); | |
| close(map_relocations[i].fd); | |
| } | |
| free(map_relocations); | |
| umount2(mount_dir, MNT_FORCE); | |
| delete_dir_tree(mount_dir, true); | |
| free(mount_dir); | |
| delete_dir_tree(src_dir, true); | |
| free(src_dir); | |
| if (trace_pid != -1) | |
| kill(trace_pid, SIGKILL); | |
| return result; | |
| } |