Adds basic selftests for fuse. These check that you can add fuse_op programs, and perform basic operations Signed-off-by: Daniel Rosenberg <drosen@xxxxxxxxxx> Signed-off-by: Paul Lawrence <paullawrence@xxxxxxxxxx> Signed-off-by: Alessio Balsini <balsini@xxxxxxxxxx> --- .../selftests/filesystems/fuse/.gitignore | 2 + .../selftests/filesystems/fuse/Makefile | 188 ++ .../testing/selftests/filesystems/fuse/OWNERS | 2 + .../selftests/filesystems/fuse/bpf_common.h | 51 + .../selftests/filesystems/fuse/bpf_loader.c | 597 ++++ .../testing/selftests/filesystems/fuse/fd.txt | 21 + .../selftests/filesystems/fuse/fd_bpf.bpf.c | 397 +++ .../selftests/filesystems/fuse/fuse_daemon.c | 300 ++ .../selftests/filesystems/fuse/fuse_test.c | 2412 +++++++++++++++++ .../selftests/filesystems/fuse/test.bpf.c | 996 +++++++ .../filesystems/fuse/test_framework.h | 172 ++ .../selftests/filesystems/fuse/test_fuse.h | 494 ++++ 12 files changed, 5632 insertions(+) create mode 100644 tools/testing/selftests/filesystems/fuse/.gitignore create mode 100644 tools/testing/selftests/filesystems/fuse/Makefile create mode 100644 tools/testing/selftests/filesystems/fuse/OWNERS create mode 100644 tools/testing/selftests/filesystems/fuse/bpf_common.h create mode 100644 tools/testing/selftests/filesystems/fuse/bpf_loader.c create mode 100644 tools/testing/selftests/filesystems/fuse/fd.txt create mode 100644 tools/testing/selftests/filesystems/fuse/fd_bpf.bpf.c create mode 100644 tools/testing/selftests/filesystems/fuse/fuse_daemon.c create mode 100644 tools/testing/selftests/filesystems/fuse/fuse_test.c create mode 100644 tools/testing/selftests/filesystems/fuse/test.bpf.c create mode 100644 tools/testing/selftests/filesystems/fuse/test_framework.h create mode 100644 tools/testing/selftests/filesystems/fuse/test_fuse.h diff --git a/tools/testing/selftests/filesystems/fuse/.gitignore b/tools/testing/selftests/filesystems/fuse/.gitignore new file mode 100644 index 000000000000..3ee9a27fe66a --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/.gitignore @@ -0,0 +1,2 @@ +fuse_test +*.raw diff --git a/tools/testing/selftests/filesystems/fuse/Makefile b/tools/testing/selftests/filesystems/fuse/Makefile new file mode 100644 index 000000000000..b2df4dec0651 --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/Makefile @@ -0,0 +1,188 @@ +# SPDX-License-Identifier: GPL-2.0 +include ../../../../build/Build.include +include ../../../../scripts/Makefile.arch +include ../../../../scripts/Makefile.include + +#if 0 +ifneq ($(LLVM),) +ifneq ($(filter %/,$(LLVM)),) +LLVM_PREFIX := $(LLVM) +else ifneq ($(filter -%,$(LLVM)),) +LLVM_SUFFIX := $(LLVM) +endif + +CLANG_TARGET_FLAGS_arm := arm-linux-gnueabi +CLANG_TARGET_FLAGS_arm64 := aarch64-linux-gnu +CLANG_TARGET_FLAGS_hexagon := hexagon-linux-musl +CLANG_TARGET_FLAGS_m68k := m68k-linux-gnu +CLANG_TARGET_FLAGS_mips := mipsel-linux-gnu +CLANG_TARGET_FLAGS_powerpc := powerpc64le-linux-gnu +CLANG_TARGET_FLAGS_riscv := riscv64-linux-gnu +CLANG_TARGET_FLAGS_s390 := s390x-linux-gnu +CLANG_TARGET_FLAGS_x86 := x86_64-linux-gnu +CLANG_TARGET_FLAGS := $(CLANG_TARGET_FLAGS_$(ARCH)) +#endif + +ifeq ($(CROSS_COMPILE),) +ifeq ($(CLANG_TARGET_FLAGS),) +$(error Specify CROSS_COMPILE or add '--target=' option to lib.mk +else +CLANG_FLAGS += --target=$(CLANG_TARGET_FLAGS) +endif # CLANG_TARGET_FLAGS +else +CLANG_FLAGS += --target=$(notdir $(CROSS_COMPILE:%-=%)) +endif # CROSS_COMPILE + +CC := $(LLVM_PREFIX)clang$(LLVM_SUFFIX) $(CLANG_FLAGS) -fintegrated-as +else +CC := $(CROSS_COMPILE)gcc +endif # LLVM + +CURDIR := $(abspath .) +TOOLSDIR := $(abspath ../../../..) +LIBDIR := $(TOOLSDIR)/lib +BPFDIR := $(LIBDIR)/bpf +TOOLSINCDIR := $(TOOLSDIR)/include +BPFTOOLDIR := $(TOOLSDIR)/bpf/bpftool +APIDIR := $(TOOLSINCDIR)/uapi +GENDIR := $(abspath ../../../../../include/generated) +GENHDR := $(GENDIR)/autoconf.h +SELFTESTS:=$(TOOLSDIR)/testing/selftests/ + +LDLIBS := -lpthread -lelf -lz +TEST_GEN_PROGS := fuse_test fuse_daemon +TEST_GEN_FILES := \ + test.skel.h \ + fd.sh \ + +include ../../lib.mk + +# Put after include ../../lib.mk since that changes $(TEST_GEN_PROGS) +# Otherwise you get multiple targets, this becomes the default, and it's a mess +EXTRA_SOURCES := bpf_loader.c $(OUTPUT)/test.skel.h +$(TEST_GEN_PROGS) : $(EXTRA_SOURCES) $(BPFOBJ) + +SCRATCH_DIR := $(OUTPUT)/tools +BUILD_DIR := $(SCRATCH_DIR)/build +INCLUDE_DIR := $(SCRATCH_DIR)/include +BPFOBJ := $(BUILD_DIR)/libbpf/libbpf.a +SKEL_DIR := $(OUTPUT) +ifneq ($(CROSS_COMPILE),) +HOST_BUILD_DIR := $(BUILD_DIR)/host +HOST_SCRATCH_DIR := host-tools +HOST_INCLUDE_DIR := $(HOST_SCRATCH_DIR)/include +else +HOST_BUILD_DIR := $(BUILD_DIR) +HOST_SCRATCH_DIR := $(SCRATCH_DIR) +HOST_INCLUDE_DIR := $(INCLUDE_DIR) +endif +HOST_BPFOBJ := $(HOST_BUILD_DIR)/libbpf/libbpf.a +RESOLVE_BTFIDS := $(HOST_BUILD_DIR)/resolve_btfids/resolve_btfids +DEFAULT_BPFTOOL := $(HOST_SCRATCH_DIR)/sbin/bpftool + +VMLINUX_BTF_PATHS ?= $(if $(OUTPUT),$(OUTPUT)/../../../../../vmlinux) \ + $(if $(KBUILD_OUTPUT),$(KBUILD_OUTPUT)/vmlinux) \ + ../../../../../vmlinux \ + /sys/kernel/btf/vmlinux \ + /boot/vmlinux-$(shell uname -r) +VMLINUX_BTF ?= $(abspath $(firstword $(wildcard $(VMLINUX_BTF_PATHS)))) +ifeq ($(VMLINUX_BTF),) +$(error Cannot find a vmlinux for VMLINUX_BTF at any of "$(VMLINUX_BTF_PATHS)") +endif + +BPFTOOL ?= $(DEFAULT_BPFTOOL) + +ifneq ($(wildcard $(GENHDR)),) + GENFLAGS := -DHAVE_GENHDR +endif + +CFLAGS += -g -O2 -rdynamic -pthread -Wall -Werror $(GENFLAGS) \ + -I$(INCLUDE_DIR) -I$(GENDIR) -I$(LIBDIR) \ + -I$(TOOLSINCDIR) -I$(APIDIR) -I$(SELFTESTS) \ + -I$(SKEL_DIR) + +# Silence some warnings when compiled with clang +ifneq ($(LLVM),) +CFLAGS += -Wno-unused-command-line-argument +endif + +#LDFLAGS = -lelf -lz + +IS_LITTLE_ENDIAN = $(shell $(CC) -dM -E - </dev/null | \ + grep 'define __BYTE_ORDER__ __ORDER_LITTLE_ENDIAN__') + +# Get Clang's default includes on this system, as opposed to those seen by +# '-target bpf'. This fixes "missing" files on some architectures/distros, +# such as asm/byteorder.h, asm/socket.h, asm/sockios.h, sys/cdefs.h etc. +# +# Use '-idirafter': Don't interfere with include mechanics except where the +# build would have failed anyways. +define get_sys_includes +$(shell $(1) -v -E - </dev/null 2>&1 \ + | sed -n '/<...> search starts here:/,/End of search list./{ s| \(/.*\)|-idirafter \1|p }') \ +$(shell $(1) -dM -E - </dev/null | grep '__riscv_xlen ' | awk '{printf("-D__riscv_xlen=%d -D__BITS_PER_LONG=%d", $$3, $$3)}') +endef + +BPF_CFLAGS = -g -D__TARGET_ARCH_$(SRCARCH) \ + $(if $(IS_LITTLE_ENDIAN),-mlittle-endian,-mbig-endian) \ + -I$(INCLUDE_DIR) -I$(CURDIR) -I$(APIDIR) \ + -I../../../../../include \ + $(call get_sys_includes,$(CLANG)) \ + -Wno-compare-distinct-pointer-types \ + -O2 -mcpu=v3 + +# sort removes libbpf duplicates when not cross-building +MAKE_DIRS := $(sort $(BUILD_DIR)/libbpf $(HOST_BUILD_DIR)/libbpf \ + $(HOST_BUILD_DIR)/bpftool $(HOST_BUILD_DIR)/resolve_btfids \ + $(INCLUDE_DIR)) + +$(MAKE_DIRS): + $(call msg,MKDIR,,$@) + $(Q)mkdir -p $@ + +$(BPFOBJ): $(wildcard $(BPFDIR)/*.[ch] $(BPFDIR)/Makefile) \ + $(APIDIR)/linux/bpf.h \ + | $(BUILD_DIR)/libbpf + $(Q)$(MAKE) $(submake_extras) -C $(BPFDIR) OUTPUT=$(BUILD_DIR)/libbpf/ \ + EXTRA_CFLAGS='-g -O0' \ + DESTDIR=$(SCRATCH_DIR) prefix= all install_headers + +$(DEFAULT_BPFTOOL): $(wildcard $(BPFTOOLDIR)/*.[ch] $(BPFTOOLDIR)/Makefile) \ + $(HOST_BPFOBJ) | $(HOST_BUILD_DIR)/bpftool + $(Q)$(MAKE) $(submake_extras) -C $(BPFTOOLDIR) \ + ARCH= CROSS_COMPILE= CC=$(HOSTCC) LD=$(HOSTLD) \ + EXTRA_CFLAGS='-g -O0' \ + OUTPUT=$(HOST_BUILD_DIR)/bpftool/ \ + LIBBPF_OUTPUT=$(HOST_BUILD_DIR)/libbpf/ \ + LIBBPF_DESTDIR=$(HOST_SCRATCH_DIR)/ \ + prefix= DESTDIR=$(HOST_SCRATCH_DIR)/ install-bin + +$(INCLUDE_DIR)/vmlinux.h: $(VMLINUX_BTF) $(BPFTOOL) | $(INCLUDE_DIR) +ifeq ($(VMLINUX_H),) + $(call msg,GEN,,$@) + $(Q)$(BPFTOOL) btf dump file $(VMLINUX_BTF) format c > $@ +else + $(call msg,CP,,$@) + $(Q)cp "$(VMLINUX_H)" $@ +endif + +$(OUTPUT)/fuse_daemon: LDLIBS := $(HOST_BPFOBJ) $(LDLIBS) +$(OUTPUT)/fuse_test: LDLIBS := $(HOST_BPFOBJ) $(LDLIBS) + +$(OUTPUT)/%.bpf.o: %.bpf.c $(INCLUDE_DIR)/vmlinux.h \ + | $(BPFOBJ) + $(call msg,CLNG-BPF,,$@) + $(Q)$(CLANG) $(BPF_CFLAGS) -target bpf -c $< -o $@ + +$(OUTPUT)/%.skel.h: $(OUTPUT)/%.bpf.o $(BPFTOOL) + $(call msg,GEN-SKEL,,$@) + $(Q)$(BPFTOOL) gen object $(<:.o=.linked1.o) $< + $(Q)$(BPFTOOL) gen object $(<:.o=.linked2.o) $(<:.o=.linked1.o) + $(Q)$(BPFTOOL) gen object $(<:.o=.linked3.o) $(<:.o=.linked2.o) + $(Q)diff $(<:.o=.linked2.o) $(<:.o=.linked3.o) + $(Q)$(BPFTOOL) gen skeleton $(<:.o=.linked3.o) name $(notdir $(<:.bpf.o=))_bpf > $@ + $(Q)$(BPFTOOL) gen subskeleton $(<:.o=.linked3.o) name $(notdir $(<:.bpf.o=))_bpf > $(@:.skel.h=.subskel.h) + +$(OUTPUT)/fd.sh: fd.txt + cp $< $@ + chmod 755 $@ diff --git a/tools/testing/selftests/filesystems/fuse/OWNERS b/tools/testing/selftests/filesystems/fuse/OWNERS new file mode 100644 index 000000000000..5eb371e1a5a3 --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/OWNERS @@ -0,0 +1,2 @@ +# include OWNERS from the authoritative android-mainline branch +include kernel/common:android-mainline:/tools/testing/selftests/filesystems/incfs/OWNERS diff --git a/tools/testing/selftests/filesystems/fuse/bpf_common.h b/tools/testing/selftests/filesystems/fuse/bpf_common.h new file mode 100644 index 000000000000..dcf9efaef0f4 --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/bpf_common.h @@ -0,0 +1,51 @@ +// TODO: Insert description here. (generated by drosen) + +#ifndef _BPF_COMMON_H_ +#define _BPF_COMMON_H_ + +/* Return Codes for Fuse BPF programs */ +#define BPF_FUSE_CONTINUE 0 +#define BPF_FUSE_USER 1 +#define BPF_FUSE_USER_PREFILTER 2 +#define BPF_FUSE_POSTFILTER 3 +#define BPF_FUSE_USER_POSTFILTER 4 + +enum fuse_bpf_type { + FUSE_ENTRY_BACKING = 1, + FUSE_ENTRY_BPF = 2, + FUSE_ENTRY_REMOVE_BACKING = 3, + FUSE_ENTRY_REMOVE_BPF = 4, +}; + +#define BPF_FUSE_NAME_MAX 15 +struct fuse_bpf_entry_out { + uint32_t entry_type; + uint32_t unused; + union { + struct { + uint64_t unused2; + uint64_t fd; + }; + char name[BPF_FUSE_NAME_MAX + 1]; + }; +}; + +/* Op Code Filter values for BPF Programs */ +#define FUSE_OPCODE_FILTER 0x0ffff +#define FUSE_PREFILTER 0x10000 +#define FUSE_POSTFILTER 0x20000 + +#define BPF_FUSE_NAME_MAX 15 + +#define BPF_STRUCT_OPS(type, name, args...) \ +SEC("struct_ops/"#name) \ +type BPF_PROG(name, ##args) + +/* available kfuncs for fuse_bpf */ +extern uint32_t bpf_fuse_return_len(struct fuse_buffer *ptr) __ksym; +extern void bpf_fuse_get_rw_dynptr(struct fuse_buffer *buffer, struct bpf_dynptr *dynptr, u64 size, bool copy) __ksym; +extern void bpf_fuse_get_ro_dynptr(const struct fuse_buffer *buffer, struct bpf_dynptr *dynptr) __ksym; +extern void *bpf_dynptr_slice(const struct bpf_dynptr *ptr, u32 offset, void *buffer, u32 buffer__szk) __ksym; +extern void *bpf_dynptr_slice_rdwr(const struct bpf_dynptr *ptr, u32 offset, void *buffer, u32 buffer__szk) __ksym; + +#endif /* _BPF_COMMON_H_ */ diff --git a/tools/testing/selftests/filesystems/fuse/bpf_loader.c b/tools/testing/selftests/filesystems/fuse/bpf_loader.c new file mode 100644 index 000000000000..ebcced7f9430 --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/bpf_loader.c @@ -0,0 +1,597 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2021 Google LLC + */ + +#include "test_fuse.h" + +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <gelf.h> +#include <libelf.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <sys/mount.h> +#include <sys/stat.h> +#include <sys/statfs.h> +#include <sys/xattr.h> + +#include <linux/unistd.h> + +#include <uapi/linux/fuse.h> +#include <uapi/linux/bpf.h> + +struct _test_options test_options; + +struct s s(const char *s1) +{ + struct s s = {0}; + + if (!s1) + return s; + + s.s = malloc(strlen(s1) + 1); + if (!s.s) + return s; + + strcpy(s.s, s1); + return s; +} + +struct s sn(const char *s1, const char *s2) +{ + struct s s = {0}; + + if (!s1) + return s; + + s.s = malloc(s2 - s1 + 1); + if (!s.s) + return s; + + strncpy(s.s, s1, s2 - s1); + s.s[s2 - s1] = 0; + return s; +} + +int s_cmp(struct s s1, struct s s2) +{ + int result = -1; + + if (!s1.s || !s2.s) + goto out; + result = strcmp(s1.s, s2.s); +out: + free(s1.s); + free(s2.s); + return result; +} + +struct s s_cat(struct s s1, struct s s2) +{ + struct s s = {0}; + + if (!s1.s || !s2.s) + goto out; + + s.s = malloc(strlen(s1.s) + strlen(s2.s) + 1); + if (!s.s) + goto out; + + strcpy(s.s, s1.s); + strcat(s.s, s2.s); +out: + free(s1.s); + free(s2.s); + return s; +} + +struct s s_splitleft(struct s s1, char c) +{ + struct s s = {0}; + char *split; + + if (!s1.s) + return s; + + split = strchr(s1.s, c); + if (split) + s = sn(s1.s, split); + + free(s1.s); + return s; +} + +struct s s_splitright(struct s s1, char c) +{ + struct s s2 = {0}; + char *split; + + if (!s1.s) + return s2; + + split = strchr(s1.s, c); + if (split) + s2 = s(split + 1); + + free(s1.s); + return s2; +} + +struct s s_word(struct s s1, char c, size_t n) +{ + while (n--) + s1 = s_splitright(s1, c); + return s_splitleft(s1, c); +} + +struct s s_path(struct s s1, struct s s2) +{ + return s_cat(s_cat(s1, s("/")), s2); +} + +struct s s_pathn(size_t n, struct s s1, ...) +{ + va_list argp; + + va_start(argp, s1); + while (--n) + s1 = s_path(s1, va_arg(argp, struct s)); + va_end(argp); + return s1; +} + +int s_link(struct s src_pathname, struct s dst_pathname) +{ + int res; + + if (src_pathname.s && dst_pathname.s) { + res = link(src_pathname.s, dst_pathname.s); + } else { + res = -1; + errno = ENOMEM; + } + + free(src_pathname.s); + free(dst_pathname.s); + return res; +} + +int s_symlink(struct s src_pathname, struct s dst_pathname) +{ + int res; + + if (src_pathname.s && dst_pathname.s) { + res = symlink(src_pathname.s, dst_pathname.s); + } else { + res = -1; + errno = ENOMEM; + } + + free(src_pathname.s); + free(dst_pathname.s); + return res; +} + + +int s_mkdir(struct s pathname, mode_t mode) +{ + int res; + + if (!pathname.s) { + errno = ENOMEM; + return -1; + } + + res = mkdir(pathname.s, mode); + free(pathname.s); + return res; +} + +int s_rmdir(struct s pathname) +{ + int res; + + if (!pathname.s) { + errno = ENOMEM; + return -1; + } + + res = rmdir(pathname.s); + free(pathname.s); + return res; +} + +int s_unlink(struct s pathname) +{ + int res; + + if (!pathname.s) { + errno = ENOMEM; + return -1; + } + + res = unlink(pathname.s); + free(pathname.s); + return res; +} + +int s_open(struct s pathname, int flags, ...) +{ + va_list ap; + int res; + + va_start(ap, flags); + if (!pathname.s) { + errno = ENOMEM; + return -1; + } + + if (flags & (O_CREAT | O_TMPFILE)) + res = open(pathname.s, flags, va_arg(ap, mode_t)); + else + res = open(pathname.s, flags); + + free(pathname.s); + va_end(ap); + return res; +} + +int s_openat(int dirfd, struct s pathname, int flags, ...) +{ + va_list ap; + int res; + + va_start(ap, flags); + if (!pathname.s) { + errno = ENOMEM; + return -1; + } + + if (flags & (O_CREAT | O_TMPFILE)) + res = openat(dirfd, pathname.s, flags, va_arg(ap, mode_t)); + else + res = openat(dirfd, pathname.s, flags); + + free(pathname.s); + va_end(ap); + return res; +} + +int s_creat(struct s pathname, mode_t mode) +{ + int res; + + if (!pathname.s) { + errno = ENOMEM; + return -1; + } + + res = open(pathname.s, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, mode); + free(pathname.s); + return res; +} + +int s_mkfifo(struct s pathname, mode_t mode) +{ + int res; + + if (!pathname.s) { + errno = ENOMEM; + return -1; + } + + res = mknod(pathname.s, S_IFIFO | mode, 0); + free(pathname.s); + return res; +} + +int s_stat(struct s pathname, struct stat *st) +{ + int res; + + if (!pathname.s) { + errno = ENOMEM; + return -1; + } + + res = stat(pathname.s, st); + free(pathname.s); + return res; +} + +int s_statfs(struct s pathname, struct statfs *st) +{ + int res; + + if (!pathname.s) { + errno = ENOMEM; + return -1; + } + + res = statfs(pathname.s, st); + free(pathname.s); + return res; +} + +DIR *s_opendir(struct s pathname) +{ + DIR *res; + + res = opendir(pathname.s); + free(pathname.s); + return res; +} + +int s_getxattr(struct s pathname, const char name[], void *value, size_t size, + ssize_t *ret_size) +{ + if (!pathname.s) { + errno = ENOMEM; + return -1; + } + + *ret_size = getxattr(pathname.s, name, value, size); + free(pathname.s); + return *ret_size >= 0 ? 0 : -1; +} + +int s_listxattr(struct s pathname, void *list, size_t size, ssize_t *ret_size) +{ + if (!pathname.s) { + errno = ENOMEM; + return -1; + } + + *ret_size = listxattr(pathname.s, list, size); + free(pathname.s); + return *ret_size >= 0 ? 0 : -1; +} + +int s_setxattr(struct s pathname, const char name[], const void *value, size_t size, int flags) +{ + int res; + + if (!pathname.s) { + errno = ENOMEM; + return -1; + } + + res = setxattr(pathname.s, name, value, size, flags); + free(pathname.s); + return res; +} + +int s_removexattr(struct s pathname, const char name[]) +{ + int res; + + if (!pathname.s) { + errno = ENOMEM; + return -1; + } + + res = removexattr(pathname.s, name); + free(pathname.s); + return res; +} + +int s_rename(struct s oldpathname, struct s newpathname) +{ + int res; + + if (!oldpathname.s || !newpathname.s) { + errno = ENOMEM; + return -1; + } + + res = rename(oldpathname.s, newpathname.s); + free(oldpathname.s); + free(newpathname.s); + return res; +} + +int s_fuse_attr(struct s pathname, struct fuse_attr *fuse_attr_out) +{ + + struct stat st; + int result = TEST_FAILURE; + + TESTSYSCALL(s_stat(pathname, &st)); + + fuse_attr_out->ino = st.st_ino; + fuse_attr_out->mode = st.st_mode; + fuse_attr_out->nlink = st.st_nlink; + fuse_attr_out->uid = st.st_uid; + fuse_attr_out->gid = st.st_gid; + fuse_attr_out->rdev = st.st_rdev; + fuse_attr_out->size = st.st_size; + fuse_attr_out->blksize = st.st_blksize; + fuse_attr_out->blocks = st.st_blocks; + fuse_attr_out->atime = st.st_atime; + fuse_attr_out->mtime = st.st_mtime; + fuse_attr_out->ctime = st.st_ctime; + fuse_attr_out->atimensec = UINT32_MAX; + fuse_attr_out->mtimensec = UINT32_MAX; + fuse_attr_out->ctimensec = UINT32_MAX; + + result = TEST_SUCCESS; +out: + return result; +} + +struct s tracing_folder(void) +{ + struct s trace = {0}; + FILE *mounts = NULL; + char *line = NULL; + size_t size = 0; + + TEST(mounts = fopen("/proc/mounts", "re"), mounts); + while (getline(&line, &size, mounts) != -1) { + if (!s_cmp(s_word(sn(line, line + size), ' ', 2), + s("tracefs"))) { + trace = s_word(sn(line, line + size), ' ', 1); + break; + } + + if (!s_cmp(s_word(sn(line, line + size), ' ', 2), s("debugfs"))) + trace = s_path(s_word(sn(line, line + size), ' ', 1), + s("tracing")); + } + +out: + free(line); + fclose(mounts); + return trace; +} + +int tracing_on(void) +{ + int result = TEST_FAILURE; + int tracing_on = -1; + + TEST(tracing_on = s_open(s_path(tracing_folder(), s("tracing_on")), + O_WRONLY | O_CLOEXEC), + tracing_on != -1); + TESTEQUAL(write(tracing_on, "1", 1), 1); + result = TEST_SUCCESS; +out: + close(tracing_on); + return result; +} + +char *concat_file_name(const char *dir, const char *file) +{ + char full_name[FILENAME_MAX] = ""; + + if (snprintf(full_name, ARRAY_SIZE(full_name), "%s/%s", dir, file) < 0) + return NULL; + return strdup(full_name); +} + +char *setup_mount_dir(const char *name) +{ + struct stat st; + char *current_dir = getcwd(NULL, 0); + char *mount_dir = concat_file_name(current_dir, name); + + free(current_dir); + if (stat(mount_dir, &st) == 0) { + if (S_ISDIR(st.st_mode)) + return mount_dir; + + ksft_print_msg("%s is a file, not a dir.\n", mount_dir); + return NULL; + } + + if (mkdir(mount_dir, 0777)) { + ksft_print_msg("Can't create mount dir."); + return NULL; + } + + return mount_dir; +} + +int delete_dir_tree(const char *dir_path, bool remove_root) +{ + DIR *dir = NULL; + struct dirent *dp; + int result = 0; + + dir = opendir(dir_path); + if (!dir) { + result = -errno; + goto out; + } + + while ((dp = readdir(dir))) { + char *full_path; + + if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) + continue; + + full_path = concat_file_name(dir_path, dp->d_name); + if (dp->d_type == DT_DIR) + result = delete_dir_tree(full_path, true); + else + result = unlink(full_path); + free(full_path); + if (result) + goto out; + } + +out: + if (dir) + closedir(dir); + if (!result && remove_root) + rmdir(dir_path); + return result; +} + +static int mount_fuse_maybe_init(const char *mount_dir, const char *bpf_name, int dir_fd, + int *fuse_dev_ptr, bool init) +{ + int result = TEST_FAILURE; + int fuse_dev = -1; + char options[FILENAME_MAX]; + uint8_t bytes_in[FUSE_MIN_READ_BUFFER]; + uint8_t bytes_out[FUSE_MIN_READ_BUFFER]; + + DECL_FUSE_IN(init); + + TEST(fuse_dev = open("/dev/fuse", O_RDWR | O_CLOEXEC), fuse_dev != -1); + snprintf(options, FILENAME_MAX, "fd=%d,user_id=0,group_id=0,rootmode=0040000", + fuse_dev); + if (bpf_name != NULL) + snprintf(options + strlen(options), + sizeof(options) - strlen(options), + ",root_bpf=%s", bpf_name); + if (dir_fd != -1) + snprintf(options + strlen(options), + sizeof(options) - strlen(options), + ",root_dir=%d", dir_fd); + TESTSYSCALL(mount("ABC", mount_dir, "fuse", 0, options)); + + if (init) { + TESTFUSEIN(FUSE_INIT, init_in); + TESTEQUAL(init_in->major, FUSE_KERNEL_VERSION); + TESTEQUAL(init_in->minor, FUSE_KERNEL_MINOR_VERSION); + TESTFUSEOUT1(fuse_init_out, ((struct fuse_init_out) { + .major = FUSE_KERNEL_VERSION, + .minor = FUSE_KERNEL_MINOR_VERSION, + .max_readahead = 4096, + .flags = 0, + .max_background = 0, + .congestion_threshold = 0, + .max_write = 4096, + .time_gran = 1000, + .max_pages = 12, + .map_alignment = 4096, + })); + } + + *fuse_dev_ptr = fuse_dev; + fuse_dev = -1; + result = TEST_SUCCESS; +out: + close(fuse_dev); + return result; +} + +int mount_fuse(const char *mount_dir, const char * bpf_name, int dir_fd, int *fuse_dev_ptr) +{ + return mount_fuse_maybe_init(mount_dir, bpf_name, dir_fd, fuse_dev_ptr, + true); +} + +int mount_fuse_no_init(const char *mount_dir, const char * bpf_name, int dir_fd, + int *fuse_dev_ptr) +{ + return mount_fuse_maybe_init(mount_dir, bpf_name, dir_fd, fuse_dev_ptr, + false); +} + diff --git a/tools/testing/selftests/filesystems/fuse/fd.txt b/tools/testing/selftests/filesystems/fuse/fd.txt new file mode 100644 index 000000000000..15ce77180d55 --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/fd.txt @@ -0,0 +1,21 @@ +fuse_daemon $* +cd fd-dst +ls +cd show +ls +fsstress -s 123 -d . -p 4 -n 100 -l5 +echo test > wibble +ls +cat wibble +fallocate -l 1000 wobble +mkdir testdir +mkdir tmpdir +rmdir tmpdir +touch tmp +mv tmp tmp2 +rm tmp2 + +# FUSE_LINK +echo "ln_src contents" > ln_src +ln ln_src ln_link +cat ln_link diff --git a/tools/testing/selftests/filesystems/fuse/fd_bpf.bpf.c b/tools/testing/selftests/filesystems/fuse/fd_bpf.bpf.c new file mode 100644 index 000000000000..9b6377b96a6e --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/fd_bpf.bpf.c @@ -0,0 +1,397 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +// Copyright (c) 2021 Google LLC + +//#define __EXPORTED_HEADERS__ +//#define __KERNEL__ + +//#include <uapi/linux/bpf.h> +//#include <linux/fuse.h> + +#include "vmlinux.h" +#include <linux/errno.h> +#include <linux/types.h> +#include <bpf/bpf_tracing.h> +#include <bpf/bpf_helpers.h> + +#include "bpf_common.h" + +char _license[] SEC("license") = "GPL"; + +#if 0 +struct fuse_bpf_map { + int map_type; + int key_size; + int value_size; + int max_entries; +}; +SEC("dummy") + +inline int strcmp(const char *a, const char *b) +{ + int i; + + for (i = 0; i < __builtin_strlen(b) + 1; ++i) + if (a[i] != b[i]) + return -1; + + return 0; +} + +SEC("maps") struct fuse_bpf_map test_map = { + BPF_MAP_TYPE_ARRAY, + sizeof(uint32_t), + sizeof(uint32_t), + 1000, +}; + +SEC("maps") struct fuse_bpf_map test_map2 = { + BPF_MAP_TYPE_HASH, + sizeof(uint32_t), + sizeof(uint64_t), + 76, +}; + +SEC("test_daemon") + +int trace_daemon(struct __bpf_fuse_args *fa) +{ + uint64_t uid_gid = bpf_get_current_uid_gid(); + uint32_t uid = uid_gid & 0xffffffff; + uint64_t pid_tgid = bpf_get_current_pid_tgid(); + uint32_t pid = pid_tgid & 0xffffffff; + uint32_t key = 23; + uint32_t *pvalue; + + + pvalue = bpf_map_lookup_elem(&test_map, &key); + if (pvalue) { + uint32_t value = *pvalue; + + bpf_printk("pid %u uid %u value %u", pid, uid, value); + value++; + bpf_map_update_elem(&test_map, &key, &value, BPF_ANY); + } + + switch (fa->opcode) { +#endif +BPF_STRUCT_OPS(uint32_t, trace_access_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_access_in *in) +{ + bpf_printk("Access: %d", meta->nodeid); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_getattr_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_getattr_in *in) +{ + bpf_printk("Get Attr %d", in->fh); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_setattr_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_setattr_in *in) +{ + bpf_printk("Set Attr %d", in->fh); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_opendir_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_open_in *in) +{ + bpf_printk("Open Dir: %d", meta->nodeid); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_readdir_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_read_in *in) +{ + bpf_printk("Read Dir: fh: %lu", in->fh, in->offset); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_lookup_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char name_buf[255]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + bpf_dynptr_read(name_buf, 255, &name_ptr, 0, 0); + bpf_printk("Lookup: %lx %s", meta->nodeid, name_buf); + if (meta->nodeid == 1) + return BPF_FUSE_USER_PREFILTER; + else + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_mknod_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_mknod_in *in, struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char name_buf[255]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + bpf_dynptr_read(name_buf, 255, &name_ptr, 0, 0); + bpf_printk("mknod %s %x %x", name_buf, in->rdev | in->mode, in->umask); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_mkdir_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_mkdir_in *in, struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char name_buf[255]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + bpf_dynptr_read(name_buf, 255, &name_ptr, 0, 0); + bpf_printk("mkdir: %s %x %x", name_buf, in->mode, in->umask); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_rmdir_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char name_buf[255]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + bpf_dynptr_read(name_buf, 255, &name_ptr, 0, 0); + bpf_printk("rmdir: %s", name_buf); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_rename_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_rename_in *in, struct fuse_buffer *old_name, + struct fuse_buffer *new_name) +{ + struct bpf_dynptr old_name_ptr; + struct bpf_dynptr new_name_ptr; + char old_name_buf[255]; + //char new_name_buf[255]; + + bpf_fuse_get_ro_dynptr(old_name, &old_name_ptr); + //bpf_fuse_get_ro_dynptr(new_name, &new_name_ptr); + bpf_dynptr_read(old_name_buf, 255, &old_name_ptr, 0, 0); + //bpf_dynptr_read(new_name_buf, 255, &new_name_ptr, 0, 0); + bpf_printk("rename from %s", old_name_buf); + //bpf_printk("rename to %s", new_name_buf); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_rename2_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_rename2_in *in, struct fuse_buffer *old_name, + struct fuse_buffer *new_name) +{ + struct bpf_dynptr old_name_ptr; + //struct bpf_dynptr new_name_ptr; + char old_name_buf[255]; + //char new_name_buf[255]; + + bpf_fuse_get_ro_dynptr(old_name, &old_name_ptr); + //bpf_fuse_get_ro_dynptr(new_name, &new_name_ptr); + bpf_dynptr_read(old_name_buf, 255, &old_name_ptr, 0, 0); + //bpf_dynptr_read(new_name_buf, 255, &new_name_ptr, 0, 0); + bpf_printk("rename(%x) from %s", in->flags, old_name_buf); + //bpf_printk("rename to %s", new_name_buf); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_unlink_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char name_buf[255]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + bpf_dynptr_read(name_buf, 255, &name_ptr, 0, 0); + bpf_printk("unlink: %s", name_buf); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_link_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_link_in *in, struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char dst_name[255]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + bpf_dynptr_read(dst_name, 255, &name_ptr, 0, 0); + bpf_printk("Link: %d %s", in->oldnodeid, dst_name); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_symlink_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name, struct fuse_buffer *path) +{ + struct bpf_dynptr name_ptr; + //struct bpf_dynptr path_ptr; + char link_name[255]; + //char link_path[4096]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + //bpf_fuse_get_ro_dynptr(path, &path_ptr); + bpf_dynptr_read(link_name, 255, &name_ptr, 0, 0); + //bpf_dynptr_read(link_path, 4096, &path_ptr, 0, 0); + + bpf_printk("symlink from %s", link_name); + //bpf_printk("symlink to %s", link_path); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_get_link_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char link_name[255]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + bpf_dynptr_read(link_name, 255, &name_ptr, 0, 0); + bpf_printk("readlink from %s", link_name); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_release_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_release_in *in) +{ + bpf_printk("Release: %d", in->fh); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_releasedir_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_release_in *in) +{ + bpf_printk("Release Dir: %d", in->fh); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_create_open_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_create_in *in, struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char name_buf[255]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + bpf_dynptr_read(name_buf, 255, &name_ptr, 0, 0); + bpf_printk("Create %s", name_buf); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_open_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_open_in *in) +{ + bpf_printk("Open: %d", meta->nodeid); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_read_iter_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_read_in *in) +{ + bpf_printk("Read: fh: %lu, offset %lu, size %lu", + in->fh, in->offset, in->size); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_write_iter_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_write_in *in) +{ + bpf_printk("Write: fh: %lu, offset %lu, size %lu", + in->fh, in->offset, in->size); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_flush_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_flush_in *in) +{ + bpf_printk("Flush %d", in->fh); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_file_fallocate_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_fallocate_in *in) +{ + bpf_printk("Fallocate %d %lu", in->fh, in->length); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_getxattr_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_getxattr_in *in, struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char name_buf[255]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + bpf_dynptr_read(name_buf, 255, &name_ptr, 0, 0); + bpf_printk("Getxattr %d %s", meta->nodeid, name_buf); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_listxattr_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_getxattr_in *in) +{ + bpf_printk("Listxattr %d %d", meta->nodeid, in->size); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_setxattr_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_setxattr_in *in, struct fuse_buffer *name, + struct fuse_buffer *value) +{ + struct bpf_dynptr name_ptr; + char name_buf[255]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + bpf_dynptr_read(name_buf, 255, &name_ptr, 0, 0); + bpf_printk("Setxattr %d %s", meta->nodeid, name_buf); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_statfs_prefilter, const struct bpf_fuse_meta_info *meta) +{ + bpf_printk("statfs %d", meta->nodeid); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_lseek_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_lseek_in *in) +{ + bpf_printk("lseek type:%d, offset:%lld", in->whence, in->offset); + return BPF_FUSE_CONTINUE; +} + +SEC(".struct_ops") +struct fuse_ops trace_ops = { + .open_prefilter = (void *)trace_open_prefilter, + .opendir_prefilter = (void *)trace_opendir_prefilter, + .create_open_prefilter = (void *)trace_create_open_prefilter, + .release_prefilter = (void *)trace_release_prefilter, + .releasedir_prefilter = (void *)trace_releasedir_prefilter, + .flush_prefilter = (void *)trace_flush_prefilter, + .lseek_prefilter = (void *)trace_lseek_prefilter, + //.copy_file_range_prefilter = (void *)trace_copy_file_range_prefilter, + //.fsync_prefilter = (void *)trace_fsync_prefilter, + //.dir_fsync_prefilter = (void *)trace_dir_fsync_prefilter, + .getxattr_prefilter = (void *)trace_getxattr_prefilter, + .listxattr_prefilter = (void *)trace_listxattr_prefilter, + .setxattr_prefilter = (void *)trace_setxattr_prefilter, + //.removexattr_prefilter = (void *)trace_removexattr_prefilter, + .read_iter_prefilter = (void *)trace_read_iter_prefilter, + .write_iter_prefilter = (void *)trace_write_iter_prefilter, + .file_fallocate_prefilter = (void *)trace_file_fallocate_prefilter, + .lookup_prefilter = (void *)trace_lookup_prefilter, + .mknod_prefilter = (void *)trace_mknod_prefilter, + .mkdir_prefilter = (void *)trace_mkdir_prefilter, + .rmdir_prefilter = (void *)trace_rmdir_prefilter, + .rename2_prefilter = (void *)trace_rename2_prefilter, + .rename_prefilter = (void *)trace_rename_prefilter, + .unlink_prefilter = (void *)trace_unlink_prefilter, + .link_prefilter = (void *)trace_link_prefilter, + .getattr_prefilter = (void *)trace_getattr_prefilter, + .setattr_prefilter = (void *)trace_setattr_prefilter, + .statfs_prefilter = (void *)trace_statfs_prefilter, + .get_link_prefilter = (void *)trace_get_link_prefilter, + .symlink_prefilter = (void *)trace_symlink_prefilter, + .readdir_prefilter = (void *)trace_readdir_prefilter, + .access_prefilter = (void *)trace_access_prefilter, + .name = "trace_ops", +}; + diff --git a/tools/testing/selftests/filesystems/fuse/fuse_daemon.c b/tools/testing/selftests/filesystems/fuse/fuse_daemon.c new file mode 100644 index 000000000000..42f9f770988b --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/fuse_daemon.c @@ -0,0 +1,300 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2021 Google LLC + */ + +#include "test_fuse.h" +#include "test.skel.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 <uapi/linux/fuse.h> +#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[]) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + int result = TEST_FAILURE; + int trace_pid = -1; + char *mount_dir = NULL; + char *src_dir = NULL; + 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); + + test_skel = test_bpf__open_and_load(); + test_link = bpf_map__attach_struct_ops(test_skel->maps.trace_ops); + + 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, "trace_ops", 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 { + printf("Lookup Prefilter response: %s\n", name); + 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);*/ + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + 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; +} diff --git a/tools/testing/selftests/filesystems/fuse/fuse_test.c b/tools/testing/selftests/filesystems/fuse/fuse_test.c new file mode 100644 index 000000000000..cc14b79615c1 --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/fuse_test.c @@ -0,0 +1,2412 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2021 Google LLC + */ +#define _GNU_SOURCE + +#include "test_fuse.h" +#include "test.skel.h" + +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <sys/inotify.h> +#include <sys/mman.h> +#include <sys/mount.h> +#include <sys/syscall.h> +#include <sys/wait.h> + +#include <linux/capability.h> +#include <linux/random.h> + +#include <uapi/linux/fuse.h> +#include <uapi/linux/bpf.h> + +static const char *ft_src = "ft-src"; +static const char *ft_dst = "ft-dst"; + +static void fill_buffer(uint8_t *data, size_t len, int file, int block) +{ + int i; + int seed = 7919 * file + block; + + for (i = 0; i < len; i++) { + seed = 1103515245 * seed + 12345; + data[i] = (uint8_t)(seed >> (i % 13)); + } +} + +static bool test_buffer(uint8_t *data, size_t len, int file, int block) +{ + int i; + int seed = 7919 * file + block; + + for (i = 0; i < len; i++) { + seed = 1103515245 * seed + 12345; + if (data[i] != (uint8_t)(seed >> (i % 13))) + return false; + } + + return true; +} + +static int create_file(int dir, struct s name, int index, size_t blocks) +{ + int result = TEST_FAILURE; + int fd = -1; + int i; + uint8_t data[PAGE_SIZE]; + + TEST(fd = s_openat(dir, name, O_CREAT | O_WRONLY, 0777), fd != -1); + for (i = 0; i < blocks; ++i) { + fill_buffer(data, PAGE_SIZE, index, i); + TESTEQUAL(write(fd, data, sizeof(data)), PAGE_SIZE); + } + TESTSYSCALL(close(fd)); + result = TEST_SUCCESS; + +out: + close(fd); + return result; +} + +static int bpf_clear_trace(void) +{ + int result = TEST_FAILURE; + int tp = -1; + + TEST(tp = s_open(s_path(tracing_folder(), s("trace")), + O_WRONLY | O_TRUNC | O_CLOEXEC), tp != -1); + + result = TEST_SUCCESS; +out: + close(tp); + return result; +} + +static int bpf_test_trace_maybe(const char *substr, bool present) +{ + int result = TEST_FAILURE; + int tp = -1; + char trace_buffer[4096] = {}; + ssize_t bytes_read; + + TEST(tp = s_open(s_path(tracing_folder(), s("trace_pipe")), + O_RDONLY | O_CLOEXEC), + tp != -1); + fcntl(tp, F_SETFL, O_NONBLOCK); + + for (;;) { + bytes_read = read(tp, trace_buffer, sizeof(trace_buffer)); + if (present) + TESTCOND(bytes_read > 0); + else if (bytes_read <= 0) { + result = TEST_SUCCESS; + break; + } + + if (test_options.verbose) + ksft_print_msg("%s\n", trace_buffer); + + if (strstr(trace_buffer, substr)) { + if (present) + result = TEST_SUCCESS; + break; + } + } +out: + close(tp); + return result; +} + +static int bpf_test_trace(const char *substr) +{ + return bpf_test_trace_maybe(substr, true); +} + +static int bpf_test_no_trace(const char *substr) +{ + return bpf_test_trace_maybe(substr, false); +} + +static int basic_test(const char *mount_dir) +{ + const char *test_name = "test"; + const char *test_data = "data"; + + int result = TEST_FAILURE; + int fuse_dev = -1; + char *filename = NULL; + int fd = -1; + int pid = -1; + int status; + + TESTEQUAL(mount_fuse(mount_dir, NULL, -1, &fuse_dev), 0); + FUSE_ACTION + char data[256]; + + filename = concat_file_name(mount_dir, test_name); + TESTERR(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1); + TESTEQUAL(read(fd, data, strlen(test_data)), strlen(test_data)); + TESTCOND(!strcmp(data, test_data)); + TESTSYSCALL(close(fd)); + fd = -1; + FUSE_DAEMON + DECL_FUSE_IN(open); + DECL_FUSE_IN(read); + DECL_FUSE_IN(flush); + DECL_FUSE_IN(release); + + TESTFUSELOOKUP(test_name, 0); + TESTFUSEOUT1(fuse_entry_out, ((struct fuse_entry_out) { + .nodeid = 2, + .generation = 1, + .attr.ino = 100, + .attr.size = 4, + .attr.blksize = 512, + .attr.mode = S_IFREG | 0777, + })); + + TESTFUSEIN(FUSE_OPEN, open_in); + TESTFUSEOUT1(fuse_open_out, ((struct fuse_open_out) { + .fh = 1, + .open_flags = open_in->flags, + })); + + TESTFUSEIN(FUSE_READ, read_in); + TESTFUSEOUTREAD(test_data, strlen(test_data)); + + TESTFUSEIN(FUSE_FLUSH, flush_in); + TESTFUSEOUTEMPTY(); + + TESTFUSEIN(FUSE_RELEASE, release_in); + TESTFUSEOUTEMPTY(); + FUSE_DONE + + result = TEST_SUCCESS; +out: + if (!pid) + exit(TEST_FAILURE); + close(fuse_dev); + close(fd); + free(filename); + umount(mount_dir); + return result; +} + +static int bpf_test_real(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *test_name = "real"; + const char *test_data = "Weebles wobble but they don't fall down"; + int result = TEST_FAILURE; + int src_fd = -1; + int fuse_dev = -1; + char *filename = NULL; + int fd = -1; + char read_buffer[256] = {}; + ssize_t bytes_read; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TEST(fd = openat(src_fd, test_name, O_CREAT | O_RDWR | O_CLOEXEC, 0777), + fd != -1); + TESTEQUAL(write(fd, test_data, strlen(test_data)), strlen(test_data)); + TESTSYSCALL(close(fd)); + fd = -1; + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.test_trace_ops), test_link != NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_trace_ops", src_fd, &fuse_dev), 0); + + filename = concat_file_name(mount_dir, test_name); + TESTERR(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1); + bytes_read = read(fd, read_buffer, strlen(test_data)); + + TESTEQUAL(bytes_read, strlen(test_data)); + TESTEQUAL(strcmp(test_data, read_buffer), 0); + TESTEQUAL(bpf_test_trace("read"), 0); + result = TEST_SUCCESS; +out: + close(fuse_dev); + close(fd); + free(filename); + umount(mount_dir); + close(src_fd); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + + +static int bpf_test_partial(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *test_name = "partial"; + int result = TEST_FAILURE; + int src_fd = -1; + int fuse_dev = -1; + char *filename = NULL; + int fd = -1; + int pid = -1; + int status; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TESTEQUAL(create_file(src_fd, s(test_name), 1, 2), 0); + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.test_trace_ops), test_link != NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_trace_ops", src_fd, &fuse_dev), 0); + + FUSE_ACTION + uint8_t data[PAGE_SIZE]; + + TEST(filename = concat_file_name(mount_dir, test_name), + filename); + TESTERR(fd = open(filename, O_RDONLY | O_CLOEXEC), fd != -1); + TESTEQUAL(read(fd, data, PAGE_SIZE), PAGE_SIZE); + //TESTEQUAL(bpf_test_trace("read"), 0); + TESTCOND(test_buffer(data, PAGE_SIZE, 2, 0)); + TESTCOND(!test_buffer(data, PAGE_SIZE, 1, 0)); + TESTEQUAL(read(fd, data, PAGE_SIZE), PAGE_SIZE); + TESTCOND(test_buffer(data, PAGE_SIZE, 1, 1)); + TESTCOND(!test_buffer(data, PAGE_SIZE, 2, 1)); + TESTSYSCALL(close(fd)); + fd = -1; + FUSE_DAEMON + uint32_t *err_in; + DECL_FUSE(open); + DECL_FUSE(read); + DECL_FUSE(release); + uint8_t data[PAGE_SIZE]; + + TESTFUSEIN2_ERR_IN(FUSE_OPEN | FUSE_POSTFILTER, open_in, open_out, err_in); + TESTEQUAL(*err_in, 0); + TESTFUSEOUT1(fuse_open_out, ((struct fuse_open_out) { + .fh = 1, + .open_flags = open_in->flags, + })); + + TESTFUSEIN(FUSE_READ, read_in); + fill_buffer(data, PAGE_SIZE, 2, 0); + TESTFUSEOUTREAD(data, PAGE_SIZE); + + //TESTFUSEIN(FUSE_RELEASE, release_in); + //TESTFUSEOUTEMPTY(); + FUSE_DONE + + result = TEST_SUCCESS; +out: + if (!pid) + exit(TEST_FAILURE); + close(fuse_dev); + close(fd); + free(filename); + umount(mount_dir); + close(src_fd); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +static int bpf_test_attrs(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *test_name = "partial"; + int result = TEST_FAILURE; + int src_fd = -1; + int fuse_dev = -1; + char *filename = NULL; + struct stat st; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TESTEQUAL(create_file(src_fd, s(test_name), 1, 2), 0); + + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.test_trace_ops), test_link != NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_trace_ops", src_fd, &fuse_dev), 0); + + TEST(filename = concat_file_name(mount_dir, test_name), filename); + TESTSYSCALL(stat(filename, &st)); + TESTSYSCALL(chmod(filename, 0111)); + TESTSYSCALL(stat(filename, &st)); + TESTEQUAL(st.st_mode & 0777, 0111); + TESTSYSCALL(chmod(filename, 0777)); + TESTSYSCALL(stat(filename, &st)); + TESTEQUAL(st.st_mode & 0777, 0777); + TESTSYSCALL(chown(filename, 5, 6)); + TESTSYSCALL(stat(filename, &st)); + TESTEQUAL(st.st_uid, 5); + TESTEQUAL(st.st_gid, 6); + + result = TEST_SUCCESS; +out: + close(fuse_dev); + free(filename); + umount(mount_dir); + close(src_fd); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +static int bpf_test_readdir(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *names[] = {"real", "partial", "fake", ".", ".."}; + int result = TEST_FAILURE; + int src_fd = -1; + int fuse_dev = -1; + int pid = -1; + int status; + DIR *dir = NULL; + struct dirent *dirent; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TESTEQUAL(create_file(src_fd, s(names[0]), 1, 2), 0); + TESTEQUAL(create_file(src_fd, s(names[1]), 1, 2), 0); + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.test_trace_ops), test_link != NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_trace_ops", src_fd, &fuse_dev), 0); + + FUSE_ACTION + int i, j; + + TEST(dir = s_opendir(s(mount_dir)), dir); + TESTEQUAL(bpf_test_trace("opendir"), 0); + + for (i = 0; i < ARRAY_SIZE(names); ++i) { + TEST(dirent = readdir(dir), dirent); + + for (j = 0; j < ARRAY_SIZE(names); ++j) + if (names[j] && + strcmp(names[j], dirent->d_name) == 0) { + names[j] = NULL; + break; + } + TESTNE(j, ARRAY_SIZE(names)); + } + TEST(dirent = readdir(dir), dirent == NULL); + TESTSYSCALL(closedir(dir)); + dir = NULL; + TESTEQUAL(bpf_test_trace("readdir"), 0); + FUSE_DAEMON + struct fuse_in_header *in_header = + (struct fuse_in_header *)bytes_in; + ssize_t res = read(fuse_dev, bytes_in, sizeof(bytes_in)); + // ignore the error in extension + res -= ERR_IN_EXT_LEN; + struct fuse_read_out *read_out = + (struct fuse_read_out *) (bytes_in + + sizeof(*in_header) + + sizeof(struct fuse_read_in)); + struct fuse_dirent *fuse_dirent = + (struct fuse_dirent *) (bytes_in + res); + + TESTGE(res, sizeof(*in_header) + sizeof(struct fuse_read_in)); + TESTEQUAL(in_header->opcode, FUSE_READDIR | FUSE_POSTFILTER); + *fuse_dirent = (struct fuse_dirent) { + .ino = 100, + .off = 5, + .namelen = strlen("fake"), + .type = DT_REG, + }; + strcpy((char *)(bytes_in + res + sizeof(*fuse_dirent)), "fake"); + res += FUSE_DIRENT_ALIGN(sizeof(*fuse_dirent) + strlen("fake") + + 1); + TESTFUSEDIROUTREAD(read_out, + bytes_in + + sizeof(struct fuse_in_header) + + sizeof(struct fuse_read_in) + + sizeof(struct fuse_read_out), + res - sizeof(struct fuse_in_header) - + sizeof(struct fuse_read_in) - + sizeof(struct fuse_read_out)); + res = read(fuse_dev, bytes_in, sizeof(bytes_in)); + TESTEQUAL(res, sizeof(*in_header) + + sizeof(struct fuse_read_in) + + sizeof(struct fuse_read_out) + ERR_IN_EXT_LEN); + TESTEQUAL(in_header->opcode, FUSE_READDIR | FUSE_POSTFILTER); + TESTFUSEDIROUTREAD(read_out, bytes_in, 0); + FUSE_DONE + + result = TEST_SUCCESS; +out: + closedir(dir); + close(fuse_dev); + umount(mount_dir); + close(src_fd); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +static int bpf_test_redact_readdir(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *names[] = {"f1", "f2", "f3", "f4", "f5", "f6", ".", ".."}; + int num_shown = (ARRAY_SIZE(names) - 2) / 2 + 2; + int result = TEST_FAILURE; + int src_fd = -1; + int fuse_dev = -1; + int pid = -1; + int status; + DIR *dir = NULL; + struct dirent *dirent; + int i; + int count = 0; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + for (i = 0; i < ARRAY_SIZE(names) - 2; i++) + TESTEQUAL(create_file(src_fd, s(names[i]), 1, 2), 0); + + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.readdir_redact_ops), test_link != NULL); + TESTEQUAL(mount_fuse(mount_dir, "readdir_redact", src_fd, &fuse_dev), 0); + + FUSE_ACTION + int j; + + TEST(dir = s_opendir(s(mount_dir)), dir); + while ((dirent = readdir(dir))) { + errno = 0; + TESTEQUAL(errno, 0); + for (j = 0; j < ARRAY_SIZE(names); ++j) + if (names[j] && + strcmp(names[j], dirent->d_name) == 0) { + names[j] = NULL; + count++; + break; + } + TESTNE(j, ARRAY_SIZE(names)); + TESTGE(num_shown, count); + } + TESTEQUAL(count, num_shown); + TESTSYSCALL(closedir(dir)); + dir = NULL; + FUSE_DAEMON + bool skip = true; + for (int i = 0; i < ARRAY_SIZE(names) + 1; i++) { + uint8_t bytes_in[FUSE_MIN_READ_BUFFER]; + uint8_t bytes_out[FUSE_MIN_READ_BUFFER]; + struct fuse_in_header *in_header = + (struct fuse_in_header *)bytes_in; + ssize_t res = read(fuse_dev, bytes_in, sizeof(bytes_in)); + int length_out = 0; + uint8_t *pos; + uint8_t *dirs_in; + uint8_t *dirs_out; + struct fuse_read_in *fuse_read_in; + struct fuse_read_out *fuse_read_out_in; + struct fuse_read_out *fuse_read_out_out; + struct fuse_dirent *fuse_dirent_in = NULL; + struct fuse_dirent *next = NULL; + bool again = false; + int dir_ent_len = 0; + + // We're ignoring the error_in extension + res -= ERR_IN_EXT_LEN; + TESTGE(res, sizeof(struct fuse_in_header) + + sizeof(struct fuse_read_in) + + sizeof(struct fuse_read_out)); + + pos = bytes_in + sizeof(struct fuse_in_header); + fuse_read_in = (struct fuse_read_in *) pos; + pos += sizeof(*fuse_read_in); + fuse_read_out_in = (struct fuse_read_out *) pos; + pos += sizeof(*fuse_read_out_in); + dirs_in = pos; + + pos = bytes_out + sizeof(struct fuse_out_header); + fuse_read_out_out = (struct fuse_read_out *) pos; + pos += sizeof(*fuse_read_out_out); + dirs_out = pos; + + if (dirs_in < bytes_in + res) { + bool is_dot; + + fuse_dirent_in = (struct fuse_dirent *) dirs_in; + is_dot = (fuse_dirent_in->namelen == 1 && + !strncmp(fuse_dirent_in->name, ".", 1)) || + (fuse_dirent_in->namelen == 2 && + !strncmp(fuse_dirent_in->name, "..", 2)); + + dir_ent_len = FUSE_DIRENT_ALIGN( + sizeof(*fuse_dirent_in) + + fuse_dirent_in->namelen); + + if (dirs_in + dir_ent_len < bytes_in + res) + next = (struct fuse_dirent *) + (dirs_in + dir_ent_len); + + if (!skip || is_dot) { + memcpy(dirs_out, fuse_dirent_in, + sizeof(struct fuse_dirent) + + fuse_dirent_in->namelen); + length_out += dir_ent_len; + } + again = ((skip && !is_dot) && next); + + if (!is_dot) + skip = !skip; + } + + fuse_read_out_out->offset = next ? next->off : + fuse_read_out_in->offset; + fuse_read_out_out->again = again; + + { + struct fuse_out_header *out_header = + (struct fuse_out_header *)bytes_out; + + *out_header = (struct fuse_out_header) { + .len = sizeof(*out_header) + + sizeof(*fuse_read_out_out) + length_out, + .unique = in_header->unique, + }; + TESTEQUAL(write(fuse_dev, bytes_out, out_header->len), + out_header->len); + } + } + FUSE_DONE + + result = TEST_SUCCESS; +out: + closedir(dir); + close(fuse_dev); + umount(mount_dir); + close(src_fd); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +/* + * This test is more to show what classic fuse does with a creat in a subdir + * than a test of any new functionality + */ +static int bpf_test_creat(const char *mount_dir) +{ + const char *dir_name = "show"; + const char *file_name = "file"; + int result = TEST_FAILURE; + int fuse_dev = -1; + int pid = -1; + int status; + int fd = -1; + + TESTEQUAL(mount_fuse(mount_dir, NULL, -1, &fuse_dev), 0); + + FUSE_ACTION + TEST(fd = s_creat(s_path(s_path(s(mount_dir), s(dir_name)), + s(file_name)), + 0777), + fd != -1); + TESTSYSCALL(close(fd)); + FUSE_DAEMON + DECL_FUSE_IN(create); + DECL_FUSE_IN(release); + DECL_FUSE_IN(flush); + + TESTFUSELOOKUP(dir_name, 0); + TESTFUSEOUT1(fuse_entry_out, ((struct fuse_entry_out) { + .nodeid = 3, + .generation = 1, + .attr.ino = 100, + .attr.size = 4, + .attr.blksize = 512, + .attr.mode = S_IFDIR | 0777, + })); + + TESTFUSELOOKUP(file_name, 0); + TESTFUSEOUTERROR(-ENOENT); + + TESTFUSEINEXT(FUSE_CREATE, create_in, strlen(file_name) + 1); + TESTFUSEOUT2(fuse_entry_out, ((struct fuse_entry_out) { + .nodeid = 2, + .generation = 1, + .attr.ino = 200, + .attr.size = 4, + .attr.blksize = 512, + .attr.mode = S_IFREG, + }), + fuse_open_out, ((struct fuse_open_out) { + .fh = 1, + .open_flags = create_in->flags, + })); + + TESTFUSEIN(FUSE_FLUSH, flush_in); + TESTFUSEOUTEMPTY(); + + TESTFUSEIN(FUSE_RELEASE, release_in); + TESTFUSEOUTEMPTY(); + FUSE_DONE + + result = TEST_SUCCESS; +out: + close(fuse_dev); + umount(mount_dir); + return result; +} + +static int bpf_test_hidden_entries(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + static const char * const dir_names[] = { + "show", + "hide", + }; + const char *file_name = "file"; + const char *data = "The quick brown fox jumps over the lazy dog\n"; + int result = TEST_FAILURE; + int src_fd = -1; + int fuse_dev = -1; + int fd = -1; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TESTSYSCALL(mkdirat(src_fd, dir_names[0], 0777)); + TESTSYSCALL(mkdirat(src_fd, dir_names[1], 0777)); + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.test_hidden_ops), test_link != NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_hidden", src_fd, &fuse_dev), 0); + + TEST(fd = s_creat(s_path(s_path(s(mount_dir), s(dir_names[0])), + s(file_name)), + 0777), + fd != -1); + TESTSYSCALL(fallocate(fd, 0, 0, 4096)); + TEST(write(fd, data, strlen(data)), strlen(data)); + TESTSYSCALL(close(fd)); + TESTEQUAL(bpf_test_trace("Create"), 0); + + result = TEST_SUCCESS; +out: + close(fuse_dev); + umount(mount_dir); + close(src_fd); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +static int bpf_test_dir(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *dir_name = "dir"; + int result = TEST_FAILURE; + int src_fd = -1; + int fuse_dev = -1; + struct stat st; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.test_trace_ops), test_link != NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_trace_ops", src_fd, &fuse_dev), 0); + + TESTSYSCALL(s_mkdir(s_path(s(mount_dir), s(dir_name)), 0777)); + TESTEQUAL(bpf_test_trace("mkdir"), 0); + TESTSYSCALL(s_stat(s_path(s(ft_src), s(dir_name)), &st)); + TESTSYSCALL(s_rmdir(s_path(s(mount_dir), s(dir_name)))); + TESTEQUAL(s_stat(s_path(s(ft_src), s(dir_name)), &st), -1); + TESTEQUAL(errno, ENOENT); + result = TEST_SUCCESS; +out: + close(fuse_dev); + umount(mount_dir); + close(src_fd); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +static int bpf_test_file(const char *mount_dir, bool close_first) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *file_name = "real"; + int result = TEST_FAILURE; + int src_fd = -1; + int fuse_dev = -1; + int fd = -1; + struct stat st; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.test_trace_ops), test_link != NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_trace_ops", src_fd, &fuse_dev), 0); + + TEST(fd = s_creat(s_path(s(mount_dir), s(file_name)), + 0777), + fd != -1); + TESTEQUAL(bpf_test_trace("Create"), 0); + if (close_first) { + TESTSYSCALL(close(fd)); + fd = -1; + } + TESTSYSCALL(s_stat(s_path(s(ft_src), s(file_name)), &st)); + TESTSYSCALL(s_unlink(s_path(s(mount_dir), s(file_name)))); + TESTEQUAL(bpf_test_trace("unlink"), 0); + TESTEQUAL(s_stat(s_path(s(ft_src), s(file_name)), &st), -1); + TESTEQUAL(errno, ENOENT); + if (!close_first) { + TESTSYSCALL(close(fd)); + fd = -1; + } + result = TEST_SUCCESS; +out: + close(fd); + close(fuse_dev); + umount(mount_dir); + close(src_fd); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +static int bpf_test_file_early_close(const char *mount_dir) +{ + return bpf_test_file(mount_dir, true); +} + +static int bpf_test_file_late_close(const char *mount_dir) +{ + return bpf_test_file(mount_dir, false); +} + +static int bpf_test_alter_errcode_bpf(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *dir_name = "dir"; + int result = TEST_FAILURE; + int src_fd = -1; + int fuse_dev = -1; + struct stat st; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.test_error_ops), test_link != NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_error", src_fd, &fuse_dev), 0); + + TESTSYSCALL(s_mkdir(s_path(s(mount_dir), s(dir_name)), 0777)); + //TESTEQUAL(bpf_test_trace("mkdir"), 0); + TESTSYSCALL(s_stat(s_path(s(ft_src), s(dir_name)), &st)); + TESTEQUAL(s_mkdir(s_path(s(mount_dir), s(dir_name)), 0777), -EPERM); + TESTSYSCALL(s_rmdir(s_path(s(mount_dir), s(dir_name)))); + TESTEQUAL(s_stat(s_path(s(ft_src), s(dir_name)), &st), -1); + TESTEQUAL(errno, ENOENT); + result = TEST_SUCCESS; +out: + close(fuse_dev); + umount(mount_dir); + close(src_fd); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +static int bpf_test_alter_errcode_userspace(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *dir_name = "doesnotexist"; + int result = TEST_FAILURE; + int src_fd = -1; + int fuse_dev = -1; + int pid = -1; + int status; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.test_error_ops), test_link != NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_error", src_fd, &fuse_dev), 0); + + FUSE_ACTION + TESTEQUAL(s_unlink(s_path(s(mount_dir), s(dir_name))), + -1); + TESTEQUAL(errno, ENOMEM); + FUSE_DAEMON + TESTFUSELOOKUP("doesnotexist", FUSE_POSTFILTER); + TESTFUSEOUTERROR(-ENOMEM); + FUSE_DONE + result = TEST_SUCCESS; +out: + close(fuse_dev); + umount(mount_dir); + close(src_fd); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} +//TODO: Make equivalent struct_op tests +#if 0 +static int bpf_test_verifier(const char *mount_dir) +{ + int result = TEST_FAILURE; + int bpf_fd1 = -1; + int bpf_fd2 = -1; + int bpf_fd3 = -1; + + TESTEQUAL(install_elf_bpf("test_bpf.bpf.o", "test_verify", + &bpf_fd1, NULL, NULL), 0); + TESTEQUAL(install_elf_bpf_invalid("test_bpf.bpf.o", "test_verify_fail", + &bpf_fd2, NULL, NULL), 0); + TESTEQUAL(install_elf_bpf_invalid("test_bpf.bpf.o", "test_verify_fail2", + &bpf_fd3, NULL, NULL), 0); + result = TEST_SUCCESS; +out: + close(bpf_fd1); + close(bpf_fd2); + close(bpf_fd3); + return result; +} + +static int bpf_test_verifier_out_args(const char *mount_dir) +{ + int result = TEST_FAILURE; + int bpf_fd1 = -1; + int bpf_fd2 = -1; + + TESTEQUAL(install_elf_bpf_invalid("test_bpf.bpf.o", "test_verify_fail3", + &bpf_fd1, NULL, NULL), 0); + TESTEQUAL(install_elf_bpf_invalid("test_bpf.bpf.o", "test_verify_fail4", + &bpf_fd2, NULL, NULL), 0); + result = TEST_SUCCESS; +out: + close(bpf_fd1); + close(bpf_fd2); + return result; +} + +static int bpf_test_verifier_packet_invalidation(const char *mount_dir) +{ + int result = TEST_FAILURE; + int bpf_fd1 = -1; + int bpf_fd2 = -1; + + TESTEQUAL(install_elf_bpf_invalid("test_bpf.bpf.o", "test_verify_fail5", + &bpf_fd1, NULL, NULL), 0); + TESTEQUAL(install_elf_bpf("test_bpf.bpf.o", "test_verify5", + &bpf_fd2, NULL, NULL), 0); + result = TEST_SUCCESS; +out: + close(bpf_fd1); + close(bpf_fd2); + return result; +} + +static int bpf_test_verifier_nonsense_read(const char *mount_dir) +{ + int result = TEST_FAILURE; + int bpf_fd1 = -1; + + TESTEQUAL(install_elf_bpf_invalid("test_bpf.bpf.o", "test_verify_fail6", + &bpf_fd1, NULL, NULL), 0); + result = TEST_SUCCESS; +out: + close(bpf_fd1); + return result; +} +#endif + +static int bpf_test_mknod(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *file_name = "real"; + int result = TEST_FAILURE; + int src_fd = -1; + int bpf_fd = -1; + int fuse_dev = -1; + struct stat st; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.test_trace_ops), test_link != NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_trace_ops", src_fd, &fuse_dev), 0); + + TESTSYSCALL(s_mkfifo(s_path(s(mount_dir), s(file_name)), 0777)); + TESTEQUAL(bpf_test_trace("mknod"), 0); + TESTSYSCALL(s_stat(s_path(s(ft_src), s(file_name)), &st)); + TESTSYSCALL(s_unlink(s_path(s(mount_dir), s(file_name)))); + TESTEQUAL(bpf_test_trace("unlink"), 0); + TESTEQUAL(s_stat(s_path(s(ft_src), s(file_name)), &st), -1); + TESTEQUAL(errno, ENOENT); + result = TEST_SUCCESS; +out: + close(fuse_dev); + umount(mount_dir); + close(bpf_fd); + close(src_fd); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +static int bpf_test_largedir(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *show = "show"; + const int files = 1000; + + int result = TEST_FAILURE; + int src_fd = -1; + int bpf_fd = -1; + int fuse_dev = -1; + int pid = -1; + int status; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.trace_ops), test_link != NULL); + TESTEQUAL(mount_fuse(mount_dir, "trace_ops", src_fd, &fuse_dev), 0); + + FUSE_ACTION + int i; + int fd; + DIR *dir = NULL; + struct dirent *dirent; + + TESTSYSCALL(s_mkdir(s_path(s(mount_dir), s(show)), 0777)); + for (i = 0; i < files; ++i) { + char filename[NAME_MAX]; + + sprintf(filename, "%d", i); + TEST(fd = s_creat(s_path(s_path(s(mount_dir), s(show)), + s(filename)), 0777), fd != -1); + TESTSYSCALL(close(fd)); + } + + TEST(dir = s_opendir(s_path(s(mount_dir), s(show))), dir); + for (dirent = readdir(dir); dirent; dirent = readdir(dir)) + ; + closedir(dir); + FUSE_DAEMON + int i; + + for (i = 0; i < files + 2; ++i) { + TESTFUSELOOKUP(show, FUSE_PREFILTER); + TESTFUSEOUTREAD(show, 5); + } + FUSE_DONE + + result = TEST_SUCCESS; +out: + close(fuse_dev); + umount(mount_dir); + close(bpf_fd); + close(src_fd); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +static int bpf_test_link(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *file_name = "real"; + const char *link_name = "partial"; + int result = TEST_FAILURE; + int fd = -1; + int src_fd = -1; + int bpf_fd = -1; + int fuse_dev = -1; + struct stat st; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.test_trace_ops), test_link != NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_trace_ops", src_fd, &fuse_dev), 0); + + TEST(fd = s_creat(s_path(s(mount_dir), s(file_name)), 0777), fd != -1); + TESTEQUAL(bpf_test_trace("Create"), 0); + TESTSYSCALL(s_stat(s_path(s(ft_src), s(file_name)), &st)); + + TESTSYSCALL(s_link(s_path(s(mount_dir), s(file_name)), + s_path(s(mount_dir), s(link_name)))); + + TESTEQUAL(bpf_test_trace("link"), 0); + TESTSYSCALL(s_stat(s_path(s(ft_src), s(link_name)), &st)); + + TESTSYSCALL(s_unlink(s_path(s(mount_dir), s(link_name)))); + TESTEQUAL(bpf_test_trace("unlink"), 0); + TESTEQUAL(s_stat(s_path(s(ft_src), s(link_name)), &st), -1); + TESTEQUAL(errno, ENOENT); + + TESTSYSCALL(s_unlink(s_path(s(mount_dir), s(file_name)))); + TESTEQUAL(bpf_test_trace("unlink"), 0); + TESTEQUAL(s_stat(s_path(s(ft_src), s(file_name)), &st), -1); + TESTEQUAL(errno, ENOENT); + + result = TEST_SUCCESS; +out: + close(fd); + close(fuse_dev); + umount(mount_dir); + close(bpf_fd); + close(src_fd); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +static int bpf_test_symlink(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *test_name = "real"; + const char *symlink_name = "partial"; + const char *test_data = "Weebles wobble but they don't fall down"; + int result = TEST_FAILURE; + int bpf_fd = -1; + int src_fd = -1; + int fuse_dev = -1; + int fd = -1; + char read_buffer[256] = {}; + ssize_t bytes_read; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TEST(fd = openat(src_fd, test_name, O_CREAT | O_RDWR | O_CLOEXEC, 0777), + fd != -1); + TESTEQUAL(write(fd, test_data, strlen(test_data)), strlen(test_data)); + TESTSYSCALL(close(fd)); + fd = -1; + + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.test_trace_ops), test_link != NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_trace_ops", src_fd, &fuse_dev), 0); + + TESTSYSCALL(s_symlink(s_path(s(mount_dir), s(test_name)), + s_path(s(mount_dir), s(symlink_name)))); + TESTEQUAL(bpf_test_trace("symlink"), 0); + + TESTERR(fd = s_open(s_path(s(mount_dir), s(symlink_name)), O_RDONLY | O_CLOEXEC), fd != -1); + bytes_read = read(fd, read_buffer, strlen(test_data)); + TESTEQUAL(bpf_test_trace("readlink"), 0); + TESTEQUAL(bytes_read, strlen(test_data)); + TESTEQUAL(strcmp(test_data, read_buffer), 0); + + result = TEST_SUCCESS; +out: + close(fuse_dev); + close(fd); + umount(mount_dir); + close(src_fd); + close(bpf_fd); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +static int bpf_test_xattr(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + static const char file_name[] = "real"; + static const char xattr_name[] = "user.xattr_test_name"; + static const char xattr_value[] = "this_is_a_test"; + const size_t xattr_size = sizeof(xattr_value); + char xattr_value_ret[256]; + ssize_t xattr_size_ret; + int result = TEST_FAILURE; + int fd = -1; + int src_fd = -1; + int bpf_fd = -1; + int fuse_dev = -1; + struct stat st; + + memset(xattr_value_ret, '\0', sizeof(xattr_value_ret)); + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.test_trace_ops), test_link != NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_trace_ops", src_fd, &fuse_dev), 0); + + + TEST(fd = s_creat(s_path(s(mount_dir), s(file_name)), 0777), fd != -1); + TESTEQUAL(bpf_test_trace("Create"), 0); + TESTSYSCALL(close(fd)); + + TESTSYSCALL(s_stat(s_path(s(ft_src), s(file_name)), &st)); + TEST(result = s_getxattr(s_path(s(mount_dir), s(file_name)), xattr_name, + xattr_value_ret, sizeof(xattr_value_ret), + &xattr_size_ret), + result == -1); + TESTEQUAL(errno, ENODATA); + TESTEQUAL(bpf_test_trace("getxattr"), 0); + + TESTSYSCALL(s_listxattr(s_path(s(mount_dir), s(file_name)), + xattr_value_ret, sizeof(xattr_value_ret), + &xattr_size_ret)); + TESTEQUAL(bpf_test_trace("listxattr"), 0); + TESTEQUAL(xattr_size_ret, 0); + + TESTSYSCALL(s_setxattr(s_path(s(mount_dir), s(file_name)), xattr_name, + xattr_value, xattr_size, 0)); + TESTEQUAL(bpf_test_trace("setxattr"), 0); + + TESTSYSCALL(s_listxattr(s_path(s(mount_dir), s(file_name)), + xattr_value_ret, sizeof(xattr_value_ret), + &xattr_size_ret)); + TESTEQUAL(bpf_test_trace("listxattr"), 0); + TESTEQUAL(xattr_size_ret, sizeof(xattr_name)); + TESTEQUAL(strcmp(xattr_name, xattr_value_ret), 0); + + TESTSYSCALL(s_getxattr(s_path(s(mount_dir), s(file_name)), xattr_name, + xattr_value_ret, sizeof(xattr_value_ret), + &xattr_size_ret)); + TESTEQUAL(bpf_test_trace("getxattr"), 0); + TESTEQUAL(xattr_size, xattr_size_ret); + TESTEQUAL(strcmp(xattr_value, xattr_value_ret), 0); + + TESTSYSCALL(s_removexattr(s_path(s(mount_dir), s(file_name)), xattr_name)); + TESTEQUAL(bpf_test_trace("removexattr"), 0); + + TESTEQUAL(s_getxattr(s_path(s(mount_dir), s(file_name)), xattr_name, + xattr_value_ret, sizeof(xattr_value_ret), + &xattr_size_ret), -1); + TESTEQUAL(errno, ENODATA); + + TESTSYSCALL(s_unlink(s_path(s(mount_dir), s(file_name)))); + TESTEQUAL(bpf_test_trace("unlink"), 0); + TESTEQUAL(s_stat(s_path(s(ft_src), s(file_name)), &st), -1); + TESTEQUAL(errno, ENOENT); + + result = TEST_SUCCESS; +out: + close(fuse_dev); + umount(mount_dir); + close(bpf_fd); + close(src_fd); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +static int bpf_test_set_backing(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *backing_name = "backing"; + const char *test_data = "data"; + const char *test_name = "test"; + + int result = TEST_FAILURE; + int fuse_dev = -1; + int fd = -1; + int pid = -1; + int status; + + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.trace_ops), test_link != NULL); + TESTEQUAL(mount_fuse_no_init(mount_dir, NULL, -1, &fuse_dev), 0); + FUSE_ACTION + char data[256] = {0}; + + TESTERR(fd = s_open(s_path(s(mount_dir), s(test_name)), + O_RDONLY | O_CLOEXEC), fd != -1); + TESTEQUAL(read(fd, data, strlen(test_data)), strlen(test_data)); + TESTCOND(!strcmp(data, test_data)); + TESTSYSCALL(close(fd)); + fd = -1; + TESTSYSCALL(umount(mount_dir)); + FUSE_DAEMON + //int bpf_fd = -1; + int backing_fd = -1; + struct fuse_bpf_entry_out bpf_entry[2]; + + TESTERR(backing_fd = s_creat(s_path(s(ft_src), s(backing_name)), 0777), + backing_fd != -1); + TESTEQUAL(write(backing_fd, test_data, strlen(test_data)), + strlen(test_data)); + + TESTFUSEINIT(); + TESTFUSELOOKUP(test_name, 0); + bpf_entry[0] = (struct fuse_bpf_entry_out) { + .entry_type = FUSE_ENTRY_BPF, + .name = "trace_ops", + }; + bpf_entry[1] = (struct fuse_bpf_entry_out) { + .entry_type = FUSE_ENTRY_BACKING, + .fd = backing_fd, + }; + TESTFUSEOUT3_IOCTL(fuse_entry_out, ((struct fuse_entry_out) {0}), + fuse_bpf_entry_out, bpf_entry[0], + fuse_bpf_entry_out, bpf_entry[1]); + read(fuse_dev, bytes_in, sizeof(bytes_in)); + //TESTSYSCALL(close(bpf_fd)); + TESTSYSCALL(close(backing_fd)); + FUSE_DONE + + result = TEST_SUCCESS; +out: + if (!pid) + exit(TEST_FAILURE); + close(fuse_dev); + close(fd); + umount(mount_dir); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +static int bpf_test_set_backing_no_ioctl(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *backing_name = "backing"; + const char *test_name = "test"; + + int result = TEST_FAILURE; + int fuse_dev = -1; + int fd = -1; + int pid = -1; + int status; + + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.trace_ops), test_link != NULL); + TESTEQUAL(mount_fuse_no_init(mount_dir, NULL, -1, &fuse_dev), 0); + FUSE_ACTION + + TESTERR(fd = s_open(s_path(s(mount_dir), s(test_name)), + O_RDONLY | O_CLOEXEC), fd == -1); + FUSE_DAEMON + int backing_fd = -1; + struct fuse_bpf_entry_out bpf_entry[2]; + + TESTERR(backing_fd = s_creat(s_path(s(ft_src), s(backing_name)), 0777), + backing_fd != -1); + + TESTFUSEINIT(); + TESTFUSELOOKUP(test_name, 0); + bpf_entry[0] = (struct fuse_bpf_entry_out) { + .entry_type = FUSE_ENTRY_BPF, + .name = "trace_ops", + }; + bpf_entry[1] = (struct fuse_bpf_entry_out) { + .entry_type = FUSE_ENTRY_BACKING, + .fd = backing_fd, + }; + TESTFUSEOUT3_FAIL(fuse_entry_out, ((struct fuse_entry_out) {0}), + fuse_bpf_entry_out, bpf_entry[0], + fuse_bpf_entry_out, bpf_entry[1]); + TESTSYSCALL(close(backing_fd)); + FUSE_DONE + + result = TEST_SUCCESS; +out: + if (!pid) + exit(TEST_FAILURE); + close(fuse_dev); + close(fd); + umount(mount_dir); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +static int bpf_test_set_backing_folder(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *backing_name = "backingdir"; + const char *test_name = "testdir"; + const char *names[] = {"file", ".", ".."}; + + int result = TEST_FAILURE; + int fuse_dev = -1; + int fd = -1; + int pid = -1; + int status; + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.trace_ops), test_link != NULL); + TESTEQUAL(mount_fuse_no_init(mount_dir, NULL, -1, &fuse_dev), 0); + FUSE_ACTION + DIR *dir = NULL; + struct dirent *dirent; + int i, j; + + TEST(dir = s_opendir(s_path(s(mount_dir), s(test_name))), dir); + + for (i = 0; i < ARRAY_SIZE(names); ++i) { + TEST(dirent = readdir(dir), dirent); + + for (j = 0; j < ARRAY_SIZE(names); ++j) + if (names[j] && + strcmp(names[j], dirent->d_name) == 0) { + names[j] = NULL; + break; + } + TESTNE(j, ARRAY_SIZE(names)); + } + TEST(dirent = readdir(dir), dirent == NULL); + TESTSYSCALL(closedir(dir)); + dir = NULL; + TESTEQUAL(bpf_test_trace("Read Dir"), 0); + TESTSYSCALL(umount(mount_dir)); + FUSE_DAEMON + int backing_fd = -1; + struct fuse_bpf_entry_out bpf_entry[2]; + + TESTSYSCALL(s_mkdir(s_path(s(ft_src), s(backing_name)), 0777)); + TESTERR(backing_fd = s_open(s_path(s(ft_src), s(backing_name)), O_RDONLY | O_CLOEXEC), + backing_fd != -1); + TESTSYSCALL(s_mkdir(s_pathn(3, s(ft_src), s(backing_name), s(names[0])), 0777)); + + TESTFUSEINIT(); + TESTFUSELOOKUP(test_name, 0); + + bpf_entry[0] = (struct fuse_bpf_entry_out) { + .entry_type = FUSE_ENTRY_BPF, + .name = "passthrough", + }; + bpf_entry[1] = (struct fuse_bpf_entry_out) { + .entry_type = FUSE_ENTRY_BACKING, + .fd = backing_fd, + }; + bpf_entry[0] = (struct fuse_bpf_entry_out) { + .entry_type = FUSE_ENTRY_BPF, + .name = "trace_ops", + }; + bpf_entry[1] = (struct fuse_bpf_entry_out) { + .entry_type = FUSE_ENTRY_BACKING, + .fd = backing_fd, + }; + TESTFUSEOUT3_IOCTL(fuse_entry_out, ((struct fuse_entry_out) {0}), + fuse_bpf_entry_out, bpf_entry[0], + fuse_bpf_entry_out, bpf_entry[1]); + TESTSYSCALL(close(backing_fd)); + FUSE_DONE + + result = TEST_SUCCESS; +out: + if (!pid) + exit(TEST_FAILURE); + close(fuse_dev); + close(fd); + umount(mount_dir); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +static int bpf_test_remove_backing(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *folder1 = "folder1"; + const char *folder2 = "folder2"; + const char *file = "file1"; + const char *contents1 = "contents1"; + const char *contents2 = "contents2"; + + int result = TEST_FAILURE; + int fuse_dev = -1; + int fd = -1; + int src_fd = -1; + int pid = -1; + int status; + char data[256] = {0}; + + /* + * Create folder1/file + * folder2/file + * + * test will install bpf into mount + * bpf will postfilter root lookup to daemon + * daemon will remove bpf and redirect opens on folder1 to folder2 + * test will open folder1/file which will be redirected to folder2 + * test will check no traces for file, and contents are folder2/file + */ + TESTEQUAL(bpf_clear_trace(), 0); + TESTSYSCALL(s_mkdir(s_path(s(ft_src), s(folder1)), 0777)); + TEST(fd = s_creat(s_pathn(3, s(ft_src), s(folder1), s(file)), 0777), + fd != -1); + TESTEQUAL(write(fd, contents1, strlen(contents1)), strlen(contents1)); + TESTSYSCALL(close(fd)); + TESTSYSCALL(s_mkdir(s_path(s(ft_src), s(folder2)), 0777)); + TEST(fd = s_creat(s_pathn(3, s(ft_src), s(folder2), s(file)), 0777), + fd != -1); + TESTEQUAL(write(fd, contents2, strlen(contents2)), strlen(contents2)); + TESTSYSCALL(close(fd)); + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.passthrough_ops), test_link != NULL); + TESTEQUAL(mount_fuse_no_init(mount_dir, "passthrough", src_fd, &fuse_dev), 0); + + FUSE_ACTION + TESTERR(fd = s_open(s_pathn(3, s(mount_dir), s(folder1), + s(file)), + O_RDONLY | O_CLOEXEC), fd != -1); + TESTEQUAL(read(fd, data, sizeof(data)), strlen(contents2)); + TESTCOND(!strcmp(data, contents2)); + TESTEQUAL(bpf_test_no_trace("file"), 0); + TESTSYSCALL(close(fd)); + fd = -1; + TESTSYSCALL(umount(mount_dir)); + FUSE_DAEMON + // The bpf postfilter only sets one fuse_bpf_entry_out + struct in_str { + char name[8]; + struct fuse_entry_out feo; + struct fuse_bpf_entry_out febo[1]; + } __attribute__((packed)); + uint32_t *err_in; + struct in_str *in; + int backing_fd = -1; + struct fuse_bpf_entry_out bpf_entry[2]; + + TESTFUSEINIT(); + TESTFUSEIN_ERR_IN(FUSE_LOOKUP | FUSE_POSTFILTER, in, err_in); + TESTEQUAL(*err_in, 0); + TEST(backing_fd = s_open(s_path(s(ft_src), s(folder2)), + O_DIRECTORY | O_RDONLY | O_CLOEXEC), + backing_fd != -1); + + bpf_entry[0] = (struct fuse_bpf_entry_out) { + .entry_type = FUSE_ENTRY_REMOVE_BPF, + }; + bpf_entry[1] = (struct fuse_bpf_entry_out) { + .entry_type = FUSE_ENTRY_BACKING, + .fd = backing_fd, + }; + TESTFUSEOUT3_IOCTL(fuse_entry_out, ((struct fuse_entry_out) {0}), + fuse_bpf_entry_out, bpf_entry[0], + fuse_bpf_entry_out, bpf_entry[1]); + + while (read(fuse_dev, bytes_in, sizeof(bytes_in)) != -1) + ; + TESTSYSCALL(close(backing_fd)); + FUSE_DONE + + result = TEST_SUCCESS; +out: + close(fuse_dev); + close(fd); + close(src_fd); + umount(mount_dir); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +static int bpf_test_dir_rename(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *dir_name = "dir"; + const char *dir_name2 = "dir2"; + int result = TEST_FAILURE; + int src_fd = -1; + int fuse_dev = -1; + struct stat st; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.test_trace_ops), test_link != NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_trace_ops", src_fd, &fuse_dev), 0); + + TESTSYSCALL(s_mkdir(s_path(s(mount_dir), s(dir_name)), 0777)); + TESTEQUAL(bpf_test_trace("mkdir"), 0); + TESTSYSCALL(s_stat(s_path(s(ft_src), s(dir_name)), &st)); + TESTSYSCALL(s_rename(s_path(s(mount_dir), s(dir_name)), + s_path(s(mount_dir), s(dir_name2)))); + TESTEQUAL(s_stat(s_path(s(ft_src), s(dir_name)), &st), -1); + TESTEQUAL(errno, ENOENT); + TESTSYSCALL(s_stat(s_path(s(ft_src), s(dir_name2)), &st)); + result = TEST_SUCCESS; +out: + close(fuse_dev); + umount(mount_dir); + close(src_fd); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +static int bpf_test_file_rename(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *dir = "dir"; + const char *file1 = "file1"; + const char *file2 = "file2"; + int result = TEST_FAILURE; + int src_fd = -1; + int fuse_dev = -1; + int fd = -1; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.test_trace_ops), test_link != NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_trace_ops", src_fd, &fuse_dev), 0); + + TESTSYSCALL(s_mkdir(s_path(s(mount_dir), s(dir)), 0777)); + TEST(fd = s_creat(s_pathn(3, s(mount_dir), s(dir), s(file1)), 0777), + fd != -1); + TESTSYSCALL(s_rename(s_pathn(3, s(mount_dir), s(dir), s(file1)), + s_pathn(3, s(mount_dir), s(dir), s(file2)))); + result = TEST_SUCCESS; +out: + close(fd); + umount(mount_dir); + close(fuse_dev); + close(src_fd); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +static int mmap_test(const char *mount_dir) +{ + const char *file = "file"; + int result = TEST_FAILURE; + int src_fd = -1; + int fuse_dev = -1; + int fd = -1; + char *addr = NULL; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TESTEQUAL(mount_fuse(mount_dir, NULL, src_fd, &fuse_dev), 0); + TEST(fd = s_open(s_path(s(mount_dir), s(file)), + O_CREAT | O_RDWR | O_CLOEXEC, 0777), + fd != -1); + TESTSYSCALL(fallocate(fd, 0, 4096, SEEK_CUR)); + TEST(addr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0), + addr != (void *) -1); + memset(addr, 'a', 4096); + + result = TEST_SUCCESS; +out: + munmap(addr, 4096); + close(fd); + umount(mount_dir); + close(fuse_dev); + close(src_fd); + return result; +} + +static int readdir_perms_test(const char *mount_dir) +{ + int result = TEST_FAILURE; + struct __user_cap_header_struct uchs = { _LINUX_CAPABILITY_VERSION_3 }; + struct __user_cap_data_struct ucds[2]; + int src_fd = -1; + int fuse_dev = -1; + DIR *dir = NULL; + + /* Must remove capabilities for this test. */ + TESTSYSCALL(syscall(SYS_capget, &uchs, ucds)); + ucds[0].effective &= ~(1 << CAP_DAC_OVERRIDE | 1 << CAP_DAC_READ_SEARCH); + TESTSYSCALL(syscall(SYS_capset, &uchs, ucds)); + + /* This is what we are testing in fuseland. First test without fuse, */ + TESTSYSCALL(mkdir("test", 0111)); + TEST(dir = opendir("test"), dir == NULL); + if (dir) + closedir(dir); + dir = NULL; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TESTEQUAL(mount_fuse(mount_dir, NULL, src_fd, &fuse_dev), 0); + + TESTSYSCALL(s_mkdir(s_path(s(mount_dir), s("test")), 0111)); + TEST(dir = s_opendir(s_path(s(mount_dir), s("test"))), dir == NULL); + + result = TEST_SUCCESS; +out: + ucds[0].effective |= 1 << CAP_DAC_OVERRIDE | 1 << CAP_DAC_READ_SEARCH; + syscall(SYS_capset, &uchs, ucds); + + closedir(dir); + s_rmdir(s_path(s(mount_dir), s("test"))); + umount(mount_dir); + close(fuse_dev); + close(src_fd); + rmdir("test"); + return result; +} + +static int bpf_test_statfs(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + int result = TEST_FAILURE; + int src_fd = -1; + int fuse_dev = -1; + int fd = -1; + struct statfs st; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.test_trace_ops), test_link != NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_trace_ops", src_fd, &fuse_dev), 0); + + TESTSYSCALL(s_statfs(s(mount_dir), &st)); + TESTEQUAL(bpf_test_trace("statfs"), 0); + TESTEQUAL(st.f_type, 0x65735546); + result = TEST_SUCCESS; +out: + close(fd); + umount(mount_dir); + close(fuse_dev); + close(src_fd); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +static int bpf_test_lseek(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *file = "real"; + const char *test_data = "data"; + int result = TEST_FAILURE; + int src_fd = -1; + int fuse_dev = -1; + int fd = -1; + + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TEST(fd = openat(src_fd, file, O_CREAT | O_RDWR | O_CLOEXEC, 0777), + fd != -1); + TESTEQUAL(write(fd, test_data, strlen(test_data)), strlen(test_data)); + TESTSYSCALL(close(fd)); + fd = -1; + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.test_trace_ops), test_link != NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_trace_ops", src_fd, &fuse_dev), 0); + + TEST(fd = s_open(s_path(s(mount_dir), s(file)), O_RDONLY | O_CLOEXEC), + fd != -1); + TESTEQUAL(lseek(fd, 3, SEEK_SET), 3); + TESTEQUAL(bpf_test_trace("lseek"), 0); + TESTEQUAL(lseek(fd, 5, SEEK_END), 9); + TESTEQUAL(bpf_test_trace("lseek"), 0); + TESTEQUAL(lseek(fd, 1, SEEK_CUR), 10); + TESTEQUAL(bpf_test_trace("lseek"), 0); + TESTEQUAL(lseek(fd, 1, SEEK_DATA), 1); + TESTEQUAL(bpf_test_trace("lseek"), 0); + result = TEST_SUCCESS; +out: + close(fd); + umount(mount_dir); + close(fuse_dev); + close(src_fd); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +/* + * State: + * Original: dst/folder1/content.txt + * ^ + * | + * | + * Backing: src/folder1/content.txt + * + * Step 1: open(folder1) - set backing to src/folder1 + * Check 1: cat(content.txt) - check not receiving call on the fuse daemon + * and content is the same + * Step 2: readdirplus(dst) + * Check 2: cat(content.txt) - check not receiving call on the fuse daemon + * and content is the same + */ +static int bpf_test_readdirplus_not_overriding_backing(const char *mount_dir) +{ + const char *folder1 = "folder1"; + const char *content_file = "content.txt"; + const char *content = "hello world"; + + int result = TEST_FAILURE; + int fuse_dev = -1; + int src_fd = -1; + int content_fd = -1; + int pid = -1; + int status; + + TESTSYSCALL(s_mkdir(s_path(s(ft_src), s(folder1)), 0777)); + TEST(content_fd = s_creat(s_pathn(3, s(ft_src), s(folder1), s(content_file)), 0777), + content_fd != -1); + TESTEQUAL(write(content_fd, content, strlen(content)), strlen(content)); + TESTEQUAL(mount_fuse_no_init(mount_dir, NULL, -1, &fuse_dev), 0); + + FUSE_ACTION + DIR *open_mount_dir = NULL; + struct dirent *mount_dirent; + int dst_folder1_fd = -1; + int dst_content_fd = -1; + int dst_content_read_size = -1; + char content_buffer[12]; + + // Step 1: Lookup folder1 + TESTERR(dst_folder1_fd = s_open(s_path(s(mount_dir), s(folder1)), + O_RDONLY | O_CLOEXEC), dst_folder1_fd != -1); + + // Check 1: Read content file (backed) + TESTERR(dst_content_fd = + s_open(s_pathn(3, s(mount_dir), s(folder1), s(content_file)), + O_RDONLY | O_CLOEXEC), dst_content_fd != -1); + + TEST(dst_content_read_size = + read(dst_content_fd, content_buffer, strlen(content)), + dst_content_read_size == strlen(content) && + strcmp(content, content_buffer) == 0); + + TESTSYSCALL(close(dst_content_fd)); + dst_content_fd = -1; + TESTSYSCALL(close(dst_folder1_fd)); + dst_folder1_fd = -1; + memset(content_buffer, 0, strlen(content)); + + // Step 2: readdir folder 1 + TEST(open_mount_dir = s_opendir(s(mount_dir)), + open_mount_dir != NULL); + TEST(mount_dirent = readdir(open_mount_dir), mount_dirent != NULL); + TESTSYSCALL(closedir(open_mount_dir)); + open_mount_dir = NULL; + + // Check 2: Read content file again (must be backed) + TESTERR(dst_content_fd = + s_open(s_pathn(3, s(mount_dir), s(folder1), s(content_file)), + O_RDONLY | O_CLOEXEC), dst_content_fd != -1); + + TEST(dst_content_read_size = + read(dst_content_fd, content_buffer, strlen(content)), + dst_content_read_size == strlen(content) && + strcmp(content, content_buffer) == 0); + + TESTSYSCALL(close(dst_content_fd)); + dst_content_fd = -1; + FUSE_DAEMON + size_t read_size = 0; + struct fuse_in_header *in_header = (struct fuse_in_header *)bytes_in; + struct fuse_read_out *read_out = NULL; + struct fuse_attr attr = {}; + int backing_fd = -1; + DECL_FUSE_IN(open); + DECL_FUSE_IN(getattr); + + TESTFUSEINITFLAGS(FUSE_DO_READDIRPLUS | FUSE_READDIRPLUS_AUTO); + + // Step 1: Lookup folder 1 with backing + TESTFUSELOOKUP(folder1, 0); + TESTSYSCALL(s_fuse_attr(s_path(s(ft_src), s(folder1)), &attr)); + TEST(backing_fd = s_open(s_path(s(ft_src), s(folder1)), + O_DIRECTORY | O_RDONLY | O_CLOEXEC), + backing_fd != -1); + TESTFUSEOUT2_IOCTL(fuse_entry_out, ((struct fuse_entry_out) { + .nodeid = attr.ino, + .generation = 0, + .entry_valid = UINT64_MAX, + .attr_valid = UINT64_MAX, + .entry_valid_nsec = UINT32_MAX, + .attr_valid_nsec = UINT32_MAX, + .attr = attr, + }), fuse_bpf_entry_out, ((struct fuse_bpf_entry_out) { + .entry_type = FUSE_ENTRY_BACKING, + .fd = backing_fd, + })); + TESTSYSCALL(close(backing_fd)); + + // Step 2: Open root dir + TESTFUSEIN(FUSE_OPENDIR, open_in); + TESTFUSEOUT1(fuse_open_out, ((struct fuse_open_out) { + .fh = 100, + .open_flags = open_in->flags + })); + + // Step 2: Handle getattr + TESTFUSEIN(FUSE_GETATTR, getattr_in); + TESTSYSCALL(s_fuse_attr(s(ft_src), &attr)); + TESTFUSEOUT1(fuse_attr_out, ((struct fuse_attr_out) { + .attr_valid = UINT64_MAX, + .attr_valid_nsec = UINT32_MAX, + .attr = attr + })); + + // Step 2: Handle readdirplus + read_size = read(fuse_dev, bytes_in, sizeof(bytes_in)); + TESTEQUAL(in_header->opcode, FUSE_READDIRPLUS); + + struct fuse_direntplus *dirent_plus = + (struct fuse_direntplus *) (bytes_in + read_size); + struct fuse_dirent dirent; + struct fuse_entry_out entry_out; + + read_out = (struct fuse_read_out *) (bytes_in + + sizeof(*in_header) + + sizeof(struct fuse_read_in)); + + TESTSYSCALL(s_fuse_attr(s_path(s(ft_src), s(folder1)), &attr)); + + dirent = (struct fuse_dirent) { + .ino = attr.ino, + .off = 1, + .namelen = strlen(folder1), + .type = DT_REG + }; + entry_out = (struct fuse_entry_out) { + .nodeid = attr.ino, + .generation = 0, + .entry_valid = UINT64_MAX, + .attr_valid = UINT64_MAX, + .entry_valid_nsec = UINT32_MAX, + .attr_valid_nsec = UINT32_MAX, + .attr = attr + }; + *dirent_plus = (struct fuse_direntplus) { + .dirent = dirent, + .entry_out = entry_out + }; + + strcpy((char *)(bytes_in + read_size + sizeof(*dirent_plus)), folder1); + read_size += FUSE_DIRENT_ALIGN(sizeof(*dirent_plus) + strlen(folder1) + + 1); + TESTFUSEDIROUTREAD(read_out, + bytes_in + + sizeof(struct fuse_in_header) + + sizeof(struct fuse_read_in) + + sizeof(struct fuse_read_out), + read_size - sizeof(struct fuse_in_header) - + sizeof(struct fuse_read_in) - + sizeof(struct fuse_read_out)); + FUSE_DONE + + result = TEST_SUCCESS; + +out: + close(fuse_dev); + close(content_fd); + close(src_fd); + umount(mount_dir); + return result; +} + +static int bpf_test_no_readdirplus_without_nodeid(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *folder1 = "folder1"; + const char *folder2 = "folder2"; + int result = TEST_FAILURE; + int fuse_dev = -1; + int src_fd = -1; + int content_fd = -1; + int pid = -1; + int status; + + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.readdir_plus_ops), test_link != NULL); + TESTSYSCALL(s_mkdir(s_path(s(ft_src), s(folder1)), 0777)); + TESTSYSCALL(s_mkdir(s_path(s(ft_src), s(folder2)), 0777)); + TESTEQUAL(mount_fuse_no_init(mount_dir, NULL, -1, &fuse_dev), 0); + FUSE_ACTION + DIR *open_dir = NULL; + struct dirent *dirent; + + // Folder 1: Readdir with no nodeid + TEST(open_dir = s_opendir(s_path(s(ft_dst), s(folder1))), + open_dir != NULL); + TEST(dirent = readdir(open_dir), dirent == NULL); + TESTCOND(errno == EINVAL); + TESTSYSCALL(closedir(open_dir)); + open_dir = NULL; + + // Folder 2: Readdir with a nodeid + TEST(open_dir = s_opendir(s_path(s(ft_dst), s(folder2))), + open_dir != NULL); + TEST(dirent = readdir(open_dir), dirent == NULL); + TESTCOND(errno == EINVAL); + TESTSYSCALL(closedir(open_dir)); + open_dir = NULL; + FUSE_DAEMON + size_t read_size; + struct fuse_in_header *in_header = (struct fuse_in_header *)bytes_in; + struct fuse_attr attr = {}; + int backing_fd = -1; + struct fuse_bpf_entry_out bpf_entry[2]; + + TESTFUSEINITFLAGS(FUSE_DO_READDIRPLUS | FUSE_READDIRPLUS_AUTO); + + // folder 1: Set 0 as nodeid, Expect READDIR + TESTFUSELOOKUP(folder1, 0); + TEST(backing_fd = s_open(s_path(s(ft_src), s(folder1)), + O_DIRECTORY | O_RDONLY | O_CLOEXEC), + backing_fd != -1); + + bpf_entry[0] = (struct fuse_bpf_entry_out) { + .entry_type = FUSE_ENTRY_BPF, + .name = "readdir_plus", + }; + bpf_entry[1] = (struct fuse_bpf_entry_out) { + .entry_type = FUSE_ENTRY_BACKING, + .fd = backing_fd, + }; + TESTFUSEOUT3_IOCTL(fuse_entry_out, ((struct fuse_entry_out) { + .nodeid = 0, + .generation = 0, + .entry_valid = UINT64_MAX, + .attr_valid = UINT64_MAX, + .entry_valid_nsec = UINT32_MAX, + .attr_valid_nsec = UINT32_MAX, + .attr = attr, + }), fuse_bpf_entry_out, bpf_entry[0], + fuse_bpf_entry_out, bpf_entry[1]); + TESTSYSCALL(close(backing_fd)); + TEST(read_size = read(fuse_dev, bytes_in, sizeof(bytes_in)), read_size > 0); + TESTEQUAL(in_header->opcode, FUSE_READDIR); + TESTFUSEOUTERROR(-EINVAL); + + // folder 2: Set 10 as nodeid, Expect READDIRPLUS + TESTFUSELOOKUP(folder2, 0); + TEST(backing_fd = s_open(s_path(s(ft_src), s(folder2)), + O_DIRECTORY | O_RDONLY | O_CLOEXEC), + backing_fd != -1); + bpf_entry[0] = (struct fuse_bpf_entry_out) { + .entry_type = FUSE_ENTRY_BPF, + .name = "readdir_plus", + }; + bpf_entry[1] = (struct fuse_bpf_entry_out) { + .entry_type = FUSE_ENTRY_BACKING, + .fd = backing_fd, + }; + TESTFUSEOUT3_IOCTL(fuse_entry_out, ((struct fuse_entry_out) { + .nodeid = 10, + .generation = 0, + .entry_valid = UINT64_MAX, + .attr_valid = UINT64_MAX, + .entry_valid_nsec = UINT32_MAX, + .attr_valid_nsec = UINT32_MAX, + .attr = attr, + }), fuse_bpf_entry_out, bpf_entry[0], + fuse_bpf_entry_out, bpf_entry[1]); + TESTSYSCALL(close(backing_fd)); + TEST(read_size = read(fuse_dev, bytes_in, sizeof(bytes_in)), read_size > 0); + TESTEQUAL(in_header->opcode, FUSE_READDIRPLUS); + TESTFUSEOUTERROR(-EINVAL); + FUSE_DONE + result = TEST_SUCCESS; +out: + close(fuse_dev); + close(content_fd); + close(src_fd); + umount(mount_dir); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +/* + * State: + * Original: dst/folder1/content.txt + * ^ + * | + * | + * Backing: src/folder1/content.txt + * + * Step 1: open(folder1) - lookup folder1 with entry_timeout set to 0 + * Step 2: open(folder1) - lookup folder1 again to trigger revalidate wich will + * set backing fd + * + * Check 1: cat(content.txt) - check not receiving call on the fuse daemon + * and content is the same + */ +static int bpf_test_revalidate_handle_backing_fd(const char *mount_dir) +{ + const char *folder1 = "folder1"; + const char *content_file = "content.txt"; + const char *content = "hello world"; + int result = TEST_FAILURE; + int fuse_dev = -1; + int src_fd = -1; + int content_fd = -1; + int pid = -1; + int status; + TESTSYSCALL(s_mkdir(s_path(s(ft_src), s(folder1)), 0777)); + TEST(content_fd = s_creat(s_pathn(3, s(ft_src), s(folder1), s(content_file)), 0777), + content_fd != -1); + TESTEQUAL(write(content_fd, content, strlen(content)), strlen(content)); + TESTSYSCALL(close(content_fd)); + content_fd = -1; + TESTEQUAL(mount_fuse_no_init(mount_dir, NULL, -1, &fuse_dev), 0); + FUSE_ACTION + int dst_folder1_fd = -1; + int dst_content_fd = -1; + int dst_content_read_size = -1; + char content_buffer[11] = {0}; + // Step 1: Lookup folder1 + TESTERR(dst_folder1_fd = s_open(s_path(s(mount_dir), s(folder1)), + O_RDONLY | O_CLOEXEC), dst_folder1_fd != -1); + TESTSYSCALL(close(dst_folder1_fd)); + dst_folder1_fd = -1; + // Step 2: Lookup folder1 again + TESTERR(dst_folder1_fd = s_open(s_path(s(mount_dir), s(folder1)), + O_RDONLY | O_CLOEXEC), dst_folder1_fd != -1); + TESTSYSCALL(close(dst_folder1_fd)); + dst_folder1_fd = -1; + // Check 1: Read content file (must be backed) + TESTERR(dst_content_fd = + s_open(s_pathn(3, s(mount_dir), s(folder1), s(content_file)), + O_RDONLY | O_CLOEXEC), dst_content_fd != -1); + TEST(dst_content_read_size = + read(dst_content_fd, content_buffer, strlen(content)), + dst_content_read_size == strlen(content) && + strcmp(content, content_buffer) == 0); + TESTSYSCALL(close(dst_content_fd)); + dst_content_fd = -1; + FUSE_DAEMON + struct fuse_attr attr = {}; + int backing_fd = -1; + TESTFUSEINITFLAGS(FUSE_DO_READDIRPLUS | FUSE_READDIRPLUS_AUTO); + // Step 1: Lookup folder1 set entry_timeout to 0 to trigger + // revalidate later + TESTFUSELOOKUP(folder1, 0); + TESTSYSCALL(s_fuse_attr(s_path(s(ft_src), s(folder1)), &attr)); + TEST(backing_fd = s_open(s_path(s(ft_src), s(folder1)), + O_DIRECTORY | O_RDONLY | O_CLOEXEC), + backing_fd != -1); + TESTFUSEOUT2_IOCTL(fuse_entry_out, ((struct fuse_entry_out) { + .nodeid = attr.ino, + .generation = 0, + .entry_valid = 0, + .attr_valid = UINT64_MAX, + .entry_valid_nsec = 0, + .attr_valid_nsec = UINT32_MAX, + .attr = attr, + }), fuse_bpf_entry_out, ((struct fuse_bpf_entry_out) { + .entry_type = FUSE_ENTRY_BACKING, + .fd = backing_fd, + })); + TESTSYSCALL(close(backing_fd)); + // Step 1: Lookup folder1 as a reaction to revalidate call + // This attempts to change the backing node, which is not allowed on revalidate + TESTFUSELOOKUP(folder1, 0); + TESTSYSCALL(s_fuse_attr(s_path(s(ft_src), s(folder1)), &attr)); + TEST(backing_fd = s_open(s_path(s(ft_src), s(folder1)), + O_DIRECTORY | O_RDONLY | O_CLOEXEC), + backing_fd != -1); + TESTFUSEOUT2_IOCTL(fuse_entry_out, ((struct fuse_entry_out) { + .nodeid = attr.ino, + .generation = 0, + .entry_valid = UINT64_MAX, + .attr_valid = UINT64_MAX, + .entry_valid_nsec = UINT32_MAX, + .attr_valid_nsec = UINT32_MAX, + .attr = attr, + }), fuse_bpf_entry_out, ((struct fuse_bpf_entry_out) { + .entry_type = FUSE_ENTRY_BACKING, + .fd = backing_fd, + })); + TESTSYSCALL(close(backing_fd)); + + // Lookup folder1 as a reaction to failed revalidate + TESTFUSELOOKUP(folder1, 0); + TESTSYSCALL(s_fuse_attr(s_path(s(ft_src), s(folder1)), &attr)); + TEST(backing_fd = s_open(s_path(s(ft_src), s(folder1)), + O_DIRECTORY | O_RDONLY | O_CLOEXEC), + backing_fd != -1); + TESTFUSEOUT2_IOCTL(fuse_entry_out, ((struct fuse_entry_out) { + .nodeid = attr.ino, + .generation = 0, + .entry_valid = UINT64_MAX, + .attr_valid = UINT64_MAX, + .entry_valid_nsec = UINT32_MAX, + .attr_valid_nsec = UINT32_MAX, + .attr = attr, + }), fuse_bpf_entry_out, ((struct fuse_bpf_entry_out) { + .entry_type = FUSE_ENTRY_BACKING, + .fd = backing_fd, + })); + TESTSYSCALL(close(backing_fd)); + FUSE_DONE + result = TEST_SUCCESS; +out: + close(fuse_dev); + close(content_fd); + close(src_fd); + umount(mount_dir); + return result; +} + +static int bpf_test_lookup_postfilter(const char *mount_dir) +{ + struct test_bpf *test_skel = NULL; + struct bpf_link *test_link = NULL; + const char *file1_name = "file1"; + const char *file2_name = "file2"; + const char *file3_name = "file3"; + int result = TEST_FAILURE; + int bpf_fd = -1; + int src_fd = -1; + int fuse_dev = -1; + int file_fd = -1; + int pid = -1; + int status; + + TEST(file_fd = s_creat(s_path(s(ft_src), s(file1_name)), 0777), + file_fd != -1); + TESTSYSCALL(close(file_fd)); + TEST(file_fd = s_creat(s_path(s(ft_src), s(file2_name)), 0777), + file_fd != -1); + TESTSYSCALL(close(file_fd)); + file_fd = -1; + TEST(src_fd = open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd != -1); + TEST(test_skel = test_bpf__open_and_load(), test_skel != NULL); + TEST(test_link = bpf_map__attach_struct_ops(test_skel->maps.lookup_postfilter_ops), test_link != NULL); + TESTEQUAL(mount_fuse(mount_dir, "lookup_post", src_fd, &fuse_dev), 0); + FUSE_ACTION + int fd = -1; + + TESTEQUAL(s_open(s_path(s(mount_dir), s(file1_name)), O_RDONLY), + -1); + TESTEQUAL(errno, ENOENT); + TEST(fd = s_open(s_path(s(mount_dir), s(file2_name)), O_RDONLY), + fd != -1); + TESTSYSCALL(close(fd)); + TESTEQUAL(s_open(s_path(s(mount_dir), s(file3_name)), O_RDONLY), + -1); + FUSE_DAEMON + struct fuse_entry_out *feo; + uint32_t *err_in; + + TESTFUSELOOKUP(file1_name, FUSE_POSTFILTER); + TESTFUSEOUTERROR(-ENOENT); + + TESTFUSELOOKUP(file2_name, FUSE_POSTFILTER); + feo = (struct fuse_entry_out *) (bytes_in + + sizeof(struct fuse_in_header) + strlen(file2_name) + 1); + TESTFUSEOUT1(fuse_entry_out, *feo); + + TESTFUSELOOKUP_POST_ERRIN(file3_name, err_in); + TESTEQUAL(*err_in, -ENOENT); + TESTFUSEOUTERROR(-ENOENT); + FUSE_DONE + + result = TEST_SUCCESS; +out: + close(file_fd); + close(fuse_dev); + umount(mount_dir); + close(src_fd); + close(bpf_fd); + bpf_link__destroy(test_link); + test_bpf__destroy(test_skel); + return result; +} + +static void parse_range(const char *ranges, bool *run_test, size_t tests) +{ + size_t i; + char *range; + + for (i = 0; i < tests; ++i) + run_test[i] = false; + + range = strtok(optarg, ","); + while (range) { + char *dash = strchr(range, '-'); + + if (dash) { + size_t start = 1, end = tests; + char *end_ptr; + + if (dash > range) { + start = strtol(range, &end_ptr, 10); + if (*end_ptr != '-' || start <= 0 || start > tests) + ksft_exit_fail_msg("Bad range\n"); + } + + if (dash[1]) { + end = strtol(dash + 1, &end_ptr, 10); + if (*end_ptr || end <= start || end > tests) + ksft_exit_fail_msg("Bad range\n"); + } + + for (i = start; i <= end; ++i) + run_test[i - 1] = true; + } else { + char *end; + long value = strtol(range, &end, 10); + + if (*end || value <= 0 || value > tests) + ksft_exit_fail_msg("Bad range\n"); + run_test[value - 1] = true; + } + range = strtok(NULL, ","); + } +} + +static int parse_options(int argc, char *const *argv, bool *run_test, + size_t tests) +{ + signed char c; + + while ((c = getopt(argc, argv, "f:t:v")) != -1) + switch (c) { + case 'f': + test_options.file = strtol(optarg, NULL, 10); + break; + + case 't': + parse_range(optarg, run_test, tests); + break; + + case 'v': + test_options.verbose = true; + break; + + default: + return -EINVAL; + } + + return 0; +} + +struct test_case { + int (*pfunc)(const char *dir); + const char *name; +}; + +static void run_one_test(const char *mount_dir, + const struct test_case *test_case) +{ + ksft_print_msg("Running %s\n", test_case->name); + bpf_clear_trace(); + if (test_case->pfunc(mount_dir) == TEST_SUCCESS) + ksft_test_result_pass("%s\n", test_case->name); + else + ksft_test_result_fail("%s\n", test_case->name); +} + +int main(int argc, char *argv[]) +{ + char *mount_dir = NULL; + char *src_dir = NULL; + int i; + int fd, count; + +#define MAKE_TEST(test) \ + { \ + test, #test \ + } + const struct test_case cases[] = { + MAKE_TEST(basic_test), + MAKE_TEST(bpf_test_real), + MAKE_TEST(bpf_test_partial), + MAKE_TEST(bpf_test_attrs), + MAKE_TEST(bpf_test_readdir), + MAKE_TEST(bpf_test_creat), + MAKE_TEST(bpf_test_hidden_entries), + MAKE_TEST(bpf_test_dir), + MAKE_TEST(bpf_test_file_early_close), + MAKE_TEST(bpf_test_file_late_close), + MAKE_TEST(bpf_test_mknod), + MAKE_TEST(bpf_test_largedir), + MAKE_TEST(bpf_test_link), + MAKE_TEST(bpf_test_symlink), + MAKE_TEST(bpf_test_xattr), + MAKE_TEST(bpf_test_redact_readdir), + MAKE_TEST(bpf_test_set_backing), + MAKE_TEST(bpf_test_set_backing_no_ioctl), + MAKE_TEST(bpf_test_set_backing_folder), + MAKE_TEST(bpf_test_remove_backing), + MAKE_TEST(bpf_test_dir_rename), + MAKE_TEST(bpf_test_file_rename), + MAKE_TEST(bpf_test_alter_errcode_bpf), + MAKE_TEST(bpf_test_alter_errcode_userspace), + MAKE_TEST(mmap_test), + MAKE_TEST(readdir_perms_test), + MAKE_TEST(bpf_test_statfs), + MAKE_TEST(bpf_test_lseek), + MAKE_TEST(bpf_test_readdirplus_not_overriding_backing), + MAKE_TEST(bpf_test_no_readdirplus_without_nodeid), + MAKE_TEST(bpf_test_revalidate_handle_backing_fd), + MAKE_TEST(bpf_test_lookup_postfilter), + //MAKE_TEST(bpf_test_verifier), + //MAKE_TEST(bpf_test_verifier_out_args), + //MAKE_TEST(bpf_test_verifier_packet_invalidation), + //MAKE_TEST(bpf_test_verifier_nonsense_read) + }; +#undef MAKE_TEST + + bool run_test[ARRAY_SIZE(cases)]; + + for (int i = 0; i < ARRAY_SIZE(cases); ++i) + run_test[i] = true; + + if (parse_options(argc, argv, run_test, ARRAY_SIZE(cases))) + ksft_exit_fail_msg("Bad options\n"); + + // Seed randomness pool for testing on QEMU + // NOTE - this abuses the concept of randomness - do *not* ever do this + // on a machine for production use - the device will think it has good + // randomness when it does not. + fd = open("/dev/urandom", O_WRONLY | O_CLOEXEC); + count = 4096; + for (int i = 0; i < 128; ++i) + ioctl(fd, RNDADDTOENTCNT, &count); + close(fd); + + ksft_print_header(); + + if (geteuid() != 0) + ksft_print_msg("Not a root, might fail to mount.\n"); + + if (tracing_on() != TEST_SUCCESS) + ksft_exit_fail_msg("Can't turn on tracing\n"); + + src_dir = setup_mount_dir(ft_src); + mount_dir = setup_mount_dir(ft_dst); + if (src_dir == NULL || mount_dir == NULL) + ksft_exit_fail_msg("Can't create a mount dir\n"); + + ksft_set_plan(ARRAY_SIZE(run_test)); + + for (i = 0; i < ARRAY_SIZE(run_test); ++i) + if (run_test[i]) { + delete_dir_tree(mount_dir, false); + delete_dir_tree(src_dir, false); + run_one_test(mount_dir, &cases[i]); + } else + ksft_cnt.ksft_xskip++; + + umount2(mount_dir, MNT_FORCE); + delete_dir_tree(mount_dir, true); + delete_dir_tree(src_dir, true); + return !ksft_get_fail_cnt() ? ksft_exit_pass() : ksft_exit_fail(); +} diff --git a/tools/testing/selftests/filesystems/fuse/test.bpf.c b/tools/testing/selftests/filesystems/fuse/test.bpf.c new file mode 100644 index 000000000000..3128bf50016f --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/test.bpf.c @@ -0,0 +1,996 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +// Copyright (c) 2021 Google LLC + +#include "vmlinux.h" +#include <linux/errno.h> +#include <linux/types.h> +#include <bpf/bpf_tracing.h> +#include <bpf/bpf_helpers.h> + +#include <stdbool.h> + +#include "bpf_common.h" + +char _license[] SEC("license") = "GPL"; + +#if 0 +inline __always_inline int local_strcmp(const char *a, const char *b) +{ + int i; + + for (i = 0; i < __builtin_strlen(b) + 1; ++i) + if (a[i] != b[i]) + return -1; + return 0; +} + + +/* This is a macro to enforce inlining. Without it, the compiler will do the wrong thing for bpf */ +#define strcmp_check(a, b, end_b) \ + (((b) + __builtin_strlen(a) + 1 > (end_b)) ? -1 : local_strcmp((b), (a))) +#endif + +//trace ops + +BPF_STRUCT_OPS(uint32_t, trace_access_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_access_in *in) +{ + bpf_printk("Access: %d", meta->nodeid); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_getattr_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_getattr_in *in) +{ + bpf_printk("Get Attr %d", in->fh); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_setattr_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_setattr_in *in) +{ + bpf_printk("Set Attr %d", in->fh); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_opendir_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_open_in *in) +{ + bpf_printk("Open Dir: %d", meta->nodeid); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_readdir_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_read_in *in) +{ + bpf_printk("Read Dir: fh: %lu", in->fh, in->offset); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_lookup_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char *name_buf; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + name_buf = bpf_dynptr_slice(&name_ptr, 0, NULL, 1); + bpf_printk("Lookup: %lx %s", meta->nodeid, name_buf); + if (meta->nodeid == 1) + return BPF_FUSE_USER_PREFILTER; + else + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_mknod_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_mknod_in *in, struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char name_buf[255]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + bpf_dynptr_read(name_buf, 255, &name_ptr, 0, 0); + bpf_printk("mknod %s %x %x", name_buf, in->rdev | in->mode, in->umask); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_mkdir_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_mkdir_in *in, struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char name_buf[255]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + bpf_dynptr_read(name_buf, 255, &name_ptr, 0, 0); + bpf_printk("mkdir: %s %x %x", name_buf, in->mode, in->umask); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_rmdir_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char name_buf[255]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + bpf_dynptr_read(name_buf, 255, &name_ptr, 0, 0); + bpf_printk("rmdir: %s", name_buf); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_rename_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_rename_in *in, struct fuse_buffer *old_name, + struct fuse_buffer *new_name) +{ + struct bpf_dynptr old_name_ptr; + struct bpf_dynptr new_name_ptr; + char old_name_buf[255]; + //char new_name_buf[255]; + + bpf_fuse_get_ro_dynptr(old_name, &old_name_ptr); + //bpf_fuse_get_ro_dynptr(new_name, &new_name_ptr); + bpf_dynptr_read(old_name_buf, 255, &old_name_ptr, 0, 0); + //bpf_dynptr_read(new_name_buf, 255, &new_name_ptr, 0, 0); + bpf_printk("rename from %s", old_name_buf); + //bpf_printk("rename to %s", new_name_buf); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_rename2_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_rename2_in *in, struct fuse_buffer *old_name, + struct fuse_buffer *new_name) +{ + struct bpf_dynptr old_name_ptr; + //struct bpf_dynptr new_name_ptr; + char old_name_buf[255]; + //char new_name_buf[255]; + + bpf_fuse_get_ro_dynptr(old_name, &old_name_ptr); + //bpf_fuse_get_ro_dynptr(new_name, &new_name_ptr); + bpf_dynptr_read(old_name_buf, 255, &old_name_ptr, 0, 0); + //bpf_dynptr_read(new_name_buf, 255, &new_name_ptr, 0, 0); + bpf_printk("rename(%x) from %s", in->flags, old_name_buf); + //bpf_printk("rename to %s", new_name_buf); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_unlink_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char name_buf[255]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + bpf_dynptr_read(name_buf, 255, &name_ptr, 0, 0); + bpf_printk("unlink: %s", name_buf); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_link_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_link_in *in, struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char dst_name[255]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + bpf_dynptr_read(dst_name, 255, &name_ptr, 0, 0); + bpf_printk("link: %d %s", in->oldnodeid, dst_name); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_symlink_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name, struct fuse_buffer *path) +{ + struct bpf_dynptr name_ptr; + //struct bpf_dynptr path_ptr; + char link_name[255]; + //char link_path[4096]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + //bpf_fuse_get_ro_dynptr(path, &path_ptr); + bpf_dynptr_read(link_name, 255, &name_ptr, 0, 0); + //bpf_dynptr_read(link_path, 4096, &path_ptr, 0, 0); + + bpf_printk("symlink from %s", link_name); + //bpf_printk("symlink to %s", link_path); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_get_link_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char link_name[255]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + bpf_dynptr_read(link_name, 255, &name_ptr, 0, 0); + bpf_printk("readlink from %s", link_name); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_release_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_release_in *in) +{ + bpf_printk("Release: %d", in->fh); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_releasedir_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_release_in *in) +{ + bpf_printk("Release Dir: %d", in->fh); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_create_open_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_create_in *in, struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char name_buf[255]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + bpf_dynptr_read(name_buf, 255, &name_ptr, 0, 0); + bpf_printk("Create %s", name_buf); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_open_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_open_in *in) +{ + bpf_printk("Open: %d", meta->nodeid); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_read_iter_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_read_in *in) +{ + bpf_printk("Read: fh: %lu, offset %lu, size %lu", + in->fh, in->offset, in->size); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_write_iter_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_write_in *in) +{ + bpf_printk("Write: fh: %lu, offset %lu, size %lu", + in->fh, in->offset, in->size); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_flush_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_flush_in *in) +{ + bpf_printk("flush %d", in->fh); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_file_fallocate_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_fallocate_in *in) +{ + bpf_printk("fallocate %d %lu", in->fh, in->length); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_getxattr_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_getxattr_in *in, struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char name_buf[255]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + bpf_dynptr_read(name_buf, 255, &name_ptr, 0, 0); + bpf_printk("getxattr %d %s", meta->nodeid, name_buf); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_listxattr_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_getxattr_in *in) +{ + bpf_printk("listxattr %d %d", meta->nodeid, in->size); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_setxattr_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_setxattr_in *in, struct fuse_buffer *name, + struct fuse_buffer *value) +{ + struct bpf_dynptr name_ptr; + char name_buf[255]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + bpf_dynptr_read(name_buf, 255, &name_ptr, 0, 0); + bpf_printk("setxattr %d %s", meta->nodeid, name_buf); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_removexattr_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char name_buf[255]; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + bpf_dynptr_read(name_buf, 255, &name_ptr, 0, 0); + bpf_printk("removexattr %d %s", meta->nodeid, name_buf); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_statfs_prefilter, const struct bpf_fuse_meta_info *meta) +{ + bpf_printk("statfs %d", meta->nodeid); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, trace_lseek_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_lseek_in *in) +{ + bpf_printk("lseek type:%d, offset:%lld", in->whence, in->offset); + return BPF_FUSE_CONTINUE; +} + +// readdir_test_ops +BPF_STRUCT_OPS(uint32_t, readdir_redact_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_read_in *in) +{ + bpf_printk("readdir %d", in->fh); + return BPF_FUSE_POSTFILTER; +} + +BPF_STRUCT_OPS(uint32_t, readdir_redact_postfilter, const struct bpf_fuse_meta_info *meta, + const struct fuse_read_in *in, + struct fuse_read_out *out, struct fuse_buffer *buffer) +{ + bpf_printk("readdir postfilter %x", in->fh); + return BPF_FUSE_USER_POSTFILTER; +} + +// test operations + +BPF_STRUCT_OPS(uint32_t, test_lookup_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char *name_buf; + bool backing = false; + int ret; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + + /* bpf_dynptr_slice will only return a pointer if the dynptr is long enough */ + name_buf = bpf_dynptr_slice(&name_ptr, 0, NULL, 8); + if (name_buf) { + if (bpf_strncmp(name_buf, 8, "partial") == 0) + backing = true; + goto print; + } + + name_buf = bpf_dynptr_slice(&name_ptr, 0, NULL, 6); + if (name_buf) { + if (bpf_strncmp(name_buf, 6, "file1") == 0) + backing = true; + if (bpf_strncmp(name_buf, 6, "file2") == 0) + backing = true; + goto print; + } + + name_buf = bpf_dynptr_slice(&name_ptr, 0, NULL, 5); + if (name_buf) { + if (bpf_strncmp(name_buf, 5, "dir2") == 0) + backing = true; + if (bpf_strncmp(name_buf, 5, "real") == 0) + backing = true; + goto print; + } + + name_buf = bpf_dynptr_slice(&name_ptr, 0, NULL, 4); + if (name_buf) { + if (bpf_strncmp(name_buf, 4, "dir") == 0) + backing = true; + goto print; + } +print: + if (name_buf) + bpf_printk("lookup %s %d", name_buf, backing); + else + bpf_printk("lookup [name length under 3] %d", backing); + return backing ? BPF_FUSE_POSTFILTER : BPF_FUSE_USER; +} + +BPF_STRUCT_OPS(uint32_t, test_lookup_postfilter, const struct bpf_fuse_meta_info *meta, + const struct fuse_buffer *name, + struct fuse_entry_out *out, struct fuse_buffer *entries) +{ + struct bpf_dynptr name_ptr; + char *name_buf; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + + name_buf = bpf_dynptr_slice(&name_ptr, 0, NULL, 8); + if (name_buf) { + if (bpf_strncmp(name_buf, 8, "partial") == 0) + out->nodeid = 6; + goto print; + } + + name_buf = bpf_dynptr_slice(&name_ptr, 0, NULL, 5); + if (name_buf) { + if (bpf_strncmp(name_buf, 5, "real") == 0) + out->nodeid = 5; + goto print; + } +print: + if (name_buf) + bpf_printk("post-lookup %s %d", name_buf, out->nodeid); + else + bpf_printk("post-lookup [name length under 4] %d", out->nodeid); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, test_open_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_open_in *in) +{ + int backing = BPF_FUSE_USER; + + switch (meta->nodeid) { + case 5: + backing = BPF_FUSE_CONTINUE; + bpf_printk("Setting BPF_FUSE_CONTINUE:%d", BPF_FUSE_CONTINUE); + break; + + case 6: + backing = BPF_FUSE_POSTFILTER; + bpf_printk("Setting BPF_FUSE_CONTINUE:%d", BPF_FUSE_POSTFILTER); + break; + + default: + bpf_printk("Setting NOTHING %d", BPF_FUSE_USER); + break; + } + + bpf_printk("open: %d %d", meta->nodeid, backing); + return backing; +} + +BPF_STRUCT_OPS(uint32_t, test_open_postfilter, const struct bpf_fuse_meta_info *meta, + const struct fuse_open_in *in, + struct fuse_open_out *out) +{ + bpf_printk("open postfilter"); + return BPF_FUSE_USER_POSTFILTER; +} + +BPF_STRUCT_OPS(uint32_t, test_read_iter_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_read_in *in) +{ + bpf_printk("read %llu %llu", in->fh, in->offset); + if (in->fh == 1 && in->offset == 0) + return BPF_FUSE_USER; + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, test_getattr_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_getattr_in *in) +{ + /* real and partial use backing file */ + int backing = BPF_FUSE_USER; + + switch (meta->nodeid) { + case 1: + case 5: + case 6: + /* + * TODO: Find better solution + * Add 100 to stop clang compiling to jump table which bpf hates + */ + case 100: + backing = BPF_FUSE_CONTINUE; + break; + } + + bpf_printk("getattr %d %d", meta->nodeid, backing); + return backing; +} + +BPF_STRUCT_OPS(uint32_t, test_setattr_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_setattr_in *in) +{ + /* real and partial use backing file */ + int backing = BPF_FUSE_USER; + + switch (meta->nodeid) { + case 1: + case 5: + case 6: + /* TODO See above */ + case 100: + backing = BPF_FUSE_CONTINUE; + break; + } + + bpf_printk("setattr %d %d", meta->nodeid, backing); + return backing; +} + +BPF_STRUCT_OPS(uint32_t, test_opendir_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_open_in *in) +{ + int backing = BPF_FUSE_USER; + + switch (meta->nodeid) { + case 1: + backing = BPF_FUSE_POSTFILTER; + break; + } + bpf_printk("opendir %d %d", meta->nodeid, backing); + return backing; +} + +BPF_STRUCT_OPS(uint32_t, test_opendir_postfilter, const struct bpf_fuse_meta_info *meta, + const struct fuse_open_in *in, + struct fuse_open_out *out) +{ + out->fh = 2; + bpf_printk("opendir postfilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, test_readdir_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_read_in *in) +{ + int backing = BPF_FUSE_USER; + + if (in->fh == 2) + backing = BPF_FUSE_POSTFILTER; + + bpf_printk("readdir %d %d", in->fh, backing); + return backing; +} + +BPF_STRUCT_OPS(uint32_t, test_readdir_postfilter, const struct bpf_fuse_meta_info *meta, + const struct fuse_read_in *in, + struct fuse_read_out *out, struct fuse_buffer *buffer) +{ + int backing = BPF_FUSE_CONTINUE; + + if (in->fh == 2) + backing = BPF_FUSE_USER_POSTFILTER; + + bpf_printk("readdir postfilter %d %d", in->fh, backing); + return backing; +} + +// test_hidden + +BPF_STRUCT_OPS(uint32_t, hidden_lookup_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char *name_buf; + bool backing = false; + int ret; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + + /* bpf_dynptr_slice will only return a pointer if the dynptr is long enough */ + name_buf = bpf_dynptr_slice(&name_ptr, 0, NULL, 5); + if (name_buf) + bpf_printk("Lookup: %s", name_buf); + else + bpf_printk("lookup [name length under 4]"); + if (name_buf) { + if (bpf_strncmp(name_buf, 5, "show") == 0) + return BPF_FUSE_CONTINUE; + if (bpf_strncmp(name_buf, 5, "hide") == 0) + return -ENOENT; + } + + return BPF_FUSE_CONTINUE; +} + +// test_error + +BPF_STRUCT_OPS(uint32_t, error_mkdir_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_mkdir_in *in, struct fuse_buffer *name) +{ + bpf_printk("mkdir"); + + return BPF_FUSE_POSTFILTER; +} + +BPF_STRUCT_OPS(uint32_t, error_mkdir_postfilter, const struct bpf_fuse_meta_info *meta, + const struct fuse_mkdir_in *in, const struct fuse_buffer *name) +{ + bpf_printk("mkdir postfilter"); + + if (meta->error_in == -EEXIST) + return -EPERM; + return 0; +} + +BPF_STRUCT_OPS(uint32_t, error_lookup_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char *name_buf; + bool backing = false; + int ret; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + + /* bpf_dynptr_slice will only return a pointer if the dynptr is long enough */ + name_buf = bpf_dynptr_slice(&name_ptr, 0, NULL, 1); + bpf_printk("lookup prefilter %s", name); + return BPF_FUSE_POSTFILTER; +} + +BPF_STRUCT_OPS(uint32_t, error_lookup_postfilter, const struct bpf_fuse_meta_info *meta, + const struct fuse_buffer *name, + struct fuse_entry_out *out, struct fuse_buffer *entries) +{ + struct bpf_dynptr name_ptr; + char *name_buf; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + name_buf = bpf_dynptr_slice(&name_ptr, 0, NULL, 13); + if (name_buf) + bpf_printk("post-lookup %s %d", name_buf, out->nodeid); + else + bpf_printk("post-lookup [name length under 13] %d", out->nodeid); + if (name_buf) { + if (bpf_strncmp(name_buf, 13, "doesnotexist") == 0) { + bpf_printk("lookup postfilter doesnotexist"); + return BPF_FUSE_USER_POSTFILTER; + } + } + + return 0; +} + +// test readdirplus + +BPF_STRUCT_OPS(uint32_t, readdirplus_readdir_prefilter, const struct bpf_fuse_meta_info *meta, + struct fuse_read_in *in) +{ + return BPF_FUSE_USER; +} + +// Test passthrough + +// Reuse error_lookup_prefilter + +BPF_STRUCT_OPS(uint32_t, passthrough_lookup_postfilter, const struct bpf_fuse_meta_info *meta, + const struct fuse_buffer *name, + struct fuse_entry_out *out, struct fuse_buffer *entries) +{ + struct bpf_dynptr name_ptr; + struct bpf_dynptr entries_ptr; + char *name_buf; + struct fuse_bpf_entry_out entry; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + name_buf = bpf_dynptr_slice(&name_ptr, 0, NULL, 1); + if (name_buf) + bpf_printk("post-lookup %s %d", name_buf, out->nodeid); + else + bpf_printk("post-lookup [name length under 1???] %d", out->nodeid); + bpf_fuse_get_rw_dynptr(entries, &entries_ptr, sizeof(entry), false); + entry = (struct fuse_bpf_entry_out) { + .entry_type = FUSE_ENTRY_REMOVE_BPF, + }; + bpf_dynptr_write(&entries_ptr, 0, &entry, sizeof(entry), 0); + + return BPF_FUSE_USER_POSTFILTER; +} + +// lookup_postfilter_ops + +//reuse error_lookup_prefilter + +BPF_STRUCT_OPS(uint32_t, test_bpf_lookup_postfilter, const struct bpf_fuse_meta_info *meta, + const struct fuse_buffer *name, + struct fuse_entry_out *out, struct fuse_buffer *entries) +{ + return BPF_FUSE_USER_POSTFILTER; +} + +SEC(".struct_ops") +struct fuse_ops trace_ops = { + .open_prefilter = (void *)trace_open_prefilter, + .opendir_prefilter = (void *)trace_opendir_prefilter, + .create_open_prefilter = (void *)trace_create_open_prefilter, + .release_prefilter = (void *)trace_release_prefilter, + .releasedir_prefilter = (void *)trace_releasedir_prefilter, + .flush_prefilter = (void *)trace_flush_prefilter, + .lseek_prefilter = (void *)trace_lseek_prefilter, + //.copy_file_range_prefilter = (void *)trace_copy_file_range_prefilter, + //.fsync_prefilter = (void *)trace_fsync_prefilter, + //.dir_fsync_prefilter = (void *)trace_dir_fsync_prefilter, + .getxattr_prefilter = (void *)trace_getxattr_prefilter, + .listxattr_prefilter = (void *)trace_listxattr_prefilter, + .setxattr_prefilter = (void *)trace_setxattr_prefilter, + .removexattr_prefilter = (void *)trace_removexattr_prefilter, + .read_iter_prefilter = (void *)trace_read_iter_prefilter, + .write_iter_prefilter = (void *)trace_write_iter_prefilter, + .file_fallocate_prefilter = (void *)trace_file_fallocate_prefilter, + .lookup_prefilter = (void *)trace_lookup_prefilter, + .mknod_prefilter = (void *)trace_mknod_prefilter, + .mkdir_prefilter = (void *)trace_mkdir_prefilter, + .rmdir_prefilter = (void *)trace_rmdir_prefilter, + .rename2_prefilter = (void *)trace_rename2_prefilter, + .rename_prefilter = (void *)trace_rename_prefilter, + .unlink_prefilter = (void *)trace_unlink_prefilter, + .link_prefilter = (void *)trace_link_prefilter, + .getattr_prefilter = (void *)trace_getattr_prefilter, + .setattr_prefilter = (void *)trace_setattr_prefilter, + .statfs_prefilter = (void *)trace_statfs_prefilter, + .get_link_prefilter = (void *)trace_get_link_prefilter, + .symlink_prefilter = (void *)trace_symlink_prefilter, + .readdir_prefilter = (void *)trace_readdir_prefilter, + .access_prefilter = (void *)trace_access_prefilter, + .name = "trace_ops", +}; + +SEC(".struct_ops") +struct fuse_ops test_trace_ops = { + .open_prefilter = (void *)test_open_prefilter, + .open_postfilter = (void *)test_open_postfilter, + .opendir_prefilter = (void *)test_opendir_prefilter, + .opendir_postfilter = (void *)test_opendir_postfilter, + .create_open_prefilter = (void *)trace_create_open_prefilter, + .release_prefilter = (void *)trace_release_prefilter, + .releasedir_prefilter = (void *)trace_releasedir_prefilter, + .flush_prefilter = (void *)trace_flush_prefilter, + .lseek_prefilter = (void *)trace_lseek_prefilter, + //.copy_file_range_prefilter = (void *)trace_copy_file_range_prefilter, + //.fsync_prefilter = (void *)trace_fsync_prefilter, + //.dir_fsync_prefilter = (void *)trace_dir_fsync_prefilter, + .getxattr_prefilter = (void *)trace_getxattr_prefilter, + .listxattr_prefilter = (void *)trace_listxattr_prefilter, + .setxattr_prefilter = (void *)trace_setxattr_prefilter, + .removexattr_prefilter = (void *)trace_removexattr_prefilter, + .read_iter_prefilter = (void *)test_read_iter_prefilter, + .write_iter_prefilter = (void *)trace_write_iter_prefilter, + .file_fallocate_prefilter = (void *)trace_file_fallocate_prefilter, + .lookup_prefilter = (void *)test_lookup_prefilter, + .lookup_postfilter = (void *)test_lookup_postfilter, + .mknod_prefilter = (void *)trace_mknod_prefilter, + .mkdir_prefilter = (void *)trace_mkdir_prefilter, + .rmdir_prefilter = (void *)trace_rmdir_prefilter, + .rename2_prefilter = (void *)trace_rename2_prefilter, + .rename_prefilter = (void *)trace_rename_prefilter, + .unlink_prefilter = (void *)trace_unlink_prefilter, + .link_prefilter = (void *)trace_link_prefilter, + .getattr_prefilter = (void *)test_getattr_prefilter, + .setattr_prefilter = (void *)test_setattr_prefilter, + .statfs_prefilter = (void *)trace_statfs_prefilter, + .get_link_prefilter = (void *)trace_get_link_prefilter, + .symlink_prefilter = (void *)trace_symlink_prefilter, + .readdir_prefilter = (void *)test_readdir_prefilter, + .readdir_postfilter = (void *)test_readdir_postfilter, + .access_prefilter = (void *)trace_access_prefilter, + .name = "test_trace_ops", +}; + +SEC(".struct_ops") +struct fuse_ops readdir_redact_ops = { + .readdir_prefilter = (void *)readdir_redact_prefilter, + .readdir_postfilter = (void *)readdir_redact_postfilter, + .name = "readdir_redact", +}; + +SEC(".struct_ops") +struct fuse_ops test_hidden_ops = { + .lookup_prefilter = (void *)hidden_lookup_prefilter, + .access_prefilter = (void *)trace_access_prefilter, + .create_open_prefilter = (void *)trace_create_open_prefilter, + .name = "test_hidden", +}; + +SEC(".struct_ops") +struct fuse_ops test_error_ops = { + .lookup_prefilter = (void *)error_lookup_prefilter, + .lookup_postfilter = (void *)error_lookup_postfilter, + .mkdir_prefilter = (void *)error_mkdir_prefilter, + .mkdir_postfilter = (void *)error_mkdir_postfilter, + .name = "test_error", +}; + +SEC(".struct_ops") +struct fuse_ops readdir_plus_ops = { + .readdir_prefilter = (void *)readdirplus_readdir_prefilter, + .name = "readdir_plus", +}; + +SEC(".struct_ops") +struct fuse_ops passthrough_ops = { + .lookup_prefilter = (void *)error_lookup_prefilter, + .lookup_postfilter = (void *)passthrough_lookup_postfilter, + .name = "passthrough", +}; + +SEC(".struct_ops") +struct fuse_ops lookup_postfilter_ops = { + .lookup_prefilter = (void *)error_lookup_prefilter, + .lookup_postfilter = (void *)test_bpf_lookup_postfilter, + .name = "lookup_post", +}; + +#if 0 +//TODO: Figure out what to do with these +SEC("test_verify") + +int verify_test(struct __bpf_fuse_args *fa) +{ + if (fa->opcode == (FUSE_MKDIR | FUSE_PREFILTER)) { + const char *start; + const char *end; + const struct fuse_mkdir_in *in; + + start = (void *)(long) fa->in_args[0].value; + end = (void *)(long) fa->in_args[0].end_offset; + if (start + sizeof(*in) <= end) { + in = (struct fuse_mkdir_in *)(start); + bpf_printk("test1: %d %d", in->mode, in->umask); + } + + return BPF_FUSE_CONTINUE; + } + return BPF_FUSE_CONTINUE; +} + +SEC("test_verify_fail") + +int verify_fail_test(struct __bpf_fuse_args *fa) +{ + struct t { + uint32_t a; + uint32_t b; + char d[]; + }; + if (fa->opcode == (FUSE_MKDIR | FUSE_PREFILTER)) { + const char *start; + const char *end; + const struct t *c; + + start = (void *)(long) fa->in_args[0].value; + end = (void *)(long) fa->in_args[0].end_offset; + if (start + sizeof(struct t) <= end) { + c = (struct t *)start; + bpf_printk("test1: %d %d %d", c->a, c->b, c->d[0]); + } + return BPF_FUSE_CONTINUE; + } + return BPF_FUSE_CONTINUE; +} + +SEC("test_verify_fail2") + +int verify_fail_test2(struct __bpf_fuse_args *fa) +{ + if (fa->opcode == (FUSE_MKDIR | FUSE_PREFILTER)) { + const char *start; + const char *end; + struct fuse_mkdir_in *c; + + start = (void *)(long) fa->in_args[0].value; + end = (void *)(long) fa->in_args[1].end_offset; + if (start + sizeof(*c) <= end) { + c = (struct fuse_mkdir_in *)start; + bpf_printk("test1: %d %d", c->mode, c->umask); + } + return BPF_FUSE_CONTINUE; + } + return BPF_FUSE_CONTINUE; +} + +SEC("test_verify_fail3") +/* Cannot write directly to fa */ +int verify_fail_test3(struct __bpf_fuse_args *fa) +{ + if (fa->opcode == (FUSE_LOOKUP | FUSE_POSTFILTER)) { + const char *name = (void *)(long)fa->in_args[0].value; + const char *end = (void *)(long)fa->in_args[0].end_offset; + struct fuse_entry_out *feo = fa_verify_out(fa, 0, sizeof(*feo)); + + if (!feo) + return -1; + + if (strcmp_check("real", name, end) == 0) + feo->nodeid = 5; + else if (strcmp_check("partial", name, end) == 0) + feo->nodeid = 6; + + bpf_printk("post-lookup %s %d", name, feo->nodeid); + return BPF_FUSE_CONTINUE; + } + return BPF_FUSE_CONTINUE; +} + +SEC("test_verify_fail4") +/* Cannot write outside of requested area */ +int verify_fail_test4(struct __bpf_fuse_args *fa) +{ + if (fa->opcode == (FUSE_LOOKUP | FUSE_POSTFILTER)) { + const char *name = (void *)(long)fa->in_args[0].value; + const char *end = (void *)(long)fa->in_args[0].end_offset; + struct fuse_entry_out *feo = bpf_make_writable_out(fa, 0, fa->out_args[0].value, + 1, true); + + if (!feo) + return -1; + + if (strcmp_check("real", name, end) == 0) + feo->nodeid = 5; + else if (strcmp_check("partial", name, end) == 0) + feo->nodeid = 6; + + bpf_printk("post-lookup %s %d", name, feo->nodeid); + return BPF_FUSE_CONTINUE; + } + return BPF_FUSE_CONTINUE; +} + +SEC("test_verify_fail5") +/* Cannot use old verification after requesting writable */ +int verify_fail_test5(struct __bpf_fuse_args *fa) +{ + if (fa->opcode == (FUSE_LOOKUP | FUSE_POSTFILTER)) { + struct fuse_entry_out *feo; + struct fuse_entry_out *feo_w; + + feo = fa_verify_out(fa, 0, sizeof(*feo)); + if (!feo) + return -1; + + feo_w = bpf_make_writable_out(fa, 0, fa->out_args[0].value, sizeof(*feo_w), true); + bpf_printk("post-lookup %d", feo->nodeid); + if (!feo_w) + return -1; + + feo_w->nodeid = 5; + + return BPF_FUSE_CONTINUE; + } + return BPF_FUSE_CONTINUE; +} + +SEC("test_verify5") +/* Can use new verification after requesting writable */ +int verify_pass_test5(struct __bpf_fuse_args *fa) +{ + if (fa->opcode == (FUSE_LOOKUP | FUSE_POSTFILTER)) { + struct fuse_entry_out *feo; + struct fuse_entry_out *feo_w; + + feo = fa_verify_out(fa, 0, sizeof(*feo)); + if (!feo) + return -1; + + bpf_printk("post-lookup %d", feo->nodeid); + + feo_w = bpf_make_writable_out(fa, 0, fa->out_args[0].value, sizeof(*feo_w), true); + + feo = fa_verify_out(fa, 0, sizeof(*feo)); + if (feo) + bpf_printk("post-lookup %d", feo->nodeid); + if (!feo_w) + return -1; + + feo_w->nodeid = 5; + + return BPF_FUSE_CONTINUE; + } + return BPF_FUSE_CONTINUE; +} + +SEC("test_verify_fail6") +/* Reading context from a nonsense offset is not allowed */ +int verify_pass_test6(struct __bpf_fuse_args *fa) +{ + char *nonsense = (char *)fa; + + bpf_printk("post-lookup %d", nonsense[1]); + + return BPF_FUSE_CONTINUE; +} +#endif diff --git a/tools/testing/selftests/filesystems/fuse/test_framework.h b/tools/testing/selftests/filesystems/fuse/test_framework.h new file mode 100644 index 000000000000..24896b5e172f --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/test_framework.h @@ -0,0 +1,172 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2021 Google LLC + */ + +#ifndef _TEST_FRAMEWORK_H +#define _TEST_FRAMEWORK_H + +#include <stdbool.h> +#include <stdio.h> + +#ifdef __ANDROID__ +static int test_case_pass; +static int test_case_fail; +#define ksft_print_msg printf +#define ksft_test_result_pass(...) ({test_case_pass++; printf(__VA_ARGS__); }) +#define ksft_test_result_fail(...) ({test_case_fail++; printf(__VA_ARGS__); }) +#define ksft_exit_fail_msg(...) printf(__VA_ARGS__) +#define ksft_print_header() +#define ksft_set_plan(cnt) +#define ksft_get_fail_cnt() test_case_fail +#define ksft_exit_pass() 0 +#define ksft_exit_fail() 1 +#else +#include <kselftest.h> +#endif + +#define TEST_FAILURE 1 +#define TEST_SUCCESS 0 + +#define ptr_to_u64(p) ((__u64)p) + +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define le16_to_cpu(x) (x) +#define le32_to_cpu(x) (x) +#define le64_to_cpu(x) (x) +#else +#error Big endian not supported! +#endif + +struct _test_options { + int file; + bool verbose; +}; + +extern struct _test_options test_options; + +#define TESTCOND(condition) \ + do { \ + if (!(condition)) { \ + ksft_print_msg("%s failed %d\n", \ + __func__, __LINE__); \ + goto out; \ + } else if (test_options.verbose) \ + ksft_print_msg("%s succeeded %d\n", \ + __func__, __LINE__); \ + } while (false) + +#define TESTCONDERR(condition) \ + do { \ + if (!(condition)) { \ + ksft_print_msg("%s failed %d\n", \ + __func__, __LINE__); \ + ksft_print_msg("Error %d (\"%s\")\n", \ + errno, strerror(errno)); \ + goto out; \ + } else if (test_options.verbose) \ + ksft_print_msg("%s succeeded %d\n", \ + __func__, __LINE__); \ + } while (false) + +#define TEST(statement, condition) \ + do { \ + statement; \ + TESTCOND(condition); \ + } while (false) + +#define TESTERR(statement, condition) \ + do { \ + statement; \ + TESTCONDERR(condition); \ + } while (false) + +enum _operator { + _eq, + _ne, + _ge, +}; + +static const char * const _operator_name[] = { + "==", + "!=", + ">=", +}; + +#define _TEST_OPERATOR(name, _type, format_specifier) \ +static inline int _test_operator_##name(const char *func, int line, \ + _type a, _type b, enum _operator o) \ +{ \ + bool pass; \ + switch (o) { \ + case _eq: pass = a == b; break; \ + case _ne: pass = a != b; break; \ + case _ge: pass = a >= b; break; \ + } \ + \ + if (!pass) \ + ksft_print_msg("Failed: %s at line %d, " \ + format_specifier " %s " \ + format_specifier "\n", \ + func, line, a, _operator_name[o], b); \ + else if (test_options.verbose) \ + ksft_print_msg("Passed: %s at line %d, " \ + format_specifier " %s " \ + format_specifier "\n", \ + func, line, a, _operator_name[o], b); \ + \ + return pass ? TEST_SUCCESS : TEST_FAILURE; \ +} + +_TEST_OPERATOR(i, int, "%d") +_TEST_OPERATOR(ui, unsigned int, "%u") +_TEST_OPERATOR(lui, unsigned long, "%lu") +_TEST_OPERATOR(ss, ssize_t, "%zd") +_TEST_OPERATOR(vp, void *, "%px") +_TEST_OPERATOR(cp, char *, "%px") + +#define _CALL_TO(_type, name, a, b, o) \ + _type:_test_operator_##name(__func__, __LINE__, \ + (_type) (long long) (a), \ + (_type) (long long) (b), o) + +#define TESTOPERATOR(a, b, o) \ + do { \ + if (_Generic((a), \ + _CALL_TO(int, i, a, b, o), \ + _CALL_TO(unsigned int, ui, a, b, o), \ + _CALL_TO(unsigned long, lui, a, b, o), \ + _CALL_TO(ssize_t, ss, a, b, o), \ + _CALL_TO(void *, vp, a, b, o), \ + _CALL_TO(char *, cp, a, b, o) \ + )) \ + goto out; \ + } while (false) + +#define TESTEQUAL(a, b) TESTOPERATOR(a, b, _eq) +#define TESTNE(a, b) TESTOPERATOR(a, b, _ne) +#define TESTGE(a, b) TESTOPERATOR(a, b, _ge) + +/* For testing a syscall that returns 0 on success and sets errno otherwise */ +#define TESTSYSCALL(statement) TESTCONDERR((statement) == 0) + +static inline void print_bytes(const void *data, size_t size) +{ + const char *bytes = data; + int i; + + for (i = 0; i < size; ++i) { + if (i % 0x10 == 0) + printf("%08x:", i); + printf("%02x ", (unsigned int) (unsigned char) bytes[i]); + if (i % 0x10 == 0x0f) + printf("\n"); + } + + if (i % 0x10 != 0) + printf("\n"); +} + + + +#endif diff --git a/tools/testing/selftests/filesystems/fuse/test_fuse.h b/tools/testing/selftests/filesystems/fuse/test_fuse.h new file mode 100644 index 000000000000..ca22b26775a0 --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/test_fuse.h @@ -0,0 +1,494 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2021 Google LLC + */ + +#ifndef TEST_FUSE__H +#define TEST_FUSE__H + +#define _GNU_SOURCE + +#include "test_framework.h" + +#include <dirent.h> +#include <sys/stat.h> +#include <sys/statfs.h> +#include <sys/types.h> + +#include <uapi/linux/fuse.h> + +#define PAGE_SIZE 4096 +#define FUSE_POSTFILTER 0x20000 + +extern struct _test_options test_options; + +/* Slow but semantically easy string functions */ + +/* + * struct s just wraps a char pointer + * It is a pointer to a malloc'd string, or null + * All consumers handle null input correctly + * All consumers free the string + */ +struct s { + char *s; +}; + +struct s s(const char *s1); +struct s sn(const char *s1, const char *s2); +int s_cmp(struct s s1, struct s s2); +struct s s_cat(struct s s1, struct s s2); +struct s s_splitleft(struct s s1, char c); +struct s s_splitright(struct s s1, char c); +struct s s_word(struct s s1, char c, size_t n); +struct s s_path(struct s s1, struct s s2); +struct s s_pathn(size_t n, struct s s1, ...); +int s_link(struct s src_pathname, struct s dst_pathname); +int s_symlink(struct s src_pathname, struct s dst_pathname); +int s_mkdir(struct s pathname, mode_t mode); +int s_rmdir(struct s pathname); +int s_unlink(struct s pathname); +int s_open(struct s pathname, int flags, ...); +int s_openat(int dirfd, struct s pathname, int flags, ...); +int s_creat(struct s pathname, mode_t mode); +int s_mkfifo(struct s pathname, mode_t mode); +int s_stat(struct s pathname, struct stat *st); +int s_statfs(struct s pathname, struct statfs *st); +int s_fuse_attr(struct s pathname, struct fuse_attr *fuse_attr_out); +DIR *s_opendir(struct s pathname); +int s_getxattr(struct s pathname, const char name[], void *value, size_t size, + ssize_t *ret_size); +int s_listxattr(struct s pathname, void *list, size_t size, ssize_t *ret_size); +int s_setxattr(struct s pathname, const char name[], const void *value, + size_t size, int flags); +int s_removexattr(struct s pathname, const char name[]); +int s_rename(struct s oldpathname, struct s newpathname); + +struct s tracing_folder(void); +int tracing_on(void); + +char *concat_file_name(const char *dir, const char *file); +char *setup_mount_dir(const char *name); +int delete_dir_tree(const char *dir_path, bool remove_root); + +#define TESTFUSEINNULL(_opcode) \ + do { \ + struct fuse_in_header *in_header = \ + (struct fuse_in_header *)bytes_in; \ + ssize_t res = read(fuse_dev, &bytes_in, \ + sizeof(bytes_in)); \ + \ + TESTEQUAL(in_header->opcode, _opcode); \ + TESTEQUAL(res, sizeof(*in_header)); \ + } while (false) + +static inline void print_header(struct fuse_in_header *header) +{ + printf("~~HEADER~~"); + printf("len:\t%d\n", header->len); + printf("opcode:\t%d\n", header->opcode); + printf("unique:\t%ld\n", header->unique); + printf("nodeid:\t%ld\n", header->nodeid); + printf("uid:\t%d\n", header->uid); + printf("gid:\t%d\n", header->gid); + printf("pid:\t%d\n", header->pid); + printf("total_extlen:\t%d\n", header->total_extlen); + printf("padding:\t%d\n", header->padding); +} + +static inline int test_fuse_in(int fuse_dev, uint8_t *bytes_in, int opcode, int size) +{ + struct fuse_in_header *in_header = + (struct fuse_in_header *)bytes_in; + ssize_t res = read(fuse_dev, &bytes_in, + sizeof(bytes_in)); + + TESTEQUAL(res, sizeof(*in_header) + size); + TESTEQUAL(in_header->opcode, opcode); + return 0; +out: + return -1; +} + +#define ERR_IN_EXT_LEN (FUSE_REC_ALIGN(sizeof(struct fuse_ext_header) + sizeof(uint32_t))) + +#define TESTFUSEIN(_opcode, in_struct) \ + do { \ + struct fuse_in_header *in_header = \ + (struct fuse_in_header *)bytes_in; \ + ssize_t res = read(fuse_dev, &bytes_in, \ + sizeof(bytes_in)); \ + \ + TESTEQUAL(res, sizeof(*in_header) + sizeof(*in_struct));\ + TESTEQUAL(in_header->opcode, _opcode); \ + in_struct = (void *)(bytes_in + sizeof(*in_header)); \ + } while (false) + +#define TESTFUSEIN_ERR_IN(_opcode, in_struct, err_in) \ + do { \ + struct fuse_in_header *in_header = \ + (struct fuse_in_header *)bytes_in; \ + struct fuse_ext_header *ext_h; \ + ssize_t res = read(fuse_dev, &bytes_in, \ + sizeof(bytes_in)); \ + \ + TESTEQUAL(res, sizeof(*in_header) + sizeof(*in_struct) \ + + ERR_IN_EXT_LEN); \ + TESTEQUAL(in_header->opcode, _opcode); \ + in_struct = (void *)(bytes_in + sizeof(*in_header)); \ + ext_h = (void *)&bytes_in[in_header->len \ + - in_header->total_extlen * 8]; \ + err_in = (void *)&ext_h[1]; \ + } while (false) + +#define TESTFUSEIN2(_opcode, in_struct1, in_struct2) \ + do { \ + struct fuse_in_header *in_header = \ + (struct fuse_in_header *)bytes_in; \ + ssize_t res = read(fuse_dev, &bytes_in, \ + sizeof(bytes_in)); \ + \ + TESTEQUAL(res, sizeof(*in_header) + sizeof(*in_struct1) \ + + sizeof(*in_struct2)); \ + TESTEQUAL(in_header->opcode, _opcode); \ + in_struct1 = (void *)(bytes_in + sizeof(*in_header)); \ + in_struct2 = (void *)(bytes_in + sizeof(*in_header) \ + + sizeof(*in_struct1)); \ + } while (false) + +#define TESTFUSEIN2_ERR_IN(_opcode, in_struct1, in_struct2, err_in) \ + do { \ + struct fuse_in_header *in_header = \ + (struct fuse_in_header *)bytes_in; \ + struct fuse_ext_header *ext_h; \ + ssize_t res = read(fuse_dev, &bytes_in, \ + sizeof(bytes_in)); \ + \ + TESTEQUAL(res, sizeof(*in_header) + sizeof(*in_struct1) \ + + sizeof(*in_struct2) \ + + ERR_IN_EXT_LEN); \ + TESTEQUAL(in_header->opcode, _opcode); \ + in_struct1 = (void *)(bytes_in + sizeof(*in_header)); \ + in_struct2 = (void *)(bytes_in + sizeof(*in_header) \ + + sizeof(*in_struct1)); \ + ext_h = (void *)&bytes_in[in_header->len \ + - in_header->total_extlen * 8]; \ + err_in = (void *)&ext_h[1]; \ + } while (false) + +#define TESTFUSEINEXT(_opcode, in_struct, extra) \ + do { \ + struct fuse_in_header *in_header = \ + (struct fuse_in_header *)bytes_in; \ + ssize_t res = read(fuse_dev, &bytes_in, \ + sizeof(bytes_in)); \ + \ + TESTEQUAL(in_header->opcode, _opcode); \ + TESTEQUAL(res, \ + sizeof(*in_header) + sizeof(*in_struct) + extra);\ + in_struct = (void *)(bytes_in + sizeof(*in_header)); \ + } while (false) + +#define TESTFUSEINUNKNOWN() \ + do { \ + struct fuse_in_header *in_header = \ + (struct fuse_in_header *)bytes_in; \ + ssize_t res = read(fuse_dev, &bytes_in, \ + sizeof(bytes_in)); \ + \ + TESTGE(res, sizeof(*in_header)); \ + TESTEQUAL(in_header->opcode, -1); \ + } while (false) + +/* Special case lookup since it is asymmetric */ +#define TESTFUSELOOKUP(expected, filter) \ + do { \ + struct fuse_in_header *in_header = \ + (struct fuse_in_header *)bytes_in; \ + char *name = (char *) (bytes_in + sizeof(*in_header)); \ + ssize_t res; \ + \ + TEST(res = read(fuse_dev, &bytes_in, sizeof(bytes_in)), \ + res != -1); \ + /* TODO once we handle forgets properly, remove */ \ + if (in_header->opcode == FUSE_FORGET) \ + continue; \ + if (in_header->opcode == FUSE_BATCH_FORGET) \ + continue; \ + TESTGE(res, sizeof(*in_header)); \ + TESTEQUAL(in_header->opcode, \ + FUSE_LOOKUP | filter); \ + /* Post filter only recieves fuse_bpf_entry_out if it's \ + * filled in. TODO: Should we populate this for user \ + * postfilter, and if so, how to handle backing? */ \ + TESTEQUAL(res, \ + sizeof(*in_header) + strlen(expected) + 1 + \ + (filter == FUSE_POSTFILTER ? \ + sizeof(struct fuse_entry_out) + \ + sizeof(struct fuse_bpf_entry_out) * 0 + \ + ERR_IN_EXT_LEN: 0)); \ + TESTCOND(!strcmp(name, expected)); \ + break; \ + } while (true) + +/* Special case lookup since it is asymmetric */ +#define TESTFUSELOOKUP_POST_ERRIN(expected, err_in) \ + do { \ + struct fuse_in_header *in_header = \ + (struct fuse_in_header *)bytes_in; \ + struct fuse_ext_header *ext_h; \ + char *name = (char *) (bytes_in + sizeof(*in_header)); \ + ssize_t res; \ + \ + TEST(res = read(fuse_dev, &bytes_in, sizeof(bytes_in)), \ + res != -1); \ + /* TODO once we handle forgets properly, remove */ \ + if (in_header->opcode == FUSE_FORGET) \ + continue; \ + if (in_header->opcode == FUSE_BATCH_FORGET) \ + continue; \ + TESTGE(res, sizeof(*in_header)); \ + TESTEQUAL(in_header->opcode, \ + FUSE_LOOKUP | FUSE_POSTFILTER); \ + /* Post filter only recieves fuse_bpf_entry_out if it's \ + * filled in. TODO: Should we populate this for user \ + * postfilter, and if so, how to handle backing? */ \ + TESTEQUAL(res, \ + sizeof(*in_header) + strlen(expected) + 1 + \ + sizeof(struct fuse_entry_out) + \ + sizeof(struct fuse_bpf_entry_out) * 0 + \ + ERR_IN_EXT_LEN); \ + TESTCOND(!strcmp(name, expected)); \ + \ + ext_h = (void *)&bytes_in[in_header->len \ + - in_header->total_extlen * 8]; \ + err_in = (void *)&ext_h[1]; \ + break; \ + } while (true) + +#define TESTFUSEOUTEMPTY() \ + do { \ + struct fuse_in_header *in_header = \ + (struct fuse_in_header *)bytes_in; \ + struct fuse_out_header *out_header = \ + (struct fuse_out_header *)bytes_out; \ + \ + *out_header = (struct fuse_out_header) { \ + .len = sizeof(*out_header), \ + .unique = in_header->unique, \ + }; \ + TESTEQUAL(write(fuse_dev, bytes_out, out_header->len), \ + out_header->len); \ + } while (false) + +#define TESTFUSEOUTERROR(errno) \ + do { \ + struct fuse_in_header *in_header = \ + (struct fuse_in_header *)bytes_in; \ + struct fuse_out_header *out_header = \ + (struct fuse_out_header *)bytes_out; \ + \ + *out_header = (struct fuse_out_header) { \ + .len = sizeof(*out_header), \ + .error = errno, \ + .unique = in_header->unique, \ + }; \ + TESTEQUAL(write(fuse_dev, bytes_out, out_header->len), \ + out_header->len); \ + } while (false) + +#define TESTFUSEOUTREAD(data, length) \ + do { \ + struct fuse_in_header *in_header = \ + (struct fuse_in_header *)bytes_in; \ + struct fuse_out_header *out_header = \ + (struct fuse_out_header *)bytes_out; \ + \ + *out_header = (struct fuse_out_header) { \ + .len = sizeof(*out_header) + length, \ + .unique = in_header->unique, \ + }; \ + memcpy(bytes_out + sizeof(*out_header), data, length); \ + TESTEQUAL(write(fuse_dev, bytes_out, out_header->len), \ + out_header->len); \ + } while (false) + +#define TESTFUSEDIROUTREAD(read_out, data, length) \ + do { \ + struct fuse_in_header *in_header = \ + (struct fuse_in_header *)bytes_in; \ + struct fuse_out_header *out_header = \ + (struct fuse_out_header *)bytes_out; \ + \ + *out_header = (struct fuse_out_header) { \ + .len = sizeof(*out_header) + \ + sizeof(*read_out) + length, \ + .unique = in_header->unique, \ + }; \ + memcpy(bytes_out + sizeof(*out_header) + \ + sizeof(*read_out), data, length); \ + memcpy(bytes_out + sizeof(*out_header), \ + read_out, sizeof(*read_out)); \ + TESTEQUAL(write(fuse_dev, bytes_out, out_header->len), \ + out_header->len); \ + } while (false) + +#define TESTFUSEOUT1(type1, obj1) \ + do { \ + *(struct fuse_out_header *) bytes_out \ + = (struct fuse_out_header) { \ + .len = sizeof(struct fuse_out_header) \ + + sizeof(struct type1), \ + .unique = ((struct fuse_in_header *) \ + bytes_in)->unique, \ + }; \ + *(struct type1 *) (bytes_out \ + + sizeof(struct fuse_out_header)) \ + = obj1; \ + TESTEQUAL(write(fuse_dev, bytes_out, \ + ((struct fuse_out_header *)bytes_out)->len), \ + ((struct fuse_out_header *)bytes_out)->len); \ + } while (false) + +#define SETFUSEOUT2(type1, obj1, type2, obj2) \ + do { \ + *(struct fuse_out_header *) bytes_out \ + = (struct fuse_out_header) { \ + .len = sizeof(struct fuse_out_header) \ + + sizeof(struct type1) \ + + sizeof(struct type2), \ + .unique = ((struct fuse_in_header *) \ + bytes_in)->unique, \ + }; \ + *(struct type1 *) (bytes_out \ + + sizeof(struct fuse_out_header)) \ + = obj1; \ + *(struct type2 *) (bytes_out \ + + sizeof(struct fuse_out_header) \ + + sizeof(struct type1)) \ + = obj2; \ + } while (false) + +#define TESTFUSEOUT2(type1, obj1, type2, obj2) \ + do { \ + SETFUSEOUT2(type1, obj1, type2, obj2); \ + TESTEQUAL(write(fuse_dev, bytes_out, \ + ((struct fuse_out_header *)bytes_out)->len), \ + ((struct fuse_out_header *)bytes_out)->len); \ + } while (false) + +#define TESTFUSEOUT2_IOCTL(type1, obj1, type2, obj2) \ + do { \ + SETFUSEOUT2(type1, obj1, type2, obj2); \ + TESTEQUAL(ioctl(fuse_dev, \ + FUSE_DEV_IOC_BPF_RESPONSE( \ + ((struct fuse_out_header *)bytes_out)->len), \ + bytes_out), \ + ((struct fuse_out_header *)bytes_out)->len); \ + } while (false) + +#define SETFUSEOUT3(type1, obj1, type2, obj2, type3, obj3) \ + do { \ + *(struct fuse_out_header *) bytes_out \ + = (struct fuse_out_header) { \ + .len = sizeof(struct fuse_out_header) \ + + sizeof(struct type1) \ + + sizeof(struct type2) \ + + sizeof(struct type3), \ + .unique = ((struct fuse_in_header *) \ + bytes_in)->unique, \ + }; \ + *(struct type1 *) (bytes_out \ + + sizeof(struct fuse_out_header)) \ + = obj1; \ + *(struct type2 *) (bytes_out \ + + sizeof(struct fuse_out_header) \ + + sizeof(struct type1)) \ + = obj2; \ + *(struct type3 *) (bytes_out \ + + sizeof(struct fuse_out_header) \ + + sizeof(struct type1) \ + + sizeof(struct type2)) \ + = obj3; \ + } while (false) + +#define TESTFUSEOUT3(type1, obj1, type2, obj2, type3, obj3) \ + do { \ + SETFUSEOUT3(type1, obj1, type2, obj2, type3, obj3); \ + TESTEQUAL(write(fuse_dev, bytes_out, \ + ((struct fuse_out_header *)bytes_out)->len), \ + ((struct fuse_out_header *)bytes_out)->len); \ + } while (false) + +#define TESTFUSEOUT3_FAIL(type1, obj1, type2, obj2, type3, obj3) \ + do { \ + SETFUSEOUT3(type1, obj1, type2, obj2, type3, obj3); \ + TESTEQUAL(write(fuse_dev, bytes_out, \ + ((struct fuse_out_header *)bytes_out)->len), \ + -1); \ + } while (false) + +#define FUSE_DEV_IOC_BPF_RESPONSE(N) _IOW(FUSE_DEV_IOC_MAGIC, 125, char[N]) + +#define TESTFUSEOUT3_IOCTL(type1, obj1, type2, obj2, type3, obj3) \ + do { \ + SETFUSEOUT3(type1, obj1, type2, obj2, type3, obj3); \ + TESTEQUAL(ioctl(fuse_dev, \ + FUSE_DEV_IOC_BPF_RESPONSE( \ + ((struct fuse_out_header *)bytes_out)->len), \ + bytes_out), \ + ((struct fuse_out_header *)bytes_out)->len); \ + } while (false) + +#define TESTFUSEINITFLAGS(fuse_connection_flags) \ + do { \ + DECL_FUSE_IN(init); \ + \ + TESTFUSEIN(FUSE_INIT, init_in); \ + TESTEQUAL(init_in->major, FUSE_KERNEL_VERSION); \ + TESTEQUAL(init_in->minor, FUSE_KERNEL_MINOR_VERSION); \ + TESTFUSEOUT1(fuse_init_out, ((struct fuse_init_out) { \ + .major = FUSE_KERNEL_VERSION, \ + .minor = FUSE_KERNEL_MINOR_VERSION, \ + .max_readahead = 4096, \ + .flags = fuse_connection_flags, \ + .max_background = 0, \ + .congestion_threshold = 0, \ + .max_write = 4096, \ + .time_gran = 1000, \ + .max_pages = 12, \ + .map_alignment = 4096, \ + })); \ + } while (false) + +#define TESTFUSEINIT() \ + TESTFUSEINITFLAGS(0) + +#define DECL_FUSE_IN(name) \ + struct fuse_##name##_in *name##_in = \ + (struct fuse_##name##_in *) \ + (bytes_in + sizeof(struct fuse_in_header)) + +#define DECL_FUSE(name) \ + struct fuse_##name##_in *name##_in __attribute__((unused)); \ + struct fuse_##name##_out *name##_out __attribute__((unused)) + +#define FUSE_ACTION TEST(pid = fork(), pid != -1); \ + if (pid) { + +#define FUSE_DAEMON } else { \ + uint8_t bytes_in[FUSE_MIN_READ_BUFFER] \ + __attribute__((unused)); \ + uint8_t bytes_out[FUSE_MIN_READ_BUFFER] \ + __attribute__((unused)); + +#define FUSE_DONE exit(TEST_SUCCESS); \ + } \ + TESTEQUAL(waitpid(pid, &status, 0), pid); \ + TESTEQUAL(status, TEST_SUCCESS); + +int mount_fuse(const char *mount_dir, const char *bpf_name, int dir_fd, + int *fuse_dev_ptr); +int mount_fuse_no_init(const char *mount_dir, const char *bpf_name, int dir_fd, + int *fuse_dev_ptr); +#endif -- 2.40.0.634.g4ca3ef3211-goog