From nobody Wed Dec 17 19:22:07 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 7CA9CC77B75 for ; Tue, 18 Apr 2023 01:41:05 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230003AbjDRBlD (ORCPT ); Mon, 17 Apr 2023 21:41:03 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53014 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229882AbjDRBk4 (ORCPT ); Mon, 17 Apr 2023 21:40:56 -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 5A6B355BF for ; Mon, 17 Apr 2023 18:40:54 -0700 (PDT) Received: by mail-yw1-x1149.google.com with SMTP id 00721157ae682-54f87e44598so162894547b3.5 for ; Mon, 17 Apr 2023 18:40:54 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20221208; t=1681782053; x=1684374053; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=NfkPJzUlysoaTODKR2vMaar8Vfxo3RC8DMLPrNdWTm0=; b=egQ1giZWzQQU6IgqDiN0fqNaR+tCjtq8YtqkqBFfrhgCOHzoH5BL4EpM1SjL2V9YPT zw5vjo04SRS98y4aBh4IKDh/0Gc/NqpsqnvGnxUVTxf4LsjVKui23mBcXeJ9KXrvhZKA ZjR7hMSIiiecVTikf6jSWY5aIuy3zBgyY+o31s5RG669mrcwpbn4LneWBnlC4odlvOM6 uVEg8sDa7KRWgPiubS+g6d2Di3xIboxV1D2Yox6S+/NnnYnYXgpHb/utSPyJALD1t4u2 t3AZaYcWNjd484y99DBX0CWCj1HZ0+dTbwbIXfNH7ltNnMYthcViod3d8MoUwvFYMiDJ Wo3Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681782053; x=1684374053; 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=NfkPJzUlysoaTODKR2vMaar8Vfxo3RC8DMLPrNdWTm0=; b=CW5pZfDtCdSzCVeKe9VyfowwfpSr/vtet8catQZPQehkSJJHEsPnYFigiqLGXNzYTY J3n/L38noXlPbmptL56Lonp10KwLrqRB8tWKUFMhZbovwYyDv4VhGeZt0m+80SNF8zjV Ys8TeGS/gsv1JZrQkbsXxyZTqY3Q8mEMD+4uT0lLAeD9TctxRpilZHYTzFcznLN8HNcS ymTIZrCc17K9jIuAsTr4K1VeAIwjb2wtb1D27Pomu2RIe0nXo14dsDnLa8J/TWDPJYO3 0wLK8H/oP+1Ic/ilAOcWi+biK/2wo0QZaw1rXfVUjwhZG4TRUIaNM8Y11hbHMffp9MhS 0Gwg== X-Gm-Message-State: AAQBX9d0OF8Hwky7Nm21qJ6Citkuw+NX+v/Dz7yfRjBisSNXiOOmxh1o GHWbyAvvzxK5ND0S1EOYpH3FsMQlyt0= X-Google-Smtp-Source: AKy350YmlxhwmblENoNjk9kVV5cLsVBPE97MdbSgohua40Z5njEAKzoWuQFLyOxBvwVm7Hfg/W6OWPDkKZ4= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:e67a:98b0:942d:86aa]) (user=drosen job=sendgmr) by 2002:a81:af53:0:b0:549:14b0:84af with SMTP id x19-20020a81af53000000b0054914b084afmr10738506ywj.1.1681782053642; Mon, 17 Apr 2023 18:40:53 -0700 (PDT) Date: Mon, 17 Apr 2023 18:40:01 -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-2-drosen@google.com> Subject: [RFC PATCH v3 01/37] bpf: verifier: Accept dynptr mem as mem in herlpers 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 Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This allows using memory retrieved from dynptrs with helper functions that accept ARG_PTR_TO_MEM. For instance, results from bpf_dynptr_data can be passed along to bpf_strncmp. Signed-off-by: Daniel Rosenberg --- kernel/bpf/verifier.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 1e05355facdc..ebc638bfed87 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -7128,12 +7128,16 @@ static int check_reg_type(struct bpf_verifier_env *= env, u32 regno, * ARG_PTR_TO_MEM + MAYBE_NULL is compatible with PTR_TO_MEM and PTR_TO_M= EM + MAYBE_NULL, * but ARG_PTR_TO_MEM is compatible only with PTR_TO_MEM but NOT with PTR= _TO_MEM + MAYBE_NULL * + * ARG_PTR_TO_MEM is compatible with PTR_TO_MEM that is tagged with a dyn= ptr type. + * * Therefore we fold these flags depending on the arg_type before compari= son. */ if (arg_type & MEM_RDONLY) type &=3D ~MEM_RDONLY; if (arg_type & PTR_MAYBE_NULL) type &=3D ~PTR_MAYBE_NULL; + if (base_type(arg_type) =3D=3D ARG_PTR_TO_MEM) + type &=3D ~DYNPTR_TYPE_FLAG_MASK; =20 if (meta->func_id =3D=3D BPF_FUNC_kptr_xchg && type & MEM_ALLOC) type &=3D ~MEM_ALLOC; --=20 2.40.0.634.g4ca3ef3211-goog From nobody Wed Dec 17 19:22:07 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 48EEDC77B75 for ; Tue, 18 Apr 2023 01:41:08 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230070AbjDRBlG (ORCPT ); Mon, 17 Apr 2023 21:41:06 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53074 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229838AbjDRBk6 (ORCPT ); Mon, 17 Apr 2023 21:40:58 -0400 Received: from mail-yb1-xb4a.google.com (mail-yb1-xb4a.google.com [IPv6:2607:f8b0:4864:20::b4a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id DD49A5279 for ; Mon, 17 Apr 2023 18:40:56 -0700 (PDT) Received: by mail-yb1-xb4a.google.com with SMTP id 84-20020a251457000000b00b8f59a09e1fso11379682ybu.5 for ; Mon, 17 Apr 2023 18:40:56 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20221208; t=1681782055; x=1684374055; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=WjDDJW+GHYKcFKqxIZ+BL5gG6uAgwAshe83Msya4lZk=; b=NlOg4YH4aS6Jqn57OyoDroboiAPLs6DTDbrvGBZbNhHR10+m/WdBxEvhRm0xAGWYMp f9bQ4TPfKNqD7xy2km5YYg0yfmm/J4l/kQ9agtg79DmAEtF6sxG6YZL0P09rDUitmuJT kXa0oepXRXmncPOGL54W0tDR+viFUV7oZolHNgNGYjHS3czEp7wnPo/SRvxpM/LZwElo qtthAQBa5tFhv3tSpPuuHV9MiyJGbjl+TBimhWKh7FOD20dvSUG3x1gbWqH++RfHPFb1 qQwmzd7SdI+qv/79iFoti9uokwoWrsiLI5aQMJ9aVjw//OVLtTGARlAgnsUtTxomxwzx BL5A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681782055; x=1684374055; 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=WjDDJW+GHYKcFKqxIZ+BL5gG6uAgwAshe83Msya4lZk=; b=R0blVFL9eTfHO/Qh4xsm+4rggNzi0UKNd4hCUKBYrf+krR8Pib7cQOKgF+jtF5i3lB 1xsBJxm7qMKltReMExYxNLYUeRxDW+dkP5yiecwN2gCHvfKJaaqPsBWmrQa/Mg83a2dh W8Tsyc7VfDMo8oWkgTQlYqtgEsGrhzqJ01/OKi27bMX94B7d8yDcOLMEhZhnLFK6JV75 BwvEiG6/7k0A3HJeRvSB9hN0wZt94rq2Uu3KkEj1AgrqEt7y6gxKCcnaEI0h+b+HVP0R BtFZrzAbeijvOBECmmRPZi6TTBPSqSQ5TEJ7BLlioY4lIOX3qWSQFl5pZy0OrGUQmk7d skEw== X-Gm-Message-State: AAQBX9cGW7FhnF13PLF1qVMwMGRNjNCj4ccc1vwv+BY2DJ16uG9SbYyq sBh68cMUtaCuFLPRlmTV0BymZJw/kNI= X-Google-Smtp-Source: AKy350b6WbLfFOIpMTkeOSl2mlRWjzNHXOblepg4SNDqaWtzBw/uJNst+ZDP03Eg7cvOh/SLsMoc8N9aowY= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:e67a:98b0:942d:86aa]) (user=drosen job=sendgmr) by 2002:a05:690c:706:b0:545:5f92:f7ee with SMTP id bs6-20020a05690c070600b005455f92f7eemr11333022ywb.2.1681782055788; Mon, 17 Apr 2023 18:40:55 -0700 (PDT) Date: Mon, 17 Apr 2023 18:40:02 -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-3-drosen@google.com> Subject: [RFC PATCH v3 02/37] bpf: Allow NULL buffers in bpf_dynptr_slice(_rw) 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 Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" bpf_dynptr_slice(_rw) uses a user provided buffer if it can not provide a pointer to a block of contiguous memory. This buffer is unused in the case of local dynptrs, and may be unused in other cases as well. There is no need to require the buffer, as the kfunc can just return NULL if it was needed and not provided. This adds another kfunc annotation, __opt, which combines with __sz and __szk to allow the buffer associated with the size to be NULL. If the buffer is NULL, the verifier does not check that the buffer is of sufficient size. Signed-off-by: Daniel Rosenberg --- Documentation/bpf/kfuncs.rst | 23 ++++++++++++++++++++++- kernel/bpf/helpers.c | 32 ++++++++++++++++++++------------ kernel/bpf/verifier.c | 19 +++++++++++++++++++ 3 files changed, 61 insertions(+), 13 deletions(-) diff --git a/Documentation/bpf/kfuncs.rst b/Documentation/bpf/kfuncs.rst index ea2516374d92..7a3d9de5f315 100644 --- a/Documentation/bpf/kfuncs.rst +++ b/Documentation/bpf/kfuncs.rst @@ -100,7 +100,7 @@ Hence, whenever a constant scalar argument is accepted = by a kfunc which is not a size parameter, and the value of the constant matters for program safety, = __k suffix should be used. =20 -2.2.2 __uninit Annotation +2.2.3 __uninit Annotation ------------------------- =20 This annotation is used to indicate that the argument will be treated as @@ -117,6 +117,27 @@ Here, the dynptr will be treated as an uninitialized d= ynptr. Without this annotation, the verifier will reject the program if the dynptr passed in is not initialized. =20 +2.2.4 __opt Annotation +------------------------- + +This annotation is used to indicate that the buffer associated with an __s= z or __szk +argument may be null. If the function is passed a nullptr in place of the = buffer, +the verifier will not check that length is appropriate for the buffer. The= kfunc is +responsible for checking if this buffer is null before using it. + +An example is given below:: + + __bpf_kfunc void *bpf_dynptr_slice(..., void *buffer__opt, u32 buf= fer__szk) + { + ... + } + +Here, the buffer may be null. If buffer is not null, it at least of size b= uffer_szk. +Either way, the returned buffer is either NULL, or of size buffer_szk. Wit= hout this +annotation, the verifier will reject the program if a null pointer is pass= ed in with +a nonzero size. + + .. _BPF_kfunc_nodef: =20 2.3 Using an existing kernel function diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c index 00e5fb0682ac..bfb75ecacb76 100644 --- a/kernel/bpf/helpers.c +++ b/kernel/bpf/helpers.c @@ -2167,13 +2167,15 @@ __bpf_kfunc struct task_struct *bpf_task_from_pid(s= 32 pid) * bpf_dynptr_slice() - Obtain a read-only pointer to the dynptr data. * @ptr: The dynptr whose data slice to retrieve * @offset: Offset into the dynptr - * @buffer: User-provided buffer to copy contents into - * @buffer__szk: Size (in bytes) of the buffer. This is the length of the - * requested slice. This must be a constant. + * @buffer__opt: User-provided buffer to copy contents into. May be NULL + * @buffer__szk: Size (in bytes) of the buffer if present. This is the + * length of the requested slice. This must be a constant. * * For non-skb and non-xdp type dynptrs, there is no difference between * bpf_dynptr_slice and bpf_dynptr_data. * + * If buffer__opt is NULL, the call will fail if buffer_opt was needed. + * * If the intention is to write to the data slice, please use * bpf_dynptr_slice_rdwr. * @@ -2190,7 +2192,7 @@ __bpf_kfunc struct task_struct *bpf_task_from_pid(s32= pid) * direct pointer) */ __bpf_kfunc void *bpf_dynptr_slice(const struct bpf_dynptr_kern *ptr, u32 = offset, - void *buffer, u32 buffer__szk) + void *buffer__opt, u32 buffer__szk) { enum bpf_dynptr_type type; u32 len =3D buffer__szk; @@ -2210,15 +2212,19 @@ __bpf_kfunc void *bpf_dynptr_slice(const struct bpf= _dynptr_kern *ptr, u32 offset case BPF_DYNPTR_TYPE_RINGBUF: return ptr->data + ptr->offset + offset; case BPF_DYNPTR_TYPE_SKB: - return skb_header_pointer(ptr->data, ptr->offset + offset, len, buffer); + if (!buffer__opt) + return NULL; + return skb_header_pointer(ptr->data, ptr->offset + offset, len, buffer__= opt); case BPF_DYNPTR_TYPE_XDP: { void *xdp_ptr =3D bpf_xdp_pointer(ptr->data, ptr->offset + offset, len); if (xdp_ptr) return xdp_ptr; =20 - bpf_xdp_copy_buf(ptr->data, ptr->offset + offset, buffer, len, false); - return buffer; + if (!buffer__opt) + return NULL; + bpf_xdp_copy_buf(ptr->data, ptr->offset + offset, buffer__opt, len, fals= e); + return buffer__opt; } default: WARN_ONCE(true, "unknown dynptr type %d\n", type); @@ -2230,13 +2236,15 @@ __bpf_kfunc void *bpf_dynptr_slice(const struct bpf= _dynptr_kern *ptr, u32 offset * bpf_dynptr_slice_rdwr() - Obtain a writable pointer to the dynptr data. * @ptr: The dynptr whose data slice to retrieve * @offset: Offset into the dynptr - * @buffer: User-provided buffer to copy contents into - * @buffer__szk: Size (in bytes) of the buffer. This is the length of the - * requested slice. This must be a constant. + * @buffer__opt: User-provided buffer to copy contents into. May be NULL + * @buffer__szk: Size (in bytes) of the buffer if present. This is the + * length of the requested slice. This must be a constant. * * For non-skb and non-xdp type dynptrs, there is no difference between * bpf_dynptr_slice and bpf_dynptr_data. * + * If buffer__opt is NULL, the call will fail if buffer_opt was needed. + * * The returned pointer is writable and may point to either directly the d= ynptr * data at the requested offset or to the buffer if unable to obtain a dir= ect * data pointer to (example: the requested slice is to the paged area of a= n skb @@ -2267,7 +2275,7 @@ __bpf_kfunc void *bpf_dynptr_slice(const struct bpf_d= ynptr_kern *ptr, u32 offset * direct pointer) */ __bpf_kfunc void *bpf_dynptr_slice_rdwr(const struct bpf_dynptr_kern *ptr,= u32 offset, - void *buffer, u32 buffer__szk) + void *buffer__opt, u32 buffer__szk) { if (!ptr->data || bpf_dynptr_is_rdonly(ptr)) return NULL; @@ -2294,7 +2302,7 @@ __bpf_kfunc void *bpf_dynptr_slice_rdwr(const struct = bpf_dynptr_kern *ptr, u32 o * will be copied out into the buffer and the user will need to call * bpf_dynptr_write() to commit changes. */ - return bpf_dynptr_slice(ptr, offset, buffer, buffer__szk); + return bpf_dynptr_slice(ptr, offset, buffer__opt, buffer__szk); } =20 __bpf_kfunc void *bpf_cast_to_kern_ctx(void *obj) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index ebc638bfed87..fd959824469d 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -9387,6 +9387,19 @@ static bool is_kfunc_arg_const_mem_size(const struct= btf *btf, return __kfunc_param_match_suffix(btf, arg, "__szk"); } =20 +static bool is_kfunc_arg_optional(const struct btf *btf, + const struct btf_param *arg, + const struct bpf_reg_state *reg) +{ + const struct btf_type *t; + + t =3D btf_type_skip_modifiers(btf, arg->type, NULL); + if (!btf_type_is_ptr(t) || reg->type !=3D SCALAR_VALUE || reg->umax_value= > 0) + return false; + + return __kfunc_param_match_suffix(btf, arg, "__opt"); +} + static bool is_kfunc_arg_constant(const struct btf *btf, const struct btf_= param *arg) { return __kfunc_param_match_suffix(btf, arg, "__k"); @@ -10453,10 +10466,16 @@ static int check_kfunc_args(struct bpf_verifier_e= nv *env, struct bpf_kfunc_call_ break; case KF_ARG_PTR_TO_MEM_SIZE: { + struct bpf_reg_state *buff_reg =3D ®s[regno]; + const struct btf_param *buff_arg =3D &args[i]; struct bpf_reg_state *size_reg =3D ®s[regno + 1]; const struct btf_param *size_arg =3D &args[i + 1]; =20 ret =3D check_kfunc_mem_size_reg(env, size_reg, regno + 1); + if (ret < 0 && is_kfunc_arg_optional(meta->btf, buff_arg, buff_reg)) { + verbose(env, "error was %d", ret); + ret =3D 0; + } if (ret < 0) { verbose(env, "arg#%d arg#%d memory, len pair leads to invalid memory a= ccess\n", i, i + 1); return ret; --=20 2.40.0.634.g4ca3ef3211-goog From nobody Wed Dec 17 19:22:07 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 E7439C7EE22 for ; Tue, 18 Apr 2023 01:41:21 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230164AbjDRBlT (ORCPT ); Mon, 17 Apr 2023 21:41:19 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53738 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230105AbjDRBlO (ORCPT ); Mon, 17 Apr 2023 21:41:14 -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 880525FD6 for ; Mon, 17 Apr 2023 18:40:58 -0700 (PDT) Received: by mail-yw1-x1149.google.com with SMTP id 00721157ae682-54fba72c1adso123374547b3.18 for ; Mon, 17 Apr 2023 18:40:58 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20221208; t=1681782058; x=1684374058; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=XBLJQNnM/sLgs4vCqBW41kO32Rg1yqnQ90D80Go8URk=; b=SnlbZJLk5Z7OPvXPJ7Etlra3omvGygVLH4yu2u9bDq4nCNczqjAQAtQj22wZ5lGEym a2kgjgQG7BqI5HdR0cpPUsEdGAS9YmKkxAht+4KyhF01JzBn+Q5S7f6M6omvM0kkv6X/ evFfdOnXQGFj9+0aeZ9WMkNy0KSNFXRwAthNf6y5bZ8rWO/w27CqQ5dQ154gW44ArDBX LVcYLjt6nZT7hh2nhtXt7Y7pRN6WJB/9+wNxQ55m08D8GmFVYpj4xvYPc2V9ZA197fka hn+T6Z4cpwohIn0+D00N0XUIWGvmcKaMk9jEbjb1w7J1OC1OpxepDIj+esRwnqQA0EyG F+LA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681782058; x=1684374058; 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=XBLJQNnM/sLgs4vCqBW41kO32Rg1yqnQ90D80Go8URk=; b=dJJqq4X4wvy4rB4HIG2SRtmdpe7d5mhz+102Rv5ah/eK1fhRO6glQQwB2U8mp/u9xZ qjPgtwLKKsQGd77dXE7tjdRGcCljw05bddL8Cy5oEzhTdmDLfYV1wBqpfHECkFxTf82s zWhtCpoMjuZej5j/lVfd6BzFI/xtYukuuxQVwd9O0wxxgTOm9C12GDvK6wat+56Ee7J9 0Rh4de3FlVeUVxth4UgFXyFPIiAoLKb+Id8g7MmTwL7SnSO3XNUxwf83raFtIYzBkwDI HE853vJySjRvISHLKMHhphq+mfImzJliLueNjldW6LJ5KXqJPn6RrByjdzx7hAYMuBN1 FleQ== X-Gm-Message-State: AAQBX9eDaqiSZa3JUlKdffZi5xlwiZ+yzpWB3yl8ucTOw5DoL/wXKyYw mvwcz7HBWT7QqosKqhQGoIW4LCAFmsk= X-Google-Smtp-Source: AKy350aqzaT5PLfkv6k+7JDnNf8qfXfskwThdpbUGB15DarKosOyvH1tWqog8UU17iAVxBXL9PBghU8UzVE= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:e67a:98b0:942d:86aa]) (user=drosen job=sendgmr) by 2002:a25:d7d3:0:b0:b6d:80ab:8bb6 with SMTP id o202-20020a25d7d3000000b00b6d80ab8bb6mr11010336ybg.1.1681782057858; Mon, 17 Apr 2023 18:40:57 -0700 (PDT) Date: Mon, 17 Apr 2023 18:40:03 -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-4-drosen@google.com> Subject: [RFC PATCH v3 03/37] selftests/bpf: Test allowing NULL buffer in dynptr slice 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 Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" bpf_dynptr_slice(_rw) no longer requires a buffer for verification. If the buffer is needed, but not present, the function will return NULL. Signed-off-by: Daniel Rosenberg --- .../testing/selftests/bpf/prog_tests/dynptr.c | 1 + .../selftests/bpf/progs/dynptr_success.c | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/tools/testing/selftests/bpf/prog_tests/dynptr.c b/tools/testin= g/selftests/bpf/prog_tests/dynptr.c index d176c34a7d2e..db22cad32657 100644 --- a/tools/testing/selftests/bpf/prog_tests/dynptr.c +++ b/tools/testing/selftests/bpf/prog_tests/dynptr.c @@ -20,6 +20,7 @@ static struct { {"test_ringbuf", SETUP_SYSCALL_SLEEP}, {"test_skb_readonly", SETUP_SKB_PROG}, {"test_dynptr_skb_data", SETUP_SKB_PROG}, + {"test_dynptr_skb_nobuff", SETUP_SKB_PROG}, }; =20 static void verify_success(const char *prog_name, enum test_setup_type set= up_type) diff --git a/tools/testing/selftests/bpf/progs/dynptr_success.c b/tools/tes= ting/selftests/bpf/progs/dynptr_success.c index b2fa6c47ecc0..a059ed8d4590 100644 --- a/tools/testing/selftests/bpf/progs/dynptr_success.c +++ b/tools/testing/selftests/bpf/progs/dynptr_success.c @@ -207,3 +207,24 @@ int test_dynptr_skb_data(struct __sk_buff *skb) =20 return 1; } + +SEC("?cgroup_skb/egress") +int test_dynptr_skb_no_buff(struct __sk_buff *skb) +{ + struct bpf_dynptr ptr; + __u64 *data; + + if (bpf_dynptr_from_skb(skb, 0, &ptr)) { + err =3D 1; + return 1; + } + + /* This should return NULL. SKB may require a buffer */ + data =3D bpf_dynptr_slice(&ptr, 0, NULL, 1); + if (data) { + err =3D 2; + return 1; + } + + return 1; +} --=20 2.40.0.634.g4ca3ef3211-goog From nobody Wed Dec 17 19:22:07 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 2B9B9C77B72 for ; Tue, 18 Apr 2023 01:41:33 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230239AbjDRBlb (ORCPT ); Mon, 17 Apr 2023 21:41:31 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53244 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230143AbjDRBlO (ORCPT ); Mon, 17 Apr 2023 21:41:14 -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 43B555B84 for ; Mon, 17 Apr 2023 18:41:00 -0700 (PDT) Received: by mail-yw1-x1149.google.com with SMTP id 00721157ae682-54f97aac3d0so147121987b3.15 for ; Mon, 17 Apr 2023 18:41:00 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20221208; t=1681782060; x=1684374060; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=tI4xcmLYwpmFva3i/dRZyfQFdXSO1OOUgkc/BOul5Ro=; b=2V9e6mUN9Le2shS/G4YvhPJcIGT2cNDZPr1QkNJOacyGU//myujH64KDbejS/tMlXz 4E6nR0HLcdcTv09CWWYmtKIX5wVZpN0ReS4cFCAJVsgaJjGnWqRP+K97KcNMss7R5lc/ 8wSlpIQsxhUI3w04o4cPXpyg3kA0F6KHlojC/d7SlYfU/cCvQzPLbwOg1psSwLmSzRXn Qn3vxYWDKyf/Fn+ASbJ7PU1gdKO1me/kfleFJ2griqgfwNcqIJ9AF057vwZ+pRcgxCsX sF1c9SZLi/nimZi9xbCd1jVXTRuM8JHWnHdMqXKLly0BeGcWX5EH1LSs7jCfFcSZkogo szkw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681782060; x=1684374060; 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=tI4xcmLYwpmFva3i/dRZyfQFdXSO1OOUgkc/BOul5Ro=; b=akRD7qR63m/SRMFyF2VdmTfCNH9yxtOgnH/0ukOE9Pz8s2QBThKiPOB3SHNXP8z8xZ lG/+cDuyKgfkYv/xGdrydvlidgwutLOyozwL0dcuTzOvATzFBmx9wQbaVn8Fc9J5YhH9 x0cyQQhBvXzp/8humwlaVOiUxts5oK5mL1hBCLJ7/fHGreQ3a1pWtxf3UvKjYlIemL/Y jsOavmCCtJkjSkKiQCdW0mgTBEUSUbRTARRiCZPLoy9ei7Cr3sn/rh5/pepq00dBggMv +R8drgkRSAHxkIl/j8Sq/EEQYwlfaLsSh1rZPaTP8YT0L8FHLkv+nhLXcFaLujomT3Uw oQzA== X-Gm-Message-State: AAQBX9dP/tFGyFKNCMd9docWbp3Rw5V3wH08eDEeUQxqMKpfYY2YcAj9 d8eo2gCNtNdcSEKIrBEu/bHkacMsQXg= X-Google-Smtp-Source: AKy350YKpzErYw3bq9/BlI+znfoyrC46XNLf6K9cGOqwUJLaLH9t7tNWGgZpyi5xKkrMqdSo1O9knK3ZgUU= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:e67a:98b0:942d:86aa]) (user=drosen job=sendgmr) by 2002:a25:d70a:0:b0:b77:ae4b:1b27 with SMTP id o10-20020a25d70a000000b00b77ae4b1b27mr8727839ybg.8.1681782059999; Mon, 17 Apr 2023 18:40:59 -0700 (PDT) Date: Mon, 17 Apr 2023 18:40:04 -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-5-drosen@google.com> Subject: [RFC PATCH v3 04/37] fs: Generic function to convert iocb to rw flags 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, Alessio Balsini , 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" From: Alessio Balsini OverlayFS implements its own function to translate iocb flags into rw flags, so that they can be passed into another vfs call. With commit ce71bfea207b4 ("fs: align IOCB_* flags with RWF_* flags") Jens created a 1:1 matching between the iocb flags and rw flags, simplifying the conversion. Reduce the OverlayFS code by making the flag conversion function generic and reusable. Signed-off-by: Alessio Balsini --- fs/overlayfs/file.c | 23 +++++------------------ include/linux/fs.h | 5 +++++ 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/fs/overlayfs/file.c b/fs/overlayfs/file.c index 7c04f033aadd..759893e4da04 100644 --- a/fs/overlayfs/file.c +++ b/fs/overlayfs/file.c @@ -15,6 +15,8 @@ #include #include "overlayfs.h" =20 +#define OVL_IOCB_MASK (IOCB_DSYNC | IOCB_HIPRI | IOCB_NOWAIT | IOCB_SYNC) + struct ovl_aio_req { struct kiocb iocb; refcount_t ref; @@ -241,22 +243,6 @@ static void ovl_file_accessed(struct file *file) touch_atime(&file->f_path); } =20 -static rwf_t ovl_iocb_to_rwf(int ifl) -{ - rwf_t flags =3D 0; - - if (ifl & IOCB_NOWAIT) - flags |=3D RWF_NOWAIT; - if (ifl & IOCB_HIPRI) - flags |=3D RWF_HIPRI; - if (ifl & IOCB_DSYNC) - flags |=3D RWF_DSYNC; - if (ifl & IOCB_SYNC) - flags |=3D RWF_SYNC; - - return flags; -} - static inline void ovl_aio_put(struct ovl_aio_req *aio_req) { if (refcount_dec_and_test(&aio_req->ref)) { @@ -316,7 +302,8 @@ static ssize_t ovl_read_iter(struct kiocb *iocb, struct= iov_iter *iter) old_cred =3D ovl_override_creds(file_inode(file)->i_sb); if (is_sync_kiocb(iocb)) { ret =3D vfs_iter_read(real.file, iter, &iocb->ki_pos, - ovl_iocb_to_rwf(iocb->ki_flags)); + iocb_to_rw_flags(iocb->ki_flags, + OVL_IOCB_MASK)); } else { struct ovl_aio_req *aio_req; =20 @@ -380,7 +367,7 @@ static ssize_t ovl_write_iter(struct kiocb *iocb, struc= t iov_iter *iter) if (is_sync_kiocb(iocb)) { file_start_write(real.file); ret =3D vfs_iter_write(real.file, iter, &iocb->ki_pos, - ovl_iocb_to_rwf(ifl)); + iocb_to_rw_flags(ifl, OVL_IOCB_MASK)); file_end_write(real.file); /* Update size */ ovl_copyattr(inode); diff --git a/include/linux/fs.h b/include/linux/fs.h index c85916e9f7db..c849074f44b7 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -3009,6 +3009,11 @@ static inline int kiocb_set_rw_flags(struct kiocb *k= i, rwf_t flags) return 0; } =20 +static inline rwf_t iocb_to_rw_flags(int ifl, int iocb_mask) +{ + return ifl & iocb_mask; +} + static inline ino_t parent_ino(struct dentry *dentry) { ino_t res; --=20 2.40.0.634.g4ca3ef3211-goog From nobody Wed Dec 17 19:22:07 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 0485EC77B75 for ; Tue, 18 Apr 2023 01:41:27 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230211AbjDRBlZ (ORCPT ); Mon, 17 Apr 2023 21:41:25 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53246 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230218AbjDRBlQ (ORCPT ); Mon, 17 Apr 2023 21:41:16 -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 A8F086A63 for ; Mon, 17 Apr 2023 18:41:03 -0700 (PDT) Received: by mail-yw1-x1149.google.com with SMTP id 00721157ae682-54f69fb5cafso190541017b3.12 for ; Mon, 17 Apr 2023 18:41:03 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20221208; t=1681782062; x=1684374062; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=lsQkf1cfcx1tPqZWAOmJuqvsGQVDqkZnL5eNvi6K+GA=; b=UQ9uxhJbkgCQxRon02p9V9Zp2euXuXeVILgWOJj+rbvZ3QD7E30qpSzuqQRtpHzIcs 8tnRPiCMZyAQmeuciCikVJLw4VTasapz/7TmXSqFJVxajgMpiDPL3jaC69UU0eF3Pmld suwPAwdtbxwHDIwMmZV1+ebFmsf+0c1mQqM0wqAbBAcWmTR/pu/B+pWRBe6CMDjEcF28 52pROSussOyzq+qdDWeLqKG/NteVXVxS9qOWASMLH+0EyOqr9kwiYoF84wPX73T5L1ct 9vIqWaFSNn9DOrA0aD+R+WbFQQl5+R6HM2rzlIC/vsjZJqWw523DmBw3bRNkdvwjG4sw pLVQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681782062; x=1684374062; 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=lsQkf1cfcx1tPqZWAOmJuqvsGQVDqkZnL5eNvi6K+GA=; b=MYDpaAb1FGSm1tmEBadiDlrnmLgLqycgxMXAHINK1dYZDzKhY7+Hd5ktxp2Oy1N4GK ncPzwMxjBQAI8ybD8AlbuA8+U/CGpdKk45TWcoglbA5SsGdW7gN4EcK8YiIpFtSNoAEy 1O4tNe1ArLyqPnaQK1pDZS10PP7DD54KuAUVEh8Xcb1359bOdSTkDHAOutPeAWyI0Itz h8nf7iRxByUTwW//Swqeg2WeiD86S6DP7oaBTA1GQw35qKEE4giU8szAFh4nruV/b/+2 +nQ51S9dkmrbKMFHfd1qWdr/OfxjrqnANM4rLpll4tRkTYdHJYwg/7H1EitUj/xjharn rdtw== X-Gm-Message-State: AAQBX9cmV3vz/sP9EpYGYo9YtSIwYDP/HkqP0ULIXlXgu6pbhPtk/5pa R0LslPewxeq6y7Aovph1dgnLi24OGL8= X-Google-Smtp-Source: AKy350ZwWg/HHOg0CI32Fv3NaozHYx5kISDmv+5KnVkQwLfS05n+gGaQY14q9hCSUWer05c4Oi37kCTsPHw= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:e67a:98b0:942d:86aa]) (user=drosen job=sendgmr) by 2002:a81:c406:0:b0:54d:913b:c9e8 with SMTP id j6-20020a81c406000000b0054d913bc9e8mr10100712ywi.5.1681782062021; Mon, 17 Apr 2023 18:41:02 -0700 (PDT) Date: Mon, 17 Apr 2023 18:40:05 -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-6-drosen@google.com> Subject: [RFC PATCH v3 05/37] fuse-bpf: Update fuse side uapi 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 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 structures which will be used to inform fuse about what it is being stacked on top of. Once filters are in place, error_in will inform the post filter if the backing call returned an error. Signed-off-by: Daniel Rosenberg Signed-off-by: Paul Lawrence --- include/uapi/linux/fuse.h | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h index 1b9d0dfae72d..04d96f34e9a1 100644 --- a/include/uapi/linux/fuse.h +++ b/include/uapi/linux/fuse.h @@ -607,6 +607,29 @@ struct fuse_entry_out { struct fuse_attr attr; }; =20 +#define FUSE_BPF_MAX_ENTRIES 2 + +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]; + }; +}; + struct fuse_forget_in { uint64_t nlookup; }; --=20 2.40.0.634.g4ca3ef3211-goog From nobody Wed Dec 17 19:22:07 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 45E18C77B72 for ; Tue, 18 Apr 2023 01:41:46 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230429AbjDRBlp (ORCPT ); Mon, 17 Apr 2023 21:41:45 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53816 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230253AbjDRBlR (ORCPT ); Mon, 17 Apr 2023 21:41:17 -0400 Received: from mail-yb1-xb49.google.com (mail-yb1-xb49.google.com [IPv6:2607:f8b0:4864:20::b49]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 779E65279 for ; Mon, 17 Apr 2023 18:41:04 -0700 (PDT) Received: by mail-yb1-xb49.google.com with SMTP id 63-20020a250042000000b00b924691cef2so3638174yba.6 for ; Mon, 17 Apr 2023 18:41:04 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20221208; t=1681782064; x=1684374064; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=rGQ9sB/Rwqr1E4RbtpywhCeCd351RoNViaB6XX6OAcM=; b=7a7FthehtIU1LDZHaPCjM+OU4rQTnwIJO65M0avbzwBgquVMpBou6qbE9n76s23acW 0ZLBEZD6ViB5CGK7UFFeuO58ubsaYIEMTp4GXx/KH8KrYV2koGO24wTVipTnAT5jPPv2 vDoFrFqHAHBzZbYHVKyk17HtLm24Tprum8PLjWK078LB0eS6ATINZq6HTShyS6UJyTbI Pz0+UzgNTfLj4UnUR7Pl9WJGy4IzEKBoqH51/+5DCpzyuozlPN46l34XGNt300XMi2vv 0zHLgyVCJ6YKv4vRZGoF3hq5Tl/PhsQ2JO+iP5xq437JP1RbmlU4dS+oNmu6RXuBXKdD Wvyw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681782064; x=1684374064; 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=rGQ9sB/Rwqr1E4RbtpywhCeCd351RoNViaB6XX6OAcM=; b=dNbLy+jxe7VppqSRL2VHzWRFKeWaWnxGaVpYkrHeYF/zhdE4GZ8+wsbtk9zv6e6UAQ AGEx77tGHgx12sb+N0lgWstpy1DjRAQ/Xx+nZ2BSA31LqG9/0ATfieIzcBtv3VOmSc2Q c0m0v/mpZ+pSt8vvAOyI/tCHsc5wCZyL+dcUSu0Ozvl/BEaSpSYgoyOmBym1Sxh68MAu quVkBav1uYzrOR1t6+DUbu/d1nLMrxxPqnGz8pcNk13pePXCUmqPsk2oykVMAbgMfscw XodknTl0Qz5ePCderu9675CaSpR/lrJeBvO9tu3Ivq9/ax+eLNsJBRxDDFANNXC31qYa hJWw== X-Gm-Message-State: AAQBX9essutExp53NUpyXXibcdvfJd2Z+22ADAAOqytJbw9fJIfk8Vfm gL574/HtdqugeL0nlyhg91MxU+BzPZw= X-Google-Smtp-Source: AKy350bEg5Yq5yN301eh/701p5EkdiweIgQB8V883Xlr2DXOiBdY6xkRbjC+SVvzsfu/0C5Gu4BhRNTqFN8= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:e67a:98b0:942d:86aa]) (user=drosen job=sendgmr) by 2002:a25:d10e:0:b0:b8f:3881:1638 with SMTP id i14-20020a25d10e000000b00b8f38811638mr8759594ybg.7.1681782064219; Mon, 17 Apr 2023 18:41:04 -0700 (PDT) Date: Mon, 17 Apr 2023 18:40:06 -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-7-drosen@google.com> Subject: [RFC PATCH v3 06/37] fuse-bpf: Add data structures for fuse-bpf 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 Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" These structures will be used to interact between the fuse bpf calls and normal userspace calls Signed-off-by: Daniel Rosenberg --- include/linux/bpf_fuse.h | 84 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 include/linux/bpf_fuse.h diff --git a/include/linux/bpf_fuse.h b/include/linux/bpf_fuse.h new file mode 100644 index 000000000000..ce8b1b347496 --- /dev/null +++ b/include/linux/bpf_fuse.h @@ -0,0 +1,84 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2022 Google LLC. + */ + +#ifndef _BPF_FUSE_H +#define _BPF_FUSE_H + +#include +#include + +struct fuse_buffer { + void *data; + unsigned size; + unsigned alloc_size; + unsigned max_size; + int flags; +}; + +/* These flags are used internally to track information about the fuse buf= fers. + * Fuse sets some of the flags in init. The helper functions sets others, = depending on what + * was requested by the bpf program. + */ +// Flags set by FUSE +#define BPF_FUSE_IMMUTABLE (1 << 0) // Buffer may not be written to +#define BPF_FUSE_VARIABLE_SIZE (1 << 1) // Buffer length may be changed (g= rowth requires alloc) +#define BPF_FUSE_MUST_ALLOCATE (1 << 2) // Buffer must be re allocated bef= ore allowing writes + +// Flags set by helper function +#define BPF_FUSE_MODIFIED (1 << 3) // The helper function allowed writes t= o the buffer +#define BPF_FUSE_ALLOCATED (1 << 4) // The helper function allocated the b= uffer + +/* + * BPF Fuse Args + * + * Used to translate between bpf program parameters and their userspace eq= uivalent calls. + * Variable sized arguments are held in fuse_buffers. To access these, bpf= programs must + * use kfuncs to access them as dynptrs. + * + */ + +#define FUSE_MAX_ARGS_IN 3 +#define FUSE_MAX_ARGS_OUT 2 + +struct bpf_fuse_arg { + union { + void *value; + struct fuse_buffer *buffer; + }; + unsigned size; + bool is_buffer; +}; + +struct bpf_fuse_meta_info { + uint64_t nodeid; + uint32_t opcode; + uint32_t error_in; +}; + +struct bpf_fuse_args { + struct bpf_fuse_meta_info info; + uint32_t in_numargs; + uint32_t out_numargs; + uint32_t flags; + struct bpf_fuse_arg in_args[FUSE_MAX_ARGS_IN]; + struct bpf_fuse_arg out_args[FUSE_MAX_ARGS_OUT]; +}; + +// Mirrors for struct fuse_args flags +#define FUSE_BPF_FORCE (1 << 0) +#define FUSE_BPF_OUT_ARGVAR (1 << 6) +#define FUSE_BPF_IS_LOOKUP (1 << 11) + +static inline void *bpf_fuse_arg_value(const struct bpf_fuse_arg *arg) +{ + return arg->is_buffer ? arg->buffer : arg->value; +} + +static inline unsigned bpf_fuse_arg_size(const struct bpf_fuse_arg *arg) +{ + return arg->is_buffer ? arg->buffer->size : arg->size; +} + +#endif /* _BPF_FUSE_H */ --=20 2.40.0.634.g4ca3ef3211-goog From nobody Wed Dec 17 19:22:07 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 1869EC77B75 for ; Tue, 18 Apr 2023 01:41:50 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230454AbjDRBls (ORCPT ); Mon, 17 Apr 2023 21:41:48 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53850 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230311AbjDRBlS (ORCPT ); Mon, 17 Apr 2023 21:41:18 -0400 Received: from mail-yw1-x114a.google.com (mail-yw1-x114a.google.com [IPv6:2607:f8b0:4864:20::114a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 7831B65B5 for ; Mon, 17 Apr 2023 18:41:07 -0700 (PDT) Received: by mail-yw1-x114a.google.com with SMTP id 00721157ae682-552f169d85eso23912897b3.13 for ; Mon, 17 Apr 2023 18:41:07 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20221208; t=1681782066; x=1684374066; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=Ma6LP7l9KdDYxD0yEhLpdRJpUgrAcoFlwvcgm95S2g0=; b=d51SS6WG6e8IhBQxgiwtwAX6W6u5b/p5f3qAWDcVPo2jEbJQRrKfqB7fD2WB7ABXfm f1QDkxS0AxwE1oa6FQ4jYY7ixPpnGwXCPx+AsK15KrITTIQeNrqN2cahcwamg4mHST/1 dLSOe/MQEeIXaOymPStG/HcmyjMuppTVyWgNDT62/a0q/oG3tDfVWH7rMGNawaD0o+Tp Ni0kuHRqOhAOcNAtD4v/LtaKrjnY1QvynB5VRgotRKQA6MFysty3xo5ey1gq6oFLlnLv /nnda2RIAREqoKgyxD+dqXX80U30yBXf5e+3lSppGCBf17IWxLGBotULgIChHzE5mTEv issw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681782066; x=1684374066; 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=Ma6LP7l9KdDYxD0yEhLpdRJpUgrAcoFlwvcgm95S2g0=; b=YWVr6QEnZT5WLiVXcWg0CQsrmXdLQ/bZAtNRKBuE3vjwz44hfLI3i3hN52tAen81IO PoEY+1sIx1pkU1Tke0bE0rHav+MiARkLtqDQyMdXZxgblwbQ1ykdM47OhfLZGrr5C8NA oYAsrwOy5N7PjuKZz09ApYcuZkBjdA6D1f6jk3CLSviGKKrOod7K6kJlaHR+t9OQYP8k wm2pMGnXsBOd0p1ndPQf1EUChn2hT1I0SIrk5d9dlTIwbYNkiTs18xN/tq5eBBE5rlpE QOKYUiujw7OW8hRiaWQmXsAByPEBqIwhygIA2uEHO/m1oj+2G/vQOulSUDFHOv28Mh9N 38Zw== X-Gm-Message-State: AAQBX9f3pMeerhfOQZxfJr46oa5A5roZ7jBdSldl8H3Bu9tZmYcyweSq VdcT9bzn3J6tKz22D49SOXMiPV5CYlE= X-Google-Smtp-Source: AKy350al/X5V7uOGTMzTFULq0yhZ2D34oueue8qfbReq7qB7OLV2hrBAIi9Rsio853MG4jn2Pq7Glue+WGc= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:e67a:98b0:942d:86aa]) (user=drosen job=sendgmr) by 2002:a25:aaad:0:b0:b8f:6a46:537d with SMTP id t42-20020a25aaad000000b00b8f6a46537dmr8996331ybi.6.1681782066667; Mon, 17 Apr 2023 18:41:06 -0700 (PDT) Date: Mon, 17 Apr 2023 18:40:07 -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-8-drosen@google.com> Subject: [RFC PATCH v3 07/37] fuse-bpf: Prepare for fuse-bpf patch 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 Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This moves some functions and structs around to make the following patch easier to read. Signed-off-by: Daniel Rosenberg Signed-off-by: Paul Lawrence --- fs/fuse/dir.c | 30 ------------------------------ fs/fuse/fuse_i.h | 35 +++++++++++++++++++++++++++++++++++ fs/fuse/inode.c | 44 ++++++++++++++++++++++---------------------- 3 files changed, 57 insertions(+), 52 deletions(-) diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 35bc174f9ba2..55dd6e8b2e43 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -46,10 +46,6 @@ static inline u64 fuse_dentry_time(const struct dentry *= entry) } =20 #else -union fuse_dentry { - u64 time; - struct rcu_head rcu; -}; =20 static inline void __fuse_dentry_settime(struct dentry *dentry, u64 time) { @@ -83,27 +79,6 @@ static void fuse_dentry_settime(struct dentry *dentry, u= 64 time) __fuse_dentry_settime(dentry, time); } =20 -/* - * FUSE caches dentries and attributes with separate timeout. The - * time in jiffies until the dentry/attributes are valid is stored in - * dentry->d_fsdata and fuse_inode->i_time respectively. - */ - -/* - * Calculate the time in jiffies until a dentry/attributes are valid - */ -static u64 time_to_jiffies(u64 sec, u32 nsec) -{ - if (sec || nsec) { - struct timespec64 ts =3D { - sec, - min_t(u32, nsec, NSEC_PER_SEC - 1) - }; - - return get_jiffies_64() + timespec64_to_jiffies(&ts); - } else - return 0; -} =20 /* * Set dentry and possibly attribute timeouts from the lookup/mk* @@ -115,11 +90,6 @@ void fuse_change_entry_timeout(struct dentry *entry, st= ruct fuse_entry_out *o) time_to_jiffies(o->entry_valid, o->entry_valid_nsec)); } =20 -static u64 attr_timeout(struct fuse_attr_out *o) -{ - return time_to_jiffies(o->attr_valid, o->attr_valid_nsec); -} - u64 entry_attr_timeout(struct fuse_entry_out *o) { return time_to_jiffies(o->attr_valid, o->attr_valid_nsec); diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 9b7fc7d3c7f1..01ca8bb87b4f 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -63,6 +63,14 @@ struct fuse_forget_link { struct fuse_forget_link *next; }; =20 +/** FUSE specific dentry data */ +#if BITS_PER_LONG < 64 +union fuse_dentry { + u64 time; + struct rcu_head rcu; +}; +#endif + /** FUSE inode */ struct fuse_inode { /** Inode data */ @@ -1324,4 +1332,31 @@ struct fuse_file *fuse_file_open(struct fuse_mount *= fm, u64 nodeid, void fuse_file_release(struct inode *inode, struct fuse_file *ff, unsigned int open_flags, fl_owner_t id, bool isdir); =20 +/* + * FUSE caches dentries and attributes with separate timeout. The + * time in jiffies until the dentry/attributes are valid is stored in + * dentry->d_fsdata and fuse_inode->i_time respectively. + */ + +/* + * Calculate the time in jiffies until a dentry/attributes are valid + */ +static inline u64 time_to_jiffies(u64 sec, u32 nsec) +{ + if (sec || nsec) { + struct timespec64 ts =3D { + sec, + min_t(u32, nsec, NSEC_PER_SEC - 1) + }; + + return get_jiffies_64() + timespec64_to_jiffies(&ts); + } else + return 0; +} + +static inline u64 attr_timeout(struct fuse_attr_out *o) +{ + return time_to_jiffies(o->attr_valid, o->attr_valid_nsec); +} + #endif /* _FS_FUSE_I_H */ diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index d66070af145d..a824ca100047 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -162,6 +162,28 @@ static ino_t fuse_squash_ino(u64 ino64) return ino; } =20 +static void fuse_fill_attr_from_inode(struct fuse_attr *attr, + const struct fuse_inode *fi) +{ + *attr =3D (struct fuse_attr){ + .ino =3D fi->inode.i_ino, + .size =3D fi->inode.i_size, + .blocks =3D fi->inode.i_blocks, + .atime =3D fi->inode.i_atime.tv_sec, + .mtime =3D fi->inode.i_mtime.tv_sec, + .ctime =3D fi->inode.i_ctime.tv_sec, + .atimensec =3D fi->inode.i_atime.tv_nsec, + .mtimensec =3D fi->inode.i_mtime.tv_nsec, + .ctimensec =3D fi->inode.i_ctime.tv_nsec, + .mode =3D fi->inode.i_mode, + .nlink =3D fi->inode.i_nlink, + .uid =3D fi->inode.i_uid.val, + .gid =3D fi->inode.i_gid.val, + .rdev =3D fi->inode.i_rdev, + .blksize =3D 1u << fi->inode.i_blkbits, + }; +} + void fuse_change_attributes_common(struct inode *inode, struct fuse_attr *= attr, u64 attr_valid, u32 cache_mask) { @@ -1394,28 +1416,6 @@ void fuse_dev_free(struct fuse_dev *fud) } EXPORT_SYMBOL_GPL(fuse_dev_free); =20 -static void fuse_fill_attr_from_inode(struct fuse_attr *attr, - const struct fuse_inode *fi) -{ - *attr =3D (struct fuse_attr){ - .ino =3D fi->inode.i_ino, - .size =3D fi->inode.i_size, - .blocks =3D fi->inode.i_blocks, - .atime =3D fi->inode.i_atime.tv_sec, - .mtime =3D fi->inode.i_mtime.tv_sec, - .ctime =3D fi->inode.i_ctime.tv_sec, - .atimensec =3D fi->inode.i_atime.tv_nsec, - .mtimensec =3D fi->inode.i_mtime.tv_nsec, - .ctimensec =3D fi->inode.i_ctime.tv_nsec, - .mode =3D fi->inode.i_mode, - .nlink =3D fi->inode.i_nlink, - .uid =3D fi->inode.i_uid.val, - .gid =3D fi->inode.i_gid.val, - .rdev =3D fi->inode.i_rdev, - .blksize =3D 1u << fi->inode.i_blkbits, - }; -} - static void fuse_sb_defaults(struct super_block *sb) { sb->s_magic =3D FUSE_SUPER_MAGIC; --=20 2.40.0.634.g4ca3ef3211-goog From nobody Wed Dec 17 19:22:07 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 1501DC77B75 for ; Tue, 18 Apr 2023 01:42:07 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230408AbjDRBmE (ORCPT ); Mon, 17 Apr 2023 21:42:04 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53824 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230373AbjDRBln (ORCPT ); Mon, 17 Apr 2023 21:41:43 -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 DAC5F76B7 for ; Mon, 17 Apr 2023 18:41:10 -0700 (PDT) Received: by mail-yw1-x1149.google.com with SMTP id 00721157ae682-54be7584b28so368332047b3.16 for ; Mon, 17 Apr 2023 18:41:10 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20221208; t=1681782069; x=1684374069; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=43QxFVc50DAyWgu57dIHIILS8j92JgJ+spQAbuJ/QvU=; b=Ltmi58KxaJRM+xuYPqy2BRHGm5n20ZyE8MpSN8weeGUxH/6LjHoVp2zzmJoBXYUfVN nuhTslW/LLX6Ujijwmez5/vmwwEhdVBm0s06X6nvnlpk9zrYLXM5Jnc7bWhzXSPPJIxE +Zl1xzhrFSUBn2Xhvypy54vX8yGiq2KmWBJyVpTywwAWOaOehypOEjmz9oszH+GzW8DF mJ8v6T/dexXI+6FblRvM/5OCKtMR8pTbO/upGDZVyVxo+97DDjn/8vWsVXeYAHBRhp/A OCBVMetoYH0EK/RbHiNUkJQKGANQO1GGhtRFSlXXYOKcU07jMdn1BmqALJ0gBteEBBFQ gNNw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681782069; x=1684374069; 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=43QxFVc50DAyWgu57dIHIILS8j92JgJ+spQAbuJ/QvU=; b=YdDKP2I2NhyyiBNWPUgPMOoXDx8mHAdMYtOa2/n35vexfJL8qXk1ting/BBcjgXEm8 geZi4eo5+gmq1ne+e+absLZtYbksa4+qo1SRXqGAPJ3xMruIs5ehpjcvU7317GAP7vhO u/bVCS62RwsxkKOBidSWr0+351tE3h2Yml26TP7T1FLZIvdHRY5t3VQUY/XKWqNMDB1r 5jrnoJOzazRwmzLCxEwaWeCtHU0gsnw+tV1CSF6L43im+odtE+kpRlMZgaRu9LekjUjd Yomw9oDZjtOUQOV0d+eaqIwB5Jnl71q/78F16N9SGj7YHxh5//zFKJd4WKDgsGzVfcnE I4ag== X-Gm-Message-State: AAQBX9c70QqGPh82I4JvKZ91qDOt1nWVux1iXuFok1vJ0MlNEzBROkdp jYLYOlIaBHatSiwrEwOgUCZrItYk/X8= X-Google-Smtp-Source: AKy350Y/8JtCHjP1QW/pvn4hJ1FgSbBVMwhqN5jn2gWMFPYp2imD4c1za6dXD+I2jmUpdindf2WdmxacnOA= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:e67a:98b0:942d:86aa]) (user=drosen job=sendgmr) by 2002:a05:690c:d91:b0:54f:e88d:79ba with SMTP id da17-20020a05690c0d9100b0054fe88d79bamr9713625ywb.5.1681782069111; Mon, 17 Apr 2023 18:41:09 -0700 (PDT) Date: Mon, 17 Apr 2023 18:40:08 -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-9-drosen@google.com> Subject: [RFC PATCH v3 08/37] fuse: Add fuse-bpf, a stacked fs extension for FUSE 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" Fuse-bpf provides a short circuit path for Fuse implementations that act as a stacked filesystem. For cases that are directly unchanged, operations are passed directly to the backing filesystem. Small adjustments can be handled by bpf prefilters or postfilters, with the option to fall back to userspace as needed. Fuse implementations may supply backing node information, as well as bpf programs via an optional add on to the lookup structure. This has been split over the next set of patches for readability. Clusters of fuse ops have been split into their own patches, as well as the actual bpf calls and userspace calls for filters. Signed-off-by: Daniel Rosenberg Signed-off-by: Paul Lawrence Signed-off-by: Alessio Balsini --- fs/fuse/Kconfig | 8 + fs/fuse/Makefile | 1 + fs/fuse/backing.c | 419 ++++++++++++++++++++++++++++++++++++++++++++++ fs/fuse/dev.c | 41 ++++- fs/fuse/dir.c | 187 +++++++++++++++++---- fs/fuse/file.c | 25 ++- fs/fuse/fuse_i.h | 99 ++++++++++- fs/fuse/inode.c | 189 +++++++++++++++++---- fs/fuse/ioctl.c | 2 +- 9 files changed, 888 insertions(+), 83 deletions(-) create mode 100644 fs/fuse/backing.c diff --git a/fs/fuse/Kconfig b/fs/fuse/Kconfig index 038ed0b9aaa5..3a64fa73e591 100644 --- a/fs/fuse/Kconfig +++ b/fs/fuse/Kconfig @@ -52,3 +52,11 @@ config FUSE_DAX =20 If you want to allow mounting a Virtio Filesystem with the "dax" option, answer Y. + +config FUSE_BPF + bool "Adds BPF to fuse" + depends on FUSE_FS + depends on BPF + help + Extends FUSE by adding BPF to prefilter calls and potentially pass to a + backing file system diff --git a/fs/fuse/Makefile b/fs/fuse/Makefile index 0c48b35c058d..a0853c439db2 100644 --- a/fs/fuse/Makefile +++ b/fs/fuse/Makefile @@ -9,5 +9,6 @@ obj-$(CONFIG_VIRTIO_FS) +=3D virtiofs.o =20 fuse-y :=3D dev.o dir.o file.o inode.o control.o xattr.o acl.o readdir.o i= octl.o fuse-$(CONFIG_FUSE_DAX) +=3D dax.o +fuse-$(CONFIG_FUSE_BPF) +=3D backing.o =20 virtiofs-y :=3D virtio_fs.o diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c new file mode 100644 index 000000000000..3d895957b5ce --- /dev/null +++ b/fs/fuse/backing.c @@ -0,0 +1,419 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * FUSE-BPF: Filesystem in Userspace with BPF + * Copyright (c) 2021 Google LLC + */ + +#include "fuse_i.h" + +#include +#include +#include +#include +#include + +/* + * expression statement to wrap the backing filter logic + * struct inode *inode: inode with bpf and backing inode + * typedef io: (typically complex) type whose components fuse_args can poi= nt to. + * An instance of this type is created locally and passed to initialize + * void initialize_in(struct bpf_fuse_args *fa, io *in_out, args...): func= tion that sets + * up fa and io based on args + * void initialize_out(struct bpf_fuse_args *fa, io *in_out, args...): fun= ction that sets + * up fa and io based on args + * int backing(struct fuse_bpf_args_internal *fa, args...): function that = actually performs + * the backing io operation + * void *finalize(struct fuse_bpf_args *, args...): function that performs= any final + * work needed to commit the backing io + */ +#define bpf_fuse_backing(inode, io, out, \ + initialize_in, initialize_out, \ + backing, finalize, args...) \ +({ \ + struct fuse_inode *fuse_inode =3D get_fuse_inode(inode); \ + struct bpf_fuse_args fa =3D { 0 }; \ + bool initialized =3D false; \ + bool handled =3D false; \ + ssize_t res; \ + io feo =3D { 0 }; \ + int error =3D 0; \ + \ + do { \ + if (!inode || !fuse_inode->backing_inode) \ + break; \ + \ + handled =3D true; \ + error =3D initialize_in(&fa, &feo, args); \ + if (error) \ + break; \ + \ + error =3D initialize_out(&fa, &feo, args); \ + if (error) \ + break; \ + \ + initialized =3D true; \ + \ + error =3D backing(&fa, out, args); \ + if (error < 0) \ + fa.info.error_in =3D error; \ + \ + } while (false); \ + \ + if (initialized && handled) { \ + res =3D finalize(&fa, out, args); \ + if (res) \ + error =3D res; \ + } \ + \ + *out =3D error ? _Generic((*out), \ + default : \ + error, \ + struct dentry * : \ + ERR_PTR(error), \ + const char * : \ + ERR_PTR(error) \ + ) : (*out); \ + handled; \ +}) + +static void fuse_get_backing_path(struct file *file, struct path *path) +{ + path_get(&file->f_path); + *path =3D file->f_path; +} + +static bool has_file(int type) +{ + return type =3D=3D FUSE_ENTRY_BACKING; +} + +/* + * The optional fuse bpf entry lists the backing file for a particular + * lookup. These are inherited by default. + * + * In the future, we may support multiple bpfs, and multiple backing files= for + * the bpf to choose between. + * + * Currently, the expected format is possibly a bpf program, then the back= ing + * file. Changing only the bpf is valid, though meaningless if there isn't= an + * inherited backing file. + * + * Support for the bpf program will be added in a later patch + * + */ +int parse_fuse_bpf_entry(struct fuse_bpf_entry *fbe, int num) +{ + struct fuse_bpf_entry_out *fbeo; + struct file *file; + bool has_backing =3D false; + int num_entries; + int err =3D -EINVAL; + int i; + + if (num > 0) + num_entries =3D num; + else + num_entries =3D FUSE_BPF_MAX_ENTRIES; + + for (i =3D 0; i < num_entries; i++) { + file =3D NULL; + fbeo =3D &fbe->out[i]; + + /* reserved for future use */ + if (fbeo->unused !=3D 0) + goto out_err; + + if (has_file(fbeo->entry_type)) { + file =3D fget(fbeo->fd); + if (!file) { + err =3D -EBADF; + goto out_err; + } + } + + switch (fbeo->entry_type) { + case 0: + if (num =3D=3D -1) + num_entries =3D i; + else + goto out_err; + break; + case FUSE_ENTRY_REMOVE_BACKING: + if (fbe->backing_action) + goto out_err; + fbe->backing_action =3D FUSE_BPF_REMOVE; + break; + case FUSE_ENTRY_BACKING: + if (fbe->backing_action) + goto out_err; + fuse_get_backing_path(file, &fbe->backing_path); + fbe->backing_action =3D FUSE_BPF_SET; + has_backing =3D true; + break; + default: + err =3D -EINVAL; + goto out_err; + } + if (has_file(fbeo->entry_type)) { + fput(file); + file =3D NULL; + } + } + + fbe->is_used =3D num_entries > 0; + + return 0; +out_err: + if (file) + fput(file); + if (has_backing) + path_put_init(&fbe->backing_path); + return err; +} + +static void fuse_stat_to_attr(struct fuse_conn *fc, struct inode *inode, + struct kstat *stat, struct fuse_attr *attr) +{ + unsigned int blkbits; + + /* see the comment in fuse_change_attributes() */ + if (fc->writeback_cache && S_ISREG(inode->i_mode)) { + stat->size =3D i_size_read(inode); + stat->mtime.tv_sec =3D inode->i_mtime.tv_sec; + stat->mtime.tv_nsec =3D inode->i_mtime.tv_nsec; + stat->ctime.tv_sec =3D inode->i_ctime.tv_sec; + stat->ctime.tv_nsec =3D inode->i_ctime.tv_nsec; + } + + attr->ino =3D stat->ino; + attr->mode =3D (inode->i_mode & S_IFMT) | (stat->mode & 07777); + attr->nlink =3D stat->nlink; + attr->uid =3D from_kuid(fc->user_ns, stat->uid); + attr->gid =3D from_kgid(fc->user_ns, stat->gid); + attr->atime =3D stat->atime.tv_sec; + attr->atimensec =3D stat->atime.tv_nsec; + attr->mtime =3D stat->mtime.tv_sec; + attr->mtimensec =3D stat->mtime.tv_nsec; + attr->ctime =3D stat->ctime.tv_sec; + attr->ctimensec =3D stat->ctime.tv_nsec; + attr->size =3D stat->size; + attr->blocks =3D stat->blocks; + + if (stat->blksize !=3D 0) + blkbits =3D ilog2(stat->blksize); + else + blkbits =3D inode->i_sb->s_blocksize_bits; + + attr->blksize =3D 1 << blkbits; +} + +/*************************************************************************= ****** + * Directory operations after here = * + *************************************************************************= *****/ + +struct fuse_lookup_args { + struct fuse_buffer name; + struct fuse_entry_out out; + struct fuse_bpf_entry entries_storage; + struct fuse_buffer bpf_entries; +}; + +static int fuse_lookup_initialize_in(struct bpf_fuse_args *fa, struct fuse= _lookup_args *args, + struct inode *dir, struct dentry *entry, unsigned int flags) +{ + *args =3D (struct fuse_lookup_args) { + .name =3D (struct fuse_buffer) { + .data =3D (void *) entry->d_name.name, + .size =3D entry->d_name.len + 1, + .max_size =3D NAME_MAX + 1, + .flags =3D BPF_FUSE_VARIABLE_SIZE | BPF_FUSE_MUST_ALLOCATE, + }, + }; + *fa =3D (struct bpf_fuse_args) { + .info =3D (struct bpf_fuse_meta_info) { + .nodeid =3D get_fuse_inode(dir)->nodeid, + .opcode =3D FUSE_LOOKUP, + }, + .in_numargs =3D 1, + .in_args[0] =3D (struct bpf_fuse_arg) { + .is_buffer =3D true, + .buffer =3D &args->name, + }, + }; + + return 0; +} + +static int fuse_lookup_initialize_out(struct bpf_fuse_args *fa, struct fus= e_lookup_args *args, + struct inode *dir, struct dentry *entry, unsigned int flags) +{ + args->bpf_entries =3D (struct fuse_buffer) { + .data =3D args->entries_storage.out, + .size =3D 0, + .alloc_size =3D sizeof(args->entries_storage.out), + .max_size =3D sizeof(args->entries_storage.out), + .flags =3D BPF_FUSE_VARIABLE_SIZE, + }, + + fa->out_numargs =3D 2; + fa->flags =3D FUSE_BPF_OUT_ARGVAR | FUSE_BPF_IS_LOOKUP; + fa->out_args[0] =3D (struct bpf_fuse_arg) { + .size =3D sizeof(args->out), + .value =3D &args->out, + }; + fa->out_args[1] =3D (struct bpf_fuse_arg) { + .is_buffer =3D true, + .buffer =3D &args->bpf_entries, + }; + + return 0; +} + +static int fuse_lookup_backing(struct bpf_fuse_args *fa, struct dentry **o= ut, struct inode *dir, + struct dentry *entry, unsigned int flags) +{ + struct fuse_dentry *fuse_entry =3D get_fuse_dentry(entry); + struct fuse_dentry *dir_fuse_entry =3D get_fuse_dentry(entry->d_parent); + struct dentry *dir_backing_entry =3D dir_fuse_entry->backing_path.dentry; + struct inode *dir_backing_inode =3D dir_backing_entry->d_inode; + struct fuse_entry_out *feo =3D (void *)fa->out_args[0].value; + struct dentry *backing_entry; + const char *name; + struct kstat stat; + int len; + int err; + + /* TODO this will not handle lookups over mount points */ + inode_lock_nested(dir_backing_inode, I_MUTEX_PARENT); + if (fa->in_args[0].buffer->flags & BPF_FUSE_MODIFIED) { + name =3D (char *)fa->in_args[0].buffer->data; + len =3D strnlen(name, fa->in_args[0].buffer->size); + } else { + name =3D entry->d_name.name; + len =3D entry->d_name.len; + } + backing_entry =3D lookup_one_len(name, dir_backing_entry, len); + inode_unlock(dir_backing_inode); + + if (IS_ERR(backing_entry)) + return PTR_ERR(backing_entry); + + fuse_entry->backing_path =3D (struct path) { + .dentry =3D backing_entry, + .mnt =3D mntget(dir_fuse_entry->backing_path.mnt), + }; + + if (d_is_negative(backing_entry)) { + fa->info.error_in =3D -ENOENT; + return 0; + } + + err =3D vfs_getattr(&fuse_entry->backing_path, &stat, + STATX_BASIC_STATS, 0); + if (err) { + path_put_init(&fuse_entry->backing_path); + return err; + } + + fuse_stat_to_attr(get_fuse_conn(dir), + backing_entry->d_inode, &stat, &feo->attr); + return 0; +} + +int fuse_handle_backing(struct fuse_bpf_entry *fbe, struct path *backing_p= ath) +{ + switch (fbe->backing_action) { + case FUSE_BPF_UNCHANGED: + /* backing inode/path are added in fuse_lookup_backing */ + break; + + case FUSE_BPF_REMOVE: + path_put_init(backing_path); + break; + + case FUSE_BPF_SET: { + if (!fbe->backing_path.dentry) + return -EINVAL; + + path_put(backing_path); + *backing_path =3D fbe->backing_path; + fbe->backing_path.dentry =3D NULL; + fbe->backing_path.mnt =3D NULL; + + break; + } + + default: + return -EINVAL; + } + + return 0; +} + +static int fuse_lookup_finalize(struct bpf_fuse_args *fa, struct dentry **= out, + struct inode *dir, struct dentry *entry, unsigned int flags) +{ + struct fuse_dentry *fd; + struct dentry *backing_dentry; + struct inode *inode, *backing_inode; + struct inode *d_inode =3D entry->d_inode; + struct fuse_entry_out *feo =3D fa->out_args[0].value; + struct fuse_bpf_entry_out *febo =3D fa->out_args[1].buffer->data; + struct fuse_bpf_entry *fbe =3D container_of(febo, struct fuse_bpf_entry, = out[0]); + int error =3D -1; + u64 target_nodeid =3D 0; + + parse_fuse_bpf_entry(fbe, -1); + fd =3D get_fuse_dentry(entry); + if (!fd) + return -EIO; + error =3D fuse_handle_backing(fbe, &fd->backing_path); + if (error) + return error; + backing_dentry =3D fd->backing_path.dentry; + if (!backing_dentry) + return -ENOENT; + backing_inode =3D backing_dentry->d_inode; + if (!backing_inode) { + *out =3D 0; + return 0; + } + + if (d_inode) + target_nodeid =3D get_fuse_inode(d_inode)->nodeid; + + inode =3D fuse_iget_backing(dir->i_sb, target_nodeid, backing_inode); + + if (IS_ERR(inode)) + return PTR_ERR(inode); + + get_fuse_inode(inode)->nodeid =3D feo->nodeid; + + *out =3D d_splice_alias(inode, entry); + return 0; +} + +int fuse_bpf_lookup(struct dentry **out, struct inode *dir, struct dentry = *entry, unsigned int flags) +{ + return bpf_fuse_backing(dir, struct fuse_lookup_args, out, + fuse_lookup_initialize_in, fuse_lookup_initialize_out, + fuse_lookup_backing, fuse_lookup_finalize, + dir, entry, flags); +} + +int fuse_revalidate_backing(struct dentry *entry, unsigned int flags) +{ + struct fuse_dentry *fuse_dentry =3D get_fuse_dentry(entry); + struct dentry *backing_entry =3D fuse_dentry->backing_path.dentry; + + spin_lock(&backing_entry->d_lock); + if (d_unhashed(backing_entry)) { + spin_unlock(&backing_entry->d_lock); + return 0; + } + spin_unlock(&backing_entry->d_lock); + + if (unlikely(backing_entry->d_flags & DCACHE_OP_REVALIDATE)) + return backing_entry->d_op->d_revalidate(backing_entry, flags); + return 1; +} diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index eb4f88e3dc97..a3029824c24f 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -238,6 +238,11 @@ void fuse_queue_forget(struct fuse_conn *fc, struct fu= se_forget_link *forget, { struct fuse_iqueue *fiq =3D &fc->iq; =20 + if (nodeid =3D=3D 0) { + kfree(forget); + return; + } + forget->forget_one.nodeid =3D nodeid; forget->forget_one.nlookup =3D nlookup; =20 @@ -1009,10 +1014,38 @@ static int fuse_copy_one(struct fuse_copy_state *cs= , void *val, unsigned size) return 0; } =20 +/* Copy the fuse-bpf lookup args and verify them */ +#ifdef CONFIG_FUSE_BPF +static int fuse_copy_lookup(struct fuse_copy_state *cs, void *val, unsigne= d size) +{ + struct fuse_bpf_entry_out *fbeo =3D (struct fuse_bpf_entry_out *)val; + struct fuse_bpf_entry *feb =3D container_of(fbeo, struct fuse_bpf_entry, = out[0]); + int num_entries =3D size / sizeof(*fbeo); + int err; + + if (size && size % sizeof(*fbeo) !=3D 0) + return -EINVAL; + + if (num_entries > FUSE_BPF_MAX_ENTRIES) + return -EINVAL; + err =3D fuse_copy_one(cs, val, size); + if (err) + return err; + if (size) + err =3D parse_fuse_bpf_entry(feb, num_entries); + return err; +} +#else +static int fuse_copy_lookup(struct fuse_copy_state *cs, void *val, unsigne= d size) +{ + return fuse_copy_one(cs, val, size); +} +#endif + /* Copy request arguments to/from userspace buffer */ static int fuse_copy_args(struct fuse_copy_state *cs, unsigned numargs, unsigned argpages, struct fuse_arg *args, - int zeroing) + int zeroing, unsigned is_lookup) { int err =3D 0; unsigned i; @@ -1021,6 +1054,8 @@ static int fuse_copy_args(struct fuse_copy_state *cs,= unsigned numargs, struct fuse_arg *arg =3D &args[i]; if (i =3D=3D numargs - 1 && argpages) err =3D fuse_copy_pages(cs, arg->size, zeroing); + else if (i =3D=3D numargs - 1 && is_lookup) + err =3D fuse_copy_lookup(cs, arg->value, arg->size); else err =3D fuse_copy_one(cs, arg->value, arg->size); } @@ -1298,7 +1333,7 @@ static ssize_t fuse_dev_do_read(struct fuse_dev *fud,= struct file *file, err =3D fuse_copy_one(cs, &req->in.h, sizeof(req->in.h)); if (!err) err =3D fuse_copy_args(cs, args->in_numargs, args->in_pages, - (struct fuse_arg *) args->in_args, 0); + (struct fuse_arg *) args->in_args, 0, 0); fuse_copy_finish(cs); spin_lock(&fpq->lock); clear_bit(FR_LOCKED, &req->flags); @@ -1837,7 +1872,7 @@ static int copy_out_args(struct fuse_copy_state *cs, = struct fuse_args *args, lastarg->size -=3D diffsize; } return fuse_copy_args(cs, args->out_numargs, args->out_pages, - args->out_args, args->page_zeroing); + args->out_args, args->page_zeroing, args->is_lookup); } =20 /* diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 55dd6e8b2e43..73ebe3498fb9 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -34,7 +34,7 @@ static void fuse_advise_use_readdirplus(struct inode *dir) set_bit(FUSE_I_ADVISE_RDPLUS, &fi->state); } =20 -#if BITS_PER_LONG >=3D 64 +#if BITS_PER_LONG >=3D 64 && !defined(CONFIG_FUSE_BPF) static inline void __fuse_dentry_settime(struct dentry *entry, u64 time) { entry->d_fsdata =3D (void *) time; @@ -49,12 +49,12 @@ static inline u64 fuse_dentry_time(const struct dentry = *entry) =20 static inline void __fuse_dentry_settime(struct dentry *dentry, u64 time) { - ((union fuse_dentry *) dentry->d_fsdata)->time =3D time; + ((struct fuse_dentry *) dentry->d_fsdata)->time =3D time; } =20 static inline u64 fuse_dentry_time(const struct dentry *entry) { - return ((union fuse_dentry *) entry->d_fsdata)->time; + return ((struct fuse_dentry *) entry->d_fsdata)->time; } #endif =20 @@ -79,6 +79,17 @@ static void fuse_dentry_settime(struct dentry *dentry, u= 64 time) __fuse_dentry_settime(dentry, time); } =20 +void fuse_init_dentry_root(struct dentry *root, struct file *backing_dir) +{ +#ifdef CONFIG_FUSE_BPF + struct fuse_dentry *fuse_dentry =3D root->d_fsdata; + + if (backing_dir) { + fuse_dentry->backing_path =3D backing_dir->f_path; + path_get(&fuse_dentry->backing_path); + } +#endif +} =20 /* * Set dentry and possibly attribute timeouts from the lookup/mk* @@ -150,7 +161,8 @@ static void fuse_invalidate_entry(struct dentry *entry) =20 static void fuse_lookup_init(struct fuse_conn *fc, struct fuse_args *args, u64 nodeid, const struct qstr *name, - struct fuse_entry_out *outarg) + struct fuse_entry_out *outarg, + struct fuse_bpf_entry_out *bpf_outarg) { memset(outarg, 0, sizeof(struct fuse_entry_out)); args->opcode =3D FUSE_LOOKUP; @@ -158,10 +170,43 @@ static void fuse_lookup_init(struct fuse_conn *fc, st= ruct fuse_args *args, args->in_numargs =3D 1; args->in_args[0].size =3D name->len + 1; args->in_args[0].value =3D name->name; - args->out_numargs =3D 1; + args->out_argvar =3D true; + args->out_numargs =3D 2; args->out_args[0].size =3D sizeof(struct fuse_entry_out); args->out_args[0].value =3D outarg; + args->out_args[1].size =3D sizeof(struct fuse_bpf_entry_out) * FUSE_BPF_M= AX_ENTRIES; + args->out_args[1].value =3D bpf_outarg; + args->is_lookup =3D 1; +} + +#ifdef CONFIG_FUSE_BPF +static bool backing_data_changed(struct fuse_inode *fi, struct dentry *ent= ry, + struct fuse_bpf_entry *bpf_arg) +{ + struct path new_backing_path; + struct inode *new_backing_inode; + int err; + bool ret =3D true; + + if (!entry) + return false; + + get_fuse_backing_path(entry, &new_backing_path); + + err =3D fuse_handle_backing(bpf_arg, &new_backing_path); + new_backing_inode =3D d_inode(new_backing_path.dentry); + + if (err) + goto put_inode; + + ret =3D (fi->backing_inode !=3D new_backing_inode || + !path_equal(&get_fuse_dentry(entry)->backing_path, &new_backing_path)); + +put_inode: + path_put(&new_backing_path); + return ret; } +#endif =20 /* * Check whether the dentry is still valid @@ -183,9 +228,23 @@ static int fuse_dentry_revalidate(struct dentry *entry= , unsigned int flags) inode =3D d_inode_rcu(entry); if (inode && fuse_is_bad(inode)) goto invalid; - else if (time_before64(fuse_dentry_time(entry), get_jiffies_64()) || + +#ifdef CONFIG_FUSE_BPF + /* TODO: Do we need bpf support for revalidate? + * If the lower filesystem says the entry is invalid, FUSE probably shoul= dn't + * try to fix that without going through the normal lookup path... + */ + if (get_fuse_dentry(entry)->backing_path.dentry) { + ret =3D fuse_revalidate_backing(entry, flags); + if (ret <=3D 0) { + goto out; + } + } +#endif + if (time_before64(fuse_dentry_time(entry), get_jiffies_64()) || (flags & (LOOKUP_EXCL | LOOKUP_REVAL | LOOKUP_RENAME_TARGET))) { struct fuse_entry_out outarg; + struct fuse_bpf_entry bpf_arg; FUSE_ARGS(args); struct fuse_forget_link *forget; u64 attr_version; @@ -197,27 +256,44 @@ static int fuse_dentry_revalidate(struct dentry *entr= y, unsigned int flags) ret =3D -ECHILD; if (flags & LOOKUP_RCU) goto out; - fm =3D get_fuse_mount(inode); =20 + parent =3D dget_parent(entry); + +#ifdef CONFIG_FUSE_BPF + /* TODO: Once we're handling timeouts for backing inodes, do a + * bpf based lookup_revalidate here. + */ + if (get_fuse_inode(parent->d_inode)->backing_inode) { + dput(parent); + ret =3D 1; + goto out; + } +#endif forget =3D fuse_alloc_forget(); ret =3D -ENOMEM; - if (!forget) + if (!forget) { + dput(parent); goto out; + } =20 attr_version =3D fuse_get_attr_version(fm->fc); =20 - parent =3D dget_parent(entry); fuse_lookup_init(fm->fc, &args, get_node_id(d_inode(parent)), - &entry->d_name, &outarg); + &entry->d_name, &outarg, bpf_arg.out); ret =3D fuse_simple_request(fm, &args); dput(parent); + /* Zero nodeid is same as -ENOENT */ if (!ret && !outarg.nodeid) ret =3D -ENOENT; - if (!ret) { + if (!ret || bpf_arg.is_used) { fi =3D get_fuse_inode(inode); if (outarg.nodeid !=3D get_node_id(inode) || +#ifdef CONFIG_FUSE_BPF + (bpf_arg.is_used && + backing_data_changed(fi, entry, &bpf_arg)) || +#endif (bool) IS_AUTOMOUNT(inode) !=3D (bool) (outarg.attr.flags & FUSE_AT= TR_SUBMOUNT)) { fuse_queue_forget(fm->fc, forget, outarg.nodeid, 1); @@ -259,17 +335,20 @@ static int fuse_dentry_revalidate(struct dentry *entr= y, unsigned int flags) goto out; } =20 -#if BITS_PER_LONG < 64 +#if BITS_PER_LONG < 64 || defined(CONFIG_FUSE_BPF) static int fuse_dentry_init(struct dentry *dentry) { - dentry->d_fsdata =3D kzalloc(sizeof(union fuse_dentry), + dentry->d_fsdata =3D kzalloc(sizeof(struct fuse_dentry), GFP_KERNEL_ACCOUNT | __GFP_RECLAIMABLE); =20 return dentry->d_fsdata ? 0 : -ENOMEM; } static void fuse_dentry_release(struct dentry *dentry) { - union fuse_dentry *fd =3D dentry->d_fsdata; + struct fuse_dentry *fd =3D dentry->d_fsdata; + + if (fd && fd->backing_path.dentry) + path_put(&fd->backing_path); =20 kfree_rcu(fd, rcu); } @@ -310,7 +389,7 @@ static struct vfsmount *fuse_dentry_automount(struct pa= th *path) const struct dentry_operations fuse_dentry_operations =3D { .d_revalidate =3D fuse_dentry_revalidate, .d_delete =3D fuse_dentry_delete, -#if BITS_PER_LONG < 64 +#if BITS_PER_LONG < 64 || defined(CONFIG_FUSE_BPF) .d_init =3D fuse_dentry_init, .d_release =3D fuse_dentry_release, #endif @@ -318,7 +397,7 @@ const struct dentry_operations fuse_dentry_operations = =3D { }; =20 const struct dentry_operations fuse_root_dentry_operations =3D { -#if BITS_PER_LONG < 64 +#if BITS_PER_LONG < 64 || defined(CONFIG_FUSE_BPF) .d_init =3D fuse_dentry_init, .d_release =3D fuse_dentry_release, #endif @@ -336,11 +415,13 @@ bool fuse_invalid_attr(struct fuse_attr *attr) attr->size > LLONG_MAX; } =20 -int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr= *name, - struct fuse_entry_out *outarg, struct inode **inode) +int fuse_lookup_name(struct super_block *sb, u64 nodeid, + const struct qstr *name, struct fuse_entry_out *outarg, + struct dentry *entry, struct inode **inode) { struct fuse_mount *fm =3D get_fuse_mount_super(sb); FUSE_ARGS(args); + struct fuse_bpf_entry bpf_arg =3D { 0 }; struct fuse_forget_link *forget; u64 attr_version; int err; @@ -358,23 +439,56 @@ int fuse_lookup_name(struct super_block *sb, u64 node= id, const struct qstr *name =20 attr_version =3D fuse_get_attr_version(fm->fc); =20 - fuse_lookup_init(fm->fc, &args, nodeid, name, outarg); + fuse_lookup_init(fm->fc, &args, nodeid, name, outarg, bpf_arg.out); err =3D fuse_simple_request(fm, &args); - /* Zero nodeid is same as -ENOENT, but with valid timeout */ - if (err || !outarg->nodeid) - goto out_put_forget; =20 - err =3D -EIO; - if (!outarg->nodeid) - goto out_put_forget; - if (fuse_invalid_attr(&outarg->attr)) - goto out_put_forget; - - *inode =3D fuse_iget(sb, outarg->nodeid, outarg->generation, - &outarg->attr, entry_attr_timeout(outarg), - attr_version); +#ifdef CONFIG_FUSE_BPF + if (bpf_arg.is_used) { + /* TODO Make sure this handles invalid handles */ + struct path *backing_path; + struct inode *backing_inode; + + err =3D -ENOENT; + if (!entry) + goto out_queue_forget; + + err =3D -EINVAL; + backing_path =3D &bpf_arg.backing_path; + if (!backing_path->dentry) + goto out_queue_forget; + + err =3D fuse_handle_backing(&bpf_arg, + &get_fuse_dentry(entry)->backing_path); + if (err) + goto out_queue_forget; + + backing_inode =3D d_inode(get_fuse_dentry(entry)->backing_path.dentry); + *inode =3D fuse_iget_backing(sb, outarg->nodeid, backing_inode); + if (!*inode) + goto out_queue_forget; + } else +#endif + { + /* Zero nodeid is same as -ENOENT, but with valid timeout */ + if (err || !outarg->nodeid) + goto out_put_forget; + + err =3D -EIO; + if (!outarg->nodeid) + goto out_put_forget; + if (fuse_invalid_attr(&outarg->attr)) + goto out_put_forget; + + *inode =3D fuse_iget(sb, outarg->nodeid, outarg->generation, + &outarg->attr, entry_attr_timeout(outarg), + attr_version); + } + err =3D -ENOMEM; - if (!*inode) { +#ifdef CONFIG_FUSE_BPF +out_queue_forget: +#endif + if (!*inode && outarg->nodeid) { fuse_queue_forget(fm->fc, forget, outarg->nodeid, 1); goto out; } @@ -399,9 +513,12 @@ static struct dentry *fuse_lookup(struct inode *dir, s= truct dentry *entry, if (fuse_is_bad(dir)) return ERR_PTR(-EIO); =20 + if (fuse_bpf_lookup(&newent, dir, entry, flags)) + return newent; + locked =3D fuse_lock_inode(dir); err =3D fuse_lookup_name(dir->i_sb, get_node_id(dir), &entry->d_name, - &outarg, &inode); + &outarg, entry, &inode); fuse_unlock_inode(dir, locked); if (err =3D=3D -ENOENT) { outarg_valid =3D false; @@ -1370,6 +1487,7 @@ static int fuse_permission(struct mnt_idmap *idmap, struct fuse_conn *fc =3D get_fuse_conn(inode); bool refreshed =3D false; int err =3D 0; + struct fuse_inode *fi =3D get_fuse_inode(inode); =20 if (fuse_is_bad(inode)) return -EIO; @@ -1382,7 +1500,6 @@ static int fuse_permission(struct mnt_idmap *idmap, */ if (fc->default_permissions || ((mask & MAY_EXEC) && S_ISREG(inode->i_mode))) { - struct fuse_inode *fi =3D get_fuse_inode(inode); u32 perm_mask =3D STATX_MODE | STATX_UID | STATX_GID; =20 if (perm_mask & READ_ONCE(fi->inval_mask) || @@ -1559,7 +1676,7 @@ static long fuse_dir_compat_ioctl(struct file *file, = unsigned int cmd, FUSE_IOCTL_COMPAT | FUSE_IOCTL_DIR); } =20 -static bool update_mtime(unsigned ivalid, bool trust_local_mtime) +static inline bool update_mtime(unsigned int ivalid, bool trust_local_mtim= e) { /* Always update if mtime is explicitly set */ if (ivalid & ATTR_MTIME_SET) diff --git a/fs/fuse/file.c b/fs/fuse/file.c index de37a3a06a71..25fb49f0a9f7 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -8,6 +8,7 @@ =20 #include "fuse_i.h" =20 +#include #include #include #include @@ -127,13 +128,18 @@ static void fuse_file_put(struct fuse_file *ff, bool = sync, bool isdir) } =20 struct fuse_file *fuse_file_open(struct fuse_mount *fm, u64 nodeid, - unsigned int open_flags, bool isdir) + unsigned int open_flags, bool isdir, struct file *file) { struct fuse_conn *fc =3D fm->fc; struct fuse_file *ff; int opcode =3D isdir ? FUSE_OPENDIR : FUSE_OPEN; =20 - ff =3D fuse_file_alloc(fm); + if (file && file->private_data) { + ff =3D file->private_data; + file->private_data =3D NULL; + } else { + ff =3D fuse_file_alloc(fm); + } if (!ff) return ERR_PTR(-ENOMEM); =20 @@ -171,7 +177,7 @@ struct fuse_file *fuse_file_open(struct fuse_mount *fm,= u64 nodeid, int fuse_do_open(struct fuse_mount *fm, u64 nodeid, struct file *file, bool isdir) { - struct fuse_file *ff =3D fuse_file_open(fm, nodeid, file->f_flags, isdir); + struct fuse_file *ff =3D fuse_file_open(fm, nodeid, file->f_flags, isdir,= file); =20 if (!IS_ERR(ff)) file->private_data =3D ff; @@ -1948,6 +1954,19 @@ int fuse_write_inode(struct inode *inode, struct wri= teback_control *wbc) */ WARN_ON(wbc->for_reclaim); =20 + /** + * TODO - fully understand why this is necessary + * + * With fuse-bpf, fsstress fails if rename is enabled without this + * + * We are getting writes here on directory inodes, which do not have an + * initialized file list so crash. + * + * The question is why we are getting those writes + */ + if (!S_ISREG(inode->i_mode)) + return 0; + ff =3D __fuse_write_file_get(fi); err =3D fuse_flush_times(inode, ff); if (ff) diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 01ca8bb87b4f..c24878f4a89f 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -16,6 +16,8 @@ #include #include #include +#include +#include #include #include #include @@ -31,6 +33,7 @@ #include #include #include +#include =20 /** Default max number of pages that can be used in a single read request = */ #define FUSE_DEFAULT_MAX_PAGES_PER_REQ 32 @@ -64,11 +67,35 @@ struct fuse_forget_link { }; =20 /** FUSE specific dentry data */ -#if BITS_PER_LONG < 64 -union fuse_dentry { - u64 time; - struct rcu_head rcu; +#if BITS_PER_LONG < 64 || defined(CONFIG_FUSE_BPF) +struct fuse_dentry { + union { + u64 time; + struct rcu_head rcu; + }; + struct path backing_path; }; + +static inline struct fuse_dentry *get_fuse_dentry(const struct dentry *ent= ry) +{ + return entry->d_fsdata; +} +#endif + +#ifdef CONFIG_FUSE_BPF +static inline void get_fuse_backing_path(const struct dentry *d, + struct path *path) +{ + struct fuse_dentry *di =3D get_fuse_dentry(d); + + if (!di) { + *path =3D (struct path) { .mnt =3D 0, .dentry =3D 0 }; + return; + } + + *path =3D di->backing_path; + path_get(path); +} #endif =20 /** FUSE inode */ @@ -76,6 +103,14 @@ struct fuse_inode { /** Inode data */ struct inode inode; =20 +#ifdef CONFIG_FUSE_BPF + /** + * Backing inode, if this inode is from a backing file system. + * If this is set, nodeid is 0. + */ + struct inode *backing_inode; +#endif + /** Unique ID, which identifies the inode between userspace * and kernel */ u64 nodeid; @@ -226,6 +261,14 @@ struct fuse_file { =20 } readdir; =20 +#ifdef CONFIG_FUSE_BPF + /** + * TODO: Reconcile with passthrough file + * backing file when in bpf mode + */ + struct file *backing_file; +#endif + /** RB node to be linked on fuse_conn->polled_files */ struct rb_node polled_node; =20 @@ -257,6 +300,7 @@ struct fuse_page_desc { struct fuse_args { uint64_t nodeid; uint32_t opcode; + uint32_t error_in; // May need adjustments??? uint8_t in_numargs; uint8_t out_numargs; uint8_t ext_idx; @@ -271,6 +315,7 @@ struct fuse_args { bool page_replace:1; bool may_block:1; bool is_ext:1; + bool is_lookup:1; struct fuse_in_arg in_args[3]; struct fuse_arg out_args[2]; void (*end)(struct fuse_mount *fm, struct fuse_args *args, int error); @@ -524,6 +569,7 @@ struct fuse_fs_context { unsigned int max_read; unsigned int blksize; const char *subtype; + struct file *root_dir; =20 /* DAX device, may be NULL */ struct dax_device *dax_dev; @@ -970,12 +1016,16 @@ extern const struct dentry_operations fuse_root_dent= ry_operations; /** * Get a filled in inode */ +struct inode *fuse_iget_backing(struct super_block *sb, + u64 nodeid, + struct inode *backing_inode); struct inode *fuse_iget(struct super_block *sb, u64 nodeid, int generation, struct fuse_attr *attr, u64 attr_valid, u64 attr_version); =20 int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr= *name, - struct fuse_entry_out *outarg, struct inode **inode); + struct fuse_entry_out *outarg, + struct dentry *entry, struct inode **inode); =20 /** * Send FORGET command @@ -1120,6 +1170,7 @@ void fuse_invalidate_entry_cache(struct dentry *entry= ); void fuse_invalidate_atime(struct inode *inode); =20 u64 entry_attr_timeout(struct fuse_entry_out *o); +void fuse_init_dentry_root(struct dentry *root, struct file *backing_dir); void fuse_change_entry_timeout(struct dentry *entry, struct fuse_entry_out= *o); =20 /** @@ -1328,10 +1379,46 @@ int fuse_fileattr_set(struct mnt_idmap *idmap, /* file.c */ =20 struct fuse_file *fuse_file_open(struct fuse_mount *fm, u64 nodeid, - unsigned int open_flags, bool isdir); + unsigned int open_flags, bool isdir, + struct file *file); void fuse_file_release(struct inode *inode, struct fuse_file *ff, unsigned int open_flags, fl_owner_t id, bool isdir); =20 +/* backing.c */ + +enum fuse_bpf_set { + FUSE_BPF_UNCHANGED =3D 0, + FUSE_BPF_SET, + FUSE_BPF_REMOVE, +}; + +struct fuse_bpf_entry { + struct fuse_bpf_entry_out out[FUSE_BPF_MAX_ENTRIES]; + + enum fuse_bpf_set backing_action; + struct path backing_path; + bool is_used; +}; + +int parse_fuse_bpf_entry(struct fuse_bpf_entry *fbe, int num_entries); + +#ifdef CONFIG_FUSE_BPF + +int fuse_bpf_lookup(struct dentry **out, struct inode *dir, struct dentry = *entry, unsigned int flags); + +#else + +static inline int fuse_bpf_lookup(struct dentry **out, struct inode *dir, = struct dentry *entry, unsigned int flags) +{ + return 0; +} + +#endif // CONFIG_FUSE_BPF + +int fuse_handle_backing(struct fuse_bpf_entry *feb, struct path *backing_p= ath); + +int fuse_revalidate_backing(struct dentry *entry, unsigned int flags); + /* * FUSE caches dentries and attributes with separate timeout. The * time in jiffies until the dentry/attributes are valid is stored in diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index a824ca100047..b71e8758fab5 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -78,6 +78,9 @@ static struct inode *fuse_alloc_inode(struct super_block = *sb) =20 fi->i_time =3D 0; fi->inval_mask =3D 0; +#ifdef CONFIG_FUSE_BPF + fi->backing_inode =3D NULL; +#endif fi->nodeid =3D 0; fi->nlookup =3D 0; fi->attr_version =3D 0; @@ -120,6 +123,10 @@ static void fuse_evict_inode(struct inode *inode) /* Will write inode on close/munmap and in all other dirtiers */ WARN_ON(inode->i_state & I_DIRTY_INODE); =20 +#ifdef CONFIG_FUSE_BPF + iput(fi->backing_inode); +#endif + truncate_inode_pages_final(&inode->i_data); clear_inode(inode); if (inode->i_sb->s_flags & SB_ACTIVE) { @@ -163,24 +170,24 @@ static ino_t fuse_squash_ino(u64 ino64) } =20 static void fuse_fill_attr_from_inode(struct fuse_attr *attr, - const struct fuse_inode *fi) + const struct inode *inode) { *attr =3D (struct fuse_attr){ - .ino =3D fi->inode.i_ino, - .size =3D fi->inode.i_size, - .blocks =3D fi->inode.i_blocks, - .atime =3D fi->inode.i_atime.tv_sec, - .mtime =3D fi->inode.i_mtime.tv_sec, - .ctime =3D fi->inode.i_ctime.tv_sec, - .atimensec =3D fi->inode.i_atime.tv_nsec, - .mtimensec =3D fi->inode.i_mtime.tv_nsec, - .ctimensec =3D fi->inode.i_ctime.tv_nsec, - .mode =3D fi->inode.i_mode, - .nlink =3D fi->inode.i_nlink, - .uid =3D fi->inode.i_uid.val, - .gid =3D fi->inode.i_gid.val, - .rdev =3D fi->inode.i_rdev, - .blksize =3D 1u << fi->inode.i_blkbits, + .ino =3D inode->i_ino, + .size =3D inode->i_size, + .blocks =3D inode->i_blocks, + .atime =3D inode->i_atime.tv_sec, + .mtime =3D inode->i_mtime.tv_sec, + .ctime =3D inode->i_ctime.tv_sec, + .atimensec =3D inode->i_atime.tv_nsec, + .mtimensec =3D inode->i_mtime.tv_nsec, + .ctimensec =3D inode->i_ctime.tv_nsec, + .mode =3D inode->i_mode, + .nlink =3D inode->i_nlink, + .uid =3D inode->i_uid.val, + .gid =3D inode->i_gid.val, + .rdev =3D inode->i_rdev, + .blksize =3D 1u << inode->i_blkbits, }; } =20 @@ -352,8 +359,7 @@ static void fuse_init_inode(struct inode *inode, struct= fuse_attr *attr, else if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode) || S_ISFIFO(inode->i_mode) || S_ISSOCK(inode->i_mode)) { fuse_init_common(inode); - init_special_inode(inode, inode->i_mode, - new_decode_dev(attr->rdev)); + init_special_inode(inode, inode->i_mode, attr->rdev); } else BUG(); /* @@ -364,22 +370,100 @@ static void fuse_init_inode(struct inode *inode, str= uct fuse_attr *attr, inode->i_acl =3D inode->i_default_acl =3D ACL_DONT_CACHE; } =20 +struct fuse_inode_identifier { + u64 nodeid; + struct inode *backing_inode; +}; + static int fuse_inode_eq(struct inode *inode, void *_nodeidp) { - u64 nodeid =3D *(u64 *) _nodeidp; - if (get_node_id(inode) =3D=3D nodeid) - return 1; - else - return 0; + struct fuse_inode_identifier *fii =3D + (struct fuse_inode_identifier *) _nodeidp; + struct fuse_inode *fi =3D get_fuse_inode(inode); + + return fii->nodeid =3D=3D fi->nodeid; +} + +static int fuse_inode_backing_eq(struct inode *inode, void *_nodeidp) +{ + struct fuse_inode_identifier *fii =3D + (struct fuse_inode_identifier *) _nodeidp; + struct fuse_inode *fi =3D get_fuse_inode(inode); + + return fii->nodeid =3D=3D fi->nodeid +#ifdef CONFIG_FUSE_BPF + && fii->backing_inode =3D=3D fi->backing_inode +#endif + ; } =20 static int fuse_inode_set(struct inode *inode, void *_nodeidp) { - u64 nodeid =3D *(u64 *) _nodeidp; - get_fuse_inode(inode)->nodeid =3D nodeid; + struct fuse_inode_identifier *fii =3D + (struct fuse_inode_identifier *) _nodeidp; + struct fuse_inode *fi =3D get_fuse_inode(inode); + + fi->nodeid =3D fii->nodeid; + + return 0; +} + +static int fuse_inode_backing_set(struct inode *inode, void *_nodeidp) +{ + struct fuse_inode_identifier *fii =3D + (struct fuse_inode_identifier *) _nodeidp; + struct fuse_inode *fi =3D get_fuse_inode(inode); + + fi->nodeid =3D fii->nodeid; +#ifdef CONFIG_FUSE_BPF + BUG_ON(fi->backing_inode !=3D NULL); + fi->backing_inode =3D fii->backing_inode; + if (fi->backing_inode) + ihold(fi->backing_inode); +#endif + return 0; } =20 +struct inode *fuse_iget_backing(struct super_block *sb, u64 nodeid, + struct inode *backing_inode) +{ + struct inode *inode; + struct fuse_inode *fi; + struct fuse_conn *fc =3D get_fuse_conn_super(sb); + struct fuse_inode_identifier fii =3D { + .nodeid =3D nodeid, + .backing_inode =3D backing_inode, + }; + struct fuse_attr attr; + unsigned long hash =3D (unsigned long) backing_inode; + + if (nodeid) + hash =3D nodeid; + + fuse_fill_attr_from_inode(&attr, backing_inode); + inode =3D iget5_locked(sb, hash, fuse_inode_backing_eq, + fuse_inode_backing_set, &fii); + if (!inode) + return NULL; + + if ((inode->i_state & I_NEW)) { + inode->i_flags |=3D S_NOATIME; + if (!fc->writeback_cache) + inode->i_flags |=3D S_NOCMTIME; + fuse_init_common(inode); + unlock_new_inode(inode); + } + + fi =3D get_fuse_inode(inode); + fuse_init_inode(inode, &attr, fc); + spin_lock(&fi->lock); + fi->nlookup++; + spin_unlock(&fi->lock); + + return inode; +} + struct inode *fuse_iget(struct super_block *sb, u64 nodeid, int generation, struct fuse_attr *attr, u64 attr_valid, u64 attr_version) @@ -387,6 +471,9 @@ struct inode *fuse_iget(struct super_block *sb, u64 nod= eid, struct inode *inode; struct fuse_inode *fi; struct fuse_conn *fc =3D get_fuse_conn_super(sb); + struct fuse_inode_identifier fii =3D { + .nodeid =3D nodeid, + }; =20 /* * Auto mount points get their node id from the submount root, which is @@ -408,7 +495,7 @@ struct inode *fuse_iget(struct super_block *sb, u64 nod= eid, } =20 retry: - inode =3D iget5_locked(sb, nodeid, fuse_inode_eq, fuse_inode_set, &nodeid= ); + inode =3D iget5_locked(sb, nodeid, fuse_inode_eq, fuse_inode_set, &fii); if (!inode) return NULL; =20 @@ -440,13 +527,16 @@ struct inode *fuse_ilookup(struct fuse_conn *fc, u64 = nodeid, { struct fuse_mount *fm_iter; struct inode *inode; + struct fuse_inode_identifier fii =3D { + .nodeid =3D nodeid, + }; =20 WARN_ON(!rwsem_is_locked(&fc->killsb)); list_for_each_entry(fm_iter, &fc->mounts, fc_entry) { if (!fm_iter->sb) continue; =20 - inode =3D ilookup5(fm_iter->sb, nodeid, fuse_inode_eq, &nodeid); + inode =3D ilookup5(fm_iter->sb, nodeid, fuse_inode_eq, &fii); if (inode) { if (fm) *fm =3D fm_iter; @@ -676,6 +766,7 @@ enum { OPT_ALLOW_OTHER, OPT_MAX_READ, OPT_BLKSIZE, + OPT_ROOT_DIR, OPT_ERR }; =20 @@ -690,6 +781,7 @@ static const struct fs_parameter_spec fuse_fs_parameter= s[] =3D { fsparam_u32 ("max_read", OPT_MAX_READ), fsparam_u32 ("blksize", OPT_BLKSIZE), fsparam_string ("subtype", OPT_SUBTYPE), + fsparam_u32 ("root_dir", OPT_ROOT_DIR), {} }; =20 @@ -773,6 +865,12 @@ static int fuse_parse_param(struct fs_context *fsc, st= ruct fs_parameter *param) ctx->blksize =3D result.uint_32; break; =20 + case OPT_ROOT_DIR: + ctx->root_dir =3D fget(result.uint_32); + if (!ctx->root_dir) + return invalfc(fsc, "Unable to open root directory"); + break; + default: return -EINVAL; } @@ -785,6 +883,8 @@ static void fuse_free_fsc(struct fs_context *fsc) struct fuse_fs_context *ctx =3D fsc->fs_private; =20 if (ctx) { + if (ctx->root_dir) + fput(ctx->root_dir); kfree(ctx->subtype); kfree(ctx); } @@ -912,15 +1012,29 @@ struct fuse_conn *fuse_conn_get(struct fuse_conn *fc) } EXPORT_SYMBOL_GPL(fuse_conn_get); =20 -static struct inode *fuse_get_root_inode(struct super_block *sb, unsigned = mode) +static struct inode *fuse_get_root_inode(struct super_block *sb, + unsigned int mode, + struct file *backing_fd) { struct fuse_attr attr; - memset(&attr, 0, sizeof(attr)); + struct inode *inode; =20 + memset(&attr, 0, sizeof(attr)); attr.mode =3D mode; attr.ino =3D FUSE_ROOT_ID; attr.nlink =3D 1; - return fuse_iget(sb, 1, 0, &attr, 0, 0); + inode =3D fuse_iget(sb, 1, 0, &attr, 0, 0); + if (!inode) + return NULL; + +#ifdef CONFIG_FUSE_BPF + if (backing_fd) { + get_fuse_inode(inode)->backing_inode =3D backing_fd->f_inode; + ihold(backing_fd->f_inode); + } +#endif + + return inode; } =20 struct fuse_inode_handle { @@ -935,11 +1049,14 @@ static struct dentry *fuse_get_dentry(struct super_b= lock *sb, struct inode *inode; struct dentry *entry; int err =3D -ESTALE; + struct fuse_inode_identifier fii =3D { + .nodeid =3D handle->nodeid, + }; =20 if (handle->nodeid =3D=3D 0) goto out_err; =20 - inode =3D ilookup5(sb, handle->nodeid, fuse_inode_eq, &handle->nodeid); + inode =3D ilookup5(sb, handle->nodeid, fuse_inode_eq, &fii); if (!inode) { struct fuse_entry_out outarg; const struct qstr name =3D QSTR_INIT(".", 1); @@ -948,7 +1065,7 @@ static struct dentry *fuse_get_dentry(struct super_blo= ck *sb, goto out_err; =20 err =3D fuse_lookup_name(sb, handle->nodeid, &name, &outarg, - &inode); + NULL, &inode); if (err && err !=3D -ENOENT) goto out_err; if (err || !inode) { @@ -1042,13 +1159,14 @@ static struct dentry *fuse_get_parent(struct dentry= *child) struct inode *inode; struct dentry *parent; struct fuse_entry_out outarg; + const struct qstr name =3D QSTR_INIT("..", 2); int err; =20 if (!fc->export_support) return ERR_PTR(-ESTALE); =20 err =3D fuse_lookup_name(child_inode->i_sb, get_node_id(child_inode), - &dotdot_name, &outarg, &inode); + &name, &outarg, NULL, &inode); if (err) { if (err =3D=3D -ENOENT) return ERR_PTR(-ESTALE); @@ -1452,7 +1570,7 @@ static int fuse_fill_super_submount(struct super_bloc= k *sb, if (parent_sb->s_subtype && !sb->s_subtype) return -ENOMEM; =20 - fuse_fill_attr_from_inode(&root_attr, parent_fi); + fuse_fill_attr_from_inode(&root_attr, &parent_fi->inode); root =3D fuse_iget(sb, parent_fi->nodeid, 0, &root_attr, 0, 0); /* * This inode is just a duplicate, so it is not looked up and @@ -1581,11 +1699,12 @@ int fuse_fill_super_common(struct super_block *sb, = struct fuse_fs_context *ctx) fc->no_force_umount =3D ctx->no_force_umount; =20 err =3D -ENOMEM; - root =3D fuse_get_root_inode(sb, ctx->rootmode); + root =3D fuse_get_root_inode(sb, ctx->rootmode, ctx->root_dir); sb->s_d_op =3D &fuse_root_dentry_operations; root_dentry =3D d_make_root(root); if (!root_dentry) goto err_dev_free; + fuse_init_dentry_root(root_dentry, ctx->root_dir); /* Root dentry doesn't have .d_revalidate */ sb->s_d_op =3D &fuse_dentry_operations; =20 diff --git a/fs/fuse/ioctl.c b/fs/fuse/ioctl.c index 8e01bfdfc430..3542d992bde6 100644 --- a/fs/fuse/ioctl.c +++ b/fs/fuse/ioctl.c @@ -428,7 +428,7 @@ static struct fuse_file *fuse_priv_ioctl_prepare(struct= inode *inode) if (!S_ISREG(inode->i_mode) && !isdir) return ERR_PTR(-ENOTTY); =20 - return fuse_file_open(fm, get_node_id(inode), O_RDONLY, isdir); + return fuse_file_open(fm, get_node_id(inode), O_RDONLY, isdir, NULL); } =20 static void fuse_priv_ioctl_cleanup(struct inode *inode, struct fuse_file = *ff) --=20 2.40.0.634.g4ca3ef3211-goog From nobody Wed Dec 17 19:22:07 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 64539C77B7C for ; Tue, 18 Apr 2023 01:42:20 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230501AbjDRBmS (ORCPT ); Mon, 17 Apr 2023 21:42:18 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53844 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230417AbjDRBlo (ORCPT ); Mon, 17 Apr 2023 21:41:44 -0400 Received: from mail-yb1-xb4a.google.com (mail-yb1-xb4a.google.com [IPv6:2607:f8b0:4864:20::b4a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id C1516729D for ; Mon, 17 Apr 2023 18:41:12 -0700 (PDT) Received: by mail-yb1-xb4a.google.com with SMTP id t66-20020a254645000000b00b74680a7904so25429632yba.15 for ; Mon, 17 Apr 2023 18:41:12 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20221208; t=1681782071; x=1684374071; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=w19M7mxJAre5VfcOLhOZhg9JC6HtCZspBwvR3oTaBtM=; b=O/uzOqnDmS58QEJVz0T7PNXTX0Mtqd93rP+7E7na6jJ7QBcC3+3JWqA624W+JCI64R g5UeaSIEQ2e9zoM9bLjzbRQ4TX4IcKtelfNTRYpmePqs0g/0s18ad9gEBxKqugTmTnXJ LzySXi8ehZLsjwTOWb1Bm2cDRdGh9W+hMegnr4ssDEOtKk1HcW9S90nfvbwuqyU9afp8 r/LRBntSZet4HdYZYg9L+SpnqePNzq+J51zllKN3K7oc10u2cosiIPhI7KlqZt97mhR3 m9YfjttiB71iUr6bziRAvbTY79chCtR9d3tTTN8GEdsXPPkdN4l+BdlYow6MfQfY3GLw IdrQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681782071; x=1684374071; 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=w19M7mxJAre5VfcOLhOZhg9JC6HtCZspBwvR3oTaBtM=; b=N0UYqChq2wTlHvoSV0CZ52QydCcQnS8L7QN0GjtzNPXpDn0x4WowWSnuQXPHmpO0/b 66jGXj3V5oFDP9Bk5P8hWP2lfRU7dDphMZsVynLFuaKH9ukZF5H/on/NGXpNSX0SLOMf Ip14pR6sVJoQJiYmMI5BOtRzWv6p17+vINH4T3lxoWkR1KiE1z8Ztmr7+LING8sEz+cA Y5Eoxr4JXwzVOfuiIpIeEIamhKgnZq3oR370zsTH+0W7Td+sjoaMb4EyRu5BzLOJOX+a q90yTIv7K9aeKQZEyVgn2sB/KT8F/ZcMgtdB8w+UjkDNSpqCL/xGgZxrfdtYF0qO85lw kafQ== X-Gm-Message-State: AAQBX9dKBoymPak4k4PBSeyLoruCnTg4dsrCQBARYRVTHA6i3+wVaStn tn0PZ9ZVvwskjwLnWk1nw4e1NDSYF5o= X-Google-Smtp-Source: AKy350YsucDg8BHeY6V0IoVOR0pulMc5pZV3Sps9tjCFUe04Pre+til+90iCxm0xGbwDMnHRM3ERPECUwNE= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:e67a:98b0:942d:86aa]) (user=drosen job=sendgmr) by 2002:a25:cbd5:0:b0:b95:630:197b with SMTP id b204-20020a25cbd5000000b00b950630197bmr691791ybg.10.1681782071432; Mon, 17 Apr 2023 18:41:11 -0700 (PDT) Date: Mon, 17 Apr 2023 18:40:09 -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-10-drosen@google.com> Subject: [RFC PATCH v3 09/37] fuse-bpf: Add ioctl interface for /dev/fuse 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 Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This introduces an alternative method of responding to fuse requests. Lookups supplying a backing fd or bpf will need to call through the ioctl to ensure there can be no attempts to fool priveledged processes into inadvertantly performing other actions. Signed-off-by: Daniel Rosenberg --- fs/fuse/dev.c | 56 ++++++++++++++++++++++++++++++++------- fs/fuse/fuse_i.h | 1 + include/uapi/linux/fuse.h | 1 + 3 files changed, 48 insertions(+), 10 deletions(-) diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index a3029824c24f..ad7d9d1e6da5 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -1016,18 +1016,19 @@ static int fuse_copy_one(struct fuse_copy_state *cs= , void *val, unsigned size) =20 /* Copy the fuse-bpf lookup args and verify them */ #ifdef CONFIG_FUSE_BPF -static int fuse_copy_lookup(struct fuse_copy_state *cs, void *val, unsigne= d size) +static int fuse_copy_lookup(struct fuse_copy_state *cs, unsigned via_ioctl= , void *val, unsigned size) { struct fuse_bpf_entry_out *fbeo =3D (struct fuse_bpf_entry_out *)val; struct fuse_bpf_entry *feb =3D container_of(fbeo, struct fuse_bpf_entry, = out[0]); int num_entries =3D size / sizeof(*fbeo); int err; =20 - if (size && size % sizeof(*fbeo) !=3D 0) + if (size && (size % sizeof(*fbeo) !=3D 0 || !via_ioctl)) return -EINVAL; =20 if (num_entries > FUSE_BPF_MAX_ENTRIES) return -EINVAL; + err =3D fuse_copy_one(cs, val, size); if (err) return err; @@ -1036,7 +1037,7 @@ static int fuse_copy_lookup(struct fuse_copy_state *c= s, void *val, unsigned size return err; } #else -static int fuse_copy_lookup(struct fuse_copy_state *cs, void *val, unsigne= d size) +static int fuse_copy_lookup(struct fuse_copy_state *cs, unsigned via_ioctl= , void *val, unsigned size) { return fuse_copy_one(cs, val, size); } @@ -1045,7 +1046,7 @@ static int fuse_copy_lookup(struct fuse_copy_state *c= s, void *val, unsigned size /* Copy request arguments to/from userspace buffer */ static int fuse_copy_args(struct fuse_copy_state *cs, unsigned numargs, unsigned argpages, struct fuse_arg *args, - int zeroing, unsigned is_lookup) + int zeroing, unsigned is_lookup, unsigned via_ioct) { int err =3D 0; unsigned i; @@ -1055,7 +1056,7 @@ static int fuse_copy_args(struct fuse_copy_state *cs,= unsigned numargs, if (i =3D=3D numargs - 1 && argpages) err =3D fuse_copy_pages(cs, arg->size, zeroing); else if (i =3D=3D numargs - 1 && is_lookup) - err =3D fuse_copy_lookup(cs, arg->value, arg->size); + err =3D fuse_copy_lookup(cs, via_ioct, arg->value, arg->size); else err =3D fuse_copy_one(cs, arg->value, arg->size); } @@ -1333,7 +1334,7 @@ static ssize_t fuse_dev_do_read(struct fuse_dev *fud,= struct file *file, err =3D fuse_copy_one(cs, &req->in.h, sizeof(req->in.h)); if (!err) err =3D fuse_copy_args(cs, args->in_numargs, args->in_pages, - (struct fuse_arg *) args->in_args, 0, 0); + (struct fuse_arg *) args->in_args, 0, 0, 0); fuse_copy_finish(cs); spin_lock(&fpq->lock); clear_bit(FR_LOCKED, &req->flags); @@ -1872,7 +1873,8 @@ static int copy_out_args(struct fuse_copy_state *cs, = struct fuse_args *args, lastarg->size -=3D diffsize; } return fuse_copy_args(cs, args->out_numargs, args->out_pages, - args->out_args, args->page_zeroing, args->is_lookup); + args->out_args, args->page_zeroing, args->is_lookup, + args->via_ioctl); } =20 /* @@ -1882,7 +1884,7 @@ static int copy_out_args(struct fuse_copy_state *cs, = struct fuse_args *args, * it from the list and copy the rest of the buffer to the request. * The request is finished by calling fuse_request_end(). */ -static ssize_t fuse_dev_do_write(struct fuse_dev *fud, +static ssize_t fuse_dev_do_write(struct fuse_dev *fud, bool from_ioctl, struct fuse_copy_state *cs, size_t nbytes) { int err; @@ -1954,6 +1956,7 @@ static ssize_t fuse_dev_do_write(struct fuse_dev *fud, if (!req->args->page_replace) cs->move_pages =3D 0; =20 + req->args->via_ioctl =3D from_ioctl; if (oh.error) err =3D nbytes !=3D sizeof(oh) ? -EINVAL : 0; else @@ -1992,7 +1995,7 @@ static ssize_t fuse_dev_write(struct kiocb *iocb, str= uct iov_iter *from) =20 fuse_copy_init(&cs, 0, from); =20 - return fuse_dev_do_write(fud, &cs, iov_iter_count(from)); + return fuse_dev_do_write(fud, false, &cs, iov_iter_count(from)); } =20 static ssize_t fuse_dev_splice_write(struct pipe_inode_info *pipe, @@ -2073,7 +2076,7 @@ static ssize_t fuse_dev_splice_write(struct pipe_inod= e_info *pipe, if (flags & SPLICE_F_MOVE) cs.move_pages =3D 1; =20 - ret =3D fuse_dev_do_write(fud, &cs, len); + ret =3D fuse_dev_do_write(fud, false, &cs, len); =20 pipe_lock(pipe); out_free: @@ -2286,6 +2289,33 @@ static int fuse_device_clone(struct fuse_conn *fc, s= truct file *new) return 0; } =20 +// Provides an alternate means to respond to a fuse request +static int fuse_handle_ioc_response(struct fuse_dev *dev, void *buff, uint= 32_t size) +{ + struct fuse_copy_state cs; + struct iovec *iov =3D NULL; + struct iov_iter iter; + int res; + + if (size > PAGE_SIZE) + return -EINVAL; + iov =3D (struct iovec *) __get_free_page(GFP_KERNEL); + if (!iov) + return -ENOMEM; + + iov->iov_base =3D buff; + iov->iov_len =3D size; + + iov_iter_init(&iter, READ, iov, 1, size); + fuse_copy_init(&cs, 0, &iter); + + + res =3D fuse_dev_do_write(dev, true, &cs, size); + free_page((unsigned long) iov); + + return res; +} + static long fuse_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { @@ -2318,6 +2348,12 @@ static long fuse_dev_ioctl(struct file *file, unsign= ed int cmd, } break; default: + if (_IOC_TYPE(cmd) =3D=3D FUSE_DEV_IOC_MAGIC + && _IOC_NR(cmd) =3D=3D _IOC_NR(FUSE_DEV_IOC_BPF_RESPONSE(0)) + && _IOC_DIR(cmd) =3D=3D _IOC_WRITE) { + res =3D fuse_handle_ioc_response(fuse_get_dev(file), (void *) arg, _IOC= _SIZE(cmd)); + break; + } res =3D -ENOTTY; break; } diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index c24878f4a89f..39a9fdf2a752 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -316,6 +316,7 @@ struct fuse_args { bool may_block:1; bool is_ext:1; bool is_lookup:1; + bool via_ioctl:1; struct fuse_in_arg in_args[3]; struct fuse_arg out_args[2]; void (*end)(struct fuse_mount *fm, struct fuse_args *args, int error); diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h index 04d96f34e9a1..3ad725a3e968 100644 --- a/include/uapi/linux/fuse.h +++ b/include/uapi/linux/fuse.h @@ -1012,6 +1012,7 @@ struct fuse_notify_retrieve_in { /* Device ioctls: */ #define FUSE_DEV_IOC_MAGIC 229 #define FUSE_DEV_IOC_CLONE _IOR(FUSE_DEV_IOC_MAGIC, 0, uint32_t) +#define FUSE_DEV_IOC_BPF_RESPONSE(N) _IOW(FUSE_DEV_IOC_MAGIC, 125, char[N]) =20 struct fuse_lseek_in { uint64_t fh; --=20 2.40.0.634.g4ca3ef3211-goog From nobody Wed Dec 17 19:22:07 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 E1BD9C77B7E for ; Tue, 18 Apr 2023 01:42:33 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230516AbjDRBmc (ORCPT ); Mon, 17 Apr 2023 21:42:32 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:54334 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230253AbjDRBlp (ORCPT ); Mon, 17 Apr 2023 21:41:45 -0400 Received: from mail-yb1-xb4a.google.com (mail-yb1-xb4a.google.com [IPv6:2607:f8b0:4864:20::b4a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 621416EBD for ; Mon, 17 Apr 2023 18:41:14 -0700 (PDT) Received: by mail-yb1-xb4a.google.com with SMTP id 188-20020a2504c5000000b00b8f6f5dca5dso8197857ybe.7 for ; Mon, 17 Apr 2023 18:41:14 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20221208; t=1681782073; x=1684374073; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=k+WSpIn3NUEIdVcNWDxlTZBL9IhEMs5qHwjDTSF5QyA=; b=gnEAvtWYbBPrlRDvmMhwWHAU4VOPAZNhzEbReBa4tJPpc5X3cQyQnOkcj+IZxU+jtd Q+YCDhkPu6ZImWiqrO7KC2hZnhfm/LoPf2Qk+B0v/m+gmZs6ydm6J9MF3xNYweVlnDD8 kzJrv+qZegriRdHZmr6G27dsKx70i7W4bP34J1/ZwAQen87rca+5p+yPyWMoLaX9Dau9 nVVsiKpQ1gJzqgxY8b3BMTPSECOFqN5iCxmNf5WcJMJVHe+OKuTEuYOpIO0xE/kXViUv CKddSMZuirEzlq3i+5fDaLh4bYaIBJcIpoMpmtH8zmyBRsHn6nqpB1ZlzCdIk6aTFlgp Gt8Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681782073; x=1684374073; 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=k+WSpIn3NUEIdVcNWDxlTZBL9IhEMs5qHwjDTSF5QyA=; b=PgHZJomjTqVBKxl9P8viDVYy1RVNc1wmmzf0WJrmmd2ZjXXGQ9Y5/EaBM+Gg7PJPze 824rWdUImBbGFBI38bki0utX6GyHtyPorAVc1xpftHvD6cowOp1EAqKyNuVPR+Ft0Nf5 9X2okA6rZvpykAn0kzckSx/7HqJq5GgG4dISvOIuJa/hty5w3J6tWZBtujc2/epyk+pD WIaLklE6GqCHvzrCfgHfo+uV/CkYJ4gCc9D94fgGD5mvCmyRa1DJQa2nO6WI84A3oVF+ F9vKihFPM7IWqPCF3DfKTtsZRCuBJg3CLbIVxPl71j0UD2mbXi1K1/CknMdHvbpVh0rP yfAg== X-Gm-Message-State: AAQBX9cLZWuPDUAXdoo0ywAzQANVL0az8+GCenmaDx7YZ9fby2hVCwx5 EBiPki2rZ3a8ZdsYg7HrHkl9i1qGeMs= X-Google-Smtp-Source: AKy350ZEiZ4KiwaDQvoDBVRd0pyWtBmDHMYqWI/Cx2Am4cudHmQsbbtrl8pLWWl9gfM9hNxwPXm0gu6TXWo= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:e67a:98b0:942d:86aa]) (user=drosen job=sendgmr) by 2002:a25:cfcc:0:b0:b78:1b26:a642 with SMTP id f195-20020a25cfcc000000b00b781b26a642mr8808364ybg.1.1681782073411; Mon, 17 Apr 2023 18:41:13 -0700 (PDT) Date: Mon, 17 Apr 2023 18:40:10 -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-11-drosen@google.com> Subject: [RFC PATCH v3 10/37] fuse-bpf: Don't support export_operations 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 Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" In the future, we may choose to support these, but it poses some challenges. In order to create a disconnected dentry/inode, we'll need to encode the mountpoint and bpf into the file_handle, which means we'd need a stable representation of them. This also won't hold up to cases where the bpf is not stateless. One possibility is registering bpf programs and mounts in a specific order, so they can be assigned consistent ids we can use in the file_handle. We can defer to the lower filesystem for the lower inode's representation in the file_handle. Signed-off-by: Daniel Rosenberg --- fs/fuse/inode.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index b71e8758fab5..fe80984f099a 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -1107,6 +1107,14 @@ static int fuse_encode_fh(struct inode *inode, u32 *= fh, int *max_len, nodeid =3D get_fuse_inode(inode)->nodeid; generation =3D inode->i_generation; =20 +#ifdef CONFIG_FUSE_BPF + /* TODO: Does it make sense to support this in some cases? */ + if (!nodeid && get_fuse_inode(inode)->backing_inode) { + *max_len =3D 0; + return FILEID_INVALID; + } +#endif + fh[0] =3D (u32)(nodeid >> 32); fh[1] =3D (u32)(nodeid & 0xffffffff); fh[2] =3D generation; --=20 2.40.0.634.g4ca3ef3211-goog From nobody Wed Dec 17 19:22:07 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 6FE4FC77B72 for ; Tue, 18 Apr 2023 01:42:36 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230521AbjDRBme (ORCPT ); Mon, 17 Apr 2023 21:42:34 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53892 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230340AbjDRBl6 (ORCPT ); Mon, 17 Apr 2023 21:41:58 -0400 Received: from mail-yb1-xb49.google.com (mail-yb1-xb49.google.com [IPv6:2607:f8b0:4864:20::b49]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 6F425659F for ; Mon, 17 Apr 2023 18:41:18 -0700 (PDT) Received: by mail-yb1-xb49.google.com with SMTP id 188-20020a250ac5000000b00b9265c9a5e9so2183164ybk.11 for ; Mon, 17 Apr 2023 18:41:18 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20221208; t=1681782075; x=1684374075; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=1Zzn8o1i1Qipa77dzB0ZPLXp5K2/As2sG8h87DoyjGM=; b=2+I98OIJuVmlxxZnL112cFQtwT3jCQDjIow9U7tinQ0J7PnT7CW2nk5rnzuaqyfrh7 t3DlfVAZR6sD5E+Luhvth8wbofyEZeHmBpkUkBocSpBEVttlXwuggShqUO9kj7Yq0/h/ aX7QFH/O8g9rnwLpOW9lAXc/V85QKHAhuJlpvE7i1s3Gysd2bhwkmvNvqQ40s9IHS+fW iwFwXKp/SUQPTFNVB9+Xbi1KWg/VuO0heZfOSN2cvyCPhvHY7TR+CKbNpElBlFthzn2Y NCkq3jwcH9x/45VO8uba3X5M5Kc8fLUu98eMXLHD5qQZ70F2CYgsgu/Pb70DPzEmoCIC +4Yw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681782075; x=1684374075; 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=1Zzn8o1i1Qipa77dzB0ZPLXp5K2/As2sG8h87DoyjGM=; b=bD75M60ySuLD9mypLf2MES9z7VbDqFfxlcYNDondj8yseEWVn4Ym2J+KU1ZcRr5STw qvquhfEYxYaJdzvoEbzDWuE7EnQSNHapw1D6qxBD2GeCzvQct1MJLh7HcTzLijard071 GSrvz+EtE2fyKtnn1/6u8F/EDGvInJvm3vBEhjXaJXKyAC0pXIYCGXaQk8hv8ZN2wNP0 h1DmcObx5kvKTNaY+ndaP75wLr5/0/zx8KUnHyyuA1iJid7RhSZ2WvRJ7NiuP61a3d5D nLkLMcfVqJbhdDfIOhg4zfPUX/2ySowZU4WCoMhn94TVG9kyTBXWkdcAnp5tyf2iG8KM x7qA== X-Gm-Message-State: AAQBX9cFikqnJBOM2ZdS/yNKydsLWBoXUQ4abZ6CUssXmTZSfsXcDXxp Ijy/xUdlL4+E3g4vSxUdXA6WZtJtkjI= X-Google-Smtp-Source: AKy350a3U2lTLEFmBlzns8CqFoaKv7hHvFxu3WPdR2gkRwnkhckHyeUhIddcfAh3AVrbzcBi92gpNo88opU= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:e67a:98b0:942d:86aa]) (user=drosen job=sendgmr) by 2002:a25:74d0:0:b0:b92:25a0:90d with SMTP id p199-20020a2574d0000000b00b9225a0090dmr5872731ybc.0.1681782075783; Mon, 17 Apr 2023 18:41:15 -0700 (PDT) Date: Mon, 17 Apr 2023 18:40:11 -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-12-drosen@google.com> Subject: [RFC PATCH v3 11/37] fuse-bpf: Add support for access 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 Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This adds backing support for FUSE_ACCESS Signed-off-by: Daniel Rosenberg Signed-off-by: Paul Lawrence --- fs/fuse/backing.c | 47 +++++++++++++++++++++++++++++++++++++++++++++++ fs/fuse/dir.c | 6 ++++++ fs/fuse/fuse_i.h | 6 ++++++ 3 files changed, 59 insertions(+) diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index 3d895957b5ce..e42622584037 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -417,3 +417,50 @@ int fuse_revalidate_backing(struct dentry *entry, unsi= gned int flags) return backing_entry->d_op->d_revalidate(backing_entry, flags); return 1; } + +static int fuse_access_initialize_in(struct bpf_fuse_args *fa, struct fuse= _access_in *in, + struct inode *inode, int mask) +{ + *in =3D (struct fuse_access_in) { + .mask =3D mask, + }; + + *fa =3D (struct bpf_fuse_args) { + .info =3D (struct bpf_fuse_meta_info) { + .opcode =3D FUSE_ACCESS, + .nodeid =3D get_node_id(inode), + }, + .in_numargs =3D 1, + .in_args[0].size =3D sizeof(*in), + .in_args[0].value =3D in, + }; + + return 0; +} + +static int fuse_access_initialize_out(struct bpf_fuse_args *fa, struct fus= e_access_in *in, + struct inode *inode, int mask) +{ + return 0; +} + +static int fuse_access_backing(struct bpf_fuse_args *fa, int *out, struct = inode *inode, int mask) +{ + struct fuse_inode *fi =3D get_fuse_inode(inode); + const struct fuse_access_in *fai =3D fa->in_args[0].value; + + *out =3D inode_permission(&nop_mnt_idmap, fi->backing_inode, fai->mask); + return 0; +} + +static int fuse_access_finalize(struct bpf_fuse_args *fa, int *out, struct= inode *inode, int mask) +{ + return 0; +} + +int fuse_bpf_access(int *out, struct inode *inode, int mask) +{ + return bpf_fuse_backing(inode, struct fuse_access_in, out, + fuse_access_initialize_in, fuse_access_initialize_out, + fuse_access_backing, fuse_access_finalize, inode, mask); +} diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 73ebe3498fb9..535e6cf9e970 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1439,6 +1439,9 @@ static int fuse_access(struct inode *inode, int mask) struct fuse_access_in inarg; int err; =20 + if (fuse_bpf_access(&err, inode, mask)) + return err; + BUG_ON(mask & MAY_NOT_BLOCK); =20 if (fm->fc->no_access) @@ -1495,6 +1498,9 @@ static int fuse_permission(struct mnt_idmap *idmap, if (!fuse_allow_current_process(fc)) return -EACCES; =20 + if (fuse_bpf_access(&err, inode, mask)) + return err; + /* * If attributes are needed, refresh them before proceeding */ diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 39a9fdf2a752..cb166168f9c2 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1406,6 +1406,7 @@ int parse_fuse_bpf_entry(struct fuse_bpf_entry *fbe, = int num_entries); #ifdef CONFIG_FUSE_BPF =20 int fuse_bpf_lookup(struct dentry **out, struct inode *dir, struct dentry = *entry, unsigned int flags); +int fuse_bpf_access(int *out, struct inode *inode, int mask); =20 #else =20 @@ -1414,6 +1415,11 @@ static inline int fuse_bpf_lookup(struct dentry **ou= t, struct inode *dir, struct return 0; } =20 +static inline int fuse_bpf_access(int *out, struct inode *inode, int mask) +{ + return 0; +} + #endif // CONFIG_FUSE_BPF =20 int fuse_handle_backing(struct fuse_bpf_entry *feb, struct path *backing_p= ath); --=20 2.40.0.634.g4ca3ef3211-goog From nobody Wed Dec 17 19:22:07 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 8FBA7C77B7E for ; Tue, 18 Apr 2023 01:42:45 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231143AbjDRBmo (ORCPT ); Mon, 17 Apr 2023 21:42:44 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53752 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230072AbjDRBl7 (ORCPT ); Mon, 17 Apr 2023 21:41:59 -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 8AC9A61A7 for ; Mon, 17 Apr 2023 18:41:19 -0700 (PDT) Received: by mail-yw1-x1149.google.com with SMTP id 00721157ae682-54faf2e22afso139555587b3.7 for ; Mon, 17 Apr 2023 18:41:19 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20221208; t=1681782078; x=1684374078; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=oN6x+PcjBowjEB90+rsteKN+eOkqOSAMj6ggqH7i4oE=; b=e2l+365Y5NfwgrmJ3glTREcW5dUMa28BQ3rkaEXIxzCaUVeE1IhaW2auQ6TAoZSlTV 2DPfi5gIQFGcRKYkma2DV8pvrKv+CMBRJ5PqPI/sdCdJlujipWSi8TX5fpC7CIATr/wd IZ08/Y5ullb70ZOKJIK0Fa3l1w37fzqtYaUA2LBOQ+U08RbTKlpgXJi+kOkZuc5emqyT txsRKHpNndZBshfMylH2G+PKKT4M75pZSQdvHa69EiYZG+SqYwtca9kBbOLbesWQNZOV ScLqr6LEl57hIDUX6HFB91pq60LCptprJlfycy0AaC9T8kF5M0PJh2968Q/Fdg6j+/Y2 nZBA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681782078; x=1684374078; 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=oN6x+PcjBowjEB90+rsteKN+eOkqOSAMj6ggqH7i4oE=; b=aXqCZNInJZsck6XY6VvJHtj05BbvRWrdP/FyjWItdVMxQaIRSB5Wh8AnRoBGxQpdOx 2L/N6PaZ+dRMBNvpArQ/fDpEum0j+sOx0/DJ7kAn+JC5QUpuZbZ25rS8pUIpGSvVW23y IWU9trc4HEOSze5zyO6w81tTIOiMrg2my8/AoftoBvfWn7PQV9PA71YubfL7iYdxLKph vfaovo95SFvkTbykSZmLBBIzThC5G5W7P7IogKvHLofBUluU75C1gL93sfpgrGvRd0ac gLuWy0MkAPkZr1mAkGNGIkp1meTk/NevWaUp5xIeeBZ2uawaxU7hzVtL+iumeYH3yMXL RGNw== X-Gm-Message-State: AAQBX9fZddaDRpbGslANUVzUe7GbQ4E8C3KTth1NZtSqherQ7+rBpBxy 7OQs93UfLlF/6CPC7VzW9EEV2ylzXQc= X-Google-Smtp-Source: AKy350ZlkcIB5L39o8Rx8chg+be3fQChvnEEvP0sguYtyhqx+uHhdIzeUdU455ATM/2zMsWmD3vY1g4K4MY= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:e67a:98b0:942d:86aa]) (user=drosen job=sendgmr) by 2002:a25:74d4:0:b0:b8e:cb88:1b8a with SMTP id p203-20020a2574d4000000b00b8ecb881b8amr10919222ybc.8.1681782078105; Mon, 17 Apr 2023 18:41:18 -0700 (PDT) Date: Mon, 17 Apr 2023 18:40:12 -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-13-drosen@google.com> Subject: [RFC PATCH v3 12/37] fuse-bpf: Partially add mapping support 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 Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This adds a backing implementation for mapping, but is not currently hooked into the infrastructure that will call the bpf programs. Signed-off-by: Daniel Rosenberg Signed-off-by: Paul Lawrence --- fs/fuse/backing.c | 37 +++++++++++++++++++++++++++++++++++++ fs/fuse/file.c | 6 ++++++ fs/fuse/fuse_i.h | 4 +++- 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index e42622584037..930aa370e376 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -207,6 +207,43 @@ static void fuse_stat_to_attr(struct fuse_conn *fc, st= ruct inode *inode, attr->blksize =3D 1 << blkbits; } =20 +ssize_t fuse_backing_mmap(struct file *file, struct vm_area_struct *vma) +{ + int ret; + struct fuse_file *ff =3D file->private_data; + struct inode *fuse_inode =3D file_inode(file); + struct file *backing_file =3D ff->backing_file; + struct inode *backing_inode =3D file_inode(backing_file); + + if (!backing_file->f_op->mmap) + return -ENODEV; + + if (WARN_ON(file !=3D vma->vm_file)) + return -EIO; + + vma->vm_file =3D get_file(backing_file); + + ret =3D call_mmap(vma->vm_file, vma); + + if (ret) + fput(backing_file); + else + fput(file); + + if (file->f_flags & O_NOATIME) + return ret; + + if ((!timespec64_equal(&fuse_inode->i_mtime, &backing_inode->i_mtime) || + !timespec64_equal(&fuse_inode->i_ctime, + &backing_inode->i_ctime))) { + fuse_inode->i_mtime =3D backing_inode->i_mtime; + fuse_inode->i_ctime =3D backing_inode->i_ctime; + } + touch_atime(&file->f_path); + + return ret; +} + /*************************************************************************= ****** * Directory operations after here = * *************************************************************************= *****/ diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 25fb49f0a9f7..865167a80d35 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -2527,6 +2527,12 @@ static int fuse_file_mmap(struct file *file, struct = vm_area_struct *vma) if (FUSE_IS_DAX(file_inode(file))) return fuse_dax_mmap(file, vma); =20 +#ifdef CONFIG_FUSE_BPF + /* TODO - this is simply passthrough, not a proper BPF filter */ + if (ff->backing_file) + return fuse_backing_mmap(file, vma); +#endif + if (ff->open_flags & FOPEN_DIRECT_IO) { /* Can't provide the coherency needed for MAP_SHARED */ if (vma->vm_flags & VM_MAYSHARE) diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index cb166168f9c2..5eb357f482dc 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1422,7 +1422,9 @@ static inline int fuse_bpf_access(int *out, struct in= ode *inode, int mask) =20 #endif // CONFIG_FUSE_BPF =20 -int fuse_handle_backing(struct fuse_bpf_entry *feb, struct path *backing_p= ath); +ssize_t fuse_backing_mmap(struct file *file, struct vm_area_struct *vma); + +int fuse_handle_backing(struct fuse_bpf_entry *fbe, struct path *backing_p= ath); =20 int fuse_revalidate_backing(struct dentry *entry, unsigned int flags); =20 --=20 2.40.0.634.g4ca3ef3211-goog From nobody Wed Dec 17 19:22:07 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 7F605C77B75 for ; Tue, 18 Apr 2023 01:42:58 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231163AbjDRBm4 (ORCPT ); Mon, 17 Apr 2023 21:42:56 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53844 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230373AbjDRBmQ (ORCPT ); Mon, 17 Apr 2023 21:42:16 -0400 Received: from mail-yb1-xb49.google.com (mail-yb1-xb49.google.com [IPv6:2607:f8b0:4864:20::b49]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 84D217AAE for ; Mon, 17 Apr 2023 18:41:23 -0700 (PDT) Received: by mail-yb1-xb49.google.com with SMTP id 132-20020a250c8a000000b00b8f4e12dd57so13411469ybm.1 for ; Mon, 17 Apr 2023 18:41:23 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20221208; t=1681782080; x=1684374080; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=NYL4IdJjh78Mx0ERjHXq1HM7WDhJyXfUI9ZLvdFWk98=; b=gDPc8KFBWBSDxYuCP48vLLO3Vue1Z2AubLCuuloL4zyW7pB2dhVnlUg0914m1YDuUd 6Ne1KGXCk4u/o9jyWFV2XXPimdtwupUUcvgkfbZVD+J5Tucff+wx4eEkpGMKftKAyHVg ggIWdp57XhPiL081zazQF4KJrhUtgo1n62jsm/nCPnSpTOEEa0QdfXLnA7/I0/JPpbtE VeS6W6NpNQNbK8tDqcm+zMNAS2xTiCwbBK1HqQrGP1vXMzw3/f3jYAJNVlOzDCvIkqDR jKpbPZPCr3xy77kbdxnHzURMl+sPneGV3POnYfCtJgR+D28OHQNxJkj6v0N/ovT13U9O wz4w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681782080; x=1684374080; 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=NYL4IdJjh78Mx0ERjHXq1HM7WDhJyXfUI9ZLvdFWk98=; b=M/ZyxA11jmAWvNaj5V6cQ8NKKap4uhKW9vNkrVVanxv+71cRwDGIJ+ozEkwk/3uZB0 wKzayWHv5brrD6ut4wEHQg7pwoyah/8Z2idjzgB2REymP8kB60ZlI2fKefYgn9830ekq pOTYGwzbUD2R+2QXJ5xxjVIKiH8VbOtX4BrAmVrJPtOiV6xGDfpzHzJ1+Dm6LxeRaJ5x cHmgvJ3P8sDCcbyt7EQX+KpwMWLqNWZSYGeFG7vjLDLi9m5oiYIuatE1de/3REwfvCpQ YB/e6/31fibpCi+KB20o+b3Vi6v+zU547giBX1OpRpnFomI8ekWQATYA0kmT1rLFWneC QYUQ== X-Gm-Message-State: AAQBX9cfjqcSimjALmfB3L/8vW8RRKqy4KbwKu3kT/8bnhRYRh3A++qw lP1Ovx0YY2yPVNJMnqzF8KSJ+iLDY8s= X-Google-Smtp-Source: AKy350YBlKkuvX66p/6m/H2JvUtFIa2769A+526n9Y5/glS4dC9W1cB3cMRNvaXNmAOriHpO5bnUIntTZ3U= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:e67a:98b0:942d:86aa]) (user=drosen job=sendgmr) by 2002:a0d:ec02:0:b0:54c:2723:560d with SMTP id q2-20020a0dec02000000b0054c2723560dmr10854311ywn.3.1681782080382; Mon, 17 Apr 2023 18:41:20 -0700 (PDT) Date: Mon, 17 Apr 2023 18:40:13 -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-14-drosen@google.com> Subject: [RFC PATCH v3 13/37] fuse-bpf: Add lseek support 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 Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This adds backing support for FUSE_LSEEK Signed-off-by: Daniel Rosenberg Signed-off-by: Paul Lawrence --- fs/fuse/backing.c | 89 +++++++++++++++++++++++++++++++++++++++++++++++ fs/fuse/file.c | 3 ++ fs/fuse/fuse_i.h | 6 ++++ 3 files changed, 98 insertions(+) diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index 930aa370e376..c4916dde48c8 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -207,6 +207,95 @@ static void fuse_stat_to_attr(struct fuse_conn *fc, st= ruct inode *inode, attr->blksize =3D 1 << blkbits; } =20 +struct fuse_lseek_args { + struct fuse_lseek_in in; + struct fuse_lseek_out out; +}; + +static int fuse_lseek_initialize_in(struct bpf_fuse_args *fa, struct fuse_= lseek_args *args, + struct file *file, loff_t offset, int whence) +{ + struct fuse_file *fuse_file =3D file->private_data; + + args->in =3D (struct fuse_lseek_in) { + .fh =3D fuse_file->fh, + .offset =3D offset, + .whence =3D whence, + }; + + *fa =3D (struct bpf_fuse_args) { + .info =3D (struct bpf_fuse_meta_info) { + .nodeid =3D get_node_id(file->f_inode), + .opcode =3D FUSE_LSEEK, + }, + .in_numargs =3D 1, + .in_args[0].size =3D sizeof(args->in), + .in_args[0].value =3D &args->in, + }; + + return 0; +} + +static int fuse_lseek_initialize_out(struct bpf_fuse_args *fa, struct fuse= _lseek_args *args, + struct file *file, loff_t offset, int whence) +{ + fa->out_numargs =3D 1; + fa->out_args[0].size =3D sizeof(args->out); + fa->out_args[0].value =3D &args->out; + + return 0; +} + +static int fuse_lseek_backing(struct bpf_fuse_args *fa, loff_t *out, + struct file *file, loff_t offset, int whence) +{ + const struct fuse_lseek_in *fli =3D fa->in_args[0].value; + struct fuse_lseek_out *flo =3D fa->out_args[0].value; + struct fuse_file *fuse_file =3D file->private_data; + struct file *backing_file =3D fuse_file->backing_file; + + /* TODO: Handle changing of the file handle */ + if (offset =3D=3D 0) { + if (whence =3D=3D SEEK_CUR) { + flo->offset =3D file->f_pos; + *out =3D flo->offset; + return 0; + } + + if (whence =3D=3D SEEK_SET) { + flo->offset =3D vfs_setpos(file, 0, 0); + *out =3D flo->offset; + return 0; + } + } + + inode_lock(file->f_inode); + backing_file->f_pos =3D file->f_pos; + *out =3D vfs_llseek(backing_file, fli->offset, fli->whence); + flo->offset =3D *out; + inode_unlock(file->f_inode); + return 0; +} + +static int fuse_lseek_finalize(struct bpf_fuse_args *fa, loff_t *out, + struct file *file, loff_t offset, int whence) +{ + struct fuse_lseek_out *flo =3D fa->out_args[0].value; + + if (!fa->info.error_in) + file->f_pos =3D flo->offset; + *out =3D flo->offset; + return 0; +} + +int fuse_bpf_lseek(loff_t *out, struct inode *inode, struct file *file, lo= ff_t offset, int whence) +{ + return bpf_fuse_backing(inode, struct fuse_lseek_args, out, + fuse_lseek_initialize_in, fuse_lseek_initialize_out, + fuse_lseek_backing, fuse_lseek_finalize, + file, offset, whence); +} + ssize_t fuse_backing_mmap(struct file *file, struct vm_area_struct *vma) { int ret; diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 865167a80d35..9758bd1665a6 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -2779,6 +2779,9 @@ static loff_t fuse_file_llseek(struct file *file, lof= f_t offset, int whence) loff_t retval; struct inode *inode =3D file_inode(file); =20 + if (fuse_bpf_lseek(&retval, inode, file, offset, whence)) + return retval; + switch (whence) { case SEEK_SET: case SEEK_CUR: diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 5eb357f482dc..624d0cebd287 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1405,11 +1405,17 @@ int parse_fuse_bpf_entry(struct fuse_bpf_entry *fbe= , int num_entries); =20 #ifdef CONFIG_FUSE_BPF =20 +int fuse_bpf_lseek(loff_t *out, struct inode *inode, struct file *file, lo= ff_t offset, int whence); int fuse_bpf_lookup(struct dentry **out, struct inode *dir, struct dentry = *entry, unsigned int flags); int fuse_bpf_access(int *out, struct inode *inode, int mask); =20 #else =20 +static inline int fuse_bpf_lseek(loff_t *out, struct inode *inode, struct = file *file, loff_t offset, int whence) +{ + return 0; +} + static inline int fuse_bpf_lookup(struct dentry **out, struct inode *dir, = struct dentry *entry, unsigned int flags) { return 0; --=20 2.40.0.634.g4ca3ef3211-goog From nobody Wed Dec 17 19:22:07 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 0D684C77B75 for ; Tue, 18 Apr 2023 01:43:01 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231164AbjDRBm7 (ORCPT ); Mon, 17 Apr 2023 21:42:59 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53736 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230493AbjDRBmR (ORCPT ); Mon, 17 Apr 2023 21:42:17 -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 6F5C1658E for ; Mon, 17 Apr 2023 18:41:25 -0700 (PDT) Received: by mail-yw1-x1149.google.com with SMTP id 00721157ae682-54f69fb5cafso190546727b3.12 for ; Mon, 17 Apr 2023 18:41:25 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20221208; t=1681782082; x=1684374082; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=XffErqhYI2OU7fEkAttVu8sFpvDYdEUvBhfwhueehVM=; b=W64W7LYmODf/FRuZNRmhqRfU8jwPzAf2EgQjFr/GwQnfixQx1lnFWxTQHQr2157bLk bOZx0B3xbbAKBKptnu3DGWAHW7YUWHUgXmFNH0B57uqjJVKJbfuIf6siNBmXF+8KrPKl rq/fklWlDzNR/LfPz5La++65odFf+sqMFipQNJ9fbMo8fpXLvw0QrXhDgD89PZ+Bh4Cv ClbkWZWwvPPc53kzJNCAMgQof+PQW1mdRELbdWXfLwzm+gUieIJC3zqNnbis5j2Pkdx0 ai+ArKZhDaSGmGokepURyuQtc1NoAeDczoa0Mwj+K+es3hQOC2CkYjntXKPcSV6FWNzd ApiQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681782082; x=1684374082; 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=XffErqhYI2OU7fEkAttVu8sFpvDYdEUvBhfwhueehVM=; b=H2q1jvRuoH+JkPogl5mIix2J1NYNcOYyUmdHriyttUfUIztDZy/vMDiC8ozDs3phON 2ukz0aZVoCh8TYrBiWAqkir9zF1BbXZ6QUyj2DjXDcbMykc34XuW6Ripfy7rQgyJJ2BD U40fsSwJRfOXQlNGo/LjRp/7Nz4GatIrPHT4E1EETrY1I8WmYc83u29Z5ZAewKVWQuBz IWFZ3HXzTef9slGziJp0fd/9cfL0LZL4eG0awQSXnII4u4QT1SB5AAPfXd81XYmCwBLn fIZU/BOZPcgXM5CTTQeD7gIwlynRlsvtSn92WcdfZft/tx9L4dPGZYNTcRmIMbSl1UK+ 3wsg== X-Gm-Message-State: AAQBX9dR4HFzKvdX9R6FzpFWZzzocHsDzuTZfroN3voXr88Xh01chC4F DVG50WRRYRmwmnb2/6Y4f6KMasai7UM= X-Google-Smtp-Source: AKy350bK1wIQRL+1GOlGecQQ1VG4drCo01/C/N6m/UsccfrVXFYrTKOINwumV372LVk08vQB5twTN47yWjc= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:e67a:98b0:942d:86aa]) (user=drosen job=sendgmr) by 2002:a81:b303:0:b0:545:1d7f:abfe with SMTP id r3-20020a81b303000000b005451d7fabfemr10434358ywh.7.1681782082574; Mon, 17 Apr 2023 18:41:22 -0700 (PDT) Date: Mon, 17 Apr 2023 18:40:14 -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-15-drosen@google.com> Subject: [RFC PATCH v3 14/37] fuse-bpf: Add support for fallocate 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 Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This adds backing support for FUSE_FALLOCATE Signed-off-by: Daniel Rosenberg Signed-off-by: Paul Lawrence --- fs/fuse/backing.c | 60 +++++++++++++++++++++++++++++++++++++++++++++++ fs/fuse/file.c | 3 +++ fs/fuse/fuse_i.h | 6 +++++ 3 files changed, 69 insertions(+) diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index c4916dde48c8..ee315598bc3f 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -333,6 +333,66 @@ ssize_t fuse_backing_mmap(struct file *file, struct vm= _area_struct *vma) return ret; } =20 +static int fuse_file_fallocate_initialize_in(struct bpf_fuse_args *fa, + struct fuse_fallocate_in *in, + struct file *file, int mode, loff_t offset, loff_t length) +{ + struct fuse_file *ff =3D file->private_data; + + *in =3D (struct fuse_fallocate_in) { + .fh =3D ff->fh, + .offset =3D offset, + .length =3D length, + .mode =3D mode, + }; + + *fa =3D (struct bpf_fuse_args) { + .info =3D (struct bpf_fuse_meta_info) { + .opcode =3D FUSE_FALLOCATE, + .nodeid =3D ff->nodeid, + }, + .in_numargs =3D 1, + .in_args[0].size =3D sizeof(*in), + .in_args[0].value =3D in, + }; + + return 0; +} + +static int fuse_file_fallocate_initialize_out(struct bpf_fuse_args *fa, + struct fuse_fallocate_in *in, + struct file *file, int mode, loff_t offset, loff_t length) +{ + return 0; +} + +static int fuse_file_fallocate_backing(struct bpf_fuse_args *fa, int *out, + struct file *file, int mode, loff_t offset, loff_t length) +{ + const struct fuse_fallocate_in *ffi =3D fa->in_args[0].value; + struct fuse_file *ff =3D file->private_data; + + *out =3D vfs_fallocate(ff->backing_file, ffi->mode, ffi->offset, + ffi->length); + return 0; +} + +static int fuse_file_fallocate_finalize(struct bpf_fuse_args *fa, int *out, + struct file *file, int mode, loff_t offset, loff_t length) +{ + return 0; +} + +int fuse_bpf_file_fallocate(int *out, struct inode *inode, struct file *fi= le, int mode, loff_t offset, loff_t length) +{ + return bpf_fuse_backing(inode, struct fuse_fallocate_in, out, + fuse_file_fallocate_initialize_in, + fuse_file_fallocate_initialize_out, + fuse_file_fallocate_backing, + fuse_file_fallocate_finalize, + file, mode, offset, length); +} + /*************************************************************************= ****** * Directory operations after here = * *************************************************************************= *****/ diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 9758bd1665a6..58cff04660db 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -3071,6 +3071,9 @@ static long fuse_file_fallocate(struct file *file, in= t mode, loff_t offset, (!(mode & FALLOC_FL_KEEP_SIZE) || (mode & (FALLOC_FL_PUNCH_HOLE | FALLOC_FL_ZERO_RANGE))); =20 + if (fuse_bpf_file_fallocate(&err, inode, file, mode, offset, length)) + return err; + if (mode & ~(FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE | FALLOC_FL_ZERO_RANGE)) return -EOPNOTSUPP; diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 624d0cebd287..1dd9cc9720df 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1406,6 +1406,7 @@ int parse_fuse_bpf_entry(struct fuse_bpf_entry *fbe, = int num_entries); #ifdef CONFIG_FUSE_BPF =20 int fuse_bpf_lseek(loff_t *out, struct inode *inode, struct file *file, lo= ff_t offset, int whence); +int fuse_bpf_file_fallocate(int *out, struct inode *inode, struct file *fi= le, int mode, loff_t offset, loff_t length); int fuse_bpf_lookup(struct dentry **out, struct inode *dir, struct dentry = *entry, unsigned int flags); int fuse_bpf_access(int *out, struct inode *inode, int mask); =20 @@ -1416,6 +1417,11 @@ static inline int fuse_bpf_lseek(loff_t *out, struct= inode *inode, struct file * return 0; } =20 +static inline int fuse_bpf_file_fallocate(int *out, struct inode *inode, s= truct file *file, int mode, loff_t offset, loff_t length) +{ + return 0; +} + static inline int fuse_bpf_lookup(struct dentry **out, struct inode *dir, = struct dentry *entry, unsigned int flags) { return 0; --=20 2.40.0.634.g4ca3ef3211-goog From nobody Wed Dec 17 19:22:07 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 9983CC77B7E for ; Tue, 18 Apr 2023 01:43:19 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230105AbjDRBnR (ORCPT ); Mon, 17 Apr 2023 21:43:17 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:54366 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230513AbjDRBmT (ORCPT ); Mon, 17 Apr 2023 21:42:19 -0400 Received: from mail-yb1-xb4a.google.com (mail-yb1-xb4a.google.com [IPv6:2607:f8b0:4864:20::b4a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id D0D1C6E9A for ; Mon, 17 Apr 2023 18:41:28 -0700 (PDT) Received: by mail-yb1-xb4a.google.com with SMTP id j193-20020a2523ca000000b00b8f6b82ec94so9094765ybj.18 for ; Mon, 17 Apr 2023 18:41:28 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20221208; t=1681782085; x=1684374085; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=EclmRuCEs8AtdYsWbPtv7S1s8N7D3XoEPaguP9KMkfI=; b=mmwYVHjRAK5Z159C4imU0XQjkYtfnCBYjv6l4C4bOCpMXKwBtApNX02vwb7wGKRz4N 5XN3HQ4NzLI38FzrpsJ+pijncme7++sNr3Lhf5SYjX2+5hrrxG0qic8LTCsLQ/L3V1tl ZnXYn62ItIO97Ypuoa624x4qnYf6vElPXxFcygSYU7x1tbJdV1rnJIz2kFBABcJUHC/R ODYpnlJdo3GOx6g8lnndopkLGXfrP5bGFTxF2LWebBVsKgZtSOfuB1fZS4av1rYR4v8d dXRXapxs4gY9IzCAWPPhicID2CTLtG3lyLn96zZ5kfplK6rLfb34n9wqbKDNzu3jXRVL DyFQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681782085; x=1684374085; 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=EclmRuCEs8AtdYsWbPtv7S1s8N7D3XoEPaguP9KMkfI=; b=gqQR1a75mVSzecRZ1mgYJEZgyyVrNHzovVLe8EG+gHdoD2HtCVOkrNzQ94yRm2qdzQ ZlmALhr+xiNPILUYEjGVxpIcV1YagB0xnMBVNuy7IsP575m6nmClXoBbVWYmSU0wm8PE 6d7mIVhqqbtD0iQJ2Fg26vwh2JUfWeqe8dMbnAqJqT53YYzk4THiZJlb3fUdFchSyI2R KSmww5uWFuU7fjtVRNAfwLYl5x1ny5XXiRUHPTAG+7J6/66R6mZ/KWdFvhFS9ed+XnBw 2mjqHdEYPhMXtjYiSEkjfSK7ndTOywzenU2Fa3QK2m8K6r6Qnm+WctyW5c2u7UjIIyGc UL9g== X-Gm-Message-State: AAQBX9f1kjyfVAVS27zpP9SkwFfkV322q94p/4o3e5vG4sTQDFwD2PQk 3yzxwxX0nwI+TEJqivtqyBhFQBhcOF0= X-Google-Smtp-Source: AKy350ZZ9Icx+O5cAn8L7UL4LyCuWNAJhMF/AQzRrb3mRpbXzhEHNdWb1PPKet4jhSmTfS5WrLVoXSBCA6Y= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:e67a:98b0:942d:86aa]) (user=drosen job=sendgmr) by 2002:a25:d08d:0:b0:b7c:1144:a708 with SMTP id h135-20020a25d08d000000b00b7c1144a708mr10996656ybg.12.1681782084915; Mon, 17 Apr 2023 18:41:24 -0700 (PDT) Date: Mon, 17 Apr 2023 18:40:15 -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-16-drosen@google.com> Subject: [RFC PATCH v3 15/37] fuse-bpf: Support file/dir open/close 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 Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This adds backing support for FUSE_OPEN, FUSE_OPENDIR, FUSE_CREATE, FUSE_RELEASE, and FUSE_RELEASEDIR Signed-off-by: Daniel Rosenberg Signed-off-by: Paul Lawrence --- fs/fuse/backing.c | 368 ++++++++++++++++++++++++++++++++++++++++++++++ fs/fuse/dir.c | 8 + fs/fuse/file.c | 7 + fs/fuse/fuse_i.h | 26 ++++ 4 files changed, 409 insertions(+) diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index ee315598bc3f..d4a214cadc15 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -207,6 +207,374 @@ static void fuse_stat_to_attr(struct fuse_conn *fc, s= truct inode *inode, attr->blksize =3D 1 << blkbits; } =20 +struct fuse_open_args { + struct fuse_open_in in; + struct fuse_open_out out; +}; + +static int fuse_open_initialize_in(struct bpf_fuse_args *fa, struct fuse_o= pen_args *args, + struct inode *inode, struct file *file, bool isdir) +{ + args->in =3D (struct fuse_open_in) { + .flags =3D file->f_flags & ~(O_CREAT | O_EXCL | O_NOCTTY), + }; + *fa =3D (struct bpf_fuse_args) { + .info =3D (struct bpf_fuse_meta_info) { + .nodeid =3D get_fuse_inode(inode)->nodeid, + .opcode =3D isdir ? FUSE_OPENDIR : FUSE_OPEN, + }, + .in_numargs =3D 1, + .in_args[0] =3D (struct bpf_fuse_arg) { + .size =3D sizeof(args->in), + .value =3D &args->in, + }, + }; + + return 0; +} + +static int fuse_open_initialize_out(struct bpf_fuse_args *fa, struct fuse_= open_args *args, + struct inode *inode, struct file *file, bool isdir) +{ + args->out =3D (struct fuse_open_out) { 0 }; + + fa->out_numargs =3D 1; + fa->out_args[0] =3D (struct bpf_fuse_arg) { + .size =3D sizeof(args->out), + .value =3D &args->out, + }; + + return 0; +} + +static int fuse_open_backing(struct bpf_fuse_args *fa, int *out, + struct inode *inode, struct file *file, bool isdir) +{ + struct fuse_mount *fm =3D get_fuse_mount(inode); + const struct fuse_open_in *foi =3D fa->in_args[0].value; + struct fuse_file *ff; + int mask; + struct fuse_dentry *fd =3D get_fuse_dentry(file->f_path.dentry); + struct file *backing_file; + + ff =3D fuse_file_alloc(fm); + if (!ff) + return -ENOMEM; + file->private_data =3D ff; + + switch (foi->flags & O_ACCMODE) { + case O_RDONLY: + mask =3D MAY_READ; + break; + + case O_WRONLY: + mask =3D MAY_WRITE; + break; + + case O_RDWR: + mask =3D MAY_READ | MAY_WRITE; + break; + + default: + return -EINVAL; + } + + *out =3D inode_permission(&nop_mnt_idmap, + get_fuse_inode(inode)->backing_inode, mask); + if (*out) + return *out; + + backing_file =3D + dentry_open(&fd->backing_path, foi->flags, current_cred()); + + if (IS_ERR(backing_file)) { + fuse_file_free(ff); + file->private_data =3D NULL; + return PTR_ERR(backing_file); + } + ff->backing_file =3D backing_file; + + *out =3D 0; + return 0; +} + +static int fuse_open_finalize(struct bpf_fuse_args *fa, int *out, + struct inode *inode, struct file *file, bool isdir) +{ + struct fuse_file *ff =3D file->private_data; + struct fuse_open_out *foo =3D fa->out_args[0].value; + + if (ff) { + ff->fh =3D foo->fh; + ff->nodeid =3D get_fuse_inode(inode)->nodeid; + } + return 0; +} + +int fuse_bpf_open(int *out, struct inode *inode, struct file *file, bool i= sdir) +{ + return bpf_fuse_backing(inode, struct fuse_open_args, out, + fuse_open_initialize_in, fuse_open_initialize_out, + fuse_open_backing, fuse_open_finalize, + inode, file, isdir); +} + +struct fuse_create_open_args { + struct fuse_create_in in; + struct fuse_buffer name; + struct fuse_entry_out entry_out; + struct fuse_open_out open_out; +}; + +static int fuse_create_open_initialize_in(struct bpf_fuse_args *fa, struct= fuse_create_open_args *args, + struct inode *dir, struct dentry *entry, + struct file *file, unsigned int flags, umode_t mode) +{ + args->in =3D (struct fuse_create_in) { + .flags =3D file->f_flags & ~(O_CREAT | O_EXCL | O_NOCTTY), + .mode =3D mode, + }; + + args->name =3D (struct fuse_buffer) { + .data =3D (void *) entry->d_name.name, + .size =3D entry->d_name.len + 1, + .flags =3D BPF_FUSE_IMMUTABLE, + }; + + *fa =3D (struct bpf_fuse_args) { + .info =3D (struct bpf_fuse_meta_info) { + .nodeid =3D get_node_id(dir), + .opcode =3D FUSE_CREATE, + }, + .in_numargs =3D 2, + .in_args[0] =3D (struct bpf_fuse_arg) { + .size =3D sizeof(args->in), + .value =3D &args->in, + }, + .in_args[1] =3D (struct bpf_fuse_arg) { + .is_buffer =3D true, + .buffer =3D &args->name, + }, + }; + + return 0; +} + +static int fuse_create_open_initialize_out(struct bpf_fuse_args *fa, struc= t fuse_create_open_args *args, + struct inode *dir, struct dentry *entry, + struct file *file, unsigned int flags, umode_t mode) +{ + args->entry_out =3D (struct fuse_entry_out) { 0 }; + args->open_out =3D (struct fuse_open_out) { 0 }; + + fa->out_numargs =3D 2; + fa->out_args[0] =3D (struct bpf_fuse_arg) { + .size =3D sizeof(args->entry_out), + .value =3D &args->entry_out, + }; + fa->out_args[1] =3D (struct bpf_fuse_arg) { + .size =3D sizeof(args->open_out), + .value =3D &args->open_out, + }; + + return 0; +} + +static int fuse_open_file_backing(struct inode *inode, struct file *file) +{ + struct fuse_mount *fm =3D get_fuse_mount(inode); + struct dentry *entry =3D file->f_path.dentry; + struct fuse_dentry *fuse_dentry =3D get_fuse_dentry(entry); + struct fuse_file *fuse_file; + struct file *backing_file; + + fuse_file =3D fuse_file_alloc(fm); + if (!fuse_file) + return -ENOMEM; + file->private_data =3D fuse_file; + + backing_file =3D dentry_open(&fuse_dentry->backing_path, file->f_flags, + current_cred()); + if (IS_ERR(backing_file)) { + fuse_file_free(fuse_file); + file->private_data =3D NULL; + return PTR_ERR(backing_file); + } + fuse_file->backing_file =3D backing_file; + + return 0; +} + +static int fuse_create_open_backing(struct bpf_fuse_args *fa, int *out, + struct inode *dir, struct dentry *entry, + struct file *file, unsigned int flags, umode_t mode) +{ + struct fuse_inode *dir_fuse_inode =3D get_fuse_inode(dir); + struct path backing_path; + struct inode *inode =3D NULL; + struct dentry *backing_parent; + struct dentry *newent; + const struct fuse_create_in *fci =3D fa->in_args[0].value; + + get_fuse_backing_path(entry, &backing_path); + if (!backing_path.dentry) + return -EBADF; + + if (IS_ERR(backing_path.dentry)) + return PTR_ERR(backing_path.dentry); + + if (d_really_is_positive(backing_path.dentry)) { + *out =3D -EIO; + goto out; + } + + backing_parent =3D dget_parent(backing_path.dentry); + inode_lock_nested(dir_fuse_inode->backing_inode, I_MUTEX_PARENT); + *out =3D vfs_create(&nop_mnt_idmap, d_inode(backing_parent), + backing_path.dentry, fci->mode, true); + inode_unlock(d_inode(backing_parent)); + dput(backing_parent); + if (*out) + goto out; + + inode =3D fuse_iget_backing(dir->i_sb, 0, backing_path.dentry->d_inode); + if (IS_ERR(inode)) { + *out =3D PTR_ERR(inode); + goto out; + } + + newent =3D d_splice_alias(inode, entry); + if (IS_ERR(newent)) { + *out =3D PTR_ERR(newent); + goto out; + } + + entry =3D newent ? newent : entry; + *out =3D finish_open(file, entry, fuse_open_file_backing); + +out: + path_put(&backing_path); + return *out; +} + +static int fuse_create_open_finalize(struct bpf_fuse_args *fa, int *out, + struct inode *dir, struct dentry *entry, + struct file *file, unsigned int flags, umode_t mode) +{ + struct fuse_file *ff =3D file->private_data; + struct fuse_inode *fi =3D get_fuse_inode(file->f_inode); + struct fuse_entry_out *feo =3D fa->out_args[0].value; + struct fuse_open_out *foo =3D fa->out_args[1].value; + + if (fi) + fi->nodeid =3D feo->nodeid; + if (ff) + ff->fh =3D foo->fh; + return 0; +} + +int fuse_bpf_create_open(int *out, struct inode *dir, struct dentry *entry, + struct file *file, unsigned int flags, umode_t mode) +{ + return bpf_fuse_backing(dir, struct fuse_create_open_args, out, + fuse_create_open_initialize_in, + fuse_create_open_initialize_out, + fuse_create_open_backing, + fuse_create_open_finalize, + dir, entry, file, flags, mode); +} + +static int fuse_release_initialize_in(struct bpf_fuse_args *fa, struct fus= e_release_in *fri, + struct inode *inode, struct file *file) +{ + struct fuse_file *fuse_file =3D file->private_data; + + /* Always put backing file whatever bpf/userspace says */ + fput(fuse_file->backing_file); + + *fri =3D (struct fuse_release_in) { + .fh =3D ((struct fuse_file *)(file->private_data))->fh, + }; + + *fa =3D (struct bpf_fuse_args) { + .info =3D (struct bpf_fuse_meta_info) { + .nodeid =3D get_fuse_inode(inode)->nodeid, + .opcode =3D FUSE_RELEASE, + }, .in_numargs =3D 1, + .in_args[0].size =3D sizeof(*fri), + .in_args[0].value =3D fri, + }; + + return 0; +} + +static int fuse_release_initialize_out(struct bpf_fuse_args *fa, struct fu= se_release_in *fri, + struct inode *inode, struct file *file) +{ + return 0; +} + +static int fuse_releasedir_initialize_in(struct bpf_fuse_args *fa, + struct fuse_release_in *fri, + struct inode *inode, struct file *file) +{ + struct fuse_file *fuse_file =3D file->private_data; + + /* Always put backing file whatever bpf/userspace says */ + fput(fuse_file->backing_file); + + *fri =3D (struct fuse_release_in) { + .fh =3D ((struct fuse_file *)(file->private_data))->fh, + }; + + *fa =3D (struct bpf_fuse_args) { + .info =3D (struct bpf_fuse_meta_info) { + .nodeid =3D get_fuse_inode(inode)->nodeid, + .opcode =3D FUSE_RELEASEDIR, + }, .in_numargs =3D 1, + .in_args[0].size =3D sizeof(*fri), + .in_args[0].value =3D fri, + }; + + return 0; +} + +static int fuse_releasedir_initialize_out(struct bpf_fuse_args *fa, + struct fuse_release_in *fri, + struct inode *inode, struct file *file) +{ + return 0; +} + +static int fuse_release_backing(struct bpf_fuse_args *fa, int *out, + struct inode *inode, struct file *file) +{ + return 0; +} + +static int fuse_release_finalize(struct bpf_fuse_args *fa, int *out, + struct inode *inode, struct file *file) +{ + fuse_file_free(file->private_data); + *out =3D 0; + return 0; +} + +int fuse_bpf_release(int *out, struct inode *inode, struct file *file) +{ + return bpf_fuse_backing(inode, struct fuse_release_in, out, + fuse_release_initialize_in, fuse_release_initialize_out, + fuse_release_backing, fuse_release_finalize, + inode, file); +} + +int fuse_bpf_releasedir(int *out, struct inode *inode, struct file *file) +{ + return bpf_fuse_backing(inode, struct fuse_release_in, out, + fuse_releasedir_initialize_in, fuse_releasedir_initialize_out, + fuse_release_backing, fuse_release_finalize, inode, file); +} + struct fuse_lseek_args { struct fuse_lseek_in in; struct fuse_lseek_out out; diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 535e6cf9e970..1df2bbc72396 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -719,6 +719,9 @@ static int fuse_create_open(struct inode *dir, struct d= entry *entry, /* Userspace expects S_IFREG in create mode */ BUG_ON((mode & S_IFMT) !=3D S_IFREG); =20 + if (fuse_bpf_create_open(&err, dir, entry, file, flags, mode)) + return err; + forget =3D fuse_alloc_forget(); err =3D -ENOMEM; if (!forget) @@ -1629,6 +1632,11 @@ static int fuse_dir_open(struct inode *inode, struct= file *file) =20 static int fuse_dir_release(struct inode *inode, struct file *file) { + int err =3D 0; + + if (fuse_bpf_releasedir(&err, inode, file)) + return err; + fuse_release_common(file, true); =20 return 0; diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 58cff04660db..1836d09d9ce3 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -243,6 +243,9 @@ int fuse_open_common(struct inode *inode, struct file *= file, bool isdir) if (err) return err; =20 + if (fuse_bpf_open(&err, inode, file, isdir)) + return err; + if (is_wb_truncate || dax_truncate) inode_lock(inode); =20 @@ -351,6 +354,10 @@ static int fuse_open(struct inode *inode, struct file = *file) static int fuse_release(struct inode *inode, struct file *file) { struct fuse_conn *fc =3D get_fuse_conn(inode); + int err; + + if (fuse_bpf_release(&err, inode, file)) + return err; =20 /* * Dirty pages might remain despite write_inode_now() call from diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 1dd9cc9720df..feecc1ebfdda 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1405,6 +1405,11 @@ int parse_fuse_bpf_entry(struct fuse_bpf_entry *fbe,= int num_entries); =20 #ifdef CONFIG_FUSE_BPF =20 +int fuse_bpf_open(int *err, struct inode *inode, struct file *file, bool i= sdir); +int fuse_bpf_create_open(int *out, struct inode *dir, struct dentry *entry, + struct file *file, unsigned int flags, umode_t mode); +int fuse_bpf_release(int *out, struct inode *inode, struct file *file); +int fuse_bpf_releasedir(int *out, struct inode *inode, struct file *file); int fuse_bpf_lseek(loff_t *out, struct inode *inode, struct file *file, lo= ff_t offset, int whence); int fuse_bpf_file_fallocate(int *out, struct inode *inode, struct file *fi= le, int mode, loff_t offset, loff_t length); int fuse_bpf_lookup(struct dentry **out, struct inode *dir, struct dentry = *entry, unsigned int flags); @@ -1412,6 +1417,27 @@ int fuse_bpf_access(int *out, struct inode *inode, i= nt mask); =20 #else =20 +static inline int fuse_bpf_open(int *err, struct inode *inode, struct file= *file, bool isdir) +{ + return 0; +} + +static inline int fuse_bpf_create_open(int *out, struct inode *dir, struct= dentry *entry, + struct file *file, unsigned int flags, umode_t mode) +{ + return 0; +} + +static inline int fuse_bpf_release(int *out, struct inode *inode, struct f= ile *file) +{ + return 0; +} + +static inline int fuse_bpf_releasedir(int *out, struct inode *inode, struc= t file *file) +{ + return 0; +} + static inline int fuse_bpf_lseek(loff_t *out, struct inode *inode, struct = file *file, loff_t offset, int whence) { return 0; --=20 2.40.0.634.g4ca3ef3211-goog From nobody Wed Dec 17 19:22:07 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 A7978C77B75 for ; Tue, 18 Apr 2023 01:43:23 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229510AbjDRBnW (ORCPT ); Mon, 17 Apr 2023 21:43:22 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53878 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230331AbjDRBma (ORCPT ); Mon, 17 Apr 2023 21:42:30 -0400 Received: from mail-yb1-xb4a.google.com (mail-yb1-xb4a.google.com [IPv6:2607:f8b0:4864:20::b4a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id A0F067EF2 for ; Mon, 17 Apr 2023 18:41:34 -0700 (PDT) Received: by mail-yb1-xb4a.google.com with SMTP id d132-20020a254f8a000000b00b868826cdfeso2898062ybb.0 for ; Mon, 17 Apr 2023 18:41:34 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20221208; t=1681782087; x=1684374087; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=NDDGgrzSklW9PeQhCnnSrKGQK+GKlru6fDSS3O5BjmA=; b=ZH5Or0LVxK0ACl0MYkteAzESD99/7Y6eEY7bCBEA2/UuGiSKlZfZQrdX8//nPc5nnK aJRB9zReYRfrzP6d9Tm5nolDNIPFi0ZArQix6zL92mKogJZGyyJ4PoxLe7+MZrdvb9wO kQzhsjswf9u1btEBhZRZNqtsp1rTkCG+uQxMOSb7QE3FpkXqeV2NOwqoXk4OAg1om9pg xbA94KAfpNG9a0xgBppvH0P/oMFCe049pKZOYRMru06j+Wx//wEy38p0FUuAuX2U+8I7 MW5H+7BpQvPjjs+oyFzPJ+0Ar9sgfNJqCfxtarg9uNdtUS4R8CyMoCC4J77InmpYcLjF bTpA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681782087; x=1684374087; 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=NDDGgrzSklW9PeQhCnnSrKGQK+GKlru6fDSS3O5BjmA=; b=jvaYPEkWtl10HP/9VT4vGa3P1eDyRLs/73jmpBDwpEAqVnCUQ3tpPFpBtfGU6hJxqo la94Q6BgTKoUL/jx6MTxBFcgjDfk/DA8SKxymh1JqXo/htGeTcgPgz6WIFeFqa081W0G /vwVDtMNMTylgE5DreAo4H5T36zlX0Tx68ZkO8D2X51oGqWkfcCXGTOqf4fkqUnF+g2g YrStMrbnXBGKjOQRyFoIkwFMFeIWUYcXaCaS5QvDloleaUeM7iR8rfYBQldHXab2Nzdw 9NVzJxY2R3rYhDGFZ+Xiq5MdQpRU+uh0RrFCPnEiCmwtfeeusZZXFZ+KTIEGg6uqnDAU vh+g== X-Gm-Message-State: AAQBX9dRfmOXR1BkfRG7wR3hiSCctlocmStsg5feQIa4Q5xXtous3MWW m6z/KPWZ3t6cuFt101NwHuyZsrGSAx0= X-Google-Smtp-Source: AKy350b2QTc7qTknU5iRQ/AMnSRDgnYtVdT/oLHx5r2YmfDH4vM6DkZ756se8OX1GhvWk/AZEjOfYn0mUv0= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:e67a:98b0:942d:86aa]) (user=drosen job=sendgmr) by 2002:a25:d8c1:0:b0:b8b:f594:d512 with SMTP id p184-20020a25d8c1000000b00b8bf594d512mr8744186ybg.13.1681782087328; Mon, 17 Apr 2023 18:41:27 -0700 (PDT) Date: Mon, 17 Apr 2023 18:40:16 -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-17-drosen@google.com> Subject: [RFC PATCH v3 16/37] fuse-bpf: Support mknod/unlink/mkdir/rmdir 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 Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This adds backing support for FUSE_MKNOD, FUSE_MKDIR, FUSE_RMDIR, and FUSE_UNLINK Signed-off-by: Daniel Rosenberg Signed-off-by: Paul Lawrence --- fs/fuse/backing.c | 342 ++++++++++++++++++++++++++++++++++++++++++++++ fs/fuse/dir.c | 14 ++ fs/fuse/fuse_i.h | 24 ++++ 3 files changed, 380 insertions(+) diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index d4a214cadc15..c6ef10aeec15 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -972,6 +972,348 @@ int fuse_revalidate_backing(struct dentry *entry, uns= igned int flags) return 1; } =20 +struct fuse_mknod_args { + struct fuse_mknod_in in; + struct fuse_buffer name; +}; + +static int fuse_mknod_initialize_in(struct bpf_fuse_args *fa, struct fuse_= mknod_args *args, + struct inode *dir, struct dentry *entry, umode_t mode, dev_t rdev) +{ + *args =3D (struct fuse_mknod_args) { + .in =3D (struct fuse_mknod_in) { + .mode =3D mode, + .rdev =3D new_encode_dev(rdev), + .umask =3D current_umask(), + }, + .name =3D (struct fuse_buffer) { + .data =3D (void *) entry->d_name.name, + .size =3D entry->d_name.len + 1, + .flags =3D BPF_FUSE_IMMUTABLE, + }, + }; + *fa =3D (struct bpf_fuse_args) { + .info =3D (struct bpf_fuse_meta_info) { + .nodeid =3D get_node_id(dir), + .opcode =3D FUSE_MKNOD, + }, + .in_numargs =3D 2, + .in_args[0] =3D (struct bpf_fuse_arg) { + .size =3D sizeof(args->in), + .value =3D &args->in, + }, + .in_args[1] =3D (struct bpf_fuse_arg) { + .is_buffer =3D true, + .buffer =3D &args->name, + }, + }; + + return 0; +} + +static int fuse_mknod_initialize_out(struct bpf_fuse_args *fa, struct fuse= _mknod_args *args, + struct inode *dir, struct dentry *entry, umode_t mode, dev_t rdev) +{ + return 0; +} + +static int fuse_mknod_backing(struct bpf_fuse_args *fa, int *out, + struct inode *dir, struct dentry *entry, umode_t mode, dev_t rdev) +{ + const struct fuse_mknod_in *fmi =3D fa->in_args[0].value; + struct fuse_inode *fuse_inode =3D get_fuse_inode(dir); + struct inode *backing_inode =3D fuse_inode->backing_inode; + struct path backing_path; + struct inode *inode =3D NULL; + + get_fuse_backing_path(entry, &backing_path); + if (!backing_path.dentry) + return -EBADF; + + inode_lock_nested(backing_inode, I_MUTEX_PARENT); + mode =3D fmi->mode; + if (!IS_POSIXACL(backing_inode)) + mode &=3D ~fmi->umask; + *out =3D vfs_mknod(&nop_mnt_idmap, backing_inode, backing_path.dentry, mo= de, + new_decode_dev(fmi->rdev)); + inode_unlock(backing_inode); + if (*out) + goto out; + if (d_really_is_negative(backing_path.dentry) || + unlikely(d_unhashed(backing_path.dentry))) { + *out =3D -EINVAL; + /** + * TODO: overlayfs responds to this situation with a + * lookupOneLen. Should we do that too? + */ + goto out; + } + inode =3D fuse_iget_backing(dir->i_sb, fuse_inode->nodeid, backing_inode); + if (IS_ERR(inode)) { + *out =3D PTR_ERR(inode); + goto out; + } + d_instantiate(entry, inode); +out: + path_put(&backing_path); + return *out; +} + +static int fuse_mknod_finalize(struct bpf_fuse_args *fa, int *out, + struct inode *dir, struct dentry *entry, umode_t mode, dev_t rde= v) +{ + return 0; +} + +int fuse_bpf_mknod(int *out, struct inode *dir, struct dentry *entry, umod= e_t mode, dev_t rdev) +{ + return bpf_fuse_backing(dir, struct fuse_mknod_args, out, + fuse_mknod_initialize_in, fuse_mknod_initialize_out, + fuse_mknod_backing, fuse_mknod_finalize, + dir, entry, mode, rdev); +} + +struct fuse_mkdir_args { + struct fuse_mkdir_in in; + struct fuse_buffer name; +}; + +static int fuse_mkdir_initialize_in(struct bpf_fuse_args *fa, struct fuse_= mkdir_args *args, + struct inode *dir, struct dentry *entry, umode_t mode) +{ + *args =3D (struct fuse_mkdir_args) { + .in =3D (struct fuse_mkdir_in) { + .mode =3D mode, + .umask =3D current_umask(), + }, + .name =3D (struct fuse_buffer) { + .data =3D (void *) entry->d_name.name, + .size =3D entry->d_name.len + 1, + .flags =3D BPF_FUSE_IMMUTABLE, + }, + }; + + *fa =3D (struct bpf_fuse_args) { + .info =3D (struct bpf_fuse_meta_info) { + .nodeid =3D get_node_id(dir), + .opcode =3D FUSE_MKDIR, + }, + .in_numargs =3D 2, + .in_args[0] =3D (struct bpf_fuse_arg) { + .size =3D sizeof(args->in), + .value =3D &args->in, + }, + .in_args[1] =3D (struct bpf_fuse_arg) { + .value =3D &args->name, + .is_buffer =3D true, + }, + }; + + return 0; +} + +static int fuse_mkdir_initialize_out(struct bpf_fuse_args *fa, struct fuse= _mkdir_args *args, + struct inode *dir, struct dentry *entry, umode_t mode) +{ + return 0; +} + +static int fuse_mkdir_backing(struct bpf_fuse_args *fa, int *out, + struct inode *dir, struct dentry *entry, umode_t mode) +{ + const struct fuse_mkdir_in *fmi =3D fa->in_args[0].value; + struct fuse_inode *fuse_inode =3D get_fuse_inode(dir); + struct inode *backing_inode =3D fuse_inode->backing_inode; + struct path backing_path; + struct inode *inode =3D NULL; + struct dentry *d; + + get_fuse_backing_path(entry, &backing_path); + if (!backing_path.dentry) + return -EBADF; + + inode_lock_nested(backing_inode, I_MUTEX_PARENT); + mode =3D fmi->mode; + if (!IS_POSIXACL(backing_inode)) + mode &=3D ~fmi->umask; + *out =3D vfs_mkdir(&nop_mnt_idmap, backing_inode, backing_path.dentry, + mode); + if (*out) + goto out; + if (d_really_is_negative(backing_path.dentry) || + unlikely(d_unhashed(backing_path.dentry))) { + d =3D lookup_one_len(entry->d_name.name, + backing_path.dentry->d_parent, + entry->d_name.len); + if (IS_ERR(d)) { + *out =3D PTR_ERR(d); + goto out; + } + dput(backing_path.dentry); + backing_path.dentry =3D d; + } + inode =3D fuse_iget_backing(dir->i_sb, fuse_inode->nodeid, backing_inode); + if (IS_ERR(inode)) { + *out =3D PTR_ERR(inode); + goto out; + } + d_instantiate(entry, inode); +out: + inode_unlock(backing_inode); + path_put(&backing_path); + return *out; +} + +static int fuse_mkdir_finalize(struct bpf_fuse_args *fa, int *out, + struct inode *dir, struct dentry *entry, umode_t mode) +{ + return 0; +} + +int fuse_bpf_mkdir(int *out, struct inode *dir, struct dentry *entry, umod= e_t mode) +{ + return bpf_fuse_backing(dir, struct fuse_mkdir_args, out, + fuse_mkdir_initialize_in, fuse_mkdir_initialize_out, + fuse_mkdir_backing, fuse_mkdir_finalize, + dir, entry, mode); +} + +static int fuse_rmdir_initialize_in(struct bpf_fuse_args *fa, struct fuse_= buffer *name, + struct inode *dir, struct dentry *entry) +{ + *name =3D (struct fuse_buffer) { + .data =3D (void *) entry->d_name.name, + .size =3D entry->d_name.len + 1, + .flags =3D BPF_FUSE_IMMUTABLE, + }; + *fa =3D (struct bpf_fuse_args) { + .info =3D (struct bpf_fuse_meta_info) { + .nodeid =3D get_node_id(dir), + .opcode =3D FUSE_RMDIR, + }, + .in_numargs =3D 1, + .in_args[0] =3D (struct bpf_fuse_arg) { + .is_buffer =3D true, + .buffer =3D name, + }, + }; + + return 0; +} + +static int fuse_rmdir_initialize_out(struct bpf_fuse_args *fa, struct fuse= _buffer *name, + struct inode *dir, struct dentry *entry) +{ + return 0; +} + +static int fuse_rmdir_backing(struct bpf_fuse_args *fa, int *out, + struct inode *dir, struct dentry *entry) +{ + struct path backing_path; + struct dentry *backing_parent_dentry; + struct inode *backing_inode; + + get_fuse_backing_path(entry, &backing_path); + if (!backing_path.dentry) + return -EBADF; + + backing_parent_dentry =3D dget_parent(backing_path.dentry); + backing_inode =3D d_inode(backing_parent_dentry); + + inode_lock_nested(backing_inode, I_MUTEX_PARENT); + *out =3D vfs_rmdir(&nop_mnt_idmap, backing_inode, backing_path.dentry); + inode_unlock(backing_inode); + + dput(backing_parent_dentry); + if (!*out) + d_drop(entry); + path_put(&backing_path); + return *out; +} + +static int fuse_rmdir_finalize(struct bpf_fuse_args *fa, int *out, struct = inode *dir, struct dentry *entry) +{ + return 0; +} + +int fuse_bpf_rmdir(int *out, struct inode *dir, struct dentry *entry) +{ + return bpf_fuse_backing(dir, struct fuse_buffer, out, + fuse_rmdir_initialize_in, fuse_rmdir_initialize_out, + fuse_rmdir_backing, fuse_rmdir_finalize, + dir, entry); +} + +static int fuse_unlink_initialize_in(struct bpf_fuse_args *fa, struct fuse= _buffer *name, + struct inode *dir, struct dentry *entry) +{ + *name =3D (struct fuse_buffer) { + .data =3D (void *) entry->d_name.name, + .size =3D entry->d_name.len + 1, + .flags =3D BPF_FUSE_IMMUTABLE, + }; + *fa =3D (struct bpf_fuse_args) { + .info =3D (struct bpf_fuse_meta_info) { + .nodeid =3D get_node_id(dir), + .opcode =3D FUSE_UNLINK, + }, + .in_numargs =3D 1, + .in_args[0] =3D (struct bpf_fuse_arg) { + .is_buffer =3D true, + .buffer =3D name, + }, + }; + + return 0; +} + +static int fuse_unlink_initialize_out(struct bpf_fuse_args *fa, struct fus= e_buffer *name, + struct inode *dir, struct dentry *entry) +{ + return 0; +} + +static int fuse_unlink_backing(struct bpf_fuse_args *fa, int *out, struct = inode *dir, struct dentry *entry) +{ + struct path backing_path; + struct dentry *backing_parent_dentry; + struct inode *backing_inode; + + get_fuse_backing_path(entry, &backing_path); + if (!backing_path.dentry) + return -EBADF; + + /* TODO Not sure if we should reverify like overlayfs, or get inode from = d_parent */ + backing_parent_dentry =3D dget_parent(backing_path.dentry); + backing_inode =3D d_inode(backing_parent_dentry); + + inode_lock_nested(backing_inode, I_MUTEX_PARENT); + *out =3D vfs_unlink(&nop_mnt_idmap, backing_inode, backing_path.dentry, + NULL); + inode_unlock(backing_inode); + + dput(backing_parent_dentry); + if (!*out) + d_drop(entry); + path_put(&backing_path); + return *out; +} + +static int fuse_unlink_finalize(struct bpf_fuse_args *fa, int *out, + struct inode *dir, struct dentry *entry) +{ + return 0; +} + +int fuse_bpf_unlink(int *out, struct inode *dir, struct dentry *entry) +{ + return bpf_fuse_backing(dir, struct fuse_buffer, out, + fuse_unlink_initialize_in, fuse_unlink_initialize_out, + fuse_unlink_backing, fuse_unlink_finalize, + dir, entry); +} + static int fuse_access_initialize_in(struct bpf_fuse_args *fa, struct fuse= _access_in *in, struct inode *inode, int mask) { diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 1df2bbc72396..a763a45fa973 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -937,6 +937,10 @@ static int fuse_mknod(struct mnt_idmap *idmap, struct = inode *dir, struct fuse_mknod_in inarg; struct fuse_mount *fm =3D get_fuse_mount(dir); FUSE_ARGS(args); + int err; + + if (fuse_bpf_mknod(&err, dir, entry, mode, rdev)) + return err; =20 if (!fm->fc->dont_mask) mode &=3D ~current_umask(); @@ -983,6 +987,10 @@ static int fuse_mkdir(struct mnt_idmap *idmap, struct = inode *dir, struct fuse_mkdir_in inarg; struct fuse_mount *fm =3D get_fuse_mount(dir); FUSE_ARGS(args); + int err; + + if (fuse_bpf_mkdir(&err, dir, entry, mode)) + return err; =20 if (!fm->fc->dont_mask) mode &=3D ~current_umask(); @@ -1069,6 +1077,9 @@ static int fuse_unlink(struct inode *dir, struct dent= ry *entry) if (fuse_is_bad(dir)) return -EIO; =20 + if (fuse_bpf_unlink(&err, dir, entry)) + return err; + args.opcode =3D FUSE_UNLINK; args.nodeid =3D get_node_id(dir); args.in_numargs =3D 1; @@ -1092,6 +1103,9 @@ static int fuse_rmdir(struct inode *dir, struct dentr= y *entry) if (fuse_is_bad(dir)) return -EIO; =20 + if (fuse_bpf_rmdir(&err, dir, entry)) + return err; + args.opcode =3D FUSE_RMDIR; args.nodeid =3D get_node_id(dir); args.in_numargs =3D 1; diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index feecc1ebfdda..2cbe232c1048 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1408,6 +1408,10 @@ int parse_fuse_bpf_entry(struct fuse_bpf_entry *fbe,= int num_entries); int fuse_bpf_open(int *err, struct inode *inode, struct file *file, bool i= sdir); int fuse_bpf_create_open(int *out, struct inode *dir, struct dentry *entry, struct file *file, unsigned int flags, umode_t mode); +int fuse_bpf_mknod(int *out, struct inode *dir, struct dentry *entry, umod= e_t mode, dev_t rdev); +int fuse_bpf_mkdir(int *out, struct inode *dir, struct dentry *entry, umod= e_t mode); +int fuse_bpf_rmdir(int *out, struct inode *dir, struct dentry *entry); +int fuse_bpf_unlink(int *out, struct inode *dir, struct dentry *entry); int fuse_bpf_release(int *out, struct inode *inode, struct file *file); int fuse_bpf_releasedir(int *out, struct inode *inode, struct file *file); int fuse_bpf_lseek(loff_t *out, struct inode *inode, struct file *file, lo= ff_t offset, int whence); @@ -1428,6 +1432,26 @@ static inline int fuse_bpf_create_open(int *out, str= uct inode *dir, struct dentr return 0; } =20 +static inline int fuse_bpf_mknod(int *out, struct inode *dir, struct dentr= y *entry, umode_t mode, dev_t rdev) +{ + return 0; +} + +static inline int fuse_bpf_mkdir(int *out, struct inode *dir, struct dentr= y *entry, umode_t mode) +{ + return 0; +} + +static inline int fuse_bpf_rmdir(int *out, struct inode *dir, struct dentr= y *entry) +{ + return 0; +} + +static inline int fuse_bpf_unlink(int *out, struct inode *dir, struct dent= ry *entry) +{ + return 0; +} + static inline int fuse_bpf_release(int *out, struct inode *inode, struct f= ile *file) { return 0; --=20 2.40.0.634.g4ca3ef3211-goog From nobody Wed Dec 17 19:22:07 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 B4A11C77B75 for ; Tue, 18 Apr 2023 01:43:39 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231142AbjDRBnh (ORCPT ); Mon, 17 Apr 2023 21:43:37 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53748 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230000AbjDRBmb (ORCPT ); Mon, 17 Apr 2023 21:42:31 -0400 Received: from mail-yb1-xb4a.google.com (mail-yb1-xb4a.google.com [IPv6:2607:f8b0:4864:20::b4a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 1A3437EDA for ; Mon, 17 Apr 2023 18:41:38 -0700 (PDT) Received: by mail-yb1-xb4a.google.com with SMTP id 84-20020a251457000000b00b8f59a09e1fso11380534ybu.5 for ; Mon, 17 Apr 2023 18:41:38 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20221208; t=1681782089; x=1684374089; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=Z4g1tB3udC3qOuw7tknQnFIwzeoIaxj84BFW0ZiQCFQ=; b=PWNxqCY04pgGT68/fhpmjWUAwCbx54KY6uSUjjYhQqzp9lLJvHVDSqt+SI6+HzFwO7 fyG+VrOF03Whi4jmxb9IPQLjIWF1tt5QBDpzqohLc0FDjVHNm1qBZ0OdxWQJ4SOM5GaY JmoIxTjQptfm3JGLYD4GlLL/ahIixyaK+FaZLM1NRpT7qrBURyp0+1U0UNiqr7DKn0GS SnqgwZxuPPks4eiFf/F1WIrn8+rzTH/w9PcFNAyzvAMjaFEMXE/iQ1Ngtw+5daeO5Js2 2KH3kevDzRqBqh44lbo+ARV65hd/S1NuMvXtvdyiJ+OVk9nbQYzXiQwH4zNY8CIi3PYZ APvA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681782089; x=1684374089; 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=Z4g1tB3udC3qOuw7tknQnFIwzeoIaxj84BFW0ZiQCFQ=; b=aTGFA3odXCjBzxqJ4Z7W5S1vSui/L14BqavtZoJbg7pPULd6ZPbpnIXRJp+akYWf6R HwpDlUJIkN6QD1RgWv2NrxiDIyjA4o8VW/ju6x+ZXy+Dt8qhX751v3/y5dhSbf7kshuD zkzOIRLBwH+bRd9Aj5U+EOKq3l8T2KbuXXIWpsZ3p+ivcCNaZNw8P3KR362PZFSVCreq HBlzk1005MWsPcWAK2er0kFzKf4SlBlN3xYVebyJT1cCYhfqF3BHd9jQkFYTYIhJP8QG R3CXUL4svfTIv5ZGDgCJSNsi/hh7w/eD7LngkWRExx4qW5ZQle271ttWRquOh37mYsFZ hrNA== X-Gm-Message-State: AAQBX9cj1fB3/Hs7QgK6d0wX9LFnIbeUJo37ZSidfqzFY+oY6RWFJGE5 yT8L1zy2LA68kFAaVTaW4hHGVTPIdxo= X-Google-Smtp-Source: AKy350bpzdbUr7ugtKzWZK023/0mleS8ekb1JXmOGLJPSBH89ux6GBIOfdzqZrMzBVw/FzqHBnUFAl5jOMo= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:e67a:98b0:942d:86aa]) (user=drosen job=sendgmr) by 2002:a81:af53:0:b0:555:a687:e151 with SMTP id x19-20020a81af53000000b00555a687e151mr967497ywj.3.1681782089597; Mon, 17 Apr 2023 18:41:29 -0700 (PDT) Date: Mon, 17 Apr 2023 18:40:17 -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-18-drosen@google.com> Subject: [RFC PATCH v3 17/37] fuse-bpf: Add support for read/write iter 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 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 backing support for FUSE_READ and FUSE_WRITE This includes adjustments from Amir Goldstein's patch to FUSE Passthrough Signed-off-by: Daniel Rosenberg Signed-off-by: Paul Lawrence --- fs/fuse/backing.c | 371 ++++++++++++++++++++++++++++++++++++++ fs/fuse/control.c | 2 +- fs/fuse/file.c | 8 + fs/fuse/fuse_i.h | 19 +- fs/fuse/inode.c | 13 ++ include/uapi/linux/fuse.h | 10 + 6 files changed, 421 insertions(+), 2 deletions(-) diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index c6ef10aeec15..c7709a880e9c 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -11,6 +11,7 @@ #include #include #include +#include =20 /* * expression statement to wrap the backing filter logic @@ -76,6 +77,89 @@ handled; \ }) =20 +#define FUSE_BPF_IOCB_MASK (IOCB_APPEND | IOCB_DSYNC | IOCB_HIPRI | IOCB_N= OWAIT | IOCB_SYNC) + +struct fuse_bpf_aio_req { + struct kiocb iocb; + refcount_t ref; + struct kiocb *iocb_orig; + struct timespec64 pre_atime; +}; + +static struct kmem_cache *fuse_bpf_aio_request_cachep; + +static void fuse_file_accessed(struct file *dst_file, struct file *src_fil= e) +{ + struct inode *dst_inode; + struct inode *src_inode; + + if (dst_file->f_flags & O_NOATIME) + return; + + dst_inode =3D file_inode(dst_file); + src_inode =3D file_inode(src_file); + + if ((!timespec64_equal(&dst_inode->i_mtime, &src_inode->i_mtime) || + !timespec64_equal(&dst_inode->i_ctime, &src_inode->i_ctime))) { + dst_inode->i_mtime =3D src_inode->i_mtime; + dst_inode->i_ctime =3D src_inode->i_ctime; + } + + touch_atime(&dst_file->f_path); +} + +static void fuse_copyattr(struct file *dst_file, struct file *src_file) +{ + struct inode *dst =3D file_inode(dst_file); + struct inode *src =3D file_inode(src_file); + + dst->i_atime =3D src->i_atime; + dst->i_mtime =3D src->i_mtime; + dst->i_ctime =3D src->i_ctime; + i_size_write(dst, i_size_read(src)); + fuse_invalidate_attr(dst); +} + +static void fuse_file_start_write(struct file *fuse_file, struct file *bac= king_file, + loff_t pos, size_t count) +{ + struct inode *inode =3D file_inode(fuse_file); + struct fuse_inode *fi =3D get_fuse_inode(inode); + + if (inode->i_size < pos + count) + set_bit(FUSE_I_SIZE_UNSTABLE, &fi->state); + + file_start_write(backing_file); +} + +static void fuse_file_end_write(struct file *fuse_file, struct file *backi= ng_file, + loff_t pos, size_t res) +{ + struct inode *inode =3D file_inode(fuse_file); + struct fuse_inode *fi =3D get_fuse_inode(inode); + + file_end_write(backing_file); + + if (res > 0) + fuse_write_update_attr(inode, pos, res); + + clear_bit(FUSE_I_SIZE_UNSTABLE, &fi->state); + fuse_invalidate_attr(inode); +} + +static void fuse_file_start_read(struct file *backing_file, struct timespe= c64 *pre_atime) +{ + *pre_atime =3D file_inode(backing_file)->i_atime; +} + +static void fuse_file_end_read(struct file *fuse_file, struct file *backin= g_file, + struct timespec64 *pre_atime) +{ + /* Mimic atime update policy of passthrough inode, not the value */ + if (!timespec64_equal(&file_inode(backing_file)->i_atime, pre_atime)) + fuse_invalidate_atime(file_inode(fuse_file)); +} + static void fuse_get_backing_path(struct file *file, struct path *path) { path_get(&file->f_path); @@ -664,6 +748,277 @@ int fuse_bpf_lseek(loff_t *out, struct inode *inode, = struct file *file, loff_t o file, offset, whence); } =20 +static inline void fuse_bpf_aio_put(struct fuse_bpf_aio_req *aio_req) +{ + if (refcount_dec_and_test(&aio_req->ref)) + kmem_cache_free(fuse_bpf_aio_request_cachep, aio_req); +} + +static void fuse_bpf_aio_cleanup_handler(struct fuse_bpf_aio_req *aio_req,= long res) +{ + struct kiocb *iocb =3D &aio_req->iocb; + struct kiocb *iocb_orig =3D aio_req->iocb_orig; + struct file *filp =3D iocb->ki_filp; + struct file *fuse_filp =3D iocb_orig->ki_filp; + + if (iocb->ki_flags & IOCB_WRITE) { + __sb_writers_acquired(file_inode(iocb->ki_filp)->i_sb, + SB_FREEZE_WRITE); + fuse_file_end_write(iocb_orig->ki_filp, iocb->ki_filp, iocb->ki_pos, res= ); + } else { + fuse_file_end_read(fuse_filp, filp, &aio_req->pre_atime); + } + iocb_orig->ki_pos =3D iocb->ki_pos; + fuse_bpf_aio_put(aio_req); +} + +static void fuse_bpf_aio_rw_complete(struct kiocb *iocb, long res) +{ + struct fuse_bpf_aio_req *aio_req =3D + container_of(iocb, struct fuse_bpf_aio_req, iocb); + struct kiocb *iocb_orig =3D aio_req->iocb_orig; + + fuse_bpf_aio_cleanup_handler(aio_req, res); + iocb_orig->ki_complete(iocb_orig, res); +} + +struct fuse_file_read_iter_args { + struct fuse_read_in in; + struct fuse_read_iter_out out; +}; + +static int fuse_file_read_iter_initialize_in(struct bpf_fuse_args *fa, str= uct fuse_file_read_iter_args *args, + struct kiocb *iocb, struct iov_iter *to) +{ + struct file *file =3D iocb->ki_filp; + struct fuse_file *ff =3D file->private_data; + + args->in =3D (struct fuse_read_in) { + .fh =3D ff->fh, + .offset =3D iocb->ki_pos, + .size =3D to->count, + }; + + /* TODO we can't assume 'to' is a kvec */ + /* TODO we also can't assume the vector has only one component */ + *fa =3D (struct bpf_fuse_args) { + .info =3D (struct bpf_fuse_meta_info) { + .opcode =3D FUSE_READ, + .nodeid =3D ff->nodeid, + }, .in_numargs =3D 1, + .in_args[0].size =3D sizeof(args->in), + .in_args[0].value =3D &args->in, + /* + * TODO Design this properly. + * Possible approach: do not pass buf to bpf + * If going to userland, do a deep copy + * For extra credit, do that to/from the vector, rather than + * making an extra copy in the kernel + */ + }; + + return 0; +} + +static int fuse_file_read_iter_initialize_out(struct bpf_fuse_args *fa, st= ruct fuse_file_read_iter_args *args, + struct kiocb *iocb, struct iov_iter *to) +{ + args->out =3D (struct fuse_read_iter_out) { + .ret =3D args->in.size, + }; + + fa->out_numargs =3D 1; + fa->out_args[0].size =3D sizeof(args->out); + fa->out_args[0].value =3D &args->out; + + return 0; +} + +static int fuse_file_read_iter_backing(struct bpf_fuse_args *fa, ssize_t *= out, + struct kiocb *iocb, struct iov_iter *to) +{ + struct fuse_read_iter_out *frio =3D fa->out_args[0].value; + struct file *file =3D iocb->ki_filp; + struct fuse_file *ff =3D file->private_data; + + if (!iov_iter_count(to)) + return 0; + + if ((iocb->ki_flags & IOCB_DIRECT) && + (!ff->backing_file->f_mapping->a_ops || + !ff->backing_file->f_mapping->a_ops->direct_IO)) + return -EINVAL; + + /* TODO This just plain ignores any change to fuse_read_in */ + if (is_sync_kiocb(iocb)) { + struct timespec64 pre_atime; + + fuse_file_start_read(ff->backing_file, &pre_atime); + *out =3D vfs_iter_read(ff->backing_file, to, &iocb->ki_pos, + iocb_to_rw_flags(iocb->ki_flags, FUSE_BPF_IOCB_MASK)); + fuse_file_end_read(file, ff->backing_file, &pre_atime); + } else { + struct fuse_bpf_aio_req *aio_req; + + *out =3D -ENOMEM; + aio_req =3D kmem_cache_zalloc(fuse_bpf_aio_request_cachep, GFP_KERNEL); + if (!aio_req) + goto out; + + aio_req->iocb_orig =3D iocb; + fuse_file_start_read(ff->backing_file, &aio_req->pre_atime); + kiocb_clone(&aio_req->iocb, iocb, ff->backing_file); + aio_req->iocb.ki_complete =3D fuse_bpf_aio_rw_complete; + refcount_set(&aio_req->ref, 2); + *out =3D vfs_iocb_iter_read(ff->backing_file, &aio_req->iocb, to); + fuse_bpf_aio_put(aio_req); + if (*out !=3D -EIOCBQUEUED) + fuse_bpf_aio_cleanup_handler(aio_req, *out); + } + + frio->ret =3D *out; + + /* TODO Need to point value at the buffer for post-modification */ + +out: + fuse_file_accessed(file, ff->backing_file); + + return *out; +} + +static int fuse_file_read_iter_finalize(struct bpf_fuse_args *fa, ssize_t = *out, + struct kiocb *iocb, struct iov_iter *to) +{ + struct fuse_read_iter_out *frio =3D fa->out_args[0].value; + + *out =3D frio->ret; + + return 0; +} + +int fuse_bpf_file_read_iter(ssize_t *out, struct inode *inode, struct kioc= b *iocb, struct iov_iter *to) +{ + return bpf_fuse_backing(inode, struct fuse_file_read_iter_args, out, + fuse_file_read_iter_initialize_in, + fuse_file_read_iter_initialize_out, + fuse_file_read_iter_backing, + fuse_file_read_iter_finalize, + iocb, to); +} + +struct fuse_file_write_iter_args { + struct fuse_write_in in; + struct fuse_write_iter_out out; +}; + +static int fuse_file_write_iter_initialize_in(struct bpf_fuse_args *fa, + struct fuse_file_write_iter_args *args, + struct kiocb *iocb, struct iov_iter *from) +{ + struct file *file =3D iocb->ki_filp; + struct fuse_file *ff =3D file->private_data; + + *args =3D (struct fuse_file_write_iter_args) { + .in.fh =3D ff->fh, + .in.offset =3D iocb->ki_pos, + .in.size =3D from->count, + }; + + /* TODO we can't assume 'from' is a kvec */ + *fa =3D (struct bpf_fuse_args) { + .info =3D (struct bpf_fuse_meta_info) { + .opcode =3D FUSE_WRITE, + .nodeid =3D ff->nodeid, + }, + .in_numargs =3D 1, + .in_args[0].size =3D sizeof(args->in), + .in_args[0].value =3D &args->in, + }; + + return 0; +} + +static int fuse_file_write_iter_initialize_out(struct bpf_fuse_args *fa, + struct fuse_file_write_iter_args *args, + struct kiocb *iocb, struct iov_iter *from) +{ + /* TODO we can't assume 'from' is a kvec */ + fa->out_numargs =3D 1; + fa->out_args[0].size =3D sizeof(args->out); + fa->out_args[0].value =3D &args->out; + + return 0; +} + +static int fuse_file_write_iter_backing(struct bpf_fuse_args *fa, ssize_t = *out, + struct kiocb *iocb, struct iov_iter *from) +{ + struct file *file =3D iocb->ki_filp; + struct fuse_file *ff =3D file->private_data; + struct fuse_write_iter_out *fwio =3D fa->out_args[0].value; + ssize_t count =3D iov_iter_count(from); + + if (!count) + return 0; + + /* TODO This just plain ignores any change to fuse_write_in */ + /* TODO uint32_t seems smaller than ssize_t.... right? */ + inode_lock(file_inode(file)); + + fuse_copyattr(file, ff->backing_file); + + if (is_sync_kiocb(iocb)) { + fuse_file_start_write(file, ff->backing_file, iocb->ki_pos, count); + *out =3D vfs_iter_write(ff->backing_file, from, &iocb->ki_pos, + iocb_to_rw_flags(iocb->ki_flags, FUSE_BPF_IOCB_MASK)); + fuse_file_end_write(file, ff->backing_file, iocb->ki_pos, *out); + } else { + struct fuse_bpf_aio_req *aio_req; + + *out =3D -ENOMEM; + aio_req =3D kmem_cache_zalloc(fuse_bpf_aio_request_cachep, GFP_KERNEL); + if (!aio_req) + goto out; + + fuse_file_start_write(file, ff->backing_file, iocb->ki_pos, count); + __sb_writers_release(file_inode(ff->backing_file)->i_sb, SB_FREEZE_WRITE= ); + aio_req->iocb_orig =3D iocb; + kiocb_clone(&aio_req->iocb, iocb, ff->backing_file); + aio_req->iocb.ki_complete =3D fuse_bpf_aio_rw_complete; + refcount_set(&aio_req->ref, 2); + *out =3D vfs_iocb_iter_write(ff->backing_file, &aio_req->iocb, from); + fuse_bpf_aio_put(aio_req); + if (*out !=3D -EIOCBQUEUED) + fuse_bpf_aio_cleanup_handler(aio_req, *out); + } + +out: + inode_unlock(file_inode(file)); + fwio->ret =3D *out; + if (*out < 0) + return *out; + return 0; +} + +static int fuse_file_write_iter_finalize(struct bpf_fuse_args *fa, ssize_t= *out, + struct kiocb *iocb, struct iov_iter *from) +{ + struct fuse_write_iter_out *fwio =3D fa->out_args[0].value; + + *out =3D fwio->ret; + return 0; +} + +int fuse_bpf_file_write_iter(ssize_t *out, struct inode *inode, struct kio= cb *iocb, struct iov_iter *from) +{ + return bpf_fuse_backing(inode, struct fuse_file_write_iter_args, out, + fuse_file_write_iter_initialize_in, + fuse_file_write_iter_initialize_out, + fuse_file_write_iter_backing, + fuse_file_write_iter_finalize, + iocb, from); +} + ssize_t fuse_backing_mmap(struct file *file, struct vm_area_struct *vma) { int ret; @@ -1360,3 +1715,19 @@ int fuse_bpf_access(int *out, struct inode *inode, i= nt mask) fuse_access_initialize_in, fuse_access_initialize_out, fuse_access_backing, fuse_access_finalize, inode, mask); } + +int __init fuse_bpf_init(void) +{ + fuse_bpf_aio_request_cachep =3D kmem_cache_create("fuse_bpf_aio_req", + sizeof(struct fuse_bpf_aio_req), + 0, SLAB_HWCACHE_ALIGN, NULL); + if (!fuse_bpf_aio_request_cachep) + return -ENOMEM; + + return 0; +} + +void __exit fuse_bpf_cleanup(void) +{ + kmem_cache_destroy(fuse_bpf_aio_request_cachep); +} diff --git a/fs/fuse/control.c b/fs/fuse/control.c index 247ef4f76761..685552453751 100644 --- a/fs/fuse/control.c +++ b/fs/fuse/control.c @@ -378,7 +378,7 @@ int __init fuse_ctl_init(void) return register_filesystem(&fuse_ctl_fs_type); } =20 -void __exit fuse_ctl_cleanup(void) +void fuse_ctl_cleanup(void) { unregister_filesystem(&fuse_ctl_fs_type); } diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 1836d09d9ce3..5f19ef5bf124 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -1679,6 +1679,7 @@ static ssize_t fuse_file_read_iter(struct kiocb *iocb= , struct iov_iter *to) struct file *file =3D iocb->ki_filp; struct fuse_file *ff =3D file->private_data; struct inode *inode =3D file_inode(file); + ssize_t ret; =20 if (fuse_is_bad(inode)) return -EIO; @@ -1686,6 +1687,9 @@ static ssize_t fuse_file_read_iter(struct kiocb *iocb= , struct iov_iter *to) if (FUSE_IS_DAX(inode)) return fuse_dax_read_iter(iocb, to); =20 + if (fuse_bpf_file_read_iter(&ret, inode, iocb, to)) + return ret; + if (!(ff->open_flags & FOPEN_DIRECT_IO)) return fuse_cache_read_iter(iocb, to); else @@ -1697,6 +1701,7 @@ static ssize_t fuse_file_write_iter(struct kiocb *ioc= b, struct iov_iter *from) struct file *file =3D iocb->ki_filp; struct fuse_file *ff =3D file->private_data; struct inode *inode =3D file_inode(file); + ssize_t ret =3D 0; =20 if (fuse_is_bad(inode)) return -EIO; @@ -1704,6 +1709,9 @@ static ssize_t fuse_file_write_iter(struct kiocb *ioc= b, struct iov_iter *from) if (FUSE_IS_DAX(inode)) return fuse_dax_write_iter(iocb, from); =20 + if (fuse_bpf_file_write_iter(&ret, inode, iocb, from)) + return ret; + if (!(ff->open_flags & FOPEN_DIRECT_IO)) return fuse_cache_write_iter(iocb, from); else diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 2cbe232c1048..4bc070b81ac2 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1135,7 +1135,7 @@ int fuse_dev_init(void); void fuse_dev_cleanup(void); =20 int fuse_ctl_init(void); -void __exit fuse_ctl_cleanup(void); +void fuse_ctl_cleanup(void); =20 /** * Simple request sending that does request allocation and freeing @@ -1415,6 +1415,8 @@ int fuse_bpf_unlink(int *out, struct inode *dir, stru= ct dentry *entry); int fuse_bpf_release(int *out, struct inode *inode, struct file *file); int fuse_bpf_releasedir(int *out, struct inode *inode, struct file *file); int fuse_bpf_lseek(loff_t *out, struct inode *inode, struct file *file, lo= ff_t offset, int whence); +int fuse_bpf_file_read_iter(ssize_t *out, struct inode *inode, struct kioc= b *iocb, struct iov_iter *to); +int fuse_bpf_file_write_iter(ssize_t *out, struct inode *inode, struct kio= cb *iocb, struct iov_iter *from); int fuse_bpf_file_fallocate(int *out, struct inode *inode, struct file *fi= le, int mode, loff_t offset, loff_t length); int fuse_bpf_lookup(struct dentry **out, struct inode *dir, struct dentry = *entry, unsigned int flags); int fuse_bpf_access(int *out, struct inode *inode, int mask); @@ -1467,6 +1469,16 @@ static inline int fuse_bpf_lseek(loff_t *out, struct= inode *inode, struct file * return 0; } =20 +static inline int fuse_bpf_file_read_iter(ssize_t *out, struct inode *inod= e, struct kiocb *iocb, struct iov_iter *to) +{ + return 0; +} + +static inline int fuse_bpf_file_write_iter(ssize_t *out, struct inode *ino= de, struct kiocb *iocb, struct iov_iter *from) +{ + return 0; +} + static inline int fuse_bpf_file_fallocate(int *out, struct inode *inode, s= truct file *file, int mode, loff_t offset, loff_t length) { return 0; @@ -1517,4 +1529,9 @@ static inline u64 attr_timeout(struct fuse_attr_out *= o) return time_to_jiffies(o->attr_valid, o->attr_valid_nsec); } =20 +#ifdef CONFIG_FUSE_BPF +int __init fuse_bpf_init(void); +void __exit fuse_bpf_cleanup(void); +#endif /* CONFIG_FUSE_BPF */ + #endif /* _FS_FUSE_I_H */ diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index fe80984f099a..fd00910f1eb1 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -2095,11 +2095,21 @@ static int __init fuse_init(void) if (res) goto err_sysfs_cleanup; =20 +#ifdef CONFIG_FUSE_BPF + res =3D fuse_bpf_init(); + if (res) + goto err_ctl_cleanup; +#endif + sanitize_global_limit(&max_user_bgreq); sanitize_global_limit(&max_user_congthresh); =20 return 0; =20 +#ifdef CONFIG_FUSE_BPF + err_ctl_cleanup: + fuse_ctl_cleanup(); +#endif err_sysfs_cleanup: fuse_sysfs_cleanup(); err_dev_cleanup: @@ -2117,6 +2127,9 @@ static void __exit fuse_exit(void) fuse_ctl_cleanup(); fuse_sysfs_cleanup(); fuse_fs_cleanup(); +#ifdef CONFIG_FUSE_BPF + fuse_bpf_cleanup(); +#endif fuse_dev_cleanup(); } =20 diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h index 3ad725a3e968..dbfc8d501bcb 100644 --- a/include/uapi/linux/fuse.h +++ b/include/uapi/linux/fuse.h @@ -748,6 +748,11 @@ struct fuse_read_in { uint32_t padding; }; =20 +// This is likely not what we want +struct fuse_read_iter_out { + uint64_t ret; +}; + #define FUSE_COMPAT_WRITE_IN_SIZE 24 =20 struct fuse_write_in { @@ -765,6 +770,11 @@ struct fuse_write_out { uint32_t padding; }; =20 +// This is likely not what we want +struct fuse_write_iter_out { + uint64_t ret; +}; + #define FUSE_COMPAT_STATFS_SIZE 48 =20 struct fuse_statfs_out { --=20 2.40.0.634.g4ca3ef3211-goog From nobody Wed Dec 17 19:22:07 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 60A51C77B75 for ; Tue, 18 Apr 2023 01:43:44 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231233AbjDRBnm (ORCPT ); Mon, 17 Apr 2023 21:43:42 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53720 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230232AbjDRBmm (ORCPT ); Mon, 17 Apr 2023 21:42:42 -0400 Received: from mail-yb1-xb49.google.com (mail-yb1-xb49.google.com [IPv6:2607:f8b0:4864:20::b49]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id A41295B97 for ; Mon, 17 Apr 2023 18:41:42 -0700 (PDT) Received: by mail-yb1-xb49.google.com with SMTP id 188-20020a250ac5000000b00b9265c9a5e9so2183530ybk.11 for ; Mon, 17 Apr 2023 18:41:42 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20221208; t=1681782092; x=1684374092; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=KLfNH8wOaoEhBmLooOhZO4y32d1iMom08eY0cFO8TR0=; b=WjKoP43zShmBluCRjRG7SpJRr6ZhcFjGdtCDccumEdmZPIzgFTJvIKad4FYUIjjQQy IplrWk3NdRdH2gDDLJQ9Cx/UvfS1qSwkngDqd21ylbmG4ukfCRinSmNNAs5uppGzVJLG lpzuh+saSdzIC/lLpNoMzuaoBLoXD19zIu7i6cCa28+pGHuAQbBcT10P4y35gFo66XQa Tnn9wbyumgtkRMoHf71AAl1+0l3ZdUh4BWT0RNrOjfa3KXsL2/sdfWUPFVrKfqF59nvb JuM8Zm1GDKLnP4FRMd0bLhe8vn0smcB/2wdsoJsfUhL4Z5/rqi8pn3H217Z4nqyfNjCk TSiA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681782092; x=1684374092; 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=KLfNH8wOaoEhBmLooOhZO4y32d1iMom08eY0cFO8TR0=; b=RRTfa6Vcd3Y76yBLkVunQ58yjbMg6OcN2xkEgoqnvhQ2COGJUZH2I3HXDI4ymLn+4H LCWeIv8TC7OESbRcZDt8I60srhHgvk7qehAFynKrxGGoMOYtJ2tBPGeX1BGWjY0MM4G/ zcC+SAD8yAuSQy2EL7nkquXJucrCoAjeTbF+jPJarE9vk0FpL2AcQudzoUY40mlg7p2A 6TXyKcrBq2EcswZungqWpllJx61QGaVr+WwSd0/gjzyay7rIYfF07C4ZujQ1rf1lXEic nQUJDdo575x0mxd7pOzg1XLUUzwrf9qRxvmBU0Sw6XQqjtOkTTSMRWAzhtWcymfgaXNa LIgw== X-Gm-Message-State: AAQBX9fqdwbK723BqWBzdPNXe5ySo/zdas+PFpVZMrK8BjkXux0RiNpV aRzqjCzaYA9bIxLKs91fkT3vZ2ukWPk= X-Google-Smtp-Source: AKy350ZFWi1/rz2iUYQDDITIMq4pvfy+q1dsB6RG635Ep9uEFEuOKaLHWxdFW4DJWTE+80pH5vnhyBiQBOM= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:e67a:98b0:942d:86aa]) (user=drosen job=sendgmr) by 2002:a05:690c:d91:b0:54f:e88d:79ba with SMTP id da17-20020a05690c0d9100b0054fe88d79bamr9714213ywb.5.1681782091925; Mon, 17 Apr 2023 18:41:31 -0700 (PDT) Date: Mon, 17 Apr 2023 18:40:18 -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-19-drosen@google.com> Subject: [RFC PATCH v3 18/37] fuse-bpf: support readdir 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 Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This adds backing support for FUSE_READDIR Signed-off-by: Daniel Rosenberg Signed-off-by: Paul Lawrence --- fs/fuse/backing.c | 194 ++++++++++++++++++++++++++++++++++++++ fs/fuse/fuse_i.h | 6 ++ fs/fuse/readdir.c | 5 + include/uapi/linux/fuse.h | 6 ++ 4 files changed, 211 insertions(+) diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index c7709a880e9c..2908c231a695 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -1669,6 +1669,200 @@ int fuse_bpf_unlink(int *out, struct inode *dir, st= ruct dentry *entry) dir, entry); } =20 +struct fuse_read_args { + struct fuse_read_in in; + struct fuse_read_out out; + struct fuse_buffer buffer; +}; + +static int fuse_readdir_initialize_in(struct bpf_fuse_args *fa, struct fus= e_read_args *args, + struct file *file, struct dir_context *ctx, + bool *force_again, bool *allow_force, bool is_continued) +{ + struct fuse_file *ff =3D file->private_data; + + *fa =3D (struct bpf_fuse_args) { + .info =3D (struct bpf_fuse_meta_info) { + .nodeid =3D ff->nodeid, + .opcode =3D FUSE_READDIR, + }, + .in_numargs =3D 1, + .in_args[0] =3D (struct bpf_fuse_arg) { + .size =3D sizeof(args->in), + .value =3D &args->in, + }, + }; + + args->in =3D (struct fuse_read_in) { + .fh =3D ff->fh, + .offset =3D ctx->pos, + .size =3D PAGE_SIZE, + }; + + *force_again =3D false; + *allow_force =3D true; + return 0; +} + +static int fuse_readdir_initialize_out(struct bpf_fuse_args *fa, struct fu= se_read_args *args, + struct file *file, struct dir_context *ctx, + bool *force_again, bool *allow_force, bool is_continued) +{ + u8 *page =3D (u8 *)__get_free_page(GFP_KERNEL); + + if (!page) + return -ENOMEM; + + fa->flags =3D FUSE_BPF_OUT_ARGVAR; + fa->out_numargs =3D 2; + fa->out_args[0] =3D (struct bpf_fuse_arg) { + .size =3D sizeof(args->out), + .value =3D &args->out, + }; + fa->out_args[1] =3D (struct bpf_fuse_arg) { + .is_buffer =3D true, + .buffer =3D &args->buffer, + }; + args->out =3D (struct fuse_read_out) { + .again =3D 0, + .offset =3D 0, + }; + args->buffer =3D (struct fuse_buffer) { + .data =3D page, + .size =3D PAGE_SIZE, + .alloc_size =3D PAGE_SIZE, + .max_size =3D PAGE_SIZE, + .flags =3D BPF_FUSE_VARIABLE_SIZE, + }; + + return 0; +} + +struct fusebpf_ctx { + struct dir_context ctx; + u8 *addr; + size_t offset; +}; + +static bool filldir(struct dir_context *ctx, const char *name, int namelen, + loff_t offset, u64 ino, unsigned int d_type) +{ + struct fusebpf_ctx *ec =3D container_of(ctx, struct fusebpf_ctx, ctx); + struct fuse_dirent *fd =3D (struct fuse_dirent *)(ec->addr + ec->offset); + + if (ec->offset + sizeof(struct fuse_dirent) + namelen > PAGE_SIZE) + return false; + + *fd =3D (struct fuse_dirent) { + .ino =3D ino, + .off =3D offset, + .namelen =3D namelen, + .type =3D d_type, + }; + + memcpy(fd->name, name, namelen); + ec->offset +=3D FUSE_DIRENT_SIZE(fd); + + return true; +} + +static int parse_dirfile(char *buf, size_t nbytes, struct dir_context *ctx) +{ + while (nbytes >=3D FUSE_NAME_OFFSET) { + struct fuse_dirent *dirent =3D (struct fuse_dirent *) buf; + size_t reclen =3D FUSE_DIRENT_SIZE(dirent); + + if (!dirent->namelen || dirent->namelen > FUSE_NAME_MAX) + return -EIO; + if (reclen > nbytes) + break; + if (memchr(dirent->name, '/', dirent->namelen) !=3D NULL) + return -EIO; + + ctx->pos =3D dirent->off; + if (!dir_emit(ctx, dirent->name, dirent->namelen, dirent->ino, + dirent->type)) + break; + + buf +=3D reclen; + nbytes -=3D reclen; + } + + return 0; +} + +static int fuse_readdir_backing(struct bpf_fuse_args *fa, int *out, + struct file *file, struct dir_context *ctx, + bool *force_again, bool *allow_force, bool is_continued) +{ + struct fuse_file *ff =3D file->private_data; + struct file *backing_dir =3D ff->backing_file; + struct fuse_read_out *fro =3D fa->out_args[0].value; + struct fusebpf_ctx ec; + + ec =3D (struct fusebpf_ctx) { + .ctx.actor =3D filldir, + .ctx.pos =3D ctx->pos, + .addr =3D fa->out_args[1].buffer->data, + }; + + if (!ec.addr) + return -ENOMEM; + + if (!is_continued) + backing_dir->f_pos =3D file->f_pos; + + *out =3D iterate_dir(backing_dir, &ec.ctx); + if (ec.offset =3D=3D 0) + *allow_force =3D false; + fa->out_args[1].buffer->size =3D ec.offset; + + fro->offset =3D ec.ctx.pos; + fro->again =3D false; + + return *out; +} + +static int fuse_readdir_finalize(struct bpf_fuse_args *fa, int *out, + struct file *file, struct dir_context *ctx, + bool *force_again, bool *allow_force, bool is_continued) +{ + struct fuse_read_out *fro =3D fa->out_args[0].value; + struct fuse_file *ff =3D file->private_data; + struct file *backing_dir =3D ff->backing_file; + + *out =3D parse_dirfile(fa->out_args[1].buffer->data, fa->out_args[1].buff= er->size, ctx); + *force_again =3D !!fro->again; + if (*force_again && !*allow_force) + *out =3D -EINVAL; + + ctx->pos =3D fro->offset; + backing_dir->f_pos =3D fro->offset; + + free_page((unsigned long)fa->out_args[1].buffer->data); + return *out; +} + +int fuse_bpf_readdir(int *out, struct inode *inode, struct file *file, str= uct dir_context *ctx) +{ + int ret; + bool allow_force; + bool force_again =3D false; + bool is_continued =3D false; + +again: + ret =3D bpf_fuse_backing(inode, struct fuse_read_args, out, + fuse_readdir_initialize_in, fuse_readdir_initialize_out, + fuse_readdir_backing, fuse_readdir_finalize, + file, ctx, &force_again, &allow_force, is_continued); + if (force_again && *out >=3D 0) { + is_continued =3D true; + goto again; + } + + return ret; +} + static int fuse_access_initialize_in(struct bpf_fuse_args *fa, struct fuse= _access_in *in, struct inode *inode, int mask) { diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 4bc070b81ac2..fb3a77b79b0f 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1419,6 +1419,7 @@ int fuse_bpf_file_read_iter(ssize_t *out, struct inod= e *inode, struct kiocb *ioc int fuse_bpf_file_write_iter(ssize_t *out, struct inode *inode, struct kio= cb *iocb, struct iov_iter *from); int fuse_bpf_file_fallocate(int *out, struct inode *inode, struct file *fi= le, int mode, loff_t offset, loff_t length); int fuse_bpf_lookup(struct dentry **out, struct inode *dir, struct dentry = *entry, unsigned int flags); +int fuse_bpf_readdir(int *out, struct inode *inode, struct file *file, str= uct dir_context *ctx); int fuse_bpf_access(int *out, struct inode *inode, int mask); =20 #else @@ -1489,6 +1490,11 @@ static inline int fuse_bpf_lookup(struct dentry **ou= t, struct inode *dir, struct return 0; } =20 +static inline int fuse_bpf_readdir(int *out, struct inode *inode, struct f= ile *file, struct dir_context *ctx) +{ + return 0; +} + static inline int fuse_bpf_access(int *out, struct inode *inode, int mask) { return 0; diff --git a/fs/fuse/readdir.c b/fs/fuse/readdir.c index dc603479b30e..cc6548f314f2 100644 --- a/fs/fuse/readdir.c +++ b/fs/fuse/readdir.c @@ -20,6 +20,8 @@ static bool fuse_use_readdirplus(struct inode *dir, struc= t dir_context *ctx) =20 if (!fc->do_readdirplus) return false; + if (fi->nodeid =3D=3D 0) + return false; if (!fc->readdirplus_auto) return true; if (test_and_clear_bit(FUSE_I_ADVISE_RDPLUS, &fi->state)) @@ -582,6 +584,9 @@ int fuse_readdir(struct file *file, struct dir_context = *ctx) if (fuse_is_bad(inode)) return -EIO; =20 + if (fuse_bpf_readdir(&err, inode, file, ctx)) + return err; + mutex_lock(&ff->readdir.lock); =20 err =3D UNCACHED; diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h index dbfc8d501bcb..e779064f5fad 100644 --- a/include/uapi/linux/fuse.h +++ b/include/uapi/linux/fuse.h @@ -748,6 +748,12 @@ struct fuse_read_in { uint32_t padding; }; =20 +struct fuse_read_out { + uint64_t offset; + uint32_t again; + uint32_t padding; +}; + // This is likely not what we want struct fuse_read_iter_out { uint64_t ret; --=20 2.40.0.634.g4ca3ef3211-goog From nobody Wed Dec 17 19:22:07 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 EAB7CC77B76 for ; Tue, 18 Apr 2023 01:43:45 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231239AbjDRBno (ORCPT ); Mon, 17 Apr 2023 21:43:44 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:54274 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231147AbjDRBmo (ORCPT ); Mon, 17 Apr 2023 21:42:44 -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 B9B33659E for ; Mon, 17 Apr 2023 18:41:45 -0700 (PDT) Received: by mail-yw1-x1149.google.com with SMTP id 00721157ae682-54faf2e22afso139560307b3.7 for ; Mon, 17 Apr 2023 18:41:45 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20221208; t=1681782094; x=1684374094; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=yHPyQOgLAeTv27Hmk1jV/Qw6lBeg9KsOnD3+nmdgTBc=; b=WfhU75GkbNBBGPNdlQQqWpIVlOAmIt1xhsdC8ETWkv8e89D2kVJXdfo2nDvpBH4oXa nqAcJ0rkvNDwDtDwu+BjaH1Q8oFoMz/kg0Or59UBboMN/KJfqzGCb2RqOb/U/0RSQ32t dDjHoFKhRwU61k68E2xItY5TqBQZUfdSkdlDFCBzv5zViEbPhG4YAFlvC6a4+lykVyz3 PN4aTi6QZSAJo3dKZSZtek7fRmsqdmoYf8DNy1EJQUBpOuz65vrZutPzrVi47/+A8syB akZ3w8TvKAFp/kH3G4HD2NJLL5M0F242/508++FqsS0CRy19r4HQJW7deZinJ5f6k7Yh LtIA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681782094; x=1684374094; 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=yHPyQOgLAeTv27Hmk1jV/Qw6lBeg9KsOnD3+nmdgTBc=; b=RhfkT4LfBj3nbeS2AA2iSd59TOIarNWblWILdZju3gjpG75xyhLJjC+PtV1ly9Ktn0 FCqwj8TEPYour2UaRxDtUDNn0X42iPodisXL03h8IcAwCshqfweRXc0qFxN0KYLSPgKS 9hL7Yg9sn+X5x8OY3SMK4XJCoUqg/eJozlM4zE3pnWAPyy8qVh6csJ3gA3NOtWOsUhPo XbcKV9adweBj9zmhAQ4B/9Jf3LNnlYyY1hs3J/ARxhwHeYB+V7mxXAz3uqZ5PfCGizfv TOK154hAKGDMJ0aN+y74Od+PcOCnlFtbok6QWlBwDTtorGMm98UnfdjaZZj8J6qsSuQq Pqkw== X-Gm-Message-State: AAQBX9dp/twb0FveMRi7/h6RivlOtk1gQMdr89jgYua7hn0kbgvCRjA3 hrPBVTbubtO/lSmsK/241lZU0gs49VI= X-Google-Smtp-Source: AKy350ZGdfGHneASHc+2qR8hpHc/w9BF7/xnpO/COHxv9rPK2B1jF3L8CR7fh6IuzYQx0u6d+K6DOtAYt2s= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:e67a:98b0:942d:86aa]) (user=drosen job=sendgmr) by 2002:a25:8051:0:b0:b92:40f8:a46f with SMTP id a17-20020a258051000000b00b9240f8a46fmr4359628ybn.2.1681782094311; Mon, 17 Apr 2023 18:41:34 -0700 (PDT) Date: Mon, 17 Apr 2023 18:40:19 -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-20-drosen@google.com> Subject: [RFC PATCH v3 19/37] fuse-bpf: Add support for sync operations 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 Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This adds backing support for FUSE_FLUSH, FUSE_FSYNC, and FUSE_FSYNCDIR. Signed-off-by: Daniel Rosenberg Signed-off-by: Paul Lawrence --- fs/fuse/backing.c | 147 ++++++++++++++++++++++++++++++++++++++++++++++ fs/fuse/dir.c | 3 + fs/fuse/file.c | 7 +++ fs/fuse/fuse_i.h | 18 ++++++ 4 files changed, 175 insertions(+) diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index 2908c231a695..30492f7b2a05 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -659,6 +659,59 @@ int fuse_bpf_releasedir(int *out, struct inode *inode,= struct file *file) fuse_release_backing, fuse_release_finalize, inode, file); } =20 +static int fuse_flush_initialize_in(struct bpf_fuse_args *fa, struct fuse_= flush_in *ffi, + struct file *file, fl_owner_t id) +{ + struct fuse_file *fuse_file =3D file->private_data; + + *ffi =3D (struct fuse_flush_in) { + .fh =3D fuse_file->fh, + }; + + *fa =3D (struct bpf_fuse_args) { + .info =3D (struct bpf_fuse_meta_info) { + .nodeid =3D get_node_id(file->f_inode), + .opcode =3D FUSE_FLUSH, + }, + .in_numargs =3D 1, + .in_args[0].size =3D sizeof(*ffi), + .in_args[0].value =3D ffi, + .flags =3D FUSE_BPF_FORCE, + }; + + return 0; +} + +static int fuse_flush_initialize_out(struct bpf_fuse_args *fa, struct fuse= _flush_in *ffi, + struct file *file, fl_owner_t id) +{ + return 0; +} + +static int fuse_flush_backing(struct bpf_fuse_args *fa, int *out, struct f= ile *file, fl_owner_t id) +{ + struct fuse_file *fuse_file =3D file->private_data; + struct file *backing_file =3D fuse_file->backing_file; + + *out =3D 0; + if (backing_file->f_op->flush) + *out =3D backing_file->f_op->flush(backing_file, id); + return *out; +} + +static int fuse_flush_finalize(struct bpf_fuse_args *fa, int *out, struct = file *file, fl_owner_t id) +{ + return 0; +} + +int fuse_bpf_flush(int *out, struct inode *inode, struct file *file, fl_ow= ner_t id) +{ + return bpf_fuse_backing(inode, struct fuse_flush_in, out, + fuse_flush_initialize_in, fuse_flush_initialize_out, + fuse_flush_backing, fuse_flush_finalize, + file, id); +} + struct fuse_lseek_args { struct fuse_lseek_in in; struct fuse_lseek_out out; @@ -748,6 +801,100 @@ int fuse_bpf_lseek(loff_t *out, struct inode *inode, = struct file *file, loff_t o file, offset, whence); } =20 +static int fuse_fsync_initialize_in(struct bpf_fuse_args *fa, struct fuse_= fsync_in *in, + struct file *file, loff_t start, loff_t end, int datasync) +{ + struct fuse_file *fuse_file =3D file->private_data; + + *in =3D (struct fuse_fsync_in) { + .fh =3D fuse_file->fh, + .fsync_flags =3D datasync ? FUSE_FSYNC_FDATASYNC : 0, + }; + + *fa =3D (struct bpf_fuse_args) { + .info =3D (struct bpf_fuse_meta_info) { + .nodeid =3D get_fuse_inode(file->f_inode)->nodeid, + .opcode =3D FUSE_FSYNC, + }, + .in_numargs =3D 1, + .in_args[0].size =3D sizeof(*in), + .in_args[0].value =3D in, + .flags =3D FUSE_BPF_FORCE, + }; + + return 0; +} + +static int fuse_fsync_initialize_out(struct bpf_fuse_args *fa, struct fuse= _fsync_in *ffi, + struct file *file, loff_t start, loff_t end, int datasync) +{ + return 0; +} + +static int fuse_fsync_backing(struct bpf_fuse_args *fa, int *out, + struct file *file, loff_t start, loff_t end, int datasync) +{ + struct fuse_file *fuse_file =3D file->private_data; + struct file *backing_file =3D fuse_file->backing_file; + const struct fuse_fsync_in *ffi =3D fa->in_args[0].value; + int new_datasync =3D (ffi->fsync_flags & FUSE_FSYNC_FDATASYNC) ? 1 : 0; + + *out =3D vfs_fsync(backing_file, new_datasync); + return 0; +} + +static int fuse_fsync_finalize(struct bpf_fuse_args *fa, int *out, + struct file *file, loff_t start, loff_t end, int datasync) +{ + return 0; +} + +int fuse_bpf_fsync(int *out, struct inode *inode, struct file *file, loff_= t start, loff_t end, int datasync) +{ + return bpf_fuse_backing(inode, struct fuse_fsync_in, out, + fuse_fsync_initialize_in, fuse_fsync_initialize_out, + fuse_fsync_backing, fuse_fsync_finalize, + file, start, end, datasync); +} + +static int fuse_dir_fsync_initialize_in(struct bpf_fuse_args *fa, struct f= use_fsync_in *in, + struct file *file, loff_t start, loff_t end, int datasync) +{ + struct fuse_file *fuse_file =3D file->private_data; + + *in =3D (struct fuse_fsync_in) { + .fh =3D fuse_file->fh, + .fsync_flags =3D datasync ? FUSE_FSYNC_FDATASYNC : 0, + }; + + *fa =3D (struct bpf_fuse_args) { + .info =3D (struct bpf_fuse_meta_info) { + .nodeid =3D get_fuse_inode(file->f_inode)->nodeid, + .opcode =3D FUSE_FSYNCDIR, + }, + .in_numargs =3D 1, + .in_args[0].size =3D sizeof(*in), + .in_args[0].value =3D in, + .flags =3D FUSE_BPF_FORCE, + }; + + return 0; +} + +static int fuse_dir_fsync_initialize_out(struct bpf_fuse_args *fa, struct = fuse_fsync_in *ffi, + struct file *file, loff_t start, loff_t end, int datasync) +{ + return 0; +} + +int fuse_bpf_dir_fsync(int *out, struct inode *inode, struct file *file, l= off_t start, loff_t end, int datasync) +{ + return bpf_fuse_backing(inode, struct fuse_fsync_in, out, + fuse_dir_fsync_initialize_in, fuse_dir_fsync_initialize_out, + fuse_fsync_backing, fuse_fsync_finalize, + file, start, end, datasync); +} + static inline void fuse_bpf_aio_put(struct fuse_bpf_aio_req *aio_req) { if (refcount_dec_and_test(&aio_req->ref)) diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index a763a45fa973..5ce65f696980 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1666,6 +1666,9 @@ static int fuse_dir_fsync(struct file *file, loff_t s= tart, loff_t end, if (fuse_is_bad(inode)) return -EIO; =20 + if (fuse_bpf_dir_fsync(&err, inode, file, start, end, datasync)) + return err; + if (fc->no_fsyncdir) return 0; =20 diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 5f19ef5bf124..a4a0aeb28e4a 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -554,10 +554,14 @@ static int fuse_flush(struct file *file, fl_owner_t i= d) struct inode *inode =3D file_inode(file); struct fuse_mount *fm =3D get_fuse_mount(inode); struct fuse_file *ff =3D file->private_data; + int err; =20 if (fuse_is_bad(inode)) return -EIO; =20 + if (fuse_bpf_flush(&err, file_inode(file), file, id)) + return err; + if (ff->open_flags & FOPEN_NOFLUSH && !fm->fc->writeback_cache) return 0; =20 @@ -615,6 +619,9 @@ static int fuse_fsync(struct file *file, loff_t start, = loff_t end, if (fuse_is_bad(inode)) return -EIO; =20 + if (fuse_bpf_fsync(&err, inode, file, start, end, datasync)) + return err; + inode_lock(inode); =20 /* diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index fb3a77b79b0f..e60207bf66de 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1414,7 +1414,10 @@ int fuse_bpf_rmdir(int *out, struct inode *dir, stru= ct dentry *entry); int fuse_bpf_unlink(int *out, struct inode *dir, struct dentry *entry); int fuse_bpf_release(int *out, struct inode *inode, struct file *file); int fuse_bpf_releasedir(int *out, struct inode *inode, struct file *file); +int fuse_bpf_flush(int *out, struct inode *inode, struct file *file, fl_ow= ner_t id); int fuse_bpf_lseek(loff_t *out, struct inode *inode, struct file *file, lo= ff_t offset, int whence); +int fuse_bpf_fsync(int *out, struct inode *inode, struct file *file, loff_= t start, loff_t end, int datasync); +int fuse_bpf_dir_fsync(int *out, struct inode *inode, struct file *file, l= off_t start, loff_t end, int datasync); int fuse_bpf_file_read_iter(ssize_t *out, struct inode *inode, struct kioc= b *iocb, struct iov_iter *to); int fuse_bpf_file_write_iter(ssize_t *out, struct inode *inode, struct kio= cb *iocb, struct iov_iter *from); int fuse_bpf_file_fallocate(int *out, struct inode *inode, struct file *fi= le, int mode, loff_t offset, loff_t length); @@ -1465,11 +1468,26 @@ static inline int fuse_bpf_releasedir(int *out, str= uct inode *inode, struct file return 0; } =20 +static inline int fuse_bpf_flush(int *out, struct inode *inode, struct fil= e *file, fl_owner_t id) +{ + return 0; +} + static inline int fuse_bpf_lseek(loff_t *out, struct inode *inode, struct = file *file, loff_t offset, int whence) { return 0; } =20 +static inline int fuse_bpf_fsync(int *out, struct inode *inode, struct fil= e *file, loff_t start, loff_t end, int datasync) +{ + return 0; +} + +static inline int fuse_bpf_dir_fsync(int *out, struct inode *inode, struct= file *file, loff_t start, loff_t end, int datasync) +{ + return 0; +} + static inline int fuse_bpf_file_read_iter(ssize_t *out, struct inode *inod= e, struct kiocb *iocb, struct iov_iter *to) { return 0; --=20 2.40.0.634.g4ca3ef3211-goog From nobody Wed Dec 17 19:22:07 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 0DB54C77B76 for ; Tue, 18 Apr 2023 01:43:36 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231217AbjDRBnd (ORCPT ); Mon, 17 Apr 2023 21:43:33 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53888 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230448AbjDRBmb (ORCPT ); Mon, 17 Apr 2023 21:42:31 -0400 Received: from mail-yw1-x114a.google.com (mail-yw1-x114a.google.com [IPv6:2607:f8b0:4864:20::114a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 269427DBB for ; Mon, 17 Apr 2023 18:41:37 -0700 (PDT) Received: by mail-yw1-x114a.google.com with SMTP id 00721157ae682-54faf2e22afso139560967b3.7 for ; Mon, 17 Apr 2023 18:41:37 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20221208; t=1681782097; x=1684374097; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=mM3VEDiu9mRhnxPoyHTlcLFFgOhw6pqe5rwY+welbQE=; b=73fduone9MSuyhI/Vj/EBtrUNK1HlKqIXi2R/1WFSMzTZeGfp5VAaZvM2T8Z1A2hT+ TW4GWEQGhkVXdJu5a2/KltZOzXD1AQjbMH7CIR0D5QETP4evRehG6EDqGgw/XSPXtGTD vVM4cpWKlgSrNskNHkKA3nkSoYyOvtjcA/osyf9wix2pqxY+xmbkY9RApyC6c7CrreuR ziFJAw8vrjwNraBMSSyT/8rY6SHmX6pnezYrpgPEU6LWtlkL/qSjF7IjcX7GLUKf/v2+ cH0jszXzfhYMnGCvrW87cOI//DWQdwJdFT43fsUzo5cCWupIGklT1pKXaLA4TRnEWYuZ o5oQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681782097; x=1684374097; 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=mM3VEDiu9mRhnxPoyHTlcLFFgOhw6pqe5rwY+welbQE=; b=D7yHPqLz2oKwxd8HS+RO7lwUSo0bJCe+9XTEHx9nR0rqUSx6kbqzGek5psI04B1Wb1 mdhdMwViGOlKIPigaDg3H6n2WB1L58QhILknUttpJviVVtkSegLqkqt3CTjbkf7/L412 mbw6GOMWZnJcJ2zugt4thdohFcD+dDvfqedIzu6ivgr5gtaicNYrS+K7MJDL3isxpPw6 NHOyor0bS4ytSXo0fPemuNq1lD+DSAYVCb0vpZX/jNY0y3WnxM288+IXN/0kET2oI38M 616Ht00KLO5COcrN37SIglrAB0DggUOD2JivxTzEZS0mCOHGEXsBBBt1uOCGLuCmnoMI KhWA== X-Gm-Message-State: AAQBX9dnYw7R8wes6qou95yzA8rQzqn7lyU6MH63EPpwUw6tGF9lF42B uR0jq3lR/2YWCwP8yc91+djtQXkCiPs= X-Google-Smtp-Source: AKy350Zd0zSOj3A4RVvFzehl/6ZADh+4u0SSYtyXG0ZS+awJjZupIGXSlNBiv6NTnINRlJ+t8jkY64w9PeM= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:e67a:98b0:942d:86aa]) (user=drosen job=sendgmr) by 2002:a25:d24d:0:b0:b90:1777:7194 with SMTP id j74-20020a25d24d000000b00b9017777194mr8137204ybg.6.1681782096895; Mon, 17 Apr 2023 18:41:36 -0700 (PDT) Date: Mon, 17 Apr 2023 18:40:20 -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-21-drosen@google.com> Subject: [RFC PATCH v3 20/37] fuse-bpf: Add Rename support 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 Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This adds backing support for FUSE_RENAME and FUSE_RENAME2 Signed-off-by: Daniel Rosenberg Signed-off-by: Paul Lawrence --- fs/fuse/backing.c | 250 ++++++++++++++++++++++++++++++++++++++++++++++ fs/fuse/dir.c | 7 ++ fs/fuse/fuse_i.h | 18 ++++ 3 files changed, 275 insertions(+) diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index 30492f7b2a05..d3a706b55905 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -1747,6 +1747,256 @@ int fuse_bpf_rmdir(int *out, struct inode *dir, str= uct dentry *entry) dir, entry); } =20 +static int fuse_rename_backing_common(struct inode *olddir, + struct dentry *oldent, + struct inode *newdir, + struct dentry *newent, unsigned int flags) +{ + int err =3D 0; + struct path old_backing_path; + struct path new_backing_path; + struct dentry *old_backing_dir_dentry; + struct dentry *old_backing_dentry; + struct dentry *new_backing_dir_dentry; + struct dentry *new_backing_dentry; + struct dentry *trap =3D NULL; + struct inode *target_inode; + struct renamedata rd; + + //TODO Actually deal with changing anything that isn't a flag + get_fuse_backing_path(oldent, &old_backing_path); + if (!old_backing_path.dentry) + return -EBADF; + get_fuse_backing_path(newent, &new_backing_path); + if (!new_backing_path.dentry) { + /* + * TODO A file being moved from a backing path to another + * backing path which is not yet instrumented with FUSE-BPF. + * This may be slow and should be substituted with something + * more clever. + */ + err =3D -EXDEV; + goto put_old_path; + } + if (new_backing_path.mnt !=3D old_backing_path.mnt) { + err =3D -EXDEV; + goto put_new_path; + } + old_backing_dentry =3D old_backing_path.dentry; + new_backing_dentry =3D new_backing_path.dentry; + old_backing_dir_dentry =3D dget_parent(old_backing_dentry); + new_backing_dir_dentry =3D dget_parent(new_backing_dentry); + target_inode =3D d_inode(newent); + + trap =3D lock_rename(old_backing_dir_dentry, new_backing_dir_dentry); + if (trap =3D=3D old_backing_dentry) { + err =3D -EINVAL; + goto put_parents; + } + if (trap =3D=3D new_backing_dentry) { + err =3D -ENOTEMPTY; + goto put_parents; + } + + rd =3D (struct renamedata) { + .old_mnt_idmap =3D &nop_mnt_idmap, + .old_dir =3D d_inode(old_backing_dir_dentry), + .old_dentry =3D old_backing_dentry, + .new_mnt_idmap =3D &nop_mnt_idmap, + .new_dir =3D d_inode(new_backing_dir_dentry), + .new_dentry =3D new_backing_dentry, + .flags =3D flags, + }; + err =3D vfs_rename(&rd); + if (err) + goto unlock; + if (target_inode) + fsstack_copy_attr_all(target_inode, + get_fuse_inode(target_inode)->backing_inode); + fsstack_copy_attr_all(d_inode(oldent), d_inode(old_backing_dentry)); +unlock: + unlock_rename(old_backing_dir_dentry, new_backing_dir_dentry); +put_parents: + dput(new_backing_dir_dentry); + dput(old_backing_dir_dentry); +put_new_path: + path_put(&new_backing_path); +put_old_path: + path_put(&old_backing_path); + return err; +} + +struct fuse_rename2_args { + struct fuse_rename2_in in; + struct fuse_buffer old_name; + struct fuse_buffer new_name; +}; + +static int fuse_rename2_initialize_in(struct bpf_fuse_args *fa, struct fus= e_rename2_args *args, + struct inode *olddir, struct dentry *oldent, + struct inode *newdir, struct dentry *newent, + unsigned int flags) +{ + *args =3D (struct fuse_rename2_args) { + .in =3D (struct fuse_rename2_in) { + .newdir =3D get_node_id(newdir), + .flags =3D flags, + }, + .old_name =3D (struct fuse_buffer) { + .data =3D (void *) oldent->d_name.name, + .size =3D oldent->d_name.len + 1, + .flags =3D BPF_FUSE_IMMUTABLE, + }, + .new_name =3D (struct fuse_buffer) { + .data =3D (void *) newent->d_name.name, + .size =3D newent->d_name.len + 1, + .flags =3D BPF_FUSE_IMMUTABLE, + }, + + }; + *fa =3D (struct bpf_fuse_args) { + .info =3D (struct bpf_fuse_meta_info) { + .nodeid =3D get_node_id(olddir), + .opcode =3D FUSE_RENAME2, + }, + .in_numargs =3D 3, + .in_args[0] =3D (struct bpf_fuse_arg) { + .size =3D sizeof(args->in), + .value =3D &args->in, + }, + .in_args[1] =3D (struct bpf_fuse_arg) { + .is_buffer =3D true, + .buffer =3D &args->old_name, + }, + .in_args[2] =3D (struct bpf_fuse_arg) { + .is_buffer =3D true, + .buffer =3D &args->new_name, + }, + }; + + return 0; +} + +static int fuse_rename2_initialize_out(struct bpf_fuse_args *fa, struct fu= se_rename2_args *args, + struct inode *olddir, struct dentry *oldent, + struct inode *newdir, struct dentry *newent, + unsigned int flags) +{ + return 0; +} + +static int fuse_rename2_backing(struct bpf_fuse_args *fa, int *out, + struct inode *olddir, struct dentry *oldent, + struct inode *newdir, struct dentry *newent, + unsigned int flags) +{ + const struct fuse_rename2_args *fri =3D fa->in_args[0].value; + + /* TODO: deal with changing dirs/ents */ + *out =3D fuse_rename_backing_common(olddir, oldent, newdir, newent, + fri->in.flags); + return *out; +} + +static int fuse_rename2_finalize(struct bpf_fuse_args *fa, int *out, + struct inode *olddir, struct dentry *oldent, + struct inode *newdir, struct dentry *newent, + unsigned int flags) +{ + return 0; +} + +int fuse_bpf_rename2(int *out, struct inode *olddir, struct dentry *oldent, + struct inode *newdir, struct dentry *newent, + unsigned int flags) +{ + return bpf_fuse_backing(olddir, struct fuse_rename2_args, out, + fuse_rename2_initialize_in, fuse_rename2_initialize_out, + fuse_rename2_backing, fuse_rename2_finalize, + olddir, oldent, newdir, newent, flags); +} + +struct fuse_rename_args { + struct fuse_rename_in in; + struct fuse_buffer old_name; + struct fuse_buffer new_name; +}; + +static int fuse_rename_initialize_in(struct bpf_fuse_args *fa, struct fuse= _rename_args *args, + struct inode *olddir, struct dentry *oldent, + struct inode *newdir, struct dentry *newent) +{ + *args =3D (struct fuse_rename_args) { + .in =3D (struct fuse_rename_in) { + .newdir =3D get_node_id(newdir), + }, + .old_name =3D (struct fuse_buffer) { + .data =3D (void *) oldent->d_name.name, + .size =3D oldent->d_name.len + 1, + .flags =3D BPF_FUSE_IMMUTABLE, + }, + .new_name =3D (struct fuse_buffer) { + .data =3D (void *) newent->d_name.name, + .size =3D newent->d_name.len + 1, + .flags =3D BPF_FUSE_IMMUTABLE, + }, + + }; + *fa =3D (struct bpf_fuse_args) { + .info =3D (struct bpf_fuse_meta_info) { + .nodeid =3D get_node_id(olddir), + .opcode =3D FUSE_RENAME, + }, + .in_numargs =3D 3, + .in_args[0] =3D (struct bpf_fuse_arg) { + .size =3D sizeof(args->in), + .value =3D &args->in, + }, + .in_args[1] =3D (struct bpf_fuse_arg) { + .is_buffer =3D true, + .buffer =3D &args->old_name, + }, + .in_args[2] =3D (struct bpf_fuse_arg) { + .is_buffer =3D true, + .buffer =3D &args->new_name, + }, + }; + + return 0; +} + +static int fuse_rename_initialize_out(struct bpf_fuse_args *fa, struct fus= e_rename_args *args, + struct inode *olddir, struct dentry *oldent, + struct inode *newdir, struct dentry *newent) +{ + return 0; +} + +static int fuse_rename_backing(struct bpf_fuse_args *fa, int *out, + struct inode *olddir, struct dentry *oldent, + struct inode *newdir, struct dentry *newent) +{ + /* TODO: deal with changing dirs/ents */ + *out =3D fuse_rename_backing_common(olddir, oldent, newdir, newent, 0); + return *out; +} + +static int fuse_rename_finalize(struct bpf_fuse_args *fa, int *out, + struct inode *olddir, struct dentry *oldent, + struct inode *newdir, struct dentry *newent) +{ + return 0; +} + +int fuse_bpf_rename(int *out, struct inode *olddir, struct dentry *oldent, + struct inode *newdir, struct dentry *newent) +{ + return bpf_fuse_backing(olddir, struct fuse_rename_args, out, + fuse_rename_initialize_in, fuse_rename_initialize_out, + fuse_rename_backing, fuse_rename_finalize, + olddir, oldent, newdir, newent); +} + static int fuse_unlink_initialize_in(struct bpf_fuse_args *fa, struct fuse= _buffer *name, struct inode *dir, struct dentry *entry) { diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 5ce65f696980..086e3ecada19 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1184,6 +1184,10 @@ static int fuse_rename2(struct mnt_idmap *idmap, str= uct inode *olddir, return -EINVAL; =20 if (flags) { + if (fuse_bpf_rename2(&err, olddir, oldent, newdir, newent, flags)) + return err; + + /* TODO: how should this go with bpfs involved? */ if (fc->no_rename2 || fc->minor < 23) return -EINVAL; =20 @@ -1195,6 +1199,9 @@ static int fuse_rename2(struct mnt_idmap *idmap, stru= ct inode *olddir, err =3D -EINVAL; } } else { + if (fuse_bpf_rename(&err, olddir, oldent, newdir, newent)) + return err; + err =3D fuse_rename_common(olddir, oldent, newdir, newent, 0, FUSE_RENAME, sizeof(struct fuse_rename_in)); diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index e60207bf66de..5c8bd2f76fb9 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1411,6 +1411,11 @@ int fuse_bpf_create_open(int *out, struct inode *dir= , struct dentry *entry, int fuse_bpf_mknod(int *out, struct inode *dir, struct dentry *entry, umod= e_t mode, dev_t rdev); int fuse_bpf_mkdir(int *out, struct inode *dir, struct dentry *entry, umod= e_t mode); int fuse_bpf_rmdir(int *out, struct inode *dir, struct dentry *entry); +int fuse_bpf_rename2(int *out, struct inode *olddir, struct dentry *oldent, + struct inode *newdir, struct dentry *newent, + unsigned int flags); +int fuse_bpf_rename(int *out, struct inode *olddir, struct dentry *oldent, + struct inode *newdir, struct dentry *newent); int fuse_bpf_unlink(int *out, struct inode *dir, struct dentry *entry); int fuse_bpf_release(int *out, struct inode *inode, struct file *file); int fuse_bpf_releasedir(int *out, struct inode *inode, struct file *file); @@ -1453,6 +1458,19 @@ static inline int fuse_bpf_rmdir(int *out, struct in= ode *dir, struct dentry *ent return 0; } =20 +static inline int fuse_bpf_rename2(int *out, struct inode *olddir, struct = dentry *oldent, + struct inode *newdir, struct dentry *newent, + unsigned int flags) +{ + return 0; +} + +static inline int fuse_bpf_rename(int *out, struct inode *olddir, struct d= entry *oldent, + struct inode *newdir, struct dentry *newent) +{ + return 0; +} + static inline int fuse_bpf_unlink(int *out, struct inode *dir, struct dent= ry *entry) { return 0; --=20 2.40.0.634.g4ca3ef3211-goog From nobody Wed Dec 17 19:22:07 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 37D34C77B75 for ; Tue, 18 Apr 2023 01:44:09 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230271AbjDRBoI (ORCPT ); Mon, 17 Apr 2023 21:44:08 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:54316 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230262AbjDRBmz (ORCPT ); Mon, 17 Apr 2023 21:42:55 -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 C027F6A4E for ; Mon, 17 Apr 2023 18:41:50 -0700 (PDT) Received: by mail-yw1-x1149.google.com with SMTP id 00721157ae682-552e3233b26so32074897b3.9 for ; Mon, 17 Apr 2023 18:41:50 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20221208; t=1681782099; x=1684374099; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=nG4okeL2v0Jdp53r3S0fEOBeF2fbrMx1qVzxXkIqqTE=; b=u0cBnWjjy4rLWn/KKVJ6YrMH0yDyoE7VqGPJ1ZiIQiauc+J5y96wH58/hT/Tgc/dtZ qAd8rv3qhW+TfRFJg9rTk5e1V98BfhsrmoHYLyb0zIlV624hfurC4g6h1SkmtTdbB341 9N9rKVQFrHEQHE+epE3uDxR+62tw6wmYtb5fVlZg/aQUSKFfRzHj2Vdiu4aT0COlQHJD 68+YCVPzX890hTZGkZW7BXMMybXNvFt12+o4RZx6+p/OOvSLdYn1UPSLG/g5tTN3SCq7 v6msMYUXxk8pTJQWAng43xaxeTq5LLYD7um28T6hLW6CQD3nYt1tEV7tT85OQc7zg2Ss jdJw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681782099; x=1684374099; 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=nG4okeL2v0Jdp53r3S0fEOBeF2fbrMx1qVzxXkIqqTE=; b=aZCcAFOnarCdVorQaqCnsxF6f0Tts7udk/YcuSpa5OiwlsdCa7wI7l6tprjAMTMrPZ 1j5E/7ngLC0bKGPvQnrKsI80erKrBZiBWeOuuR9XSWbW13IdnxDZklfDrMVNiUbqTW/Z VdCR7vN/Mnkt97m35JdbeyEoFIyd/70bc9DFKOcZkYst3cOcl6Wde82u5LojgFUXrAwo hFUJyToSxGccXR1rIjVHGVZDAuWfRVufiTiXGG0KCNdNv+CjV3y+cnwyvE46DdtLB/JD 3C6QNi09sQzbsLeefFJYqlng3aMx4S/fbiLuZSfHPgsmPVH/Dk5dX+hvynfMbmlfh0An Ju0g== X-Gm-Message-State: AAQBX9fX31Brb0eDQkx+2dH0xE1r3DqgWd95ZTOtBLCHNfsvUz/LTQIW 2iKcUAgwTkCV/USJaf5WtGhwRZubxrw= X-Google-Smtp-Source: AKy350YhytlOZ02ss3SEwZRBfhwnYIswD0E28QPdA5RI4z8jwAukpSy/1DOKgKx6QO6ewMAWxbSCDtbtTCc= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:e67a:98b0:942d:86aa]) (user=drosen job=sendgmr) by 2002:a81:ae12:0:b0:54c:2409:c306 with SMTP id m18-20020a81ae12000000b0054c2409c306mr10197096ywh.6.1681782098995; Mon, 17 Apr 2023 18:41:38 -0700 (PDT) Date: Mon, 17 Apr 2023 18:40:21 -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-22-drosen@google.com> Subject: [RFC PATCH v3 21/37] fuse-bpf: Add attr support 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 Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This adds backing support for FUSE_GETATTR, FUSE_SETATTR, and FUSE_STATFS Signed-off-by: Daniel Rosenberg Signed-off-by: Paul Lawrence --- fs/fuse/backing.c | 288 ++++++++++++++++++++++++++++++++++++++++++++++ fs/fuse/dir.c | 68 ++--------- fs/fuse/fuse_i.h | 102 ++++++++++++++++ fs/fuse/inode.c | 17 +-- 4 files changed, 405 insertions(+), 70 deletions(-) diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index d3a706b55905..6a6130a16d2b 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -2066,6 +2066,294 @@ int fuse_bpf_unlink(int *out, struct inode *dir, st= ruct dentry *entry) dir, entry); } =20 +struct fuse_getattr_args { + struct fuse_getattr_in in; + struct fuse_attr_out out; +}; + +static int fuse_getattr_initialize_in(struct bpf_fuse_args *fa, struct fus= e_getattr_args *args, + const struct dentry *entry, struct kstat *stat, + u32 request_mask, unsigned int flags) +{ + args->in =3D (struct fuse_getattr_in) { + .getattr_flags =3D flags, + .fh =3D -1, /* TODO is this OK? */ + }; + + *fa =3D (struct bpf_fuse_args) { + .info =3D (struct bpf_fuse_meta_info) { + .nodeid =3D get_node_id(entry->d_inode), + .opcode =3D FUSE_GETATTR, + }, + .in_numargs =3D 1, + .in_args[0] =3D (struct bpf_fuse_arg) { + .size =3D sizeof(args->in), + .value =3D &args->in, + }, + }; + + return 0; +} + +static int fuse_getattr_initialize_out(struct bpf_fuse_args *fa, struct fu= se_getattr_args *args, + const struct dentry *entry, struct kstat *stat, + u32 request_mask, unsigned int flags) +{ + args->out =3D (struct fuse_attr_out) { 0 }; + + fa->out_numargs =3D 1; + fa->out_args[0] =3D (struct bpf_fuse_arg) { + .size =3D sizeof(args->out), + .value =3D &args->out, + }; + + return 0; +} + +static int fuse_getattr_backing(struct bpf_fuse_args *fa, int *out, + const struct dentry *entry, struct kstat *stat, + u32 request_mask, unsigned int flags) +{ + struct path *backing_path =3D &get_fuse_dentry(entry)->backing_path; + struct inode *backing_inode =3D backing_path->dentry->d_inode; + struct fuse_attr_out *fao =3D fa->out_args[0].value; + struct kstat tmp; + + if (!stat) + stat =3D &tmp; + + *out =3D vfs_getattr(backing_path, stat, request_mask, flags); + + if (!*out) + fuse_stat_to_attr(get_fuse_conn(entry->d_inode), backing_inode, + stat, &fao->attr); + + return 0; +} + +static int finalize_attr(struct inode *inode, struct fuse_attr_out *outarg, + u64 attr_version, struct kstat *stat) +{ + int err =3D 0; + + if (fuse_invalid_attr(&outarg->attr) || + ((inode->i_mode ^ outarg->attr.mode) & S_IFMT)) { + fuse_make_bad(inode); + err =3D -EIO; + } else { + fuse_change_attributes(inode, &outarg->attr, + attr_timeout(outarg), + attr_version); + if (stat) + fuse_fillattr(inode, &outarg->attr, stat); + } + return err; +} + +static int fuse_getattr_finalize(struct bpf_fuse_args *fa, int *out, + const struct dentry *entry, struct kstat *stat, + u32 request_mask, unsigned int flags) +{ + struct fuse_attr_out *outarg =3D fa->out_args[0].value; + struct inode *inode =3D entry->d_inode; + u64 attr_version =3D fuse_get_attr_version(get_fuse_mount(inode)->fc); + + /* TODO: Ensure this doesn't happen if we had an error getting attrs in + * backing. + */ + *out =3D finalize_attr(inode, outarg, attr_version, stat); + return 0; +} + +int fuse_bpf_getattr(int *out, struct inode *inode, const struct dentry *e= ntry, struct kstat *stat, + u32 request_mask, unsigned int flags) +{ + return bpf_fuse_backing(inode, struct fuse_getattr_args, out, + fuse_getattr_initialize_in, fuse_getattr_initialize_out, + fuse_getattr_backing, fuse_getattr_finalize, + entry, stat, request_mask, flags); +} + +static void fattr_to_iattr(struct fuse_conn *fc, + const struct fuse_setattr_in *arg, + struct iattr *iattr) +{ + unsigned int fvalid =3D arg->valid; + + if (fvalid & FATTR_MODE) + iattr->ia_valid |=3D ATTR_MODE, iattr->ia_mode =3D arg->mode; + if (fvalid & FATTR_UID) { + iattr->ia_valid |=3D ATTR_UID; + iattr->ia_uid =3D make_kuid(fc->user_ns, arg->uid); + } + if (fvalid & FATTR_GID) { + iattr->ia_valid |=3D ATTR_GID; + iattr->ia_gid =3D make_kgid(fc->user_ns, arg->gid); + } + if (fvalid & FATTR_SIZE) + iattr->ia_valid |=3D ATTR_SIZE, iattr->ia_size =3D arg->size; + if (fvalid & FATTR_ATIME) { + iattr->ia_valid |=3D ATTR_ATIME; + iattr->ia_atime.tv_sec =3D arg->atime; + iattr->ia_atime.tv_nsec =3D arg->atimensec; + if (!(fvalid & FATTR_ATIME_NOW)) + iattr->ia_valid |=3D ATTR_ATIME_SET; + } + if (fvalid & FATTR_MTIME) { + iattr->ia_valid |=3D ATTR_MTIME; + iattr->ia_mtime.tv_sec =3D arg->mtime; + iattr->ia_mtime.tv_nsec =3D arg->mtimensec; + if (!(fvalid & FATTR_MTIME_NOW)) + iattr->ia_valid |=3D ATTR_MTIME_SET; + } + if (fvalid & FATTR_CTIME) { + iattr->ia_valid |=3D ATTR_CTIME; + iattr->ia_ctime.tv_sec =3D arg->ctime; + iattr->ia_ctime.tv_nsec =3D arg->ctimensec; + } +} + +struct fuse_setattr_args { + struct fuse_setattr_in in; + struct fuse_attr_out out; +}; + +static int fuse_setattr_initialize_in(struct bpf_fuse_args *fa, struct fus= e_setattr_args *args, + struct dentry *dentry, struct iattr *attr, struct file *file) +{ + struct fuse_conn *fc =3D get_fuse_conn(dentry->d_inode); + + *args =3D (struct fuse_setattr_args) { 0 }; + iattr_to_fattr(fc, attr, &args->in, true); + + *fa =3D (struct bpf_fuse_args) { + .info =3D (struct bpf_fuse_meta_info) { + .opcode =3D FUSE_SETATTR, + .nodeid =3D get_node_id(dentry->d_inode), + }, + .in_numargs =3D 1, + .in_args[0].size =3D sizeof(args->in), + .in_args[0].value =3D &args->in, + }; + + return 0; +} + +static int fuse_setattr_initialize_out(struct bpf_fuse_args *fa, struct fu= se_setattr_args *args, + struct dentry *dentry, struct iattr *attr, struct file *file) +{ + fa->out_numargs =3D 1; + fa->out_args[0].size =3D sizeof(args->out); + fa->out_args[0].value =3D &args->out; + + return 0; +} + +static int fuse_setattr_backing(struct bpf_fuse_args *fa, int *out, + struct dentry *dentry, struct iattr *attr, struct file *file) +{ + struct fuse_conn *fc =3D get_fuse_conn(dentry->d_inode); + const struct fuse_setattr_in *fsi =3D fa->in_args[0].value; + struct iattr new_attr =3D { 0 }; + struct path *backing_path =3D &get_fuse_dentry(dentry)->backing_path; + + fattr_to_iattr(fc, fsi, &new_attr); + /* TODO: Some info doesn't get saved by the attr->fattr->attr transition + * When we actually allow the bpf to change these, we may have to consider + * the extra flags more, or pass more info into the bpf. Until then we can + * keep everything except for ATTR_FILE, since we'd need a file on the + * lower fs. For what it's worth, neither f2fs nor ext4 make use of that + * even if it is present. + */ + new_attr.ia_valid =3D attr->ia_valid & ~ATTR_FILE; + inode_lock(d_inode(backing_path->dentry)); + *out =3D notify_change(&nop_mnt_idmap, backing_path->dentry, &new_attr, + NULL); + inode_unlock(d_inode(backing_path->dentry)); + + if (*out =3D=3D 0 && (new_attr.ia_valid & ATTR_SIZE)) + i_size_write(dentry->d_inode, new_attr.ia_size); + return 0; +} + +static int fuse_setattr_finalize(struct bpf_fuse_args *fa, int *out, + struct dentry *dentry, struct iattr *attr, struct file *file) +{ + return 0; +} + +int fuse_bpf_setattr(int *out, struct inode *inode, struct dentry *dentry,= struct iattr *attr, struct file *file) +{ + return bpf_fuse_backing(inode, struct fuse_setattr_args, out, + fuse_setattr_initialize_in, fuse_setattr_initialize_out, + fuse_setattr_backing, fuse_setattr_finalize, + dentry, attr, file); +} + +static int fuse_statfs_initialize_in(struct bpf_fuse_args *fa, struct fuse= _statfs_out *out, + struct dentry *dentry, struct kstatfs *buf) +{ + *fa =3D (struct bpf_fuse_args) { + .info =3D (struct bpf_fuse_meta_info) { + .nodeid =3D get_node_id(d_inode(dentry)), + .opcode =3D FUSE_STATFS, + }, + }; + + return 0; +} + +static int fuse_statfs_initialize_out(struct bpf_fuse_args *fa, struct fus= e_statfs_out *out, + struct dentry *dentry, struct kstatfs *buf) +{ + *out =3D (struct fuse_statfs_out) { 0 }; + + fa->out_numargs =3D 1; + fa->out_args[0].size =3D sizeof(*out); + fa->out_args[0].value =3D out; + + return 0; +} + +static int fuse_statfs_backing(struct bpf_fuse_args *fa, int *out, + struct dentry *dentry, struct kstatfs *buf) +{ + struct path backing_path; + struct fuse_statfs_out *fso =3D fa->out_args[0].value; + + *out =3D 0; + get_fuse_backing_path(dentry, &backing_path); + if (!backing_path.dentry) + return -EBADF; + *out =3D vfs_statfs(&backing_path, buf); + path_put(&backing_path); + buf->f_type =3D FUSE_SUPER_MAGIC; + + //TODO Provide postfilter opportunity to modify + if (!*out) + convert_statfs_to_fuse(&fso->st, buf); + + return 0; +} + +static int fuse_statfs_finalize(struct bpf_fuse_args *fa, int *out, + struct dentry *dentry, struct kstatfs *buf) +{ + struct fuse_statfs_out *fso =3D fa->out_args[0].value; + + if (!fa->info.error_in) + convert_fuse_statfs(buf, &fso->st); + return 0; +} + +int fuse_bpf_statfs(int *out, struct inode *inode, struct dentry *dentry, = struct kstatfs *buf) +{ + return bpf_fuse_backing(dentry->d_inode, struct fuse_statfs_out, out, + fuse_statfs_initialize_in, fuse_statfs_initialize_out, + fuse_statfs_backing, fuse_statfs_finalize, + dentry, buf); +} + struct fuse_read_args { struct fuse_read_in in; struct fuse_read_out out; diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 086e3ecada19..7d589241c9b0 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1236,7 +1236,7 @@ static int fuse_link(struct dentry *entry, struct ino= de *newdir, return err; } =20 -static void fuse_fillattr(struct inode *inode, struct fuse_attr *attr, +void fuse_fillattr(struct inode *inode, struct fuse_attr *attr, struct kstat *stat) { unsigned int blkbits; @@ -1313,6 +1313,7 @@ static int fuse_do_getattr(struct inode *inode, struc= t kstat *stat, } =20 static int fuse_update_get_attr(struct inode *inode, struct file *file, + const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags) { @@ -1322,6 +1323,9 @@ static int fuse_update_get_attr(struct inode *inode, = struct file *file, u32 inval_mask =3D READ_ONCE(fi->inval_mask); u32 cache_mask =3D fuse_get_cache_mask(inode); =20 + if (fuse_bpf_getattr(&err, inode, path->dentry, stat, request_mask, flags= )) + return err; + if (flags & AT_STATX_FORCE_SYNC) sync =3D true; else if (flags & AT_STATX_DONT_SYNC) @@ -1345,7 +1349,7 @@ static int fuse_update_get_attr(struct inode *inode, = struct file *file, =20 int fuse_update_attributes(struct inode *inode, struct file *file, u32 mas= k) { - return fuse_update_get_attr(inode, file, NULL, mask, 0); + return fuse_update_get_attr(inode, file, &file->f_path, NULL, mask, 0); } =20 int fuse_reverse_inval_entry(struct fuse_conn *fc, u64 parent_nodeid, @@ -1714,58 +1718,6 @@ static long fuse_dir_compat_ioctl(struct file *file,= unsigned int cmd, FUSE_IOCTL_COMPAT | FUSE_IOCTL_DIR); } =20 -static inline bool update_mtime(unsigned int ivalid, bool trust_local_mtim= e) -{ - /* Always update if mtime is explicitly set */ - if (ivalid & ATTR_MTIME_SET) - return true; - - /* Or if kernel i_mtime is the official one */ - if (trust_local_mtime) - return true; - - /* If it's an open(O_TRUNC) or an ftruncate(), don't update */ - if ((ivalid & ATTR_SIZE) && (ivalid & (ATTR_OPEN | ATTR_FILE))) - return false; - - /* In all other cases update */ - return true; -} - -static void iattr_to_fattr(struct fuse_conn *fc, struct iattr *iattr, - struct fuse_setattr_in *arg, bool trust_local_cmtime) -{ - unsigned ivalid =3D iattr->ia_valid; - - if (ivalid & ATTR_MODE) - arg->valid |=3D FATTR_MODE, arg->mode =3D iattr->ia_mode; - if (ivalid & ATTR_UID) - arg->valid |=3D FATTR_UID, arg->uid =3D from_kuid(fc->user_ns, iattr-= >ia_uid); - if (ivalid & ATTR_GID) - arg->valid |=3D FATTR_GID, arg->gid =3D from_kgid(fc->user_ns, iattr-= >ia_gid); - if (ivalid & ATTR_SIZE) - arg->valid |=3D FATTR_SIZE, arg->size =3D iattr->ia_size; - if (ivalid & ATTR_ATIME) { - arg->valid |=3D FATTR_ATIME; - arg->atime =3D iattr->ia_atime.tv_sec; - arg->atimensec =3D iattr->ia_atime.tv_nsec; - if (!(ivalid & ATTR_ATIME_SET)) - arg->valid |=3D FATTR_ATIME_NOW; - } - if ((ivalid & ATTR_MTIME) && update_mtime(ivalid, trust_local_cmtime)) { - arg->valid |=3D FATTR_MTIME; - arg->mtime =3D iattr->ia_mtime.tv_sec; - arg->mtimensec =3D iattr->ia_mtime.tv_nsec; - if (!(ivalid & ATTR_MTIME_SET) && !trust_local_cmtime) - arg->valid |=3D FATTR_MTIME_NOW; - } - if ((ivalid & ATTR_CTIME) && trust_local_cmtime) { - arg->valid |=3D FATTR_CTIME; - arg->ctime =3D iattr->ia_ctime.tv_sec; - arg->ctimensec =3D iattr->ia_ctime.tv_nsec; - } -} - /* * Prevent concurrent writepages on inode * @@ -1880,6 +1832,9 @@ int fuse_do_setattr(struct dentry *dentry, struct iat= tr *attr, bool trust_local_cmtime =3D is_wb; bool fault_blocked =3D false; =20 + if (fuse_bpf_setattr(&err, inode, dentry, attr, file)) + return err; + if (!fc->default_permissions) attr->ia_valid |=3D ATTR_FORCE; =20 @@ -2059,7 +2014,8 @@ static int fuse_setattr(struct mnt_idmap *idmap, stru= ct dentry *entry, * ia_mode calculation may have used stale i_mode. * Refresh and recalculate. */ - ret =3D fuse_do_getattr(inode, NULL, file); + if (!fuse_bpf_getattr(&ret, inode, entry, NULL, 0, 0)) + ret =3D fuse_do_getattr(inode, NULL, file); if (ret) return ret; =20 @@ -2116,7 +2072,7 @@ static int fuse_getattr(struct mnt_idmap *idmap, return -EACCES; } =20 - return fuse_update_get_attr(inode, NULL, stat, request_mask, flags); + return fuse_update_get_attr(inode, NULL, path, stat, request_mask, flags); } =20 static const struct inode_operations fuse_dir_inode_operations =3D { diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 5c8bd2f76fb9..17899a1fe885 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1427,6 +1427,10 @@ int fuse_bpf_file_read_iter(ssize_t *out, struct ino= de *inode, struct kiocb *ioc int fuse_bpf_file_write_iter(ssize_t *out, struct inode *inode, struct kio= cb *iocb, struct iov_iter *from); int fuse_bpf_file_fallocate(int *out, struct inode *inode, struct file *fi= le, int mode, loff_t offset, loff_t length); int fuse_bpf_lookup(struct dentry **out, struct inode *dir, struct dentry = *entry, unsigned int flags); +int fuse_bpf_getattr(int *out, struct inode *inode, const struct dentry *e= ntry, struct kstat *stat, + u32 request_mask, unsigned int flags); +int fuse_bpf_setattr(int *out, struct inode *inode, struct dentry *dentry,= struct iattr *attr, struct file *file); +int fuse_bpf_statfs(int *out, struct inode *inode, struct dentry *dentry, = struct kstatfs *buf); int fuse_bpf_readdir(int *out, struct inode *inode, struct file *file, str= uct dir_context *ctx); int fuse_bpf_access(int *out, struct inode *inode, int mask); =20 @@ -1526,6 +1530,22 @@ static inline int fuse_bpf_lookup(struct dentry **ou= t, struct inode *dir, struct return 0; } =20 +static inline int fuse_bpf_getattr(int *out, struct inode *inode, const st= ruct dentry *entry, struct kstat *stat, + u32 request_mask, unsigned int flags) +{ + return 0; +} + +static inline int fuse_bpf_setattr(int *out, struct inode *inode, struct d= entry *dentry, struct iattr *attr, struct file *file) +{ + return 0; +} + +static inline int fuse_bpf_statfs(int *out, struct inode *inode, struct de= ntry *dentry, struct kstatfs *buf) +{ + return 0; +} + static inline int fuse_bpf_readdir(int *out, struct inode *inode, struct f= ile *file, struct dir_context *ctx) { return 0; @@ -1571,6 +1591,88 @@ static inline u64 attr_timeout(struct fuse_attr_out = *o) return time_to_jiffies(o->attr_valid, o->attr_valid_nsec); } =20 +static inline bool update_mtime(unsigned int ivalid, bool trust_local_mtim= e) +{ + /* Always update if mtime is explicitly set */ + if (ivalid & ATTR_MTIME_SET) + return true; + + /* Or if kernel i_mtime is the official one */ + if (trust_local_mtime) + return true; + + /* If it's an open(O_TRUNC) or an ftruncate(), don't update */ + if ((ivalid & ATTR_SIZE) && (ivalid & (ATTR_OPEN | ATTR_FILE))) + return false; + + /* In all other cases update */ + return true; +} + +void fuse_fillattr(struct inode *inode, struct fuse_attr *attr, + struct kstat *stat); + +static inline void iattr_to_fattr(struct fuse_conn *fc, struct iattr *iatt= r, + struct fuse_setattr_in *arg, bool trust_local_cmtime) +{ + unsigned int ivalid =3D iattr->ia_valid; + + if (ivalid & ATTR_MODE) + arg->valid |=3D FATTR_MODE, arg->mode =3D iattr->ia_mode; + if (ivalid & ATTR_UID) + arg->valid |=3D FATTR_UID, arg->uid =3D from_kuid(fc->user_ns, iattr-= >ia_uid); + if (ivalid & ATTR_GID) + arg->valid |=3D FATTR_GID, arg->gid =3D from_kgid(fc->user_ns, iattr-= >ia_gid); + if (ivalid & ATTR_SIZE) + arg->valid |=3D FATTR_SIZE, arg->size =3D iattr->ia_size; + if (ivalid & ATTR_ATIME) { + arg->valid |=3D FATTR_ATIME; + arg->atime =3D iattr->ia_atime.tv_sec; + arg->atimensec =3D iattr->ia_atime.tv_nsec; + if (!(ivalid & ATTR_ATIME_SET)) + arg->valid |=3D FATTR_ATIME_NOW; + } + if ((ivalid & ATTR_MTIME) && update_mtime(ivalid, trust_local_cmtime)) { + arg->valid |=3D FATTR_MTIME; + arg->mtime =3D iattr->ia_mtime.tv_sec; + arg->mtimensec =3D iattr->ia_mtime.tv_nsec; + if (!(ivalid & ATTR_MTIME_SET) && !trust_local_cmtime) + arg->valid |=3D FATTR_MTIME_NOW; + } + if ((ivalid & ATTR_CTIME) && trust_local_cmtime) { + arg->valid |=3D FATTR_CTIME; + arg->ctime =3D iattr->ia_ctime.tv_sec; + arg->ctimensec =3D iattr->ia_ctime.tv_nsec; + } +} + +static inline void convert_statfs_to_fuse(struct fuse_kstatfs *attr, struc= t kstatfs *stbuf) +{ + attr->bsize =3D stbuf->f_bsize; + attr->frsize =3D stbuf->f_frsize; + attr->blocks =3D stbuf->f_blocks; + attr->bfree =3D stbuf->f_bfree; + attr->bavail =3D stbuf->f_bavail; + attr->files =3D stbuf->f_files; + attr->ffree =3D stbuf->f_ffree; + attr->namelen =3D stbuf->f_namelen; + /* fsid is left zero */ +} + +static inline void convert_fuse_statfs(struct kstatfs *stbuf, struct fuse_= kstatfs *attr) +{ + stbuf->f_type =3D FUSE_SUPER_MAGIC; + stbuf->f_bsize =3D attr->bsize; + stbuf->f_frsize =3D attr->frsize; + stbuf->f_blocks =3D attr->blocks; + stbuf->f_bfree =3D attr->bfree; + stbuf->f_bavail =3D attr->bavail; + stbuf->f_files =3D attr->files; + stbuf->f_ffree =3D attr->ffree; + stbuf->f_namelen =3D attr->namelen; + /* fsid is left zero */ +} + #ifdef CONFIG_FUSE_BPF int __init fuse_bpf_init(void); void __exit fuse_bpf_cleanup(void); diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index fd00910f1eb1..3dfb9cfb6e73 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -623,20 +623,6 @@ static void fuse_send_destroy(struct fuse_mount *fm) } } =20 -static void convert_fuse_statfs(struct kstatfs *stbuf, struct fuse_kstatfs= *attr) -{ - stbuf->f_type =3D FUSE_SUPER_MAGIC; - stbuf->f_bsize =3D attr->bsize; - stbuf->f_frsize =3D attr->frsize; - stbuf->f_blocks =3D attr->blocks; - stbuf->f_bfree =3D attr->bfree; - stbuf->f_bavail =3D attr->bavail; - stbuf->f_files =3D attr->files; - stbuf->f_ffree =3D attr->ffree; - stbuf->f_namelen =3D attr->namelen; - /* fsid is left zero */ -} - static int fuse_statfs(struct dentry *dentry, struct kstatfs *buf) { struct super_block *sb =3D dentry->d_sb; @@ -650,6 +636,9 @@ static int fuse_statfs(struct dentry *dentry, struct ks= tatfs *buf) return 0; } =20 + if (fuse_bpf_statfs(&err, dentry->d_inode, dentry, buf)) + return err; + memset(&outarg, 0, sizeof(outarg)); args.in_numargs =3D 0; args.opcode =3D FUSE_STATFS; --=20 2.40.0.634.g4ca3ef3211-goog From nobody Wed Dec 17 19:22:07 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 95844C77B75 for ; Tue, 18 Apr 2023 01:44:12 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231272AbjDRBoL (ORCPT ); Mon, 17 Apr 2023 21:44:11 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:54320 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230280AbjDRBmz (ORCPT ); Mon, 17 Apr 2023 21:42:55 -0400 Received: from mail-yw1-x114a.google.com (mail-yw1-x114a.google.com [IPv6:2607:f8b0:4864:20::114a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 265557ED9 for ; Mon, 17 Apr 2023 18:41:54 -0700 (PDT) Received: by mail-yw1-x114a.google.com with SMTP id 00721157ae682-54fde069e4aso98094617b3.3 for ; Mon, 17 Apr 2023 18:41:54 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20221208; t=1681782101; x=1684374101; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=ikqE+zYz1BXZh8mMNk5W0C4vdov8fukuxDv9VBrvDl0=; b=lWajx8mafW1seUIDD7+qIrKfh1VFBzwDUsLiwI++++Oz4bdMluxYPYY5tEsxAWa6CH vxq2L9Ahhr00QlIXhuRa3H0R5r+rycy5muTibHI7wt1orB3xYTatC8P/ujh+GxeJv/Ea /qsU11Dq37CWkGOvJwpF5+roapL50puA2pWR52NQY0wwb8g7QRm+EFltGejT5i4Jt/Y1 OXZxsrsSrppo/t9R3VKyB1ZTuREyblAyqTJ0f7YYvHtuCgBmBUieoHEv00v6QvsgLSJm 90ZuhZjoOrQHXNOmhPGdV9IuQx/Xj2Nnt6uh+MJyIttYHlPKKkqeKwTrP9CBD6HG1qXp E0dw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681782101; x=1684374101; 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=ikqE+zYz1BXZh8mMNk5W0C4vdov8fukuxDv9VBrvDl0=; b=OutaYPA2yB1vs/evnmcrBwIqd3Xbv4qhO0OlMLB72U2WnrZ4DJW6as0U7fenyYxoXJ nbW8bYgqIe1YI6DoWX3b1JfOVVolVXP90BFRfYnaD8jJ3w9L5lzon+zqhkDelvMx4q1y mHgiWsXxS8UWBqn8hC6LpaQorOqGE7gY0tw7x2xB1M0xY2VGW4p3sHtS3hpTcnn8tlRo waVWVHzrx4s71hCURNaVJOYABJ1b1Z39WsbNo+XMRcyjak6S0Kmlbi7UrhdvLG+eRzym ZGCu64ddMqfDiV9AYfcZm5Yt3SgihlNtP2uRgmBPee2+sXiBZ43SCea/L8EjnZ2AFqLN bjiw== X-Gm-Message-State: AAQBX9cRHTZAAYclWyDlBMULBIAQE9Sd0eQ4cfvG9AIaQYoFW+cjsvtE Q4epH/0s6ZolENKZLgWxnMCaO/+Cmlo= X-Google-Smtp-Source: AKy350Y8W7u23ruYQztA6DVfVSc2+c8wyOOiKTHLOnbBM9IdQq81SwSucFeU6gGP5hv1qDXkZmp6krD4aBI= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:e67a:98b0:942d:86aa]) (user=drosen job=sendgmr) by 2002:a81:a807:0:b0:552:f777:88ce with SMTP id f7-20020a81a807000000b00552f77788cemr2101987ywh.3.1681782101478; Mon, 17 Apr 2023 18:41:41 -0700 (PDT) Date: Mon, 17 Apr 2023 18:40:22 -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-23-drosen@google.com> Subject: [RFC PATCH v3 22/37] fuse-bpf: Add support for FUSE_COPY_FILE_RANGE 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 Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This adds backing support for FUSE_COPY_FILE_RANGE Signed-off-by: Daniel Rosenberg Signed-off-by: Paul Lawrence --- fs/fuse/backing.c | 87 +++++++++++++++++++++++++++++++++++++++++++++++ fs/fuse/file.c | 4 +++ fs/fuse/fuse_i.h | 10 ++++++ 3 files changed, 101 insertions(+) diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index 6a6130a16d2b..928b24db2303 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -801,6 +801,93 @@ int fuse_bpf_lseek(loff_t *out, struct inode *inode, s= truct file *file, loff_t o file, offset, whence); } =20 +struct fuse_copy_file_range_args { + struct fuse_copy_file_range_in in; + struct fuse_write_out out; +}; + +static int fuse_copy_file_range_initialize_in(struct bpf_fuse_args *fa, + struct fuse_copy_file_range_args *args, + struct file *file_in, loff_t pos_in, struct file *file_out, + loff_t pos_out, size_t len, unsigned int flags) +{ + struct fuse_file *fuse_file_in =3D file_in->private_data; + struct fuse_file *fuse_file_out =3D file_out->private_data; + + args->in =3D (struct fuse_copy_file_range_in) { + .fh_in =3D fuse_file_in->fh, + .off_in =3D pos_in, + .nodeid_out =3D fuse_file_out->nodeid, + .fh_out =3D fuse_file_out->fh, + .off_out =3D pos_out, + .len =3D len, + .flags =3D flags, + }; + + *fa =3D (struct bpf_fuse_args) { + .info =3D (struct bpf_fuse_meta_info) { + .nodeid =3D get_node_id(file_in->f_inode), + .opcode =3D FUSE_COPY_FILE_RANGE, + }, + .in_numargs =3D 1, + .in_args[0].size =3D sizeof(args->in), + .in_args[0].value =3D &args->in, + }; + + return 0; +} + +static int fuse_copy_file_range_initialize_out(struct bpf_fuse_args *fa, + struct fuse_copy_file_range_args *args, + struct file *file_in, loff_t pos_in, struct file *file_out, + loff_t pos_out, size_t len, unsigned int flags) +{ + fa->out_numargs =3D 1; + fa->out_args[0].size =3D sizeof(args->out); + fa->out_args[0].value =3D &args->out; + + return 0; +} + +static int fuse_copy_file_range_backing(struct bpf_fuse_args *fa, ssize_t = *out, struct file *file_in, + loff_t pos_in, struct file *file_out, loff_t pos_out, size_t len, + unsigned int flags) +{ + const struct fuse_copy_file_range_in *fci =3D fa->in_args[0].value; + struct fuse_file *fuse_file_in =3D file_in->private_data; + struct file *backing_file_in =3D fuse_file_in->backing_file; + struct fuse_file *fuse_file_out =3D file_out->private_data; + struct file *backing_file_out =3D fuse_file_out->backing_file; + + /* TODO: Handle changing of in/out files */ + if (backing_file_out) + *out =3D vfs_copy_file_range(backing_file_in, fci->off_in, backing_file_= out, + fci->off_out, fci->len, fci->flags); + else + *out =3D generic_copy_file_range(file_in, pos_in, file_out, pos_out, len, + flags); + return 0; +} + +static int fuse_copy_file_range_finalize(struct bpf_fuse_args *fa, ssize_t= *out, struct file *file_in, + loff_t pos_in, struct file *file_out, loff_t pos_out, size_t len, + unsigned int flags) +{ + return 0; +} + +int fuse_bpf_copy_file_range(ssize_t *out, struct inode *inode, struct fil= e *file_in, + loff_t pos_in, struct file *file_out, loff_t pos_out, size_t len, + unsigned int flags) +{ + return bpf_fuse_backing(inode, struct fuse_copy_file_range_args, out, + fuse_copy_file_range_initialize_in, + fuse_copy_file_range_initialize_out, + fuse_copy_file_range_backing, + fuse_copy_file_range_finalize, + file_in, pos_in, file_out, pos_out, len, flags); +} + static int fuse_fsync_initialize_in(struct bpf_fuse_args *fa, struct fuse_= fsync_in *in, struct file *file, loff_t start, loff_t end, int datasync) { diff --git a/fs/fuse/file.c b/fs/fuse/file.c index a4a0aeb28e4a..8179afe28c6f 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -3199,6 +3199,10 @@ static ssize_t __fuse_copy_file_range(struct file *f= ile_in, loff_t pos_in, bool is_unstable =3D (!fc->writeback_cache) && ((pos_out + len) > inode_out->i_size); =20 + if (fuse_bpf_copy_file_range(&err, file_inode(file_in), file_in, pos_in, + file_out, pos_out, len, flags)) + return err; + if (fc->no_copy_file_range) return -EOPNOTSUPP; =20 diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 17899a1fe885..74540f308636 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1421,6 +1421,9 @@ int fuse_bpf_release(int *out, struct inode *inode, s= truct file *file); int fuse_bpf_releasedir(int *out, struct inode *inode, struct file *file); int fuse_bpf_flush(int *out, struct inode *inode, struct file *file, fl_ow= ner_t id); int fuse_bpf_lseek(loff_t *out, struct inode *inode, struct file *file, lo= ff_t offset, int whence); +int fuse_bpf_copy_file_range(ssize_t *out, struct inode *inode, struct fil= e *file_in, loff_t pos_in, + struct file *file_out, loff_t pos_out, + size_t len, unsigned int flags); int fuse_bpf_fsync(int *out, struct inode *inode, struct file *file, loff_= t start, loff_t end, int datasync); int fuse_bpf_dir_fsync(int *out, struct inode *inode, struct file *file, l= off_t start, loff_t end, int datasync); int fuse_bpf_file_read_iter(ssize_t *out, struct inode *inode, struct kioc= b *iocb, struct iov_iter *to); @@ -1500,6 +1503,13 @@ static inline int fuse_bpf_lseek(loff_t *out, struct= inode *inode, struct file * return 0; } =20 +static inline int fuse_bpf_copy_file_range(ssize_t *out, struct inode *ino= de, struct file *file_in, loff_t pos_in, + struct file *file_out, loff_t pos_out, + size_t len, unsigned int flags) +{ + return 0; +} + static inline int fuse_bpf_fsync(int *out, struct inode *inode, struct fil= e *file, loff_t start, loff_t end, int datasync) { return 0; --=20 2.40.0.634.g4ca3ef3211-goog From nobody Wed Dec 17 19:22:07 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 056DEC77B72 for ; Tue, 18 Apr 2023 01:44:17 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231292AbjDRBoP (ORCPT ); Mon, 17 Apr 2023 21:44:15 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53784 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231160AbjDRBm4 (ORCPT ); Mon, 17 Apr 2023 21:42:56 -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 CF06883C1 for ; Mon, 17 Apr 2023 18:41:54 -0700 (PDT) Received: by mail-yw1-x1149.google.com with SMTP id 00721157ae682-54f8b46f399so159472467b3.10 for ; Mon, 17 Apr 2023 18:41:54 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20221208; t=1681782104; x=1684374104; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=U2ap14iOZpnbsKNo6G93wf0F6NVTN36P+wrV7MWQ5vc=; b=LNB3R+49gmQhyMAJnqmL6KYI/ZCR+tQ/0jba5N0/i2eEk6OmYIYbiuf0Z7V4bUldDj xmAcckNGdRp5IYqNUT8hqhpXD3vdnlrJNfoEzI9hjUpQNlq3IjGbbi4vQ1h9Xgp31A+R 5jbqBakdNiRAQFySaud23Qc2bb0XZCCsv91cuEDtJ0lsyZMTvQItKMyw0e1gbKSpBG7W 2yGprGFBTkg9LHUqY/Yx/LEu6jmWcorOClliwxzsxuu4MLRG7YazaQAJntomIhbXgBUg 9zw6XZJs07Ntzj/gIsAFey2DX1dvCDUPgQXl4rZCW29e5vOds9wU1NhA4cgwPQE9OmSr p62A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681782104; x=1684374104; 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=U2ap14iOZpnbsKNo6G93wf0F6NVTN36P+wrV7MWQ5vc=; b=Eok3LXURw1mogf10smAXs2ZYMniW7HxIk1ixxm+EwJ2+AnBPNZemmwK6riBvKR75Aa ZNMoy5ICtKeAnh6c2qNPlz6blTlFJ/fwH95pdsk3POf6xVAn4rFFPup85d+StA482qwJ wkyhV27HdWfixC7I4m9kn4yydhTAGVH31ecW/LD/QBhHMAlmRSa9pR74FyNfy9rOMDpW NCsBoPZDLrKw04u+k5+FWG+LneQqsNxV/xONh15SRIylk+OvRN15ioYNf1aUrVLvcCgZ QhhVaZsnpkD/UXs0jSyqBuhyL/eP2lY4rfYiAhIhPEHJfE3lu5afTMFGK6mJH7XwQZg6 fm4g== X-Gm-Message-State: AAQBX9dEKQDyXMpbnO7W8bN3p9hX1MeDXj0OTNlisHPmWkZGptqaU0X1 Irh/EU2FSmpL2/Jd3SYClVDRl5E3LmY= X-Google-Smtp-Source: AKy350aATwmWG/o9YxDsbVJURffOBFykqGN8a34WfB/p7jnkyKN8nP3tgJdz5JXEk/Bod96TuKRjP9ed7Bo= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:e67a:98b0:942d:86aa]) (user=drosen job=sendgmr) by 2002:a25:d196:0:b0:b8f:6faa:6480 with SMTP id i144-20020a25d196000000b00b8f6faa6480mr8469156ybg.7.1681782103829; Mon, 17 Apr 2023 18:41:43 -0700 (PDT) Date: Mon, 17 Apr 2023 18:40:23 -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-24-drosen@google.com> Subject: [RFC PATCH v3 23/37] fuse-bpf: Add xattr support 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 Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This adds support for FUSE_GETXATTR, FUSE_LISTXATTR, FUSE_SETXATTR, and FUSE_REMOVEXATTR Signed-off-by: Daniel Rosenberg Signed-off-by: Paul Lawrence --- fs/fuse/backing.c | 349 ++++++++++++++++++++++++++++++++++++++++++++++ fs/fuse/fuse_i.h | 30 ++++ fs/fuse/xattr.c | 18 +++ 3 files changed, 397 insertions(+) diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index 928b24db2303..eb3eb184c867 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -982,6 +982,355 @@ int fuse_bpf_dir_fsync(int *out, struct inode *inode,= struct file *file, loff_t file, start, end, datasync); } =20 +struct fuse_getxattr_args { + struct fuse_getxattr_in in; + struct fuse_buffer name; + struct fuse_buffer value; + struct fuse_getxattr_out out; +}; + +static int fuse_getxattr_initialize_in(struct bpf_fuse_args *fa, + struct fuse_getxattr_args *args, + struct dentry *dentry, const char *name, void *value, + size_t size) +{ + *args =3D (struct fuse_getxattr_args) { + .in.size =3D size, + .name =3D (struct fuse_buffer) { + .data =3D (void *) name, + .size =3D strlen(name) + 1, + .max_size =3D XATTR_NAME_MAX + 1, + .flags =3D BPF_FUSE_MUST_ALLOCATE | BPF_FUSE_VARIABLE_SIZE, + }, + }; + + *fa =3D (struct bpf_fuse_args) { + .info =3D (struct bpf_fuse_meta_info) { + .nodeid =3D get_fuse_inode(dentry->d_inode)->nodeid, + .opcode =3D FUSE_GETXATTR, + }, + .in_numargs =3D 2, + .in_args[0] =3D (struct bpf_fuse_arg) { + .size =3D sizeof(args->in), + .value =3D &args->in, + }, + .in_args[1] =3D (struct bpf_fuse_arg) { + .is_buffer =3D true, + .buffer =3D &args->name, + }, + }; + + return 0; +} + +static int fuse_getxattr_initialize_out(struct bpf_fuse_args *fa, + struct fuse_getxattr_args *args, + struct dentry *dentry, const char *name, void *value, + size_t size) +{ + fa->flags =3D size ? FUSE_BPF_OUT_ARGVAR : 0; + fa->out_numargs =3D 1; + if (size) { + args->value =3D (struct fuse_buffer) { + .data =3D (void *) value, + .size =3D size, + .alloc_size =3D size, + .max_size =3D size, + .flags =3D BPF_FUSE_VARIABLE_SIZE, + }; + fa->out_args[0].is_buffer =3D true; + fa->out_args[0].buffer =3D &args->value; + } else { + fa->out_args[0].size =3D sizeof(args->out); + fa->out_args[0].value =3D &args->out; + } + return 0; +} + +static int fuse_getxattr_backing(struct bpf_fuse_args *fa, int *out, + struct dentry *dentry, const char *name, void *value, + size_t size) +{ + ssize_t ret; + + if (fa->in_args[1].buffer->flags & BPF_FUSE_MODIFIED) { + // Ensure bpf provided string is null terminated + char *new_name =3D fa->in_args[1].buffer->data; + new_name[fa->in_args[1].buffer->size - 1] =3D 0; + } + ret =3D vfs_getxattr(&nop_mnt_idmap, + get_fuse_dentry(dentry)->backing_path.dentry, + fa->in_args[1].buffer->data, value, size); + + if (fa->flags & FUSE_BPF_OUT_ARGVAR) + fa->out_args[0].buffer->size =3D ret; + else + ((struct fuse_getxattr_out *)fa->out_args[0].value)->size =3D ret; + + return 0; +} + +static int fuse_getxattr_finalize(struct bpf_fuse_args *fa, int *out, + struct dentry *dentry, const char *name, void *value, + size_t size) +{ + struct fuse_getxattr_out *fgo; + + if (fa->flags & FUSE_BPF_OUT_ARGVAR) { + *out =3D fa->out_args[0].buffer->size; + return 0; + } + + fgo =3D fa->out_args[0].value; + + *out =3D fgo->size; + return 0; +} + +int fuse_bpf_getxattr(int *out, struct inode *inode, struct dentry *dentry= , const char *name, + void *value, size_t size) +{ + return bpf_fuse_backing(inode, struct fuse_getxattr_args, out, + fuse_getxattr_initialize_in, fuse_getxattr_initialize_out, + fuse_getxattr_backing, fuse_getxattr_finalize, + dentry, name, value, size); +} + +static int fuse_listxattr_initialize_in(struct bpf_fuse_args *fa, + struct fuse_getxattr_args *args, + struct dentry *dentry, char *list, size_t size) +{ + *args =3D (struct fuse_getxattr_args) { + .in.size =3D size, + }; + + *fa =3D (struct bpf_fuse_args) { + .info =3D (struct bpf_fuse_meta_info) { + .nodeid =3D get_fuse_inode(dentry->d_inode)->nodeid, + .opcode =3D FUSE_LISTXATTR, + }, + .in_numargs =3D 1, + .in_args[0] =3D + (struct bpf_fuse_arg) { + .size =3D sizeof(args->in), + .value =3D &args->in, + }, + }; + + return 0; +} + +static int fuse_listxattr_initialize_out(struct bpf_fuse_args *fa, + struct fuse_getxattr_args *args, + struct dentry *dentry, char *list, size_t size) +{ + fa->out_numargs =3D 1; + + if (size) { + args->value =3D (struct fuse_buffer) { + .data =3D list, + .size =3D size, + .alloc_size =3D size, + .max_size =3D size, + .flags =3D BPF_FUSE_VARIABLE_SIZE, + }; + fa->flags =3D FUSE_BPF_OUT_ARGVAR; + fa->out_args[0].is_buffer =3D true; + fa->out_args[0].buffer =3D &args->value; + } else { + fa->out_args[0].size =3D sizeof(args->out); + fa->out_args[0].value =3D &args->out; + } + return 0; +} + +static int fuse_listxattr_backing(struct bpf_fuse_args *fa, ssize_t *out, = struct dentry *dentry, + char *list, size_t size) +{ + *out =3D vfs_listxattr(get_fuse_dentry(dentry)->backing_path.dentry, list= , size); + + if (*out < 0) + return *out; + + if (fa->flags & FUSE_BPF_OUT_ARGVAR) + fa->out_args[0].buffer->size =3D *out; + else + ((struct fuse_getxattr_out *)fa->out_args[0].value)->size =3D *out; + + return 0; +} + +static int fuse_listxattr_finalize(struct bpf_fuse_args *fa, ssize_t *out,= struct dentry *dentry, + char *list, size_t size) +{ + struct fuse_getxattr_out *fgo; + + if (fa->info.error_in) + return 0; + + if (fa->flags & FUSE_BPF_OUT_ARGVAR) { + *out =3D fa->out_args[0].buffer->size; + return 0; + } + + fgo =3D fa->out_args[0].value; + *out =3D fgo->size; + return 0; +} + +int fuse_bpf_listxattr(ssize_t *out, struct inode *inode, struct dentry *d= entry, + char *list, size_t size) +{ + return bpf_fuse_backing(inode, struct fuse_getxattr_args, out, + fuse_listxattr_initialize_in, fuse_listxattr_initialize_out, + fuse_listxattr_backing, fuse_listxattr_finalize, + dentry, list, size); +} + +struct fuse_setxattr_args { + struct fuse_setxattr_in in; + struct fuse_buffer name; + struct fuse_buffer value; +}; + +static int fuse_setxattr_initialize_in(struct bpf_fuse_args *fa, + struct fuse_setxattr_args *args, + struct dentry *dentry, const char *name, + const void *value, size_t size, int flags) +{ + *args =3D (struct fuse_setxattr_args) { + .in =3D (struct fuse_setxattr_in) { + .size =3D size, + .flags =3D flags, + }, + .name =3D (struct fuse_buffer) { + .data =3D (void *) name, + .size =3D strlen(name) + 1, + .max_size =3D XATTR_NAME_MAX + 1, + .flags =3D BPF_FUSE_VARIABLE_SIZE | BPF_FUSE_MUST_ALLOCATE, + }, + .value =3D(struct fuse_buffer) { + .data =3D (void *) value, + .size =3D size, + .max_size =3D XATTR_SIZE_MAX, + .flags =3D BPF_FUSE_VARIABLE_SIZE | BPF_FUSE_MUST_ALLOCATE, + }, + }; + + *fa =3D (struct bpf_fuse_args) { + .info =3D (struct bpf_fuse_meta_info) { + .nodeid =3D get_fuse_inode(dentry->d_inode)->nodeid, + .opcode =3D FUSE_SETXATTR, + }, + .in_numargs =3D 3, + .in_args[0] =3D (struct bpf_fuse_arg) { + .size =3D sizeof(args->in), + .value =3D &args->in, + }, + .in_args[1] =3D (struct bpf_fuse_arg) { + .is_buffer =3D true, + .buffer =3D &args->name, + }, + .in_args[2] =3D (struct bpf_fuse_arg) { + .is_buffer =3D true, + .buffer =3D &args->value, + }, + }; + + return 0; +} + +static int fuse_setxattr_initialize_out(struct bpf_fuse_args *fa, + struct fuse_setxattr_args *args, + struct dentry *dentry, const char *name, + const void *value, size_t size, int flags) +{ + return 0; +} + +static int fuse_setxattr_backing(struct bpf_fuse_args *fa, int *out, struc= t dentry *dentry, + const char *name, const void *value, size_t size, + int flags) +{ + // TODO Ensure we actually use filter values + *out =3D vfs_setxattr(&nop_mnt_idmap, + get_fuse_dentry(dentry)->backing_path.dentry, name, + value, size, flags); + return 0; +} + +static int fuse_setxattr_finalize(struct bpf_fuse_args *fa, int *out, stru= ct dentry *dentry, + const char *name, const void *value, size_t size, + int flags) +{ + return 0; +} + +int fuse_bpf_setxattr(int *out, struct inode *inode, struct dentry *dentry, + const char *name, const void *value, size_t size, int flags) +{ + return bpf_fuse_backing(inode, struct fuse_setxattr_args, out, + fuse_setxattr_initialize_in, fuse_setxattr_initialize_out, + fuse_setxattr_backing, fuse_setxattr_finalize, + dentry, name, value, size, flags); +} + +static int fuse_removexattr_initialize_in(struct bpf_fuse_args *fa, + struct fuse_buffer *in, + struct dentry *dentry, const char *name) +{ + *in =3D (struct fuse_buffer) { + .data =3D (void *) name, + .size =3D strlen(name) + 1, + .max_size =3D XATTR_NAME_MAX + 1, + .flags =3D BPF_FUSE_VARIABLE_SIZE | BPF_FUSE_MUST_ALLOCATE, + }; + *fa =3D (struct bpf_fuse_args) { + .info =3D (struct bpf_fuse_meta_info) { + .nodeid =3D get_fuse_inode(dentry->d_inode)->nodeid, + .opcode =3D FUSE_REMOVEXATTR, + }, + .in_numargs =3D 1, + .in_args[0] =3D (struct bpf_fuse_arg) { + .is_buffer =3D true, + .buffer =3D in, + }, + }; + + return 0; +} + +static int fuse_removexattr_initialize_out(struct bpf_fuse_args *fa, + struct fuse_buffer *in, + struct dentry *dentry, const char *name) +{ + return 0; +} + +static int fuse_removexattr_backing(struct bpf_fuse_args *fa, int *out, + struct dentry *dentry, const char *name) +{ + struct path *backing_path =3D &get_fuse_dentry(dentry)->backing_path; + + /* TODO account for changes of the name by prefilter */ + *out =3D vfs_removexattr(&nop_mnt_idmap, backing_path->dentry, name); + return 0; +} + +static int fuse_removexattr_finalize(struct bpf_fuse_args *fa, int *out, + struct dentry *dentry, const char *name) +{ + return 0; +} + +int fuse_bpf_removexattr(int *out, struct inode *inode, struct dentry *den= try, const char *name) +{ + return bpf_fuse_backing(inode, struct fuse_buffer, out, + fuse_removexattr_initialize_in, fuse_removexattr_initialize_out, + fuse_removexattr_backing, fuse_removexattr_finalize, + dentry, name); +} + static inline void fuse_bpf_aio_put(struct fuse_bpf_aio_req *aio_req) { if (refcount_dec_and_test(&aio_req->ref)) diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 74540f308636..243a8fe0c343 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1426,6 +1426,13 @@ int fuse_bpf_copy_file_range(ssize_t *out, struct in= ode *inode, struct file *fil size_t len, unsigned int flags); int fuse_bpf_fsync(int *out, struct inode *inode, struct file *file, loff_= t start, loff_t end, int datasync); int fuse_bpf_dir_fsync(int *out, struct inode *inode, struct file *file, l= off_t start, loff_t end, int datasync); +int fuse_bpf_getxattr(int *out, struct inode *inode, struct dentry *dentry, + const char *name, void *value, size_t size); +int fuse_bpf_listxattr(ssize_t *out, struct inode *inode, struct dentry *d= entry, char *list, size_t size); +int fuse_bpf_setxattr(int *out, struct inode *inode, struct dentry *dentry, + const char *name, const void *value, size_t size, + int flags); +int fuse_bpf_removexattr(int *out, struct inode *inode, struct dentry *den= try, const char *name); int fuse_bpf_file_read_iter(ssize_t *out, struct inode *inode, struct kioc= b *iocb, struct iov_iter *to); int fuse_bpf_file_write_iter(ssize_t *out, struct inode *inode, struct kio= cb *iocb, struct iov_iter *from); int fuse_bpf_file_fallocate(int *out, struct inode *inode, struct file *fi= le, int mode, loff_t offset, loff_t length); @@ -1520,6 +1527,29 @@ static inline int fuse_bpf_dir_fsync(int *out, struc= t inode *inode, struct file return 0; } =20 +static inline int fuse_bpf_getxattr(int *out, struct inode *inode, struct = dentry *dentry, + const char *name, void *value, size_t size) +{ + return 0; +} + +static inline int fuse_bpf_listxattr(ssize_t *out, struct inode *inode, st= ruct dentry *dentry, char *list, size_t size) +{ + return 0; +} + +static inline int fuse_bpf_setxattr(int *out, struct inode *inode, struct = dentry *dentry, + const char *name, const void *value, size_t size, + int flags) +{ + return 0; +} + +static inline int fuse_bpf_removexattr(int *out, struct inode *inode, stru= ct dentry *dentry, const char *name) +{ + return 0; +} + static inline int fuse_bpf_file_read_iter(ssize_t *out, struct inode *inod= e, struct kiocb *iocb, struct iov_iter *to) { return 0; diff --git a/fs/fuse/xattr.c b/fs/fuse/xattr.c index 49c01559580f..d00f7dc50038 100644 --- a/fs/fuse/xattr.c +++ b/fs/fuse/xattr.c @@ -118,6 +118,9 @@ ssize_t fuse_listxattr(struct dentry *entry, char *list= , size_t size) if (fuse_is_bad(inode)) return -EIO; =20 + if (fuse_bpf_listxattr(&ret, inode, entry, list, size)) + return ret; + if (!fuse_allow_current_process(fm->fc)) return -EACCES; =20 @@ -182,9 +185,14 @@ static int fuse_xattr_get(const struct xattr_handler *= handler, struct dentry *dentry, struct inode *inode, const char *name, void *value, size_t size) { + int err; + if (fuse_is_bad(inode)) return -EIO; =20 + if (fuse_bpf_getxattr(&err, inode, dentry, name, value, size)) + return err; + return fuse_getxattr(inode, name, value, size); } =20 @@ -194,9 +202,19 @@ static int fuse_xattr_set(const struct xattr_handler *= handler, const char *name, const void *value, size_t size, int flags) { + int err; + bool handled; + if (fuse_is_bad(inode)) return -EIO; =20 + if (value) + handled =3D fuse_bpf_setxattr(&err, inode, dentry, name, value, size, fl= ags); + else + handled =3D fuse_bpf_removexattr(&err, inode, dentry, name); + if (handled) + return err; + if (!value) return fuse_removexattr(inode, name); =20 --=20 2.40.0.634.g4ca3ef3211-goog From nobody Wed Dec 17 19:22:07 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 DBF3FC77B75 for ; Tue, 18 Apr 2023 01:44:33 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231315AbjDRBob (ORCPT ); Mon, 17 Apr 2023 21:44:31 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53740 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230064AbjDRBnQ (ORCPT ); Mon, 17 Apr 2023 21:43:16 -0400 Received: from mail-yb1-xb4a.google.com (mail-yb1-xb4a.google.com [IPv6:2607:f8b0:4864:20::b4a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id A7FD76E97 for ; Mon, 17 Apr 2023 18:42:04 -0700 (PDT) Received: by mail-yb1-xb4a.google.com with SMTP id 188-20020a2504c5000000b00b8f6f5dca5dso8198561ybe.7 for ; Mon, 17 Apr 2023 18:42:04 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20221208; t=1681782106; x=1684374106; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=nvRLddSkeIm9PecIuzQsl89859K3ZUpPQfxG17id1Mk=; b=XlxFgRg0kxYxSjyZXrqofVDZz+iSvTOODqUtf12czQ/mSsTbgNfeu3peW779Uw8Dc2 18GKMXLuUG+C+s8RgVUM+pHcTAB4mu2P0Oa2/5s3x2TJHxPzoaNL0gqI9Ve2X2bcARlj 8OtlZ4AlEhFGz0C72gRftnGxUtBvNhSxFRzbv5iJaoXCIKt7BX4WeruaHXiCykenQ9ZP 0MXj57A6OYaCu2AkWRsuzJcjqTUNLUf1dqEO2GjZ6bkHl298GXkexM1oElOZAKVXcdsA COWy7z5p3ou2wEN10bVz4/gExra0QrVpaW+GfbTKy+ZCyMZynWOs0Tp9Dtm3XdypbrDI YBXw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681782106; x=1684374106; 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=nvRLddSkeIm9PecIuzQsl89859K3ZUpPQfxG17id1Mk=; b=VbfSjR6RCuYHcWxt8Dgk4JgYmiOR/kUnNkSwfZ7ZexPgMaVjCRza6SRcHHH+DSq4qZ bmnZemDh69UohvqV7qOGSYJlMOP4dRO4hXQ+eCNiMP3YOBR7fPnaW0hKfcumH4j/ye82 UH1r820W8jIGlkVm0WMnKgDnxF2+wISKzjXNi9GOyWLGEYgdUIs80rivmkvqYLY8o4H7 /lx6SmhmdJtHhGwAQo7HiVdN9keRp02v9bF6aJil2HjMTquAVakmEpSZsmczU1xVz2QJ uz2W9hVUvFY1BX7sYnfCorpr6RudO3qGiMmkxdwC2ziFFZ7k/T+t3uKxa8dR+1oieMCQ S7Ww== X-Gm-Message-State: AAQBX9e11j2xFgkjzlD+Q9O71ebzI9oUB6YTvv4MyF71fig99vfo5Trc xkIQNt9K3IxFHSv2DVu5P7E22lp0lec= X-Google-Smtp-Source: AKy350bnnjuHdh4NImroO7h36oSrvoNCrPKP80I7mzBIz856gdltQzBiy3nX4BcegzARjsg+1e/Y1F6L+7o= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:e67a:98b0:942d:86aa]) (user=drosen job=sendgmr) by 2002:a25:cc05:0:b0:b8b:eea7:525a with SMTP id l5-20020a25cc05000000b00b8beea7525amr8483126ybf.7.1681782106223; Mon, 17 Apr 2023 18:41:46 -0700 (PDT) Date: Mon, 17 Apr 2023 18:40:24 -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-25-drosen@google.com> Subject: [RFC PATCH v3 24/37] fuse-bpf: Add symlink/link support 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 Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This adds backing support for FUSE_LINK, FUSE_READLINK, and FUSE_SYMLINK Signed-off-by: Daniel Rosenberg --- fs/fuse/backing.c | 327 ++++++++++++++++++++++++++++++++++++++++++++++ fs/fuse/dir.c | 11 ++ fs/fuse/fuse_i.h | 20 +++ 3 files changed, 358 insertions(+) diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index eb3eb184c867..e807ae4f6f53 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -2502,6 +2502,125 @@ int fuse_bpf_unlink(int *out, struct inode *dir, st= ruct dentry *entry) dir, entry); } =20 +struct fuse_link_args { + struct fuse_link_in in; + struct fuse_buffer name; +}; + +static int fuse_link_initialize_in(struct bpf_fuse_args *fa, struct fuse_l= ink_args *args, + struct dentry *entry, struct inode *dir, + struct dentry *newent) +{ + struct inode *src_inode =3D entry->d_inode; + + *args =3D (struct fuse_link_args) { + .in =3D (struct fuse_link_in) { + .oldnodeid =3D get_node_id(src_inode), + }, + .name =3D (struct fuse_buffer) { + .data =3D (void *) newent->d_name.name, + .size =3D newent->d_name.len + 1, + .max_size =3D NAME_MAX + 1, + .flags =3D BPF_FUSE_VARIABLE_SIZE | BPF_FUSE_MUST_ALLOCATE, + }, + }; + + *fa =3D (struct bpf_fuse_args) { + .info =3D (struct bpf_fuse_meta_info) { + .opcode =3D FUSE_LINK, + }, + .in_numargs =3D 2, + .in_args[0] =3D (struct bpf_fuse_arg) { + .size =3D sizeof(args->in), + .value =3D &args->in, + }, + .in_args[1] =3D (struct bpf_fuse_arg) { + .is_buffer =3D true, + .buffer =3D &args->name, + }, + }; + + return 0; +} + +static int fuse_link_initialize_out(struct bpf_fuse_args *fa, struct fuse_= link_args *args, + struct dentry *entry, struct inode *dir, + struct dentry *newent) +{ + return 0; +} + +static int fuse_link_backing(struct bpf_fuse_args *fa, int *out, struct de= ntry *entry, + struct inode *dir, struct dentry *newent) +{ + struct path backing_old_path; + struct path backing_new_path; + struct dentry *backing_dir_dentry; + struct inode *fuse_new_inode =3D NULL; + struct fuse_inode *fuse_dir_inode =3D get_fuse_inode(dir); + struct inode *backing_dir_inode =3D fuse_dir_inode->backing_inode; + + *out =3D 0; + get_fuse_backing_path(entry, &backing_old_path); + if (!backing_old_path.dentry) + return -EBADF; + + get_fuse_backing_path(newent, &backing_new_path); + if (!backing_new_path.dentry) { + *out =3D -EBADF; + goto err_dst_path; + } + + backing_dir_dentry =3D dget_parent(backing_new_path.dentry); + backing_dir_inode =3D d_inode(backing_dir_dentry); + + inode_lock_nested(backing_dir_inode, I_MUTEX_PARENT); + *out =3D vfs_link(backing_old_path.dentry, &nop_mnt_idmap, + backing_dir_inode, backing_new_path.dentry, NULL); + inode_unlock(backing_dir_inode); + if (*out) + goto out; + + if (d_really_is_negative(backing_new_path.dentry) || + unlikely(d_unhashed(backing_new_path.dentry))) { + *out =3D -EINVAL; + /** + * TODO: overlayfs responds to this situation with a + * lookupOneLen. Should we do that too? + */ + goto out; + } + + fuse_new_inode =3D fuse_iget_backing(dir->i_sb, fuse_dir_inode->nodeid, b= acking_dir_inode); + if (IS_ERR(fuse_new_inode)) { + *out =3D PTR_ERR(fuse_new_inode); + goto out; + } + d_instantiate(newent, fuse_new_inode); + +out: + dput(backing_dir_dentry); + path_put(&backing_new_path); +err_dst_path: + path_put(&backing_old_path); + return *out; +} + +static int fuse_link_finalize(struct bpf_fuse_args *fa, int *out, struct d= entry *entry, + struct inode *dir, struct dentry *newent) +{ + return 0; +} + +int fuse_bpf_link(int *out, struct inode *inode, struct dentry *entry, + struct inode *newdir, struct dentry *newent) +{ + return bpf_fuse_backing(inode, struct fuse_link_args, out, + fuse_link_initialize_in, fuse_link_initialize_out, + fuse_link_backing, fuse_link_finalize, + entry, newdir, newent); +} + struct fuse_getattr_args { struct fuse_getattr_in in; struct fuse_attr_out out; @@ -2790,6 +2909,214 @@ int fuse_bpf_statfs(int *out, struct inode *inode, = struct dentry *dentry, struct dentry, buf); } =20 +struct fuse_get_link_args { + struct fuse_buffer name; + struct fuse_buffer path; +}; + +static int fuse_get_link_initialize_in(struct bpf_fuse_args *fa, struct fu= se_get_link_args *args, + struct inode *inode, struct dentry *dentry, + struct delayed_call *callback) +{ + /* + * TODO + * If we want to handle changing these things, we'll need to copy + * the lower fs's data into our own buffer, and provide our own callback + * to free that buffer. + * + * Pre could change the name we're looking at + * postfilter can change the name we return + * + * We ought to only make that buffer if it's been requested, so leaving + * this unimplemented for the moment + */ + *args =3D (struct fuse_get_link_args) { + .name =3D (struct fuse_buffer) { + .data =3D (void *) dentry->d_name.name, + .size =3D dentry->d_name.len + 1, + .max_size =3D NAME_MAX + 1, + .flags =3D BPF_FUSE_VARIABLE_SIZE | BPF_FUSE_MUST_ALLOCATE, + }, + }; + + *fa =3D (struct bpf_fuse_args) { + .info =3D (struct bpf_fuse_meta_info) { + .opcode =3D FUSE_READLINK, + .nodeid =3D get_node_id(inode), + }, + .in_numargs =3D 1, + .in_args[0] =3D (struct bpf_fuse_arg) { + .is_buffer =3D true, + .buffer =3D &args->name, + }, + }; + + return 0; +} + +static int fuse_get_link_initialize_out(struct bpf_fuse_args *fa, struct f= use_get_link_args *args, + struct inode *inode, struct dentry *dentry, + struct delayed_call *callback) +{ + // TODO +#if 0 + args->path =3D (struct fuse_buffer) { + .data =3D NULL, + .size =3D 0, + .max_size =3D PATH_MAX, + .flags =3D BPF_FUSE_VARIABLE_SIZE | BPF_FUSE_MUST_ALLOCATE, + }; + fa->out_numargs =3D 1; + fa->out_args[0].is_buffer =3D true; + fa->out_args[0].buffer =3D &args->path; +#endif + + return 0; +} + +static int fuse_get_link_backing(struct bpf_fuse_args *fa, const char **ou= t, + struct inode *inode, struct dentry *dentry, + struct delayed_call *callback) +{ + struct path backing_path; + + if (!dentry) { + *out =3D ERR_PTR(-ECHILD); + return PTR_ERR(*out); + } + + get_fuse_backing_path(dentry, &backing_path); + if (!backing_path.dentry) { + *out =3D ERR_PTR(-ECHILD); + return PTR_ERR(*out); + } + + /* + * TODO: If we want to do our own thing, copy the data and then call the + * callback + */ + *out =3D vfs_get_link(backing_path.dentry, callback); + + path_put(&backing_path); + return 0; +} + +static int fuse_get_link_finalize(struct bpf_fuse_args *fa, const char **o= ut, + struct inode *inode, struct dentry *dentry, + struct delayed_call *callback) +{ + return 0; +} + +int fuse_bpf_get_link(const char **out, struct inode *inode, struct dentry= *dentry, + struct delayed_call *callback) +{ + return bpf_fuse_backing(inode, struct fuse_get_link_args, out, + fuse_get_link_initialize_in, fuse_get_link_initialize_out, + fuse_get_link_backing, fuse_get_link_finalize, + inode, dentry, callback); +} + +struct fuse_symlink_args { + struct fuse_buffer name; + struct fuse_buffer path; +}; + +static int fuse_symlink_initialize_in(struct bpf_fuse_args *fa, struct fus= e_symlink_args *args, + struct inode *dir, struct dentry *entry, const char *link, int l= en) +{ + *args =3D (struct fuse_symlink_args) { + .name =3D (struct fuse_buffer) { + .data =3D (void *) entry->d_name.name, + .size =3D entry->d_name.len + 1, + .flags =3D BPF_FUSE_IMMUTABLE, + }, + .path =3D (struct fuse_buffer) { + .data =3D (void *) link, + .size =3D len, + .max_size =3D PATH_MAX, + .flags =3D BPF_FUSE_VARIABLE_SIZE | BPF_FUSE_MUST_ALLOCATE, + }, + }; + *fa =3D (struct bpf_fuse_args) { + .info =3D (struct bpf_fuse_meta_info) { + .nodeid =3D get_node_id(dir), + .opcode =3D FUSE_SYMLINK, + }, + .in_numargs =3D 2, + .in_args[0] =3D (struct bpf_fuse_arg) { + .is_buffer =3D true, + .buffer =3D &args->name, + }, + .in_args[1] =3D (struct bpf_fuse_arg) { + .is_buffer =3D true, + .buffer =3D &args->path, + }, + }; + + return 0; +} + +static int fuse_symlink_initialize_out(struct bpf_fuse_args *fa, struct fu= se_symlink_args *args, + struct inode *dir, struct dentry *entry, const char *link, int = len) +{ + return 0; +} + +static int fuse_symlink_backing(struct bpf_fuse_args *fa, int *out, + struct inode *dir, struct dentry *entry, const char *link, int len) +{ + struct fuse_inode *fuse_inode =3D get_fuse_inode(dir); + struct inode *backing_inode =3D fuse_inode->backing_inode; + struct path backing_path; + struct inode *inode =3D NULL; + + *out =3D 0; + //TODO Actually deal with changing the backing entry in symlink + get_fuse_backing_path(entry, &backing_path); + if (!backing_path.dentry) + return -EBADF; + + inode_lock_nested(backing_inode, I_MUTEX_PARENT); + *out =3D vfs_symlink(&nop_mnt_idmap, backing_inode, backing_path.dentry, + link); + inode_unlock(backing_inode); + if (*out) + goto out; + if (d_really_is_negative(backing_path.dentry) || + unlikely(d_unhashed(backing_path.dentry))) { + *out =3D -EINVAL; + /** + * TODO: overlayfs responds to this situation with a + * lookupOneLen. Should we do that too? + */ + goto out; + } + inode =3D fuse_iget_backing(dir->i_sb, fuse_inode->nodeid, backing_inode); + if (IS_ERR(inode)) { + *out =3D PTR_ERR(inode); + goto out; + } + d_instantiate(entry, inode); +out: + path_put(&backing_path); + return *out; +} + +static int fuse_symlink_finalize(struct bpf_fuse_args *fa, int *out, + struct inode *dir, struct dentry *entry, const char *link, int len) +{ + return 0; +} + +int fuse_bpf_symlink(int *out, struct inode *dir, struct dentry *entry, c= onst char *link, int len) +{ + return bpf_fuse_backing(dir, struct fuse_symlink_args, out, + fuse_symlink_initialize_in, fuse_symlink_initialize_out, + fuse_symlink_backing, fuse_symlink_finalize, + dir, entry, link, len); +} + struct fuse_read_args { struct fuse_read_in in; struct fuse_read_out out; diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 7d589241c9b0..d1c3b2bfb0b1 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1013,6 +1013,10 @@ static int fuse_symlink(struct mnt_idmap *idmap, str= uct inode *dir, struct fuse_mount *fm =3D get_fuse_mount(dir); unsigned len =3D strlen(link) + 1; FUSE_ARGS(args); + int err; + + if (fuse_bpf_symlink(&err, dir, entry, link, len)) + return err; =20 args.opcode =3D FUSE_SYMLINK; args.in_numargs =3D 2; @@ -1219,6 +1223,9 @@ static int fuse_link(struct dentry *entry, struct ino= de *newdir, struct fuse_mount *fm =3D get_fuse_mount(inode); FUSE_ARGS(args); =20 + if (fuse_bpf_link(&err, inode, entry, newdir, newent)) + return err; + memset(&inarg, 0, sizeof(inarg)); inarg.oldnodeid =3D get_node_id(inode); args.opcode =3D FUSE_LINK; @@ -1618,12 +1625,16 @@ static const char *fuse_get_link(struct dentry *den= try, struct inode *inode, { struct fuse_conn *fc =3D get_fuse_conn(inode); struct page *page; + const char *out =3D NULL; int err; =20 err =3D -EIO; if (fuse_is_bad(inode)) goto out_err; =20 + if (fuse_bpf_get_link(&out, inode, dentry, callback)) + return out; + if (fc->cache_symlinks) return page_get_link(dentry, inode, callback); =20 diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 243a8fe0c343..121d31a04e79 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1417,6 +1417,7 @@ int fuse_bpf_rename2(int *out, struct inode *olddir, = struct dentry *oldent, int fuse_bpf_rename(int *out, struct inode *olddir, struct dentry *oldent, struct inode *newdir, struct dentry *newent); int fuse_bpf_unlink(int *out, struct inode *dir, struct dentry *entry); +int fuse_bpf_link(int *out, struct inode *inode, struct dentry *entry, str= uct inode *dir, struct dentry *newent); int fuse_bpf_release(int *out, struct inode *inode, struct file *file); int fuse_bpf_releasedir(int *out, struct inode *inode, struct file *file); int fuse_bpf_flush(int *out, struct inode *inode, struct file *file, fl_ow= ner_t id); @@ -1441,6 +1442,9 @@ int fuse_bpf_getattr(int *out, struct inode *inode, c= onst struct dentry *entry, u32 request_mask, unsigned int flags); int fuse_bpf_setattr(int *out, struct inode *inode, struct dentry *dentry,= struct iattr *attr, struct file *file); int fuse_bpf_statfs(int *out, struct inode *inode, struct dentry *dentry, = struct kstatfs *buf); +int fuse_bpf_get_link(const char **out, struct inode *inode, struct dentry= *dentry, + struct delayed_call *callback); +int fuse_bpf_symlink(int *out, struct inode *dir, struct dentry *entry, co= nst char *link, int len); int fuse_bpf_readdir(int *out, struct inode *inode, struct file *file, str= uct dir_context *ctx); int fuse_bpf_access(int *out, struct inode *inode, int mask); =20 @@ -1490,6 +1494,11 @@ static inline int fuse_bpf_unlink(int *out, struct i= node *dir, struct dentry *en return 0; } =20 +static inline int fuse_bpf_link(int *out, struct inode *inode, struct dent= ry *entry, struct inode *dir, struct dentry *newent) +{ + return 0; +} + static inline int fuse_bpf_release(int *out, struct inode *inode, struct f= ile *file) { return 0; @@ -1586,6 +1595,17 @@ static inline int fuse_bpf_statfs(int *out, struct i= node *inode, struct dentry * return 0; } =20 +static inline int fuse_bpf_get_link(const char **out, struct inode *inode,= struct dentry *dentry, + struct delayed_call *callback) +{ + return 0; +} + +static inline int fuse_bpf_symlink(int *out, struct inode *dir, struct den= try *entry, const char *link, int len) +{ + return 0; +} + static inline int fuse_bpf_readdir(int *out, struct inode *inode, struct f= ile *file, struct dir_context *ctx) { return 0; --=20 2.40.0.634.g4ca3ef3211-goog From nobody Wed Dec 17 19:22:07 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 004CEC77B75 for ; Tue, 18 Apr 2023 01:43:50 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231249AbjDRBnu (ORCPT ); Mon, 17 Apr 2023 21:43:50 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53778 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230257AbjDRBmw (ORCPT ); Mon, 17 Apr 2023 21:42:52 -0400 Received: from mail-yb1-xb49.google.com (mail-yb1-xb49.google.com [IPv6:2607:f8b0:4864:20::b49]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 801DB55AC for ; Mon, 17 Apr 2023 18:41:48 -0700 (PDT) Received: by mail-yb1-xb49.google.com with SMTP id 63-20020a250042000000b00b924691cef2so3639141yba.6 for ; Mon, 17 Apr 2023 18:41:48 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20221208; t=1681782108; x=1684374108; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=f+tC+6XFyVz5iTVVtehlShhqqyPSU3xUJhrgatVmT44=; b=rIbd7s/2ek5G8EhMwiYEWTKGjSzZ7qOMthCTcIGG3nURoO/goLZ/THknkdr/Day049 NOH2ThvnPaKS985+9T2Fj65xFkSb8mN7EIMqZpgOoWSQGsC7+K9Lo5IZgHyPfy0r15ie 76fB2pTkT8+QMtmwqL4sijLUqof+ZN+hn8E6tAbc46UFjpy1TcWRQsd5ACEpzyMLSmW0 74VzZ/vHRFvKlgEgwp6sWLYGEao1V0rHzw1TwBzEvw3oDwOtehIqQJ89/IY1OKVdN93F kJFpSyE1Czbvvz7vfD2Xz00Ts/BoJbP3VVBKgAUxrEePjcGseWyzi6UVxppwiAp8z3XC uLNw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681782108; x=1684374108; 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=f+tC+6XFyVz5iTVVtehlShhqqyPSU3xUJhrgatVmT44=; b=HfEC2tDYDRhzdmxnvKljtL0WmqdOKxSM/5xO8whkIEHKvVGzvur/xJpQ4PfBEw0YpL Cps0Yq49EStT8njQyNg+1X/rCm7Y/kgPHgNhWhLP0DSD2NgybMY6tUBAFTmFJy3xaxew 64zG0L4ISpfB6trNkSzHrQnHhsjDWapLesVQJmUEw3swfzv3xkARZqTPhAWb6iqUhxzp X5+8CMRBNd0xPldWBk9J28PnoISaDZELvxjGpc2ZzZG1nqD8GBOCG02ImjniYhmktkeo Gcfq+QllCaMPT331t7pUaUwTW9HBwDFag2DRdFBSw57MQYkby2c3FrzJ1uCEKwZHypJG jhuA== X-Gm-Message-State: AAQBX9dCFcHyhMxv+2JSLyXVPt587+VRcScrolNZtycJFr+Zl1m/P+j3 g0D+865Rn17W08/oH3pb2Lqdd2u+peg= X-Google-Smtp-Source: AKy350az5srFMYJyfvvtfh6sqad66/N+PonFGFWZFCufX/5Wu37wcKRBKPvzh2skEJqf7c2CQLiJDByUTVw= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:e67a:98b0:942d:86aa]) (user=drosen job=sendgmr) by 2002:a81:ad0e:0:b0:545:6106:5334 with SMTP id l14-20020a81ad0e000000b0054561065334mr10694952ywh.8.1681782108243; Mon, 17 Apr 2023 18:41:48 -0700 (PDT) Date: Mon, 17 Apr 2023 18:40:25 -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-26-drosen@google.com> Subject: [RFC PATCH v3 25/37] fuse-bpf: allow mounting with no userspace daemon 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 Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" When using fuse-bpf in pure passthrough mode, we don't explicitly need a userspace daemon. This allows simple testing of the backing operations. Signed-off-by: Daniel Rosenberg Signed-off-by: Paul Lawrence --- fs/fuse/fuse_i.h | 4 ++++ fs/fuse/inode.c | 25 +++++++++++++++++++------ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 121d31a04e79..2bd45c8658e8 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -566,6 +566,7 @@ struct fuse_fs_context { bool no_control:1; bool no_force_umount:1; bool legacy_opts_show:1; + bool no_daemon:1; enum fuse_dax_mode dax_mode; unsigned int max_read; unsigned int blksize; @@ -847,6 +848,9 @@ struct fuse_conn { /* Is tmpfile not implemented by fs? */ unsigned int no_tmpfile:1; =20 + /** BPF Only, no Daemon running */ + unsigned int no_daemon:1; + /** The number of requests waiting for completion */ atomic_t num_waiting; =20 diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 3dfb9cfb6e73..31f34962bc9b 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -756,6 +756,7 @@ enum { OPT_MAX_READ, OPT_BLKSIZE, OPT_ROOT_DIR, + OPT_NO_DAEMON, OPT_ERR }; =20 @@ -771,6 +772,7 @@ static const struct fs_parameter_spec fuse_fs_parameter= s[] =3D { fsparam_u32 ("blksize", OPT_BLKSIZE), fsparam_string ("subtype", OPT_SUBTYPE), fsparam_u32 ("root_dir", OPT_ROOT_DIR), + fsparam_flag ("no_daemon", OPT_NO_DAEMON), {} }; =20 @@ -860,6 +862,11 @@ static int fuse_parse_param(struct fs_context *fsc, st= ruct fs_parameter *param) return invalfc(fsc, "Unable to open root directory"); break; =20 + case OPT_NO_DAEMON: + ctx->no_daemon =3D true; + ctx->fd_present =3D true; + break; + default: return -EINVAL; } @@ -1419,7 +1426,7 @@ void fuse_send_init(struct fuse_mount *fm) ia->args.nocreds =3D true; ia->args.end =3D process_init_reply; =20 - if (fuse_simple_background(fm, &ia->args, GFP_KERNEL) !=3D 0) + if (unlikely(fm->fc->no_daemon) || fuse_simple_background(fm, &ia->args, = GFP_KERNEL) !=3D 0) process_init_reply(fm, &ia->args, -ENOTCONN); } EXPORT_SYMBOL_GPL(fuse_send_init); @@ -1694,6 +1701,7 @@ int fuse_fill_super_common(struct super_block *sb, st= ruct fuse_fs_context *ctx) fc->destroy =3D ctx->destroy; fc->no_control =3D ctx->no_control; fc->no_force_umount =3D ctx->no_force_umount; + fc->no_daemon =3D ctx->no_daemon; =20 err =3D -ENOMEM; root =3D fuse_get_root_inode(sb, ctx->rootmode, ctx->root_dir); @@ -1740,7 +1748,7 @@ static int fuse_fill_super(struct super_block *sb, st= ruct fs_context *fsc) struct fuse_fs_context *ctx =3D fsc->fs_private; int err; =20 - if (!ctx->file || !ctx->rootmode_present || + if (!!ctx->file =3D=3D ctx->no_daemon || !ctx->rootmode_present || !ctx->user_id_present || !ctx->group_id_present) return -EINVAL; =20 @@ -1748,10 +1756,12 @@ static int fuse_fill_super(struct super_block *sb, = struct fs_context *fsc) * Require mount to happen from the same user namespace which * opened /dev/fuse to prevent potential attacks. */ - if ((ctx->file->f_op !=3D &fuse_dev_operations) || - (ctx->file->f_cred->user_ns !=3D sb->s_user_ns)) - return -EINVAL; - ctx->fudptr =3D &ctx->file->private_data; + if (ctx->file) { + if ((ctx->file->f_op !=3D &fuse_dev_operations) || + (ctx->file->f_cred->user_ns !=3D sb->s_user_ns)) + return -EINVAL; + ctx->fudptr =3D &ctx->file->private_data; + } =20 err =3D fuse_fill_super_common(sb, ctx); if (err) @@ -1801,6 +1811,9 @@ static int fuse_get_tree(struct fs_context *fsc) =20 fsc->s_fs_info =3D fm; =20 + if (ctx->no_daemon) + return get_tree_nodev(fsc, fuse_fill_super);; + if (ctx->fd_present) ctx->file =3D fget(ctx->fd); =20 --=20 2.40.0.634.g4ca3ef3211-goog From nobody Wed Dec 17 19:22:07 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 1E6ABC77B72 for ; Tue, 18 Apr 2023 01:44:46 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231334AbjDRBoo (ORCPT ); Mon, 17 Apr 2023 21:44:44 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53706 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231186AbjDRBna (ORCPT ); Mon, 17 Apr 2023 21:43:30 -0400 Received: from mail-yb1-xb4a.google.com (mail-yb1-xb4a.google.com [IPv6:2607:f8b0:4864:20::b4a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 9644EBF for ; Mon, 17 Apr 2023 18:42:10 -0700 (PDT) Received: by mail-yb1-xb4a.google.com with SMTP id v200-20020a252fd1000000b00b8f548a72bbso12436676ybv.9 for ; Mon, 17 Apr 2023 18:42:10 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20221208; t=1681782110; x=1684374110; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=zWwdeYSbe2MZzb6+0YuKWkWzO0OIunr3DIdjLXb+QPs=; b=bvVRAayaKb3f4XHecCOw1fq1zrxbTJlfu+X0j4JJ+ODZZ8s343yV2zF5V5cJPVfBrx vO1QktBuUoSHL08PHmb741zrbYTtLR44mlVkerlNbRbCJGYvY5rfLmHur4kpmRZXckp/ 5Xt5y4R3xULWEw01wuUy+z/jQtalviK0udMiGXZVR4o34gUJMO46tjSd9TtEsLa3oC0C Qs9ggxL4witMvtnXFhB17F+6dIIHcCSX5M76pppw2FB4zNzSuRHvs30bGFNla01g3E11 K2LsOQQ7l/LXtTgEgSoMdbEAFkvDtok6PTyc3QsDYVhsfSDu4Pfk86mtQUxZrOSNJ97d ihKA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681782110; x=1684374110; 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=zWwdeYSbe2MZzb6+0YuKWkWzO0OIunr3DIdjLXb+QPs=; b=GkMqVkTSA4mBxptcuHTEs738R1c78YFGh/cXNen0BgIgYkWxAp5Pl47zhg8t+R02zE b2FF4/gK72ZELC8bozTiiOjlZUlS+tQpfVhDLvBlhu22/fBHqFYH01X68gMCTDacaiLn ZtnFTkc3xi7CShNQAt+p0qV+TOcdxvMSACPvzOrSbQ9Y1w0PWCrfgZejjtXDOZPOAd6M F3d9uTHV8Oc8E9dAoV6/dzr2k+r3/+wQj1kUyLCRzb/aklQGW2JjxNEAv063U+7o4k6c ma7O0zUTxAFE+wROWoTeNp08y714rDkbXrhzzZ4/uIEEdHEQNJzwDngaabded6ll3W4x iroA== X-Gm-Message-State: AAQBX9dejJ+DoPjlyV9zUNYvivcirv10u8Vdmmn8xaK1l7YBkoIizQlq Ics6zl6i3GIkWgEmOb5Q762byF8Vjcg= X-Google-Smtp-Source: AKy350aIiy0yEVjIhRUwhKZEZY7qdBZhQoYfVQEj5j3ErV9/GTclD1JkHLmadrVOBoXwbliIvZF/mWfRLJ4= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:e67a:98b0:942d:86aa]) (user=drosen job=sendgmr) by 2002:a0d:ec02:0:b0:54c:2723:560d with SMTP id q2-20020a0dec02000000b0054c2723560dmr10855123ywn.3.1681782110238; Mon, 17 Apr 2023 18:41:50 -0700 (PDT) Date: Mon, 17 Apr 2023 18:40:26 -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-27-drosen@google.com> Subject: [RFC PATCH v3 26/37] bpf: Increase struct_op limits 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 Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Fuse bpf goes a bit past the '64' limit here, although in reality, this limit seems to be more like 37. After 37, we start overrunning the safety checks while setting up the trampoline. This simply doubles some of these values. This will have the same issue, as we'll run out of space way before hitting the 128 limit, but for now that unblocks fuse-bpf. Signed-off-by: Daniel Rosenberg --- include/linux/bpf.h | 2 +- kernel/bpf/bpf_struct_ops.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/linux/bpf.h b/include/linux/bpf.h index 18b592fde896..c006f823e634 100644 --- a/include/linux/bpf.h +++ b/include/linux/bpf.h @@ -1537,7 +1537,7 @@ struct bpf_link_primer { struct bpf_struct_ops_value; struct btf_member; =20 -#define BPF_STRUCT_OPS_MAX_NR_MEMBERS 64 +#define BPF_STRUCT_OPS_MAX_NR_MEMBERS 128 struct bpf_struct_ops { const struct bpf_verifier_ops *verifier_ops; int (*init)(struct btf *btf); diff --git a/kernel/bpf/bpf_struct_ops.c b/kernel/bpf/bpf_struct_ops.c index d3f0a4825fa6..deb9eecaf1e4 100644 --- a/kernel/bpf/bpf_struct_ops.c +++ b/kernel/bpf/bpf_struct_ops.c @@ -417,7 +417,7 @@ static long bpf_struct_ops_map_update_elem(struct bpf_m= ap *map, void *key, udata =3D &uvalue->data; kdata =3D &kvalue->data; image =3D st_map->image; - image_end =3D st_map->image + PAGE_SIZE; + image_end =3D st_map->image + 2 * PAGE_SIZE; =20 for_each_member(i, t, member) { const struct btf_type *mtype, *ptype; @@ -688,7 +688,7 @@ static struct bpf_map *bpf_struct_ops_map_alloc(union b= pf_attr *attr) st_map->links =3D bpf_map_area_alloc(btf_type_vlen(t) * sizeof(struct bpf_links *), NUMA_NO_NODE); - st_map->image =3D bpf_jit_alloc_exec(PAGE_SIZE); + st_map->image =3D bpf_jit_alloc_exec(2 * PAGE_SIZE); if (!st_map->uvalue || !st_map->links || !st_map->image) { __bpf_struct_ops_map_free(map); return ERR_PTR(-ENOMEM); --=20 2.40.0.634.g4ca3ef3211-goog From nobody Wed Dec 17 19:22:07 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 430A1C77B75 for ; Tue, 18 Apr 2023 01:44:41 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231216AbjDRBoj (ORCPT ); Mon, 17 Apr 2023 21:44:39 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53814 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230459AbjDRBnb (ORCPT ); Mon, 17 Apr 2023 21:43:31 -0400 Received: from mail-yw1-x114a.google.com (mail-yw1-x114a.google.com [IPv6:2607:f8b0:4864:20::114a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 37EEC4222 for ; Mon, 17 Apr 2023 18:42:13 -0700 (PDT) Received: by mail-yw1-x114a.google.com with SMTP id 00721157ae682-552e3233b26so32077457b3.9 for ; Mon, 17 Apr 2023 18:42:13 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20221208; t=1681782112; x=1684374112; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=tnG7XRDx2oVnxRfx5yqwQBjNsF82L1FllZqCFSzhSvk=; b=SkI3OL7Xwcb2D8buNw1xsFgokzhAAQBXqENLcRBKEhMxZBceAdT76zRqhUKaAlZRib aI6PlfHc5n4ddtN3+6mjTW4Cs8BBdyXGjyExSVPyeQf5XRY0Liaw+CyvPQArNpVYYnib TGm738pcBWjnR5XstajeRfCSTCqIp9dGcgPyzLhYYauS/Ba9DT/7Oo4iwDdfQ5MeXhvv m9omEapvVlOwz2XNsPXLPYl6qDc4wN5t+WJMQ3+JHgRWz6iur0twsD7zOIFKKeIe51D7 dw3h5jHufm+Ui9oRM1TAW/H897y1hff2PlchumkhwBxLImVetPWMILw+PxvkA+8WTkDa ztcA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681782112; x=1684374112; 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=tnG7XRDx2oVnxRfx5yqwQBjNsF82L1FllZqCFSzhSvk=; b=UmUaHr4w5L5GZYziru10QtrCT0OKF7qY/s+orrKY849RmRJB9kmZLtxc8esWSARW6/ Yh491LKjw8lYATDgl59HYiHqeEGNvxP3VvDd6OqAOvfmmkrc7BfXl67gcOwZsbhjbRP6 GClHnyj19FiW55GJJpReSDpsIO0FgEyHrzLxWEZ7FNw7pUOXyYGxTtBrbUdPgK946ljB KI7P1dx6Q+jVW7Nb/GYi4Y/gwD/OLP9F19lGrOwn+eIMvCGFTEW5rgOhlg35AbU8b1o/ JBEiBcq2qajUZHyl8mq1Pq/WvhsEsx8xxYrBRWJuxNd3ASLE8wrYFG4uLEGZ0TlruvNR 3/+Q== X-Gm-Message-State: AAQBX9e0mAbPGThWmQaTCYTluGEScSJanXRKVnPkfpvLhdzYdOMSqWst kNgMwdz5mWE0KviJA3oVmlLxTFDoxjY= X-Google-Smtp-Source: AKy350b7LZaOW7Db/+iu/rILXEm8KL2d9x1YzXYuCbML8M1OSVxY74Uh31fhIpMfF3C/uFjNLKcy7inXoIs= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:e67a:98b0:942d:86aa]) (user=drosen job=sendgmr) by 2002:a81:e503:0:b0:54f:40fe:10cc with SMTP id s3-20020a81e503000000b0054f40fe10ccmr10636927ywl.9.1681782112257; Mon, 17 Apr 2023 18:41:52 -0700 (PDT) Date: Mon, 17 Apr 2023 18:40:27 -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-28-drosen@google.com> Subject: [RFC PATCH v3 27/37] fuse-bpf: Add fuse-bpf constants 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 Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This adds constants that fuse_op programs will rely on for communicating what action fuse should take next. Signed-off-by: Daniel Rosenberg --- include/uapi/linux/bpf.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index 4b20a7269bee..6521c40875c7 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -7155,4 +7155,16 @@ struct bpf_iter_num { __u64 __opaque[1]; } __attribute__((aligned(8))); =20 +/* Return Codes for Fuse BPF struct_op 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 + +/* Op Code Filter values for BPF Programs */ +#define FUSE_OPCODE_FILTER 0x0ffff +#define FUSE_PREFILTER 0x10000 +#define FUSE_POSTFILTER 0x20000 + #endif /* _UAPI__LINUX_BPF_H__ */ --=20 2.40.0.634.g4ca3ef3211-goog From nobody Wed Dec 17 19:22:07 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 8079AC77B7C for ; Tue, 18 Apr 2023 01:45:12 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231344AbjDRBpK (ORCPT ); Mon, 17 Apr 2023 21:45:10 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53844 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231146AbjDRBoD (ORCPT ); Mon, 17 Apr 2023 21:44:03 -0400 Received: from mail-pg1-x54a.google.com (mail-pg1-x54a.google.com [IPv6:2607:f8b0:4864:20::54a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 9C44386B5 for ; Mon, 17 Apr 2023 18:42:21 -0700 (PDT) Received: by mail-pg1-x54a.google.com with SMTP id 41be03b00d2f7-517c01e6c98so1559958a12.2 for ; Mon, 17 Apr 2023 18:42:21 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20221208; t=1681782114; x=1684374114; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=uItCp9dmlSq9K5w/dO5KDhSfWqlCiTpddbyFnUdvY1o=; b=6ZG7MrPplM5M81seOPN0hvXOXCiSEwqx1nO2/dhdDCrhZSmG9COpDr1eX2fxHouiFO e/p0ot7SOR2DqTtejD+ao/RKa21LSQkoUotrF6i4BLWIuTlGH9VzaoyiD599p5QSXw2F bQTC68wutfGQQ0eI74oDjeJVg0AEAToVsf2UFB6NyFGzhg8MljgXOsKeuihOuHCSVDsw XreW21ONO4vhlYTSSmXeQYqGb7nwbDqxO4UByZJQczd+uVUQorJ83f6LfpPcZj+7/PMk 2rvlYT4gUyoofHRhNBzeNxQMYk2VT6VUhLo0ZnNohLDKM637Qfr2oAIbXfmiz0imCiDA IFsw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681782114; x=1684374114; 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=uItCp9dmlSq9K5w/dO5KDhSfWqlCiTpddbyFnUdvY1o=; b=Y553qp2Mf3M2VYhf5QpDdI8uDPAvMcS2exIg9m+pRGIQUefeZD3Gq8r6TlKRltMe0I 2JCShFZj3kRl2/ZviiE0EG90DUXe4o8FltYuIRcG5Y3+8rZHm7Ue4vlwx38GAoiWRv4M R20d6MAhbJKzjte2+jKeKbOzC9wvidverdb2GNyGpuh+7naBs8v3OOh/SOgkXK3jFyfq UUJj8t+dhqlfgP6S8noIrpAHtLWPe7rbxyukPhP8bNXOm7j3Rp+uZUKa5ryN5QHX+/Wa dVfHwrsDgqzQmUFfgXw/zEcbBqhXopTOGKBwSnUaFJOdcYZkBPFpyftjGlDnl/zO9zz3 S8gA== X-Gm-Message-State: AAQBX9cqVTrytP+6OuTZujc96q3+49/6eHhTGjHm/OjF4WbLEfTTCTxm NALY7YV3ICJbJ8k9J89SpohJp1J7LrI= X-Google-Smtp-Source: AKy350ZppNJyCCMUoSVMCNcn1UBo1hOQckQ73RYyKMhi9AzOeJREQF2N4JedMcWfQRQtWg8BteVAlnRCSrQ= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:e67a:98b0:942d:86aa]) (user=drosen job=sendgmr) by 2002:a17:903:294c:b0:1a6:898a:41fd with SMTP id li12-20020a170903294c00b001a6898a41fdmr201053plb.6.1681782114402; Mon, 17 Apr 2023 18:41:54 -0700 (PDT) Date: Mon, 17 Apr 2023 18:40:28 -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-29-drosen@google.com> Subject: [RFC PATCH v3 28/37] WIP: bpf: Add fuse_ops struct_op programs 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 Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This introduces a new struct_op type: fuse_ops. This program set provides pre and post filters to run around fuse-bpf calls that act directly on the lower filesystem. The inputs are either fixed structures, or struct fuse_buffer's. These programs are not permitted to make any changes to these fuse_buffers unless they create a dynptr wrapper using the supplied kfunc helpers. Fuse_buffers maintain additional state information that FUSE uses to manage memory and determine if additional set up or checks are needed. Signed-off-by: Daniel Rosenberg --- include/linux/bpf_fuse.h | 189 +++++++++++++++++++++++ kernel/bpf/Makefile | 4 + kernel/bpf/bpf_fuse.c | 241 ++++++++++++++++++++++++++++++ kernel/bpf/bpf_struct_ops_types.h | 4 + kernel/bpf/btf.c | 1 + kernel/bpf/verifier.c | 9 ++ 6 files changed, 448 insertions(+) create mode 100644 kernel/bpf/bpf_fuse.c diff --git a/include/linux/bpf_fuse.h b/include/linux/bpf_fuse.h index ce8b1b347496..780a7889aea2 100644 --- a/include/linux/bpf_fuse.h +++ b/include/linux/bpf_fuse.h @@ -30,6 +30,8 @@ struct fuse_buffer { #define BPF_FUSE_MODIFIED (1 << 3) // The helper function allowed writes t= o the buffer #define BPF_FUSE_ALLOCATED (1 << 4) // The helper function allocated the b= uffer =20 +extern void *bpf_fuse_get_writeable(struct fuse_buffer *arg, u64 size, boo= l copy); + /* * BPF Fuse Args * @@ -81,4 +83,191 @@ static inline unsigned bpf_fuse_arg_size(const struct b= pf_fuse_arg *arg) return arg->is_buffer ? arg->buffer->size : arg->size; } =20 +struct fuse_ops { + uint32_t (*open_prefilter)(const struct bpf_fuse_meta_info *meta, + struct fuse_open_in *in); + uint32_t (*open_postfilter)(const struct bpf_fuse_meta_info *meta, + const struct fuse_open_in *in, + struct fuse_open_out *out); + + uint32_t (*opendir_prefilter)(const struct bpf_fuse_meta_info *meta, + struct fuse_open_in *in); + uint32_t (*opendir_postfilter)(const struct bpf_fuse_meta_info *meta, + const struct fuse_open_in *in, + struct fuse_open_out *out); + + uint32_t (*create_open_prefilter)(const struct bpf_fuse_meta_info *meta, + struct fuse_create_in *in, struct fuse_buffer *name); + uint32_t (*create_open_postfilter)(const struct bpf_fuse_meta_info *meta, + const struct fuse_create_in *in, const struct fuse_buffer *name, + struct fuse_entry_out *entry_out, struct fuse_open_out *out); + + uint32_t (*release_prefilter)(const struct bpf_fuse_meta_info *meta, + struct fuse_release_in *in); + uint32_t (*release_postfilter)(const struct bpf_fuse_meta_info *meta, + const struct fuse_release_in *in); + + uint32_t (*releasedir_prefilter)(const struct bpf_fuse_meta_info *meta, + struct fuse_release_in *in); + uint32_t (*releasedir_postfilter)(const struct bpf_fuse_meta_info *meta, + const struct fuse_release_in *in); + + uint32_t (*flush_prefilter)(const struct bpf_fuse_meta_info *meta, + struct fuse_flush_in *in); + uint32_t (*flush_postfilter)(const struct bpf_fuse_meta_info *meta, + const struct fuse_flush_in *in); + + uint32_t (*lseek_prefilter)(const struct bpf_fuse_meta_info *meta, + struct fuse_lseek_in *in); + uint32_t (*lseek_postfilter)(const struct bpf_fuse_meta_info *meta, + const struct fuse_lseek_in *in, + struct fuse_lseek_out *out); + + uint32_t (*copy_file_range_prefilter)(const struct bpf_fuse_meta_info *me= ta, + struct fuse_copy_file_range_in *in); + uint32_t (*copy_file_range_postfilter)(const struct bpf_fuse_meta_info *m= eta, + const struct fuse_copy_file_range_in *in, + struct fuse_write_out *out); + + uint32_t (*fsync_prefilter)(const struct bpf_fuse_meta_info *meta, + struct fuse_fsync_in *in); + uint32_t (*fsync_postfilter)(const struct bpf_fuse_meta_info *meta, + const struct fuse_fsync_in *in); + + uint32_t (*dir_fsync_prefilter)(const struct bpf_fuse_meta_info *meta, + struct fuse_fsync_in *in); + uint32_t (*dir_fsync_postfilter)(const struct bpf_fuse_meta_info *meta, + const struct fuse_fsync_in *in); + + uint32_t (*getxattr_prefilter)(const struct bpf_fuse_meta_info *meta, + struct fuse_getxattr_in *in, struct fuse_buffer *name); + // if in->size > 0, use value. If in->size =3D=3D 0, use out. + uint32_t (*getxattr_postfilter)(const struct bpf_fuse_meta_info *meta, + const struct fuse_getxattr_in *in, const struct fuse_buffer *name, + struct fuse_buffer *value, struct fuse_getxattr_out *out); + + uint32_t (*listxattr_prefilter)(const struct bpf_fuse_meta_info *meta, + struct fuse_getxattr_in *in); + // if in->size > 0, use value. If in->size =3D=3D 0, use out. + uint32_t (*listxattr_postfilter)(const struct bpf_fuse_meta_info *meta, + const struct fuse_getxattr_in *in, + struct fuse_buffer *value, struct fuse_getxattr_out *out); + + uint32_t (*setxattr_prefilter)(const struct bpf_fuse_meta_info *meta, + struct fuse_setxattr_in *in, struct fuse_buffer *name, + struct fuse_buffer *value); + uint32_t (*setxattr_postfilter)(const struct bpf_fuse_meta_info *meta, + const struct fuse_setxattr_in *in, const struct fuse_buffer *name, + const struct fuse_buffer *value); + + uint32_t (*removexattr_prefilter)(const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name); + uint32_t (*removexattr_postfilter)(const struct bpf_fuse_meta_info *meta, + const struct fuse_buffer *name); + + /* Read and Write iter will likely undergo some sort of change/addition t= o handle changing + * the data buffer passed in/out. */ + uint32_t (*read_iter_prefilter)(const struct bpf_fuse_meta_info *meta, + struct fuse_read_in *in); + uint32_t (*read_iter_postfilter)(const struct bpf_fuse_meta_info *meta, + const struct fuse_read_in *in, + struct fuse_read_iter_out *out); + + uint32_t (*write_iter_prefilter)(const struct bpf_fuse_meta_info *meta, + struct fuse_write_in *in); + uint32_t (*write_iter_postfilter)(const struct bpf_fuse_meta_info *meta, + const struct fuse_write_in *in, + struct fuse_write_iter_out *out); + + uint32_t (*file_fallocate_prefilter)(const struct bpf_fuse_meta_info *met= a, + struct fuse_fallocate_in *in); + uint32_t (*file_fallocate_postfilter)(const struct bpf_fuse_meta_info *me= ta, + const struct fuse_fallocate_in *in); + + uint32_t (*lookup_prefilter)(const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name); + uint32_t (*lookup_postfilter)(const struct bpf_fuse_meta_info *meta, + const struct fuse_buffer *name, + struct fuse_entry_out *out, struct fuse_buffer *entries); + + uint32_t (*mknod_prefilter)(const struct bpf_fuse_meta_info *meta, + struct fuse_mknod_in *in, struct fuse_buffer *name); + uint32_t (*mknod_postfilter)(const struct bpf_fuse_meta_info *meta, + const struct fuse_mknod_in *in, const struct fuse_buffer *name); + + uint32_t (*mkdir_prefilter)(const struct bpf_fuse_meta_info *meta, + struct fuse_mkdir_in *in, struct fuse_buffer *name); + uint32_t (*mkdir_postfilter)(const struct bpf_fuse_meta_info *meta, + const struct fuse_mkdir_in *in, const struct fuse_buffer *name); + + uint32_t (*rmdir_prefilter)(const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name); + uint32_t (*rmdir_postfilter)(const struct bpf_fuse_meta_info *meta, + const struct fuse_buffer *name); + + uint32_t (*rename2_prefilter)(const struct bpf_fuse_meta_info *meta, + struct fuse_rename2_in *in, struct fuse_buffer *old_name, + struct fuse_buffer *new_name); + uint32_t (*rename2_postfilter)(const struct bpf_fuse_meta_info *meta, + const struct fuse_rename2_in *in, const struct fuse_buffer *old_name, + const struct fuse_buffer *new_name); + + uint32_t (*rename_prefilter)(const struct bpf_fuse_meta_info *meta, + struct fuse_rename_in *in, struct fuse_buffer *old_name, + struct fuse_buffer *new_name); + uint32_t (*rename_postfilter)(const struct bpf_fuse_meta_info *meta, + const struct fuse_rename_in *in, const struct fuse_buffer *old_name, + const struct fuse_buffer *new_name); + + uint32_t (*unlink_prefilter)(const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name); + uint32_t (*unlink_postfilter)(const struct bpf_fuse_meta_info *meta, + const struct fuse_buffer *name); + + uint32_t (*link_prefilter)(const struct bpf_fuse_meta_info *meta, + struct fuse_link_in *in, struct fuse_buffer *name); + uint32_t (*link_postfilter)(const struct bpf_fuse_meta_info *meta, + const struct fuse_link_in *in, const struct fuse_buffer *name); + + uint32_t (*getattr_prefilter)(const struct bpf_fuse_meta_info *meta, + struct fuse_getattr_in *in); + uint32_t (*getattr_postfilter)(const struct bpf_fuse_meta_info *meta, + const struct fuse_getattr_in *in, + struct fuse_attr_out *out); + + uint32_t (*setattr_prefilter)(const struct bpf_fuse_meta_info *meta, + struct fuse_setattr_in *in); + uint32_t (*setattr_postfilter)(const struct bpf_fuse_meta_info *meta, + const struct fuse_setattr_in *in, + struct fuse_attr_out *out); + + uint32_t (*statfs_prefilter)(const struct bpf_fuse_meta_info *meta); + uint32_t (*statfs_postfilter)(const struct bpf_fuse_meta_info *meta, + struct fuse_statfs_out *out); + + //TODO: This does not allow doing anything with path + uint32_t (*get_link_prefilter)(const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name); + uint32_t (*get_link_postfilter)(const struct bpf_fuse_meta_info *meta, + const struct fuse_buffer *name); + + uint32_t (*symlink_prefilter)(const struct bpf_fuse_meta_info *meta, + struct fuse_buffer *name, struct fuse_buffer *path); + uint32_t (*symlink_postfilter)(const struct bpf_fuse_meta_info *meta, + const struct fuse_buffer *name, const struct fuse_buffer *path); + + uint32_t (*readdir_prefilter)(const struct bpf_fuse_meta_info *meta, + struct fuse_read_in *in); + uint32_t (*readdir_postfilter)(const struct bpf_fuse_meta_info *meta, + const struct fuse_read_in *in, + struct fuse_read_out *out, struct fuse_buffer *buffer); + + uint32_t (*access_prefilter)(const struct bpf_fuse_meta_info *meta, + struct fuse_access_in *in); + uint32_t (*access_postfilter)(const struct bpf_fuse_meta_info *meta, + const struct fuse_access_in *in); + + char name[BPF_FUSE_NAME_MAX]; +}; + #endif /* _BPF_FUSE_H */ diff --git a/kernel/bpf/Makefile b/kernel/bpf/Makefile index 1d3892168d32..26a2e741ef61 100644 --- a/kernel/bpf/Makefile +++ b/kernel/bpf/Makefile @@ -45,3 +45,7 @@ obj-$(CONFIG_BPF_PRELOAD) +=3D preload/ obj-$(CONFIG_BPF_SYSCALL) +=3D relo_core.o $(obj)/relo_core.o: $(srctree)/tools/lib/bpf/relo_core.c FORCE $(call if_changed_rule,cc_o_c) + +ifeq ($(CONFIG_FUSE_BPF),y) +obj-$(CONFIG_BPF_SYSCALL) +=3D bpf_fuse.o +endif diff --git a/kernel/bpf/bpf_fuse.c b/kernel/bpf/bpf_fuse.c new file mode 100644 index 000000000000..35125c1f8eef --- /dev/null +++ b/kernel/bpf/bpf_fuse.c @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2021 Google LLC + +#include +#include +#include +#include +#include + +void *bpf_fuse_get_writeable(struct fuse_buffer *arg, u64 size, bool copy) +{ + void *writeable_val; + + if (arg->flags & BPF_FUSE_IMMUTABLE) + return 0; + + if (size <=3D arg->size && + (!(arg->flags & BPF_FUSE_MUST_ALLOCATE) || + (arg->flags & BPF_FUSE_ALLOCATED))) { + if (arg->flags & BPF_FUSE_VARIABLE_SIZE) + arg->size =3D size; + arg->flags |=3D BPF_FUSE_MODIFIED; + return arg->data; + } + /* Variable sized arrays must stay below max size. If the buffer must be = fixed size, + * don't change the allocated size. Verifier will enforce requested size = for accesses + */ + if (arg->flags & BPF_FUSE_VARIABLE_SIZE) { + if (size > arg->max_size) + return 0; + } else { + if (size > arg->size) + return 0; + size =3D arg->size; + } + + if (size !=3D arg->size && size > arg->max_size) + return 0; + + /* If our buffer is big enough, just adjust size */ + if (size <=3D arg->alloc_size) { + if (!copy) + arg->size =3D size; + arg->flags |=3D BPF_FUSE_MODIFIED; + return arg->data; + } + + writeable_val =3D kzalloc(size, GFP_KERNEL); + if (!writeable_val) + return 0; + + arg->alloc_size =3D size; + /* If we're copying the buffer, assume the same amount is used. If that i= sn't the case, + * caller must change size. Otherwise, assume entirety of new buffer is u= sed. + */ + if (copy) + memcpy(writeable_val, arg->data, (arg->size > size) ? size : arg->size); + else + arg->size =3D size; + + if (arg->flags & BPF_FUSE_ALLOCATED) + kfree(arg->data); + arg->data =3D writeable_val; + + arg->flags |=3D BPF_FUSE_ALLOCATED | BPF_FUSE_MODIFIED; + + return arg->data; +} +EXPORT_SYMBOL(bpf_fuse_get_writeable); + +__diag_push(); +__diag_ignore_all("-Wmissing-prototypes", + "Global kfuncs as their definitions will be in BTF"); +void bpf_fuse_get_rw_dynptr(struct fuse_buffer *buffer, struct bpf_dynptr_= kern *dynptr__uninit, u64 size, bool copy) +{ + buffer->data =3D bpf_fuse_get_writeable(buffer, size, copy); + bpf_dynptr_init(dynptr__uninit, buffer->data, BPF_DYNPTR_TYPE_LOCAL, 0, b= uffer->size); +} + +void bpf_fuse_get_ro_dynptr(const struct fuse_buffer *buffer, struct bpf_d= ynptr_kern *dynptr__uninit) +{ + bpf_dynptr_init(dynptr__uninit, buffer->data, BPF_DYNPTR_TYPE_LOCAL, 0, b= uffer->size); + bpf_dynptr_set_rdonly(dynptr__uninit); +} + +uint32_t bpf_fuse_return_len(struct fuse_buffer *buffer) +{ + return buffer->size; +} +__diag_pop(); +BTF_SET8_START(fuse_kfunc_set) +BTF_ID_FLAGS(func, bpf_fuse_get_rw_dynptr) +BTF_ID_FLAGS(func, bpf_fuse_get_ro_dynptr) +BTF_ID_FLAGS(func, bpf_fuse_return_len) +BTF_SET8_END(fuse_kfunc_set) + +static const struct btf_kfunc_id_set bpf_fuse_kfunc_set =3D { + .owner =3D THIS_MODULE, + .set =3D &fuse_kfunc_set, +}; + +static int __init bpf_fuse_kfuncs_init(void) +{ + return register_btf_kfunc_id_set(BPF_PROG_TYPE_STRUCT_OPS, + &bpf_fuse_kfunc_set); +} + +late_initcall(bpf_fuse_kfuncs_init); + +static const struct bpf_func_proto *bpf_fuse_get_func_proto(enum bpf_func_= id func_id, + const struct bpf_prog *prog) +{ + switch (func_id) { + default: + return bpf_base_func_proto(func_id); + } +} + +static bool bpf_fuse_is_valid_access(int off, int size, + enum bpf_access_type type, + const struct bpf_prog *prog, + struct bpf_insn_access_aux *info) +{ + return bpf_tracing_btf_ctx_access(off, size, type, prog, info); +} + +const struct btf_type *fuse_buffer_struct_type; + +static int bpf_fuse_btf_struct_access(struct bpf_verifier_log *log, + const struct bpf_reg_state *reg, + int off, int size) +{ + const struct btf_type *t; + + t =3D btf_type_by_id(reg->btf, reg->btf_id); + if (t =3D=3D fuse_buffer_struct_type) { + bpf_log(log, + "direct access to fuse_buffer is disallowed\n"); + return -EACCES; + } + + return 0; +} + +static const struct bpf_verifier_ops bpf_fuse_verifier_ops =3D { + .get_func_proto =3D bpf_fuse_get_func_proto, + .is_valid_access =3D bpf_fuse_is_valid_access, + .btf_struct_access =3D bpf_fuse_btf_struct_access, +}; + +static int bpf_fuse_check_member(const struct btf_type *t, + const struct btf_member *member, + const struct bpf_prog *prog) +{ + //if (is_unsupported(__btf_member_bit_offset(t, member) / 8)) + // return -ENOTSUPP; + return 0; +} + +static int bpf_fuse_init_member(const struct btf_type *t, + const struct btf_member *member, + void *kdata, const void *udata) +{ + const struct fuse_ops *uf_ops; + struct fuse_ops *f_ops; + u32 moff; + + uf_ops =3D (const struct fuse_ops *)udata; + f_ops =3D (struct fuse_ops *)kdata; + + moff =3D __btf_member_bit_offset(t, member) / 8; + switch (moff) { + case offsetof(struct fuse_ops, name): + if (bpf_obj_name_cpy(f_ops->name, uf_ops->name, + sizeof(f_ops->name)) <=3D 0) + return -EINVAL; + //if (tcp_ca_find(utcp_ca->name)) + // return -EEXIST; + return 1; + } + + return 0; +} + +static int bpf_fuse_init(struct btf *btf) +{ + s32 type_id; + + type_id =3D btf_find_by_name_kind(btf, "fuse_buffer", BTF_KIND_STRUCT); + if (type_id < 0) + return -EINVAL; + fuse_buffer_struct_type =3D btf_type_by_id(btf, type_id); + + return 0; +} + +static struct bpf_fuse_ops_attach *fuse_reg =3D NULL; + +static int bpf_fuse_reg(void *kdata) +{ + if (fuse_reg) + return fuse_reg->fuse_register_bpf(kdata); + pr_warn("Cannot register fuse_ops, FUSE not found"); + return -EOPNOTSUPP; +} + +static void bpf_fuse_unreg(void *kdata) +{ + if(fuse_reg) + return fuse_reg->fuse_unregister_bpf(kdata); +} + +int register_fuse_bpf(struct bpf_fuse_ops_attach *reg_ops) +{ + fuse_reg =3D reg_ops; + return 0; +} +EXPORT_SYMBOL_GPL(register_fuse_bpf); + +void unregister_fuse_bpf(struct bpf_fuse_ops_attach *reg_ops) +{ + if (reg_ops =3D=3D fuse_reg) + fuse_reg =3D NULL; + else + pr_warn("Refusing to unregister unregistered FUSE"); +} +EXPORT_SYMBOL_GPL(unregister_fuse_bpf); + +/* "extern" is to avoid sparse warning. It is only used in bpf_struct_ops= .c. */ +extern struct bpf_struct_ops bpf_fuse_ops; + +struct bpf_struct_ops bpf_fuse_ops =3D { + .verifier_ops =3D &bpf_fuse_verifier_ops, + .reg =3D bpf_fuse_reg, + .unreg =3D bpf_fuse_unreg, + .check_member =3D bpf_fuse_check_member, + .init_member =3D bpf_fuse_init_member, + .init =3D bpf_fuse_init, + .name =3D "fuse_ops", +}; + diff --git a/kernel/bpf/bpf_struct_ops_types.h b/kernel/bpf/bpf_struct_ops_= types.h index 5678a9ddf817..fabb2c1a9482 100644 --- a/kernel/bpf/bpf_struct_ops_types.h +++ b/kernel/bpf/bpf_struct_ops_types.h @@ -5,6 +5,10 @@ #ifdef CONFIG_NET BPF_STRUCT_OPS_TYPE(bpf_dummy_ops) #endif +#ifdef CONFIG_FUSE_BPF +#include +BPF_STRUCT_OPS_TYPE(fuse_ops) +#endif #ifdef CONFIG_INET #include BPF_STRUCT_OPS_TYPE(tcp_congestion_ops) diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c index 027f9f8a3551..c34fd9e70039 100644 --- a/kernel/bpf/btf.c +++ b/kernel/bpf/btf.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include "../tools/lib/bpf/relo_core.h" =20 diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index fd959824469d..b3bda15283c0 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -9597,6 +9597,8 @@ enum special_kfunc_type { KF_bpf_dynptr_from_xdp, KF_bpf_dynptr_slice, KF_bpf_dynptr_slice_rdwr, + KF_bpf_fuse_get_rw_dynptr, + KF_bpf_fuse_get_ro_dynptr, }; =20 BTF_SET_START(special_kfunc_set) @@ -9616,6 +9618,8 @@ BTF_ID(func, bpf_dynptr_from_skb) BTF_ID(func, bpf_dynptr_from_xdp) BTF_ID(func, bpf_dynptr_slice) BTF_ID(func, bpf_dynptr_slice_rdwr) +BTF_ID(func, bpf_fuse_get_rw_dynptr) +BTF_ID(func, bpf_fuse_get_ro_dynptr) BTF_SET_END(special_kfunc_set) =20 BTF_ID_LIST(special_kfunc_list) @@ -9637,6 +9641,8 @@ BTF_ID(func, bpf_dynptr_from_skb) BTF_ID(func, bpf_dynptr_from_xdp) BTF_ID(func, bpf_dynptr_slice) BTF_ID(func, bpf_dynptr_slice_rdwr) +BTF_ID(func, bpf_fuse_get_rw_dynptr) +BTF_ID(func, bpf_fuse_get_ro_dynptr) =20 static bool is_kfunc_bpf_rcu_read_lock(struct bpf_kfunc_call_arg_meta *met= a) { @@ -10349,6 +10355,9 @@ static int check_kfunc_args(struct bpf_verifier_env= *env, struct bpf_kfunc_call_ dynptr_arg_type |=3D DYNPTR_TYPE_SKB; else if (meta->func_id =3D=3D special_kfunc_list[KF_bpf_dynptr_from_xdp= ]) dynptr_arg_type |=3D DYNPTR_TYPE_XDP; + else if (meta->func_id =3D=3D special_kfunc_list[KF_bpf_fuse_get_rw_dyn= ptr] || + meta->func_id =3D=3D special_kfunc_list[KF_bpf_fuse_get_ro_dynptr]) + dynptr_arg_type |=3D DYNPTR_TYPE_LOCAL; =20 ret =3D process_dynptr_func(env, regno, insn_idx, dynptr_arg_type); if (ret < 0) --=20 2.40.0.634.g4ca3ef3211-goog From nobody Wed Dec 17 19:22:07 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 A2A78C77B72 for ; Tue, 18 Apr 2023 01:45:17 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230501AbjDRBpQ (ORCPT ); Mon, 17 Apr 2023 21:45:16 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53864 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231294AbjDRBoQ (ORCPT ); Mon, 17 Apr 2023 21:44:16 -0400 Received: from mail-yb1-xb49.google.com (mail-yb1-xb49.google.com [IPv6:2607:f8b0:4864:20::b49]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 64E909742 for ; Mon, 17 Apr 2023 18:42:25 -0700 (PDT) Received: by mail-yb1-xb49.google.com with SMTP id f66-20020a255145000000b00b7f75c3cafdso9881114ybb.16 for ; Mon, 17 Apr 2023 18:42:25 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20221208; t=1681782116; x=1684374116; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=DALAO2ExmfoURx8M3mnwBONzpAf0Sbtvw4FW/zSKTOw=; b=b1pQ3Wp8pZEzXCbTH1QL/Ey40sMcU1WsteMeb2jyEsZgnjoUBXs5syd1fmCtVJ54BM ucvMArZe1DkIAPgCv6jTYYdZaXqPr1r6ky4LmwZHPamxAuGCy1nVGcp7zftCUflOLmmi aJeYevODjYE4KqIk27YivRkHUUBNXZTg0n5uJXu+HFhCaXhwv304SKI0sISs3mHSDnGE 5o+CzIg5h/iHNjnXmuv+lRIg+H2gVBT1ZeY2rFDaAJ2oL3N4SRNTTIZZDhGXtWOFHsrW uSS3X2aDmNNPhbbfdU+hPMiEwfIzOyfTT3qhJgQj1/9EC9ZxyNIJ7+fpnVzr+Azzqjmw gbbw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681782116; x=1684374116; 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=DALAO2ExmfoURx8M3mnwBONzpAf0Sbtvw4FW/zSKTOw=; b=j6Mx8/dFbKSsYUz8c/8KOrRLuh4r4IQz/oBUZeiccaQVSB4Yz3SCvrLJQcUdcHSXoL /V4KOQIpKhXVnLCq/03t4YKRUnWa335769eEm2SJA84hqjrq7cBmKTEPFss97Nj7N6X5 tALnxmlxR+NX4levhiS/bgc0y9JHOGDZp+IOXMpThX52+ECYohO+9xnNmw3HuPs1CzBN 0AXDqWRBSyGP6AHB33p/axLMCBf45CvruSZ+mnti+ciYNrYwdpZ7BmfnYNY2ZG5KIOKH 0x54oWeR9ZXXeV4HYp6p1JkR4ad8LuDu/Lu6gT/Ev7RRzUfdbi1QFGGbglg37IwPiL7o yuuQ== X-Gm-Message-State: AAQBX9e+NXCJBFIIcTTb91+pIeye/mpKcloBB4IWu37n1iHPv06m3uJH SH8rjWk8s4q9wEHEYeZacAZKdPqMmTQ= X-Google-Smtp-Source: AKy350YnTlka0bcTU0RKtykV/F5v449o4LL77KqLH6Q3VuPHp90P6xAOoKpCMm9SycClz+UembUKg7Mmil0= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:e67a:98b0:942d:86aa]) (user=drosen job=sendgmr) by 2002:a25:8051:0:b0:b92:40f8:a46f with SMTP id a17-20020a258051000000b00b9240f8a46fmr4360158ybn.2.1681782116629; Mon, 17 Apr 2023 18:41:56 -0700 (PDT) Date: Mon, 17 Apr 2023 18:40:29 -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-30-drosen@google.com> Subject: [RFC PATCH v3 29/37] fuse-bpf: Export Functions 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 Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" These functions needed to be exported to build fuse as a module Signed-off-by: Daniel Rosenberg --- kernel/bpf/bpf_struct_ops.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/bpf/bpf_struct_ops.c b/kernel/bpf/bpf_struct_ops.c index deb9eecaf1e4..0bf727996a08 100644 --- a/kernel/bpf/bpf_struct_ops.c +++ b/kernel/bpf/bpf_struct_ops.c @@ -745,6 +745,7 @@ bool bpf_struct_ops_get(const void *kdata) map =3D __bpf_map_inc_not_zero(&st_map->map, false); return !IS_ERR(map); } +EXPORT_SYMBOL_GPL(bpf_struct_ops_get); =20 void bpf_struct_ops_put(const void *kdata) { @@ -756,6 +757,7 @@ void bpf_struct_ops_put(const void *kdata) =20 bpf_map_put(&st_map->map); } +EXPORT_SYMBOL_GPL(bpf_struct_ops_put); =20 static bool bpf_struct_ops_valid_to_reg(struct bpf_map *map) { --=20 2.40.0.634.g4ca3ef3211-goog From nobody Wed Dec 17 19:22:07 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 08043C77B7C for ; Tue, 18 Apr 2023 01:45:34 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231375AbjDRBpc (ORCPT ); Mon, 17 Apr 2023 21:45:32 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53752 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230026AbjDRBoa (ORCPT ); Mon, 17 Apr 2023 21:44: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 00E709037 for ; Mon, 17 Apr 2023 18:42:27 -0700 (PDT) Received: by mail-yw1-x1149.google.com with SMTP id 00721157ae682-552e3233b26so32078577b3.9 for ; Mon, 17 Apr 2023 18:42:27 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20221208; t=1681782118; x=1684374118; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=aAO1Q9dHIBUjzlD7EaIYD+isx8yohgW2kA3a//3wT4c=; b=Kll014u8X4/3frsvKv6leX7edC+R7R1Svq3qo68yXW+C9ILWLgAfAjeNGMvs85FLGh zRrNJAuImgvQ5aLqSDs6M3APLSG0xdaiP4iK1agE9vnbv/bb9c9BBSAvBPElsvcznwhS 18Td8YTl8Nbb5K44I/dYYJ0RKx30ve0GpDthDWt69IIdqj5AW7DXB5TyEF4h/R+gSB59 20TGhlcI9NUnrlAgE2aCP3qYcsMrg8xF371ZknTy0YTdphlpmwpe1R4AMCUMeHYSZNSm B6IbrnWBvKY3/GcoP2N2xJuw2VM6t3g9rGLiGtVmfAxmLksP2oeb1gliWysDzZiaXu/O zeeg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681782118; x=1684374118; 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=aAO1Q9dHIBUjzlD7EaIYD+isx8yohgW2kA3a//3wT4c=; b=ZWsFVtaXTR9b8sNhrTqDnBSl8UlasUrfuvz7eUWtxcIaoshdNS6nJdjbLSQhfa1Wwx bMs6pawYcc7G5Nl6BOwWQ6t8L00RPbemq1wYGIHZrPpteJFtfKVaw8/qLyHA9wq38KFT LKLb9aG3WVqj5KId7geJJIANjmBkPp/EkLGIpGcY3FicYoZOnlxV3yhVqVzaKbtSUjjm Q86kc8YCoFV3Wf3SQSV189yEbSoxKAJJVs4wDzNyu3Kswk+ne+XoVy9aLPbrMRCaBIHz 9KIBT5w3hoj1sKfGuMQjoXpNkyDDWUelBrwp6+ZLqp1hEa24eAm7+VpM5zLPLc5VO9ml 44zg== X-Gm-Message-State: AAQBX9dmDP7oj0B0JGLnG1SYtqr8WUBRToY0gbQQuClXNAKzfSIZ/1D7 nthaaTCyiJSiTZinqA0IJeKT1pLhgjE= X-Google-Smtp-Source: AKy350b2qWWm6u3O3O0nJnqw2v6qkgnCPTFrenW321HSICqNzQeyV4bIcR+CmpW2E20ABopGYna2UCgS8Vs= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:e67a:98b0:942d:86aa]) (user=drosen job=sendgmr) by 2002:a0d:ec02:0:b0:54c:2723:560d with SMTP id q2-20020a0dec02000000b0054c2723560dmr10855330ywn.3.1681782118566; Mon, 17 Apr 2023 18:41:58 -0700 (PDT) Date: Mon, 17 Apr 2023 18:40:30 -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-31-drosen@google.com> Subject: [RFC PATCH v3 30/37] fuse: Provide registration functions for fuse-bpf 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 Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Fuse may be built as a module, but verifier components are not. This provides a means for fuse-bpf to handle struct op programs once the module is loaded. Signed-off-by: Daniel Rosenberg --- fs/fuse/Makefile | 2 +- fs/fuse/backing.c | 2 + fs/fuse/bpf_register.c | 209 +++++++++++++++++++++++++++++++++++++++ fs/fuse/fuse_i.h | 26 +++++ include/linux/bpf_fuse.h | 8 ++ 5 files changed, 246 insertions(+), 1 deletion(-) create mode 100644 fs/fuse/bpf_register.c diff --git a/fs/fuse/Makefile b/fs/fuse/Makefile index a0853c439db2..903253db7285 100644 --- a/fs/fuse/Makefile +++ b/fs/fuse/Makefile @@ -9,6 +9,6 @@ obj-$(CONFIG_VIRTIO_FS) +=3D virtiofs.o =20 fuse-y :=3D dev.o dir.o file.o inode.o control.o xattr.o acl.o readdir.o i= octl.o fuse-$(CONFIG_FUSE_DAX) +=3D dax.o -fuse-$(CONFIG_FUSE_BPF) +=3D backing.o +fuse-$(CONFIG_FUSE_BPF) +=3D backing.o bpf_register.o =20 virtiofs-y :=3D virtio_fs.o diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index e807ae4f6f53..898ef9e05e9d 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -3360,6 +3360,7 @@ int fuse_bpf_access(int *out, struct inode *inode, in= t mask) =20 int __init fuse_bpf_init(void) { + init_fuse_bpf(); fuse_bpf_aio_request_cachep =3D kmem_cache_create("fuse_bpf_aio_req", sizeof(struct fuse_bpf_aio_req), 0, SLAB_HWCACHE_ALIGN, NULL); @@ -3371,5 +3372,6 @@ int __init fuse_bpf_init(void) =20 void __exit fuse_bpf_cleanup(void) { + uninit_fuse_bpf(); kmem_cache_destroy(fuse_bpf_aio_request_cachep); } diff --git a/fs/fuse/bpf_register.c b/fs/fuse/bpf_register.c new file mode 100644 index 000000000000..dfe15dcf3477 --- /dev/null +++ b/fs/fuse/bpf_register.c @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * FUSE-BPF: Filesystem in Userspace with BPF + * Copyright (c) 2021 Google LLC + */ + +#include +#include +#include +#include +#include + +#include "fuse_i.h" + +struct fuse_ops tmp_f_op_empty =3D { 0 }; +struct fuse_ops *tmp_f_op =3D &tmp_f_op_empty; + +struct hashtable_entry { + struct hlist_node hlist; + struct hlist_node dlist; /* for deletion cleanup */ + struct qstr key; + struct fuse_ops *ops; +}; + +static DEFINE_HASHTABLE(name_to_ops, 8); + +static unsigned int full_name_case_hash(const void *salt, const unsigned c= har *name, unsigned int len) +{ + unsigned long hash =3D init_name_hash(salt); + + while (len--) + hash =3D partial_name_hash(tolower(*name++), hash); + return end_name_hash(hash); +} + +static inline void qstr_init(struct qstr *q, const char *name) +{ + q->name =3D name; + q->len =3D strlen(q->name); + q->hash =3D full_name_case_hash(0, q->name, q->len); +} + +static inline int qstr_copy(const struct qstr *src, struct qstr *dest) +{ + dest->name =3D kstrdup(src->name, GFP_KERNEL); + dest->hash_len =3D src->hash_len; + return !!dest->name; +} + +static inline int qstr_eq(const struct qstr *s1, const struct qstr *s2) +{ + int res, r1, r2, r3; + + r1 =3D s1->len =3D=3D s2->len; + r2 =3D s1->hash =3D=3D s2->hash; + r3 =3D memcmp(s1->name, s2->name, s1->len); + res =3D (s1->len =3D=3D s2->len && s1->hash =3D=3D s2->hash && !memcmp(s1= ->name, s2->name, s1->len)); + return res; +} + +static struct fuse_ops *__find_fuse_ops(const struct qstr *key) +{ + struct hashtable_entry *hash_cur; + unsigned int hash =3D key->hash; + struct fuse_ops *ret_ops; + + rcu_read_lock(); + hash_for_each_possible_rcu(name_to_ops, hash_cur, hlist, hash) { + if (qstr_eq(key, &hash_cur->key)) { + ret_ops =3D hash_cur->ops; + ret_ops =3D get_fuse_ops(ret_ops); + rcu_read_unlock(); + return ret_ops; + } + } + rcu_read_unlock(); + return NULL; +} + +struct fuse_ops *get_fuse_ops(struct fuse_ops *ops) +{ + if (bpf_try_module_get(ops, BPF_MODULE_OWNER)) + return ops; + else + return NULL; +} + +void put_fuse_ops(struct fuse_ops *ops) +{ + if (ops) + bpf_module_put(ops, BPF_MODULE_OWNER); +} + +struct fuse_ops *find_fuse_ops(const char *key) +{ + struct qstr q; + + qstr_init(&q, key); + return __find_fuse_ops(&q); +} + +static struct hashtable_entry *alloc_hashtable_entry(const struct qstr *ke= y, + struct fuse_ops *value) +{ + struct hashtable_entry *ret =3D kzalloc(sizeof(*ret), GFP_KERNEL); + if (!ret) + return NULL; + INIT_HLIST_NODE(&ret->dlist); + INIT_HLIST_NODE(&ret->hlist); + + if (!qstr_copy(key, &ret->key)) { + kfree(ret); + return NULL; + } + + ret->ops =3D value; + return ret; +} + +static int __register_fuse_op(struct fuse_ops *value) +{ + struct hashtable_entry *hash_cur; + struct hashtable_entry *new_entry; + struct qstr key; + unsigned int hash; + + qstr_init(&key, value->name); + hash =3D key.hash; + hash_for_each_possible_rcu(name_to_ops, hash_cur, hlist, hash) { + if (qstr_eq(&key, &hash_cur->key)) { + return -EEXIST; + } + } + new_entry =3D alloc_hashtable_entry(&key, value); + if (!new_entry) + return -ENOMEM; + hash_add_rcu(name_to_ops, &new_entry->hlist, hash); + return 0; +} + +static int register_fuse_op(struct fuse_ops *value) +{ + int err; + + if (bpf_try_module_get(value, BPF_MODULE_OWNER)) + err =3D __register_fuse_op(value); + else + return -EBUSY; + + return err; +} + +static void unregister_fuse_op(struct fuse_ops *value) +{ + struct hashtable_entry *hash_cur; + struct qstr key; + unsigned int hash; + struct hlist_node *h_t; + HLIST_HEAD(free_list); + + qstr_init(&key, value->name); + hash =3D key.hash; + + hash_for_each_possible_rcu(name_to_ops, hash_cur, hlist, hash) { + if (qstr_eq(&key, &hash_cur->key)) { + hash_del_rcu(&hash_cur->hlist); + hlist_add_head(&hash_cur->dlist, &free_list); + } + } + synchronize_rcu(); + bpf_module_put(value, BPF_MODULE_OWNER); + hlist_for_each_entry_safe(hash_cur, h_t, &free_list, dlist) + kfree(hash_cur); +} + +static void fuse_op_list_destroy(void) +{ + struct hashtable_entry *hash_cur; + struct hlist_node *h_t; + HLIST_HEAD(free_list); + int i; + + //mutex_lock(&sdcardfs_super_list_lock); + hash_for_each_rcu(name_to_ops, i, hash_cur, hlist) { + hash_del_rcu(&hash_cur->hlist); + hlist_add_head(&hash_cur->dlist, &free_list); + } + synchronize_rcu(); + hlist_for_each_entry_safe(hash_cur, h_t, &free_list, dlist) + kfree(hash_cur); + //mutex_unlock(&sdcardfs_super_list_lock); + pr_info("fuse: destroyed fuse_op list\n"); +} + +static struct bpf_fuse_ops_attach bpf_fuse_ops_connect =3D { + .fuse_register_bpf =3D ®ister_fuse_op, + .fuse_unregister_bpf =3D &unregister_fuse_op, +}; + +int init_fuse_bpf(void) +{ + return register_fuse_bpf(&bpf_fuse_ops_connect); +} + +void uninit_fuse_bpf(void) +{ + unregister_fuse_bpf(&bpf_fuse_ops_connect); + fuse_op_list_destroy(); +} diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 2bd45c8658e8..84c591d02e43 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -1390,6 +1390,32 @@ void fuse_file_release(struct inode *inode, struct f= use_file *ff, unsigned int open_flags, fl_owner_t id, bool isdir); =20 /* backing.c */ +#ifdef CONFIG_FUSE_BPF +struct fuse_ops *find_fuse_ops(const char *key); +struct fuse_ops *get_fuse_ops(struct fuse_ops *ops); +void put_fuse_ops(struct fuse_ops *ops); +int init_fuse_bpf(void); +void uninit_fuse_bpf(void); +#else +int init_fuse_bpf(void) +{ + return -EOPNOTSUPP; +} +void uninit_fuse_bpf(void) +{ +} +struct fuse_ops *find_fuse_ops(const char *key) +{ + return NULL; +} +struct fuse_ops *get_fuse_ops(struct fuse_ops *ops) +{ + return NULL; +} +void put_fuse_ops(struct fuse_ops *ops) +{ +} +#endif =20 enum fuse_bpf_set { FUSE_BPF_UNCHANGED =3D 0, diff --git a/include/linux/bpf_fuse.h b/include/linux/bpf_fuse.h index 780a7889aea2..2183a7a45c92 100644 --- a/include/linux/bpf_fuse.h +++ b/include/linux/bpf_fuse.h @@ -270,4 +270,12 @@ struct fuse_ops { char name[BPF_FUSE_NAME_MAX]; }; =20 +struct bpf_fuse_ops_attach { + int (*fuse_register_bpf)(struct fuse_ops *f_ops); + void (*fuse_unregister_bpf)(struct fuse_ops *f_ops); +}; + +int register_fuse_bpf(struct bpf_fuse_ops_attach *reg_ops); +void unregister_fuse_bpf(struct bpf_fuse_ops_attach *reg_ops); + #endif /* _BPF_FUSE_H */ --=20 2.40.0.634.g4ca3ef3211-goog From nobody Wed Dec 17 19:22:07 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 13573C77B72 for ; Tue, 18 Apr 2023 01:45:46 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231186AbjDRBpo (ORCPT ); Mon, 17 Apr 2023 21:45:44 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53842 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230502AbjDRBpI (ORCPT ); Mon, 17 Apr 2023 21:45:08 -0400 Received: from mail-yb1-xb4a.google.com (mail-yb1-xb4a.google.com [IPv6:2607:f8b0:4864:20::b4a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 107CF61A3 for ; Mon, 17 Apr 2023 18:42:32 -0700 (PDT) Received: by mail-yb1-xb4a.google.com with SMTP id d10-20020a25cd0a000000b00b956d4a5a04so134197ybf.22 for ; Mon, 17 Apr 2023 18:42:32 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20221208; t=1681782120; x=1684374120; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=YCFXbIE/7zM5T1Zxz4s1sjI+Fr3ZActGUdsgPEik6eM=; b=FB1wts4EjjmXAYzwfnd2fXz7dClLikX5H2f+j5h+pJVNsx6Q6NlCRmHSItjyPUrnLv YKyra7iCWVEBF0LSP7pSpNJnt31Essq6CEOYUvx3a7DeB1H1zp6/AzOIMV4nIMmPQ1qh qMCnxSgQQEmKpoo+wUGmGbWJWaPB9mdD+g4OxND3tsdF6PhQCvPKTZ1L9qfeLvvDeRwC diebbFiKsuAfFKHUnGox1H1Vm8nPX77oEHMs7kh9H6yCekzNrbtTKcZMsq9SW5ZfMvTo tES6furCQMp++TtGvH27jawxyKvTX19Ki63HlB/d0cozAwalDTSnp6pYD+fi6iqjgBt8 e32w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681782120; x=1684374120; 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=YCFXbIE/7zM5T1Zxz4s1sjI+Fr3ZActGUdsgPEik6eM=; b=eEAs0MlFzrJaPfavObY5Pu1M5MnzFWkRaOI7/ttd0rhM8gUYoZAXoIIcwHLbr3wD2i KqUm8L+Z6XhC1eLMzMScaIrx4Dt3NsGinBWW4qVEXV6WIEGJD8HZ6PLG9gyO8WN9qKII I/chx/2Qv/rzW+aOGXYaJ0JYoi+Z9pr2tUx+0F/FF66UmlUvx220Umgk6Ld1/qTvie4W f2TP1g9NAWL6E/kyz8rmsRbQtoi6cJnEd5S0A9HHXnDAYlmIRWlCprKaT4T0ijG++JSl 19R4qtTW3jPzrBKjCeTUXeR72jiOBXMDWszKq0eAzHJiTvr4XldqVK5mq0RsMGFxkmuM GmaQ== X-Gm-Message-State: AAQBX9epe0QkbDyucAKj0IBW8i205zeATNw5AGwwSIAvv0olg1yhxG3X Xn0JpyOXe1FVHkTIvyseYJ6mwak2lec= X-Google-Smtp-Source: AKy350bP8nkD4q8ZefsJwT0Q2ZhJNsKNBRwFjIJbXhB6OG/HhtE2gYEray13+6teD80zVxL1NhMk7TiPsY0= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:e67a:98b0:942d:86aa]) (user=drosen job=sendgmr) by 2002:a81:af0c:0:b0:54c:2889:7105 with SMTP id n12-20020a81af0c000000b0054c28897105mr10920487ywh.0.1681782120540; Mon, 17 Apr 2023 18:42:00 -0700 (PDT) Date: Mon, 17 Apr 2023 18:40:31 -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-32-drosen@google.com> Subject: [RFC PATCH v3 31/37] fuse-bpf: Set fuse_ops at mount or lookup time 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 Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This adds the ability to associate a fuse_op struct_op program with inodes in fuse. This can be done at mount time at the root level, or by inode at lookup time. Signed-off-by: Daniel Rosenberg --- fs/fuse/backing.c | 91 +++++++++++++++++++++++++++++++++++++++++++---- fs/fuse/dir.c | 16 +++++++-- fs/fuse/fuse_i.h | 12 +++++++ fs/fuse/inode.c | 28 ++++++++++++++- 4 files changed, 138 insertions(+), 9 deletions(-) diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index 898ef9e05e9d..d5ba1e334e69 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -6,6 +6,7 @@ =20 #include "fuse_i.h" =20 +#include #include #include #include @@ -168,12 +169,13 @@ static void fuse_get_backing_path(struct file *file, = struct path *path) =20 static bool has_file(int type) { - return type =3D=3D FUSE_ENTRY_BACKING; + return (type =3D=3D FUSE_ENTRY_BACKING); } =20 /* - * The optional fuse bpf entry lists the backing file for a particular - * lookup. These are inherited by default. + * The optional fuse bpf entry lists the bpf and backing files for a parti= cular + * lookup. These are inherited by default. A Bpf requires a backing file t= o be + * meaningful. * * In the future, we may support multiple bpfs, and multiple backing files= for * the bpf to choose between. @@ -182,14 +184,14 @@ static bool has_file(int type) * file. Changing only the bpf is valid, though meaningless if there isn't= an * inherited backing file. * - * Support for the bpf program will be added in a later patch - * */ int parse_fuse_bpf_entry(struct fuse_bpf_entry *fbe, int num) { struct fuse_bpf_entry_out *fbeo; + struct fuse_ops *ops; struct file *file; bool has_backing =3D false; + bool has_bpf_ops =3D false; int num_entries; int err =3D -EINVAL; int i; @@ -227,6 +229,11 @@ int parse_fuse_bpf_entry(struct fuse_bpf_entry *fbe, i= nt num) goto out_err; fbe->backing_action =3D FUSE_BPF_REMOVE; break; + case FUSE_ENTRY_REMOVE_BPF: + if (fbe->bpf_action || i =3D=3D 2) + goto out_err; + fbe->bpf_action =3D FUSE_BPF_REMOVE; + break; case FUSE_ENTRY_BACKING: if (fbe->backing_action) goto out_err; @@ -234,8 +241,17 @@ int parse_fuse_bpf_entry(struct fuse_bpf_entry *fbe, i= nt num) fbe->backing_action =3D FUSE_BPF_SET; has_backing =3D true; break; + case FUSE_ENTRY_BPF: + if (fbe->bpf_action || i =3D=3D 2) + goto out_err; + ops =3D find_fuse_ops(fbeo->name); + if (!ops) + goto out_err; + has_bpf_ops =3D true; + fbe->bpf_action =3D FUSE_BPF_SET; + fbe->ops =3D ops; + break; default: - err =3D -EINVAL; goto out_err; } if (has_file(fbeo->entry_type)) { @@ -252,6 +268,10 @@ int parse_fuse_bpf_entry(struct fuse_bpf_entry *fbe, i= nt num) fput(file); if (has_backing) path_put_init(&fbe->backing_path); + if (has_bpf_ops) { + put_fuse_ops(fbe->ops); + fbe->ops =3D NULL; + } return err; } =20 @@ -527,6 +547,15 @@ static int fuse_create_open_backing(struct bpf_fuse_ar= gs *fa, int *out, goto out; } =20 + if (get_fuse_inode(inode)->bpf_ops) + put_fuse_ops(get_fuse_inode(inode)->bpf_ops); + get_fuse_inode(inode)->bpf_ops =3D dir_fuse_inode->bpf_ops; + if (get_fuse_inode(inode)->bpf_ops) + if (!get_fuse_ops(get_fuse_inode(inode)->bpf_ops)) { + *out =3D -EINVAL; + goto out; + } + newent =3D d_splice_alias(inode, entry); if (IS_ERR(newent)) { *out =3D PTR_ERR(newent); @@ -1842,6 +1871,52 @@ int fuse_handle_backing(struct fuse_bpf_entry *fbe, = struct path *backing_path) return 0; } =20 +int fuse_handle_bpf_ops(struct fuse_bpf_entry *fbe, struct inode *parent, + struct fuse_ops **ops) +{ + struct fuse_ops *new_ops; + + /* Parent isn't presented, but we want to keep + * Don't touch bpf program at all in this case + */ + if (fbe->bpf_action =3D=3D FUSE_BPF_UNCHANGED && !parent) + return 0; + + switch (fbe->bpf_action) { + case FUSE_BPF_UNCHANGED: { + struct fuse_inode *pi =3D get_fuse_inode(parent); + + new_ops =3D pi->bpf_ops; + if (new_ops && !get_fuse_ops(new_ops)) + return -EINVAL; + break; + } + + case FUSE_BPF_REMOVE: + new_ops =3D NULL; + break; + + case FUSE_BPF_SET: + new_ops =3D fbe->ops; + + if (!new_ops) + return -EINVAL; + break; + + default: + return -EINVAL; + } + + /* Cannot change existing program */ + if (*ops) { + put_fuse_ops(new_ops); + return new_ops =3D=3D *ops ? 0 : -EINVAL; + } + + *ops =3D new_ops; + return 0; +} + static int fuse_lookup_finalize(struct bpf_fuse_args *fa, struct dentry **= out, struct inode *dir, struct dentry *entry, unsigned int flags) { @@ -1879,6 +1954,10 @@ static int fuse_lookup_finalize(struct bpf_fuse_args= *fa, struct dentry **out, if (IS_ERR(inode)) return PTR_ERR(inode); =20 + error =3D fuse_handle_bpf_ops(fbe, dir, &get_fuse_inode(inode)->bpf_ops); + if (error) + return error; + get_fuse_inode(inode)->nodeid =3D feo->nodeid; =20 *out =3D d_splice_alias(inode, entry); diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index d1c3b2bfb0b1..b7bc8260a537 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -10,6 +10,7 @@ =20 #include #include +#include #include #include #include @@ -185,6 +186,7 @@ static bool backing_data_changed(struct fuse_inode *fi,= struct dentry *entry, { struct path new_backing_path; struct inode *new_backing_inode; + struct fuse_ops *ops =3D NULL; int err; bool ret =3D true; =20 @@ -199,9 +201,15 @@ static bool backing_data_changed(struct fuse_inode *fi= , struct dentry *entry, if (err) goto put_inode; =20 - ret =3D (fi->backing_inode !=3D new_backing_inode || - !path_equal(&get_fuse_dentry(entry)->backing_path, &new_backing_path)); + err =3D fuse_handle_bpf_ops(bpf_arg, entry->d_parent->d_inode, &ops); + if (err) + goto put_bpf; =20 + ret =3D (ops !=3D fi->bpf_ops || fi->backing_inode !=3D new_backing_inode= || + !path_equal(&get_fuse_dentry(entry)->backing_path, &new_backing_path)); +put_bpf: + if (ops) + put_fuse_ops(ops); put_inode: path_put(&new_backing_path); return ret; @@ -466,6 +474,10 @@ int fuse_lookup_name(struct super_block *sb, u64 nodei= d, *inode =3D fuse_iget_backing(sb, outarg->nodeid, backing_inode); if (!*inode) goto out_queue_forget; + + err =3D fuse_handle_bpf_ops(&bpf_arg, NULL, &get_fuse_inode(*inode)->bpf= _ops); + if (err) + goto out; } else #endif { diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 84c591d02e43..15962ab3b381 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -33,6 +33,7 @@ #include #include #include +#include #include =20 /** Default max number of pages that can be used in a single read request = */ @@ -109,6 +110,12 @@ struct fuse_inode { * If this is set, nodeid is 0. */ struct inode *backing_inode; + + /** + * fuse_ops, provides handlers to run on all operations to determine + * whether to pass through or handle in place + */ + struct fuse_ops *bpf_ops; #endif =20 /** Unique ID, which identifies the inode between userspace @@ -571,6 +578,7 @@ struct fuse_fs_context { unsigned int max_read; unsigned int blksize; const char *subtype; + struct fuse_ops *root_ops; struct file *root_dir; =20 /* DAX device, may be NULL */ @@ -1428,6 +1436,8 @@ struct fuse_bpf_entry { =20 enum fuse_bpf_set backing_action; struct path backing_path; + enum fuse_bpf_set bpf_action; + struct fuse_ops *ops; bool is_used; }; =20 @@ -1651,6 +1661,8 @@ static inline int fuse_bpf_access(int *out, struct in= ode *inode, int mask) ssize_t fuse_backing_mmap(struct file *file, struct vm_area_struct *vma); =20 int fuse_handle_backing(struct fuse_bpf_entry *fbe, struct path *backing_p= ath); +int fuse_handle_bpf_ops(struct fuse_bpf_entry *fbe, struct inode *parent, + struct fuse_ops **ops); =20 int fuse_revalidate_backing(struct dentry *entry, unsigned int flags); =20 diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 31f34962bc9b..7fd79efbdac1 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -80,6 +80,7 @@ static struct inode *fuse_alloc_inode(struct super_block = *sb) fi->inval_mask =3D 0; #ifdef CONFIG_FUSE_BPF fi->backing_inode =3D NULL; + fi->bpf_ops =3D NULL; #endif fi->nodeid =3D 0; fi->nlookup =3D 0; @@ -125,6 +126,9 @@ static void fuse_evict_inode(struct inode *inode) =20 #ifdef CONFIG_FUSE_BPF iput(fi->backing_inode); + if (fi->bpf_ops) + put_fuse_ops(fi->bpf_ops); + fi->bpf_ops =3D NULL; #endif =20 truncate_inode_pages_final(&inode->i_data); @@ -755,6 +759,7 @@ enum { OPT_ALLOW_OTHER, OPT_MAX_READ, OPT_BLKSIZE, + OPT_ROOT_BPF, OPT_ROOT_DIR, OPT_NO_DAEMON, OPT_ERR @@ -771,6 +776,7 @@ static const struct fs_parameter_spec fuse_fs_parameter= s[] =3D { fsparam_u32 ("max_read", OPT_MAX_READ), fsparam_u32 ("blksize", OPT_BLKSIZE), fsparam_string ("subtype", OPT_SUBTYPE), + fsparam_string ("root_bpf", OPT_ROOT_BPF), fsparam_u32 ("root_dir", OPT_ROOT_DIR), fsparam_flag ("no_daemon", OPT_NO_DAEMON), {} @@ -856,6 +862,18 @@ static int fuse_parse_param(struct fs_context *fsc, st= ruct fs_parameter *param) ctx->blksize =3D result.uint_32; break; =20 + case OPT_ROOT_BPF: + if (strnlen(param->string, BPF_FUSE_NAME_MAX + 1) > BPF_FUSE_NAME_MAX) { + return invalfc(fsc, "root_bpf name too long. Max length is %d", BPF_FUS= E_NAME_MAX); + } + + ctx->root_ops =3D find_fuse_ops(param->string); + if (IS_ERR_OR_NULL(ctx->root_ops)) { + ctx->root_ops =3D NULL; + return invalfc(fsc, "Unable to find bpf program"); + } + break; + case OPT_ROOT_DIR: ctx->root_dir =3D fget(result.uint_32); if (!ctx->root_dir) @@ -881,6 +899,8 @@ static void fuse_free_fsc(struct fs_context *fsc) if (ctx) { if (ctx->root_dir) fput(ctx->root_dir); + if (ctx->root_ops) + put_fuse_ops(ctx->root_ops); kfree(ctx->subtype); kfree(ctx); } @@ -1010,6 +1030,7 @@ EXPORT_SYMBOL_GPL(fuse_conn_get); =20 static struct inode *fuse_get_root_inode(struct super_block *sb, unsigned int mode, + struct fuse_ops *root_bpf_ops, struct file *backing_fd) { struct fuse_attr attr; @@ -1024,6 +1045,10 @@ static struct inode *fuse_get_root_inode(struct supe= r_block *sb, return NULL; =20 #ifdef CONFIG_FUSE_BPF + get_fuse_inode(inode)->bpf_ops =3D root_bpf_ops; + if (root_bpf_ops) + get_fuse_ops(root_bpf_ops); + if (backing_fd) { get_fuse_inode(inode)->backing_inode =3D backing_fd->f_inode; ihold(backing_fd->f_inode); @@ -1704,7 +1729,8 @@ int fuse_fill_super_common(struct super_block *sb, st= ruct fuse_fs_context *ctx) fc->no_daemon =3D ctx->no_daemon; =20 err =3D -ENOMEM; - root =3D fuse_get_root_inode(sb, ctx->rootmode, ctx->root_dir); + root =3D fuse_get_root_inode(sb, ctx->rootmode, ctx->root_ops, + ctx->root_dir); sb->s_d_op =3D &fuse_root_dentry_operations; root_dentry =3D d_make_root(root); if (!root_dentry) --=20 2.40.0.634.g4ca3ef3211-goog From nobody Wed Dec 17 19:22:07 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 6D884C77B7C for ; Tue, 18 Apr 2023 01:45:49 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231288AbjDRBpr (ORCPT ); Mon, 17 Apr 2023 21:45:47 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:54320 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231261AbjDRBpK (ORCPT ); Mon, 17 Apr 2023 21:45:10 -0400 Received: from mail-yw1-x114a.google.com (mail-yw1-x114a.google.com [IPv6:2607:f8b0:4864:20::114a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 6788F9EC7 for ; Mon, 17 Apr 2023 18:42:37 -0700 (PDT) Received: by mail-yw1-x114a.google.com with SMTP id 00721157ae682-54f97aac3d0so147137967b3.15 for ; Mon, 17 Apr 2023 18:42:37 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20221208; t=1681782123; x=1684374123; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=rV9cf7X8542M+vZlF6IZmm0T2c7Hcn7L+aL9IBbyW5E=; b=HzUxzGdXoVkcjZV6vHWK2fyeDSJjSY1BBS0UdnnsTptpTqqiHorVFtUY2wrJbm+wO6 c6YZbS6lKh3efNHCQ50jJX0wC8VyZjZRkK23zqrpmzD4ei1yb0QYNd5rcDWau5SzeBJQ rDlcg6fdSokuniQsaZDb6N8xlmOwZBb8POwJL9DzFUs4EG1RzaiKuvjbe3Lo7t9bQE9x 7KtfUpluZQaqe3VCF32gmQurOzqALEDbob0EvuC9YrxPDt/0W9mtlVP65GhxaB+gMeEQ r1ro2/gZJ93/hQh+dmunABaKF8jUu1yGu0CShwO4GzI+5HpmwSJfNjIOArphd+PrRqgI uiuA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681782123; x=1684374123; 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=rV9cf7X8542M+vZlF6IZmm0T2c7Hcn7L+aL9IBbyW5E=; b=FrSd6M9tLi3DMzT12FxQhrJmuY0SSwW1aflIeIIbMtyRxYMBqFuavkpELVQyuQkXqo 27v/dtSGc+iGYIvBWGnmCYauBSe4z4XKGJzVzdDclFvk+1kC7AuTH4/beNrSGLPz5SR6 m3wfrAjIU/Q8UKNxfqpABTSZTjXwpRyncTYY828H6TocFehQE+ItBaRKmvkHJkkemLac doXifwGa6WIb/h5KuV0tZ0+DbryWw6VaXu2sxYwhy/T++QsAYnLTke7rjICraNcN1Nre bX1kzI7fY4OE97uSFTE1h0eklma5+vRDATs+ksMhaD2lkvdyo0LV00O9E9AuRGGkzAZG 95Nw== X-Gm-Message-State: AAQBX9cpb1dfergMmpyENviVj7Vopbgwj6x3S6V6nfO5rqAHsAFdkoRD 8Zdfr+lruFk5v+mtSWjz7902H0jYqEQ= X-Google-Smtp-Source: AKy350brwRJR3viQIbnQAfQLMqCkkFC/uJkI1XPJ61jychKX9KIwFh23vN/gKLI87pp9DsQRargy36UV8QQ= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:e67a:98b0:942d:86aa]) (user=drosen job=sendgmr) by 2002:a25:da89:0:b0:b8f:4f1d:be06 with SMTP id n131-20020a25da89000000b00b8f4f1dbe06mr10985720ybf.11.1681782122938; Mon, 17 Apr 2023 18:42:02 -0700 (PDT) Date: Mon, 17 Apr 2023 18:40:32 -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-33-drosen@google.com> Subject: [RFC PATCH v3 32/37] fuse-bpf: Call bpf for pre/post filters 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 Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This allows altering input or output parameters to fuse calls that will be handled directly by the backing filesystems. BPF programs can signal whether the entire operation should instead go through regular fuse, or if a postfilter call is needed. Signed-off-by: Daniel Rosenberg --- fs/fuse/backing.c | 606 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 606 insertions(+) diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index d5ba1e334e69..9217e9f83d98 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -14,6 +14,27 @@ #include #include =20 +static inline void bpf_fuse_set_in_immutable(struct bpf_fuse_args *fa) +{ + int i; + + for (i =3D 0; i < FUSE_MAX_ARGS_IN; i++) + if (fa->in_args[i].is_buffer) + fa->in_args[i].buffer->flags |=3D BPF_FUSE_IMMUTABLE; +} + +static inline void bpf_fuse_free_alloced(struct bpf_fuse_args *fa) +{ + int i; + + for (i =3D 0; i < FUSE_MAX_ARGS_IN; i++) + if (fa->in_args[i].is_buffer && (fa->in_args[i].buffer->flags & BPF_FUSE= _ALLOCATED)) + kfree(fa->in_args[i].buffer->data); + for (i =3D 0; i < FUSE_MAX_ARGS_OUT; i++) + if (fa->out_args[i].is_buffer && (fa->out_args[i].buffer->flags & BPF_FU= SE_ALLOCATED)) + kfree(fa->out_args[i].buffer->data); +} + /* * expression statement to wrap the backing filter logic * struct inode *inode: inode with bpf and backing inode @@ -23,6 +44,10 @@ * up fa and io based on args * void initialize_out(struct bpf_fuse_args *fa, io *in_out, args...): fun= ction that sets * up fa and io based on args + * int call_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_info *met= a, io *in_out): Calls + * the struct_op prefilter function for the given fuse op + * int call_prostfilter(struct fuse_ops *ops, struct bpf_fuse_meta_info *m= eta, io *in_out): Calls + * the struct_op postfilter function for the given fuse op * int backing(struct fuse_bpf_args_internal *fa, args...): function that = actually performs * the backing io operation * void *finalize(struct fuse_bpf_args *, args...): function that performs= any final @@ -30,13 +55,16 @@ */ #define bpf_fuse_backing(inode, io, out, \ initialize_in, initialize_out, \ + call_prefilter, call_postfilter, \ backing, finalize, args...) \ ({ \ struct fuse_inode *fuse_inode =3D get_fuse_inode(inode); \ + struct fuse_ops *fuse_ops =3D fuse_inode->bpf_ops; \ struct bpf_fuse_args fa =3D { 0 }; \ bool initialized =3D false; \ bool handled =3D false; \ ssize_t res; \ + int bpf_next; \ io feo =3D { 0 }; \ int error =3D 0; \ \ @@ -49,16 +77,46 @@ if (error) \ break; \ \ + fa.info.opcode |=3D FUSE_PREFILTER; \ + if (fuse_ops) \ + bpf_next =3D call_prefilter(fuse_ops, \ + &fa.info, &feo); \ + else \ + bpf_next =3D BPF_FUSE_CONTINUE; \ + if (bpf_next < 0) { \ + error =3D bpf_next; \ + break; \ + } \ + \ + bpf_fuse_set_in_immutable(&fa); \ + \ error =3D initialize_out(&fa, &feo, args); \ if (error) \ break; \ \ initialized =3D true; \ + if (bpf_next =3D=3D BPF_FUSE_USER) { \ + handled =3D false; \ + break; \ + } \ + \ + fa.info.opcode &=3D ~FUSE_PREFILTER; \ \ error =3D backing(&fa, out, args); \ if (error < 0) \ fa.info.error_in =3D error; \ \ + if (bpf_next =3D=3D BPF_FUSE_CONTINUE) \ + break; \ + \ + fa.info.opcode |=3D FUSE_POSTFILTER; \ + if (bpf_next =3D=3D BPF_FUSE_POSTFILTER) \ + bpf_next =3D call_postfilter(fuse_ops, &fa.info, &feo);\ + if (bpf_next < 0) { \ + error =3D bpf_next; \ + break; \ + } \ + \ } while (false); \ \ if (initialized && handled) { \ @@ -66,6 +124,7 @@ if (res) \ error =3D res; \ } \ + bpf_fuse_free_alloced(&fa); \ \ *out =3D error ? _Generic((*out), \ default : \ @@ -351,6 +410,34 @@ static int fuse_open_initialize_out(struct bpf_fuse_ar= gs *fa, struct fuse_open_a return 0; } =20 +static int fuse_open_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_= info *meta, + struct fuse_open_args *open) +{ + if (meta->opcode =3D=3D (FUSE_OPEN | FUSE_PREFILTER)) { + if (ops->open_prefilter) + return ops->open_prefilter(meta, &open->in); + } + if (meta->opcode =3D=3D (FUSE_OPENDIR | FUSE_PREFILTER)) { + if (ops->opendir_prefilter) + return ops->opendir_prefilter(meta, &open->in); + } + return BPF_FUSE_CONTINUE; +} + +static int fuse_open_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta= _info *meta, + struct fuse_open_args *open) +{ + if (meta->opcode =3D=3D (FUSE_OPEN | FUSE_POSTFILTER)) { + if (ops->open_postfilter) + return ops->open_postfilter(meta, &open->in, &open->out); + } + if (meta->opcode =3D=3D (FUSE_OPENDIR | FUSE_POSTFILTER)) { + if (ops->opendir_postfilter) + return ops->opendir_postfilter(meta, &open->in, &open->out); + } + return BPF_FUSE_CONTINUE; +} + static int fuse_open_backing(struct bpf_fuse_args *fa, int *out, struct inode *inode, struct file *file, bool isdir) { @@ -419,6 +506,7 @@ int fuse_bpf_open(int *out, struct inode *inode, struct= file *file, bool isdir) { return bpf_fuse_backing(inode, struct fuse_open_args, out, fuse_open_initialize_in, fuse_open_initialize_out, + fuse_open_prefilter, fuse_open_postfilter, fuse_open_backing, fuse_open_finalize, inode, file, isdir); } @@ -484,6 +572,22 @@ static int fuse_create_open_initialize_out(struct bpf_= fuse_args *fa, struct fuse return 0; } =20 +static int fuse_create_open_prefilter(struct fuse_ops *ops, struct bpf_fus= e_meta_info *meta, + struct fuse_create_open_args *args) +{ + if (ops->create_open_prefilter) + return ops->create_open_prefilter(meta, &args->in, &args->name); + return BPF_FUSE_CONTINUE; +} + +static int fuse_create_open_postfilter(struct fuse_ops *ops, struct bpf_fu= se_meta_info *meta, + struct fuse_create_open_args *args) +{ + if (ops->create_open_postfilter) + return ops->create_open_postfilter(meta, &args->in, &args->name, &args->= entry_out, &args->open_out); + return BPF_FUSE_CONTINUE; +} + static int fuse_open_file_backing(struct inode *inode, struct file *file) { struct fuse_mount *fm =3D get_fuse_mount(inode); @@ -586,12 +690,16 @@ static int fuse_create_open_finalize(struct bpf_fuse_= args *fa, int *out, return 0; } =20 + + int fuse_bpf_create_open(int *out, struct inode *dir, struct dentry *entry, struct file *file, unsigned int flags, umode_t mode) { return bpf_fuse_backing(dir, struct fuse_create_open_args, out, fuse_create_open_initialize_in, fuse_create_open_initialize_out, + fuse_create_open_prefilter, + fuse_create_open_postfilter, fuse_create_open_backing, fuse_create_open_finalize, dir, entry, file, flags, mode); @@ -652,6 +760,38 @@ static int fuse_releasedir_initialize_in(struct bpf_fu= se_args *fa, return 0; } =20 +static int fuse_release_prefilter(struct fuse_ops *ops, struct bpf_fuse_me= ta_info *meta, + struct fuse_release_in *args) +{ + if (ops->release_prefilter) + return ops->release_prefilter(meta, args); + return BPF_FUSE_CONTINUE; +} + +static int fuse_release_postfilter(struct fuse_ops *ops, struct bpf_fuse_m= eta_info *meta, + struct fuse_release_in *args) +{ + if (ops->release_postfilter) + return ops->release_postfilter(meta, args); + return BPF_FUSE_CONTINUE; +} + +static int fuse_releasedir_prefilter(struct fuse_ops *ops, struct bpf_fuse= _meta_info *meta, + struct fuse_release_in *args) +{ + if (ops->releasedir_prefilter) + return ops->releasedir_prefilter(meta, args); + return BPF_FUSE_CONTINUE; +} + +static int fuse_releasedir_postfilter(struct fuse_ops *ops, struct bpf_fus= e_meta_info *meta, + struct fuse_release_in *args) +{ + if (ops->releasedir_postfilter) + return ops->releasedir_postfilter(meta, args); + return BPF_FUSE_CONTINUE; +} + static int fuse_releasedir_initialize_out(struct bpf_fuse_args *fa, struct fuse_release_in *fri, struct inode *inode, struct file *file) @@ -677,6 +817,7 @@ int fuse_bpf_release(int *out, struct inode *inode, str= uct file *file) { return bpf_fuse_backing(inode, struct fuse_release_in, out, fuse_release_initialize_in, fuse_release_initialize_out, + fuse_release_prefilter, fuse_release_postfilter, fuse_release_backing, fuse_release_finalize, inode, file); } @@ -685,6 +826,7 @@ int fuse_bpf_releasedir(int *out, struct inode *inode, = struct file *file) { return bpf_fuse_backing(inode, struct fuse_release_in, out, fuse_releasedir_initialize_in, fuse_releasedir_initialize_out, + fuse_releasedir_prefilter, fuse_releasedir_postfilter, fuse_release_backing, fuse_release_finalize, inode, file); } =20 @@ -717,6 +859,22 @@ static int fuse_flush_initialize_out(struct bpf_fuse_a= rgs *fa, struct fuse_flush return 0; } =20 +static int fuse_flush_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta= _info *meta, + struct fuse_flush_in *args) +{ + if (ops->flush_prefilter) + return ops->flush_prefilter(meta, args); + return BPF_FUSE_CONTINUE; +} + +static int fuse_flush_postfilter(struct fuse_ops *ops, struct bpf_fuse_met= a_info *meta, + struct fuse_flush_in *args) +{ + if (ops->flush_postfilter) + return ops->flush_postfilter(meta, args); + return BPF_FUSE_CONTINUE; +} + static int fuse_flush_backing(struct bpf_fuse_args *fa, int *out, struct f= ile *file, fl_owner_t id) { struct fuse_file *fuse_file =3D file->private_data; @@ -737,6 +895,7 @@ int fuse_bpf_flush(int *out, struct inode *inode, struc= t file *file, fl_owner_t { return bpf_fuse_backing(inode, struct fuse_flush_in, out, fuse_flush_initialize_in, fuse_flush_initialize_out, + fuse_flush_prefilter, fuse_flush_postfilter, fuse_flush_backing, fuse_flush_finalize, file, id); } @@ -780,6 +939,22 @@ static int fuse_lseek_initialize_out(struct bpf_fuse_a= rgs *fa, struct fuse_lseek return 0; } =20 +static int fuse_lseek_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta= _info *meta, + struct fuse_lseek_args *args) +{ + if (ops->lseek_prefilter) + return ops->lseek_prefilter(meta, &args->in); + return BPF_FUSE_CONTINUE; +} + +static int fuse_lseek_postfilter(struct fuse_ops *ops, struct bpf_fuse_met= a_info *meta, + struct fuse_lseek_args *args) +{ + if (ops->lseek_postfilter) + return ops->lseek_postfilter(meta, &args->in, &args->out); + return BPF_FUSE_CONTINUE; +} + static int fuse_lseek_backing(struct bpf_fuse_args *fa, loff_t *out, struct file *file, loff_t offset, int whence) { @@ -826,6 +1001,7 @@ int fuse_bpf_lseek(loff_t *out, struct inode *inode, s= truct file *file, loff_t o { return bpf_fuse_backing(inode, struct fuse_lseek_args, out, fuse_lseek_initialize_in, fuse_lseek_initialize_out, + fuse_lseek_prefilter, fuse_lseek_postfilter, fuse_lseek_backing, fuse_lseek_finalize, file, offset, whence); } @@ -878,6 +1054,22 @@ static int fuse_copy_file_range_initialize_out(struct= bpf_fuse_args *fa, return 0; } =20 +static int fuse_copy_file_range_prefilter(struct fuse_ops *ops, struct bpf= _fuse_meta_info *meta, + struct fuse_copy_file_range_args *args) +{ + if (ops->copy_file_range_prefilter) + return ops->copy_file_range_prefilter(meta, &args->in); + return BPF_FUSE_CONTINUE; +} + +static int fuse_copy_file_range_postfilter(struct fuse_ops *ops, struct bp= f_fuse_meta_info *meta, + struct fuse_copy_file_range_args *args) +{ + if (ops->copy_file_range_postfilter) + return ops->copy_file_range_postfilter(meta, &args->in, &args->out); + return BPF_FUSE_CONTINUE; +} + static int fuse_copy_file_range_backing(struct bpf_fuse_args *fa, ssize_t = *out, struct file *file_in, loff_t pos_in, struct file *file_out, loff_t pos_out, size_t len, unsigned int flags) @@ -912,6 +1104,8 @@ int fuse_bpf_copy_file_range(ssize_t *out, struct inod= e *inode, struct file *fil return bpf_fuse_backing(inode, struct fuse_copy_file_range_args, out, fuse_copy_file_range_initialize_in, fuse_copy_file_range_initialize_out, + fuse_copy_file_range_prefilter, + fuse_copy_file_range_postfilter, fuse_copy_file_range_backing, fuse_copy_file_range_finalize, file_in, pos_in, file_out, pos_out, len, flags); @@ -947,6 +1141,22 @@ static int fuse_fsync_initialize_out(struct bpf_fuse_= args *fa, struct fuse_fsync return 0; } =20 +static int fuse_fsync_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta= _info *meta, + struct fuse_fsync_in *in) +{ + if (ops->fsync_prefilter) + return ops->fsync_prefilter(meta, in); + return BPF_FUSE_CONTINUE; +} + +static int fuse_fsync_postfilter(struct fuse_ops *ops, struct bpf_fuse_met= a_info *meta, + struct fuse_fsync_in *in) +{ + if (ops->fsync_postfilter) + return ops->fsync_postfilter(meta, in); + return BPF_FUSE_CONTINUE; +} + static int fuse_fsync_backing(struct bpf_fuse_args *fa, int *out, struct file *file, loff_t start, loff_t end, int datasync) { @@ -969,6 +1179,7 @@ int fuse_bpf_fsync(int *out, struct inode *inode, stru= ct file *file, loff_t star { return bpf_fuse_backing(inode, struct fuse_fsync_in, out, fuse_fsync_initialize_in, fuse_fsync_initialize_out, + fuse_fsync_prefilter, fuse_fsync_postfilter, fuse_fsync_backing, fuse_fsync_finalize, file, start, end, datasync); } @@ -1003,10 +1214,27 @@ static int fuse_dir_fsync_initialize_out(struct bpf= _fuse_args *fa, struct fuse_f return 0; } =20 +static int fuse_dir_fsync_prefilter(struct fuse_ops *ops, struct bpf_fuse_= meta_info *meta, + struct fuse_fsync_in *in) +{ + if (ops->dir_fsync_prefilter) + return ops->fsync_prefilter(meta, in); + return BPF_FUSE_CONTINUE; +} + +static int fuse_dir_fsync_postfilter(struct fuse_ops *ops, struct bpf_fuse= _meta_info *meta, + struct fuse_fsync_in *in) +{ + if (ops->dir_fsync_postfilter) + return ops->dir_fsync_postfilter(meta, in); + return BPF_FUSE_CONTINUE; +} + int fuse_bpf_dir_fsync(int *out, struct inode *inode, struct file *file, l= off_t start, loff_t end, int datasync) { return bpf_fuse_backing(inode, struct fuse_fsync_in, out, fuse_dir_fsync_initialize_in, fuse_dir_fsync_initialize_out, + fuse_dir_fsync_prefilter, fuse_dir_fsync_postfilter, fuse_fsync_backing, fuse_fsync_finalize, file, start, end, datasync); } @@ -1076,6 +1304,22 @@ static int fuse_getxattr_initialize_out(struct bpf_f= use_args *fa, return 0; } =20 +static int fuse_getxattr_prefilter(struct fuse_ops *ops, struct bpf_fuse_m= eta_info *meta, + struct fuse_getxattr_args *args) +{ + if (ops->getxattr_prefilter) + return ops->getxattr_prefilter(meta, &args->in, &args->name); + return BPF_FUSE_CONTINUE; +} + +static int fuse_getxattr_postfilter(struct fuse_ops *ops, struct bpf_fuse_= meta_info *meta, + struct fuse_getxattr_args *args) +{ + if (ops->getxattr_postfilter) + return ops->getxattr_postfilter(meta, &args->in, &args->name, &args->val= ue, &args->out); + return BPF_FUSE_CONTINUE; +} + static int fuse_getxattr_backing(struct bpf_fuse_args *fa, int *out, struct dentry *dentry, const char *name, void *value, size_t size) @@ -1121,6 +1365,7 @@ int fuse_bpf_getxattr(int *out, struct inode *inode, = struct dentry *dentry, cons { return bpf_fuse_backing(inode, struct fuse_getxattr_args, out, fuse_getxattr_initialize_in, fuse_getxattr_initialize_out, + fuse_getxattr_prefilter, fuse_getxattr_postfilter, fuse_getxattr_backing, fuse_getxattr_finalize, dentry, name, value, size); } @@ -1173,6 +1418,22 @@ static int fuse_listxattr_initialize_out(struct bpf_= fuse_args *fa, return 0; } =20 +static int fuse_listxattr_prefilter(struct fuse_ops *ops, struct bpf_fuse_= meta_info *meta, + struct fuse_getxattr_args *args) +{ + if (ops->listxattr_prefilter) + return ops->listxattr_prefilter(meta, &args->in); + return BPF_FUSE_CONTINUE; +} + +static int fuse_listxattr_postfilter(struct fuse_ops *ops, struct bpf_fuse= _meta_info *meta, + struct fuse_getxattr_args *args) +{ + if (ops->listxattr_postfilter) + return ops->listxattr_postfilter(meta, &args->in, &args->value, &args->o= ut); + return BPF_FUSE_CONTINUE; +} + static int fuse_listxattr_backing(struct bpf_fuse_args *fa, ssize_t *out, = struct dentry *dentry, char *list, size_t size) { @@ -1212,6 +1473,7 @@ int fuse_bpf_listxattr(ssize_t *out, struct inode *in= ode, struct dentry *dentry, { return bpf_fuse_backing(inode, struct fuse_getxattr_args, out, fuse_listxattr_initialize_in, fuse_listxattr_initialize_out, + fuse_listxattr_prefilter, fuse_listxattr_postfilter, fuse_listxattr_backing, fuse_listxattr_finalize, dentry, list, size); } @@ -1277,6 +1539,22 @@ static int fuse_setxattr_initialize_out(struct bpf_f= use_args *fa, return 0; } =20 +static int fuse_setxattr_prefilter(struct fuse_ops *ops, struct bpf_fuse_m= eta_info *meta, + struct fuse_setxattr_args *args) +{ + if (ops->setxattr_prefilter) + return ops->setxattr_prefilter(meta, &args->in, &args->name, &args->valu= e); + return BPF_FUSE_CONTINUE; +} + +static int fuse_setxattr_postfilter(struct fuse_ops *ops, struct bpf_fuse_= meta_info *meta, + struct fuse_setxattr_args *args) +{ + if (ops->setxattr_postfilter) + return ops->setxattr_postfilter(meta, &args->in, &args->name, &args->val= ue); + return BPF_FUSE_CONTINUE; +} + static int fuse_setxattr_backing(struct bpf_fuse_args *fa, int *out, struc= t dentry *dentry, const char *name, const void *value, size_t size, int flags) @@ -1300,6 +1578,7 @@ int fuse_bpf_setxattr(int *out, struct inode *inode, = struct dentry *dentry, { return bpf_fuse_backing(inode, struct fuse_setxattr_args, out, fuse_setxattr_initialize_in, fuse_setxattr_initialize_out, + fuse_setxattr_prefilter, fuse_setxattr_postfilter, fuse_setxattr_backing, fuse_setxattr_finalize, dentry, name, value, size, flags); } @@ -1336,6 +1615,22 @@ static int fuse_removexattr_initialize_out(struct bp= f_fuse_args *fa, return 0; } =20 +static int fuse_removexattr_prefilter(struct fuse_ops *ops, struct bpf_fus= e_meta_info *meta, + struct fuse_buffer *in) +{ + if (ops->removexattr_prefilter) + return ops->removexattr_prefilter(meta, in); + return BPF_FUSE_CONTINUE; +} + +static int fuse_removexattr_postfilter(struct fuse_ops *ops, struct bpf_fu= se_meta_info *meta, + struct fuse_buffer *in) +{ + if (ops->removexattr_postfilter) + return ops->removexattr_postfilter(meta, in); + return BPF_FUSE_CONTINUE; +} + static int fuse_removexattr_backing(struct bpf_fuse_args *fa, int *out, struct dentry *dentry, const char *name) { @@ -1356,6 +1651,7 @@ int fuse_bpf_removexattr(int *out, struct inode *inod= e, struct dentry *dentry, c { return bpf_fuse_backing(inode, struct fuse_buffer, out, fuse_removexattr_initialize_in, fuse_removexattr_initialize_out, + fuse_removexattr_prefilter, fuse_removexattr_postfilter, fuse_removexattr_backing, fuse_removexattr_finalize, dentry, name); } @@ -1446,6 +1742,22 @@ static int fuse_file_read_iter_initialize_out(struct= bpf_fuse_args *fa, struct f return 0; } =20 +static int fuse_file_read_iter_prefilter(struct fuse_ops *ops, struct bpf_= fuse_meta_info *meta, + struct fuse_file_read_iter_args *args) +{ + if (ops->read_iter_prefilter) + return ops->read_iter_prefilter(meta, &args->in); + return BPF_FUSE_CONTINUE; +} + +static int fuse_file_read_iter_postfilter(struct fuse_ops *ops, struct bpf= _fuse_meta_info *meta, + struct fuse_file_read_iter_args *args) +{ + if (ops->read_iter_postfilter) + return ops->read_iter_postfilter(meta, &args->in, &args->out); + return BPF_FUSE_CONTINUE; +} + static int fuse_file_read_iter_backing(struct bpf_fuse_args *fa, ssize_t *= out, struct kiocb *iocb, struct iov_iter *to) { @@ -1513,6 +1825,8 @@ int fuse_bpf_file_read_iter(ssize_t *out, struct inod= e *inode, struct kiocb *ioc return bpf_fuse_backing(inode, struct fuse_file_read_iter_args, out, fuse_file_read_iter_initialize_in, fuse_file_read_iter_initialize_out, + fuse_file_read_iter_prefilter, + fuse_file_read_iter_postfilter, fuse_file_read_iter_backing, fuse_file_read_iter_finalize, iocb, to); @@ -1562,6 +1876,22 @@ static int fuse_file_write_iter_initialize_out(struc= t bpf_fuse_args *fa, return 0; } =20 +static int fuse_write_iter_prefilter(struct fuse_ops *ops, struct bpf_fuse= _meta_info *meta, + struct fuse_file_write_iter_args *args) +{ + if (ops->write_iter_prefilter) + return ops->write_iter_prefilter(meta, &args->in); + return BPF_FUSE_CONTINUE; +} + +static int fuse_write_iter_postfilter(struct fuse_ops *ops, struct bpf_fus= e_meta_info *meta, + struct fuse_file_write_iter_args *args) +{ + if (ops->write_iter_postfilter) + return ops->write_iter_postfilter(meta, &args->in, &args->out); + return BPF_FUSE_CONTINUE; +} + static int fuse_file_write_iter_backing(struct bpf_fuse_args *fa, ssize_t = *out, struct kiocb *iocb, struct iov_iter *from) { @@ -1626,6 +1956,8 @@ int fuse_bpf_file_write_iter(ssize_t *out, struct ino= de *inode, struct kiocb *io return bpf_fuse_backing(inode, struct fuse_file_write_iter_args, out, fuse_file_write_iter_initialize_in, fuse_file_write_iter_initialize_out, + fuse_write_iter_prefilter, + fuse_write_iter_postfilter, fuse_file_write_iter_backing, fuse_file_write_iter_finalize, iocb, from); @@ -1701,6 +2033,22 @@ static int fuse_file_fallocate_initialize_out(struct= bpf_fuse_args *fa, return 0; } =20 +static int fuse_file_fallocate_prefilter(struct fuse_ops *ops, struct bpf_= fuse_meta_info *meta, + struct fuse_fallocate_in *in) +{ + if (ops->file_fallocate_prefilter) + return ops->file_fallocate_prefilter(meta, in); + return BPF_FUSE_CONTINUE; +} + +static int fuse_file_fallocate_postfilter(struct fuse_ops *ops, struct bpf= _fuse_meta_info *meta, + struct fuse_fallocate_in *in) +{ + if (ops->file_fallocate_postfilter) + return ops->file_fallocate_postfilter(meta, in); + return BPF_FUSE_CONTINUE; +} + static int fuse_file_fallocate_backing(struct bpf_fuse_args *fa, int *out, struct file *file, int mode, loff_t offset, loff_t length) { @@ -1723,6 +2071,8 @@ int fuse_bpf_file_fallocate(int *out, struct inode *i= node, struct file *file, in return bpf_fuse_backing(inode, struct fuse_fallocate_in, out, fuse_file_fallocate_initialize_in, fuse_file_fallocate_initialize_out, + fuse_file_fallocate_prefilter, + fuse_file_fallocate_postfilter, fuse_file_fallocate_backing, fuse_file_fallocate_finalize, file, mode, offset, length); @@ -1790,6 +2140,22 @@ static int fuse_lookup_initialize_out(struct bpf_fus= e_args *fa, struct fuse_look return 0; } =20 +static int fuse_lookup_prefilter(struct fuse_ops *ops, struct bpf_fuse_met= a_info *meta, + struct fuse_lookup_args *args) +{ + if (ops->lookup_prefilter) + return ops->lookup_prefilter(meta, &args->name); + return BPF_FUSE_CONTINUE; +} + +static int fuse_lookup_postfilter(struct fuse_ops *ops, struct bpf_fuse_me= ta_info *meta, + struct fuse_lookup_args *args) +{ + if (ops->lookup_postfilter) + return ops->lookup_postfilter(meta, &args->name, &args->out, &args->bpf_= entries); + return BPF_FUSE_CONTINUE; +} + static int fuse_lookup_backing(struct bpf_fuse_args *fa, struct dentry **o= ut, struct inode *dir, struct dentry *entry, unsigned int flags) { @@ -1968,6 +2334,7 @@ int fuse_bpf_lookup(struct dentry **out, struct inode= *dir, struct dentry *entry { return bpf_fuse_backing(dir, struct fuse_lookup_args, out, fuse_lookup_initialize_in, fuse_lookup_initialize_out, + fuse_lookup_prefilter, fuse_lookup_postfilter, fuse_lookup_backing, fuse_lookup_finalize, dir, entry, flags); } @@ -2034,6 +2401,22 @@ static int fuse_mknod_initialize_out(struct bpf_fuse= _args *fa, struct fuse_mknod return 0; } =20 +static int fuse_mknod_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta= _info *meta, + struct fuse_mknod_args *args) +{ + if (ops->mknod_prefilter) + return ops->mknod_prefilter(meta, &args->in, &args->name); + return BPF_FUSE_CONTINUE; +} + +static int fuse_mknod_postfilter(struct fuse_ops *ops, struct bpf_fuse_met= a_info *meta, + struct fuse_mknod_args *args) +{ + if (ops->mknod_postfilter) + return ops->mknod_postfilter(meta, &args->in, &args->name); + return BPF_FUSE_CONTINUE; +} + static int fuse_mknod_backing(struct bpf_fuse_args *fa, int *out, struct inode *dir, struct dentry *entry, umode_t mode, dev_t rdev) { @@ -2086,6 +2469,7 @@ int fuse_bpf_mknod(int *out, struct inode *dir, struc= t dentry *entry, umode_t mo { return bpf_fuse_backing(dir, struct fuse_mknod_args, out, fuse_mknod_initialize_in, fuse_mknod_initialize_out, + fuse_mknod_prefilter, fuse_mknod_postfilter, fuse_mknod_backing, fuse_mknod_finalize, dir, entry, mode, rdev); } @@ -2187,10 +2571,27 @@ static int fuse_mkdir_finalize(struct bpf_fuse_args= *fa, int *out, return 0; } =20 +static int fuse_mkdir_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta= _info *meta, + struct fuse_mkdir_args *args) +{ + if (ops->mkdir_prefilter) + return ops->mkdir_prefilter(meta, &args->in, &args->name); + return BPF_FUSE_CONTINUE; +} + +static int fuse_mkdir_postfilter(struct fuse_ops *ops, struct bpf_fuse_met= a_info *meta, + struct fuse_mkdir_args *args) +{ + if (ops->mkdir_prefilter) + return ops->mkdir_postfilter(meta, &args->in, &args->name); + return BPF_FUSE_CONTINUE; +} + int fuse_bpf_mkdir(int *out, struct inode *dir, struct dentry *entry, umod= e_t mode) { return bpf_fuse_backing(dir, struct fuse_mkdir_args, out, fuse_mkdir_initialize_in, fuse_mkdir_initialize_out, + fuse_mkdir_prefilter, fuse_mkdir_postfilter, fuse_mkdir_backing, fuse_mkdir_finalize, dir, entry, mode); } @@ -2224,6 +2625,22 @@ static int fuse_rmdir_initialize_out(struct bpf_fuse= _args *fa, struct fuse_buffe return 0; } =20 +static int fuse_rmdir_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta= _info *meta, + struct fuse_buffer *name) +{ + if (ops->rmdir_prefilter) + return ops->rmdir_prefilter(meta, name); + return BPF_FUSE_CONTINUE; +} + +static int fuse_rmdir_postfilter(struct fuse_ops *ops, struct bpf_fuse_met= a_info *meta, + struct fuse_buffer *name) +{ + if (ops->rmdir_postfilter) + return ops->rmdir_postfilter(meta, name); + return BPF_FUSE_CONTINUE; +} + static int fuse_rmdir_backing(struct bpf_fuse_args *fa, int *out, struct inode *dir, struct dentry *entry) { @@ -2258,6 +2675,7 @@ int fuse_bpf_rmdir(int *out, struct inode *dir, struc= t dentry *entry) { return bpf_fuse_backing(dir, struct fuse_buffer, out, fuse_rmdir_initialize_in, fuse_rmdir_initialize_out, + fuse_rmdir_prefilter, fuse_rmdir_postfilter, fuse_rmdir_backing, fuse_rmdir_finalize, dir, entry); } @@ -2400,6 +2818,22 @@ static int fuse_rename2_initialize_out(struct bpf_fu= se_args *fa, struct fuse_ren return 0; } =20 +static int fuse_rename2_prefilter(struct fuse_ops *ops, struct bpf_fuse_me= ta_info *meta, + struct fuse_rename2_args *args) +{ + if (ops->rename2_prefilter) + return ops->rename2_prefilter(meta, &args->in, &args->old_name, &args->n= ew_name); + return BPF_FUSE_CONTINUE; +} + +static int fuse_rename2_postfilter(struct fuse_ops *ops, struct bpf_fuse_m= eta_info *meta, + struct fuse_rename2_args *args) +{ + if (ops->rename2_postfilter) + return ops->rename2_postfilter(meta, &args->in, &args->old_name, &args->= new_name); + return BPF_FUSE_CONTINUE; +} + static int fuse_rename2_backing(struct bpf_fuse_args *fa, int *out, struct inode *olddir, struct dentry *oldent, struct inode *newdir, struct dentry *newent, @@ -2427,6 +2861,7 @@ int fuse_bpf_rename2(int *out, struct inode *olddir, = struct dentry *oldent, { return bpf_fuse_backing(olddir, struct fuse_rename2_args, out, fuse_rename2_initialize_in, fuse_rename2_initialize_out, + fuse_rename2_prefilter, fuse_rename2_postfilter, fuse_rename2_backing, fuse_rename2_finalize, olddir, oldent, newdir, newent, flags); } @@ -2487,6 +2922,22 @@ static int fuse_rename_initialize_out(struct bpf_fus= e_args *fa, struct fuse_rena return 0; } =20 +static int fuse_rename_prefilter(struct fuse_ops *ops, struct bpf_fuse_met= a_info *meta, + struct fuse_rename_args *args) +{ + if (ops->rename_prefilter) + return ops->rename_prefilter(meta, &args->in, &args->old_name, &args->ne= w_name); + return BPF_FUSE_CONTINUE; +} + +static int fuse_rename_postfilter(struct fuse_ops *ops, struct bpf_fuse_me= ta_info *meta, + struct fuse_rename_args *args) +{ + if (ops->rename_postfilter) + return ops->rename_postfilter(meta, &args->in, &args->old_name, &args->n= ew_name); + return BPF_FUSE_CONTINUE; +} + static int fuse_rename_backing(struct bpf_fuse_args *fa, int *out, struct inode *olddir, struct dentry *oldent, struct inode *newdir, struct dentry *newent) @@ -2508,6 +2959,7 @@ int fuse_bpf_rename(int *out, struct inode *olddir, s= truct dentry *oldent, { return bpf_fuse_backing(olddir, struct fuse_rename_args, out, fuse_rename_initialize_in, fuse_rename_initialize_out, + fuse_rename_prefilter, fuse_rename_postfilter, fuse_rename_backing, fuse_rename_finalize, olddir, oldent, newdir, newent); } @@ -2541,6 +2993,22 @@ static int fuse_unlink_initialize_out(struct bpf_fus= e_args *fa, struct fuse_buff return 0; } =20 +static int fuse_unlink_prefilter(struct fuse_ops *ops, struct bpf_fuse_met= a_info *meta, + struct fuse_buffer *name) +{ + if (ops->unlink_prefilter) + return ops->unlink_prefilter(meta, name); + return BPF_FUSE_CONTINUE; +} + +static int fuse_unlink_postfilter(struct fuse_ops *ops, struct bpf_fuse_me= ta_info *meta, + struct fuse_buffer *name) +{ + if (ops->unlink_postfilter) + return ops->unlink_postfilter(meta, name); + return BPF_FUSE_CONTINUE; +} + static int fuse_unlink_backing(struct bpf_fuse_args *fa, int *out, struct = inode *dir, struct dentry *entry) { struct path backing_path; @@ -2577,6 +3045,7 @@ int fuse_bpf_unlink(int *out, struct inode *dir, stru= ct dentry *entry) { return bpf_fuse_backing(dir, struct fuse_buffer, out, fuse_unlink_initialize_in, fuse_unlink_initialize_out, + fuse_unlink_prefilter, fuse_unlink_postfilter, fuse_unlink_backing, fuse_unlink_finalize, dir, entry); } @@ -2629,6 +3098,23 @@ static int fuse_link_initialize_out(struct bpf_fuse_= args *fa, struct fuse_link_a return 0; } =20 +static int fuse_link_prefilter(struct fuse_ops *ops, struct bpf_fuse_meta_= info *meta, + struct fuse_link_args *args) +{ + if (ops->link_prefilter) + return ops->link_prefilter(meta, &args->in, &args->name); + return BPF_FUSE_CONTINUE; +} + +static int fuse_link_postfilter(struct fuse_ops *ops, struct bpf_fuse_meta= _info *meta, + struct fuse_link_args *args) +{ + if (ops->link_postfilter) + return ops->link_postfilter(meta, &args->in, &args->name); + return BPF_FUSE_CONTINUE; +} + + static int fuse_link_backing(struct bpf_fuse_args *fa, int *out, struct de= ntry *entry, struct inode *dir, struct dentry *newent) { @@ -2696,6 +3182,7 @@ int fuse_bpf_link(int *out, struct inode *inode, stru= ct dentry *entry, { return bpf_fuse_backing(inode, struct fuse_link_args, out, fuse_link_initialize_in, fuse_link_initialize_out, + fuse_link_prefilter, fuse_link_postfilter, fuse_link_backing, fuse_link_finalize, entry, newdir, newent); } @@ -2744,6 +3231,22 @@ static int fuse_getattr_initialize_out(struct bpf_fu= se_args *fa, struct fuse_get return 0; } =20 +static int fuse_getattr_prefilter(struct fuse_ops *ops, struct bpf_fuse_me= ta_info *meta, + struct fuse_getattr_args *args) +{ + if (ops->getattr_prefilter) + return ops->getattr_prefilter(meta, &args->in); + return BPF_FUSE_CONTINUE; +} + +static int fuse_getattr_postfilter(struct fuse_ops *ops, struct bpf_fuse_m= eta_info *meta, + struct fuse_getattr_args *args) +{ + if (ops->getattr_postfilter) + return ops->getattr_postfilter(meta, &args->in, &args->out); + return BPF_FUSE_CONTINUE; +} + static int fuse_getattr_backing(struct bpf_fuse_args *fa, int *out, const struct dentry *entry, struct kstat *stat, u32 request_mask, unsigned int flags) @@ -2804,6 +3307,7 @@ int fuse_bpf_getattr(int *out, struct inode *inode, c= onst struct dentry *entry, { return bpf_fuse_backing(inode, struct fuse_getattr_args, out, fuse_getattr_initialize_in, fuse_getattr_initialize_out, + fuse_getattr_prefilter, fuse_getattr_postfilter, fuse_getattr_backing, fuse_getattr_finalize, entry, stat, request_mask, flags); } @@ -2883,6 +3387,22 @@ static int fuse_setattr_initialize_out(struct bpf_fu= se_args *fa, struct fuse_set return 0; } =20 +static int fuse_setattr_prefilter(struct fuse_ops *ops, struct bpf_fuse_me= ta_info *meta, + struct fuse_setattr_args *args) +{ + if (ops->setattr_prefilter) + return ops->setattr_prefilter(meta, &args->in); + return BPF_FUSE_CONTINUE; +} + +static int fuse_setattr_postfilter(struct fuse_ops *ops, struct bpf_fuse_m= eta_info *meta, + struct fuse_setattr_args *args) +{ + if (ops->setattr_postfilter) + return ops->setattr_postfilter(meta, &args->in, &args->out); + return BPF_FUSE_CONTINUE; +} + static int fuse_setattr_backing(struct bpf_fuse_args *fa, int *out, struct dentry *dentry, struct iattr *attr, struct file *file) { @@ -2920,6 +3440,7 @@ int fuse_bpf_setattr(int *out, struct inode *inode, s= truct dentry *dentry, struc { return bpf_fuse_backing(inode, struct fuse_setattr_args, out, fuse_setattr_initialize_in, fuse_setattr_initialize_out, + fuse_setattr_prefilter, fuse_setattr_postfilter, fuse_setattr_backing, fuse_setattr_finalize, dentry, attr, file); } @@ -2949,6 +3470,22 @@ static int fuse_statfs_initialize_out(struct bpf_fus= e_args *fa, struct fuse_stat return 0; } =20 +static int fuse_statfs_prefilter(struct fuse_ops *ops, struct bpf_fuse_met= a_info *meta, + struct fuse_statfs_out *out) +{ + if (ops->statfs_prefilter) + return ops->statfs_prefilter(meta); + return BPF_FUSE_CONTINUE; +} + +static int fuse_statfs_postfilter(struct fuse_ops *ops, struct bpf_fuse_me= ta_info *meta, + struct fuse_statfs_out *out) +{ + if (ops->statfs_postfilter) + return ops->statfs_postfilter(meta, out); + return BPF_FUSE_CONTINUE; +} + static int fuse_statfs_backing(struct bpf_fuse_args *fa, int *out, struct dentry *dentry, struct kstatfs *buf) { @@ -2984,6 +3521,7 @@ int fuse_bpf_statfs(int *out, struct inode *inode, st= ruct dentry *dentry, struct { return bpf_fuse_backing(dentry->d_inode, struct fuse_statfs_out, out, fuse_statfs_initialize_in, fuse_statfs_initialize_out, + fuse_statfs_prefilter, fuse_statfs_postfilter, fuse_statfs_backing, fuse_statfs_finalize, dentry, buf); } @@ -3053,6 +3591,22 @@ static int fuse_get_link_initialize_out(struct bpf_f= use_args *fa, struct fuse_ge return 0; } =20 +static int fuse_get_link_prefilter(struct fuse_ops *ops, struct bpf_fuse_m= eta_info *meta, + struct fuse_get_link_args *args) +{ + if (ops->get_link_prefilter) + return ops->get_link_prefilter(meta, &args->name); + return BPF_FUSE_CONTINUE; +} + +static int fuse_get_link_postfilter(struct fuse_ops *ops, struct bpf_fuse_= meta_info *meta, + struct fuse_get_link_args *args) +{ + if (ops->get_link_postfilter) + return ops->get_link_postfilter(meta, &args->name); + return BPF_FUSE_CONTINUE; +} + static int fuse_get_link_backing(struct bpf_fuse_args *fa, const char **ou= t, struct inode *inode, struct dentry *dentry, struct delayed_call *callback) @@ -3092,6 +3646,7 @@ int fuse_bpf_get_link(const char **out, struct inode = *inode, struct dentry *dent { return bpf_fuse_backing(inode, struct fuse_get_link_args, out, fuse_get_link_initialize_in, fuse_get_link_initialize_out, + fuse_get_link_prefilter, fuse_get_link_postfilter, fuse_get_link_backing, fuse_get_link_finalize, inode, dentry, callback); } @@ -3142,6 +3697,22 @@ static int fuse_symlink_initialize_out(struct bpf_fu= se_args *fa, struct fuse_sym return 0; } =20 +static int fuse_symlink_prefilter(struct fuse_ops *ops, struct bpf_fuse_me= ta_info *meta, + struct fuse_symlink_args *args) +{ + if (ops->symlink_prefilter) + return ops->symlink_prefilter(meta, &args->name, &args->path); + return BPF_FUSE_CONTINUE; +} + +static int fuse_symlink_postfilter(struct fuse_ops *ops, struct bpf_fuse_m= eta_info *meta, + struct fuse_symlink_args *args) +{ + if (ops->symlink_postfilter) + return ops->symlink_postfilter(meta, &args->name, &args->path); + return BPF_FUSE_CONTINUE; +} + static int fuse_symlink_backing(struct bpf_fuse_args *fa, int *out, struct inode *dir, struct dentry *entry, const char *link, int len) { @@ -3192,6 +3763,7 @@ int fuse_bpf_symlink(int *out, struct inode *dir, st= ruct dentry *entry, const c { return bpf_fuse_backing(dir, struct fuse_symlink_args, out, fuse_symlink_initialize_in, fuse_symlink_initialize_out, + fuse_symlink_prefilter, fuse_symlink_postfilter, fuse_symlink_backing, fuse_symlink_finalize, dir, entry, link, len); } @@ -3265,6 +3837,22 @@ static int fuse_readdir_initialize_out(struct bpf_fu= se_args *fa, struct fuse_rea return 0; } =20 +static int fuse_readdir_prefilter(struct fuse_ops *ops, struct bpf_fuse_me= ta_info *meta, + struct fuse_read_args *args) +{ + if (ops->readdir_prefilter) + return ops->readdir_prefilter(meta, &args->in); + return BPF_FUSE_CONTINUE; +} + +static int fuse_readdir_postfilter(struct fuse_ops *ops, struct bpf_fuse_m= eta_info *meta, + struct fuse_read_args *args) +{ + if (ops->readdir_postfilter) + return ops->readdir_postfilter(meta, &args->in, &args->out, &args->buffe= r); + return BPF_FUSE_CONTINUE; +} + struct fusebpf_ctx { struct dir_context ctx; u8 *addr; @@ -3380,6 +3968,7 @@ int fuse_bpf_readdir(int *out, struct inode *inode, s= truct file *file, struct di again: ret =3D bpf_fuse_backing(inode, struct fuse_read_args, out, fuse_readdir_initialize_in, fuse_readdir_initialize_out, + fuse_readdir_prefilter, fuse_readdir_postfilter, fuse_readdir_backing, fuse_readdir_finalize, file, ctx, &force_again, &allow_force, is_continued); if (force_again && *out >=3D 0) { @@ -3416,6 +4005,22 @@ static int fuse_access_initialize_out(struct bpf_fus= e_args *fa, struct fuse_acce return 0; } =20 +static int fuse_access_prefilter(struct fuse_ops *ops, struct bpf_fuse_met= a_info *meta, + struct fuse_access_in *in) +{ + if (ops->access_prefilter) + return ops->access_prefilter(meta, in); + return BPF_FUSE_CONTINUE; +} + +static int fuse_access_postfilter(struct fuse_ops *ops, struct bpf_fuse_me= ta_info *meta, + struct fuse_access_in *in) +{ + if (ops->access_postfilter) + return ops->access_postfilter(meta, in); + return BPF_FUSE_CONTINUE; +} + static int fuse_access_backing(struct bpf_fuse_args *fa, int *out, struct = inode *inode, int mask) { struct fuse_inode *fi =3D get_fuse_inode(inode); @@ -3434,6 +4039,7 @@ int fuse_bpf_access(int *out, struct inode *inode, in= t mask) { return bpf_fuse_backing(inode, struct fuse_access_in, out, fuse_access_initialize_in, fuse_access_initialize_out, + fuse_access_prefilter, fuse_access_postfilter, fuse_access_backing, fuse_access_finalize, inode, mask); } =20 --=20 2.40.0.634.g4ca3ef3211-goog From nobody Wed Dec 17 19:22:07 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 DD5F4C7EE22 for ; Tue, 18 Apr 2023 01:45:52 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231165AbjDRBpu (ORCPT ); Mon, 17 Apr 2023 21:45:50 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53858 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231286AbjDRBpN (ORCPT ); Mon, 17 Apr 2023 21:45:13 -0400 Received: from mail-yb1-xb49.google.com (mail-yb1-xb49.google.com [IPv6:2607:f8b0:4864:20::b49]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 4FEC45FEB for ; Mon, 17 Apr 2023 18:42:40 -0700 (PDT) Received: by mail-yb1-xb49.google.com with SMTP id b5-20020a253405000000b00b8f66e29f5cso10122480yba.21 for ; Mon, 17 Apr 2023 18:42:40 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20221208; t=1681782125; x=1684374125; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=K9AGDYeuciPVrN915Qcuh9H5YY4BpsMk+FO3fkMyYec=; b=VrPrsw2bmIPnUEb2XN3ynu2DfaIBg5kjX6yHgZ+daQdf4ahQzkoZoDDJ/j3zK9mhn0 ihY05K+k0Wdzb+pobv2yPCs/mtGZLkjsBIBCNie6c+RAIF2PN2A4TYaV+awyzNs/97JO 4QvTBNjWrWQld0yZckQ8YWsYK/0ESvJMFy0QKQuc1LZA3usE9Eik7oeDIO2SD9GAEjlV b6Byxc+kS4ICk2MND+KAZIzsj03hdQU2XOmxLL8wMOJdjLkHLRiM72bO2UEsaoU33HAw BETE2SUeDZaNZOTmoWT9KJSdOYkkwz4mCp2S23gxi4Ts1cpkx4bIb6ox0s83mhudTU9Z RX2w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681782125; x=1684374125; 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=K9AGDYeuciPVrN915Qcuh9H5YY4BpsMk+FO3fkMyYec=; b=Dk2mmj2FgSbBSEmo2iqOuBpVm4z8gX/IL27rVkaykDLd4j0AwAlGUufQ/DhjySsObT lHy8Ss7fXSVymbt2t8U19adErMtwGGbosDunY4q0usFdLA0rEvyoDUJsaP+m8aOHi4TE 242+t0BLgZ09o6sll2nf0/ddStJ0Ogj+O7w2S0C5Pg1aOy82hTyF9zGreiVxtndiq2hj fTtYSn3b/+o7zU+kRGJirAJok40tEa7yoDNflVKkGAWArDfPsToXEeHLWmtLPvE7kcTf 0fNKjnV6r3k7IpRmbp7IX1gWvXAMEvHJHQ1+o26404S1B2+hj6ECoJZkirHMEd8AcIuf dwzw== X-Gm-Message-State: AAQBX9etbey0JFrJbEPARkOI/AdEBH2rM6WVkqx6IZwEMyw8Hivcfvsf i7KcELxTYyAwayhrA7q8yW7T/AZZv8M= X-Google-Smtp-Source: AKy350ZLirn3I/BrcVzO0YqPwNPD1HbdzOUcnYLbOs6JaJ1QgTQX8WAFlA2B666sWoEpeNpU9WPT5G7v98g= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:e67a:98b0:942d:86aa]) (user=drosen job=sendgmr) by 2002:a05:690c:d91:b0:54f:e88d:79ba with SMTP id da17-20020a05690c0d9100b0054fe88d79bamr9715005ywb.5.1681782125267; Mon, 17 Apr 2023 18:42:05 -0700 (PDT) Date: Mon, 17 Apr 2023 18:40:33 -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-34-drosen@google.com> Subject: [RFC PATCH v3 33/37] fuse-bpf: Add userspace pre/post filters 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 Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This allows fuse-bpf to call out to userspace to handle pre and post filters. Any of the inputs may be changed by the prefilter, so we must handle up to 3 outputs. For the postfilter, our inputs include the output arguments, so we must handle up to 5 inputs. Additionally, we add an extension for passing the return code of the backing call to the postfilter, adding one additional possible output bringing the total to 4. As long as you don't request both pre-filter and post-filter in userspace, we will end up doing fewer round trips to userspace. Signed-off-by: Daniel Rosenberg --- fs/fuse/backing.c | 179 ++++++++++++++++++++++++++++++++++++++ fs/fuse/dev.c | 2 + fs/fuse/dir.c | 6 +- fs/fuse/fuse_i.h | 33 ++++++- include/linux/bpf_fuse.h | 1 + include/uapi/linux/fuse.h | 1 + 6 files changed, 217 insertions(+), 5 deletions(-) diff --git a/fs/fuse/backing.c b/fs/fuse/backing.c index 9217e9f83d98..1de302fc91b6 100644 --- a/fs/fuse/backing.c +++ b/fs/fuse/backing.c @@ -14,6 +14,163 @@ #include #include =20 +static void set_in_args(struct fuse_in_arg *dst, struct bpf_fuse_arg *src) +{ + if (src->is_buffer) { + struct fuse_buffer *buffer =3D src->buffer; + + *dst =3D (struct fuse_in_arg) { + .size =3D buffer->size, + .value =3D buffer->data, + }; + } else { + *dst =3D (struct fuse_in_arg) { + .size =3D src->size, + .value =3D src->value, + }; + } +} + +static void set_out_args(struct fuse_arg *dst, struct bpf_fuse_arg *src) +{ + if (src->is_buffer) { + struct fuse_buffer *buffer =3D src->buffer; + + // Userspace out args presents as much space as needed + *dst =3D (struct fuse_arg) { + .size =3D buffer->max_size, + .value =3D buffer->data, + }; + } else { + *dst =3D (struct fuse_arg) { + .size =3D src->size, + .value =3D src->value, + }; + } +} + +static int get_err_in(uint32_t error, struct fuse_in_arg *ext) +{ + struct fuse_ext_header *xh; + uint32_t *err_in; + uint32_t err_in_size =3D fuse_ext_size(sizeof(*err_in)); + + xh =3D extend_arg(ext, err_in_size); + if (!xh) + return -ENOMEM; + xh->size =3D err_in_size; + xh->type =3D FUSE_ERROR_IN; + + err_in =3D (uint32_t *)&xh[1]; + *err_in =3D error; + return 0; +} + +static int get_filter_ext(struct fuse_args *args) +{ + struct fuse_in_arg ext =3D { .size =3D 0, .value =3D NULL }; + int err =3D 0; + + if (args->is_filter) + err =3D get_err_in(args->error_in, &ext); + if (!err && ext.size) { + WARN_ON(args->in_numargs >=3D ARRAY_SIZE(args->in_args)); + args->is_ext =3D true; + args->ext_idx =3D args->in_numargs++; + args->in_args[args->ext_idx] =3D ext; + } else { + kfree(ext.value); + } + return err; +} + +static ssize_t fuse_bpf_simple_request(struct fuse_mount *fm, struct bpf_f= use_args *fa, + unsigned short in_numargs, unsigned short out_numargs, + struct bpf_fuse_arg *out_arg_array, bool add_out_to_in) +{ + int i; + ssize_t res; + + struct fuse_args args =3D { + .nodeid =3D fa->info.nodeid, + .opcode =3D fa->info.opcode, + .error_in =3D fa->info.error_in, + .in_numargs =3D in_numargs, + .out_numargs =3D out_numargs, + .force =3D !!(fa->flags & FUSE_BPF_FORCE), + .out_argvar =3D !!(fa->flags & FUSE_BPF_OUT_ARGVAR), + .is_lookup =3D !!(fa->flags & FUSE_BPF_IS_LOOKUP), + .is_filter =3D true, + }; + + /* All out args must be writeable */ + for (i =3D 0; i < out_numargs; ++i) { + struct fuse_buffer *buffer; + + if (!out_arg_array[i].is_buffer) + continue; + buffer =3D out_arg_array[i].buffer; + if (!bpf_fuse_get_writeable(buffer, buffer->max_size, true)) + return -ENOMEM; + } + + /* Set in args */ + for (i =3D 0; i < fa->in_numargs; ++i) + set_in_args(&args.in_args[i], &fa->in_args[i]); + if (add_out_to_in) { + for (i =3D 0; i < fa->out_numargs; ++i) { + set_in_args(&args.in_args[fa->in_numargs + i], &fa->out_args[i]); + } + } + + /* Set out args */ + for (i =3D 0; i < out_numargs; ++i) + set_out_args(&args.out_args[i], &out_arg_array[i]); + + if (out_arg_array[out_numargs - 1].is_buffer) { + struct fuse_buffer *buff =3D out_arg_array[out_numargs - 1].buffer; + + if (buff->flags & BPF_FUSE_VARIABLE_SIZE) + args.out_argvar =3D true; + } + if (add_out_to_in) { + res =3D get_filter_ext(&args); + if (res) + return res; + } + res =3D fuse_simple_request(fm, &args); + + /* update used areas of buffers */ + for (i =3D 0; i < out_numargs; ++i) + if (out_arg_array[i].is_buffer && + (out_arg_array[i].buffer->flags & BPF_FUSE_VARIABLE_SIZE)) + out_arg_array[i].buffer->size =3D args.out_args[i].size; + fa->ret =3D args.ret; + + free_ext_value(&args); + + return res; +} + +static ssize_t fuse_prefilter_simple_request(struct fuse_mount *fm, struct= bpf_fuse_args *fa) +{ + uint32_t out_args =3D fa->in_numargs; + + // mkdir and company are not permitted to change the name. This should be= done at lookup + // Thus, these can't be set by the userspace prefilter + if (fa->in_args[fa->in_numargs - 1].is_buffer && + (fa->in_args[fa->in_numargs - 1].buffer->flags & BPF_FUSE_IMMUTABLE)) + out_args--; + return fuse_bpf_simple_request(fm, fa, fa->in_numargs, out_args, + fa->in_args, false); +} + +static ssize_t fuse_postfilter_simple_request(struct fuse_mount *fm, struc= t bpf_fuse_args *fa) +{ + return fuse_bpf_simple_request(fm, fa, fa->in_numargs + fa->out_numargs, = fa->out_numargs, + fa->out_args, true); +} + static inline void bpf_fuse_set_in_immutable(struct bpf_fuse_args *fa) { int i; @@ -60,9 +217,11 @@ static inline void bpf_fuse_free_alloced(struct bpf_fus= e_args *fa) ({ \ struct fuse_inode *fuse_inode =3D get_fuse_inode(inode); \ struct fuse_ops *fuse_ops =3D fuse_inode->bpf_ops; \ + struct fuse_mount *fm =3D get_fuse_mount(inode); \ struct bpf_fuse_args fa =3D { 0 }; \ bool initialized =3D false; \ bool handled =3D false; \ + bool locked; \ ssize_t res; \ int bpf_next; \ io feo =3D { 0 }; \ @@ -88,6 +247,16 @@ static inline void bpf_fuse_free_alloced(struct bpf_fus= e_args *fa) break; \ } \ \ + if (bpf_next =3D=3D BPF_FUSE_USER_PREFILTER) { \ + locked =3D fuse_lock_inode(inode); \ + res =3D fuse_prefilter_simple_request(fm, &fa); \ + fuse_unlock_inode(inode, locked); \ + if (res < 0) { \ + error =3D res; \ + break; \ + } \ + bpf_next =3D fa.ret; \ + } \ bpf_fuse_set_in_immutable(&fa); \ \ error =3D initialize_out(&fa, &feo, args); \ @@ -117,6 +286,16 @@ static inline void bpf_fuse_free_alloced(struct bpf_fu= se_args *fa) break; \ } \ \ + if (!(bpf_next =3D=3D BPF_FUSE_USER_POSTFILTER)) \ + break; \ + \ + locked =3D fuse_lock_inode(inode); \ + res =3D fuse_postfilter_simple_request(fm, &fa); \ + fuse_unlock_inode(inode, locked); \ + if (res < 0) { \ + error =3D res; \ + break; \ + } \ } while (false); \ \ if (initialized && handled) { \ diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index ad7d9d1e6da5..139f40b70228 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -521,6 +521,8 @@ ssize_t fuse_simple_request(struct fuse_mount *fm, stru= ct fuse_args *args) BUG_ON(args->out_numargs =3D=3D 0); ret =3D args->out_args[args->out_numargs - 1].size; } + if (args->is_filter && args->is_ext) + args->ret =3D req->out.h.error; fuse_put_request(req); =20 return ret; diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index b7bc8260a537..bea5f1698127 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -620,7 +620,7 @@ static int get_security_context(struct dentry *entry, u= mode_t mode, return err; } =20 -static void *extend_arg(struct fuse_in_arg *buf, u32 bytes) +void *extend_arg(struct fuse_in_arg *buf, u32 bytes) { void *p; u32 newlen =3D buf->size + bytes; @@ -640,7 +640,7 @@ static void *extend_arg(struct fuse_in_arg *buf, u32 by= tes) return p + newlen - bytes; } =20 -static u32 fuse_ext_size(size_t size) +u32 fuse_ext_size(size_t size) { return FUSE_REC_ALIGN(sizeof(struct fuse_ext_header) + size); } @@ -700,7 +700,7 @@ static int get_create_ext(struct fuse_args *args, return err; } =20 -static void free_ext_value(struct fuse_args *args) +void free_ext_value(struct fuse_args *args) { if (args->is_ext) kfree(args->in_args[args->ext_idx].value); diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 15962ab3b381..0504c136632d 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -304,6 +304,17 @@ struct fuse_page_desc { unsigned int offset; }; =20 +/* To deal with bpf pre and post filters in userspace calls, we must suppo= rt + * passing the inputs and outputs as inputs, and we must have enough space= in + * outputs to handle all of the inputs. Plus one more for extensions. + */ +#define FUSE_EXTENDED_MAX_ARGS_IN (FUSE_MAX_ARGS_IN + FUSE_MAX_ARGS_OUT + = 1) +#if FUSE_MAX_ARGS_IN > FUSE_MAX_ARGS_OUT +#define FUSE_EXTENDED_MAX_ARGS_OUT FUSE_MAX_ARGS_IN +#else +#define FUSE_EXTENDED_MAX_ARGS_OUT FUSE_MAX_ARGS_OUT +#endif + struct fuse_args { uint64_t nodeid; uint32_t opcode; @@ -322,10 +333,12 @@ struct fuse_args { bool page_replace:1; bool may_block:1; bool is_ext:1; + bool is_filter:1; bool is_lookup:1; bool via_ioctl:1; - struct fuse_in_arg in_args[3]; - struct fuse_arg out_args[2]; + uint32_t ret; + struct fuse_in_arg in_args[FUSE_EXTENDED_MAX_ARGS_IN]; + struct fuse_arg out_args[FUSE_EXTENDED_MAX_ARGS_OUT]; void (*end)(struct fuse_mount *fm, struct fuse_args *args, int error); }; =20 @@ -1165,6 +1178,22 @@ void fuse_request_end(struct fuse_req *req); void fuse_abort_conn(struct fuse_conn *fc); void fuse_wait_aborted(struct fuse_conn *fc); =20 +/** + * Allocated/Reallocate extended header information + * Returns pointer to start of most recent allocation + */ +void *extend_arg(struct fuse_in_arg *buf, u32 bytes); + +/** + * Returns adjusted size field for extensions + */ +u32 fuse_ext_size(size_t size); + +/** + * Free allocated extended header information + */ +void free_ext_value(struct fuse_args *args); + /** * Invalidate inode attributes */ diff --git a/include/linux/bpf_fuse.h b/include/linux/bpf_fuse.h index 2183a7a45c92..159b850e1b46 100644 --- a/include/linux/bpf_fuse.h +++ b/include/linux/bpf_fuse.h @@ -64,6 +64,7 @@ struct bpf_fuse_args { uint32_t in_numargs; uint32_t out_numargs; uint32_t flags; + uint32_t ret; struct bpf_fuse_arg in_args[FUSE_MAX_ARGS_IN]; struct bpf_fuse_arg out_args[FUSE_MAX_ARGS_OUT]; }; diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h index e779064f5fad..bbcda421ee8e 100644 --- a/include/uapi/linux/fuse.h +++ b/include/uapi/linux/fuse.h @@ -520,6 +520,7 @@ enum fuse_ext_type { /* Types 0..31 are reserved for fuse_secctx_header */ FUSE_MAX_NR_SECCTX =3D 31, FUSE_EXT_GROUPS =3D 32, + FUSE_ERROR_IN =3D 33, }; =20 enum fuse_opcode { --=20 2.40.0.634.g4ca3ef3211-goog From nobody Wed Dec 17 19:22:07 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 2C394C77B75 for ; Tue, 18 Apr 2023 01:44:38 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230464AbjDRBog (ORCPT ); Mon, 17 Apr 2023 21:44:36 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53888 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230332AbjDRBn3 (ORCPT ); Mon, 17 Apr 2023 21:43:29 -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 C64B68688 for ; Mon, 17 Apr 2023 18:42:07 -0700 (PDT) Received: by mail-yw1-x1149.google.com with SMTP id 00721157ae682-54f69fb5cafso190558287b3.12 for ; Mon, 17 Apr 2023 18:42:07 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20221208; t=1681782127; x=1684374127; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=ddXTLGLPlq5L3n42BI32GqoM9UbKxCD+81BzG53VpiQ=; b=0MA+38wAROiLJwnnmkOR/8zmb/14kIavQqPqw5eB3G6C7GmifVtBaTDn3Wsfybkp37 b6dSdIwLTyNCmW3wmimtWhAUVTIK1yom6ecNpdWCmuMN8VINN/r4G1jXgbOY4cu+cZ+a Pt2r2hp2yE4x1rZokntaJd0+V/IAOiTEqK0lvMYUT9NoXy+nusYQTLGzsc6+78Z99LtC SjL6n9ZideGEO833c88L+9rdxIqF491lV1zcvATg2XLJ7T64aoqdeqgUJwGQDDFLOtda 4vQypY8y9YjForGSQpK/s/rOXTF8rdgpZ+X4T737iBV7hJiGed/JtsKQjYrD8ZaDlk5T P0sw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681782127; x=1684374127; 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=ddXTLGLPlq5L3n42BI32GqoM9UbKxCD+81BzG53VpiQ=; b=aT9iSMHSKt48K5wvm51t3Ni8Fo+/QOwsmEGpaw7y7jL++DD8Fm1Zv5Pvcn3HEwYgRS kNdfMelb/A13Z6pXzRs1pbb5ntGL+TtI84tLCBXFp3j8Q8N5MCqdw+lmNLun3lGCLCMV mls2g+SMxVkNpA53JctcHkyOyXtB4G8hsxpb9aTCU6Ezv5W5Pa2+ZTRlaSr/brNYPipT hy6ElxOKKl3/itxj9tDNRDTbwFF1o1iwfgYpAXf5j7XgCLmJm+HLP8xbeXohOQlV5N0h 2004FGBEK/k0lHi6oMgrSsVJSCYF+iGgke+wMM3GuRPMfAT6WbSelN71krxHQ+ztkaKs iJWg== X-Gm-Message-State: AAQBX9dH6v0b+aduUItF9xpd0Lo3h3TKSF1J7kDRoCadNBeHwliIdbSz AbM7gxSsbPRwX6RA7pm45n4fgnW2IrE= X-Google-Smtp-Source: AKy350bwpQo+izJgr59DpjqCYtbKMquWrfrRxB2SDqJN3j5Kmuer+Tdqg9Aw5PoVXSidCg0l3wDXvhOOkII= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:e67a:98b0:942d:86aa]) (user=drosen job=sendgmr) by 2002:a81:4415:0:b0:54f:9718:1d39 with SMTP id r21-20020a814415000000b0054f97181d39mr10593880ywa.0.1681782127537; Mon, 17 Apr 2023 18:42:07 -0700 (PDT) Date: Mon, 17 Apr 2023 18:40:34 -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-35-drosen@google.com> Subject: [RFC PATCH v3 34/37] WIP: fuse-bpf: add error_out 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 Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" error_out field will allow differentiating between altering error code from bpf programs, and the bpf program returning an error. TODO Signed-off-by: Daniel Rosenberg --- include/linux/bpf_fuse.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/linux/bpf_fuse.h b/include/linux/bpf_fuse.h index 159b850e1b46..15646ba59c41 100644 --- a/include/linux/bpf_fuse.h +++ b/include/linux/bpf_fuse.h @@ -57,6 +57,7 @@ struct bpf_fuse_meta_info { uint64_t nodeid; uint32_t opcode; uint32_t error_in; + uint32_t error_out; // TODO: struct_op programs may set this to alter rep= orted error code }; =20 struct bpf_fuse_args { --=20 2.40.0.634.g4ca3ef3211-goog From nobody Wed Dec 17 19:22:07 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 DFF9CC77B75 for ; Tue, 18 Apr 2023 01:44:43 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231327AbjDRBom (ORCPT ); Mon, 17 Apr 2023 21:44:42 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53752 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231190AbjDRBnb (ORCPT ); Mon, 17 Apr 2023 21:43:31 -0400 Received: from mail-yw1-x114a.google.com (mail-yw1-x114a.google.com [IPv6:2607:f8b0:4864:20::114a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 2924B76B7 for ; Mon, 17 Apr 2023 18:42:10 -0700 (PDT) Received: by mail-yw1-x114a.google.com with SMTP id 00721157ae682-54fbc270950so116961477b3.8 for ; Mon, 17 Apr 2023 18:42:10 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20221208; t=1681782130; x=1684374130; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=sonv2s4tEj+40/A7AfoN8AqUe0dfHfI4UdTNmRzEFxw=; b=oBeVq5vPwvXmVXOF0/baapps028QmRMh+Vlm0GWkzmw+jgbsqFHQ9sBTAZd0JvZW7H scU0JaIRxGobib6u6o+5zkjMzh3ppHGop73In0Ks/95MOV9IySs+APkuaui0dDeA7Xs2 TNvxJEoPS5uUE3SKbFMDmqLPAzAN+EJZX72saESajrud3lyX+7DUAurdWqlQrzXD/2NP SeN8VkGxabld1EkHRBpg69tL/kTM+CsY5ugSdmrcY5bWD/9zwLIU70IlmO0rvJiKt9Ta x1P81M1rWJhi4F9P7KBAyNmIkaqe9wFJAvJZLcBnygwrhgMV8cacNucqUlI87UDqlhWR dDxw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681782130; x=1684374130; 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=sonv2s4tEj+40/A7AfoN8AqUe0dfHfI4UdTNmRzEFxw=; b=Oib1LLvSvq6TmyIa8usw7FqdWPFI7MN7+EQ5afdIDKEiUFVL9qPvi6Eu/lV9m04Tob QaIuscWbbLytlQjY2Ez4MRgsAqFumGNVas9ciEskXb+dayNDhJQ6MvX0/5flk86WHpup WfMFcyLQIEOcBw6C2oC9Hm/oWMfKVPLbzahBMeMl+U4w6Q+byWw3hhV+IehZpyj7YA8F yiQoel95y2JWFXCX2RAw14Pbv/AOM+87Dml3VG5jlz+8zWQLX+Kwuj82HOUE7qyaNg6v C2AS+7ZxP7e8VnrKoE3D5m2NYkJO5pVHKk46zsIctpYpH7bVgCGka2+Q2HJizjbA0D/B DBbw== X-Gm-Message-State: AAQBX9fuffNhoEegQE8B0egEZAxYZ8Am05ys6okagtM5IMpBK4AkBcbl iWQkiaREE2pFhIlX2ll1X7lMWsJrfBA= X-Google-Smtp-Source: AKy350atgbwvzcoS6D9krUGkQk5hMb9O8+MnqZi16WWjaHH6yiJdYPqnKXAdzvHYzUemaFFJFZgGkpCmwCw= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:e67a:98b0:942d:86aa]) (user=drosen job=sendgmr) by 2002:a25:d40f:0:b0:b8c:ad5:6b4e with SMTP id m15-20020a25d40f000000b00b8c0ad56b4emr10977247ybf.12.1681782129901; Mon, 17 Apr 2023 18:42:09 -0700 (PDT) Date: Mon, 17 Apr 2023 18:40:35 -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-36-drosen@google.com> Subject: [RFC PATCH v3 35/37] tools: Add FUSE, update bpf includes 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 Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Updates the bpf includes under tools, and adds fuse Signed-off-by: Daniel Rosenberg --- tools/include/uapi/linux/bpf.h | 12 + tools/include/uapi/linux/fuse.h | 1135 +++++++++++++++++++++++++++++++ 2 files changed, 1147 insertions(+) create mode 100644 tools/include/uapi/linux/fuse.h diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index 4b20a7269bee..6521c40875c7 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -7155,4 +7155,16 @@ struct bpf_iter_num { __u64 __opaque[1]; } __attribute__((aligned(8))); =20 +/* Return Codes for Fuse BPF struct_op 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 + +/* Op Code Filter values for BPF Programs */ +#define FUSE_OPCODE_FILTER 0x0ffff +#define FUSE_PREFILTER 0x10000 +#define FUSE_POSTFILTER 0x20000 + #endif /* _UAPI__LINUX_BPF_H__ */ diff --git a/tools/include/uapi/linux/fuse.h b/tools/include/uapi/linux/fus= e.h new file mode 100644 index 000000000000..72c2190a1b0a --- /dev/null +++ b/tools/include/uapi/linux/fuse.h @@ -0,0 +1,1135 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-2-Cl= ause) */ +/* + This file defines the kernel interface of FUSE + Copyright (C) 2001-2008 Miklos Szeredi + + This program can be distributed under the terms of the GNU GPL. + See the file COPYING. + + This -- and only this -- header file may also be distributed under + the terms of the BSD Licence as follows: + + Copyright (C) 2001-2007 Miklos Szeredi. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PUR= POSE + ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUEN= TIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, ST= RICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY = WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. +*/ + +/* + * This file defines the kernel interface of FUSE + * + * Protocol changelog: + * + * 7.1: + * - add the following messages: + * FUSE_SETATTR, FUSE_SYMLINK, FUSE_MKNOD, FUSE_MKDIR, FUSE_UNLINK, + * FUSE_RMDIR, FUSE_RENAME, FUSE_LINK, FUSE_OPEN, FUSE_READ, FUSE_WRI= TE, + * FUSE_RELEASE, FUSE_FSYNC, FUSE_FLUSH, FUSE_SETXATTR, FUSE_GETXATTR, + * FUSE_LISTXATTR, FUSE_REMOVEXATTR, FUSE_OPENDIR, FUSE_READDIR, + * FUSE_RELEASEDIR + * - add padding to messages to accommodate 32-bit servers on 64-bit kern= els + * + * 7.2: + * - add FOPEN_DIRECT_IO and FOPEN_KEEP_CACHE flags + * - add FUSE_FSYNCDIR message + * + * 7.3: + * - add FUSE_ACCESS message + * - add FUSE_CREATE message + * - add filehandle to fuse_setattr_in + * + * 7.4: + * - add frsize to fuse_kstatfs + * - clean up request size limit checking + * + * 7.5: + * - add flags and max_write to fuse_init_out + * + * 7.6: + * - add max_readahead to fuse_init_in and fuse_init_out + * + * 7.7: + * - add FUSE_INTERRUPT message + * - add POSIX file lock support + * + * 7.8: + * - add lock_owner and flags fields to fuse_release_in + * - add FUSE_BMAP message + * - add FUSE_DESTROY message + * + * 7.9: + * - new fuse_getattr_in input argument of GETATTR + * - add lk_flags in fuse_lk_in + * - add lock_owner field to fuse_setattr_in, fuse_read_in and fuse_write= _in + * - add blksize field to fuse_attr + * - add file flags field to fuse_read_in and fuse_write_in + * - Add ATIME_NOW and MTIME_NOW flags to fuse_setattr_in + * + * 7.10 + * - add nonseekable open flag + * + * 7.11 + * - add IOCTL message + * - add unsolicited notification support + * - add POLL message and NOTIFY_POLL notification + * + * 7.12 + * - add umask flag to input argument of create, mknod and mkdir + * - add notification messages for invalidation of inodes and + * directory entries + * + * 7.13 + * - make max number of background requests and congestion threshold + * tunables + * + * 7.14 + * - add splice support to fuse device + * + * 7.15 + * - add store notify + * - add retrieve notify + * + * 7.16 + * - add BATCH_FORGET request + * - FUSE_IOCTL_UNRESTRICTED shall now return with array of 'struct + * fuse_ioctl_iovec' instead of ambiguous 'struct iovec' + * - add FUSE_IOCTL_32BIT flag + * + * 7.17 + * - add FUSE_FLOCK_LOCKS and FUSE_RELEASE_FLOCK_UNLOCK + * + * 7.18 + * - add FUSE_IOCTL_DIR flag + * - add FUSE_NOTIFY_DELETE + * + * 7.19 + * - add FUSE_FALLOCATE + * + * 7.20 + * - add FUSE_AUTO_INVAL_DATA + * + * 7.21 + * - add FUSE_READDIRPLUS + * - send the requested events in POLL request + * + * 7.22 + * - add FUSE_ASYNC_DIO + * + * 7.23 + * - add FUSE_WRITEBACK_CACHE + * - add time_gran to fuse_init_out + * - add reserved space to fuse_init_out + * - add FATTR_CTIME + * - add ctime and ctimensec to fuse_setattr_in + * - add FUSE_RENAME2 request + * - add FUSE_NO_OPEN_SUPPORT flag + * + * 7.24 + * - add FUSE_LSEEK for SEEK_HOLE and SEEK_DATA support + * + * 7.25 + * - add FUSE_PARALLEL_DIROPS + * + * 7.26 + * - add FUSE_HANDLE_KILLPRIV + * - add FUSE_POSIX_ACL + * + * 7.27 + * - add FUSE_ABORT_ERROR + * + * 7.28 + * - add FUSE_COPY_FILE_RANGE + * - add FOPEN_CACHE_DIR + * - add FUSE_MAX_PAGES, add max_pages to init_out + * - add FUSE_CACHE_SYMLINKS + * + * 7.29 + * - add FUSE_NO_OPENDIR_SUPPORT flag + * + * 7.30 + * - add FUSE_EXPLICIT_INVAL_DATA + * - add FUSE_IOCTL_COMPAT_X32 + * + * 7.31 + * - add FUSE_WRITE_KILL_PRIV flag + * - add FUSE_SETUPMAPPING and FUSE_REMOVEMAPPING + * - add map_alignment to fuse_init_out, add FUSE_MAP_ALIGNMENT flag + * + * 7.32 + * - add flags to fuse_attr, add FUSE_ATTR_SUBMOUNT, add FUSE_SUBMOUNTS + * + * 7.33 + * - add FUSE_HANDLE_KILLPRIV_V2, FUSE_WRITE_KILL_SUIDGID, FATTR_KILL_SUI= DGID + * - add FUSE_OPEN_KILL_SUIDGID + * - extend fuse_setxattr_in, add FUSE_SETXATTR_EXT + * - add FUSE_SETXATTR_ACL_KILL_SGID + * + * 7.34 + * - add FUSE_SYNCFS + * + * 7.35 + * - add FOPEN_NOFLUSH + * + * 7.36 + * - extend fuse_init_in with reserved fields, add FUSE_INIT_EXT init flag + * - add flags2 to fuse_init_in and fuse_init_out + * - add FUSE_SECURITY_CTX init flag + * - add security context to create, mkdir, symlink, and mknod requests + * - add FUSE_HAS_INODE_DAX, FUSE_ATTR_DAX + * + * 7.37 + * - add FUSE_TMPFILE + * + * 7.38 + * - add FUSE_EXPIRE_ONLY flag to fuse_notify_inval_entry + * - add FOPEN_PARALLEL_DIRECT_WRITES + * - add total_extlen to fuse_in_header + * - add FUSE_MAX_NR_SECCTX + * - add extension header + * - add FUSE_EXT_GROUPS + * - add FUSE_CREATE_SUPP_GROUP + */ + +#ifndef _LINUX_FUSE_H +#define _LINUX_FUSE_H + +#ifdef __KERNEL__ +#include +#else +#include +#endif + +/* + * Version negotiation: + * + * Both the kernel and userspace send the version they support in the + * INIT request and reply respectively. + * + * If the major versions match then both shall use the smallest + * of the two minor versions for communication. + * + * If the kernel supports a larger major version, then userspace shall + * reply with the major version it supports, ignore the rest of the + * INIT message and expect a new INIT message from the kernel with a + * matching major version. + * + * If the library supports a larger major version, then it shall fall + * back to the major protocol version sent by the kernel for + * communication and reply with that major version (and an arbitrary + * supported minor version). + */ + +/** Version number of this interface */ +#define FUSE_KERNEL_VERSION 7 + +/** Minor version number of this interface */ +#define FUSE_KERNEL_MINOR_VERSION 38 + +/** The node ID of the root inode */ +#define FUSE_ROOT_ID 1 + +/* Make sure all structures are padded to 64bit boundary, so 32bit + userspace works under 64bit kernels */ + +struct fuse_attr { + uint64_t ino; + uint64_t size; + uint64_t blocks; + uint64_t atime; + uint64_t mtime; + uint64_t ctime; + uint32_t atimensec; + uint32_t mtimensec; + uint32_t ctimensec; + uint32_t mode; + uint32_t nlink; + uint32_t uid; + uint32_t gid; + uint32_t rdev; + uint32_t blksize; + uint32_t flags; +}; + +struct fuse_kstatfs { + uint64_t blocks; + uint64_t bfree; + uint64_t bavail; + uint64_t files; + uint64_t ffree; + uint32_t bsize; + uint32_t namelen; + uint32_t frsize; + uint32_t padding; + uint32_t spare[6]; +}; + +struct fuse_file_lock { + uint64_t start; + uint64_t end; + uint32_t type; + uint32_t pid; /* tgid */ +}; + +/** + * Bitmasks for fuse_setattr_in.valid + */ +#define FATTR_MODE (1 << 0) +#define FATTR_UID (1 << 1) +#define FATTR_GID (1 << 2) +#define FATTR_SIZE (1 << 3) +#define FATTR_ATIME (1 << 4) +#define FATTR_MTIME (1 << 5) +#define FATTR_FH (1 << 6) +#define FATTR_ATIME_NOW (1 << 7) +#define FATTR_MTIME_NOW (1 << 8) +#define FATTR_LOCKOWNER (1 << 9) +#define FATTR_CTIME (1 << 10) +#define FATTR_KILL_SUIDGID (1 << 11) + +/** + * Flags returned by the OPEN request + * + * FOPEN_DIRECT_IO: bypass page cache for this open file + * FOPEN_KEEP_CACHE: don't invalidate the data cache on open + * FOPEN_NONSEEKABLE: the file is not seekable + * FOPEN_CACHE_DIR: allow caching this directory + * FOPEN_STREAM: the file is stream-like (no file position at all) + * FOPEN_NOFLUSH: don't flush data cache on close (unless FUSE_WRITEBACK_C= ACHE) + * FOPEN_PARALLEL_DIRECT_WRITES: Allow concurrent direct writes on the sam= e inode + */ +#define FOPEN_DIRECT_IO (1 << 0) +#define FOPEN_KEEP_CACHE (1 << 1) +#define FOPEN_NONSEEKABLE (1 << 2) +#define FOPEN_CACHE_DIR (1 << 3) +#define FOPEN_STREAM (1 << 4) +#define FOPEN_NOFLUSH (1 << 5) +#define FOPEN_PARALLEL_DIRECT_WRITES (1 << 6) + +/** + * INIT request/reply flags + * + * FUSE_ASYNC_READ: asynchronous read requests + * FUSE_POSIX_LOCKS: remote locking for POSIX file locks + * FUSE_FILE_OPS: kernel sends file handle for fstat, etc... (not yet supp= orted) + * FUSE_ATOMIC_O_TRUNC: handles the O_TRUNC open flag in the filesystem + * FUSE_EXPORT_SUPPORT: filesystem handles lookups of "." and ".." + * FUSE_BIG_WRITES: filesystem can handle write size larger than 4kB + * FUSE_DONT_MASK: don't apply umask to file mode on create operations + * FUSE_SPLICE_WRITE: kernel supports splice write on the device + * FUSE_SPLICE_MOVE: kernel supports splice move on the device + * FUSE_SPLICE_READ: kernel supports splice read on the device + * FUSE_FLOCK_LOCKS: remote locking for BSD style file locks + * FUSE_HAS_IOCTL_DIR: kernel supports ioctl on directories + * FUSE_AUTO_INVAL_DATA: automatically invalidate cached pages + * FUSE_DO_READDIRPLUS: do READDIRPLUS (READDIR+LOOKUP in one) + * FUSE_READDIRPLUS_AUTO: adaptive readdirplus + * FUSE_ASYNC_DIO: asynchronous direct I/O submission + * FUSE_WRITEBACK_CACHE: use writeback cache for buffered writes + * FUSE_NO_OPEN_SUPPORT: kernel supports zero-message opens + * FUSE_PARALLEL_DIROPS: allow parallel lookups and readdir + * FUSE_HANDLE_KILLPRIV: fs handles killing suid/sgid/cap on write/chown/t= runc + * FUSE_POSIX_ACL: filesystem supports posix acls + * FUSE_ABORT_ERROR: reading the device after abort returns ECONNABORTED + * FUSE_MAX_PAGES: init_out.max_pages contains the max number of req pages + * FUSE_CACHE_SYMLINKS: cache READLINK responses + * FUSE_NO_OPENDIR_SUPPORT: kernel supports zero-message opendir + * FUSE_EXPLICIT_INVAL_DATA: only invalidate cached pages on explicit requ= est + * FUSE_MAP_ALIGNMENT: init_out.map_alignment contains log2(byte alignment= ) for + * foffset and moffset fields in struct + * fuse_setupmapping_out and fuse_removemapping_one. + * FUSE_SUBMOUNTS: kernel supports auto-mounting directory submounts + * FUSE_HANDLE_KILLPRIV_V2: fs kills suid/sgid/cap on write/chown/trunc. + * Upon write/truncate suid/sgid is only killed if caller + * does not have CAP_FSETID. Additionally upon + * write/truncate sgid is killed only if file has group + * execute permission. (Same as Linux VFS behavior). + * FUSE_SETXATTR_EXT: Server supports extended struct fuse_setxattr_in + * FUSE_INIT_EXT: extended fuse_init_in request + * FUSE_INIT_RESERVED: reserved, do not use + * FUSE_SECURITY_CTX: add security context to create, mkdir, symlink, and + * mknod + * FUSE_HAS_INODE_DAX: use per inode DAX + * FUSE_CREATE_SUPP_GROUP: add supplementary group info to create, mkdir, + * symlink and mknod (single group that matches parent) + */ +#define FUSE_ASYNC_READ (1 << 0) +#define FUSE_POSIX_LOCKS (1 << 1) +#define FUSE_FILE_OPS (1 << 2) +#define FUSE_ATOMIC_O_TRUNC (1 << 3) +#define FUSE_EXPORT_SUPPORT (1 << 4) +#define FUSE_BIG_WRITES (1 << 5) +#define FUSE_DONT_MASK (1 << 6) +#define FUSE_SPLICE_WRITE (1 << 7) +#define FUSE_SPLICE_MOVE (1 << 8) +#define FUSE_SPLICE_READ (1 << 9) +#define FUSE_FLOCK_LOCKS (1 << 10) +#define FUSE_HAS_IOCTL_DIR (1 << 11) +#define FUSE_AUTO_INVAL_DATA (1 << 12) +#define FUSE_DO_READDIRPLUS (1 << 13) +#define FUSE_READDIRPLUS_AUTO (1 << 14) +#define FUSE_ASYNC_DIO (1 << 15) +#define FUSE_WRITEBACK_CACHE (1 << 16) +#define FUSE_NO_OPEN_SUPPORT (1 << 17) +#define FUSE_PARALLEL_DIROPS (1 << 18) +#define FUSE_HANDLE_KILLPRIV (1 << 19) +#define FUSE_POSIX_ACL (1 << 20) +#define FUSE_ABORT_ERROR (1 << 21) +#define FUSE_MAX_PAGES (1 << 22) +#define FUSE_CACHE_SYMLINKS (1 << 23) +#define FUSE_NO_OPENDIR_SUPPORT (1 << 24) +#define FUSE_EXPLICIT_INVAL_DATA (1 << 25) +#define FUSE_MAP_ALIGNMENT (1 << 26) +#define FUSE_SUBMOUNTS (1 << 27) +#define FUSE_HANDLE_KILLPRIV_V2 (1 << 28) +#define FUSE_SETXATTR_EXT (1 << 29) +#define FUSE_INIT_EXT (1 << 30) +#define FUSE_INIT_RESERVED (1 << 31) +/* bits 32..63 get shifted down 32 bits into the flags2 field */ +#define FUSE_SECURITY_CTX (1ULL << 32) +#define FUSE_HAS_INODE_DAX (1ULL << 33) +#define FUSE_CREATE_SUPP_GROUP (1ULL << 34) + +/** + * CUSE INIT request/reply flags + * + * CUSE_UNRESTRICTED_IOCTL: use unrestricted ioctl + */ +#define CUSE_UNRESTRICTED_IOCTL (1 << 0) + +/** + * Release flags + */ +#define FUSE_RELEASE_FLUSH (1 << 0) +#define FUSE_RELEASE_FLOCK_UNLOCK (1 << 1) + +/** + * Getattr flags + */ +#define FUSE_GETATTR_FH (1 << 0) + +/** + * Lock flags + */ +#define FUSE_LK_FLOCK (1 << 0) + +/** + * WRITE flags + * + * FUSE_WRITE_CACHE: delayed write from page cache, file handle is guessed + * FUSE_WRITE_LOCKOWNER: lock_owner field is valid + * FUSE_WRITE_KILL_SUIDGID: kill suid and sgid bits + */ +#define FUSE_WRITE_CACHE (1 << 0) +#define FUSE_WRITE_LOCKOWNER (1 << 1) +#define FUSE_WRITE_KILL_SUIDGID (1 << 2) + +/* Obsolete alias; this flag implies killing suid/sgid only. */ +#define FUSE_WRITE_KILL_PRIV FUSE_WRITE_KILL_SUIDGID + +/** + * Read flags + */ +#define FUSE_READ_LOCKOWNER (1 << 1) + +/** + * Ioctl flags + * + * FUSE_IOCTL_COMPAT: 32bit compat ioctl on 64bit machine + * FUSE_IOCTL_UNRESTRICTED: not restricted to well-formed ioctls, retry al= lowed + * FUSE_IOCTL_RETRY: retry with new iovecs + * FUSE_IOCTL_32BIT: 32bit ioctl + * FUSE_IOCTL_DIR: is a directory + * FUSE_IOCTL_COMPAT_X32: x32 compat ioctl on 64bit machine (64bit time_t) + * + * FUSE_IOCTL_MAX_IOV: maximum of in_iovecs + out_iovecs + */ +#define FUSE_IOCTL_COMPAT (1 << 0) +#define FUSE_IOCTL_UNRESTRICTED (1 << 1) +#define FUSE_IOCTL_RETRY (1 << 2) +#define FUSE_IOCTL_32BIT (1 << 3) +#define FUSE_IOCTL_DIR (1 << 4) +#define FUSE_IOCTL_COMPAT_X32 (1 << 5) + +#define FUSE_IOCTL_MAX_IOV 256 + +/** + * Poll flags + * + * FUSE_POLL_SCHEDULE_NOTIFY: request poll notify + */ +#define FUSE_POLL_SCHEDULE_NOTIFY (1 << 0) + +/** + * Fsync flags + * + * FUSE_FSYNC_FDATASYNC: Sync data only, not metadata + */ +#define FUSE_FSYNC_FDATASYNC (1 << 0) + +/** + * fuse_attr flags + * + * FUSE_ATTR_SUBMOUNT: Object is a submount root + * FUSE_ATTR_DAX: Enable DAX for this file in per inode DAX mode + */ +#define FUSE_ATTR_SUBMOUNT (1 << 0) +#define FUSE_ATTR_DAX (1 << 1) + +/** + * Open flags + * FUSE_OPEN_KILL_SUIDGID: Kill suid and sgid if executable + */ +#define FUSE_OPEN_KILL_SUIDGID (1 << 0) + +/** + * setxattr flags + * FUSE_SETXATTR_ACL_KILL_SGID: Clear SGID when system.posix_acl_access is= set + */ +#define FUSE_SETXATTR_ACL_KILL_SGID (1 << 0) + +/** + * notify_inval_entry flags + * FUSE_EXPIRE_ONLY + */ +#define FUSE_EXPIRE_ONLY (1 << 0) + +/** + * extension type + * FUSE_MAX_NR_SECCTX: maximum value of &fuse_secctx_header.nr_secctx + * FUSE_EXT_GROUPS: &fuse_supp_groups extension + */ +enum fuse_ext_type { + /* Types 0..31 are reserved for fuse_secctx_header */ + FUSE_MAX_NR_SECCTX =3D 31, + FUSE_EXT_GROUPS =3D 32, + FUSE_ERROR_IN =3D 33, +}; + +enum fuse_opcode { + FUSE_LOOKUP =3D 1, + FUSE_FORGET =3D 2, /* no reply */ + FUSE_GETATTR =3D 3, + FUSE_SETATTR =3D 4, + FUSE_READLINK =3D 5, + FUSE_SYMLINK =3D 6, + FUSE_MKNOD =3D 8, + FUSE_MKDIR =3D 9, + FUSE_UNLINK =3D 10, + FUSE_RMDIR =3D 11, + FUSE_RENAME =3D 12, + FUSE_LINK =3D 13, + FUSE_OPEN =3D 14, + FUSE_READ =3D 15, + FUSE_WRITE =3D 16, + FUSE_STATFS =3D 17, + FUSE_RELEASE =3D 18, + FUSE_FSYNC =3D 20, + FUSE_SETXATTR =3D 21, + FUSE_GETXATTR =3D 22, + FUSE_LISTXATTR =3D 23, + FUSE_REMOVEXATTR =3D 24, + FUSE_FLUSH =3D 25, + FUSE_INIT =3D 26, + FUSE_OPENDIR =3D 27, + FUSE_READDIR =3D 28, + FUSE_RELEASEDIR =3D 29, + FUSE_FSYNCDIR =3D 30, + FUSE_GETLK =3D 31, + FUSE_SETLK =3D 32, + FUSE_SETLKW =3D 33, + FUSE_ACCESS =3D 34, + FUSE_CREATE =3D 35, + FUSE_INTERRUPT =3D 36, + FUSE_BMAP =3D 37, + FUSE_DESTROY =3D 38, + FUSE_IOCTL =3D 39, + FUSE_POLL =3D 40, + FUSE_NOTIFY_REPLY =3D 41, + FUSE_BATCH_FORGET =3D 42, + FUSE_FALLOCATE =3D 43, + FUSE_READDIRPLUS =3D 44, + FUSE_RENAME2 =3D 45, + FUSE_LSEEK =3D 46, + FUSE_COPY_FILE_RANGE =3D 47, + FUSE_SETUPMAPPING =3D 48, + FUSE_REMOVEMAPPING =3D 49, + FUSE_SYNCFS =3D 50, + FUSE_TMPFILE =3D 51, + + /* CUSE specific operations */ + CUSE_INIT =3D 4096, + + /* Reserved opcodes: helpful to detect structure endian-ness */ + CUSE_INIT_BSWAP_RESERVED =3D 1048576, /* CUSE_INIT << 8 */ + FUSE_INIT_BSWAP_RESERVED =3D 436207616, /* FUSE_INIT << 24 */ +}; + +enum fuse_notify_code { + FUSE_NOTIFY_POLL =3D 1, + FUSE_NOTIFY_INVAL_INODE =3D 2, + FUSE_NOTIFY_INVAL_ENTRY =3D 3, + FUSE_NOTIFY_STORE =3D 4, + FUSE_NOTIFY_RETRIEVE =3D 5, + FUSE_NOTIFY_DELETE =3D 6, + FUSE_NOTIFY_CODE_MAX, +}; + +/* The read buffer is required to be at least 8k, but may be much larger */ +#define FUSE_MIN_READ_BUFFER 8192 + +#define FUSE_COMPAT_ENTRY_OUT_SIZE 120 + +struct fuse_entry_out { + uint64_t nodeid; /* Inode ID */ + uint64_t generation; /* Inode generation: nodeid:gen must + be unique for the fs's lifetime */ + uint64_t entry_valid; /* Cache timeout for the name */ + uint64_t attr_valid; /* Cache timeout for the attributes */ + uint32_t entry_valid_nsec; + uint32_t attr_valid_nsec; + struct fuse_attr attr; +}; + +#define FUSE_BPF_MAX_ENTRIES 2 + +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]; + }; +}; + +struct fuse_forget_in { + uint64_t nlookup; +}; + +struct fuse_forget_one { + uint64_t nodeid; + uint64_t nlookup; +}; + +struct fuse_batch_forget_in { + uint32_t count; + uint32_t dummy; +}; + +struct fuse_getattr_in { + uint32_t getattr_flags; + uint32_t dummy; + uint64_t fh; +}; + +#define FUSE_COMPAT_ATTR_OUT_SIZE 96 + +struct fuse_attr_out { + uint64_t attr_valid; /* Cache timeout for the attributes */ + uint32_t attr_valid_nsec; + uint32_t dummy; + struct fuse_attr attr; +}; + +#define FUSE_COMPAT_MKNOD_IN_SIZE 8 + +struct fuse_mknod_in { + uint32_t mode; + uint32_t rdev; + uint32_t umask; + uint32_t padding; +}; + +struct fuse_mkdir_in { + uint32_t mode; + uint32_t umask; +}; + +struct fuse_rename_in { + uint64_t newdir; +}; + +struct fuse_rename2_in { + uint64_t newdir; + uint32_t flags; + uint32_t padding; +}; + +struct fuse_link_in { + uint64_t oldnodeid; +}; + +struct fuse_setattr_in { + uint32_t valid; + uint32_t padding; + uint64_t fh; + uint64_t size; + uint64_t lock_owner; + uint64_t atime; + uint64_t mtime; + uint64_t ctime; + uint32_t atimensec; + uint32_t mtimensec; + uint32_t ctimensec; + uint32_t mode; + uint32_t unused4; + uint32_t uid; + uint32_t gid; + uint32_t unused5; +}; + +struct fuse_open_in { + uint32_t flags; + uint32_t open_flags; /* FUSE_OPEN_... */ +}; + +struct fuse_create_in { + uint32_t flags; + uint32_t mode; + uint32_t umask; + uint32_t open_flags; /* FUSE_OPEN_... */ +}; + +struct fuse_open_out { + uint64_t fh; + uint32_t open_flags; + uint32_t padding; +}; + +struct fuse_release_in { + uint64_t fh; + uint32_t flags; + uint32_t release_flags; + uint64_t lock_owner; +}; + +struct fuse_flush_in { + uint64_t fh; + uint32_t unused; + uint32_t padding; + uint64_t lock_owner; +}; + +struct fuse_read_in { + uint64_t fh; + uint64_t offset; + uint32_t size; + uint32_t read_flags; + uint64_t lock_owner; + uint32_t flags; + uint32_t padding; +}; + +struct fuse_read_out { + uint64_t offset; + uint32_t again; + uint32_t padding; +}; + +// This is likely not what we want +struct fuse_read_iter_out { + uint64_t ret; +}; + +#define FUSE_COMPAT_WRITE_IN_SIZE 24 + +struct fuse_write_in { + uint64_t fh; + uint64_t offset; + uint32_t size; + uint32_t write_flags; + uint64_t lock_owner; + uint32_t flags; + uint32_t padding; +}; + +struct fuse_write_out { + uint32_t size; + uint32_t padding; +}; + +// This is likely not what we want +struct fuse_write_iter_out { + uint64_t ret; +}; + +#define FUSE_COMPAT_STATFS_SIZE 48 + +struct fuse_statfs_out { + struct fuse_kstatfs st; +}; + +struct fuse_fsync_in { + uint64_t fh; + uint32_t fsync_flags; + uint32_t padding; +}; + +#define FUSE_COMPAT_SETXATTR_IN_SIZE 8 + +struct fuse_setxattr_in { + uint32_t size; + uint32_t flags; + uint32_t setxattr_flags; + uint32_t padding; +}; + +struct fuse_getxattr_in { + uint32_t size; + uint32_t padding; +}; + +struct fuse_getxattr_out { + uint32_t size; + uint32_t padding; +}; + +struct fuse_lk_in { + uint64_t fh; + uint64_t owner; + struct fuse_file_lock lk; + uint32_t lk_flags; + uint32_t padding; +}; + +struct fuse_lk_out { + struct fuse_file_lock lk; +}; + +struct fuse_access_in { + uint32_t mask; + uint32_t padding; +}; + +struct fuse_init_in { + uint32_t major; + uint32_t minor; + uint32_t max_readahead; + uint32_t flags; + uint32_t flags2; + uint32_t unused[11]; +}; + +#define FUSE_COMPAT_INIT_OUT_SIZE 8 +#define FUSE_COMPAT_22_INIT_OUT_SIZE 24 + +struct fuse_init_out { + uint32_t major; + uint32_t minor; + uint32_t max_readahead; + uint32_t flags; + uint16_t max_background; + uint16_t congestion_threshold; + uint32_t max_write; + uint32_t time_gran; + uint16_t max_pages; + uint16_t map_alignment; + uint32_t flags2; + uint32_t unused[7]; +}; + +#define CUSE_INIT_INFO_MAX 4096 + +struct cuse_init_in { + uint32_t major; + uint32_t minor; + uint32_t unused; + uint32_t flags; +}; + +struct cuse_init_out { + uint32_t major; + uint32_t minor; + uint32_t unused; + uint32_t flags; + uint32_t max_read; + uint32_t max_write; + uint32_t dev_major; /* chardev major */ + uint32_t dev_minor; /* chardev minor */ + uint32_t spare[10]; +}; + +struct fuse_interrupt_in { + uint64_t unique; +}; + +struct fuse_bmap_in { + uint64_t block; + uint32_t blocksize; + uint32_t padding; +}; + +struct fuse_bmap_out { + uint64_t block; +}; + +struct fuse_ioctl_in { + uint64_t fh; + uint32_t flags; + uint32_t cmd; + uint64_t arg; + uint32_t in_size; + uint32_t out_size; +}; + +struct fuse_ioctl_iovec { + uint64_t base; + uint64_t len; +}; + +struct fuse_ioctl_out { + int32_t result; + uint32_t flags; + uint32_t in_iovs; + uint32_t out_iovs; +}; + +struct fuse_poll_in { + uint64_t fh; + uint64_t kh; + uint32_t flags; + uint32_t events; +}; + +struct fuse_poll_out { + uint32_t revents; + uint32_t padding; +}; + +struct fuse_notify_poll_wakeup_out { + uint64_t kh; +}; + +struct fuse_fallocate_in { + uint64_t fh; + uint64_t offset; + uint64_t length; + uint32_t mode; + uint32_t padding; +}; + +struct fuse_in_header { + uint32_t len; + uint32_t opcode; + uint64_t unique; + uint64_t nodeid; + uint32_t uid; + uint32_t gid; + uint32_t pid; + uint16_t total_extlen; /* length of extensions in 8byte units */ + uint16_t padding; + //uint32_t error_in; uh oh +}; + +struct fuse_out_header { + uint32_t len; + int32_t error; + uint64_t unique; +}; + +struct fuse_dirent { + uint64_t ino; + uint64_t off; + uint32_t namelen; + uint32_t type; + char name[]; +}; + +/* Align variable length records to 64bit boundary */ +#define FUSE_REC_ALIGN(x) \ + (((x) + sizeof(uint64_t) - 1) & ~(sizeof(uint64_t) - 1)) + +#define FUSE_NAME_OFFSET offsetof(struct fuse_dirent, name) +#define FUSE_DIRENT_ALIGN(x) FUSE_REC_ALIGN(x) +#define FUSE_DIRENT_SIZE(d) \ + FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET + (d)->namelen) + +struct fuse_direntplus { + struct fuse_entry_out entry_out; + struct fuse_dirent dirent; +}; + +#define FUSE_NAME_OFFSET_DIRENTPLUS \ + offsetof(struct fuse_direntplus, dirent.name) +#define FUSE_DIRENTPLUS_SIZE(d) \ + FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET_DIRENTPLUS + (d)->dirent.namelen) + +struct fuse_notify_inval_inode_out { + uint64_t ino; + int64_t off; + int64_t len; +}; + +struct fuse_notify_inval_entry_out { + uint64_t parent; + uint32_t namelen; + uint32_t flags; +}; + +struct fuse_notify_delete_out { + uint64_t parent; + uint64_t child; + uint32_t namelen; + uint32_t padding; +}; + +struct fuse_notify_store_out { + uint64_t nodeid; + uint64_t offset; + uint32_t size; + uint32_t padding; +}; + +struct fuse_notify_retrieve_out { + uint64_t notify_unique; + uint64_t nodeid; + uint64_t offset; + uint32_t size; + uint32_t padding; +}; + +/* Matches the size of fuse_write_in */ +struct fuse_notify_retrieve_in { + uint64_t dummy1; + uint64_t offset; + uint32_t size; + uint32_t dummy2; + uint64_t dummy3; + uint64_t dummy4; +}; + +/* Device ioctls: */ +#define FUSE_DEV_IOC_MAGIC 229 +#define FUSE_DEV_IOC_CLONE _IOR(FUSE_DEV_IOC_MAGIC, 0, uint32_t) +#define FUSE_DEV_IOC_BPF_RESPONSE(N) _IOW(FUSE_DEV_IOC_MAGIC, 125, char[N]) + +struct fuse_lseek_in { + uint64_t fh; + uint64_t offset; + uint32_t whence; + uint32_t padding; +}; + +struct fuse_lseek_out { + uint64_t offset; +}; + +struct fuse_copy_file_range_in { + uint64_t fh_in; + uint64_t off_in; + uint64_t nodeid_out; + uint64_t fh_out; + uint64_t off_out; + uint64_t len; + uint64_t flags; +}; + +#define FUSE_SETUPMAPPING_FLAG_WRITE (1ull << 0) +#define FUSE_SETUPMAPPING_FLAG_READ (1ull << 1) +struct fuse_setupmapping_in { + /* An already open handle */ + uint64_t fh; + /* Offset into the file to start the mapping */ + uint64_t foffset; + /* Length of mapping required */ + uint64_t len; + /* Flags, FUSE_SETUPMAPPING_FLAG_* */ + uint64_t flags; + /* Offset in Memory Window */ + uint64_t moffset; +}; + +struct fuse_removemapping_in { + /* number of fuse_removemapping_one follows */ + uint32_t count; +}; + +struct fuse_removemapping_one { + /* Offset into the dax window start the unmapping */ + uint64_t moffset; + /* Length of mapping required */ + uint64_t len; +}; + +#define FUSE_REMOVEMAPPING_MAX_ENTRY \ + (PAGE_SIZE / sizeof(struct fuse_removemapping_one)) + +struct fuse_syncfs_in { + uint64_t padding; +}; + +/* + * For each security context, send fuse_secctx with size of security conte= xt + * fuse_secctx will be followed by security context name and this in turn + * will be followed by actual context label. + * fuse_secctx, name, context + */ +struct fuse_secctx { + uint32_t size; + uint32_t padding; +}; + +/* + * Contains the information about how many fuse_secctx structures are being + * sent and what's the total size of all security contexts (including + * size of fuse_secctx_header). + * + */ +struct fuse_secctx_header { + uint32_t size; + uint32_t nr_secctx; +}; + +/** + * struct fuse_ext_header - extension header + * @size: total size of this extension including this header + * @type: type of extension + * + * This is made compatible with fuse_secctx_header by using type values > + * FUSE_MAX_NR_SECCTX + */ +struct fuse_ext_header { + uint32_t size; + uint32_t type; +}; + +/** + * struct fuse_supp_groups - Supplementary group extension + * @nr_groups: number of supplementary groups + * @groups: flexible array of group IDs + */ +struct fuse_supp_groups { + uint32_t nr_groups; + uint32_t groups[]; +}; + +#endif /* _LINUX_FUSE_H */ --=20 2.40.0.634.g4ca3ef3211-goog From nobody Wed Dec 17 19:22:07 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 From nobody Wed Dec 17 19:22:07 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 F3E8AC77B76 for ; Tue, 18 Apr 2023 01:46:17 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229755AbjDRBqQ (ORCPT ); Mon, 17 Apr 2023 21:46:16 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53752 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229635AbjDRBp3 (ORCPT ); Mon, 17 Apr 2023 21:45:29 -0400 Received: from mail-yb1-xb4a.google.com (mail-yb1-xb4a.google.com [IPv6:2607:f8b0:4864:20::b4a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 120E16A6E for ; Mon, 17 Apr 2023 18:42:49 -0700 (PDT) Received: by mail-yb1-xb4a.google.com with SMTP id t66-20020a254645000000b00b74680a7904so25430893yba.15 for ; Mon, 17 Apr 2023 18:42:49 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20221208; t=1681782135; x=1684374135; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=qi1Po8S34Kt7QbElMTr2iZrr48NSEyGebr4FBWlSvn0=; b=MSsv99K1wdyZt0ehocN3dFGnyuMghLajhZABLr8jg/8pE9UjNyOpDMEfWyojc7bZxL aIdIEzKOVqQM6eAiGt15nHbmio8M/8pFdNUqUKZ2eWJVCzjDh32WmTdHJZ10f5/POWF2 pAAEo+YIza9Rwrb6KL2LEkS8NvJFgrwQvEqP9WD4eGIxb87r1fEPbOg58UfyeUZSKVSH sLjYM7/L0kSRcgdwJcbirerE7VCZnsIM4Z5WtlxXaqbbgNOGaNOEVE53+XYyUpwoREZh Y35+i/5dEgBZjBXFGcHvoTytJSDXWndNAxfmRes5d05cuW6DV5UOQZPI/AnztxHlgPeK mLsw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1681782135; x=1684374135; 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=qi1Po8S34Kt7QbElMTr2iZrr48NSEyGebr4FBWlSvn0=; b=KpJZ1c188vvDAmomHlsz3mDLhWeyfj8QxJ7NKg3O/DNQw+te9hu0YwQnncPbkT9Jsp Nw4jOF6vsFbNKiHULsy1DWYNpHXEPaY/DpOYvE7+0vD/w5ojjAo8s1y5hp8+Gs8xn9dv X0YaalOqpAgLyWTuh6Rtbm49arJtKQg2gixwWO6n3vGTFKS/UcaxcQeWX3sGBuW9OcC8 uC1B1gxGG41Njo0M9KJ8J2Rckcmnfy4Ci806E2IMk7V6lHINwbOFNIn0mIZF4C9CZF/c x/EJ5iRhpXXwQGPC+91IiTscyQ2XYfQlCjzsJbGh6NxFcI9mJWghGWkjg81amLkdLXSO hvhg== X-Gm-Message-State: AAQBX9cdpTRryVIUe05d1Cxpwa+ETVmqwMxw4YgCbKtYEj3DFL1Jh7Gl w4GR2Qe0A9dV30mxx9NCCFHUewpZtgI= X-Google-Smtp-Source: AKy350abrQ11ASojcePeUYEdiv/KTIkMP9GG/A+eTvQ5myvKQeYGNjXLXkAgj+/aiYds5ZlRZX6RdyULoaA= X-Received: from drosen.mtv.corp.google.com ([2620:15c:211:201:e67a:98b0:942d:86aa]) (user=drosen job=sendgmr) by 2002:a25:6c41:0:b0:b8f:1d2c:243f with SMTP id h62-20020a256c41000000b00b8f1d2c243fmr8371770ybc.1.1681782134824; Mon, 17 Apr 2023 18:42:14 -0700 (PDT) Date: Mon, 17 Apr 2023 18:40:37 -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-38-drosen@google.com> Subject: [RFC PATCH v3 37/37] fuse: Provide easy way to test fuse struct_op call 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 Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" This is useful for quickly testing a struct_op program. I've been using this set up to test verifier changes. I'll eventually move those sorts of tests to bpf selftests Signed-off-by: Daniel Rosenberg --- fs/fuse/inode.c | 70 ++ .../selftests/filesystems/fuse/Makefile | 1 + .../filesystems/fuse/struct_op_test.bpf.c | 642 ++++++++++++++++++ 3 files changed, 713 insertions(+) create mode 100644 tools/testing/selftests/filesystems/fuse/struct_op_test= .bpf.c diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 7fd79efbdac1..d80c7282c91c 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -2071,16 +2071,83 @@ static void fuse_fs_cleanup(void) =20 static struct kobject *fuse_kobj; =20 +static char struct_op_name[BPF_FUSE_NAME_MAX]; +static struct fuse_ops *fop =3D NULL; + +static ssize_t struct_op_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + size_t max =3D count; + + if (max > BPF_FUSE_NAME_MAX) max =3D BPF_FUSE_NAME_MAX; + strncpy(struct_op_name, buf, max); + if (struct_op_name[max-1] =3D=3D '\n') + struct_op_name[max-1] =3D 0; + put_fuse_ops(fop); + fop =3D find_fuse_ops(struct_op_name); + if (!fop) + printk("No struct op named %s found", struct_op_name); + + return count; +} + +static ssize_t struct_op_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct fuse_ops *op; + uint32_t result =3D 0; + struct bpf_fuse_meta_info meta; + struct fuse_mkdir_in in; + struct fuse_buffer name; + char name_buff[10] =3D "test"; + + name.data =3D &name_buff[0]; + name.flags =3D BPF_FUSE_VARIABLE_SIZE; + name.max_size =3D 10; + name.size =3D 5; + + op =3D fop; + if (!op) { + printk("Could not find fuse_op for %s", struct_op_name); + return 0; + } + + if (op->mkdir_prefilter) + result =3D op->mkdir_prefilter(&meta, &in, &name); + else + printk("No func!!"); + + printk("in->mode:%d, name:%s result:%d", in.mode, (char *)name.data, resu= lt); + return sprintf(buf, "%d dyn:%s\n", result, (char *)name.data); +} + +static struct kobj_attribute test_attr =3D __ATTR_RW(struct_op); + +static struct attribute *test_attrs[] =3D { + &test_attr.attr, + NULL, +}; + +static const struct attribute_group test_attr_group =3D { + .attrs =3D test_attrs, +}; + static int fuse_sysfs_init(void) { int err; =20 + memset(struct_op_name, 0, BPF_FUSE_NAME_MAX); fuse_kobj =3D kobject_create_and_add("fuse", fs_kobj); if (!fuse_kobj) { err =3D -ENOMEM; goto out_err; } =20 + err =3D sysfs_create_group(fuse_kobj, &test_attr_group); + if (err) + goto tmp; + err =3D sysfs_create_mount_point(fuse_kobj, "connections"); if (err) goto out_fuse_unregister; @@ -2089,6 +2156,8 @@ static int fuse_sysfs_init(void) =20 out_fuse_unregister: kobject_put(fuse_kobj); +tmp: + sysfs_remove_group(fuse_kobj, &test_attr_group); out_err: return err; } @@ -2096,6 +2165,7 @@ static int fuse_sysfs_init(void) static void fuse_sysfs_cleanup(void) { sysfs_remove_mount_point(fuse_kobj, "connections"); + sysfs_remove_group(fuse_kobj, &test_attr_group); kobject_put(fuse_kobj); } =20 diff --git a/tools/testing/selftests/filesystems/fuse/Makefile b/tools/test= ing/selftests/filesystems/fuse/Makefile index b2df4dec0651..ff28859f3268 100644 --- a/tools/testing/selftests/filesystems/fuse/Makefile +++ b/tools/testing/selftests/filesystems/fuse/Makefile @@ -52,6 +52,7 @@ SELFTESTS:=3D$(TOOLSDIR)/testing/selftests/ LDLIBS :=3D -lpthread -lelf -lz TEST_GEN_PROGS :=3D fuse_test fuse_daemon TEST_GEN_FILES :=3D \ + struct_op_test.bpf.o \ test.skel.h \ fd.sh \ =20 diff --git a/tools/testing/selftests/filesystems/fuse/struct_op_test.bpf.c = b/tools/testing/selftests/filesystems/fuse/struct_op_test.bpf.c new file mode 100644 index 000000000000..2cb178d2fa0c --- /dev/null +++ b/tools/testing/selftests/filesystems/fuse/struct_op_test.bpf.c @@ -0,0 +1,642 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +// Copyright (c) 2021 Google LLC + +#include "vmlinux.h" +//#include +#include +#include +//#include +#include +#include +#include "bpf_common.h" + +char _license[] SEC("license") =3D "GPL"; + +#define BPF_STRUCT_OPS(type, name, args...) \ +SEC("struct_ops/"#name) \ +type BPF_PROG(name, ##args) + +/* +struct test_struct { + uint32_t a; + uint32_t b; +}; + + +*/ +//struct fuse_buffer; +#define BPF_FUSE_CONTINUE 0 +/*struct fuse_ops { + uint32_t (*test_func)(void); + uint32_t (*test_func2)(struct test_struct *a); + uint32_t (*test_func3)(struct fuse_name *ptr); + //u32 (*open_prefilter)(struct bpf_fuse_hidden_info meh, struct bpf_fuse_= meta_info header, struct fuse_open_in foi); + //u32 (*open_postfilter)(struct bpf_fuse_hidden_info meh, struct bpf_fuse= _meta_info header, const struct fuse_open_in foi, struct fuse_open_out foo); + char name[BPF_FUSE_NAME_MAX]; +}; +*/ +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 struct bpf_key *bpf_lookup_user_key(__u32 serial, __u64 flags) __= ksym; +//extern struct bpf_key *bpf_lookup_system_key(__u64 id) __ksym; +//extern void bpf_key_put(struct bpf_key *key) __ksym; +//extern int bpf_verify_pkcs7_signature(struct bpf_dynptr *data_ptr, +// struct bpf_dynptr *sig_ptr, +// struct bpf_key *trusted_keyring) __ksym; + +BPF_STRUCT_OPS(uint32_t, test_func, const struct bpf_fuse_meta_info *meta, + struct fuse_mkdir_in *in, struct fuse_buffer *name) +{ + int res =3D 0; + struct bpf_dynptr name_ptr; + char *name_buf; + //char dummy[7] =3D {}; + + bpf_fuse_get_ro_dynptr(name, &name_ptr); + name_buf =3D bpf_dynptr_slice(&name_ptr, 0, NULL, 4); + bpf_printk("Hello test print"); + if (!name_buf) + return -ENOMEM; + if (!bpf_strncmp(name_buf, 4, "test")) + return 42;=09 + + //if (bpf_fuse_namecmp(name, "test", 4) =3D=3D 0) + // return 42; + + return res; +} + +SEC(".struct_ops") +struct fuse_ops test_ops =3D { + .mkdir_prefilter =3D (void *)test_func, + .name =3D "test", +}; + +BPF_STRUCT_OPS(uint32_t, open_prefilter, const struct bpf_fuse_meta_info *= meta, + struct fuse_open_in *in) +{ + bpf_printk("open_prefilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, 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_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, opendir_prefilter, const struct bpf_fuse_meta_inf= o *meta, + struct fuse_open_in *in) +{ + bpf_printk("opendir_prefilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, opendir_postfilter, const struct bpf_fuse_meta_in= fo *meta, + const struct fuse_open_in *in, + struct fuse_open_out *out) +{ + bpf_printk("opendir_postfilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, create_open_prefilter, const struct bpf_fuse_meta= _info *meta, + struct fuse_create_in *in, struct fuse_buffer *name) +{ + bpf_printk("create_open_prefilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, create_open_postfilter, const struct bpf_fuse_met= a_info *meta, + const struct fuse_create_in *in, const struct fuse_buffer *name, + struct fuse_entry_out *entry_out, struct fuse_open_out *out) +{ + bpf_printk("create_open_postfilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, release_prefilter, const struct bpf_fuse_meta_inf= o *meta, + struct fuse_release_in *in) +{ + bpf_printk("release_prefilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, release_postfilter, const struct bpf_fuse_meta_in= fo *meta, + const struct fuse_release_in *in) +{ + bpf_printk("release_postfilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, releasedir_prefilter, const struct bpf_fuse_meta_= info *meta, + struct fuse_release_in *in) +{ + bpf_printk("releasedir_prefilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, releasedir_postfilter, const struct bpf_fuse_meta= _info *meta, + const struct fuse_release_in *in) +{ + bpf_printk("releasedir_postfilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, flush_prefilter, const struct bpf_fuse_meta_info = *meta, + struct fuse_flush_in *in) +{ + bpf_printk("flush_prefilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, flush_postfilter, const struct bpf_fuse_meta_info= *meta, + const struct fuse_flush_in *in) +{ + bpf_printk("flush_postfilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, lseek_prefilter, const struct bpf_fuse_meta_info = *meta, + struct fuse_lseek_in *in) +{ + bpf_printk("lseek_prefilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, lseek_postfilter, const struct bpf_fuse_meta_info= *meta, + const struct fuse_lseek_in *in, + struct fuse_lseek_out *out) +{ + bpf_printk("lseek_postfilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, copy_file_range_prefilter, const struct bpf_fuse_= meta_info *meta, + struct fuse_copy_file_range_in *in) +{ + bpf_printk("copy_file_range_prefilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, copy_file_range_postfilter, const struct bpf_fuse= _meta_info *meta, + const struct fuse_copy_file_range_in *in, + struct fuse_write_out *out) +{ + bpf_printk("copy_file_range_postfilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, fsync_prefilter, const struct bpf_fuse_meta_info = *meta, + struct fuse_fsync_in *in) +{ + bpf_printk("fsync_prefilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, fsync_postfilter, const struct bpf_fuse_meta_info= *meta, + const struct fuse_fsync_in *in) +{ + bpf_printk("fsync_postfilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, dir_fsync_prefilter, const struct bpf_fuse_meta_i= nfo *meta, + struct fuse_fsync_in *in) +{ + bpf_printk("dir_fsync_prefilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, dir_fsync_postfilter, const struct bpf_fuse_meta_= info *meta, + const struct fuse_fsync_in *in) +{ + bpf_printk("dir_fsync_postfilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, getxattr_prefilter, const struct bpf_fuse_meta_in= fo *meta, + struct fuse_getxattr_in *in, struct fuse_buffer *name) +{ + bpf_printk("getxattr_prefilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, getxattr_postfilter, const struct bpf_fuse_meta_i= nfo *meta, + const struct fuse_getxattr_in *in, const struct fuse_buffer *name, + struct fuse_buffer *value, struct fuse_getxattr_out *out) +{ + bpf_printk("getxattr_postfilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, listxattr_prefilter, const struct bpf_fuse_meta_i= nfo *meta, + struct fuse_getxattr_in *in) +{ + bpf_printk("listxattr_prefilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, listxattr_postfilter, const struct bpf_fuse_meta_= info *meta, + const struct fuse_getxattr_in *in, + struct fuse_buffer *value, struct fuse_getxattr_out *out) +{ + bpf_printk("listxattr_postfilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, setxattr_prefilter, const struct bpf_fuse_meta_in= fo *meta, + struct fuse_setxattr_in *in, struct fuse_buffer *name, + struct fuse_buffer *value) +{ + bpf_printk("setxattr_prefilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, setxattr_postfilter, const struct bpf_fuse_meta_i= nfo *meta, + const struct fuse_setxattr_in *in, const struct fuse_buffer *name, + const struct fuse_buffer *value) +{ + bpf_printk("setxattr_postfilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, removexattr_prefilter, const struct bpf_fuse_meta= _info *meta, + struct fuse_buffer *name) +{ + bpf_printk("removexattr_prefilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, removexattr_postfilter, const struct bpf_fuse_met= a_info *meta, + const struct fuse_buffer *name) +{ + bpf_printk("removexattr_postfilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, read_iter_prefilter, const struct bpf_fuse_meta_i= nfo *meta, + struct fuse_read_in *in) +{ + bpf_printk("read_iter_prefilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, read_iter_postfilter, const struct bpf_fuse_meta_= info *meta, + const struct fuse_read_in *in, + struct fuse_read_iter_out *out) +{ + bpf_printk("read_iter_postfilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, write_iter_prefilter, const struct bpf_fuse_meta_= info *meta, + struct fuse_write_in *in) +{ + bpf_printk("write_iter_prefilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, write_iter_postfilter, const struct bpf_fuse_meta= _info *meta, + const struct fuse_write_in *in, + struct fuse_write_iter_out *out) +{ + bpf_printk("write_iter_postfilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, file_fallocate_prefilter, const struct bpf_fuse_m= eta_info *meta, + struct fuse_fallocate_in *in) +{ + bpf_printk("file_fallocate_prefilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, file_fallocate_postfilter, const struct bpf_fuse_= meta_info *meta, + const struct fuse_fallocate_in *in) +{ + bpf_printk("file_fallocate_postfilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, lookup_prefilter, const struct bpf_fuse_meta_info= *meta, + struct fuse_buffer *name) +{ + bpf_printk("lookup_prefilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, lookup_postfilter, const struct bpf_fuse_meta_inf= o *meta, + const struct fuse_buffer *name, + struct fuse_entry_out *out, struct fuse_buffer *entries) +{ + bpf_printk("lookup_postfilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, mknod_prefilter, const struct bpf_fuse_meta_info = *meta, + struct fuse_mknod_in *in, struct fuse_buffer *name) +{ + bpf_printk("mknod_prefilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, mknod_postfilter, const struct bpf_fuse_meta_info= *meta, + const struct fuse_mknod_in *in, const struct fuse_buffer *name) +{ + bpf_printk("mknod_postfilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, mkdir_prefilter, const struct bpf_fuse_meta_info = *meta, + struct fuse_mkdir_in *in, struct fuse_buffer *name) +{ + bpf_printk("mkdir_prefilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, mkdir_postfilter, const struct bpf_fuse_meta_info= *meta, + const struct fuse_mkdir_in *in, const struct fuse_buffer *name) +{ + bpf_printk("mkdir_postfilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, rmdir_prefilter, const struct bpf_fuse_meta_info = *meta, + struct fuse_buffer *name) +{ + bpf_printk("rmdir_prefilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, rmdir_postfilter, const struct bpf_fuse_meta_info= *meta, + const struct fuse_buffer *name) +{ + bpf_printk("rmdir_postfilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, rename2_prefilter, const struct bpf_fuse_meta_inf= o *meta, + struct fuse_rename2_in *in, struct fuse_buffer *old_name, + struct fuse_buffer *new_name) +{ + bpf_printk("rename2_prefilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, rename2_postfilter, const struct bpf_fuse_meta_in= fo *meta, + const struct fuse_rename2_in *in, const struct fuse_buffer *old_name, + const struct fuse_buffer *new_name) +{ + bpf_printk("rename2_postfilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, rename_prefilter, const struct bpf_fuse_meta_info= *meta, + struct fuse_rename_in *in, struct fuse_buffer *old_name, + struct fuse_buffer *new_name) +{ + bpf_printk("rename_prefilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, rename_postfilter, const struct bpf_fuse_meta_inf= o *meta, + const struct fuse_rename_in *in, const struct fuse_buffer *old_name, + const struct fuse_buffer *new_name) +{ + bpf_printk("rename_postfilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, unlink_prefilter, const struct bpf_fuse_meta_info= *meta, + struct fuse_buffer *name) +{ + bpf_printk("unlink_prefilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, unlink_postfilter, const struct bpf_fuse_meta_inf= o *meta, + const struct fuse_buffer *name) +{ + bpf_printk("unlink_postfilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, link_prefilter, const struct bpf_fuse_meta_info *= meta, + struct fuse_link_in *in, struct fuse_buffer *name) +{ + bpf_printk("link_prefilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, link_postfilter, const struct bpf_fuse_meta_info = *meta, + const struct fuse_link_in *in, const struct fuse_buffer *name) +{ + bpf_printk("link_postfilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, getattr_prefilter, const struct bpf_fuse_meta_inf= o *meta, + struct fuse_getattr_in *in) +{ + bpf_printk("getattr_prefilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, getattr_postfilter, const struct bpf_fuse_meta_in= fo *meta, + const struct fuse_getattr_in *in, + struct fuse_attr_out *out) +{ + bpf_printk("getattr_postfilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, setattr_prefilter, const struct bpf_fuse_meta_inf= o *meta, + struct fuse_setattr_in *in) +{ + bpf_printk("setattr_prefilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, setattr_postfilter, const struct bpf_fuse_meta_in= fo *meta, + const struct fuse_setattr_in *in, + struct fuse_attr_out *out) +{ + bpf_printk("setattr_postfilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, statfs_prefilter, const struct bpf_fuse_meta_info= *meta) +{ + bpf_printk("statfs_prefilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, statfs_postfilter, const struct bpf_fuse_meta_inf= o *meta, + struct fuse_statfs_out *out) +{ + bpf_printk("statfs_postfilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, get_link_prefilter, const struct bpf_fuse_meta_in= fo *meta, + struct fuse_buffer *name) +{ + bpf_printk("get_link_prefilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, get_link_postfilter, const struct bpf_fuse_meta_i= nfo *meta, + const struct fuse_buffer *name) +{ + bpf_printk("get_link_postfilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, symlink_prefilter, const struct bpf_fuse_meta_inf= o *meta, + struct fuse_buffer *name, struct fuse_buffer *path) +{ + bpf_printk("symlink_prefilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, symlink_postfilter, const struct bpf_fuse_meta_in= fo *meta, + const struct fuse_buffer *name, const struct fuse_buffer *path) +{ + bpf_printk("symlink_postfilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, readdir_prefilter, const struct bpf_fuse_meta_inf= o *meta, + struct fuse_read_in *in) +{ + bpf_printk("readdir_prefilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, readdir_postfilter, const struct bpf_fuse_meta_in= fo *meta, + const struct fuse_read_in *in, + struct fuse_read_out *out, struct fuse_buffer *buffer) +{ + bpf_printk("readdir_postfilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, access_prefilter, const struct bpf_fuse_meta_info= *meta, + struct fuse_access_in *in) +{ + bpf_printk("access_prefilter"); + return BPF_FUSE_CONTINUE; +} + +BPF_STRUCT_OPS(uint32_t, access_postfilter, const struct bpf_fuse_meta_inf= o *meta, + const struct fuse_access_in *in) +{ + bpf_printk("access_postfilter"); + return BPF_FUSE_CONTINUE; +} + +SEC(".struct_ops") +struct fuse_ops trace_ops =3D { + .open_prefilter =3D (void *)open_prefilter, + .open_postfilter =3D (void *)open_postfilter, + + .opendir_prefilter =3D (void *)opendir_prefilter, + .opendir_postfilter =3D (void *)opendir_postfilter, + + .create_open_prefilter =3D (void *)create_open_prefilter, + .create_open_postfilter =3D (void *)create_open_postfilter, + + .release_prefilter =3D (void *)release_prefilter, + .release_postfilter =3D (void *)release_postfilter, + + .releasedir_prefilter =3D (void *)releasedir_prefilter, + .releasedir_postfilter =3D (void *)releasedir_postfilter, + + .flush_prefilter =3D (void *)flush_prefilter, + .flush_postfilter =3D (void *)flush_postfilter, + + .lseek_prefilter =3D (void *)lseek_prefilter, + .lseek_postfilter =3D (void *)lseek_postfilter, + + .copy_file_range_prefilter =3D (void *)copy_file_range_prefilter, + .copy_file_range_postfilter =3D (void *)copy_file_range_postfilter, + + .fsync_prefilter =3D (void *)fsync_prefilter, + .fsync_postfilter =3D (void *)fsync_postfilter, + + .dir_fsync_prefilter =3D (void *)dir_fsync_prefilter, + .dir_fsync_postfilter =3D (void *)dir_fsync_postfilter, + + .getxattr_prefilter =3D (void *)getxattr_prefilter, + .getxattr_postfilter =3D (void *)getxattr_postfilter, + + .listxattr_prefilter =3D (void *)listxattr_prefilter, + .listxattr_postfilter =3D (void *)listxattr_postfilter, + + .setxattr_prefilter =3D (void *)setxattr_prefilter, + .setxattr_postfilter =3D (void *)setxattr_postfilter, + + .removexattr_prefilter =3D (void *)removexattr_prefilter, + .removexattr_postfilter =3D (void *)removexattr_postfilter, + + .read_iter_prefilter =3D (void *)read_iter_prefilter, + .read_iter_postfilter =3D (void *)read_iter_postfilter, + + .write_iter_prefilter =3D (void *)write_iter_prefilter, + .write_iter_postfilter =3D (void *)write_iter_postfilter, + + .file_fallocate_prefilter =3D (void *)file_fallocate_prefilter, + .file_fallocate_postfilter =3D (void *)file_fallocate_postfilter, + + .lookup_prefilter =3D (void *)lookup_prefilter, + .lookup_postfilter =3D (void *)lookup_postfilter, + + .mknod_prefilter =3D (void *)mknod_prefilter, + .mknod_postfilter =3D (void *)mknod_postfilter, + + .mkdir_prefilter =3D (void *)mkdir_prefilter, + .mkdir_postfilter =3D (void *)mkdir_postfilter, + + .rmdir_prefilter =3D (void *)rmdir_prefilter, + .rmdir_postfilter =3D (void *)rmdir_postfilter, + + .rename2_prefilter =3D (void *)rename2_prefilter, + .rename2_postfilter =3D (void *)rename2_postfilter, + + .rename_prefilter =3D (void *)rename_prefilter, + .rename_postfilter =3D (void *)rename_postfilter, + + .unlink_prefilter =3D (void *)unlink_prefilter, + .unlink_postfilter =3D (void *)unlink_postfilter, + + .link_prefilter =3D (void *)link_prefilter, + .link_postfilter =3D (void *)link_postfilter, + + .getattr_prefilter =3D (void *)getattr_prefilter, + .getattr_postfilter =3D (void *)getattr_postfilter, + + .setattr_prefilter =3D (void *)setattr_prefilter, + .setattr_postfilter =3D (void *)setattr_postfilter, + + .statfs_prefilter =3D (void *)statfs_prefilter, + .statfs_postfilter =3D (void *)statfs_postfilter, + + .get_link_prefilter =3D (void *)get_link_prefilter, + .get_link_postfilter =3D (void *)get_link_postfilter, + + .symlink_prefilter =3D (void *)symlink_prefilter, + .symlink_postfilter =3D (void *)symlink_postfilter, + + .readdir_prefilter =3D (void *)readdir_prefilter, + .readdir_postfilter =3D (void *)readdir_postfilter, + + .access_prefilter =3D (void *)access_prefilter, + .access_postfilter =3D (void *)access_postfilter, + + .name =3D "trace_pre_ops", +}; --=20 2.40.0.634.g4ca3ef3211-goog