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