From nobody Wed Dec 17 21:16:14 2025 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id F411AC77B72 for ; Tue, 18 Apr 2023 01:46:22 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231420AbjDRBqU (ORCPT ); Mon, 17 Apr 2023 21:46:20 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:54262 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231309AbjDRBpa (ORCPT ); Mon, 17 Apr 2023 21:45:30 -0400 Received: from mail-yw1-x1149.google.com (mail-yw1-x1149.google.com [IPv6:2607:f8b0:4864:20::1149]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id E3B6CA5D4 for ; Mon, 17 Apr 2023 18:42:47 -0700 (PDT) Received: by mail-yw1-x1149.google.com with SMTP id 00721157ae682-54ee12aa4b5so247181327b3.4 for ; Mon, 17 Apr 2023 18:42:47 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20221208; t=1681782132; x=1684374132; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=Rk7KbeOo8rhdSgTgXnHyow+gqIED12sjelCBSds/WYg=; b=N5opq6WcCzz8RaETiMvJHlpqhJ0gaPR36LASZdNjZdjznRLjsv7TOeO2HlNcQyFJ57 tjT0EfFA9PwM25junTv4yMCuAz+UK6Fe0yyC8OEn8mUlb3o5wcNe4pTD23fyCvyTnaeY fxHyoPj4F2NgwqIM3OkzDFAwYGKeB2ZDzh/xmLiFqnG6ZNlfniopKfucgCx7zkZdvcUg id9wnl9fjuSs+ArZZzIMczPlNM8kMd1YBRJMExFtaAE+FCOYE5eZm65uRomYLebS63PF 0OIbviFZzbeMldjlHj0vf5B0aZhn2ym2vbEm4q6DcLjlnzUuvqaaNvYNveMRWHovpvpl HCAg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681782132; x=1684374132; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=Rk7KbeOo8rhdSgTgXnHyow+gqIED12sjelCBSds/WYg=; b=eGDRv1O0Srf4LrJRb3cTOM33SPS5go758wcgDohrwSciJ+SFNH1zYzPI3etSpECOME Ex6SrOodIONs/hRD3ol4vDhuy4RzE21e0NNyBLXF0y5gJZr5I+OE3qYURgW/lC6eopkm 7ZKR5sGM3Zdh/v4H1qIE4EyJVtV8d76WYxEZi41z8I6oBBt0LWckLsK7D0J0nk5potYy tClv5ykCKJ1jTB6MyRdfWHtjIEyaV7m2TdylZXD1meVaLL7Bq1SmOFKVhJzmp7P8CSXI qZwV/j5tk6vJJUqSGRo994Wo4YTBDFXBDPxQJoR+ek9iQMpezOM2IIHfleJ03opSB02F rPmw== X-Gm-Message-State: AAQBX9fPN/T5bte8ifM7rxxPx7m+bm5Z3/jBfjw81looc1puUQglgccs uckC/E+S2nJ49KtaLazzvP8Qx/sWx00= X-Google-Smtp-Source: AKy350ZgL/jsllSontpbaAxiQgyNNKAA4k0sDxqfSe/h20gFKUGZlWmnWMvj8iaNFQgkF086PipsFDr1g4Y= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:e67a:98b0:942d:86aa]) (user=drosen job=sendgmr) by 2002:a81:ac1a:0:b0:544:bce8:980f with SMTP id k26-20020a81ac1a000000b00544bce8980fmr10886637ywh.6.1681782132472; Mon, 17 Apr 2023 18:42:12 -0700 (PDT) Date: Mon, 17 Apr 2023 18:40:36 -0700 In-Reply-To: <20230418014037.2412394-1-drosen@google.com> Mime-Version: 1.0 References: <20230418014037.2412394-1-drosen@google.com> X-Mailer: git-send-email 2.40.0.634.g4ca3ef3211-goog Message-ID: <20230418014037.2412394-37-drosen@google.com> Subject: [RFC PATCH v3 36/37] fuse-bpf: Add selftests From: Daniel Rosenberg To: Miklos Szeredi , bpf@vger.kernel.org, Alexei Starovoitov Cc: Amir Goldstein , linux-kernel@vger.kernel.org, linux-fsdevel@vger.kernel.org, linux-unionfs@vger.kernel.org, Daniel Borkmann , John Fastabend , Andrii Nakryiko , Martin KaFai Lau , Song Liu , Yonghong Song , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Shuah Khan , Jonathan Corbet , Joanne Koong , Mykola Lysenko , kernel-team@android.com, Daniel Rosenberg , Paul Lawrence , Alessio Balsini Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Adds basic selftests for fuse. These check that you can add fuse_op programs, and perform basic operations Signed-off-by: Daniel Rosenberg Signed-off-by: Paul Lawrence Signed-off-by: Alessio Balsini --- .../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/te= sting/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/test= ing/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 :=3D $(LLVM) +else ifneq ($(filter -%,$(LLVM)),) +LLVM_SUFFIX :=3D $(LLVM) +endif + +CLANG_TARGET_FLAGS_arm :=3D arm-linux-gnueabi +CLANG_TARGET_FLAGS_arm64 :=3D aarch64-linux-gnu +CLANG_TARGET_FLAGS_hexagon :=3D hexagon-linux-musl +CLANG_TARGET_FLAGS_m68k :=3D m68k-linux-gnu +CLANG_TARGET_FLAGS_mips :=3D mipsel-linux-gnu +CLANG_TARGET_FLAGS_powerpc :=3D powerpc64le-linux-gnu +CLANG_TARGET_FLAGS_riscv :=3D riscv64-linux-gnu +CLANG_TARGET_FLAGS_s390 :=3D s390x-linux-gnu +CLANG_TARGET_FLAGS_x86 :=3D x86_64-linux-gnu +CLANG_TARGET_FLAGS :=3D $(CLANG_TARGET_FLAGS_$(ARCH)) +#endif + +ifeq ($(CROSS_COMPILE),) +ifeq ($(CLANG_TARGET_FLAGS),) +$(error Specify CROSS_COMPILE or add '--target=3D' option to lib.mk +else +CLANG_FLAGS +=3D --target=3D$(CLANG_TARGET_FLAGS) +endif # CLANG_TARGET_FLAGS +else +CLANG_FLAGS +=3D --target=3D$(notdir $(CROSS_COMPILE:%-=3D%)) +endif # CROSS_COMPILE + +CC :=3D $(LLVM_PREFIX)clang$(LLVM_SUFFIX) $(CLANG_FLAGS) -fintegrated-as +else +CC :=3D $(CROSS_COMPILE)gcc +endif # LLVM + +CURDIR :=3D $(abspath .) +TOOLSDIR :=3D $(abspath ../../../..) +LIBDIR :=3D $(TOOLSDIR)/lib +BPFDIR :=3D $(LIBDIR)/bpf +TOOLSINCDIR :=3D $(TOOLSDIR)/include +BPFTOOLDIR :=3D $(TOOLSDIR)/bpf/bpftool +APIDIR :=3D $(TOOLSINCDIR)/uapi +GENDIR :=3D $(abspath ../../../../../include/generated) +GENHDR :=3D $(GENDIR)/autoconf.h +SELFTESTS:=3D$(TOOLSDIR)/testing/selftests/ + +LDLIBS :=3D -lpthread -lelf -lz +TEST_GEN_PROGS :=3D fuse_test fuse_daemon +TEST_GEN_FILES :=3D \ + 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 :=3D bpf_loader.c $(OUTPUT)/test.skel.h +$(TEST_GEN_PROGS) : $(EXTRA_SOURCES) $(BPFOBJ) + +SCRATCH_DIR :=3D $(OUTPUT)/tools +BUILD_DIR :=3D $(SCRATCH_DIR)/build +INCLUDE_DIR :=3D $(SCRATCH_DIR)/include +BPFOBJ :=3D $(BUILD_DIR)/libbpf/libbpf.a +SKEL_DIR :=3D $(OUTPUT) +ifneq ($(CROSS_COMPILE),) +HOST_BUILD_DIR :=3D $(BUILD_DIR)/host +HOST_SCRATCH_DIR :=3D host-tools +HOST_INCLUDE_DIR :=3D $(HOST_SCRATCH_DIR)/include +else +HOST_BUILD_DIR :=3D $(BUILD_DIR) +HOST_SCRATCH_DIR :=3D $(SCRATCH_DIR) +HOST_INCLUDE_DIR :=3D $(INCLUDE_DIR) +endif +HOST_BPFOBJ :=3D $(HOST_BUILD_DIR)/libbpf/libbpf.a +RESOLVE_BTFIDS :=3D $(HOST_BUILD_DIR)/resolve_btfids/resolve_btfids +DEFAULT_BPFTOOL :=3D $(HOST_SCRATCH_DIR)/sbin/bpftool + +VMLINUX_BTF_PATHS ?=3D $(if $(OUTPUT),$(OUTPUT)/../../../../../vmlinux) \ + $(if $(KBUILD_OUTPUT),$(KBUILD_OUTPUT)/vmlinux) \ + ../../../../../vmlinux \ + /sys/kernel/btf/vmlinux \ + /boot/vmlinux-$(shell uname -r) +VMLINUX_BTF ?=3D $(abspath $(firstword $(wildcard $(VMLINUX_BTF_PATHS)))) +ifeq ($(VMLINUX_BTF),) +$(error Cannot find a vmlinux for VMLINUX_BTF at any of "$(VMLINUX_BTF_PAT= HS)") +endif + +BPFTOOL ?=3D $(DEFAULT_BPFTOOL) + +ifneq ($(wildcard $(GENHDR)),) + GENFLAGS :=3D -DHAVE_GENHDR +endif + +CFLAGS +=3D -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 +=3D -Wno-unused-command-line-argument +endif + +#LDFLAGS =3D -lelf -lz + +IS_LITTLE_ENDIAN =3D $(shell $(CC) -dM -E - &1 \ + | sed -n '/<...> search starts here:/,/End of search list./{ s| \(/.*\)|-= idirafter \1|p }') \ +$(shell $(1) -dM -E - $@ +else + $(call msg,CP,,$@) + $(Q)cp "$(VMLINUX_H)" $@ +endif + +$(OUTPUT)/fuse_daemon: LDLIBS :=3D $(HOST_BPFOBJ) $(LDLIBS) +$(OUTPUT)/fuse_test: LDLIBS :=3D $(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=3D.linked1.o) $< + $(Q)$(BPFTOOL) gen object $(<:.o=3D.linked2.o) $(<:.o=3D.linked1.o) + $(Q)$(BPFTOOL) gen object $(<:.o=3D.linked3.o) $(<:.o=3D.linked2.o) + $(Q)diff $(<:.o=3D.linked2.o) $(<:.o=3D.linked3.o) + $(Q)$(BPFTOOL) gen skeleton $(<:.o=3D.linked3.o) name $(notdir $(<:.bpf.o= =3D))_bpf > $@ + $(Q)$(BPFTOOL) gen subskeleton $(<:.o=3D.linked3.o) name $(notdir $(<:.bp= f.o=3D))_bpf > $(@:.skel.h=3D.subskel.h) + +$(OUTPUT)/fd.sh: fd.txt + cp $< $@ + chmod 755 $@ diff --git a/tools/testing/selftests/filesystems/fuse/OWNERS b/tools/testin= g/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/filesystem= s/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 =3D 1, + FUSE_ENTRY_BPF =3D 2, + FUSE_ENTRY_REMOVE_BACKING =3D 3, + FUSE_ENTRY_REMOVE_BPF =3D 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, struc= t bpf_dynptr *dynptr) __ksym; +extern void *bpf_dynptr_slice(const struct bpf_dynptr *ptr, u32 offset, vo= id *buffer, u32 buffer__szk) __ksym; +extern void *bpf_dynptr_slice_rdwr(const struct bpf_dynptr *ptr, u32 offse= t, 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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include + +struct _test_options test_options; + +struct s s(const char *s1) +{ + struct s s =3D {0}; + + if (!s1) + return s; + + s.s =3D 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 =3D {0}; + + if (!s1) + return s; + + s.s =3D malloc(s2 - s1 + 1); + if (!s.s) + return s; + + strncpy(s.s, s1, s2 - s1); + s.s[s2 - s1] =3D 0; + return s; +} + +int s_cmp(struct s s1, struct s s2) +{ + int result =3D -1; + + if (!s1.s || !s2.s) + goto out; + result =3D 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 =3D {0}; + + if (!s1.s || !s2.s) + goto out; + + s.s =3D 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 =3D {0}; + char *split; + + if (!s1.s) + return s; + + split =3D strchr(s1.s, c); + if (split) + s =3D sn(s1.s, split); + + free(s1.s); + return s; +} + +struct s s_splitright(struct s s1, char c) +{ + struct s s2 =3D {0}; + char *split; + + if (!s1.s) + return s2; + + split =3D strchr(s1.s, c); + if (split) + s2 =3D s(split + 1); + + free(s1.s); + return s2; +} + +struct s s_word(struct s s1, char c, size_t n) +{ + while (n--) + s1 =3D 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 =3D 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 =3D link(src_pathname.s, dst_pathname.s); + } else { + res =3D -1; + errno =3D 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 =3D symlink(src_pathname.s, dst_pathname.s); + } else { + res =3D -1; + errno =3D 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 =3D ENOMEM; + return -1; + } + + res =3D mkdir(pathname.s, mode); + free(pathname.s); + return res; +} + +int s_rmdir(struct s pathname) +{ + int res; + + if (!pathname.s) { + errno =3D ENOMEM; + return -1; + } + + res =3D rmdir(pathname.s); + free(pathname.s); + return res; +} + +int s_unlink(struct s pathname) +{ + int res; + + if (!pathname.s) { + errno =3D ENOMEM; + return -1; + } + + res =3D 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 =3D ENOMEM; + return -1; + } + + if (flags & (O_CREAT | O_TMPFILE)) + res =3D open(pathname.s, flags, va_arg(ap, mode_t)); + else + res =3D 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 =3D ENOMEM; + return -1; + } + + if (flags & (O_CREAT | O_TMPFILE)) + res =3D openat(dirfd, pathname.s, flags, va_arg(ap, mode_t)); + else + res =3D 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 =3D ENOMEM; + return -1; + } + + res =3D 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 =3D ENOMEM; + return -1; + } + + res =3D 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 =3D ENOMEM; + return -1; + } + + res =3D stat(pathname.s, st); + free(pathname.s); + return res; +} + +int s_statfs(struct s pathname, struct statfs *st) +{ + int res; + + if (!pathname.s) { + errno =3D ENOMEM; + return -1; + } + + res =3D statfs(pathname.s, st); + free(pathname.s); + return res; +} + +DIR *s_opendir(struct s pathname) +{ + DIR *res; + + res =3D opendir(pathname.s); + free(pathname.s); + return res; +} + +int s_getxattr(struct s pathname, const char name[], void *value, size_t s= ize, + ssize_t *ret_size) +{ + if (!pathname.s) { + errno =3D ENOMEM; + return -1; + } + + *ret_size =3D getxattr(pathname.s, name, value, size); + free(pathname.s); + return *ret_size >=3D 0 ? 0 : -1; +} + +int s_listxattr(struct s pathname, void *list, size_t size, ssize_t *ret_s= ize) +{ + if (!pathname.s) { + errno =3D ENOMEM; + return -1; + } + + *ret_size =3D listxattr(pathname.s, list, size); + free(pathname.s); + return *ret_size >=3D 0 ? 0 : -1; +} + +int s_setxattr(struct s pathname, const char name[], const void *value, si= ze_t size, int flags) +{ + int res; + + if (!pathname.s) { + errno =3D ENOMEM; + return -1; + } + + res =3D 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 =3D ENOMEM; + return -1; + } + + res =3D 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 =3D ENOMEM; + return -1; + } + + res =3D 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 =3D TEST_FAILURE; + + TESTSYSCALL(s_stat(pathname, &st)); + + fuse_attr_out->ino =3D st.st_ino; + fuse_attr_out->mode =3D st.st_mode; + fuse_attr_out->nlink =3D st.st_nlink; + fuse_attr_out->uid =3D st.st_uid; + fuse_attr_out->gid =3D st.st_gid; + fuse_attr_out->rdev =3D st.st_rdev; + fuse_attr_out->size =3D st.st_size; + fuse_attr_out->blksize =3D st.st_blksize; + fuse_attr_out->blocks =3D st.st_blocks; + fuse_attr_out->atime =3D st.st_atime; + fuse_attr_out->mtime =3D st.st_mtime; + fuse_attr_out->ctime =3D st.st_ctime; + fuse_attr_out->atimensec =3D UINT32_MAX; + fuse_attr_out->mtimensec =3D UINT32_MAX; + fuse_attr_out->ctimensec =3D UINT32_MAX; + + result =3D TEST_SUCCESS; +out: + return result; +} + +struct s tracing_folder(void) +{ + struct s trace =3D {0}; + FILE *mounts =3D NULL; + char *line =3D NULL; + size_t size =3D 0; + + TEST(mounts =3D fopen("/proc/mounts", "re"), mounts); + while (getline(&line, &size, mounts) !=3D -1) { + if (!s_cmp(s_word(sn(line, line + size), ' ', 2), + s("tracefs"))) { + trace =3D s_word(sn(line, line + size), ' ', 1); + break; + } + + if (!s_cmp(s_word(sn(line, line + size), ' ', 2), s("debugfs"))) + trace =3D s_path(s_word(sn(line, line + size), ' ', 1), + s("tracing")); + } + +out: + free(line); + fclose(mounts); + return trace; +} + +int tracing_on(void) +{ + int result =3D TEST_FAILURE; + int tracing_on =3D -1; + + TEST(tracing_on =3D s_open(s_path(tracing_folder(), s("tracing_on")), + O_WRONLY | O_CLOEXEC), + tracing_on !=3D -1); + TESTEQUAL(write(tracing_on, "1", 1), 1); + result =3D TEST_SUCCESS; +out: + close(tracing_on); + return result; +} + +char *concat_file_name(const char *dir, const char *file) +{ + char full_name[FILENAME_MAX] =3D ""; + + 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 =3D getcwd(NULL, 0); + char *mount_dir =3D concat_file_name(current_dir, name); + + free(current_dir); + if (stat(mount_dir, &st) =3D=3D 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 =3D NULL; + struct dirent *dp; + int result =3D 0; + + dir =3D opendir(dir_path); + if (!dir) { + result =3D -errno; + goto out; + } + + while ((dp =3D readdir(dir))) { + char *full_path; + + if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) + continue; + + full_path =3D concat_file_name(dir_path, dp->d_name); + if (dp->d_type =3D=3D DT_DIR) + result =3D delete_dir_tree(full_path, true); + else + result =3D 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_na= me, int dir_fd, + int *fuse_dev_ptr, bool init) +{ + int result =3D TEST_FAILURE; + int fuse_dev =3D -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 =3D open("/dev/fuse", O_RDWR | O_CLOEXEC), fuse_dev !=3D -1= ); + snprintf(options, FILENAME_MAX, "fd=3D%d,user_id=3D0,group_id=3D0,rootmod= e=3D0040000", + fuse_dev); + if (bpf_name !=3D NULL) + snprintf(options + strlen(options), + sizeof(options) - strlen(options), + ",root_bpf=3D%s", bpf_name); + if (dir_fd !=3D -1) + snprintf(options + strlen(options), + sizeof(options) - strlen(options), + ",root_dir=3D%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 =3D FUSE_KERNEL_VERSION, + .minor =3D FUSE_KERNEL_MINOR_VERSION, + .max_readahead =3D 4096, + .flags =3D 0, + .max_background =3D 0, + .congestion_threshold =3D 0, + .max_write =3D 4096, + .time_gran =3D 1000, + .max_pages =3D 12, + .map_alignment =3D 4096, + })); + } + + *fuse_dev_ptr =3D fuse_dev; + fuse_dev =3D -1; + result =3D TEST_SUCCESS; +out: + close(fuse_dev); + return result; +} + +int mount_fuse(const char *mount_dir, const char * bpf_name, int dir_fd, i= nt *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 d= ir_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/testin= g/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 +//#include + +#include "vmlinux.h" +#include +#include +#include +#include + +#include "bpf_common.h" + +char _license[] SEC("license") =3D "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 =3D 0; i < __builtin_strlen(b) + 1; ++i) + if (a[i] !=3D b[i]) + return -1; + + return 0; +} + +SEC("maps") struct fuse_bpf_map test_map =3D { + BPF_MAP_TYPE_ARRAY, + sizeof(uint32_t), + sizeof(uint32_t), + 1000, +}; + +SEC("maps") struct fuse_bpf_map test_map2 =3D { + 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 =3D bpf_get_current_uid_gid(); + uint32_t uid =3D uid_gid & 0xffffffff; + uint64_t pid_tgid =3D bpf_get_current_pid_tgid(); + uint32_t pid =3D pid_tgid & 0xffffffff; + uint32_t key =3D 23; + uint32_t *pvalue; + + + pvalue =3D bpf_map_lookup_elem(&test_map, &key); + if (pvalue) { + uint32_t value =3D *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_met= a_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_me= ta_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_me= ta_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_me= ta_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_me= ta_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_met= a_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 =3D=3D 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_met= a_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_me= ta_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_met= a_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_me= ta_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_m= eta_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_me= ta_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_fus= e_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_m= eta_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_m= eta_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_met= a_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 =3D { + .open_prefilter =3D (void *)trace_open_prefilter, + .opendir_prefilter =3D (void *)trace_opendir_prefilter, + .create_open_prefilter =3D (void *)trace_create_open_prefilter, + .release_prefilter =3D (void *)trace_release_prefilter, + .releasedir_prefilter =3D (void *)trace_releasedir_prefilter, + .flush_prefilter =3D (void *)trace_flush_prefilter, + .lseek_prefilter =3D (void *)trace_lseek_prefilter, + //.copy_file_range_prefilter =3D (void *)trace_copy_file_range_prefilter, + //.fsync_prefilter =3D (void *)trace_fsync_prefilter, + //.dir_fsync_prefilter =3D (void *)trace_dir_fsync_prefilter, + .getxattr_prefilter =3D (void *)trace_getxattr_prefilter, + .listxattr_prefilter =3D (void *)trace_listxattr_prefilter, + .setxattr_prefilter =3D (void *)trace_setxattr_prefilter, + //.removexattr_prefilter =3D (void *)trace_removexattr_prefilter, + .read_iter_prefilter =3D (void *)trace_read_iter_prefilter, + .write_iter_prefilter =3D (void *)trace_write_iter_prefilter, + .file_fallocate_prefilter =3D (void *)trace_file_fallocate_prefilter, + .lookup_prefilter =3D (void *)trace_lookup_prefilter, + .mknod_prefilter =3D (void *)trace_mknod_prefilter, + .mkdir_prefilter =3D (void *)trace_mkdir_prefilter, + .rmdir_prefilter =3D (void *)trace_rmdir_prefilter, + .rename2_prefilter =3D (void *)trace_rename2_prefilter, + .rename_prefilter =3D (void *)trace_rename_prefilter, + .unlink_prefilter =3D (void *)trace_unlink_prefilter, + .link_prefilter =3D (void *)trace_link_prefilter, + .getattr_prefilter =3D (void *)trace_getattr_prefilter, + .setattr_prefilter =3D (void *)trace_setattr_prefilter, + .statfs_prefilter =3D (void *)trace_statfs_prefilter, + .get_link_prefilter =3D (void *)trace_get_link_prefilter, + .symlink_prefilter =3D (void *)trace_symlink_prefilter, + .readdir_prefilter =3D (void *)trace_readdir_prefilter, + .access_prefilter =3D (void *)trace_access_prefilter, + .name =3D "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 +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include + +bool user_messages; +bool kernel_messages; + +static int display_trace(void) +{ + int pid =3D -1; + int tp =3D -1; + char c; + ssize_t bytes_read; + static char line[256] =3D {0}; + + if (!kernel_messages) + return TEST_SUCCESS; + + TEST(pid =3D fork(), pid !=3D -1); + if (pid !=3D 0) + return pid; + + TESTEQUAL(tracing_on(), 0); + TEST(tp =3D s_open(s_path(tracing_folder(), s("trace_pipe")), + O_RDONLY | O_CLOEXEC), tp !=3D -1); + for (;;) { + TEST(bytes_read =3D read(tp, &c, sizeof(c)), + bytes_read =3D=3D 1); + if (c =3D=3D '\n') { + printf("%s\n", line); + line[0] =3D 0; + } else + sprintf(line + strlen(line), "%c", c); + } +out: + if (pid =3D=3D 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 =3D getopt(argc, argv, "kuv")) !=3D -1) + switch (c) { + case 'v': + test_options.verbose =3D true; + break; + + case 'u': + user_messages =3D true; + break; + + case 'k': + kernel_messages =3D true; + break; + + default: + return -EINVAL; + } + + return 0; +} + +int main(int argc, char *argv[]) +{ + struct test_bpf *test_skel =3D NULL; + struct bpf_link *test_link =3D NULL; + int result =3D TEST_FAILURE; + int trace_pid =3D -1; + char *mount_dir =3D NULL; + char *src_dir =3D NULL; + int src_fd =3D -1; + int fuse_dev =3D -1; + //struct map_relocation *map_relocations =3D NULL; + //size_t map_count =3D 0; + //int i; + + if (geteuid() !=3D 0) + ksft_print_msg("Not a root, might fail to mount.\n"); + TESTEQUAL(parse_options(argc, argv), 0); + + TEST(trace_pid =3D display_trace(), trace_pid !=3D -1); + + delete_dir_tree("fd-src", true); + TEST(src_dir =3D setup_mount_dir("fd-src"), src_dir); + delete_dir_tree("fd-dst", true); + TEST(mount_dir =3D setup_mount_dir("fd-dst"), mount_dir); + + test_skel =3D test_bpf__open_and_load(); + test_link =3D bpf_map__attach_struct_ops(test_skel->maps.trace_ops); + + TEST(src_fd =3D open("fd-src", O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd !=3D -1); + TESTSYSCALL(mkdirat(src_fd, "show", 0777)); + TESTSYSCALL(mkdirat(src_fd, "hide", 0777)); + + /*for (i =3D 0; i < map_count; ++i) + if (!strcmp(map_relocations[i].name, "test_map")) { + uint32_t key =3D 23; + uint32_t value =3D 1234; + union bpf_attr attr =3D { + .map_fd =3D map_relocations[i].fd, + .key =3D ptr_to_u64(&key), + .value =3D ptr_to_u64(&value), + .flags =3D 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 =3D + (struct fuse_in_header *)bytes_in; + ssize_t res =3D read(fuse_dev, bytes_in, sizeof(bytes_in)); + + if (res =3D=3D -1) + break; + + switch (in_header->opcode) { + case FUSE_LOOKUP | FUSE_PREFILTER: { + char *name =3D (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 =3D TEST_SUCCESS; + +out: + /*for (i =3D 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 !=3D -1) + kill(trace_pid, SIGKILL); + return result; +} diff --git a/tools/testing/selftests/filesystems/fuse/fuse_test.c b/tools/t= esting/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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +static const char *ft_src =3D "ft-src"; +static const char *ft_dst =3D "ft-dst"; + +static void fill_buffer(uint8_t *data, size_t len, int file, int block) +{ + int i; + int seed =3D 7919 * file + block; + + for (i =3D 0; i < len; i++) { + seed =3D 1103515245 * seed + 12345; + data[i] =3D (uint8_t)(seed >> (i % 13)); + } +} + +static bool test_buffer(uint8_t *data, size_t len, int file, int block) +{ + int i; + int seed =3D 7919 * file + block; + + for (i =3D 0; i < len; i++) { + seed =3D 1103515245 * seed + 12345; + if (data[i] !=3D (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 =3D TEST_FAILURE; + int fd =3D -1; + int i; + uint8_t data[PAGE_SIZE]; + + TEST(fd =3D s_openat(dir, name, O_CREAT | O_WRONLY, 0777), fd !=3D -1); + for (i =3D 0; i < blocks; ++i) { + fill_buffer(data, PAGE_SIZE, index, i); + TESTEQUAL(write(fd, data, sizeof(data)), PAGE_SIZE); + } + TESTSYSCALL(close(fd)); + result =3D TEST_SUCCESS; + +out: + close(fd); + return result; +} + +static int bpf_clear_trace(void) +{ + int result =3D TEST_FAILURE; + int tp =3D -1; + + TEST(tp =3D s_open(s_path(tracing_folder(), s("trace")), + O_WRONLY | O_TRUNC | O_CLOEXEC), tp !=3D -1); + + result =3D TEST_SUCCESS; +out: + close(tp); + return result; +} + +static int bpf_test_trace_maybe(const char *substr, bool present) +{ + int result =3D TEST_FAILURE; + int tp =3D -1; + char trace_buffer[4096] =3D {}; + ssize_t bytes_read; + + TEST(tp =3D s_open(s_path(tracing_folder(), s("trace_pipe")), + O_RDONLY | O_CLOEXEC), + tp !=3D -1); + fcntl(tp, F_SETFL, O_NONBLOCK); + + for (;;) { + bytes_read =3D read(tp, trace_buffer, sizeof(trace_buffer)); + if (present) + TESTCOND(bytes_read > 0); + else if (bytes_read <=3D 0) { + result =3D TEST_SUCCESS; + break; + } + + if (test_options.verbose) + ksft_print_msg("%s\n", trace_buffer); + + if (strstr(trace_buffer, substr)) { + if (present) + result =3D 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 =3D "test"; + const char *test_data =3D "data"; + + int result =3D TEST_FAILURE; + int fuse_dev =3D -1; + char *filename =3D NULL; + int fd =3D -1; + int pid =3D -1; + int status; + + TESTEQUAL(mount_fuse(mount_dir, NULL, -1, &fuse_dev), 0); + FUSE_ACTION + char data[256]; + + filename =3D concat_file_name(mount_dir, test_name); + TESTERR(fd =3D open(filename, O_RDONLY | O_CLOEXEC), fd !=3D -1); + TESTEQUAL(read(fd, data, strlen(test_data)), strlen(test_data)); + TESTCOND(!strcmp(data, test_data)); + TESTSYSCALL(close(fd)); + fd =3D -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 =3D 2, + .generation =3D 1, + .attr.ino =3D 100, + .attr.size =3D 4, + .attr.blksize =3D 512, + .attr.mode =3D S_IFREG | 0777, + })); + + TESTFUSEIN(FUSE_OPEN, open_in); + TESTFUSEOUT1(fuse_open_out, ((struct fuse_open_out) { + .fh =3D 1, + .open_flags =3D 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 =3D 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 =3D NULL; + struct bpf_link *test_link =3D NULL; + const char *test_name =3D "real"; + const char *test_data =3D "Weebles wobble but they don't fall down"; + int result =3D TEST_FAILURE; + int src_fd =3D -1; + int fuse_dev =3D -1; + char *filename =3D NULL; + int fd =3D -1; + char read_buffer[256] =3D {}; + ssize_t bytes_read; + + TEST(src_fd =3D open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd !=3D -1); + TEST(fd =3D openat(src_fd, test_name, O_CREAT | O_RDWR | O_CLOEXEC, 0777), + fd !=3D -1); + TESTEQUAL(write(fd, test_data, strlen(test_data)), strlen(test_data)); + TESTSYSCALL(close(fd)); + fd =3D -1; + TEST(test_skel =3D test_bpf__open_and_load(), test_skel !=3D NULL); + TEST(test_link =3D bpf_map__attach_struct_ops(test_skel->maps.test_trace_= ops), test_link !=3D NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_trace_ops", src_fd, &fuse_dev), 0); + + filename =3D concat_file_name(mount_dir, test_name); + TESTERR(fd =3D open(filename, O_RDONLY | O_CLOEXEC), fd !=3D -1); + bytes_read =3D 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 =3D 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 =3D NULL; + struct bpf_link *test_link =3D NULL; + const char *test_name =3D "partial"; + int result =3D TEST_FAILURE; + int src_fd =3D -1; + int fuse_dev =3D -1; + char *filename =3D NULL; + int fd =3D -1; + int pid =3D -1; + int status; + + TEST(src_fd =3D open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd !=3D -1); + TESTEQUAL(create_file(src_fd, s(test_name), 1, 2), 0); + TEST(test_skel =3D test_bpf__open_and_load(), test_skel !=3D NULL); + TEST(test_link =3D bpf_map__attach_struct_ops(test_skel->maps.test_trace_= ops), test_link !=3D NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_trace_ops", src_fd, &fuse_dev), 0); + + FUSE_ACTION + uint8_t data[PAGE_SIZE]; + + TEST(filename =3D concat_file_name(mount_dir, test_name), + filename); + TESTERR(fd =3D open(filename, O_RDONLY | O_CLOEXEC), fd !=3D -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 =3D -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_i= n); + TESTEQUAL(*err_in, 0); + TESTFUSEOUT1(fuse_open_out, ((struct fuse_open_out) { + .fh =3D 1, + .open_flags =3D 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 =3D 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 =3D NULL; + struct bpf_link *test_link =3D NULL; + const char *test_name =3D "partial"; + int result =3D TEST_FAILURE; + int src_fd =3D -1; + int fuse_dev =3D -1; + char *filename =3D NULL; + struct stat st; + + TEST(src_fd =3D open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd !=3D -1); + TESTEQUAL(create_file(src_fd, s(test_name), 1, 2), 0); + + TEST(test_skel =3D test_bpf__open_and_load(), test_skel !=3D NULL); + TEST(test_link =3D bpf_map__attach_struct_ops(test_skel->maps.test_trace_= ops), test_link !=3D NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_trace_ops", src_fd, &fuse_dev), 0); + + TEST(filename =3D 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 =3D 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 =3D NULL; + struct bpf_link *test_link =3D NULL; + const char *names[] =3D {"real", "partial", "fake", ".", ".."}; + int result =3D TEST_FAILURE; + int src_fd =3D -1; + int fuse_dev =3D -1; + int pid =3D -1; + int status; + DIR *dir =3D NULL; + struct dirent *dirent; + + TEST(src_fd =3D open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd !=3D -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 =3D test_bpf__open_and_load(), test_skel !=3D NULL); + TEST(test_link =3D bpf_map__attach_struct_ops(test_skel->maps.test_trace_= ops), test_link !=3D NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_trace_ops", src_fd, &fuse_dev), 0); + + FUSE_ACTION + int i, j; + + TEST(dir =3D s_opendir(s(mount_dir)), dir); + TESTEQUAL(bpf_test_trace("opendir"), 0); + + for (i =3D 0; i < ARRAY_SIZE(names); ++i) { + TEST(dirent =3D readdir(dir), dirent); + + for (j =3D 0; j < ARRAY_SIZE(names); ++j) + if (names[j] && + strcmp(names[j], dirent->d_name) =3D=3D 0) { + names[j] =3D NULL; + break; + } + TESTNE(j, ARRAY_SIZE(names)); + } + TEST(dirent =3D readdir(dir), dirent =3D=3D NULL); + TESTSYSCALL(closedir(dir)); + dir =3D NULL; + TESTEQUAL(bpf_test_trace("readdir"), 0); + FUSE_DAEMON + struct fuse_in_header *in_header =3D + (struct fuse_in_header *)bytes_in; + ssize_t res =3D read(fuse_dev, bytes_in, sizeof(bytes_in)); + // ignore the error in extension + res -=3D ERR_IN_EXT_LEN; + struct fuse_read_out *read_out =3D + (struct fuse_read_out *) (bytes_in + + sizeof(*in_header) + + sizeof(struct fuse_read_in)); + struct fuse_dirent *fuse_dirent =3D + (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 =3D (struct fuse_dirent) { + .ino =3D 100, + .off =3D 5, + .namelen =3D strlen("fake"), + .type =3D DT_REG, + }; + strcpy((char *)(bytes_in + res + sizeof(*fuse_dirent)), "fake"); + res +=3D 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 =3D 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 =3D 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 =3D NULL; + struct bpf_link *test_link =3D NULL; + const char *names[] =3D {"f1", "f2", "f3", "f4", "f5", "f6", ".", ".."}; + int num_shown =3D (ARRAY_SIZE(names) - 2) / 2 + 2; + int result =3D TEST_FAILURE; + int src_fd =3D -1; + int fuse_dev =3D -1; + int pid =3D -1; + int status; + DIR *dir =3D NULL; + struct dirent *dirent; + int i; + int count =3D 0; + + TEST(src_fd =3D open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd !=3D -1); + for (i =3D 0; i < ARRAY_SIZE(names) - 2; i++) + TESTEQUAL(create_file(src_fd, s(names[i]), 1, 2), 0); + + TEST(test_skel =3D test_bpf__open_and_load(), test_skel !=3D NULL); + TEST(test_link =3D bpf_map__attach_struct_ops(test_skel->maps.readdir_red= act_ops), test_link !=3D NULL); + TESTEQUAL(mount_fuse(mount_dir, "readdir_redact", src_fd, &fuse_dev), 0); + + FUSE_ACTION + int j; + + TEST(dir =3D s_opendir(s(mount_dir)), dir); + while ((dirent =3D readdir(dir))) { + errno =3D 0; + TESTEQUAL(errno, 0); + for (j =3D 0; j < ARRAY_SIZE(names); ++j) + if (names[j] && + strcmp(names[j], dirent->d_name) =3D=3D 0) { + names[j] =3D NULL; + count++; + break; + } + TESTNE(j, ARRAY_SIZE(names)); + TESTGE(num_shown, count); + } + TESTEQUAL(count, num_shown); + TESTSYSCALL(closedir(dir)); + dir =3D NULL; + FUSE_DAEMON + bool skip =3D true; + for (int i =3D 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 =3D + (struct fuse_in_header *)bytes_in; + ssize_t res =3D read(fuse_dev, bytes_in, sizeof(bytes_in)); + int length_out =3D 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 =3D NULL; + struct fuse_dirent *next =3D NULL; + bool again =3D false; + int dir_ent_len =3D 0; + + // We're ignoring the error_in extension + res -=3D ERR_IN_EXT_LEN; + TESTGE(res, sizeof(struct fuse_in_header) + + sizeof(struct fuse_read_in) + + sizeof(struct fuse_read_out)); + + pos =3D bytes_in + sizeof(struct fuse_in_header); + fuse_read_in =3D (struct fuse_read_in *) pos; + pos +=3D sizeof(*fuse_read_in); + fuse_read_out_in =3D (struct fuse_read_out *) pos; + pos +=3D sizeof(*fuse_read_out_in); + dirs_in =3D pos; + + pos =3D bytes_out + sizeof(struct fuse_out_header); + fuse_read_out_out =3D (struct fuse_read_out *) pos; + pos +=3D sizeof(*fuse_read_out_out); + dirs_out =3D pos; + + if (dirs_in < bytes_in + res) { + bool is_dot; + + fuse_dirent_in =3D (struct fuse_dirent *) dirs_in; + is_dot =3D (fuse_dirent_in->namelen =3D=3D 1 && + !strncmp(fuse_dirent_in->name, ".", 1)) || + (fuse_dirent_in->namelen =3D=3D 2 && + !strncmp(fuse_dirent_in->name, "..", 2)); + + dir_ent_len =3D FUSE_DIRENT_ALIGN( + sizeof(*fuse_dirent_in) + + fuse_dirent_in->namelen); + + if (dirs_in + dir_ent_len < bytes_in + res) + next =3D (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 +=3D dir_ent_len; + } + again =3D ((skip && !is_dot) && next); + + if (!is_dot) + skip =3D !skip; + } + + fuse_read_out_out->offset =3D next ? next->off : + fuse_read_out_in->offset; + fuse_read_out_out->again =3D again; + + { + struct fuse_out_header *out_header =3D + (struct fuse_out_header *)bytes_out; + + *out_header =3D (struct fuse_out_header) { + .len =3D sizeof(*out_header) + + sizeof(*fuse_read_out_out) + length_out, + .unique =3D in_header->unique, + }; + TESTEQUAL(write(fuse_dev, bytes_out, out_header->len), + out_header->len); + } + } + FUSE_DONE + + result =3D 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 subd= ir + * than a test of any new functionality + */ +static int bpf_test_creat(const char *mount_dir) +{ + const char *dir_name =3D "show"; + const char *file_name =3D "file"; + int result =3D TEST_FAILURE; + int fuse_dev =3D -1; + int pid =3D -1; + int status; + int fd =3D -1; + + TESTEQUAL(mount_fuse(mount_dir, NULL, -1, &fuse_dev), 0); + + FUSE_ACTION + TEST(fd =3D s_creat(s_path(s_path(s(mount_dir), s(dir_name)), + s(file_name)), + 0777), + fd !=3D -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 =3D 3, + .generation =3D 1, + .attr.ino =3D 100, + .attr.size =3D 4, + .attr.blksize =3D 512, + .attr.mode =3D 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 =3D 2, + .generation =3D 1, + .attr.ino =3D 200, + .attr.size =3D 4, + .attr.blksize =3D 512, + .attr.mode =3D S_IFREG, + }), + fuse_open_out, ((struct fuse_open_out) { + .fh =3D 1, + .open_flags =3D create_in->flags, + })); + + TESTFUSEIN(FUSE_FLUSH, flush_in); + TESTFUSEOUTEMPTY(); + + TESTFUSEIN(FUSE_RELEASE, release_in); + TESTFUSEOUTEMPTY(); + FUSE_DONE + + result =3D 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 =3D NULL; + struct bpf_link *test_link =3D NULL; + static const char * const dir_names[] =3D { + "show", + "hide", + }; + const char *file_name =3D "file"; + const char *data =3D "The quick brown fox jumps over the lazy dog\n"; + int result =3D TEST_FAILURE; + int src_fd =3D -1; + int fuse_dev =3D -1; + int fd =3D -1; + + TEST(src_fd =3D open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd !=3D -1); + TESTSYSCALL(mkdirat(src_fd, dir_names[0], 0777)); + TESTSYSCALL(mkdirat(src_fd, dir_names[1], 0777)); + TEST(test_skel =3D test_bpf__open_and_load(), test_skel !=3D NULL); + TEST(test_link =3D bpf_map__attach_struct_ops(test_skel->maps.test_hidden= _ops), test_link !=3D NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_hidden", src_fd, &fuse_dev), 0); + + TEST(fd =3D s_creat(s_path(s_path(s(mount_dir), s(dir_names[0])), + s(file_name)), + 0777), + fd !=3D -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 =3D 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 =3D NULL; + struct bpf_link *test_link =3D NULL; + const char *dir_name =3D "dir"; + int result =3D TEST_FAILURE; + int src_fd =3D -1; + int fuse_dev =3D -1; + struct stat st; + + TEST(src_fd =3D open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd !=3D -1); + TEST(test_skel =3D test_bpf__open_and_load(), test_skel !=3D NULL); + TEST(test_link =3D bpf_map__attach_struct_ops(test_skel->maps.test_trace_= ops), test_link !=3D 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 =3D 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 =3D NULL; + struct bpf_link *test_link =3D NULL; + const char *file_name =3D "real"; + int result =3D TEST_FAILURE; + int src_fd =3D -1; + int fuse_dev =3D -1; + int fd =3D -1; + struct stat st; + + TEST(src_fd =3D open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd !=3D -1); + TEST(test_skel =3D test_bpf__open_and_load(), test_skel !=3D NULL); + TEST(test_link =3D bpf_map__attach_struct_ops(test_skel->maps.test_trace_= ops), test_link !=3D NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_trace_ops", src_fd, &fuse_dev), 0); + + TEST(fd =3D s_creat(s_path(s(mount_dir), s(file_name)), + 0777), + fd !=3D -1); + TESTEQUAL(bpf_test_trace("Create"), 0); + if (close_first) { + TESTSYSCALL(close(fd)); + fd =3D -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 =3D -1; + } + result =3D 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 =3D NULL; + struct bpf_link *test_link =3D NULL; + const char *dir_name =3D "dir"; + int result =3D TEST_FAILURE; + int src_fd =3D -1; + int fuse_dev =3D -1; + struct stat st; + + TEST(src_fd =3D open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd !=3D -1); + TEST(test_skel =3D test_bpf__open_and_load(), test_skel !=3D NULL); + TEST(test_link =3D bpf_map__attach_struct_ops(test_skel->maps.test_error_= ops), test_link !=3D 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 =3D 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 =3D NULL; + struct bpf_link *test_link =3D NULL; + const char *dir_name =3D "doesnotexist"; + int result =3D TEST_FAILURE; + int src_fd =3D -1; + int fuse_dev =3D -1; + int pid =3D -1; + int status; + + TEST(src_fd =3D open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd !=3D -1); + TEST(test_skel =3D test_bpf__open_and_load(), test_skel !=3D NULL); + TEST(test_link =3D bpf_map__attach_struct_ops(test_skel->maps.test_error_= ops), test_link !=3D 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 =3D 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 =3D TEST_FAILURE; + int bpf_fd1 =3D -1; + int bpf_fd2 =3D -1; + int bpf_fd3 =3D -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 =3D 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 =3D TEST_FAILURE; + int bpf_fd1 =3D -1; + int bpf_fd2 =3D -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 =3D TEST_SUCCESS; +out: + close(bpf_fd1); + close(bpf_fd2); + return result; +} + +static int bpf_test_verifier_packet_invalidation(const char *mount_dir) +{ + int result =3D TEST_FAILURE; + int bpf_fd1 =3D -1; + int bpf_fd2 =3D -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 =3D TEST_SUCCESS; +out: + close(bpf_fd1); + close(bpf_fd2); + return result; +} + +static int bpf_test_verifier_nonsense_read(const char *mount_dir) +{ + int result =3D TEST_FAILURE; + int bpf_fd1 =3D -1; + + TESTEQUAL(install_elf_bpf_invalid("test_bpf.bpf.o", "test_verify_fail6", + &bpf_fd1, NULL, NULL), 0); + result =3D TEST_SUCCESS; +out: + close(bpf_fd1); + return result; +} +#endif + +static int bpf_test_mknod(const char *mount_dir) +{ + struct test_bpf *test_skel =3D NULL; + struct bpf_link *test_link =3D NULL; + const char *file_name =3D "real"; + int result =3D TEST_FAILURE; + int src_fd =3D -1; + int bpf_fd =3D -1; + int fuse_dev =3D -1; + struct stat st; + + TEST(src_fd =3D open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd !=3D -1); + TEST(test_skel =3D test_bpf__open_and_load(), test_skel !=3D NULL); + TEST(test_link =3D bpf_map__attach_struct_ops(test_skel->maps.test_trace_= ops), test_link !=3D 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 =3D 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 =3D NULL; + struct bpf_link *test_link =3D NULL; + const char *show =3D "show"; + const int files =3D 1000; + + int result =3D TEST_FAILURE; + int src_fd =3D -1; + int bpf_fd =3D -1; + int fuse_dev =3D -1; + int pid =3D -1; + int status; + + TEST(src_fd =3D open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd !=3D -1); + TEST(test_skel =3D test_bpf__open_and_load(), test_skel !=3D NULL); + TEST(test_link =3D bpf_map__attach_struct_ops(test_skel->maps.trace_ops),= test_link !=3D NULL); + TESTEQUAL(mount_fuse(mount_dir, "trace_ops", src_fd, &fuse_dev), 0); + + FUSE_ACTION + int i; + int fd; + DIR *dir =3D NULL; + struct dirent *dirent; + + TESTSYSCALL(s_mkdir(s_path(s(mount_dir), s(show)), 0777)); + for (i =3D 0; i < files; ++i) { + char filename[NAME_MAX]; + + sprintf(filename, "%d", i); + TEST(fd =3D s_creat(s_path(s_path(s(mount_dir), s(show)), + s(filename)), 0777), fd !=3D -1); + TESTSYSCALL(close(fd)); + } + + TEST(dir =3D s_opendir(s_path(s(mount_dir), s(show))), dir); + for (dirent =3D readdir(dir); dirent; dirent =3D readdir(dir)) + ; + closedir(dir); + FUSE_DAEMON + int i; + + for (i =3D 0; i < files + 2; ++i) { + TESTFUSELOOKUP(show, FUSE_PREFILTER); + TESTFUSEOUTREAD(show, 5); + } + FUSE_DONE + + result =3D 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 =3D NULL; + struct bpf_link *test_link =3D NULL; + const char *file_name =3D "real"; + const char *link_name =3D "partial"; + int result =3D TEST_FAILURE; + int fd =3D -1; + int src_fd =3D -1; + int bpf_fd =3D -1; + int fuse_dev =3D -1; + struct stat st; + + TEST(src_fd =3D open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd !=3D -1); + TEST(test_skel =3D test_bpf__open_and_load(), test_skel !=3D NULL); + TEST(test_link =3D bpf_map__attach_struct_ops(test_skel->maps.test_trace_= ops), test_link !=3D NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_trace_ops", src_fd, &fuse_dev), 0); + + TEST(fd =3D s_creat(s_path(s(mount_dir), s(file_name)), 0777), fd !=3D -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 =3D 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 =3D NULL; + struct bpf_link *test_link =3D NULL; + const char *test_name =3D "real"; + const char *symlink_name =3D "partial"; + const char *test_data =3D "Weebles wobble but they don't fall down"; + int result =3D TEST_FAILURE; + int bpf_fd =3D -1; + int src_fd =3D -1; + int fuse_dev =3D -1; + int fd =3D -1; + char read_buffer[256] =3D {}; + ssize_t bytes_read; + + TEST(src_fd =3D open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd !=3D -1); + TEST(fd =3D openat(src_fd, test_name, O_CREAT | O_RDWR | O_CLOEXEC, 0777), + fd !=3D -1); + TESTEQUAL(write(fd, test_data, strlen(test_data)), strlen(test_data)); + TESTSYSCALL(close(fd)); + fd =3D -1; + + TEST(test_skel =3D test_bpf__open_and_load(), test_skel !=3D NULL); + TEST(test_link =3D bpf_map__attach_struct_ops(test_skel->maps.test_trace_= ops), test_link !=3D 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 =3D s_open(s_path(s(mount_dir), s(symlink_name)), O_RDONLY | O= _CLOEXEC), fd !=3D -1); + bytes_read =3D 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 =3D 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 =3D NULL; + struct bpf_link *test_link =3D NULL; + static const char file_name[] =3D "real"; + static const char xattr_name[] =3D "user.xattr_test_name"; + static const char xattr_value[] =3D "this_is_a_test"; + const size_t xattr_size =3D sizeof(xattr_value); + char xattr_value_ret[256]; + ssize_t xattr_size_ret; + int result =3D TEST_FAILURE; + int fd =3D -1; + int src_fd =3D -1; + int bpf_fd =3D -1; + int fuse_dev =3D -1; + struct stat st; + + memset(xattr_value_ret, '\0', sizeof(xattr_value_ret)); + + TEST(src_fd =3D open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd !=3D -1); + TEST(test_skel =3D test_bpf__open_and_load(), test_skel !=3D NULL); + TEST(test_link =3D bpf_map__attach_struct_ops(test_skel->maps.test_trace_= ops), test_link !=3D NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_trace_ops", src_fd, &fuse_dev), 0); + + + TEST(fd =3D s_creat(s_path(s(mount_dir), s(file_name)), 0777), fd !=3D -1= ); + TESTEQUAL(bpf_test_trace("Create"), 0); + TESTSYSCALL(close(fd)); + + TESTSYSCALL(s_stat(s_path(s(ft_src), s(file_name)), &st)); + TEST(result =3D s_getxattr(s_path(s(mount_dir), s(file_name)), xattr_name, + xattr_value_ret, sizeof(xattr_value_ret), + &xattr_size_ret), + result =3D=3D -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 =3D 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 =3D NULL; + struct bpf_link *test_link =3D NULL; + const char *backing_name =3D "backing"; + const char *test_data =3D "data"; + const char *test_name =3D "test"; + + int result =3D TEST_FAILURE; + int fuse_dev =3D -1; + int fd =3D -1; + int pid =3D -1; + int status; + + TEST(test_skel =3D test_bpf__open_and_load(), test_skel !=3D NULL); + TEST(test_link =3D bpf_map__attach_struct_ops(test_skel->maps.trace_ops),= test_link !=3D NULL); + TESTEQUAL(mount_fuse_no_init(mount_dir, NULL, -1, &fuse_dev), 0); + FUSE_ACTION + char data[256] =3D {0}; + + TESTERR(fd =3D s_open(s_path(s(mount_dir), s(test_name)), + O_RDONLY | O_CLOEXEC), fd !=3D -1); + TESTEQUAL(read(fd, data, strlen(test_data)), strlen(test_data)); + TESTCOND(!strcmp(data, test_data)); + TESTSYSCALL(close(fd)); + fd =3D -1; + TESTSYSCALL(umount(mount_dir)); + FUSE_DAEMON + //int bpf_fd =3D -1; + int backing_fd =3D -1; + struct fuse_bpf_entry_out bpf_entry[2]; + + TESTERR(backing_fd =3D s_creat(s_path(s(ft_src), s(backing_name)), 0777), + backing_fd !=3D -1); + TESTEQUAL(write(backing_fd, test_data, strlen(test_data)), + strlen(test_data)); + + TESTFUSEINIT(); + TESTFUSELOOKUP(test_name, 0); + bpf_entry[0] =3D (struct fuse_bpf_entry_out) { + .entry_type =3D FUSE_ENTRY_BPF, + .name =3D "trace_ops", + }; + bpf_entry[1] =3D (struct fuse_bpf_entry_out) { + .entry_type =3D FUSE_ENTRY_BACKING, + .fd =3D 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 =3D 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 =3D NULL; + struct bpf_link *test_link =3D NULL; + const char *backing_name =3D "backing"; + const char *test_name =3D "test"; + + int result =3D TEST_FAILURE; + int fuse_dev =3D -1; + int fd =3D -1; + int pid =3D -1; + int status; + + TEST(test_skel =3D test_bpf__open_and_load(), test_skel !=3D NULL); + TEST(test_link =3D bpf_map__attach_struct_ops(test_skel->maps.trace_ops),= test_link !=3D NULL); + TESTEQUAL(mount_fuse_no_init(mount_dir, NULL, -1, &fuse_dev), 0); + FUSE_ACTION + + TESTERR(fd =3D s_open(s_path(s(mount_dir), s(test_name)), + O_RDONLY | O_CLOEXEC), fd =3D=3D -1); + FUSE_DAEMON + int backing_fd =3D -1; + struct fuse_bpf_entry_out bpf_entry[2]; + + TESTERR(backing_fd =3D s_creat(s_path(s(ft_src), s(backing_name)), 0777), + backing_fd !=3D -1); + + TESTFUSEINIT(); + TESTFUSELOOKUP(test_name, 0); + bpf_entry[0] =3D (struct fuse_bpf_entry_out) { + .entry_type =3D FUSE_ENTRY_BPF, + .name =3D "trace_ops", + }; + bpf_entry[1] =3D (struct fuse_bpf_entry_out) { + .entry_type =3D FUSE_ENTRY_BACKING, + .fd =3D 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 =3D 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 =3D NULL; + struct bpf_link *test_link =3D NULL; + const char *backing_name =3D "backingdir"; + const char *test_name =3D "testdir"; + const char *names[] =3D {"file", ".", ".."}; + + int result =3D TEST_FAILURE; + int fuse_dev =3D -1; + int fd =3D -1; + int pid =3D -1; + int status; + TEST(test_skel =3D test_bpf__open_and_load(), test_skel !=3D NULL); + TEST(test_link =3D bpf_map__attach_struct_ops(test_skel->maps.trace_ops),= test_link !=3D NULL); + TESTEQUAL(mount_fuse_no_init(mount_dir, NULL, -1, &fuse_dev), 0); + FUSE_ACTION + DIR *dir =3D NULL; + struct dirent *dirent; + int i, j; + + TEST(dir =3D s_opendir(s_path(s(mount_dir), s(test_name))), dir); + + for (i =3D 0; i < ARRAY_SIZE(names); ++i) { + TEST(dirent =3D readdir(dir), dirent); + + for (j =3D 0; j < ARRAY_SIZE(names); ++j) + if (names[j] && + strcmp(names[j], dirent->d_name) =3D=3D 0) { + names[j] =3D NULL; + break; + } + TESTNE(j, ARRAY_SIZE(names)); + } + TEST(dirent =3D readdir(dir), dirent =3D=3D NULL); + TESTSYSCALL(closedir(dir)); + dir =3D NULL; + TESTEQUAL(bpf_test_trace("Read Dir"), 0); + TESTSYSCALL(umount(mount_dir)); + FUSE_DAEMON + int backing_fd =3D -1; + struct fuse_bpf_entry_out bpf_entry[2]; + + TESTSYSCALL(s_mkdir(s_path(s(ft_src), s(backing_name)), 0777)); + TESTERR(backing_fd =3D s_open(s_path(s(ft_src), s(backing_name)), O_RDON= LY | O_CLOEXEC), + backing_fd !=3D -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] =3D (struct fuse_bpf_entry_out) { + .entry_type =3D FUSE_ENTRY_BPF, + .name =3D "passthrough", + }; + bpf_entry[1] =3D (struct fuse_bpf_entry_out) { + .entry_type =3D FUSE_ENTRY_BACKING, + .fd =3D backing_fd, + }; + bpf_entry[0] =3D (struct fuse_bpf_entry_out) { + .entry_type =3D FUSE_ENTRY_BPF, + .name =3D "trace_ops", + }; + bpf_entry[1] =3D (struct fuse_bpf_entry_out) { + .entry_type =3D FUSE_ENTRY_BACKING, + .fd =3D 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 =3D 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 =3D NULL; + struct bpf_link *test_link =3D NULL; + const char *folder1 =3D "folder1"; + const char *folder2 =3D "folder2"; + const char *file =3D "file1"; + const char *contents1 =3D "contents1"; + const char *contents2 =3D "contents2"; + + int result =3D TEST_FAILURE; + int fuse_dev =3D -1; + int fd =3D -1; + int src_fd =3D -1; + int pid =3D -1; + int status; + char data[256] =3D {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 =3D s_creat(s_pathn(3, s(ft_src), s(folder1), s(file)), 0777), + fd !=3D -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 =3D s_creat(s_pathn(3, s(ft_src), s(folder2), s(file)), 0777), + fd !=3D -1); + TESTEQUAL(write(fd, contents2, strlen(contents2)), strlen(contents2)); + TESTSYSCALL(close(fd)); + + TEST(src_fd =3D open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd !=3D -1); + TEST(test_skel =3D test_bpf__open_and_load(), test_skel !=3D NULL); + TEST(test_link =3D bpf_map__attach_struct_ops(test_skel->maps.passthrough= _ops), test_link !=3D NULL); + TESTEQUAL(mount_fuse_no_init(mount_dir, "passthrough", src_fd, &fuse_dev)= , 0); + + FUSE_ACTION + TESTERR(fd =3D s_open(s_pathn(3, s(mount_dir), s(folder1), + s(file)), + O_RDONLY | O_CLOEXEC), fd !=3D -1); + TESTEQUAL(read(fd, data, sizeof(data)), strlen(contents2)); + TESTCOND(!strcmp(data, contents2)); + TESTEQUAL(bpf_test_no_trace("file"), 0); + TESTSYSCALL(close(fd)); + fd =3D -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 =3D -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 =3D s_open(s_path(s(ft_src), s(folder2)), + O_DIRECTORY | O_RDONLY | O_CLOEXEC), + backing_fd !=3D -1); + + bpf_entry[0] =3D (struct fuse_bpf_entry_out) { + .entry_type =3D FUSE_ENTRY_REMOVE_BPF, + }; + bpf_entry[1] =3D (struct fuse_bpf_entry_out) { + .entry_type =3D FUSE_ENTRY_BACKING, + .fd =3D 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)) !=3D -1) + ; + TESTSYSCALL(close(backing_fd)); + FUSE_DONE + + result =3D 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 =3D NULL; + struct bpf_link *test_link =3D NULL; + const char *dir_name =3D "dir"; + const char *dir_name2 =3D "dir2"; + int result =3D TEST_FAILURE; + int src_fd =3D -1; + int fuse_dev =3D -1; + struct stat st; + + TEST(src_fd =3D open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd !=3D -1); + TEST(test_skel =3D test_bpf__open_and_load(), test_skel !=3D NULL); + TEST(test_link =3D bpf_map__attach_struct_ops(test_skel->maps.test_trace_= ops), test_link !=3D 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 =3D 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 =3D NULL; + struct bpf_link *test_link =3D NULL; + const char *dir =3D "dir"; + const char *file1 =3D "file1"; + const char *file2 =3D "file2"; + int result =3D TEST_FAILURE; + int src_fd =3D -1; + int fuse_dev =3D -1; + int fd =3D -1; + + TEST(src_fd =3D open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd !=3D -1); + TEST(test_skel =3D test_bpf__open_and_load(), test_skel !=3D NULL); + TEST(test_link =3D bpf_map__attach_struct_ops(test_skel->maps.test_trace_= ops), test_link !=3D 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 =3D s_creat(s_pathn(3, s(mount_dir), s(dir), s(file1)), 0777), + fd !=3D -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 =3D 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 =3D "file"; + int result =3D TEST_FAILURE; + int src_fd =3D -1; + int fuse_dev =3D -1; + int fd =3D -1; + char *addr =3D NULL; + + TEST(src_fd =3D open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd !=3D -1); + TESTEQUAL(mount_fuse(mount_dir, NULL, src_fd, &fuse_dev), 0); + TEST(fd =3D s_open(s_path(s(mount_dir), s(file)), + O_CREAT | O_RDWR | O_CLOEXEC, 0777), + fd !=3D -1); + TESTSYSCALL(fallocate(fd, 0, 4096, SEEK_CUR)); + TEST(addr =3D mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0), + addr !=3D (void *) -1); + memset(addr, 'a', 4096); + + result =3D 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 =3D TEST_FAILURE; + struct __user_cap_header_struct uchs =3D { _LINUX_CAPABILITY_VERSION_3 }; + struct __user_cap_data_struct ucds[2]; + int src_fd =3D -1; + int fuse_dev =3D -1; + DIR *dir =3D NULL; + + /* Must remove capabilities for this test. */ + TESTSYSCALL(syscall(SYS_capget, &uchs, ucds)); + ucds[0].effective &=3D ~(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 =3D opendir("test"), dir =3D=3D NULL); + if (dir) + closedir(dir); + dir =3D NULL; + + TEST(src_fd =3D open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd !=3D -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 =3D s_opendir(s_path(s(mount_dir), s("test"))), dir =3D=3D NULL); + + result =3D TEST_SUCCESS; +out: + ucds[0].effective |=3D 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 =3D NULL; + struct bpf_link *test_link =3D NULL; + int result =3D TEST_FAILURE; + int src_fd =3D -1; + int fuse_dev =3D -1; + int fd =3D -1; + struct statfs st; + + TEST(src_fd =3D open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd !=3D -1); + TEST(test_skel =3D test_bpf__open_and_load(), test_skel !=3D NULL); + TEST(test_link =3D bpf_map__attach_struct_ops(test_skel->maps.test_trace_= ops), test_link !=3D 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 =3D 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 =3D NULL; + struct bpf_link *test_link =3D NULL; + const char *file =3D "real"; + const char *test_data =3D "data"; + int result =3D TEST_FAILURE; + int src_fd =3D -1; + int fuse_dev =3D -1; + int fd =3D -1; + + TEST(src_fd =3D open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd !=3D -1); + TEST(fd =3D openat(src_fd, file, O_CREAT | O_RDWR | O_CLOEXEC, 0777), + fd !=3D -1); + TESTEQUAL(write(fd, test_data, strlen(test_data)), strlen(test_data)); + TESTSYSCALL(close(fd)); + fd =3D -1; + TEST(test_skel =3D test_bpf__open_and_load(), test_skel !=3D NULL); + TEST(test_link =3D bpf_map__attach_struct_ops(test_skel->maps.test_trace_= ops), test_link !=3D NULL); + TESTEQUAL(mount_fuse(mount_dir, "test_trace_ops", src_fd, &fuse_dev), 0); + + TEST(fd =3D s_open(s_path(s(mount_dir), s(file)), O_RDONLY | O_CLOEXEC), + fd !=3D -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 =3D 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_d= ir) +{ + const char *folder1 =3D "folder1"; + const char *content_file =3D "content.txt"; + const char *content =3D "hello world"; + + int result =3D TEST_FAILURE; + int fuse_dev =3D -1; + int src_fd =3D -1; + int content_fd =3D -1; + int pid =3D -1; + int status; + + TESTSYSCALL(s_mkdir(s_path(s(ft_src), s(folder1)), 0777)); + TEST(content_fd =3D s_creat(s_pathn(3, s(ft_src), s(folder1), s(content_f= ile)), 0777), + content_fd !=3D -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 =3D NULL; + struct dirent *mount_dirent; + int dst_folder1_fd =3D -1; + int dst_content_fd =3D -1; + int dst_content_read_size =3D -1; + char content_buffer[12]; + + // Step 1: Lookup folder1 + TESTERR(dst_folder1_fd =3D s_open(s_path(s(mount_dir), s(folder1)), + O_RDONLY | O_CLOEXEC), dst_folder1_fd !=3D -1); + + // Check 1: Read content file (backed) + TESTERR(dst_content_fd =3D + s_open(s_pathn(3, s(mount_dir), s(folder1), s(content_file)), + O_RDONLY | O_CLOEXEC), dst_content_fd !=3D -1); + + TEST(dst_content_read_size =3D + read(dst_content_fd, content_buffer, strlen(content)), + dst_content_read_size =3D=3D strlen(content) && + strcmp(content, content_buffer) =3D=3D 0); + + TESTSYSCALL(close(dst_content_fd)); + dst_content_fd =3D -1; + TESTSYSCALL(close(dst_folder1_fd)); + dst_folder1_fd =3D -1; + memset(content_buffer, 0, strlen(content)); + + // Step 2: readdir folder 1 + TEST(open_mount_dir =3D s_opendir(s(mount_dir)), + open_mount_dir !=3D NULL); + TEST(mount_dirent =3D readdir(open_mount_dir), mount_dirent !=3D NULL); + TESTSYSCALL(closedir(open_mount_dir)); + open_mount_dir =3D NULL; + + // Check 2: Read content file again (must be backed) + TESTERR(dst_content_fd =3D + s_open(s_pathn(3, s(mount_dir), s(folder1), s(content_file)), + O_RDONLY | O_CLOEXEC), dst_content_fd !=3D -1); + + TEST(dst_content_read_size =3D + read(dst_content_fd, content_buffer, strlen(content)), + dst_content_read_size =3D=3D strlen(content) && + strcmp(content, content_buffer) =3D=3D 0); + + TESTSYSCALL(close(dst_content_fd)); + dst_content_fd =3D -1; + FUSE_DAEMON + size_t read_size =3D 0; + struct fuse_in_header *in_header =3D (struct fuse_in_header *)bytes_in; + struct fuse_read_out *read_out =3D NULL; + struct fuse_attr attr =3D {}; + int backing_fd =3D -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 =3D s_open(s_path(s(ft_src), s(folder1)), + O_DIRECTORY | O_RDONLY | O_CLOEXEC), + backing_fd !=3D -1); + TESTFUSEOUT2_IOCTL(fuse_entry_out, ((struct fuse_entry_out) { + .nodeid =3D attr.ino, + .generation =3D 0, + .entry_valid =3D UINT64_MAX, + .attr_valid =3D UINT64_MAX, + .entry_valid_nsec =3D UINT32_MAX, + .attr_valid_nsec =3D UINT32_MAX, + .attr =3D attr, + }), fuse_bpf_entry_out, ((struct fuse_bpf_entry_out) { + .entry_type =3D FUSE_ENTRY_BACKING, + .fd =3D 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 =3D 100, + .open_flags =3D 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 =3D UINT64_MAX, + .attr_valid_nsec =3D UINT32_MAX, + .attr =3D attr + })); + + // Step 2: Handle readdirplus + read_size =3D read(fuse_dev, bytes_in, sizeof(bytes_in)); + TESTEQUAL(in_header->opcode, FUSE_READDIRPLUS); + + struct fuse_direntplus *dirent_plus =3D + (struct fuse_direntplus *) (bytes_in + read_size); + struct fuse_dirent dirent; + struct fuse_entry_out entry_out; + + read_out =3D (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 =3D (struct fuse_dirent) { + .ino =3D attr.ino, + .off =3D 1, + .namelen =3D strlen(folder1), + .type =3D DT_REG + }; + entry_out =3D (struct fuse_entry_out) { + .nodeid =3D attr.ino, + .generation =3D 0, + .entry_valid =3D UINT64_MAX, + .attr_valid =3D UINT64_MAX, + .entry_valid_nsec =3D UINT32_MAX, + .attr_valid_nsec =3D UINT32_MAX, + .attr =3D attr + }; + *dirent_plus =3D (struct fuse_direntplus) { + .dirent =3D dirent, + .entry_out =3D entry_out + }; + + strcpy((char *)(bytes_in + read_size + sizeof(*dirent_plus)), folder1); + read_size +=3D 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 =3D 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 =3D NULL; + struct bpf_link *test_link =3D NULL; + const char *folder1 =3D "folder1"; + const char *folder2 =3D "folder2"; + int result =3D TEST_FAILURE; + int fuse_dev =3D -1; + int src_fd =3D -1; + int content_fd =3D -1; + int pid =3D -1; + int status; + + TEST(test_skel =3D test_bpf__open_and_load(), test_skel !=3D NULL); + TEST(test_link =3D bpf_map__attach_struct_ops(test_skel->maps.readdir_plu= s_ops), test_link !=3D 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 =3D NULL; + struct dirent *dirent; + + // Folder 1: Readdir with no nodeid + TEST(open_dir =3D s_opendir(s_path(s(ft_dst), s(folder1))), + open_dir !=3D NULL); + TEST(dirent =3D readdir(open_dir), dirent =3D=3D NULL); + TESTCOND(errno =3D=3D EINVAL); + TESTSYSCALL(closedir(open_dir)); + open_dir =3D NULL; + + // Folder 2: Readdir with a nodeid + TEST(open_dir =3D s_opendir(s_path(s(ft_dst), s(folder2))), + open_dir !=3D NULL); + TEST(dirent =3D readdir(open_dir), dirent =3D=3D NULL); + TESTCOND(errno =3D=3D EINVAL); + TESTSYSCALL(closedir(open_dir)); + open_dir =3D NULL; + FUSE_DAEMON + size_t read_size; + struct fuse_in_header *in_header =3D (struct fuse_in_header *)bytes_in; + struct fuse_attr attr =3D {}; + int backing_fd =3D -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 =3D s_open(s_path(s(ft_src), s(folder1)), + O_DIRECTORY | O_RDONLY | O_CLOEXEC), + backing_fd !=3D -1); + + bpf_entry[0] =3D (struct fuse_bpf_entry_out) { + .entry_type =3D FUSE_ENTRY_BPF, + .name =3D "readdir_plus", + }; + bpf_entry[1] =3D (struct fuse_bpf_entry_out) { + .entry_type =3D FUSE_ENTRY_BACKING, + .fd =3D backing_fd, + }; + TESTFUSEOUT3_IOCTL(fuse_entry_out, ((struct fuse_entry_out) { + .nodeid =3D 0, + .generation =3D 0, + .entry_valid =3D UINT64_MAX, + .attr_valid =3D UINT64_MAX, + .entry_valid_nsec =3D UINT32_MAX, + .attr_valid_nsec =3D UINT32_MAX, + .attr =3D attr, + }), fuse_bpf_entry_out, bpf_entry[0], + fuse_bpf_entry_out, bpf_entry[1]); + TESTSYSCALL(close(backing_fd)); + TEST(read_size =3D 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 =3D s_open(s_path(s(ft_src), s(folder2)), + O_DIRECTORY | O_RDONLY | O_CLOEXEC), + backing_fd !=3D -1); + bpf_entry[0] =3D (struct fuse_bpf_entry_out) { + .entry_type =3D FUSE_ENTRY_BPF, + .name =3D "readdir_plus", + }; + bpf_entry[1] =3D (struct fuse_bpf_entry_out) { + .entry_type =3D FUSE_ENTRY_BACKING, + .fd =3D backing_fd, + }; + TESTFUSEOUT3_IOCTL(fuse_entry_out, ((struct fuse_entry_out) { + .nodeid =3D 10, + .generation =3D 0, + .entry_valid =3D UINT64_MAX, + .attr_valid =3D UINT64_MAX, + .entry_valid_nsec =3D UINT32_MAX, + .attr_valid_nsec =3D UINT32_MAX, + .attr =3D attr, + }), fuse_bpf_entry_out, bpf_entry[0], + fuse_bpf_entry_out, bpf_entry[1]); + TESTSYSCALL(close(backing_fd)); + TEST(read_size =3D read(fuse_dev, bytes_in, sizeof(bytes_in)), read_size= > 0); + TESTEQUAL(in_header->opcode, FUSE_READDIRPLUS); + TESTFUSEOUTERROR(-EINVAL); + FUSE_DONE + result =3D 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 wic= h 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 =3D "folder1"; + const char *content_file =3D "content.txt"; + const char *content =3D "hello world"; + int result =3D TEST_FAILURE; + int fuse_dev =3D -1; + int src_fd =3D -1; + int content_fd =3D -1; + int pid =3D -1; + int status; + TESTSYSCALL(s_mkdir(s_path(s(ft_src), s(folder1)), 0777)); + TEST(content_fd =3D s_creat(s_pathn(3, s(ft_src), s(folder1), s(content_f= ile)), 0777), + content_fd !=3D -1); + TESTEQUAL(write(content_fd, content, strlen(content)), strlen(content)); + TESTSYSCALL(close(content_fd)); + content_fd =3D -1; + TESTEQUAL(mount_fuse_no_init(mount_dir, NULL, -1, &fuse_dev), 0); + FUSE_ACTION + int dst_folder1_fd =3D -1; + int dst_content_fd =3D -1; + int dst_content_read_size =3D -1; + char content_buffer[11] =3D {0}; + // Step 1: Lookup folder1 + TESTERR(dst_folder1_fd =3D s_open(s_path(s(mount_dir), s(folder1)), + O_RDONLY | O_CLOEXEC), dst_folder1_fd !=3D -1); + TESTSYSCALL(close(dst_folder1_fd)); + dst_folder1_fd =3D -1; + // Step 2: Lookup folder1 again + TESTERR(dst_folder1_fd =3D s_open(s_path(s(mount_dir), s(folder1)), + O_RDONLY | O_CLOEXEC), dst_folder1_fd !=3D -1); + TESTSYSCALL(close(dst_folder1_fd)); + dst_folder1_fd =3D -1; + // Check 1: Read content file (must be backed) + TESTERR(dst_content_fd =3D + s_open(s_pathn(3, s(mount_dir), s(folder1), s(content_file)), + O_RDONLY | O_CLOEXEC), dst_content_fd !=3D -1); + TEST(dst_content_read_size =3D + read(dst_content_fd, content_buffer, strlen(content)), + dst_content_read_size =3D=3D strlen(content) && + strcmp(content, content_buffer) =3D=3D 0); + TESTSYSCALL(close(dst_content_fd)); + dst_content_fd =3D -1; + FUSE_DAEMON + struct fuse_attr attr =3D {}; + int backing_fd =3D -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 =3D s_open(s_path(s(ft_src), s(folder1)), + O_DIRECTORY | O_RDONLY | O_CLOEXEC), + backing_fd !=3D -1); + TESTFUSEOUT2_IOCTL(fuse_entry_out, ((struct fuse_entry_out) { + .nodeid =3D attr.ino, + .generation =3D 0, + .entry_valid =3D 0, + .attr_valid =3D UINT64_MAX, + .entry_valid_nsec =3D 0, + .attr_valid_nsec =3D UINT32_MAX, + .attr =3D attr, + }), fuse_bpf_entry_out, ((struct fuse_bpf_entry_out) { + .entry_type =3D FUSE_ENTRY_BACKING, + .fd =3D 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 rev= alidate + TESTFUSELOOKUP(folder1, 0); + TESTSYSCALL(s_fuse_attr(s_path(s(ft_src), s(folder1)), &attr)); + TEST(backing_fd =3D s_open(s_path(s(ft_src), s(folder1)), + O_DIRECTORY | O_RDONLY | O_CLOEXEC), + backing_fd !=3D -1); + TESTFUSEOUT2_IOCTL(fuse_entry_out, ((struct fuse_entry_out) { + .nodeid =3D attr.ino, + .generation =3D 0, + .entry_valid =3D UINT64_MAX, + .attr_valid =3D UINT64_MAX, + .entry_valid_nsec =3D UINT32_MAX, + .attr_valid_nsec =3D UINT32_MAX, + .attr =3D attr, + }), fuse_bpf_entry_out, ((struct fuse_bpf_entry_out) { + .entry_type =3D FUSE_ENTRY_BACKING, + .fd =3D 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 =3D s_open(s_path(s(ft_src), s(folder1)), + O_DIRECTORY | O_RDONLY | O_CLOEXEC), + backing_fd !=3D -1); + TESTFUSEOUT2_IOCTL(fuse_entry_out, ((struct fuse_entry_out) { + .nodeid =3D attr.ino, + .generation =3D 0, + .entry_valid =3D UINT64_MAX, + .attr_valid =3D UINT64_MAX, + .entry_valid_nsec =3D UINT32_MAX, + .attr_valid_nsec =3D UINT32_MAX, + .attr =3D attr, + }), fuse_bpf_entry_out, ((struct fuse_bpf_entry_out) { + .entry_type =3D FUSE_ENTRY_BACKING, + .fd =3D backing_fd, + })); + TESTSYSCALL(close(backing_fd)); + FUSE_DONE + result =3D 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 =3D NULL; + struct bpf_link *test_link =3D NULL; + const char *file1_name =3D "file1"; + const char *file2_name =3D "file2"; + const char *file3_name =3D "file3"; + int result =3D TEST_FAILURE; + int bpf_fd =3D -1; + int src_fd =3D -1; + int fuse_dev =3D -1; + int file_fd =3D -1; + int pid =3D -1; + int status; + + TEST(file_fd =3D s_creat(s_path(s(ft_src), s(file1_name)), 0777), + file_fd !=3D -1); + TESTSYSCALL(close(file_fd)); + TEST(file_fd =3D s_creat(s_path(s(ft_src), s(file2_name)), 0777), + file_fd !=3D -1); + TESTSYSCALL(close(file_fd)); + file_fd =3D -1; + TEST(src_fd =3D open(ft_src, O_DIRECTORY | O_RDONLY | O_CLOEXEC), + src_fd !=3D -1); + TEST(test_skel =3D test_bpf__open_and_load(), test_skel !=3D NULL); + TEST(test_link =3D bpf_map__attach_struct_ops(test_skel->maps.lookup_post= filter_ops), test_link !=3D NULL); + TESTEQUAL(mount_fuse(mount_dir, "lookup_post", src_fd, &fuse_dev), 0); + FUSE_ACTION + int fd =3D -1; + + TESTEQUAL(s_open(s_path(s(mount_dir), s(file1_name)), O_RDONLY), + -1); + TESTEQUAL(errno, ENOENT); + TEST(fd =3D s_open(s_path(s(mount_dir), s(file2_name)), O_RDONLY), + fd !=3D -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 =3D (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 =3D 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 =3D 0; i < tests; ++i) + run_test[i] =3D false; + + range =3D strtok(optarg, ","); + while (range) { + char *dash =3D strchr(range, '-'); + + if (dash) { + size_t start =3D 1, end =3D tests; + char *end_ptr; + + if (dash > range) { + start =3D strtol(range, &end_ptr, 10); + if (*end_ptr !=3D '-' || start <=3D 0 || start > tests) + ksft_exit_fail_msg("Bad range\n"); + } + + if (dash[1]) { + end =3D strtol(dash + 1, &end_ptr, 10); + if (*end_ptr || end <=3D start || end > tests) + ksft_exit_fail_msg("Bad range\n"); + } + + for (i =3D start; i <=3D end; ++i) + run_test[i - 1] =3D true; + } else { + char *end; + long value =3D strtol(range, &end, 10); + + if (*end || value <=3D 0 || value > tests) + ksft_exit_fail_msg("Bad range\n"); + run_test[value - 1] =3D true; + } + range =3D strtok(NULL, ","); + } +} + +static int parse_options(int argc, char *const *argv, bool *run_test, + size_t tests) +{ + signed char c; + + while ((c =3D getopt(argc, argv, "f:t:v")) !=3D -1) + switch (c) { + case 'f': + test_options.file =3D strtol(optarg, NULL, 10); + break; + + case 't': + parse_range(optarg, run_test, tests); + break; + + case 'v': + test_options.verbose =3D 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) =3D=3D 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 =3D NULL; + char *src_dir =3D NULL; + int i; + int fd, count; + +#define MAKE_TEST(test) = \ + { \ + test, #test \ + } + const struct test_case cases[] =3D { + 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 =3D 0; i < ARRAY_SIZE(cases); ++i) + run_test[i] =3D 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 =3D open("/dev/urandom", O_WRONLY | O_CLOEXEC); + count =3D 4096; + for (int i =3D 0; i < 128; ++i) + ioctl(fd, RNDADDTOENTCNT, &count); + close(fd); + + ksft_print_header(); + + if (geteuid() !=3D 0) + ksft_print_msg("Not a root, might fail to mount.\n"); + + if (tracing_on() !=3D TEST_SUCCESS) + ksft_exit_fail_msg("Can't turn on tracing\n"); + + src_dir =3D setup_mount_dir(ft_src); + mount_dir =3D setup_mount_dir(ft_dst); + if (src_dir =3D=3D NULL || mount_dir =3D=3D NULL) + ksft_exit_fail_msg("Can't create a mount dir\n"); + + ksft_set_plan(ARRAY_SIZE(run_test)); + + for (i =3D 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/te= sting/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 +#include +#include +#include + +#include + +#include "bpf_common.h" + +char _license[] SEC("license") =3D "GPL"; + +#if 0 +inline __always_inline int local_strcmp(const char *a, const char *b) +{ + int i; + + for (i =3D 0; i < __builtin_strlen(b) + 1; ++i) + if (a[i] !=3D b[i]) + return -1; + return 0; +} + + +/* This is a macro to enforce inlining. Without it, the compiler will do t= he 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_met= a_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_me= ta_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_me= ta_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_me= ta_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_me= ta_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_met= a_info *meta, + struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char *name_buf; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + name_buf =3D bpf_dynptr_slice(&name_ptr, 0, NULL, 1); + bpf_printk("Lookup: %lx %s", meta->nodeid, name_buf); + if (meta->nodeid =3D=3D 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_met= a_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_me= ta_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_met= a_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_me= ta_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_m= eta_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_me= ta_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_fus= e_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_m= eta_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_m= eta_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_fus= e_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_met= a_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_m= eta_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 =3D false; + int ret; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + + /* bpf_dynptr_slice will only return a pointer if the dynptr is long enou= gh */ + name_buf =3D bpf_dynptr_slice(&name_ptr, 0, NULL, 8); + if (name_buf) { + if (bpf_strncmp(name_buf, 8, "partial") =3D=3D 0) + backing =3D true; + goto print; + } +=09 + name_buf =3D bpf_dynptr_slice(&name_ptr, 0, NULL, 6); + if (name_buf) { + if (bpf_strncmp(name_buf, 6, "file1") =3D=3D 0) + backing =3D true; + if (bpf_strncmp(name_buf, 6, "file2") =3D=3D 0) + backing =3D true; + goto print; + } + + name_buf =3D bpf_dynptr_slice(&name_ptr, 0, NULL, 5); + if (name_buf) { + if (bpf_strncmp(name_buf, 5, "dir2") =3D=3D 0) + backing =3D true; + if (bpf_strncmp(name_buf, 5, "real") =3D=3D 0) + backing =3D true; + goto print; + } + + name_buf =3D bpf_dynptr_slice(&name_ptr, 0, NULL, 4); + if (name_buf) { + if (bpf_strncmp(name_buf, 4, "dir") =3D=3D 0) + backing =3D 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_met= a_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 =3D bpf_dynptr_slice(&name_ptr, 0, NULL, 8); + if (name_buf) { + if (bpf_strncmp(name_buf, 8, "partial") =3D=3D 0) + out->nodeid =3D 6; + goto print; + } +=09 + name_buf =3D bpf_dynptr_slice(&name_ptr, 0, NULL, 5); + if (name_buf) { + if (bpf_strncmp(name_buf, 5, "real") =3D=3D 0) + out->nodeid =3D 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_i= nfo *meta, + struct fuse_open_in *in) +{ + int backing =3D BPF_FUSE_USER; + + switch (meta->nodeid) { + case 5: + backing =3D BPF_FUSE_CONTINUE; + bpf_printk("Setting BPF_FUSE_CONTINUE:%d", BPF_FUSE_CONTINUE); + break; + + case 6: + backing =3D 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_m= eta_info *meta, + struct fuse_read_in *in) +{ + bpf_printk("read %llu %llu", in->fh, in->offset); + if (in->fh =3D=3D 1 && in->offset =3D=3D 0) + return BPF_FUSE_USER; + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, test_getattr_prefilter, const struct bpf_fuse_met= a_info *meta, + struct fuse_getattr_in *in) +{ + /* real and partial use backing file */ + int backing =3D 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 =3D 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_met= a_info *meta, + struct fuse_setattr_in *in) +{ + /* real and partial use backing file */ + int backing =3D BPF_FUSE_USER; + + switch (meta->nodeid) { + case 1: + case 5: + case 6: + /* TODO See above */ + case 100: + backing =3D 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_met= a_info *meta, + struct fuse_open_in *in) +{ + int backing =3D BPF_FUSE_USER; + + switch (meta->nodeid) { + case 1: + backing =3D 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_me= ta_info *meta, + const struct fuse_open_in *in, + struct fuse_open_out *out) +{ + out->fh =3D 2; + bpf_printk("opendir postfilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, test_readdir_prefilter, const struct bpf_fuse_met= a_info *meta, + struct fuse_read_in *in) +{ + int backing =3D BPF_FUSE_USER; + + if (in->fh =3D=3D 2) + backing =3D 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_me= ta_info *meta, + const struct fuse_read_in *in, + struct fuse_read_out *out, struct fuse_buffer *buffer) +{ + int backing =3D BPF_FUSE_CONTINUE; + + if (in->fh =3D=3D 2) + backing =3D 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_me= ta_info *meta, + struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char *name_buf; + bool backing =3D false; + int ret; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + + /* bpf_dynptr_slice will only return a pointer if the dynptr is long enou= gh */ + name_buf =3D 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") =3D=3D 0) + return BPF_FUSE_CONTINUE; + if (bpf_strncmp(name_buf, 5, "hide") =3D=3D 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_met= a_info *meta, + const struct fuse_mkdir_in *in, const struct fuse_buffer *name) +{ + bpf_printk("mkdir postfilter"); + + if (meta->error_in =3D=3D -EEXIST) + return -EPERM; + return 0; +} + +BPF_STRUCT_OPS(uint32_t, error_lookup_prefilter, const struct bpf_fuse_met= a_info *meta, + struct fuse_buffer *name) +{ + struct bpf_dynptr name_ptr; + char *name_buf; + bool backing =3D false; + int ret; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + + /* bpf_dynptr_slice will only return a pointer if the dynptr is long enou= gh */ + name_buf =3D 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_me= ta_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 =3D 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") =3D=3D 0) { + bpf_printk("lookup postfilter doesnotexist"); + return BPF_FUSE_USER_POSTFILTER; + } + } +=09 + return 0; +} + +// test readdirplus + +BPF_STRUCT_OPS(uint32_t, readdirplus_readdir_prefilter, const struct bpf_f= use_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_f= use_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 =3D 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 =3D (struct fuse_bpf_entry_out) { + .entry_type =3D FUSE_ENTRY_REMOVE_BPF, + }; + bpf_dynptr_write(&entries_ptr, 0, &entry, sizeof(entry), 0); +=09 + 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 =3D { + .open_prefilter =3D (void *)trace_open_prefilter, + .opendir_prefilter =3D (void *)trace_opendir_prefilter, + .create_open_prefilter =3D (void *)trace_create_open_prefilter, + .release_prefilter =3D (void *)trace_release_prefilter, + .releasedir_prefilter =3D (void *)trace_releasedir_prefilter, + .flush_prefilter =3D (void *)trace_flush_prefilter, + .lseek_prefilter =3D (void *)trace_lseek_prefilter, + //.copy_file_range_prefilter =3D (void *)trace_copy_file_range_prefilter, + //.fsync_prefilter =3D (void *)trace_fsync_prefilter, + //.dir_fsync_prefilter =3D (void *)trace_dir_fsync_prefilter, + .getxattr_prefilter =3D (void *)trace_getxattr_prefilter, + .listxattr_prefilter =3D (void *)trace_listxattr_prefilter, + .setxattr_prefilter =3D (void *)trace_setxattr_prefilter, + .removexattr_prefilter =3D (void *)trace_removexattr_prefilter, + .read_iter_prefilter =3D (void *)trace_read_iter_prefilter, + .write_iter_prefilter =3D (void *)trace_write_iter_prefilter, + .file_fallocate_prefilter =3D (void *)trace_file_fallocate_prefilter, + .lookup_prefilter =3D (void *)trace_lookup_prefilter, + .mknod_prefilter =3D (void *)trace_mknod_prefilter, + .mkdir_prefilter =3D (void *)trace_mkdir_prefilter, + .rmdir_prefilter =3D (void *)trace_rmdir_prefilter, + .rename2_prefilter =3D (void *)trace_rename2_prefilter, + .rename_prefilter =3D (void *)trace_rename_prefilter, + .unlink_prefilter =3D (void *)trace_unlink_prefilter, + .link_prefilter =3D (void *)trace_link_prefilter, + .getattr_prefilter =3D (void *)trace_getattr_prefilter, + .setattr_prefilter =3D (void *)trace_setattr_prefilter, + .statfs_prefilter =3D (void *)trace_statfs_prefilter, + .get_link_prefilter =3D (void *)trace_get_link_prefilter, + .symlink_prefilter =3D (void *)trace_symlink_prefilter, + .readdir_prefilter =3D (void *)trace_readdir_prefilter, + .access_prefilter =3D (void *)trace_access_prefilter, + .name =3D "trace_ops", +}; + +SEC(".struct_ops") +struct fuse_ops test_trace_ops =3D { + .open_prefilter =3D (void *)test_open_prefilter, + .open_postfilter =3D (void *)test_open_postfilter, + .opendir_prefilter =3D (void *)test_opendir_prefilter, + .opendir_postfilter =3D (void *)test_opendir_postfilter, + .create_open_prefilter =3D (void *)trace_create_open_prefilter, + .release_prefilter =3D (void *)trace_release_prefilter, + .releasedir_prefilter =3D (void *)trace_releasedir_prefilter, + .flush_prefilter =3D (void *)trace_flush_prefilter, + .lseek_prefilter =3D (void *)trace_lseek_prefilter, + //.copy_file_range_prefilter =3D (void *)trace_copy_file_range_prefilter, + //.fsync_prefilter =3D (void *)trace_fsync_prefilter, + //.dir_fsync_prefilter =3D (void *)trace_dir_fsync_prefilter, + .getxattr_prefilter =3D (void *)trace_getxattr_prefilter, + .listxattr_prefilter =3D (void *)trace_listxattr_prefilter, + .setxattr_prefilter =3D (void *)trace_setxattr_prefilter, + .removexattr_prefilter =3D (void *)trace_removexattr_prefilter, + .read_iter_prefilter =3D (void *)test_read_iter_prefilter, + .write_iter_prefilter =3D (void *)trace_write_iter_prefilter, + .file_fallocate_prefilter =3D (void *)trace_file_fallocate_prefilter, + .lookup_prefilter =3D (void *)test_lookup_prefilter, + .lookup_postfilter =3D (void *)test_lookup_postfilter, + .mknod_prefilter =3D (void *)trace_mknod_prefilter, + .mkdir_prefilter =3D (void *)trace_mkdir_prefilter, + .rmdir_prefilter =3D (void *)trace_rmdir_prefilter, + .rename2_prefilter =3D (void *)trace_rename2_prefilter, + .rename_prefilter =3D (void *)trace_rename_prefilter, + .unlink_prefilter =3D (void *)trace_unlink_prefilter, + .link_prefilter =3D (void *)trace_link_prefilter, + .getattr_prefilter =3D (void *)test_getattr_prefilter, + .setattr_prefilter =3D (void *)test_setattr_prefilter, + .statfs_prefilter =3D (void *)trace_statfs_prefilter, + .get_link_prefilter =3D (void *)trace_get_link_prefilter, + .symlink_prefilter =3D (void *)trace_symlink_prefilter, + .readdir_prefilter =3D (void *)test_readdir_prefilter, + .readdir_postfilter =3D (void *)test_readdir_postfilter, + .access_prefilter =3D (void *)trace_access_prefilter, + .name =3D "test_trace_ops", +}; + +SEC(".struct_ops") +struct fuse_ops readdir_redact_ops =3D { + .readdir_prefilter =3D (void *)readdir_redact_prefilter, + .readdir_postfilter =3D (void *)readdir_redact_postfilter, + .name =3D "readdir_redact", +}; + +SEC(".struct_ops") +struct fuse_ops test_hidden_ops =3D { + .lookup_prefilter =3D (void *)hidden_lookup_prefilter, + .access_prefilter =3D (void *)trace_access_prefilter, + .create_open_prefilter =3D (void *)trace_create_open_prefilter, + .name =3D "test_hidden", +}; + +SEC(".struct_ops") +struct fuse_ops test_error_ops =3D { + .lookup_prefilter =3D (void *)error_lookup_prefilter, + .lookup_postfilter =3D (void *)error_lookup_postfilter, + .mkdir_prefilter =3D (void *)error_mkdir_prefilter, + .mkdir_postfilter =3D (void *)error_mkdir_postfilter, + .name =3D "test_error", +}; + +SEC(".struct_ops") +struct fuse_ops readdir_plus_ops =3D { + .readdir_prefilter =3D (void *)readdirplus_readdir_prefilter, + .name =3D "readdir_plus", +}; + +SEC(".struct_ops") +struct fuse_ops passthrough_ops =3D { + .lookup_prefilter =3D (void *)error_lookup_prefilter, + .lookup_postfilter =3D (void *)passthrough_lookup_postfilter, + .name =3D "passthrough", +}; + +SEC(".struct_ops") +struct fuse_ops lookup_postfilter_ops =3D { + .lookup_prefilter =3D (void *)error_lookup_prefilter, + .lookup_postfilter =3D (void *)test_bpf_lookup_postfilter, + .name =3D "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 =3D=3D (FUSE_MKDIR | FUSE_PREFILTER)) { + const char *start; + const char *end; + const struct fuse_mkdir_in *in; + + start =3D (void *)(long) fa->in_args[0].value; + end =3D (void *)(long) fa->in_args[0].end_offset; + if (start + sizeof(*in) <=3D end) { + in =3D (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 =3D=3D (FUSE_MKDIR | FUSE_PREFILTER)) { + const char *start; + const char *end; + const struct t *c; + + start =3D (void *)(long) fa->in_args[0].value; + end =3D (void *)(long) fa->in_args[0].end_offset; + if (start + sizeof(struct t) <=3D end) { + c =3D (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 =3D=3D (FUSE_MKDIR | FUSE_PREFILTER)) { + const char *start; + const char *end; + struct fuse_mkdir_in *c; + + start =3D (void *)(long) fa->in_args[0].value; + end =3D (void *)(long) fa->in_args[1].end_offset; + if (start + sizeof(*c) <=3D end) { + c =3D (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 =3D=3D (FUSE_LOOKUP | FUSE_POSTFILTER)) { + const char *name =3D (void *)(long)fa->in_args[0].value; + const char *end =3D (void *)(long)fa->in_args[0].end_offset; + struct fuse_entry_out *feo =3D fa_verify_out(fa, 0, sizeof(*feo)); + + if (!feo) + return -1; + + if (strcmp_check("real", name, end) =3D=3D 0) + feo->nodeid =3D 5; + else if (strcmp_check("partial", name, end) =3D=3D 0) + feo->nodeid =3D 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 =3D=3D (FUSE_LOOKUP | FUSE_POSTFILTER)) { + const char *name =3D (void *)(long)fa->in_args[0].value; + const char *end =3D (void *)(long)fa->in_args[0].end_offset; + struct fuse_entry_out *feo =3D bpf_make_writable_out(fa, 0, fa->out_args= [0].value, + 1, true); + + if (!feo) + return -1; + + if (strcmp_check("real", name, end) =3D=3D 0) + feo->nodeid =3D 5; + else if (strcmp_check("partial", name, end) =3D=3D 0) + feo->nodeid =3D 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 =3D=3D (FUSE_LOOKUP | FUSE_POSTFILTER)) { + struct fuse_entry_out *feo; + struct fuse_entry_out *feo_w; + + feo =3D fa_verify_out(fa, 0, sizeof(*feo)); + if (!feo) + return -1; + + feo_w =3D bpf_make_writable_out(fa, 0, fa->out_args[0].value, sizeof(*fe= o_w), true); + bpf_printk("post-lookup %d", feo->nodeid); + if (!feo_w) + return -1; + + feo_w->nodeid =3D 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 =3D=3D (FUSE_LOOKUP | FUSE_POSTFILTER)) { + struct fuse_entry_out *feo; + struct fuse_entry_out *feo_w; + + feo =3D fa_verify_out(fa, 0, sizeof(*feo)); + if (!feo) + return -1; + + bpf_printk("post-lookup %d", feo->nodeid); + + feo_w =3D bpf_make_writable_out(fa, 0, fa->out_args[0].value, sizeof(*fe= o_w), true); + + feo =3D fa_verify_out(fa, 0, sizeof(*feo)); + if (feo) + bpf_printk("post-lookup %d", feo->nodeid); + if (!feo_w) + return -1; + + feo_w->nodeid =3D 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 =3D (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/to= ols/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 +#include + +#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 +#endif + +#define TEST_FAILURE 1 +#define TEST_SUCCESS 0 + +#define ptr_to_u64(p) ((__u64)p) + +#if __BYTE_ORDER__ =3D=3D __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[] =3D { + "=3D=3D", + "!=3D", + ">=3D", +}; + +#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 =3D a =3D=3D b; break; \ + case _ne: pass =3D a !=3D b; break; \ + case _ge: pass =3D a >=3D 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 otherwis= e */ +#define TESTSYSCALL(statement) TESTCONDERR((statement) =3D=3D 0) + +static inline void print_bytes(const void *data, size_t size) +{ + const char *bytes =3D data; + int i; + + for (i =3D 0; i < size; ++i) { + if (i % 0x10 =3D=3D 0) + printf("%08x:", i); + printf("%02x ", (unsigned int) (unsigned char) bytes[i]); + if (i % 0x10 =3D=3D 0x0f) + printf("\n"); + } + + if (i % 0x10 !=3D 0) + printf("\n"); +} + + + +#endif diff --git a/tools/testing/selftests/filesystems/fuse/test_fuse.h b/tools/t= esting/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 +#include +#include +#include + +#include + +#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 s= ize, + ssize_t *ret_size); +int s_listxattr(struct s pathname, void *list, size_t size, ssize_t *ret_s= ize); +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 =3D \ + (struct fuse_in_header *)bytes_in; \ + ssize_t res =3D 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 =3D + (struct fuse_in_header *)bytes_in; + ssize_t res =3D 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) + si= zeof(uint32_t))) + +#define TESTFUSEIN(_opcode, in_struct) \ + do { \ + struct fuse_in_header *in_header =3D \ + (struct fuse_in_header *)bytes_in; \ + ssize_t res =3D read(fuse_dev, &bytes_in, \ + sizeof(bytes_in)); \ + \ + TESTEQUAL(res, sizeof(*in_header) + sizeof(*in_struct));\ + TESTEQUAL(in_header->opcode, _opcode); \ + in_struct =3D (void *)(bytes_in + sizeof(*in_header)); \ + } while (false) + +#define TESTFUSEIN_ERR_IN(_opcode, in_struct, err_in) \ + do { \ + struct fuse_in_header *in_header =3D \ + (struct fuse_in_header *)bytes_in; \ + struct fuse_ext_header *ext_h; \ + ssize_t res =3D 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 =3D (void *)(bytes_in + sizeof(*in_header)); \ + ext_h =3D (void *)&bytes_in[in_header->len \ + - in_header->total_extlen * 8]; \ + err_in =3D (void *)&ext_h[1]; \ + } while (false) + +#define TESTFUSEIN2(_opcode, in_struct1, in_struct2) \ + do { \ + struct fuse_in_header *in_header =3D \ + (struct fuse_in_header *)bytes_in; \ + ssize_t res =3D 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 =3D (void *)(bytes_in + sizeof(*in_header)); \ + in_struct2 =3D (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 =3D \ + (struct fuse_in_header *)bytes_in; \ + struct fuse_ext_header *ext_h; \ + ssize_t res =3D 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 =3D (void *)(bytes_in + sizeof(*in_header)); \ + in_struct2 =3D (void *)(bytes_in + sizeof(*in_header) \ + + sizeof(*in_struct1)); \ + ext_h =3D (void *)&bytes_in[in_header->len \ + - in_header->total_extlen * 8]; \ + err_in =3D (void *)&ext_h[1]; \ + } while (false) + +#define TESTFUSEINEXT(_opcode, in_struct, extra) \ + do { \ + struct fuse_in_header *in_header =3D \ + (struct fuse_in_header *)bytes_in; \ + ssize_t res =3D read(fuse_dev, &bytes_in, \ + sizeof(bytes_in)); \ + \ + TESTEQUAL(in_header->opcode, _opcode); \ + TESTEQUAL(res, \ + sizeof(*in_header) + sizeof(*in_struct) + extra);\ + in_struct =3D (void *)(bytes_in + sizeof(*in_header)); \ + } while (false) + +#define TESTFUSEINUNKNOWN() \ + do { \ + struct fuse_in_header *in_header =3D \ + (struct fuse_in_header *)bytes_in; \ + ssize_t res =3D 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 =3D \ + (struct fuse_in_header *)bytes_in; \ + char *name =3D (char *) (bytes_in + sizeof(*in_header)); \ + ssize_t res; \ + \ + TEST(res =3D read(fuse_dev, &bytes_in, sizeof(bytes_in)), \ + res !=3D -1); \ + /* TODO once we handle forgets properly, remove */ \ + if (in_header->opcode =3D=3D FUSE_FORGET) \ + continue; \ + if (in_header->opcode =3D=3D 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 =3D=3D 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 =3D \ + (struct fuse_in_header *)bytes_in; \ + struct fuse_ext_header *ext_h; \ + char *name =3D (char *) (bytes_in + sizeof(*in_header)); \ + ssize_t res; \ + \ + TEST(res =3D read(fuse_dev, &bytes_in, sizeof(bytes_in)), \ + res !=3D -1); \ + /* TODO once we handle forgets properly, remove */ \ + if (in_header->opcode =3D=3D FUSE_FORGET) \ + continue; \ + if (in_header->opcode =3D=3D 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 =3D (void *)&bytes_in[in_header->len \ + - in_header->total_extlen * 8]; \ + err_in =3D (void *)&ext_h[1]; \ + break; \ + } while (true) + +#define TESTFUSEOUTEMPTY() \ + do { \ + struct fuse_in_header *in_header =3D \ + (struct fuse_in_header *)bytes_in; \ + struct fuse_out_header *out_header =3D \ + (struct fuse_out_header *)bytes_out; \ + \ + *out_header =3D (struct fuse_out_header) { \ + .len =3D sizeof(*out_header), \ + .unique =3D 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 =3D \ + (struct fuse_in_header *)bytes_in; \ + struct fuse_out_header *out_header =3D \ + (struct fuse_out_header *)bytes_out; \ + \ + *out_header =3D (struct fuse_out_header) { \ + .len =3D sizeof(*out_header), \ + .error =3D errno, \ + .unique =3D 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 =3D \ + (struct fuse_in_header *)bytes_in; \ + struct fuse_out_header *out_header =3D \ + (struct fuse_out_header *)bytes_out; \ + \ + *out_header =3D (struct fuse_out_header) { \ + .len =3D sizeof(*out_header) + length, \ + .unique =3D 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 =3D \ + (struct fuse_in_header *)bytes_in; \ + struct fuse_out_header *out_header =3D \ + (struct fuse_out_header *)bytes_out; \ + \ + *out_header =3D (struct fuse_out_header) { \ + .len =3D sizeof(*out_header) + \ + sizeof(*read_out) + length, \ + .unique =3D 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 \ + =3D (struct fuse_out_header) { \ + .len =3D sizeof(struct fuse_out_header) \ + + sizeof(struct type1), \ + .unique =3D ((struct fuse_in_header *) \ + bytes_in)->unique, \ + }; \ + *(struct type1 *) (bytes_out \ + + sizeof(struct fuse_out_header)) \ + =3D 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 \ + =3D (struct fuse_out_header) { \ + .len =3D sizeof(struct fuse_out_header) \ + + sizeof(struct type1) \ + + sizeof(struct type2), \ + .unique =3D ((struct fuse_in_header *) \ + bytes_in)->unique, \ + }; \ + *(struct type1 *) (bytes_out \ + + sizeof(struct fuse_out_header)) \ + =3D obj1; \ + *(struct type2 *) (bytes_out \ + + sizeof(struct fuse_out_header) \ + + sizeof(struct type1)) \ + =3D 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 \ + =3D (struct fuse_out_header) { \ + .len =3D sizeof(struct fuse_out_header) \ + + sizeof(struct type1) \ + + sizeof(struct type2) \ + + sizeof(struct type3), \ + .unique =3D ((struct fuse_in_header *) \ + bytes_in)->unique, \ + }; \ + *(struct type1 *) (bytes_out \ + + sizeof(struct fuse_out_header)) \ + =3D obj1; \ + *(struct type2 *) (bytes_out \ + + sizeof(struct fuse_out_header) \ + + sizeof(struct type1)) \ + =3D obj2; \ + *(struct type3 *) (bytes_out \ + + sizeof(struct fuse_out_header) \ + + sizeof(struct type1) \ + + sizeof(struct type2)) \ + =3D 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 =3D FUSE_KERNEL_VERSION, \ + .minor =3D FUSE_KERNEL_MINOR_VERSION, \ + .max_readahead =3D 4096, \ + .flags =3D fuse_connection_flags, \ + .max_background =3D 0, \ + .congestion_threshold =3D 0, \ + .max_write =3D 4096, \ + .time_gran =3D 1000, \ + .max_pages =3D 12, \ + .map_alignment =3D 4096, \ + })); \ + } while (false) + +#define TESTFUSEINIT() \ + TESTFUSEINITFLAGS(0) + +#define DECL_FUSE_IN(name) \ + struct fuse_##name##_in *name##_in =3D \ + (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 =3D fork(), pid !=3D -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 di= r_fd, + int *fuse_dev_ptr); +#endif --=20 2.40.0.634.g4ca3ef3211-goog